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 javafx.css.converter.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 // dataSize represents size of currently visible data. After this operation, the number will decrement by 1 308 final int dataSize = series.getDataSize(); 309 // This is the size of current data list in Series. Note that it might be totaly different from dataSize as 310 // some big operation might have happened on the list. 311 final int dataListSize = series.getData().size(); 312 if (itemIndex > 0 && itemIndex < dataSize - 1) { 313 animate = true; 314 Data<X,Y> p1 = series.getItem(itemIndex - 1); 315 Data<X,Y> p2 = series.getItem(itemIndex + 1); 316 double x1 = getXAxis().toNumericValue(p1.getXValue()); 317 double y1 = getYAxis().toNumericValue(p1.getYValue()); 318 double x3 = getXAxis().toNumericValue(p2.getXValue()); 319 double y3 = getYAxis().toNumericValue(p2.getYValue()); 320 321 double x2 = getXAxis().toNumericValue(item.getXValue()); 322 double y2 = getYAxis().toNumericValue(item.getYValue()); 323 if (x2 > x1 && x2 < x3) { 324 // //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 325 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 326 item.setCurrentX(getXAxis().toRealValue(x2)); 327 item.setCurrentY(getYAxis().toRealValue(y2)); 328 item.setXValue(getXAxis().toRealValue(x2)); 329 item.setYValue(getYAxis().toRealValue(y)); 330 } else { 331 //2. we can simply use the midpoint on the line as well.. 332 double x = (x3 + x1)/2; 333 double y = (y3 + y1)/2; 334 item.setCurrentX(getXAxis().toRealValue(x)); 335 item.setCurrentY(getYAxis().toRealValue(y)); 336 } 337 } else if (itemIndex == 0 && dataListSize > 1) { 338 animate = true; 339 item.setXValue(series.getData().get(0).getXValue()); 340 item.setYValue(series.getData().get(0).getYValue()); 341 } else if (itemIndex == (dataSize - 1) && dataListSize > 1) { 342 animate = true; 343 int last = dataListSize - 1; 344 item.setXValue(series.getData().get(last).getXValue()); 345 item.setYValue(series.getData().get(last).getYValue()); 346 } else if (symbol != null) { 347 // fade out symbol 348 fadeSymbolTransition = new FadeTransition(Duration.millis(500),symbol); 349 fadeSymbolTransition.setToValue(0); 350 fadeSymbolTransition.setOnFinished(actionEvent -> { 351 item.setSeries(null); 352 getPlotChildren().remove(symbol); 353 removeDataItemFromDisplay(series, item); 354 symbol.setOpacity(1.0); 355 }); 356 fadeSymbolTransition.play(); 357 } 358 if (animate) { 359 dataRemoveTimeline = createDataRemoveTimeline(item, symbol, series); 360 seriesOfDataRemoved = series; 361 dataItemBeingRemoved = item; 362 dataRemoveTimeline.play(); 363 } 364 } else { 365 item.setSeries(null); 366 if (symbol != null) getPlotChildren().remove(symbol); 367 removeDataItemFromDisplay(series, item); 368 } 369 //Note: better animation here, point should move from old position to new position at center point between prev and next symbols 370 } 371 372 /** @inheritDoc */ 373 @Override protected void dataItemChanged(Data<X, Y> item) { 374 } 375 376 @Override protected void seriesChanged(ListChangeListener.Change<? extends Series> c) { 377 // Update style classes for all series lines and symbols 378 // Note: is there a more efficient way of doing this? 379 for (int i = 0; i < getDataSize(); i++) { 380 final Series<X,Y> s = getData().get(i); 381 Node seriesNode = s.getNode(); 382 if (seriesNode != null) seriesNode.getStyleClass().setAll("chart-series-line", "series" + i, s.defaultColorStyleClass); 383 for (int j=0; j < s.getData().size(); j++) { 384 final Node symbol = s.getData().get(j).getNode(); 385 if (symbol != null) symbol.getStyleClass().setAll("chart-line-symbol", "series" + i, "data" + j, s.defaultColorStyleClass); 386 } 387 } 388 } 389 390 @Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) { 391 // create new path for series 392 Path seriesLine = new Path(); 393 seriesLine.setStrokeLineJoin(StrokeLineJoin.BEVEL); 394 series.setNode(seriesLine); 395 // create series Y multiplier 396 DoubleProperty seriesYAnimMultiplier = new SimpleDoubleProperty(this, "seriesYMultiplier"); 397 seriesYMultiplierMap.put(series, seriesYAnimMultiplier); 398 // handle any data already in series 399 if (shouldAnimate()) { 400 seriesLine.setOpacity(0); 401 seriesYAnimMultiplier.setValue(0d); 402 } else { 403 seriesYAnimMultiplier.setValue(1d); 404 } 405 getPlotChildren().add(seriesLine); 406 407 List<KeyFrame> keyFrames = new ArrayList<KeyFrame>(); 408 if (shouldAnimate()) { 409 // animate in new series 410 keyFrames.add(new KeyFrame(Duration.ZERO, 411 new KeyValue(seriesLine.opacityProperty(), 0), 412 new KeyValue(seriesYAnimMultiplier, 0) 413 )); 414 keyFrames.add(new KeyFrame(Duration.millis(200), 415 new KeyValue(seriesLine.opacityProperty(), 1) 416 )); 417 keyFrames.add(new KeyFrame(Duration.millis(500), 418 new KeyValue(seriesYAnimMultiplier, 1) 419 )); 420 } 421 for (int j=0; j<series.getData().size(); j++) { 422 Data<X,Y> item = series.getData().get(j); 423 final Node symbol = createSymbol(series, seriesIndex, item, j); 424 if(symbol != null) { 425 if (shouldAnimate()) symbol.setOpacity(0); 426 getPlotChildren().add(symbol); 427 if (shouldAnimate()) { 428 // fade in new symbol 429 keyFrames.add(new KeyFrame(Duration.ZERO, new KeyValue(symbol.opacityProperty(), 0))); 430 keyFrames.add(new KeyFrame(Duration.millis(200), new KeyValue(symbol.opacityProperty(), 1))); 431 } 432 } 433 } 434 if (shouldAnimate()) animate(keyFrames.toArray(new KeyFrame[keyFrames.size()])); 435 } 436 437 @Override protected void seriesRemoved(final Series<X,Y> series) { 438 // remove all symbol nodes 439 seriesYMultiplierMap.remove(series); 440 if (shouldAnimate()) { 441 seriesRemoveTimeline = new Timeline(createSeriesRemoveTimeLine(series, 900)); 442 seriesRemoveTimeline.play(); 443 } else { 444 getPlotChildren().remove(series.getNode()); 445 for (Data<X,Y> d:series.getData()) getPlotChildren().remove(d.getNode()); 446 removeSeriesFromDisplay(series); 447 } 448 } 449 450 /** @inheritDoc */ 451 @Override protected void layoutPlotChildren() { 452 List<LineTo> constructedPath = new ArrayList<>(getDataSize()); 453 for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) { 454 Series<X,Y> series = getData().get(seriesIndex); 455 final DoubleProperty seriesYAnimMultiplier = seriesYMultiplierMap.get(series); 456 if(series.getNode() instanceof Path) { 457 final ObservableList<PathElement> seriesLine = ((Path)series.getNode()).getElements(); 458 seriesLine.clear(); 459 constructedPath.clear(); 460 for (Iterator<Data<X, Y>> it = getDisplayedDataIterator(series); it.hasNext(); ) { 461 Data<X, Y> item = it.next(); 462 double x = getXAxis().getDisplayPosition(item.getCurrentX()); 463 double y = getYAxis().getDisplayPosition( 464 getYAxis().toRealValue(getYAxis().toNumericValue(item.getCurrentY()) * seriesYAnimMultiplier.getValue())); 465 if (Double.isNaN(x) || Double.isNaN(y)) { 466 continue; 467 } 468 constructedPath.add(new LineTo(x, y)); 469 470 Node symbol = item.getNode(); 471 if (symbol != null) { 472 final double w = symbol.prefWidth(-1); 473 final double h = symbol.prefHeight(-1); 474 symbol.resizeRelocate(x-(w/2), y-(h/2),w,h); 475 } 476 } 477 switch (getAxisSortingPolicy()) { 478 case X_AXIS: 479 Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getX(), e2.getX())); 480 break; 481 case Y_AXIS: 482 Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getY(), e2.getY())); 483 break; 484 } 485 486 if (!constructedPath.isEmpty()) { 487 LineTo first = constructedPath.get(0); 488 seriesLine.add(new MoveTo(first.getX(), first.getY())); 489 seriesLine.addAll(constructedPath); 490 } 491 } 492 } 493 } 494 /** @inheritDoc */ 495 @Override void dataBeingRemovedIsAdded(Data item, Series series) { 496 if (fadeSymbolTransition != null) { 497 fadeSymbolTransition.setOnFinished(null); 498 fadeSymbolTransition.stop(); 499 } 500 if (dataRemoveTimeline != null) { 501 dataRemoveTimeline.setOnFinished(null); 502 dataRemoveTimeline.stop(); 503 } 504 final Node symbol = item.getNode(); 505 if (symbol != null) getPlotChildren().remove(symbol); 506 507 item.setSeries(null); 508 removeDataItemFromDisplay(series, item); 509 510 // restore values to item 511 Double value = XYValueMap.get(item); 512 if (value != null) { 513 item.setYValue(value); 514 item.setCurrentY(value); 515 } 516 XYValueMap.clear(); 517 } 518 /** @inheritDoc */ 519 @Override void seriesBeingRemovedIsAdded(Series<X,Y> series) { 520 if (seriesRemoveTimeline != null) { 521 seriesRemoveTimeline.setOnFinished(null); 522 seriesRemoveTimeline.stop(); 523 getPlotChildren().remove(series.getNode()); 524 for (Data<X,Y> d:series.getData()) getPlotChildren().remove(d.getNode()); 525 removeSeriesFromDisplay(series); 526 } 527 } 528 529 private Timeline createDataRemoveTimeline(final Data<X,Y> item, final Node symbol, final Series<X,Y> series) { 530 Timeline t = new Timeline(); 531 // save data values in case the same data item gets added immediately. 532 XYValueMap.put(item, ((Number)item.getYValue()).doubleValue()); 533 534 t.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), 535 item.getCurrentY()), new KeyValue(item.currentXProperty(), 536 item.getCurrentX())), 537 new KeyFrame(Duration.millis(500), actionEvent -> { 538 if (symbol != null) getPlotChildren().remove(symbol); 539 removeDataItemFromDisplay(series, item); 540 XYValueMap.clear(); 541 }, 542 new KeyValue(item.currentYProperty(), 543 item.getYValue(), Interpolator.EASE_BOTH), 544 new KeyValue(item.currentXProperty(), 545 item.getXValue(), Interpolator.EASE_BOTH)) 546 ); 547 return t; 548 } 549 550 private Node createSymbol(Series<X, Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) { 551 Node symbol = item.getNode(); 552 // check if symbol has already been created 553 if (symbol == null && getCreateSymbols()) { 554 symbol = new StackPane(); 555 symbol.setAccessibleRole(AccessibleRole.TEXT); 556 symbol.setAccessibleRoleDescription("Point"); 557 symbol.focusTraversableProperty().bind(Platform.accessibilityActiveProperty()); 558 item.setNode(symbol); 559 } 560 // set symbol styles 561 if (symbol != null) symbol.getStyleClass().addAll("chart-line-symbol", "series" + seriesIndex, 562 "data" + itemIndex, series.defaultColorStyleClass); 563 return symbol; 564 } 565 566 /** 567 * This is called whenever a series is added or removed and the legend needs to be updated 568 */ 569 @Override protected void updateLegend() { 570 legend.getItems().clear(); 571 if (getData() != null) { 572 for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex++) { 573 Series<X,Y> series = getData().get(seriesIndex); 574 LegendItem legenditem = new LegendItem(series.getName()); 575 legenditem.getSymbol().getStyleClass().addAll("chart-line-symbol", "series"+seriesIndex, series.defaultColorStyleClass); 576 legend.getItems().add(legenditem); 577 } 578 } 579 if (legend.getItems().size() > 0) { 580 if (getLegend() == null) { 581 setLegend(legend); 582 } 583 } else { 584 setLegend(null); 585 } 586 } 587 588 // -------------- STYLESHEET HANDLING -------------------------------------- 589 590 private static class StyleableProperties { 591 private static final CssMetaData<LineChart<?,?>,Boolean> CREATE_SYMBOLS = 592 new CssMetaData<LineChart<?,?>,Boolean>("-fx-create-symbols", 593 BooleanConverter.getInstance(), Boolean.TRUE) { 594 595 @Override 596 public boolean isSettable(LineChart<?,?> node) { 597 return node.createSymbols == null || !node.createSymbols.isBound(); 598 } 599 600 @Override 601 public StyleableProperty<Boolean> getStyleableProperty(LineChart<?,?> node) { 602 return (StyleableProperty<Boolean>)(WritableValue<Boolean>)node.createSymbolsProperty(); 603 } 604 }; 605 606 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 607 static { 608 final List<CssMetaData<? extends Styleable, ?>> styleables = 609 new ArrayList<CssMetaData<? extends Styleable, ?>>(XYChart.getClassCssMetaData()); 610 styleables.add(CREATE_SYMBOLS); 611 STYLEABLES = Collections.unmodifiableList(styleables); 612 } 613 } 614 615 /** 616 * @return The CssMetaData associated with this class, which may include the 617 * CssMetaData of its super classes. 618 * @since JavaFX 8.0 619 */ 620 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 621 return StyleableProperties.STYLEABLES; 622 } 623 624 /** 625 * {@inheritDoc} 626 * @since JavaFX 8.0 627 */ 628 @Override 629 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 630 return getClassCssMetaData(); 631 } 632 633 /** 634 * This enum defines a policy for {@link LineChart#axisSortingPolicyProperty()}. 635 * @since JavaFX 8u40 636 */ 637 public static enum SortingPolicy { 638 /** 639 * The data should be left in the order defined by the list in {@link javafx.scene.chart.LineChart#dataProperty()}. 640 */ 641 NONE, 642 /** 643 * The data is ordered by x axis. 644 */ 645 X_AXIS, 646 /** 647 * The data is ordered by y axis. 648 */ 649 Y_AXIS 650 } 651 }