1 /* 2 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.chart; 27 28 import java.util.*; 29 30 import javafx.animation.FadeTransition; 31 import javafx.animation.Interpolator; 32 import javafx.animation.KeyFrame; 33 import javafx.animation.KeyValue; 34 import javafx.animation.Timeline; 35 import javafx.application.Platform; 36 import javafx.beans.NamedArg; 37 import javafx.beans.property.DoubleProperty; 38 import javafx.beans.property.SimpleDoubleProperty; 39 import javafx.collections.FXCollections; 40 import javafx.collections.ListChangeListener; 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) { 82 @Override protected void invalidated() { 83 for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex ++) { 84 Series<X,Y> series = getData().get(seriesIndex); 85 for (int itemIndex=0; itemIndex < series.getData().size(); itemIndex ++) { 86 Data<X,Y> item = series.getData().get(itemIndex); 87 Node symbol = item.getNode(); 88 if(get() && symbol == null) { // create any symbols 89 symbol = createSymbol(series, getData().indexOf(series), item, itemIndex); 90 if (null != symbol) { 91 getPlotChildren().add(symbol); 92 } 93 } else if (!get() && symbol != null) { // remove symbols 94 getPlotChildren().remove(symbol); 95 symbol = null; 96 item.setNode(null); 97 } 98 } 99 } 100 requestChartLayout(); 101 } 102 103 public Object getBean() { 104 return this; 105 } 106 107 public String getName() { 108 return "createSymbols"; 109 } 110 111 public CssMetaData<AreaChart<?, ?>,Boolean> getCssMetaData() { 112 return StyleableProperties.CREATE_SYMBOLS; 113 } 114 }; 115 116 /** 117 * Indicates whether symbols for data points will be created or not. 118 * 119 * @return true if symbols for data points will be created and false otherwise. 120 * @since JavaFX 8.0 121 */ 122 public final boolean getCreateSymbols() { return createSymbols.getValue(); } 123 public final void setCreateSymbols(boolean value) { createSymbols.setValue(value); } 124 public final BooleanProperty createSymbolsProperty() { return createSymbols; } 125 126 127 // -------------- CONSTRUCTORS ---------------------------------------------- 128 129 /** 130 * Construct a new Area Chart with the given axis 131 * 132 * @param xAxis The x axis to use 133 * @param yAxis The y axis to use 134 */ 135 public AreaChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) { 136 this(xAxis,yAxis, FXCollections.<Series<X,Y>>observableArrayList()); 137 } 138 139 /** 140 * Construct a new Area Chart with the given axis and data 141 * 142 * @param xAxis The x axis to use 143 * @param yAxis The y axis to use 144 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 145 */ 146 public AreaChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X,Y>> data) { 147 super(xAxis,yAxis); 148 setData(data); 149 } 150 151 // -------------- METHODS ------------------------------------------------------------------------------------------ 152 153 private static double doubleValue(Number number) { return doubleValue(number, 0); } 154 private static double doubleValue(Number number, double nullDefault) { 155 return (number == null) ? nullDefault : number.doubleValue(); 156 } 157 158 /** {@inheritDoc} */ 159 @Override protected void updateAxisRange() { 160 final Axis<X> xa = getXAxis(); 161 final Axis<Y> ya = getYAxis(); 162 List<X> xData = null; 163 List<Y> yData = null; 164 if(xa.isAutoRanging()) xData = new ArrayList<X>(); 165 if(ya.isAutoRanging()) yData = new ArrayList<Y>(); 166 if(xData != null || yData != null) { 167 for(Series<X,Y> series : getData()) { 168 for(Data<X,Y> data: series.getData()) { 169 if(xData != null) xData.add(data.getXValue()); 170 if(yData != null) yData.add(data.getYValue()); 171 } 172 } 173 if(xData != null && !(xData.size() == 1 && getXAxis().toNumericValue(xData.get(0)) == 0)) { 174 xa.invalidateRange(xData); 175 } 176 if(yData != null && !(yData.size() == 1 && getYAxis().toNumericValue(yData.get(0)) == 0)) { 177 ya.invalidateRange(yData); 178 } 179 } 180 } 181 182 @Override protected void dataItemAdded(Series<X,Y> series, int itemIndex, Data<X,Y> item) { 183 final Node symbol = createSymbol(series, getData().indexOf(series), item, itemIndex); 184 if (shouldAnimate()) { 185 boolean animate = false; 186 if (itemIndex > 0 && itemIndex < (series.getData().size()-1)) { 187 animate = true; 188 Data<X,Y> p1 = series.getData().get(itemIndex - 1); 189 Data<X,Y> p2 = series.getData().get(itemIndex + 1); 190 double x1 = getXAxis().toNumericValue(p1.getXValue()); 191 double y1 = getYAxis().toNumericValue(p1.getYValue()); 192 double x3 = getXAxis().toNumericValue(p2.getXValue()); 193 double y3 = getYAxis().toNumericValue(p2.getYValue()); 194 195 double x2 = getXAxis().toNumericValue(item.getXValue()); 196 double y2 = getYAxis().toNumericValue(item.getYValue()); 197 198 // //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 199 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 200 item.setCurrentY(getYAxis().toRealValue(y)); 201 item.setCurrentX(getXAxis().toRealValue(x2)); 202 //2. we can simply use the midpoint on the line as well.. 203 // double x = (x3 + x1)/2; 204 // double y = (y3 + y1)/2; 205 // item.setCurrentX(x); 206 // item.setCurrentY(y); 207 } else if (itemIndex == 0 && series.getData().size() > 1) { 208 animate = true; 209 item.setCurrentX(series.getData().get(1).getXValue()); 210 item.setCurrentY(series.getData().get(1).getYValue()); 211 } else if (itemIndex == (series.getData().size() - 1) && series.getData().size() > 1) { 212 animate = true; 213 int last = series.getData().size() - 2; 214 item.setCurrentX(series.getData().get(last).getXValue()); 215 item.setCurrentY(series.getData().get(last).getYValue()); 216 } 217 if (symbol != null) { 218 // fade in new symbol 219 symbol.setOpacity(0); 220 getPlotChildren().add(symbol); 221 FadeTransition ft = new FadeTransition(Duration.millis(500),symbol); 222 ft.setToValue(1); 223 ft.play(); 224 } 225 if (animate) { 226 animate( 227 new KeyFrame(Duration.ZERO, 228 (e) -> { 229 if (symbol != null && !getPlotChildren().contains(symbol)) { 230 getPlotChildren().add(symbol); 231 } }, 232 new KeyValue(item.currentYProperty(), 233 item.getCurrentY()), 234 new KeyValue(item.currentXProperty(), 235 item.getCurrentX()) 236 ), 237 new KeyFrame(Duration.millis(800), new KeyValue(item.currentYProperty(), 238 item.getYValue(), Interpolator.EASE_BOTH), 239 new KeyValue(item.currentXProperty(), 240 item.getXValue(), Interpolator.EASE_BOTH)) 241 ); 242 } 243 244 } else if (symbol != null) { 245 getPlotChildren().add(symbol); 246 } 247 } 248 249 @Override protected void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) { 250 final Node symbol = item.getNode(); 251 252 if (symbol != null) { 253 symbol.focusTraversableProperty().unbind(); 254 } 255 256 // remove item from sorted list 257 int itemIndex = series.getItemIndex(item); 258 if (shouldAnimate()) { 259 boolean animate = false; 260 // dataSize represents size of currently visible data. After this operation, the number will decrement by 1 261 final int dataSize = series.getDataSize(); 262 // This is the size of current data list in Series. Note that it might be totaly different from dataSize as 263 // some big operation might have happened on the list. 264 final int dataListSize = series.getData().size(); 265 if (itemIndex > 0 && itemIndex < dataSize -1) { 266 animate = true; 267 Data<X,Y> p1 = series.getItem(itemIndex - 1); 268 Data<X,Y> p2 = series.getItem(itemIndex + 1); 269 double x1 = getXAxis().toNumericValue(p1.getXValue()); 270 double y1 = getYAxis().toNumericValue(p1.getYValue()); 271 double x3 = getXAxis().toNumericValue(p2.getXValue()); 272 double y3 = getYAxis().toNumericValue(p2.getYValue()); 273 274 double x2 = getXAxis().toNumericValue(item.getXValue()); 275 double y2 = getYAxis().toNumericValue(item.getYValue()); 276 277 // //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 278 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 279 item.setCurrentX(getXAxis().toRealValue(x2)); 280 item.setCurrentY(getYAxis().toRealValue(y2)); 281 item.setXValue(getXAxis().toRealValue(x2)); 282 item.setYValue(getYAxis().toRealValue(y)); 283 //2. we can simply use the midpoint on the line as well.. 284 // double x = (x3 + x1)/2; 285 // double y = (y3 + y1)/2; 286 // item.setCurrentX(x); 287 // item.setCurrentY(y); 288 } else if (itemIndex == 0 && dataListSize > 1) { 289 animate = true; 290 item.setXValue(series.getData().get(0).getXValue()); 291 item.setYValue(series.getData().get(0).getYValue()); 292 } else if (itemIndex == (dataSize - 1) && dataListSize > 1) { 293 animate = true; 294 int last = dataListSize - 1; 295 item.setXValue(series.getData().get(last).getXValue()); 296 item.setYValue(series.getData().get(last).getYValue()); 297 } else if (symbol != null) { 298 // fade out symbol 299 symbol.setOpacity(0); 300 FadeTransition ft = new FadeTransition(Duration.millis(500),symbol); 301 ft.setToValue(0); 302 ft.setOnFinished(actionEvent -> { 303 getPlotChildren().remove(symbol); 304 removeDataItemFromDisplay(series, item); 305 }); 306 ft.play(); 307 } else { 308 item.setSeries(null); 309 removeDataItemFromDisplay(series, item); 310 } 311 if (animate) { 312 animate( new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), 313 item.getCurrentY()), new KeyValue(item.currentXProperty(), 314 item.getCurrentX())), 315 new KeyFrame(Duration.millis(800), actionEvent -> { 316 item.setSeries(null); 317 getPlotChildren().remove(symbol); 318 removeDataItemFromDisplay(series, item); 319 }, 320 new KeyValue(item.currentYProperty(), 321 item.getYValue(), Interpolator.EASE_BOTH), 322 new KeyValue(item.currentXProperty(), 323 item.getXValue(), Interpolator.EASE_BOTH)) 324 ); 325 } 326 } else { 327 item.setSeries(null); 328 getPlotChildren().remove(symbol); 329 removeDataItemFromDisplay(series, item); 330 } 331 //Note: better animation here, point should move from old position to new position at center point between prev and next symbols 332 } 333 334 /** {@inheritDoc} */ 335 @Override protected void dataItemChanged(Data<X, Y> item) { 336 } 337 338 @Override protected void seriesChanged(ListChangeListener.Change<? extends Series> c) { 339 // Update style classes for all series lines and symbols 340 // Note: is there a more efficient way of doing this? 341 for (int i = 0; i < getDataSize(); i++) { 342 final Series<X,Y> s = getData().get(i); 343 Path seriesLine = (Path)((Group)s.getNode()).getChildren().get(1); 344 Path fillPath = (Path)((Group)s.getNode()).getChildren().get(0); 345 seriesLine.getStyleClass().setAll("chart-series-area-line", "series" + i, s.defaultColorStyleClass); 346 fillPath.getStyleClass().setAll("chart-series-area-fill", "series" + i, s.defaultColorStyleClass); 347 for (int j=0; j < s.getData().size(); j++) { 348 final Data<X,Y> item = s.getData().get(j); 349 final Node node = item.getNode(); 350 if(node!=null) node.getStyleClass().setAll("chart-area-symbol", "series" + i, "data" + j, s.defaultColorStyleClass); 351 } 352 } 353 } 354 355 @Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) { 356 // create new paths for series 357 Path seriesLine = new Path(); 358 Path fillPath = new Path(); 359 seriesLine.setStrokeLineJoin(StrokeLineJoin.BEVEL); 360 Group areaGroup = new Group(fillPath,seriesLine); 361 series.setNode(areaGroup); 362 // create series Y multiplier 363 DoubleProperty seriesYAnimMultiplier = new SimpleDoubleProperty(this, "seriesYMultiplier"); 364 seriesYMultiplierMap.put(series, seriesYAnimMultiplier); 365 // handle any data already in series 366 if (shouldAnimate()) { 367 seriesYAnimMultiplier.setValue(0d); 368 } else { 369 seriesYAnimMultiplier.setValue(1d); 370 } 371 getPlotChildren().add(areaGroup); 372 List<KeyFrame> keyFrames = new ArrayList<KeyFrame>(); 373 if (shouldAnimate()) { 374 // animate in new series 375 keyFrames.add(new KeyFrame(Duration.ZERO, 376 new KeyValue(areaGroup.opacityProperty(), 0), 377 new KeyValue(seriesYAnimMultiplier, 0) 378 )); 379 keyFrames.add(new KeyFrame(Duration.millis(200), 380 new KeyValue(areaGroup.opacityProperty(), 1) 381 )); 382 keyFrames.add(new KeyFrame(Duration.millis(500), 383 new KeyValue(seriesYAnimMultiplier, 1) 384 )); 385 } 386 for (int j=0; j<series.getData().size(); j++) { 387 Data<X,Y> item = series.getData().get(j); 388 final Node symbol = createSymbol(series, seriesIndex, item, j); 389 if (symbol != null) { 390 if (shouldAnimate()) { 391 symbol.setOpacity(0); 392 getPlotChildren().add(symbol); 393 // fade in new symbol 394 keyFrames.add(new KeyFrame(Duration.ZERO, new KeyValue(symbol.opacityProperty(), 0))); 395 keyFrames.add(new KeyFrame(Duration.millis(200), new KeyValue(symbol.opacityProperty(), 1))); 396 } 397 else { 398 getPlotChildren().add(symbol); 399 } 400 } 401 } 402 if (shouldAnimate()) animate(keyFrames.toArray(new KeyFrame[keyFrames.size()])); 403 } 404 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 } 545 546 @Override 547 LegendItem createLegendItemForSeries(Series<X, Y> series, int seriesIndex) { 548 LegendItem legendItem = new LegendItem(series.getName()); 549 legendItem.getSymbol().getStyleClass().addAll("chart-area-symbol", "series" + seriesIndex, 550 "area-legend-symbol", series.defaultColorStyleClass); 551 return legendItem; 552 } 553 554 // -------------- STYLESHEET HANDLING -------------------------------------- 555 556 private static class StyleableProperties { 557 private static final CssMetaData<AreaChart<?,?>,Boolean> CREATE_SYMBOLS = 558 new CssMetaData<AreaChart<?,?>,Boolean>("-fx-create-symbols", 559 BooleanConverter.getInstance(), Boolean.TRUE) { 560 561 @Override 562 public boolean isSettable(AreaChart<?,?> node) { 563 return node.createSymbols == null || !node.createSymbols.isBound(); 564 } 565 566 @Override 567 public StyleableProperty<Boolean> getStyleableProperty(AreaChart<?,?> node) { 568 return (StyleableProperty<Boolean>)node.createSymbolsProperty(); 569 } 570 }; 571 572 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 573 static { 574 final List<CssMetaData<? extends Styleable, ?>> styleables = 575 new ArrayList<CssMetaData<? extends Styleable, ?>>(XYChart.getClassCssMetaData()); 576 styleables.add(CREATE_SYMBOLS); 577 STYLEABLES = Collections.unmodifiableList(styleables); 578 } 579 } 580 581 /** 582 * @return The CssMetaData associated with this class, which may include the 583 * CssMetaData of its superclasses. 584 * @since JavaFX 8.0 585 */ 586 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 587 return StyleableProperties.STYLEABLES; 588 } 589 590 /** 591 * {@inheritDoc} 592 * @since JavaFX 8.0 593 */ 594 @Override 595 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 596 return getClassCssMetaData(); 597 } 598 599 }