modules/javafx.controls/src/main/java/javafx/scene/chart/AreaChart.java

Print this page




  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     }