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.ArrayList; 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.Map; 32 33 import javafx.animation.FadeTransition; 34 import javafx.animation.Interpolator; 35 import javafx.animation.KeyFrame; 36 import javafx.animation.KeyValue; 37 import javafx.animation.Timeline; 38 import javafx.animation.Animation; 39 import javafx.application.Platform; 40 import javafx.beans.NamedArg; 41 import javafx.beans.property.BooleanProperty; 42 import javafx.beans.property.DoubleProperty; 43 import javafx.beans.property.ObjectProperty; 44 import javafx.beans.property.ObjectPropertyBase; 45 import javafx.beans.property.SimpleDoubleProperty; 46 import javafx.beans.value.WritableValue; 47 import javafx.collections.FXCollections; 48 import javafx.collections.ListChangeListener; 49 import javafx.collections.ObservableList; 50 import javafx.scene.AccessibleRole; 51 import javafx.scene.Node; 52 import javafx.scene.layout.StackPane; 53 import javafx.scene.shape.LineTo; 54 import javafx.scene.shape.MoveTo; 55 import javafx.scene.shape.Path; 56 import javafx.scene.shape.PathElement; 57 import javafx.scene.shape.StrokeLineJoin; 58 import javafx.util.Duration; 59 60 import com.sun.javafx.charts.Legend; 61 import com.sun.javafx.charts.Legend.LegendItem; 62 63 import javafx.css.StyleableBooleanProperty; 64 import javafx.css.CssMetaData; 65 66 import com.sun.javafx.css.converters.BooleanConverter; 67 68 import java.util.*; 69 70 import javafx.css.Styleable; 71 import javafx.css.StyleableProperty; 72 73 /** 74 * Line Chart plots a line connecting the data points in a series. The data points 75 * themselves can be represented by symbols optionally. Line charts are usually used 76 * to view data trends over time or category. 77 * @since JavaFX 2.0 78 */ 79 public class LineChart<X,Y> extends XYChart<X,Y> { 80 81 // -------------- PRIVATE FIELDS ------------------------------------------ 82 83 /** A multiplier for the Y values that we store for each series, it is used to animate in a new series */ 84 private Map<Series<X,Y>, DoubleProperty> seriesYMultiplierMap = new HashMap<>(); 85 private Legend legend = new Legend(); 86 private Timeline dataRemoveTimeline; 87 private Series<X,Y> seriesOfDataRemoved = null; 88 private Data<X,Y> dataItemBeingRemoved = null; 89 private FadeTransition fadeSymbolTransition = null; 90 private Map<Data<X,Y>, Double> XYValueMap = 91 new HashMap<Data<X,Y>, Double>(); 92 private Timeline seriesRemoveTimeline = null; 93 // -------------- PUBLIC PROPERTIES ---------------------------------------- 94 95 /** When true, CSS styleable symbols are created for any data items that don't have a symbol node specified. */ 96 private BooleanProperty createSymbols = new StyleableBooleanProperty(true) { 97 @Override protected void invalidated() { 98 for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex ++) { 99 Series<X,Y> series = getData().get(seriesIndex); 100 for (int itemIndex=0; itemIndex < series.getData().size(); itemIndex ++) { 101 Data<X,Y> item = series.getData().get(itemIndex); 102 Node symbol = item.getNode(); 103 if(get() && symbol == null) { // create any symbols 104 symbol = createSymbol(series, getData().indexOf(series), item, itemIndex); 105 getPlotChildren().add(symbol); 106 } else if (!get() && symbol != null) { // remove symbols 107 getPlotChildren().remove(symbol); 108 symbol = null; 109 item.setNode(null); 110 } 111 } 112 } 113 requestChartLayout(); 114 } 115 116 public Object getBean() { 117 return LineChart.this; 118 } 119 120 public String getName() { 121 return "createSymbols"; 122 } 123 124 public CssMetaData<LineChart<?,?>,Boolean> getCssMetaData() { 125 return StyleableProperties.CREATE_SYMBOLS; 126 } 127 }; 128 129 /** 130 * Indicates whether symbols for data points will be created or not. 131 * 132 * @return true if symbols for data points will be created and false otherwise. 133 */ 134 public final boolean getCreateSymbols() { return createSymbols.getValue(); } 135 public final void setCreateSymbols(boolean value) { createSymbols.setValue(value); } 136 public final BooleanProperty createSymbolsProperty() { return createSymbols; } 137 138 139 /** 140 * Indicates whether the data passed to LineChart should be sorted by natural order of one of the axes. 141 * If this is set to {@link SortingPolicy#NONE}, the order in {@link #dataProperty()} will be used. 142 * 143 * @since JavaFX 8u40 144 * @see SortingPolicy 145 * @defaultValue SortingPolicy#X_AXIS 146 */ 147 private ObjectProperty<SortingPolicy> axisSortingPolicy = new ObjectPropertyBase<SortingPolicy>(SortingPolicy.X_AXIS) { 148 @Override protected void invalidated() { 149 requestChartLayout(); 150 } 151 152 public Object getBean() { 153 return LineChart.this; 154 } 155 156 public String getName() { 157 return "axisSortingPolicy"; 158 } 159 160 }; 161 162 public final SortingPolicy getAxisSortingPolicy() { return axisSortingPolicy.getValue(); } 163 public final void setAxisSortingPolicy(SortingPolicy value) { axisSortingPolicy.setValue(value); } 164 public final ObjectProperty<SortingPolicy> axisSortingPolicyProperty() { return axisSortingPolicy; } 165 166 // -------------- CONSTRUCTORS ---------------------------------------------- 167 168 /** 169 * Construct a new LineChart with the given axis. 170 * 171 * @param xAxis The x axis to use 172 * @param yAxis The y axis to use 173 */ 174 public LineChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) { 175 this(xAxis, yAxis, FXCollections.<Series<X, Y>>observableArrayList()); 176 } 177 178 /** 179 * Construct a new LineChart with the given axis and data. 180 * 181 * @param xAxis The x axis to use 182 * @param yAxis The y axis to use 183 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 184 */ 185 public LineChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X,Y>> data) { 186 super(xAxis,yAxis); 187 setLegend(legend); 188 setData(data); 189 } 190 191 // -------------- METHODS ------------------------------------------------------------------------------------------ 192 193 /** @inheritDoc */ 194 @Override protected void updateAxisRange() { 195 final Axis<X> xa = getXAxis(); 196 final Axis<Y> ya = getYAxis(); 197 List<X> xData = null; 198 List<Y> yData = null; 199 if(xa.isAutoRanging()) xData = new ArrayList<X>(); 200 if(ya.isAutoRanging()) yData = new ArrayList<Y>(); 201 if(xData != null || yData != null) { 202 for(Series<X,Y> series : getData()) { 203 for(Data<X,Y> data: series.getData()) { 204 if(xData != null) xData.add(data.getXValue()); 205 if(yData != null) yData.add(data.getYValue()); 206 } 207 } 208 // RT-32838 No need to invalidate range if there is one data item - whose value is zero. 209 if(xData != null && !(xData.size() == 1 && getXAxis().toNumericValue(xData.get(0)) == 0)) { 210 xa.invalidateRange(xData); 211 } 212 if(yData != null && !(yData.size() == 1 && getYAxis().toNumericValue(yData.get(0)) == 0)) { 213 ya.invalidateRange(yData); 214 } 215 216 } 217 } 218 219 @Override protected void dataItemAdded(final Series<X,Y> series, int itemIndex, final Data<X,Y> item) { 220 final Node symbol = createSymbol(series, getData().indexOf(series), item, itemIndex); 221 if (shouldAnimate()) { 222 if (dataRemoveTimeline != null && dataRemoveTimeline.getStatus().equals(Animation.Status.RUNNING)) { 223 if (seriesOfDataRemoved == series) { 224 dataRemoveTimeline.stop(); 225 dataRemoveTimeline = null; 226 getPlotChildren().remove(dataItemBeingRemoved.getNode()); 227 removeDataItemFromDisplay(seriesOfDataRemoved, dataItemBeingRemoved); 228 seriesOfDataRemoved = null; 229 dataItemBeingRemoved = null; 230 } 231 } 232 boolean animate = false; 233 if (itemIndex > 0 && itemIndex < (series.getData().size()-1)) { 234 animate = true; 235 Data<X,Y> p1 = series.getData().get(itemIndex - 1); 236 Data<X,Y> p2 = series.getData().get(itemIndex + 1); 237 if (p1 != null && p2 != null) { 238 double x1 = getXAxis().toNumericValue(p1.getXValue()); 239 double y1 = getYAxis().toNumericValue(p1.getYValue()); 240 double x3 = getXAxis().toNumericValue(p2.getXValue()); 241 double y3 = getYAxis().toNumericValue(p2.getYValue()); 242 243 double x2 = getXAxis().toNumericValue(item.getXValue()); 244 //double y2 = getYAxis().toNumericValue(item.getYValue()); 245 if (x2 > x1 && x2 < x3) { 246 //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 247 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 248 item.setCurrentY(getYAxis().toRealValue(y)); 249 item.setCurrentX(getXAxis().toRealValue(x2)); 250 } else { 251 //2. we can simply use the midpoint on the line as well.. 252 double x = (x3 + x1)/2; 253 double y = (y3 + y1)/2; 254 item.setCurrentX(getXAxis().toRealValue(x)); 255 item.setCurrentY(getYAxis().toRealValue(y)); 256 } 257 } 258 } else if (itemIndex == 0 && series.getData().size() > 1) { 259 animate = true; 260 item.setCurrentX(series.getData().get(1).getXValue()); 261 item.setCurrentY(series.getData().get(1).getYValue()); 262 } else if (itemIndex == (series.getData().size() - 1) && series.getData().size() > 1) { 263 animate = true; 264 int last = series.getData().size() - 2; 265 item.setCurrentX(series.getData().get(last).getXValue()); 266 item.setCurrentY(series.getData().get(last).getYValue()); 267 } else if(symbol != null) { 268 // fade in new symbol 269 symbol.setOpacity(0); 270 getPlotChildren().add(symbol); 271 FadeTransition ft = new FadeTransition(Duration.millis(500),symbol); 272 ft.setToValue(1); 273 ft.play(); 274 } 275 if (animate) { 276 animate( 277 new KeyFrame(Duration.ZERO, 278 (e) -> { if (symbol != null && !getPlotChildren().contains(symbol)) getPlotChildren().add(symbol); }, 279 new KeyValue(item.currentYProperty(), 280 item.getCurrentY()), 281 new KeyValue(item.currentXProperty(), 282 item.getCurrentX())), 283 new KeyFrame(Duration.millis(700), new KeyValue(item.currentYProperty(), 284 item.getYValue(), Interpolator.EASE_BOTH), 285 new KeyValue(item.currentXProperty(), 286 item.getXValue(), Interpolator.EASE_BOTH)) 287 ); 288 } 289 290 } else { 291 if (symbol != null) getPlotChildren().add(symbol); 292 } 293 } 294 295 @Override protected void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) { 296 final Node symbol = item.getNode(); 297 298 if (symbol != null) { 299 symbol.focusTraversableProperty().unbind(); 300 } 301 302 // remove item from sorted list 303 int itemIndex = series.getItemIndex(item); 304 if (shouldAnimate()) { 305 XYValueMap.clear(); 306 boolean animate = false; 307 if (itemIndex > 0 && itemIndex < series.getDataSize()) { 308 animate = true; 309 Data<X,Y> p1 = series.getItem(itemIndex - 1); 310 Data<X,Y> p2 = series.getItem(itemIndex + 1); 311 if (p1 != null && p2 != null) { 312 double x1 = getXAxis().toNumericValue(p1.getXValue()); 313 double y1 = getYAxis().toNumericValue(p1.getYValue()); 314 double x3 = getXAxis().toNumericValue(p2.getXValue()); 315 double y3 = getYAxis().toNumericValue(p2.getYValue()); 316 317 double x2 = getXAxis().toNumericValue(item.getXValue()); 318 double y2 = getYAxis().toNumericValue(item.getYValue()); 319 if (x2 > x1 && x2 < x3) { 320 // //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 321 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 322 item.setCurrentX(getXAxis().toRealValue(x2)); 323 item.setCurrentY(getYAxis().toRealValue(y2)); 324 item.setXValue(getXAxis().toRealValue(x2)); 325 item.setYValue(getYAxis().toRealValue(y)); 326 } else { 327 //2. we can simply use the midpoint on the line as well.. 328 double x = (x3 + x1)/2; 329 double y = (y3 + y1)/2; 330 item.setCurrentX(getXAxis().toRealValue(x)); 331 item.setCurrentY(getYAxis().toRealValue(y)); 332 } 333 } 334 } else if (itemIndex == 0 && series.getDataSize() > 1) { 335 animate = true; 336 Iterator<Data<X,Y>> iter = getDisplayedDataIterator(series); 337 if (iter.hasNext()) { // get first data value 338 Data<X,Y> d = iter.next(); 339 item.setXValue(d.getXValue()); 340 item.setYValue(d.getYValue()); 341 } 342 } else if (itemIndex == (series.getDataSize() - 1) && series.getDataSize() > 1) { 343 animate = true; 344 int last = series.getData().size() - 1; 345 item.setXValue(series.getData().get(last).getXValue()); 346 item.setYValue(series.getData().get(last).getYValue()); 347 } else { 348 // fade out symbol 349 if (symbol != null) { 350 fadeSymbolTransition = new FadeTransition(Duration.millis(500),symbol); 351 fadeSymbolTransition.setToValue(0); 352 fadeSymbolTransition.setOnFinished(actionEvent -> { 353 item.setSeries(null); 354 getPlotChildren().remove(symbol); 355 removeDataItemFromDisplay(series, item); 356 symbol.setOpacity(1.0); 357 }); 358 fadeSymbolTransition.play(); 359 } 360 } 361 if (animate) { 362 dataRemoveTimeline = createDataRemoveTimeline(item, symbol, series); 363 seriesOfDataRemoved = series; 364 dataItemBeingRemoved = item; 365 dataRemoveTimeline.play(); 366 } 367 } else { 368 item.setSeries(null); 369 if (symbol != null) getPlotChildren().remove(symbol); 370 removeDataItemFromDisplay(series, item); 371 } 372 //Note: better animation here, point should move from old position to new position at center point between prev and next symbols 373 } 374 375 /** @inheritDoc */ 376 @Override protected void dataItemChanged(Data<X, Y> item) { 377 } 378 379 @Override protected void seriesChanged(ListChangeListener.Change<? extends Series> c) { 380 // Update style classes for all series lines and symbols 381 // Note: is there a more efficient way of doing this? 382 for (int i = 0; i < getDataSize(); i++) { 383 final Series<X,Y> s = getData().get(i); 384 Node seriesNode = s.getNode(); 385 if(seriesNode != null) seriesNode.getStyleClass().setAll("chart-series-line", "series" + i, s.defaultColorStyleClass); 386 } 387 } 388 389 @Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) { 390 // create new path for series 391 Path seriesLine = new Path(); 392 seriesLine.setStrokeLineJoin(StrokeLineJoin.BEVEL); 393 series.setNode(seriesLine); 394 // create series Y multiplier 395 DoubleProperty seriesYAnimMultiplier = new SimpleDoubleProperty(this, "seriesYMultiplier"); 396 seriesYMultiplierMap.put(series, seriesYAnimMultiplier); 397 // handle any data already in series 398 if (shouldAnimate()) { 399 seriesLine.setOpacity(0); 400 seriesYAnimMultiplier.setValue(0d); 401 } else { 402 seriesYAnimMultiplier.setValue(1d); 403 } 404 getPlotChildren().add(seriesLine); 405 406 List<KeyFrame> keyFrames = new ArrayList<KeyFrame>(); 407 if (shouldAnimate()) { 408 // animate in new series 409 keyFrames.add(new KeyFrame(Duration.ZERO, 410 new KeyValue(seriesLine.opacityProperty(), 0), 411 new KeyValue(seriesYAnimMultiplier, 0) 412 )); 413 keyFrames.add(new KeyFrame(Duration.millis(200), 414 new KeyValue(seriesLine.opacityProperty(), 1) 415 )); 416 keyFrames.add(new KeyFrame(Duration.millis(500), 417 new KeyValue(seriesYAnimMultiplier, 1) 418 )); 419 } 420 for (int j=0; j<series.getData().size(); j++) { 421 Data<X,Y> item = series.getData().get(j); 422 final Node symbol = createSymbol(series, seriesIndex, item, j); 423 if(symbol != null) { 424 if (shouldAnimate()) symbol.setOpacity(0); 425 getPlotChildren().add(symbol); 426 if (shouldAnimate()) { 427 // fade in new symbol 428 keyFrames.add(new KeyFrame(Duration.ZERO, new KeyValue(symbol.opacityProperty(), 0))); 429 keyFrames.add(new KeyFrame(Duration.millis(200), new KeyValue(symbol.opacityProperty(), 1))); 430 } 431 } 432 } 433 if (shouldAnimate()) animate(keyFrames.toArray(new KeyFrame[keyFrames.size()])); 434 } 435 private void updateDefaultColorIndex(final Series<X,Y> series) { 436 int clearIndex = seriesColorMap.get(series); 437 series.getNode().getStyleClass().remove(DEFAULT_COLOR+clearIndex); 438 for (int j=0; j < series.getData().size(); j++) { 439 final Node node = series.getData().get(j).getNode(); 440 if(node!=null) { 441 node.getStyleClass().remove(DEFAULT_COLOR+clearIndex); 442 } 443 } 444 } 445 446 @Override protected void seriesRemoved(final Series<X,Y> series) { 447 updateDefaultColorIndex(series); 448 // remove all symbol nodes 449 seriesYMultiplierMap.remove(series); 450 if (shouldAnimate()) { 451 seriesRemoveTimeline = new Timeline(createSeriesRemoveTimeLine(series, 900)); 452 seriesRemoveTimeline.play(); 453 } else { 454 getPlotChildren().remove(series.getNode()); 455 for (Data<X,Y> d:series.getData()) getPlotChildren().remove(d.getNode()); 456 removeSeriesFromDisplay(series); 457 } 458 } 459 460 /** @inheritDoc */ 461 @Override protected void layoutPlotChildren() { 462 List<LineTo> constructedPath = new ArrayList<>(getDataSize()); 463 for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) { 464 Series<X,Y> series = getData().get(seriesIndex); 465 final DoubleProperty seriesYAnimMultiplier = seriesYMultiplierMap.get(series); 466 if(series.getNode() instanceof Path) { 467 final ObservableList<PathElement> seriesLine = ((Path)series.getNode()).getElements(); 468 seriesLine.clear(); 469 constructedPath.clear(); 470 for (Iterator<Data<X, Y>> it = getDisplayedDataIterator(series); it.hasNext(); ) { 471 Data<X, Y> item = it.next(); 472 double x = getXAxis().getDisplayPosition(item.getCurrentX()); 473 double y = getYAxis().getDisplayPosition( 474 getYAxis().toRealValue(getYAxis().toNumericValue(item.getCurrentY()) * seriesYAnimMultiplier.getValue())); 475 if (Double.isNaN(x) || Double.isNaN(y)) { 476 continue; 477 } 478 constructedPath.add(new LineTo(x, y)); 479 480 Node symbol = item.getNode(); 481 if (symbol != null) { 482 final double w = symbol.prefWidth(-1); 483 final double h = symbol.prefHeight(-1); 484 symbol.resizeRelocate(x-(w/2), y-(h/2),w,h); 485 } 486 } 487 switch (getAxisSortingPolicy()) { 488 case X_AXIS: 489 Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getX(), e2.getX())); 490 break; 491 case Y_AXIS: 492 Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getY(), e2.getY())); 493 break; 494 } 495 496 if (!constructedPath.isEmpty()) { 497 LineTo first = constructedPath.get(0); 498 seriesLine.add(new MoveTo(first.getX(), first.getY())); 499 seriesLine.addAll(constructedPath); 500 } 501 } 502 } 503 } 504 /** @inheritDoc */ 505 @Override void dataBeingRemovedIsAdded(Data item, Series series) { 506 if (fadeSymbolTransition != null) { 507 fadeSymbolTransition.setOnFinished(null); 508 fadeSymbolTransition.stop(); 509 } 510 if (dataRemoveTimeline != null) { 511 dataRemoveTimeline.setOnFinished(null); 512 dataRemoveTimeline.stop(); 513 } 514 final Node symbol = item.getNode(); 515 if (symbol != null) getPlotChildren().remove(symbol); 516 517 item.setSeries(null); 518 removeDataItemFromDisplay(series, item); 519 520 // restore values to item 521 Double value = XYValueMap.get(item); 522 if (value != null) { 523 item.setYValue(value); 524 item.setCurrentY(value); 525 } 526 XYValueMap.clear(); 527 } 528 /** @inheritDoc */ 529 @Override void seriesBeingRemovedIsAdded(Series<X,Y> series) { 530 if (seriesRemoveTimeline != null) { 531 seriesRemoveTimeline.setOnFinished(null); 532 seriesRemoveTimeline.stop(); 533 getPlotChildren().remove(series.getNode()); 534 for (Data<X,Y> d:series.getData()) getPlotChildren().remove(d.getNode()); 535 removeSeriesFromDisplay(series); 536 } 537 } 538 539 private Timeline createDataRemoveTimeline(final Data<X,Y> item, final Node symbol, final Series<X,Y> series) { 540 Timeline t = new Timeline(); 541 // save data values in case the same data item gets added immediately. 542 XYValueMap.put(item, ((Number)item.getYValue()).doubleValue()); 543 544 t.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), 545 item.getCurrentY()), new KeyValue(item.currentXProperty(), 546 item.getCurrentX())), 547 new KeyFrame(Duration.millis(500), actionEvent -> { 548 if (symbol != null) getPlotChildren().remove(symbol); 549 removeDataItemFromDisplay(series, item); 550 XYValueMap.clear(); 551 }, 552 new KeyValue(item.currentYProperty(), 553 item.getYValue(), Interpolator.EASE_BOTH), 554 new KeyValue(item.currentXProperty(), 555 item.getXValue(), Interpolator.EASE_BOTH)) 556 ); 557 return t; 558 } 559 560 private Node createSymbol(Series<X, Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) { 561 Node symbol = item.getNode(); 562 // check if symbol has already been created 563 if (symbol == null && getCreateSymbols()) { 564 symbol = new StackPane(); 565 symbol.setAccessibleRole(AccessibleRole.TEXT); 566 symbol.setAccessibleRoleDescription("Point"); 567 symbol.focusTraversableProperty().bind(Platform.accessibilityActiveProperty()); 568 item.setNode(symbol); 569 } 570 // set symbol styles 571 if (symbol != null) symbol.getStyleClass().addAll("chart-line-symbol", "series" + seriesIndex, 572 "data" + itemIndex, series.defaultColorStyleClass); 573 return symbol; 574 } 575 576 /** 577 * This is called whenever a series is added or removed and the legend needs to be updated 578 */ 579 @Override protected void updateLegend() { 580 legend.getItems().clear(); 581 if (getData() != null) { 582 for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex++) { 583 Series<X,Y> series = getData().get(seriesIndex); 584 LegendItem legenditem = new LegendItem(series.getName()); 585 legenditem.getSymbol().getStyleClass().addAll("chart-line-symbol", "series"+seriesIndex, series.defaultColorStyleClass); 586 legend.getItems().add(legenditem); 587 } 588 } 589 if (legend.getItems().size() > 0) { 590 if (getLegend() == null) { 591 setLegend(legend); 592 } 593 } else { 594 setLegend(null); 595 } 596 } 597 598 // -------------- STYLESHEET HANDLING -------------------------------------- 599 600 private static class StyleableProperties { 601 private static final CssMetaData<LineChart<?,?>,Boolean> CREATE_SYMBOLS = 602 new CssMetaData<LineChart<?,?>,Boolean>("-fx-create-symbols", 603 BooleanConverter.getInstance(), Boolean.TRUE) { 604 605 @Override 606 public boolean isSettable(LineChart<?,?> node) { 607 return node.createSymbols == null || !node.createSymbols.isBound(); 608 } 609 610 @Override 611 public StyleableProperty<Boolean> getStyleableProperty(LineChart<?,?> node) { 612 return (StyleableProperty<Boolean>)(WritableValue<Boolean>)node.createSymbolsProperty(); 613 } 614 }; 615 616 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 617 static { 618 final List<CssMetaData<? extends Styleable, ?>> styleables = 619 new ArrayList<CssMetaData<? extends Styleable, ?>>(XYChart.getClassCssMetaData()); 620 styleables.add(CREATE_SYMBOLS); 621 STYLEABLES = Collections.unmodifiableList(styleables); 622 } 623 } 624 625 /** 626 * @return The CssMetaData associated with this class, which may include the 627 * CssMetaData of its super classes. 628 * @since JavaFX 8.0 629 */ 630 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 631 return StyleableProperties.STYLEABLES; 632 } 633 634 /** 635 * {@inheritDoc} 636 * @since JavaFX 8.0 637 */ 638 @Override 639 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 640 return getClassCssMetaData(); 641 } 642 643 /** 644 * This enum defines a policy for {@link LineChart#axisSortingPolicyProperty()}. 645 * @since JavaFX 8u40 646 */ 647 public static enum SortingPolicy { 648 /** 649 * The data should be left in the order defined by the list in {@link javafx.scene.chart.LineChart#dataProperty()}. 650 */ 651 NONE, 652 /** 653 * The data is ordered by x axis. 654 */ 655 X_AXIS, 656 /** 657 * The data is ordered by y axis. 658 */ 659 Y_AXIS 660 } 661 }