41 import javafx.collections.ObservableList;
42 import javafx.scene.AccessibleRole;
43 import javafx.scene.Group;
44 import javafx.scene.Node;
45 import javafx.scene.layout.StackPane;
46 import javafx.scene.shape.ClosePath;
47 import javafx.scene.shape.LineTo;
48 import javafx.scene.shape.MoveTo;
49 import javafx.scene.shape.Path;
50 import javafx.scene.shape.PathElement;
51 import javafx.scene.shape.StrokeLineJoin;
52 import javafx.util.Duration;
53
54 import com.sun.javafx.charts.Legend.LegendItem;
55 import javafx.css.converter.BooleanConverter;
56 import javafx.beans.property.BooleanProperty;
57 import javafx.css.CssMetaData;
58 import javafx.css.Styleable;
59 import javafx.css.StyleableBooleanProperty;
60 import javafx.css.StyleableProperty;
61
62 /**
63 * AreaChart - Plots the area between the line that connects the data points and
64 * the 0 line on the Y axis.
65 * @since JavaFX 2.0
66 */
67 public class AreaChart<X,Y> extends XYChart<X,Y> {
68
69 // -------------- PRIVATE FIELDS ------------------------------------------
70
71 /** A multiplier for teh Y values that we store for each series, it is used to animate in a new series */
72 private Map<Series<X,Y>, DoubleProperty> seriesYMultiplierMap = new HashMap<>();
73
74 // -------------- PUBLIC PROPERTIES ----------------------------------------
75
76 /**
77 * When true, CSS styleable symbols are created for any data items that don't have a symbol node specified.
78 * @since JavaFX 8.0
79 */
80 private BooleanProperty createSymbols = new StyleableBooleanProperty(true) {
404 @Override protected void seriesRemoved(final Series<X,Y> series) {
405 // remove series Y multiplier
406 seriesYMultiplierMap.remove(series);
407 // remove all symbol nodes
408 if (shouldAnimate()) {
409 Timeline tl = new Timeline(createSeriesRemoveTimeLine(series, 400));
410 tl.play();
411 } else {
412 getPlotChildren().remove(series.getNode());
413 for (Data<X,Y> d:series.getData()) getPlotChildren().remove(d.getNode());
414 removeSeriesFromDisplay(series);
415 }
416 }
417
418 /** {@inheritDoc} */
419 @Override protected void layoutPlotChildren() {
420 List<LineTo> constructedPath = new ArrayList<>(getDataSize());
421 for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) {
422 Series<X, Y> series = getData().get(seriesIndex);
423 DoubleProperty seriesYAnimMultiplier = seriesYMultiplierMap.get(series);
424 double lastX = 0;
425 final ObservableList<Node> children = ((Group) series.getNode()).getChildren();
426 ObservableList<PathElement> seriesLine = ((Path) children.get(1)).getElements();
427 ObservableList<PathElement> fillPath = ((Path) children.get(0)).getElements();
428 seriesLine.clear();
429 fillPath.clear();
430 constructedPath.clear();
431 for (Iterator<Data<X, Y>> it = getDisplayedDataIterator(series); it.hasNext(); ) {
432 Data<X, Y> item = it.next();
433 double x = getXAxis().getDisplayPosition(item.getCurrentX());
434 double y = getYAxis().getDisplayPosition(
435 getYAxis().toRealValue(getYAxis().toNumericValue(item.getCurrentY()) * seriesYAnimMultiplier.getValue()));
436 constructedPath.add(new LineTo(x, y));
437 if (Double.isNaN(x) || Double.isNaN(y)) {
438 continue;
439 }
440 lastX = x;
441 Node symbol = item.getNode();
442 if (symbol != null) {
443 final double w = symbol.prefWidth(-1);
444 final double h = symbol.prefHeight(-1);
445 symbol.resizeRelocate(x-(w/2), y-(h/2),w,h);
446 }
447 }
448
449 if (!constructedPath.isEmpty()) {
450 Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getX(), e2.getX()));
451 LineTo first = constructedPath.get(0);
452
453 final double displayYPos = first.getY();
454 final double numericYPos = getYAxis().toNumericValue(getYAxis().getValueForDisplay(displayYPos));
455
456 // RT-34626: We can't always use getZeroPosition(), as it may be the case
457 // that the zero position of the y-axis is not visible on the chart. In these
458 // cases, we need to use the height between the point and the y-axis line.
459 final double yAxisZeroPos = getYAxis().getZeroPosition();
460 final boolean isYAxisZeroPosVisible = !Double.isNaN(yAxisZeroPos);
461 final double yAxisHeight = getYAxis().getHeight();
462 final double yFillPos = isYAxisZeroPosVisible ? yAxisZeroPos :
463 numericYPos < 0 ? numericYPos - yAxisHeight : yAxisHeight;
464
465 seriesLine.add(new MoveTo(first.getX(), displayYPos));
466 fillPath.add(new MoveTo(first.getX(), yFillPos));
467
468 seriesLine.addAll(constructedPath);
469 fillPath.addAll(constructedPath);
470 fillPath.add(new LineTo(lastX, yFillPos));
471 fillPath.add(new ClosePath());
472 }
473 }
474 }
475
476 private Node createSymbol(Series<X,Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) {
477 Node symbol = item.getNode();
478 // check if symbol has already been created
479 if (symbol == null && getCreateSymbols()) {
480 symbol = new StackPane();
481 symbol.setAccessibleRole(AccessibleRole.TEXT);
482 symbol.setAccessibleRoleDescription("Point");
483 symbol.focusTraversableProperty().bind(Platform.accessibilityActiveProperty());
484 item.setNode(symbol);
485 }
486 // set symbol styles
487 // Note: not sure if we want to add or check, ie be more careful and efficient here
488 if (symbol != null) symbol.getStyleClass().setAll("chart-area-symbol", "series" + seriesIndex, "data" + itemIndex,
489 series.defaultColorStyleClass);
490 return symbol;
491 }
|
41 import javafx.collections.ObservableList;
42 import javafx.scene.AccessibleRole;
43 import javafx.scene.Group;
44 import javafx.scene.Node;
45 import javafx.scene.layout.StackPane;
46 import javafx.scene.shape.ClosePath;
47 import javafx.scene.shape.LineTo;
48 import javafx.scene.shape.MoveTo;
49 import javafx.scene.shape.Path;
50 import javafx.scene.shape.PathElement;
51 import javafx.scene.shape.StrokeLineJoin;
52 import javafx.util.Duration;
53
54 import com.sun.javafx.charts.Legend.LegendItem;
55 import javafx.css.converter.BooleanConverter;
56 import javafx.beans.property.BooleanProperty;
57 import javafx.css.CssMetaData;
58 import javafx.css.Styleable;
59 import javafx.css.StyleableBooleanProperty;
60 import javafx.css.StyleableProperty;
61 import javafx.scene.chart.LineChart.SortingPolicy;
62
63 /**
64 * AreaChart - Plots the area between the line that connects the data points and
65 * the 0 line on the Y axis.
66 * @since JavaFX 2.0
67 */
68 public class AreaChart<X,Y> extends XYChart<X,Y> {
69
70 // -------------- PRIVATE FIELDS ------------------------------------------
71
72 /** A multiplier for teh Y values that we store for each series, it is used to animate in a new series */
73 private Map<Series<X,Y>, DoubleProperty> seriesYMultiplierMap = new HashMap<>();
74
75 // -------------- PUBLIC PROPERTIES ----------------------------------------
76
77 /**
78 * When true, CSS styleable symbols are created for any data items that don't have a symbol node specified.
79 * @since JavaFX 8.0
80 */
81 private BooleanProperty createSymbols = new StyleableBooleanProperty(true) {
405 @Override protected void seriesRemoved(final Series<X,Y> series) {
406 // remove series Y multiplier
407 seriesYMultiplierMap.remove(series);
408 // remove all symbol nodes
409 if (shouldAnimate()) {
410 Timeline tl = new Timeline(createSeriesRemoveTimeLine(series, 400));
411 tl.play();
412 } else {
413 getPlotChildren().remove(series.getNode());
414 for (Data<X,Y> d:series.getData()) getPlotChildren().remove(d.getNode());
415 removeSeriesFromDisplay(series);
416 }
417 }
418
419 /** {@inheritDoc} */
420 @Override protected void layoutPlotChildren() {
421 List<LineTo> constructedPath = new ArrayList<>(getDataSize());
422 for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) {
423 Series<X, Y> series = getData().get(seriesIndex);
424 DoubleProperty seriesYAnimMultiplier = seriesYMultiplierMap.get(series);
425 final ObservableList<Node> children = ((Group) series.getNode()).getChildren();
426 Path fillPath = (Path) children.get(0);
427 Path linePath = (Path) children.get(1);
428 makePaths(this, series, constructedPath, fillPath, linePath,
429 seriesYAnimMultiplier.get(), SortingPolicy.X_AXIS);
430 }
431 }
432
433 static <X,Y> void makePaths(XYChart<X, Y> chart, Series<X, Y> series,
434 List<LineTo> constructedPath,
435 Path fillPath, Path linePath,
436 double yAnimMultiplier, SortingPolicy sortAxis)
437 {
438 final Axis<X> axisX = chart.getXAxis();
439 final Axis<Y> axisY = chart.getYAxis();
440 final double hlw = linePath.getStrokeWidth() / 2.0;
441 final boolean sortX = (sortAxis == SortingPolicy.X_AXIS);
442 final boolean sortY = (sortAxis == SortingPolicy.Y_AXIS);
443 final double dataXMin = sortX ? -hlw : Double.NEGATIVE_INFINITY;
444 final double dataXMax = sortX ? axisX.getWidth() + hlw : Double.POSITIVE_INFINITY;
445 final double dataYMin = sortY ? -hlw : Double.NEGATIVE_INFINITY;
446 final double dataYMax = sortY ? axisY.getHeight() + hlw : Double.POSITIVE_INFINITY;
447 LineTo prevDataPoint = null;
448 LineTo nextDataPoint = null;
449 constructedPath.clear();
450 for (Iterator<Data<X, Y>> it = chart.getDisplayedDataIterator(series); it.hasNext(); ) {
451 Data<X, Y> item = it.next();
452 double x = axisX.getDisplayPosition(item.getCurrentX());
453 double y = axisY.getDisplayPosition(
454 axisY.toRealValue(axisY.toNumericValue(item.getCurrentY()) * yAnimMultiplier));
455 boolean skip = (Double.isNaN(x) || Double.isNaN(y));
456 Node symbol = item.getNode();
457 if (symbol != null) {
458 final double w = symbol.prefWidth(-1);
459 final double h = symbol.prefHeight(-1);
460 if (skip) {
461 symbol.resizeRelocate(-w*2, -h*2, w, h);
462 } else {
463 symbol.resizeRelocate(x-(w/2), y-(h/2), w, h);
464 }
465 }
466 if (skip) continue;
467 if (x < dataXMin || y < dataYMin) {
468 if (prevDataPoint == null) {
469 prevDataPoint = new LineTo(x, y);
470 } else if ((sortX && prevDataPoint.getX() <= x) ||
471 (sortY && prevDataPoint.getY() <= y))
472 {
473 prevDataPoint.setX(x);
474 prevDataPoint.setY(y);
475 }
476 } else if (x <= dataXMax && y <= dataYMax) {
477 constructedPath.add(new LineTo(x, y));
478 } else {
479 if (nextDataPoint == null) {
480 nextDataPoint = new LineTo(x, y);
481 } else if ((sortX && x <= nextDataPoint.getX()) ||
482 (sortY && y <= nextDataPoint.getY()))
483 {
484 nextDataPoint.setX(x);
485 nextDataPoint.setY(y);
486 }
487 }
488 }
489
490 if (!constructedPath.isEmpty() || prevDataPoint != null || nextDataPoint != null) {
491 if (sortX) {
492 Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getX(), e2.getX()));
493 } else if (sortY) {
494 Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getY(), e2.getY()));
495 } else {
496 // assert prevDataPoint == null && nextDataPoint == null
497 }
498 if (prevDataPoint != null) {
499 constructedPath.add(0, prevDataPoint);
500 }
501 if (nextDataPoint != null) {
502 constructedPath.add(nextDataPoint);
503 }
504
505 // assert !constructedPath.isEmpty()
506 LineTo first = constructedPath.get(0);
507 LineTo last = constructedPath.get(constructedPath.size()-1);
508
509 final double displayYPos = first.getY();
510
511 ObservableList<PathElement> lineElements = linePath.getElements();
512 lineElements.clear();
513 lineElements.add(new MoveTo(first.getX(), displayYPos));
514 lineElements.addAll(constructedPath);
515
516 if (fillPath != null) {
517 ObservableList<PathElement> fillElements = fillPath.getElements();
518 fillElements.clear();
519 double yOrigin = axisY.getDisplayPosition(axisY.toRealValue(0.0));
520
521 fillElements.add(new MoveTo(first.getX(), yOrigin));
522 fillElements.addAll(constructedPath);
523 fillElements.add(new LineTo(last.getX(), yOrigin));
524 fillElements.add(new ClosePath());
525 }
526 }
527 }
528
529 private Node createSymbol(Series<X,Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) {
530 Node symbol = item.getNode();
531 // check if symbol has already been created
532 if (symbol == null && getCreateSymbols()) {
533 symbol = new StackPane();
534 symbol.setAccessibleRole(AccessibleRole.TEXT);
535 symbol.setAccessibleRoleDescription("Point");
536 symbol.focusTraversableProperty().bind(Platform.accessibilityActiveProperty());
537 item.setNode(symbol);
538 }
539 // set symbol styles
540 // Note: not sure if we want to add or check, ie be more careful and efficient here
541 if (symbol != null) symbol.getStyleClass().setAll("chart-area-symbol", "series" + seriesIndex, "data" + itemIndex,
542 series.defaultColorStyleClass);
543 return symbol;
544 }
|