1 /* 2 * Copyright (c) 2010, 2015, 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; 55 import com.sun.javafx.charts.Legend.LegendItem; 56 import com.sun.javafx.css.converters.BooleanConverter; 57 import javafx.beans.property.BooleanProperty; 58 import javafx.css.CssMetaData; 59 import javafx.css.Styleable; 60 import javafx.css.StyleableBooleanProperty; 61 import javafx.css.StyleableProperty; 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 private Legend legend = new Legend(); 75 76 // -------------- PUBLIC PROPERTIES ---------------------------------------- 77 78 /** 79 * When true, CSS styleable symbols are created for any data items that don't have a symbol node specified. 80 * @since JavaFX 8.0 81 */ 82 private BooleanProperty createSymbols = new StyleableBooleanProperty(true) { 83 @Override protected void invalidated() { 84 for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex ++) { 85 Series<X,Y> series = getData().get(seriesIndex); 86 for (int itemIndex=0; itemIndex < series.getData().size(); itemIndex ++) { 87 Data<X,Y> item = series.getData().get(itemIndex); 88 Node symbol = item.getNode(); 89 if(get() && symbol == null) { // create any symbols 90 symbol = createSymbol(series, getData().indexOf(series), item, itemIndex); 91 if (null != symbol) { 92 getPlotChildren().add(symbol); 93 } 94 } else if (!get() && symbol != null) { // remove symbols 95 getPlotChildren().remove(symbol); 96 symbol = null; 97 item.setNode(null); 98 } 99 } 100 } 101 requestChartLayout(); 102 } 103 104 public Object getBean() { 105 return this; 106 } 107 108 public String getName() { 109 return "createSymbols"; 110 } 111 112 public CssMetaData<AreaChart<?, ?>,Boolean> getCssMetaData() { 113 return StyleableProperties.CREATE_SYMBOLS; 114 } 115 }; 116 117 /** 118 * Indicates whether symbols for data points will be created or not. 119 * 120 * @return true if symbols for data points will be created and false otherwise. 121 * @since JavaFX 8.0 122 */ 123 public final boolean getCreateSymbols() { return createSymbols.getValue(); } 124 public final void setCreateSymbols(boolean value) { createSymbols.setValue(value); } 125 public final BooleanProperty createSymbolsProperty() { return createSymbols; } 126 127 128 // -------------- CONSTRUCTORS ---------------------------------------------- 129 130 /** 131 * Construct a new Area Chart with the given axis 132 * 133 * @param xAxis The x axis to use 134 * @param yAxis The y axis to use 135 */ 136 public AreaChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) { 137 this(xAxis,yAxis, FXCollections.<Series<X,Y>>observableArrayList()); 138 } 139 140 /** 141 * Construct a new Area Chart with the given axis and data 142 * 143 * @param xAxis The x axis to use 144 * @param yAxis The y axis to use 145 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 146 */ 147 public AreaChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X,Y>> data) { 148 super(xAxis,yAxis); 149 setLegend(legend); 150 setData(data); 151 } 152 153 // -------------- METHODS ------------------------------------------------------------------------------------------ 154 155 private static double doubleValue(Number number) { return doubleValue(number, 0); } 156 private static double doubleValue(Number number, double nullDefault) { 157 return (number == null) ? nullDefault : number.doubleValue(); 158 } 159 160 /** @inheritDoc */ 161 @Override protected void updateAxisRange() { 162 final Axis<X> xa = getXAxis(); 163 final Axis<Y> ya = getYAxis(); 164 List<X> xData = null; 165 List<Y> yData = null; 166 if(xa.isAutoRanging()) xData = new ArrayList<X>(); 167 if(ya.isAutoRanging()) yData = new ArrayList<Y>(); 168 if(xData != null || yData != null) { 169 for(Series<X,Y> series : getData()) { 170 for(Data<X,Y> data: series.getData()) { 171 if(xData != null) xData.add(data.getXValue()); 172 if(yData != null) yData.add(data.getYValue()); 173 } 174 } 175 if(xData != null && !(xData.size() == 1 && getXAxis().toNumericValue(xData.get(0)) == 0)) { 176 xa.invalidateRange(xData); 177 } 178 if(yData != null && !(yData.size() == 1 && getYAxis().toNumericValue(yData.get(0)) == 0)) { 179 ya.invalidateRange(yData); 180 } 181 } 182 } 183 184 @Override protected void dataItemAdded(Series<X,Y> series, int itemIndex, Data<X,Y> item) { 185 final Node symbol = createSymbol(series, getData().indexOf(series), item, itemIndex); 186 if (shouldAnimate()) { 187 boolean animate = false; 188 if (itemIndex > 0 && itemIndex < (series.getData().size()-1)) { 189 animate = true; 190 Data<X,Y> p1 = series.getData().get(itemIndex - 1); 191 Data<X,Y> p2 = series.getData().get(itemIndex + 1); 192 double x1 = getXAxis().toNumericValue(p1.getXValue()); 193 double y1 = getYAxis().toNumericValue(p1.getYValue()); 194 double x3 = getXAxis().toNumericValue(p2.getXValue()); 195 double y3 = getYAxis().toNumericValue(p2.getYValue()); 196 197 double x2 = getXAxis().toNumericValue(item.getXValue()); 198 double y2 = getYAxis().toNumericValue(item.getYValue()); 199 200 // //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 201 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 202 item.setCurrentY(getYAxis().toRealValue(y)); 203 item.setCurrentX(getXAxis().toRealValue(x2)); 204 //2. we can simply use the midpoint on the line as well.. 205 // double x = (x3 + x1)/2; 206 // double y = (y3 + y1)/2; 207 // item.setCurrentX(x); 208 // item.setCurrentY(y); 209 } else if (itemIndex == 0 && series.getData().size() > 1) { 210 animate = true; 211 item.setCurrentX(series.getData().get(1).getXValue()); 212 item.setCurrentY(series.getData().get(1).getYValue()); 213 } else if (itemIndex == (series.getData().size() - 1) && series.getData().size() > 1) { 214 animate = true; 215 int last = series.getData().size() - 2; 216 item.setCurrentX(series.getData().get(last).getXValue()); 217 item.setCurrentY(series.getData().get(last).getYValue()); 218 } 219 if (symbol != null) { 220 // fade in new symbol 221 symbol.setOpacity(0); 222 getPlotChildren().add(symbol); 223 FadeTransition ft = new FadeTransition(Duration.millis(500),symbol); 224 ft.setToValue(1); 225 ft.play(); 226 } 227 if (animate) { 228 animate( 229 new KeyFrame(Duration.ZERO, 230 (e) -> { 231 if (symbol != null && !getPlotChildren().contains(symbol)) { 232 getPlotChildren().add(symbol); 233 } }, 234 new KeyValue(item.currentYProperty(), 235 item.getCurrentY()), 236 new KeyValue(item.currentXProperty(), 237 item.getCurrentX()) 238 ), 239 new KeyFrame(Duration.millis(800), new KeyValue(item.currentYProperty(), 240 item.getYValue(), Interpolator.EASE_BOTH), 241 new KeyValue(item.currentXProperty(), 242 item.getXValue(), Interpolator.EASE_BOTH)) 243 ); 244 } 245 246 } else if (symbol != null) { 247 getPlotChildren().add(symbol); 248 } 249 } 250 251 @Override protected void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) { 252 final Node symbol = item.getNode(); 253 // remove item from sorted list 254 int itemIndex = series.getItemIndex(item); 255 if (shouldAnimate()) { 256 boolean animate = false; 257 // dataSize represents size of currently visible data. After this operation, the number will decrement by 1 258 final int dataSize = series.getDataSize(); 259 // This is the size of current data list in Series. Note that it might be totaly different from dataSize as 260 // some big operation might have happened on the list. 261 final int dataListSize = series.getData().size(); 262 if (itemIndex > 0 && itemIndex < dataSize -1) { 263 animate = true; 264 Data<X,Y> p1 = series.getItem(itemIndex - 1); 265 Data<X,Y> p2 = series.getItem(itemIndex + 1); 266 double x1 = getXAxis().toNumericValue(p1.getXValue()); 267 double y1 = getYAxis().toNumericValue(p1.getYValue()); 268 double x3 = getXAxis().toNumericValue(p2.getXValue()); 269 double y3 = getYAxis().toNumericValue(p2.getYValue()); 270 271 double x2 = getXAxis().toNumericValue(item.getXValue()); 272 double y2 = getYAxis().toNumericValue(item.getYValue()); 273 274 // //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 275 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 276 item.setCurrentX(getXAxis().toRealValue(x2)); 277 item.setCurrentY(getYAxis().toRealValue(y2)); 278 item.setXValue(getXAxis().toRealValue(x2)); 279 item.setYValue(getYAxis().toRealValue(y)); 280 //2. we can simply use the midpoint on the line as well.. 281 // double x = (x3 + x1)/2; 282 // double y = (y3 + y1)/2; 283 // item.setCurrentX(x); 284 // item.setCurrentY(y); 285 } else { 286 if (itemIndex == 0 && dataListSize > 1) { 287 animate = true; 288 item.setXValue(series.getData().get(0).getXValue()); 289 item.setYValue(series.getData().get(0).getYValue()); 290 } else if (itemIndex == (dataSize - 1) && dataListSize > 1) { 291 animate = true; 292 int last = dataListSize - 1; 293 item.setXValue(series.getData().get(last).getXValue()); 294 item.setYValue(series.getData().get(last).getYValue()); 295 } else { 296 // fade out symbol 297 symbol.setOpacity(0); 298 FadeTransition ft = new FadeTransition(Duration.millis(500),symbol); 299 ft.setToValue(0); 300 ft.setOnFinished(actionEvent -> { 301 getPlotChildren().remove(symbol); 302 removeDataItemFromDisplay(series, item); 303 }); 304 ft.play(); 305 } 306 } 307 if (animate) { 308 animate( new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), 309 item.getCurrentY()), new KeyValue(item.currentXProperty(), 310 item.getCurrentX())), 311 new KeyFrame(Duration.millis(800), actionEvent -> { 312 item.setSeries(null); 313 getPlotChildren().remove(symbol); 314 removeDataItemFromDisplay(series, item); 315 }, 316 new KeyValue(item.currentYProperty(), 317 item.getYValue(), Interpolator.EASE_BOTH), 318 new KeyValue(item.currentXProperty(), 319 item.getXValue(), Interpolator.EASE_BOTH)) 320 ); 321 } 322 } else { 323 item.setSeries(null); 324 getPlotChildren().remove(symbol); 325 removeDataItemFromDisplay(series, item); 326 } 327 //Note: better animation here, point should move from old position to new position at center point between prev and next symbols 328 } 329 330 /** @inheritDoc */ 331 @Override protected void dataItemChanged(Data<X, Y> item) { 332 } 333 334 @Override protected void seriesChanged(ListChangeListener.Change<? extends Series> c) { 335 // Update style classes for all series lines and symbols 336 // Note: is there a more efficient way of doing this? 337 for (int i = 0; i < getDataSize(); i++) { 338 final Series<X,Y> s = getData().get(i); 339 Path seriesLine = (Path)((Group)s.getNode()).getChildren().get(1); 340 Path fillPath = (Path)((Group)s.getNode()).getChildren().get(0); 341 seriesLine.getStyleClass().setAll("chart-series-area-line", "series" + i, s.defaultColorStyleClass); 342 fillPath.getStyleClass().setAll("chart-series-area-fill", "series" + i, s.defaultColorStyleClass); 343 for (int j=0; j < s.getData().size(); j++) { 344 final Data<X,Y> item = s.getData().get(j); 345 final Node node = item.getNode(); 346 if(node!=null) node.getStyleClass().setAll("chart-area-symbol", "series" + i, "data" + j, s.defaultColorStyleClass); 347 } 348 } 349 } 350 351 @Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) { 352 // create new paths for series 353 Path seriesLine = new Path(); 354 Path fillPath = new Path(); 355 seriesLine.setStrokeLineJoin(StrokeLineJoin.BEVEL); 356 Group areaGroup = new Group(fillPath,seriesLine); 357 series.setNode(areaGroup); 358 // create series Y multiplier 359 DoubleProperty seriesYAnimMultiplier = new SimpleDoubleProperty(this, "seriesYMultiplier"); 360 seriesYMultiplierMap.put(series, seriesYAnimMultiplier); 361 // handle any data already in series 362 if (shouldAnimate()) { 363 seriesYAnimMultiplier.setValue(0d); 364 } else { 365 seriesYAnimMultiplier.setValue(1d); 366 } 367 getPlotChildren().add(areaGroup); 368 List<KeyFrame> keyFrames = new ArrayList<KeyFrame>(); 369 if (shouldAnimate()) { 370 // animate in new series 371 keyFrames.add(new KeyFrame(Duration.ZERO, 372 new KeyValue(areaGroup.opacityProperty(), 0), 373 new KeyValue(seriesYAnimMultiplier, 0) 374 )); 375 keyFrames.add(new KeyFrame(Duration.millis(200), 376 new KeyValue(areaGroup.opacityProperty(), 1) 377 )); 378 keyFrames.add(new KeyFrame(Duration.millis(500), 379 new KeyValue(seriesYAnimMultiplier, 1) 380 )); 381 } 382 for (int j=0; j<series.getData().size(); j++) { 383 Data<X,Y> item = series.getData().get(j); 384 final Node symbol = createSymbol(series, seriesIndex, item, j); 385 if (symbol != null) { 386 if (shouldAnimate()) { 387 symbol.setOpacity(0); 388 getPlotChildren().add(symbol); 389 // fade in new symbol 390 keyFrames.add(new KeyFrame(Duration.ZERO, new KeyValue(symbol.opacityProperty(), 0))); 391 keyFrames.add(new KeyFrame(Duration.millis(200), new KeyValue(symbol.opacityProperty(), 1))); 392 } 393 else { 394 getPlotChildren().add(symbol); 395 } 396 } 397 } 398 if (shouldAnimate()) animate(keyFrames.toArray(new KeyFrame[keyFrames.size()])); 399 } 400 private void updateDefaultColorIndex(final Series<X,Y> series) { 401 int clearIndex = seriesColorMap.get(series); 402 Path seriesLine = (Path)((Group)series.getNode()).getChildren().get(1); 403 Path fillPath = (Path)((Group)series.getNode()).getChildren().get(0); 404 if (seriesLine != null) { 405 seriesLine.getStyleClass().remove(DEFAULT_COLOR+clearIndex); 406 } 407 if (fillPath != null) { 408 fillPath.getStyleClass().remove(DEFAULT_COLOR+clearIndex); 409 } 410 for (int j=0; j < series.getData().size(); j++) { 411 final Node node = series.getData().get(j).getNode(); 412 if(node!=null) { 413 node.getStyleClass().remove(DEFAULT_COLOR+clearIndex); 414 } 415 } 416 } 417 @Override protected void seriesRemoved(final Series<X,Y> series) { 418 updateDefaultColorIndex(series); 419 // remove series Y multiplier 420 seriesYMultiplierMap.remove(series); 421 // remove all symbol nodes 422 if (shouldAnimate()) { 423 // create list of all nodes we need to fade out 424 final List<Node> nodes = new ArrayList<Node>(); 425 nodes.add(series.getNode()); 426 if (getCreateSymbols()) { // RT-22124 427 // done need to fade the symbols if createSymbols is false 428 for (Data<X,Y> d: series.getData()) nodes.add(d.getNode()); 429 } 430 // fade out old and symbols 431 KeyValue[] startValues = new KeyValue[nodes.size()]; 432 KeyValue[] endValues = new KeyValue[nodes.size()]; 433 for (int j=0; j < nodes.size(); j++) { 434 startValues[j] = new KeyValue(nodes.get(j).opacityProperty(),1); 435 endValues[j] = new KeyValue(nodes.get(j).opacityProperty(),0); 436 } 437 Timeline tl = new Timeline(); 438 tl.getKeyFrames().addAll( 439 new KeyFrame(Duration.ZERO,startValues), 440 new KeyFrame(Duration.millis(400), actionEvent -> { 441 getPlotChildren().removeAll(nodes); 442 removeSeriesFromDisplay(series); 443 },endValues) 444 ); 445 tl.play(); 446 } else { 447 getPlotChildren().remove(series.getNode()); 448 for (Data<X,Y> d:series.getData()) getPlotChildren().remove(d.getNode()); 449 removeSeriesFromDisplay(series); 450 } 451 } 452 453 /** @inheritDoc */ 454 @Override protected void layoutPlotChildren() { 455 List<LineTo> constructedPath = new ArrayList<>(getDataSize()); 456 for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) { 457 Series<X, Y> series = getData().get(seriesIndex); 458 DoubleProperty seriesYAnimMultiplier = seriesYMultiplierMap.get(series); 459 double lastX = 0; 460 final ObservableList<Node> children = ((Group) series.getNode()).getChildren(); 461 ObservableList<PathElement> seriesLine = ((Path) children.get(1)).getElements(); 462 ObservableList<PathElement> fillPath = ((Path) children.get(0)).getElements(); 463 seriesLine.clear(); 464 fillPath.clear(); 465 constructedPath.clear(); 466 for (Iterator<Data<X, Y>> it = getDisplayedDataIterator(series); it.hasNext(); ) { 467 Data<X, Y> item = it.next(); 468 double x = getXAxis().getDisplayPosition(item.getCurrentX()); 469 double y = getYAxis().getDisplayPosition( 470 getYAxis().toRealValue(getYAxis().toNumericValue(item.getCurrentY()) * seriesYAnimMultiplier.getValue())); 471 constructedPath.add(new LineTo(x, y)); 472 if (Double.isNaN(x) || Double.isNaN(y)) { 473 continue; 474 } 475 lastX = x; 476 Node symbol = item.getNode(); 477 if (symbol != null) { 478 final double w = symbol.prefWidth(-1); 479 final double h = symbol.prefHeight(-1); 480 symbol.resizeRelocate(x-(w/2), y-(h/2),w,h); 481 } 482 } 483 484 if (!constructedPath.isEmpty()) { 485 Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getX(), e2.getX())); 486 LineTo first = constructedPath.get(0); 487 488 final double displayYPos = first.getY(); 489 final double numericYPos = getYAxis().toNumericValue(getYAxis().getValueForDisplay(displayYPos)); 490 491 // RT-34626: We can't always use getZeroPosition(), as it may be the case 492 // that the zero position of the y-axis is not visible on the chart. In these 493 // cases, we need to use the height between the point and the y-axis line. 494 final double yAxisZeroPos = getYAxis().getZeroPosition(); 495 final boolean isYAxisZeroPosVisible = !Double.isNaN(yAxisZeroPos); 496 final double yAxisHeight = getYAxis().getHeight(); 497 final double yFillPos = isYAxisZeroPosVisible ? yAxisZeroPos : 498 numericYPos < 0 ? numericYPos - yAxisHeight : yAxisHeight; 499 500 seriesLine.add(new MoveTo(first.getX(), displayYPos)); 501 fillPath.add(new MoveTo(first.getX(), yFillPos)); 502 503 seriesLine.addAll(constructedPath); 504 fillPath.addAll(constructedPath); 505 fillPath.add(new LineTo(lastX, yFillPos)); 506 fillPath.add(new ClosePath()); 507 } 508 } 509 } 510 511 private Node createSymbol(Series<X,Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) { 512 Node symbol = item.getNode(); 513 // check if symbol has already been created 514 if (symbol == null && getCreateSymbols()) { 515 symbol = new StackPane(); 516 symbol.setAccessibleRole(AccessibleRole.TEXT); 517 symbol.setAccessibleRoleDescription("Point"); 518 symbol.focusTraversableProperty().bind(Platform.accessibilityActiveProperty()); 519 item.setNode(symbol); 520 } 521 // set symbol styles 522 // Note: not sure if we want to add or check, ie be more careful and efficient here 523 if (symbol != null) symbol.getStyleClass().setAll("chart-area-symbol", "series" + seriesIndex, "data" + itemIndex, 524 series.defaultColorStyleClass); 525 return symbol; 526 } 527 528 /** 529 * This is called whenever a series is added or removed and the legend needs to be updated 530 */ 531 @Override protected void updateLegend() { 532 legend.getItems().clear(); 533 if (getData() != null) { 534 for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex++) { 535 Series<X,Y> series = getData().get(seriesIndex); 536 LegendItem legenditem = new LegendItem(series.getName()); 537 legenditem.getSymbol().getStyleClass().addAll("chart-area-symbol","series"+seriesIndex, 538 "area-legend-symbol", series.defaultColorStyleClass); 539 legend.getItems().add(legenditem); 540 } 541 } 542 if (legend.getItems().size() > 0) { 543 if (getLegend() == null) { 544 setLegend(legend); 545 } 546 } else { 547 setLegend(null); 548 } 549 } 550 551 // -------------- STYLESHEET HANDLING -------------------------------------- 552 553 private static class StyleableProperties { 554 private static final CssMetaData<AreaChart<?,?>,Boolean> CREATE_SYMBOLS = 555 new CssMetaData<AreaChart<?,?>,Boolean>("-fx-create-symbols", 556 BooleanConverter.getInstance(), Boolean.TRUE) { 557 558 @Override 559 public boolean isSettable(AreaChart<?,?> node) { 560 return node.createSymbols == null || !node.createSymbols.isBound(); 561 } 562 563 @Override 564 public StyleableProperty<Boolean> getStyleableProperty(AreaChart<?,?> node) { 565 return (StyleableProperty<Boolean>)node.createSymbolsProperty(); 566 } 567 }; 568 569 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 570 static { 571 final List<CssMetaData<? extends Styleable, ?>> styleables = 572 new ArrayList<CssMetaData<? extends Styleable, ?>>(XYChart.getClassCssMetaData()); 573 styleables.add(CREATE_SYMBOLS); 574 STYLEABLES = Collections.unmodifiableList(styleables); 575 } 576 } 577 578 /** 579 * @return The CssMetaData associated with this class, which may include the 580 * CssMetaData of its super classes. 581 * @since JavaFX 8.0 582 */ 583 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 584 return StyleableProperties.STYLEABLES; 585 } 586 587 /** 588 * {@inheritDoc} 589 * @since JavaFX 8.0 590 */ 591 @Override 592 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 593 return getClassCssMetaData(); 594 } 595 596 }