/**
* Sencha GXT 3.0.0 - Sencha for GWT
* Copyright(c) 2007-2012, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
package com.sencha.gxt.chart.client.chart.series;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.canvas.dom.client.Context2d.LineJoin;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.user.client.Event;
import com.sencha.gxt.chart.client.chart.event.SeriesItemOutEvent;
import com.sencha.gxt.chart.client.chart.event.SeriesItemOverEvent;
import com.sencha.gxt.chart.client.draw.Color;
import com.sencha.gxt.chart.client.draw.DrawFx;
import com.sencha.gxt.chart.client.draw.RGB;
import com.sencha.gxt.chart.client.draw.Rotation;
import com.sencha.gxt.chart.client.draw.path.LineTo;
import com.sencha.gxt.chart.client.draw.path.MoveTo;
import com.sencha.gxt.chart.client.draw.path.PathCommand;
import com.sencha.gxt.chart.client.draw.path.PathSprite;
import com.sencha.gxt.chart.client.draw.sprite.RectangleSprite;
import com.sencha.gxt.chart.client.draw.sprite.Sprite;
import com.sencha.gxt.chart.client.draw.sprite.SpriteList;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.core.client.util.PrecisePoint;
import com.sencha.gxt.core.client.util.PreciseRectangle;
import com.sencha.gxt.data.shared.ListStore;
/**
* Creates a Pie Chart. A Pie Chart is a useful visualization technique to
* display quantitative information for different categories that also have a
* meaning as a whole.
*
* @param <M> data type used by this series
*/
public class PieSeries<M> extends AbstractPieSeries<M> {
private Set<Integer> exclude = new HashSet<Integer>();
private ArrayList<ValueProvider<? super M, ? extends Number>> lengthField = new ArrayList<ValueProvider<? super M, ? extends Number>>();
private Map<Integer, Slice> slices = new HashMap<Integer, Slice>();
private Map<Integer, PrecisePoint> labelPoints = new HashMap<Integer, PrecisePoint>();
private Map<Integer, PrecisePoint> middlePoints = new HashMap<Integer, PrecisePoint>();
private Map<Integer, PathSprite> calloutLines = new HashMap<Integer, PathSprite>();
private Map<Integer, RectangleSprite> calloutBoxes = new HashMap<Integer, RectangleSprite>();
private double firstAngle;
private double popOutMargin = 20;
/**
* Creates a pie series.
*/
public PieSeries() {
// setup shadow attributes
shadowAttributes = new ArrayList<Sprite>();
PathSprite config = new PathSprite();
config.setStrokeWidth(6);
config.setStrokeOpacity(1);
config.setStroke(new RGB(200, 200, 200));
config.setTranslation(1.2, 2);
config.setStrokeLineJoin(LineJoin.ROUND);
shadowAttributes.add(config);
config = new PathSprite();
config.setStrokeWidth(4);
config.setStrokeOpacity(1);
config.setStroke(new RGB(150, 150, 150));
config.setTranslation(0.9, 1.5);
config.setStrokeLineJoin(LineJoin.ROUND);
shadowAttributes.add(config);
config = new PathSprite();
config.setStrokeWidth(2);
config.setStrokeOpacity(1);
config.setStroke(new RGB(100, 100, 100));
config.setTranslation(0.6, 1);
config.setStrokeLineJoin(LineJoin.ROUND);
shadowAttributes.add(config);
// initialize the shadow groups
if (shadowGroups.size() == 0) {
for (int i = 0; i < shadowAttributes.size(); i++) {
shadowGroups.add(new SpriteList<Sprite>());
}
}
}
/**
* Adds a {@link ValueProvider} that represents the radius of a pie slice.
*
* @param lengthField the value provider
*/
public void addLengthField(ValueProvider<? super M, ? extends Number> lengthField) {
this.lengthField.add(lengthField);
}
@Override
public void clear() {
super.clear();
for (int i = 0; i < shadowGroups.size(); i++) {
shadowGroups.get(i).clear();
}
}
@Override
public void drawSeries() {
ListStore<M> store = chart.getCurrentStore();
boolean first = true;
int layers = lengthField.size() > 0 ? lengthField.size() : 1;
PreciseRectangle chartBBox = chart.getBBox();
Map<Integer, Slice> oldSlices = slices;
slices = new HashMap<Integer, Slice>();
Map<Integer, Double> layerTotals = new HashMap<Integer, Double>();
Slice slice;
double totalField = 0;
double totalLength = 0;
double maxLength = 0;
List<PathCommand> commands = new ArrayList<PathCommand>();
M model;
double value = 0;
double angle = 0;
double middleAngle = 0;
double endAngle = 0;
double rhoAcum = 0;
if (store == null || store.size() == 0) {
return;
}
center.setX(chartBBox.getX() + (chartBBox.getWidth() / 2));
center.setY(chartBBox.getY() + (chartBBox.getHeight() / 2));
radius = Math.min(center.getX() - chartBBox.getX(), center.getY() - chartBBox.getY());
for (int i = 0; i < store.size(); i++) {
if (exclude.contains(i)) {
continue;
}
model = store.get(i);
totalField += angleField.getValue(model).doubleValue();
if (lengthField.size() > 0) {
totalLength = 0;
for (int j = 0; j < layers; j++) {
totalLength += lengthField.get(j).getValue(model).doubleValue();
}
layerTotals.put(i, totalLength);
maxLength = Math.max(maxLength, totalLength);
}
}
for (int i = 0; i < store.size(); i++) {
if (exclude.contains(i)) {
continue;
}
model = store.get(i);
value = angleField.getValue(model).doubleValue();
middleAngle = angle - 360 * value / totalField / 2;
// First Slice
if (first) {
angle = 360 - middleAngle;
firstAngle = angle;
middleAngle = angle - 360 * value / totalField / 2;
first = false;
}
endAngle = angle - 360 * value / totalField;
slice = new Slice(value, angle, endAngle, radius);
if (angle % 360 == endAngle % 360) {
slice.setStartAngle(angle - 0.0001);
}
if (lengthField.size() > 0) {
slice.setRho(radius * (layerTotals.get(i) / maxLength));
}
if (slices.get(i) == null) {
slices.put(i, slice);
}
angle = endAngle;
}
// do all shadows first.
if (chart.hasShadows()) {
for (int i = 0; i < store.size(); i++) {
if (exclude.contains(i)) {
continue;
}
slice = slices.get(i);
rhoAcum = 0;
for (int j = 0; j < layers; j++) {
double deltaRho = 0;
if (lengthField.size() > 0) {
deltaRho = lengthField.get(j).getValue(store.get(i)).doubleValue() / layerTotals.get(i) * slice.getRho();
} else {
deltaRho = slice.getRho();
}
slice.setMargin(margin);
slice.setStartRho(rhoAcum + (deltaRho * donut / 100));
slice.setEndRho(rhoAcum + deltaRho);
// create shadows
for (int shindex = 0; shindex < shadowGroups.size(); shindex++) {
Sprite shadowAttr = shadowAttributes.get(shindex);
SpriteList<Sprite> shadows = shadowGroups.get(shindex);
final PathSprite shadow;
commands = calculateSegment(slice);
if (i < shadows.size()) {
shadow = (PathSprite) shadows.get(i);
shadow.setHidden(false);
} else {
shadow = (PathSprite) shadowAttr.copy();
shadow.setFill(Color.NONE);
chart.addSprite(shadow);
shadows.add(shadow);
}
if (chart.isAnimated() && oldSlices.get(i) != null) {
createSegmentAnimator(shadow, oldSlices.get(i), slice).run(chart.getAnimationDuration(),
chart.getAnimationEasing());
} else {
shadow.setCommands(commands);
shadow.redraw();
}
if (shadowRenderer != null) {
shadowRenderer.spriteRenderer(shadow, i, chart.getCurrentStore());
}
}
}
}
shadowed = true;
} else {
hideShadows();
}
// do pie slices after
for (int i = 0; i < store.size(); i++) {
if (exclude.contains(i)) {
continue;
}
slice = slices.get(i);
rhoAcum = 0;
for (int j = 0; j < layers; j++) {
double deltaRho = 0;
if (lengthField.size() > 0) {
deltaRho = lengthField.get(j).getValue(store.get(i)).doubleValue() / layerTotals.get(i) * slice.getRho();
} else {
deltaRho = slice.getRho();
}
final PathSprite sprite;
int index = i * layers + j;
if (sprites.get(index) != null) {
sprite = (PathSprite) sprites.get(index);
sprite.setHidden(false);
} else {
// Create a new sprite if needed (no height)
sprite = new PathSprite();
sprite.setFill(getColor(i));
sprite.setZIndex(10);
sprites.add(sprite);
chart.addSprite(sprite);
}
if (stroke != null) {
sprite.setStroke(stroke);
}
if (!Double.isNaN(strokeWidth)) {
sprite.setStrokeWidth(strokeWidth);
}
slice.setMargin(margin);
slice.setStartRho(rhoAcum + (deltaRho * donut / 100));
slice.setEndRho(rhoAcum + deltaRho);
if (labelConfig != null) {
calculateMiddle(slice, i);
}
if (chart.isAnimated() && oldSlices.get(i) != null) {
createSegmentAnimator(sprite, oldSlices.get(i), slice).run(chart.getAnimationDuration(),
chart.getAnimationEasing());
} else {
sprite.setCommands(calculateSegment(slice));
sprite.redraw();
}
if (renderer != null) {
renderer.spriteRenderer(sprite, i, store);
}
rhoAcum += deltaRho;
}
}
for (int j = (slices.size() + exclude.size()) * layers; j < sprites.size(); j++) {
sprites.get(j).setHidden(true);
}
if (chart.hasShadows()) {
for (int j = 0; j < shadowGroups.size(); j++) {
SpriteList<Sprite> shadows = shadowGroups.get(j);
for (int k = slices.size() + exclude.size(); k < shadows.size(); k++) {
shadows.get(k).setHidden(true);
}
}
}
for (int j = slices.size() + exclude.size(); j < labels.size(); j++) {
labels.get(j).setHidden(true);
}
for (int j = slices.size() + exclude.size(); j < calloutLines.size(); j++) {
calloutLines.get(j).setHidden(true);
}
for (int j = slices.size() + exclude.size(); j < calloutBoxes.size(); j++) {
calloutBoxes.get(j).setHidden(true);
}
drawLabels();
}
/**
* Returns the list of value providers that represent the radius of pie
* slices.
*
* @return the list
*/
public ArrayList<ValueProvider<? super M, ? extends Number>> getLengthFields() {
return lengthField;
}
/**
* Returns the margin that the slices pop out.
*
* @return the margin that the slices pop out
*/
public double getPopOutMargin() {
return popOutMargin;
}
@Override
public void hide(int yFieldIndex) {
sprites.get(yFieldIndex).setHidden(true);
sprites.get(yFieldIndex).redraw();
for (int i = 0; i < shadowGroups.size(); i++) {
SpriteList<Sprite> shadows = shadowGroups.get(i);
if (shadows.get(yFieldIndex) != null) {
shadows.get(yFieldIndex).setHidden(true);
shadows.get(yFieldIndex).redraw();
}
}
if (labelConfig != null) {
labels.get(yFieldIndex).setHidden(true);
labels.get(yFieldIndex).redraw();
if (labelConfig.getLabelPosition() == LabelPosition.OUTSIDE) {
calloutLines.get(yFieldIndex).setHidden(true);
calloutLines.get(yFieldIndex).redraw();
calloutBoxes.get(yFieldIndex).setHidden(true);
calloutBoxes.get(yFieldIndex).redraw();
}
}
exclude.add(yFieldIndex);
drawSeries();
}
@Override
public void highlight(int yFieldIndex) {
int layers = lengthField.size() > 0 ? lengthField.size() : 1;
int spriteIndex = (yFieldIndex * layers) + (layers - 1);
if (popOutMargin > 0) {
Slice slice = new Slice(slices.get(yFieldIndex));
slice.setMargin(popOutMargin);
double labelX = 0;
double labelY = 0;
if (labels.size() > yFieldIndex && calloutBoxes.size() == 0) {
double middle = Math.toRadians((slice.startAngle + slice.endAngle) / 2.0);
PrecisePoint trans = labelPoints.get(yFieldIndex);
labelX = popOutMargin * Math.cos(middle) + trans.getX();
labelY = popOutMargin * Math.sin(middle) + trans.getY();
if (Math.abs(labelX) < 1e-10) {
labelX = 0;
}
if (Math.abs(labelY) < 1e-10) {
labelY = 0;
}
if (chart.isAnimated()) {
DrawFx.createTranslationAnimator(labels.get(yFieldIndex), labelX, labelY).run(chart.getAnimationDuration(),
chart.getAnimationEasing());
} else {
labels.get(yFieldIndex).setTranslation(labelX, labelY);
labels.get(yFieldIndex).redraw();
}
}
if (chart.isAnimated()) {
createSegmentAnimator((PathSprite) sprites.get(spriteIndex), slices.get(yFieldIndex), slice).run(
chart.getAnimationDuration(), chart.getAnimationEasing());
for (int i = 0; i < shadowGroups.size(); i++) {
SpriteList<Sprite> shadows = shadowGroups.get(i);
if (shadows.get(yFieldIndex) != null) {
createSegmentAnimator((PathSprite) shadows.get(yFieldIndex), slices.get(yFieldIndex), slice).run(
chart.getAnimationDuration(), chart.getAnimationEasing());
}
}
} else {
List<PathCommand> commands = calculateSegment(slice);
((PathSprite) sprites.get(spriteIndex)).setCommands(commands);
sprites.get(spriteIndex).redraw();
for (int i = 0; i < shadowGroups.size(); i++) {
SpriteList<Sprite> shadows = shadowGroups.get(i);
if (shadows.get(yFieldIndex) != null) {
PathSprite shadow = (PathSprite) shadows.get(yFieldIndex);
shadow.setCommands(commands);
shadow.redraw();
}
}
}
}
if (highlighter != null) {
highlighter.highlight(sprites.get(spriteIndex));
for (int i = 0; i < shadowGroups.size(); i++) {
SpriteList<Sprite> shadows = shadowGroups.get(i);
if (shadows.get(yFieldIndex) != null) {
highlighter.highlight(shadows.get(yFieldIndex));
}
}
}
}
@Override
public void highlightAll(int index) {
highlight(index);
}
@Override
public int onMouseMove(PrecisePoint point, Event event) {
if (handlerManager != null || highlighting) {
if (lastHighlighted == -1) {
int length = sprites.size();
for (int i = 0; i < length; i++) {
if (exclude.contains(i)) {
continue;
}
Slice slice = slices.get(i);
if (slice != null) {
double dx = Math.abs(point.getX() - center.getX());
double dy = Math.abs(point.getY() - center.getY());
double rho = Math.sqrt(dx * dx + dy * dy);
double angle = Math.toDegrees(Math.atan2(point.getY() - center.getY(), point.getX() - center.getX())) + 360;
if (angle > firstAngle) {
angle -= 360;
}
if (angle <= slice.getStartAngle() && angle > slice.getEndAngle() && rho >= slice.getStartRho()
&& rho <= slice.getEndRho()) {
ensureHandlers().fireEvent(
new SeriesItemOverEvent<M>(chart.getCurrentStore().get(i), getValueProvider(i), i, event));
if (toolTip != null) {
if (toolTipConfig.getLabelProvider() != null) {
toolTipConfig.setBodyText(toolTipConfig.getLabelProvider().getLabel(chart.getCurrentStore().get(i),
getValueProvider(i)));
}
toolTip.update(toolTipConfig);
toolTip.showAt(
(int) Math.round(point.getX() + chart.getAbsoluteLeft() + toolTipConfig.getMouseOffset()[0]),
(int) Math.round(point.getY() + chart.getAbsoluteTop() + toolTipConfig.getMouseOffset()[1]));
}
if (highlighting) {
highlight(i);
lastHighlighted = i;
}
return i;
}
}
}
} else {
Slice slice = slices.get(lastHighlighted);
if (slice != null) {
double dx = Math.abs(point.getX() - center.getX());
double dy = Math.abs(point.getY() - center.getY());
double rho = Math.sqrt(dx * dx + dy * dy);
double angle = Math.toDegrees(Math.atan2(point.getY() - center.getY(), point.getX() - center.getX())) + 360;
if (angle > firstAngle) {
angle -= 360;
}
if (!(angle <= slice.getStartAngle() && angle > slice.getEndAngle() && rho >= slice.getStartRho() && rho <= slice.getEndRho())) {
ensureHandlers().fireEvent(
new SeriesItemOutEvent<M>(chart.getCurrentStore().get(getStoreIndex(lastHighlighted)),
getValueProvider(lastHighlighted), lastHighlighted, event));
if (highlighting) {
unHighlight(lastHighlighted);
lastHighlighted = -1;
}
}
}
}
}
return -1;
}
/**
* Sets the margin that the slices pop out.
*
* @param popOutMargin the margin that the slices pop out
*/
public void setPopOutMargin(double popOutMargin) {
this.popOutMargin = popOutMargin;
}
@Override
public void show(int yFieldIndex) {
sprites.get(yFieldIndex).setHidden(false);
sprites.get(yFieldIndex).redraw();
if (chart.hasShadows()) {
for (int i = 0; i < shadowGroups.size(); i++) {
SpriteList<Sprite> shadows = shadowGroups.get(i);
if (shadows.get(yFieldIndex) != null) {
shadows.get(yFieldIndex).setHidden(false);
shadows.get(yFieldIndex).redraw();
}
}
}
exclude.remove(yFieldIndex);
if (labelConfig != null) {
labels.get(yFieldIndex).setHidden(false);
labels.get(yFieldIndex).redraw();
if (labelConfig.getLabelPosition() == LabelPosition.OUTSIDE) {
calloutLines.get(yFieldIndex).setHidden(false);
calloutLines.get(yFieldIndex).redraw();
calloutBoxes.get(yFieldIndex).setHidden(false);
calloutBoxes.get(yFieldIndex).redraw();
}
}
drawSeries();
}
@Override
public void unHighlight(int yFieldIndex) {
int layers = lengthField.size() > 0 ? lengthField.size() : 1;
int spriteIndex = (yFieldIndex * layers) + (layers - 1);
if (popOutMargin > 0) {
Slice slice = new Slice(slices.get(yFieldIndex));
slice.setMargin(margin);
double labelX = 0;
double labelY = 0;
if (labels.size() > yFieldIndex && calloutBoxes.size() == 0) {
double middle = Math.toRadians((slice.startAngle + slice.endAngle) / 2.0);
labelX = margin * Math.cos(middle) + labelPoints.get(yFieldIndex).getX();
labelY = margin * Math.sin(middle) + labelPoints.get(yFieldIndex).getY();
if (Math.abs(labelX) < 1e-10) {
labelX = 0;
}
if (Math.abs(labelY) < 1e-10) {
labelY = 0;
}
if (chart.isAnimated()) {
DrawFx.createTranslationAnimator(labels.get(yFieldIndex), labelX, labelY).run(chart.getAnimationDuration(),
chart.getAnimationEasing());
} else {
labels.get(yFieldIndex).setTranslation(labelX, labelY);
labels.get(yFieldIndex).redraw();
}
}
if (chart.isAnimated()) {
Slice startSlice = new Slice(slices.get(yFieldIndex));
startSlice.setMargin(popOutMargin);
createSegmentAnimator((PathSprite) sprites.get(spriteIndex), startSlice, slice).run(
chart.getAnimationDuration(), chart.getAnimationEasing());
for (int i = 0; i < shadowGroups.size(); i++) {
SpriteList<Sprite> shadows = shadowGroups.get(i);
if (shadows.get(yFieldIndex) != null) {
createSegmentAnimator((PathSprite) shadows.get(yFieldIndex), startSlice, slice).run(
chart.getAnimationDuration(), chart.getAnimationEasing());
}
}
} else {
List<PathCommand> commands = calculateSegment(slices.get(yFieldIndex));
((PathSprite) sprites.get(spriteIndex)).setCommands(commands);
sprites.get(spriteIndex).redraw();
for (int i = 0; i < shadowGroups.size(); i++) {
SpriteList<Sprite> shadows = shadowGroups.get(i);
if (shadows.get(yFieldIndex) != null) {
PathSprite shadow = (PathSprite) shadows.get(yFieldIndex);
shadow.setCommands(commands);
shadow.redraw();
}
}
}
}
if (highlighter != null) {
highlighter.unHighlight(sprites.get(spriteIndex));
}
}
@Override
public void unHighlightAll(int index) {
unHighlight(index);
}
@Override
public boolean visibleInLegend(int index) {
if (exclude.contains(index)) {
return false;
}
return true;
}
@Override
protected int getIndex(PrecisePoint point) {
for (int i = 0; i < slices.size(); i++) {
if (exclude.contains(i)) {
continue;
}
Slice slice = slices.get(i);
if (slice != null) {
double dx = Math.abs(point.getX() - center.getX());
double dy = Math.abs(point.getY() - center.getY());
double rho = Math.sqrt(dx * dx + dy * dy);
double angle = Math.toDegrees(Math.atan2(point.getY() - center.getY(), point.getX() - center.getX())) + 360;
if (angle > firstAngle) {
angle -= 360;
}
if (angle <= slice.getStartAngle() && angle > slice.getEndAngle() && rho >= slice.getStartRho()
&& rho <= slice.getEndRho()) {
return i;
}
}
}
return -1;
}
@Override
protected ValueProvider<? super M, ? extends Number> getValueProvider(int index) {
return angleField;
}
/**
* Utility function to calculate the middle point of a pie slice.
*
* @param slice the pie slice
*/
private void calculateMiddle(Slice slice, int index) {
double a1 = Math.toRadians(Math.min(slice.getStartAngle(), slice.getEndAngle()));
double a2 = Math.toRadians(Math.max(slice.getStartAngle(), slice.getEndAngle()));
double midAngle = -(a1 + (a2 - a1) / 2.0);
double xm = center.getX() + (slice.getEndRho() + slice.getStartRho()) / 2.0 * Math.cos(midAngle);
double ym = center.getY() - (slice.getEndRho() + slice.getStartRho()) / 2.0 * Math.sin(midAngle);
middlePoints.put(index, new PrecisePoint(xm, ym));
}
/**
* Draws the labels of the series.
*/
private void drawLabels() {
if (labelConfig != null) {
double previousDegrees = Double.NaN;
for (int i = 0; i < chart.getCurrentStore().size(); i++) {
if (exclude.contains(i)) {
continue;
}
final Sprite sprite;
if (labels.get(i) != null) {
sprite = labels.get(i);
sprite.setHidden(false);
} else {
sprite = labelConfig.getSpriteConfig().copy();
labels.put(i, sprite);
chart.addSprite(sprite);
}
setLabelText(sprite, i);
PrecisePoint middle = middlePoints.get(i);
double x = middle.getX() - center.getX();
double y = middle.getY() - center.getY();
double rho = 1;
double theta = Math.atan2(y, x == 0 ? 1 : x);
double degrees = Math.toDegrees(theta);
LabelPosition labelPosition = labelConfig.getLabelPosition();
if (labelPosition == LabelPosition.OUTSIDE) {
Slice slice = slices.get(i);
rho = slice.getEndRho() + 20;
double rhoCenter = (slice.endRho + slice.startRho) / 2.0 + (slice.endRho - slice.startRho) / 3.0;
PrecisePoint calloutPoint = new PrecisePoint(rho * Math.cos(theta) + center.getX(), rho * Math.sin(theta)
+ center.getY());
x = rhoCenter * Math.cos(theta);
y = rhoCenter * Math.sin(theta);
final PathSprite line;
if (calloutLines.get(i) != null) {
line = calloutLines.get(i);
line.setHidden(false);
} else {
line = new PathSprite();
line.setStrokeWidth(1);
line.setStroke(RGB.BLACK);
line.setFill(Color.NONE);
calloutLines.put(i, line);
chart.addSprite(line);
}
final RectangleSprite box;
if (calloutBoxes.get(i) != null) {
box = calloutBoxes.get(i);
box.setHidden(false);
} else {
box = new RectangleSprite();
box.setStroke(RGB.BLACK);
box.setStrokeWidth(1);
box.setFill(Color.NONE);
calloutBoxes.put(i, box);
chart.addSprite(box);
}
sprite.redraw();
PreciseRectangle bbox = sprite.getBBox();
List<PathCommand> commands = new ArrayList<PathCommand>();
commands.add(new MoveTo(x + center.getX(), y + center.getY()));
commands.add(new LineTo(calloutPoint.getX(), calloutPoint.getY()));
commands.add(new LineTo(x > 0 ? 10 : -10, 0, true));
PreciseRectangle rect = new PreciseRectangle(calloutPoint.getX() + (x > 0 ? 10 : -(bbox.getWidth() + 30)),
calloutPoint.getY() + (y > 0 ? (-(bbox.getHeight() - 5)) : (-(bbox.getHeight() - 5))),
bbox.getWidth() + 20, bbox.getHeight() + 20);
PrecisePoint labelPoint = new PrecisePoint(calloutPoint.getX() + (x > 0 ? 20 : -(20 + bbox.getWidth())),
calloutPoint.getY() + (y > 0 ? -bbox.getHeight() / 4.0 : -bbox.getHeight() / 4.0));
if (chart.isAnimated() && line.size() > 0 && !Double.isNaN(box.getX()) && sprite.getTranslation() != null) {
DrawFx.createCommandsAnimator(line, commands).run(chart.getAnimationDuration(), chart.getAnimationEasing());
DrawFx.createRectangleAnimator(box, rect).run(chart.getAnimationDuration(), chart.getAnimationEasing());
DrawFx.createTranslationAnimator(sprite, labelPoint.getX(), labelPoint.getY()).run(
chart.getAnimationDuration(), chart.getAnimationEasing());
} else {
line.setCommands(commands);
line.redraw();
box.setX(rect.getX());
box.setY(rect.getY());
box.setWidth(rect.getWidth());
box.setHeight(rect.getHeight());
box.redraw();
sprite.setTranslation(labelPoint.getX(), labelPoint.getY());
sprite.redraw();
}
} else if (labelPosition == LabelPosition.END) {
rho = Math.sqrt(x * x + y * y) * 2.0;
x = rho * Math.cos(theta) + center.getX();
y = rho * Math.sin(theta) + center.getY();
if (chart.isAnimated() && sprite.getTranslation() != null) {
DrawFx.createTranslationAnimator(sprite, x, y).run(chart.getAnimationDuration(), chart.getAnimationEasing());
} else {
sprite.setTranslation(x, y);
}
labelPoints.put(i, new PrecisePoint(x, y));
} else if (labelPosition == LabelPosition.START) {
degrees = fixAngle(degrees);
if (degrees > 90 && degrees < 270) {
degrees += 180;
}
if (!Double.isNaN(previousDegrees) && Math.abs(previousDegrees - degrees) > 180) {
if (degrees > previousDegrees) {
degrees -= 360;
} else {
degrees += 360;
}
degrees %= 360;
} else {
degrees = fixAngle(degrees);
}
if (labelConfig.isLabelContrast()) {
final Sprite back = sprites.get(i);
if (chart.isAnimated()) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
setLabelContrast(sprite, labelConfig, back);
}
});
} else {
setLabelContrast(sprite, labelConfig, back);
}
}
labelPoints.put(i, new PrecisePoint(middle));
if (chart.isAnimated() && sprite.getTranslation() != null) {
DrawFx.createTranslationAnimator(sprite, middle.getX(), middle.getY()).run(chart.getAnimationDuration(),
chart.getAnimationEasing());
DrawFx.createRotationAnimator(sprite, 0, 0, degrees).run(chart.getAnimationDuration(),
chart.getAnimationEasing());
} else {
sprite.setTranslation(middle.getX(), middle.getY());
sprite.setRotation(new Rotation(degrees));
}
previousDegrees = degrees;
}
sprite.redraw();
}
}
}
/**
* Fixes the given angle 360 degrees.
*
* @param angle the angle to be fixed
* @return the fixed angle
*/
private double fixAngle(double angle) {
if (angle < 0) {
angle += 360;
}
return Math.round(angle) % 360;
}
}