1 /* 2 * Copyright (c) 2010, 2016, 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.LegendItem; 61 62 import javafx.css.StyleableBooleanProperty; 63 import javafx.css.CssMetaData; 64 65 import javafx.css.converter.BooleanConverter; 66 67 import java.util.*; 68 69 import javafx.css.Styleable; 70 import javafx.css.StyleableProperty; 71 72 /** 73 * Line Chart plots a line connecting the data points in a series. The data points 74 * themselves can be represented by symbols optionally. Line charts are usually used 75 * to view data trends over time or category. 76 * @since JavaFX 2.0 77 */ 78 public class LineChart<X,Y> extends XYChart<X,Y> { 79 80 // -------------- PRIVATE FIELDS ------------------------------------------ 81 82 /** A multiplier for the Y values that we store for each series, it is used to animate in a new series */ 83 private Map<Series<X,Y>, DoubleProperty> seriesYMultiplierMap = new HashMap<>(); 84 private Timeline dataRemoveTimeline; 85 private Series<X,Y> seriesOfDataRemoved = null; 86 private Data<X,Y> dataItemBeingRemoved = null; 87 private FadeTransition fadeSymbolTransition = null; 88 private Map<Data<X,Y>, Double> XYValueMap = 89 new HashMap<Data<X,Y>, Double>(); 90 private Timeline seriesRemoveTimeline = null; 91 // -------------- PUBLIC PROPERTIES ---------------------------------------- 92 93 /** When true, CSS styleable symbols are created for any data items that don't have a symbol node specified. */ 94 private BooleanProperty createSymbols = new StyleableBooleanProperty(true) { 95 @Override protected void invalidated() { 96 for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex ++) { 97 Series<X,Y> series = getData().get(seriesIndex); 98 for (int itemIndex=0; itemIndex < series.getData().size(); itemIndex ++) { 99 Data<X,Y> item = series.getData().get(itemIndex); 100 Node symbol = item.getNode(); 101 if(get() && symbol == null) { // create any symbols 102 symbol = createSymbol(series, getData().indexOf(series), item, itemIndex); 103 getPlotChildren().add(symbol); 104 } else if (!get() && symbol != null) { // remove symbols 105 getPlotChildren().remove(symbol); 106 symbol = null; 107 item.setNode(null); 108 } 109 } 110 } 111 requestChartLayout(); 112 } 113 114 public Object getBean() { 115 return LineChart.this; 116 } 117 118 public String getName() { 119 return "createSymbols"; 120 } 121 122 public CssMetaData<LineChart<?,?>,Boolean> getCssMetaData() { 123 return StyleableProperties.CREATE_SYMBOLS; 124 } 125 }; 126 127 /** 128 * Indicates whether symbols for data points will be created or not. 129 * 130 * @return true if symbols for data points will be created and false otherwise. 131 */ 132 public final boolean getCreateSymbols() { return createSymbols.getValue(); } 133 public final void setCreateSymbols(boolean value) { createSymbols.setValue(value); } 134 public final BooleanProperty createSymbolsProperty() { return createSymbols; } 135 136 137 /** 138 * Indicates whether the data passed to LineChart should be sorted by natural order of one of the axes. 139 * If this is set to {@link SortingPolicy#NONE}, the order in {@link #dataProperty()} will be used. 140 * 141 * @since JavaFX 8u40 142 * @see SortingPolicy 143 * @defaultValue SortingPolicy#X_AXIS 144 */ 145 private ObjectProperty<SortingPolicy> axisSortingPolicy = new ObjectPropertyBase<SortingPolicy>(SortingPolicy.X_AXIS) { 146 @Override protected void invalidated() { 147 requestChartLayout(); 148 } 149 150 public Object getBean() { 151 return LineChart.this; 152 } 153 154 public String getName() { 155 return "axisSortingPolicy"; 156 } 157 158 }; 159 160 public final SortingPolicy getAxisSortingPolicy() { return axisSortingPolicy.getValue(); } 161 public final void setAxisSortingPolicy(SortingPolicy value) { axisSortingPolicy.setValue(value); } 162 public final ObjectProperty<SortingPolicy> axisSortingPolicyProperty() { return axisSortingPolicy; } 163 164 // -------------- CONSTRUCTORS ---------------------------------------------- 165 166 /** 167 * Construct a new LineChart with the given axis. 168 * 169 * @param xAxis The x axis to use 170 * @param yAxis The y axis to use 171 */ 172 public LineChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) { 173 this(xAxis, yAxis, FXCollections.<Series<X, Y>>observableArrayList()); 174 } 175 176 /** 177 * Construct a new LineChart with the given axis and data. 178 * 179 * @param xAxis The x axis to use 180 * @param yAxis The y axis to use 181 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 182 */ 183 public LineChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X,Y>> data) { 184 super(xAxis,yAxis); 185 setData(data); 186 } 187 188 // -------------- METHODS ------------------------------------------------------------------------------------------ 189 190 /** {@inheritDoc} */ 191 @Override protected void updateAxisRange() { 192 final Axis<X> xa = getXAxis(); 193 final Axis<Y> ya = getYAxis(); 194 List<X> xData = null; 195 List<Y> yData = null; 196 if(xa.isAutoRanging()) xData = new ArrayList<X>(); 197 if(ya.isAutoRanging()) yData = new ArrayList<Y>(); 198 if(xData != null || yData != null) { 199 for(Series<X,Y> series : getData()) { 200 for(Data<X,Y> data: series.getData()) { 201 if(xData != null) xData.add(data.getXValue()); 202 if(yData != null) yData.add(data.getYValue()); 203 } 204 } 205 // RT-32838 No need to invalidate range if there is one data item - whose value is zero. 206 if(xData != null && !(xData.size() == 1 && getXAxis().toNumericValue(xData.get(0)) == 0)) { 207 xa.invalidateRange(xData); 208 } 209 if(yData != null && !(yData.size() == 1 && getYAxis().toNumericValue(yData.get(0)) == 0)) { 210 ya.invalidateRange(yData); 211 } 212 213 } 214 } 215 216 @Override protected void dataItemAdded(final Series<X,Y> series, int itemIndex, final Data<X,Y> item) { 217 final Node symbol = createSymbol(series, getData().indexOf(series), item, itemIndex); 218 if (shouldAnimate()) { 219 if (dataRemoveTimeline != null && dataRemoveTimeline.getStatus().equals(Animation.Status.RUNNING)) { 220 if (seriesOfDataRemoved == series) { 221 dataRemoveTimeline.stop(); 222 dataRemoveTimeline = null; 223 getPlotChildren().remove(dataItemBeingRemoved.getNode()); 224 removeDataItemFromDisplay(seriesOfDataRemoved, dataItemBeingRemoved); 225 seriesOfDataRemoved = null; 226 dataItemBeingRemoved = null; 227 } 228 } 229 boolean animate = false; 230 if (itemIndex > 0 && itemIndex < (series.getData().size()-1)) { 231 animate = true; 232 Data<X,Y> p1 = series.getData().get(itemIndex - 1); 233 Data<X,Y> p2 = series.getData().get(itemIndex + 1); 234 if (p1 != null && p2 != null) { 235 double x1 = getXAxis().toNumericValue(p1.getXValue()); 236 double y1 = getYAxis().toNumericValue(p1.getYValue()); 237 double x3 = getXAxis().toNumericValue(p2.getXValue()); 238 double y3 = getYAxis().toNumericValue(p2.getYValue()); 239 240 double x2 = getXAxis().toNumericValue(item.getXValue()); 241 //double y2 = getYAxis().toNumericValue(item.getYValue()); 242 if (x2 > x1 && x2 < x3) { 243 //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 244 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 245 item.setCurrentY(getYAxis().toRealValue(y)); 246 item.setCurrentX(getXAxis().toRealValue(x2)); 247 } else { 248 //2. we can simply use the midpoint on the line as well.. 249 double x = (x3 + x1)/2; 250 double y = (y3 + y1)/2; 251 item.setCurrentX(getXAxis().toRealValue(x)); 252 item.setCurrentY(getYAxis().toRealValue(y)); 253 } 254 } 255 } else if (itemIndex == 0 && series.getData().size() > 1) { 256 animate = true; 257 item.setCurrentX(series.getData().get(1).getXValue()); 258 item.setCurrentY(series.getData().get(1).getYValue()); 259 } else if (itemIndex == (series.getData().size() - 1) && series.getData().size() > 1) { 260 animate = true; 261 int last = series.getData().size() - 2; 262 item.setCurrentX(series.getData().get(last).getXValue()); 263 item.setCurrentY(series.getData().get(last).getYValue()); 264 } else if(symbol != null) { 265 // fade in new symbol 266 symbol.setOpacity(0); 267 getPlotChildren().add(symbol); 268 FadeTransition ft = new FadeTransition(Duration.millis(500),symbol); 269 ft.setToValue(1); 270 ft.play(); 271 } 272 if (animate) { 273 animate( 274 new KeyFrame(Duration.ZERO, 275 (e) -> { if (symbol != null && !getPlotChildren().contains(symbol)) getPlotChildren().add(symbol); }, 276 new KeyValue(item.currentYProperty(), 277 item.getCurrentY()), 278 new KeyValue(item.currentXProperty(), 279 item.getCurrentX())), 280 new KeyFrame(Duration.millis(700), new KeyValue(item.currentYProperty(), 281 item.getYValue(), Interpolator.EASE_BOTH), 282 new KeyValue(item.currentXProperty(), 283 item.getXValue(), Interpolator.EASE_BOTH)) 284 ); 285 } 286 287 } else { 288 if (symbol != null) getPlotChildren().add(symbol); 289 } 290 } 291 292 @Override protected void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) { 293 final Node symbol = item.getNode(); 294 295 if (symbol != null) { 296 symbol.focusTraversableProperty().unbind(); 297 } 298 299 // remove item from sorted list 300 int itemIndex = series.getItemIndex(item); 301 if (shouldAnimate()) { 302 XYValueMap.clear(); 303 boolean animate = false; 304 // dataSize represents size of currently visible data. After this operation, the number will decrement by 1 305 final int dataSize = series.getDataSize(); 306 // This is the size of current data list in Series. Note that it might be totaly different from dataSize as 307 // some big operation might have happened on the list. 308 final int dataListSize = series.getData().size(); 309 if (itemIndex > 0 && itemIndex < dataSize - 1) { 310 animate = true; 311 Data<X,Y> p1 = series.getItem(itemIndex - 1); 312 Data<X,Y> p2 = series.getItem(itemIndex + 1); 313 double x1 = getXAxis().toNumericValue(p1.getXValue()); 314 double y1 = getYAxis().toNumericValue(p1.getYValue()); 315 double x3 = getXAxis().toNumericValue(p2.getXValue()); 316 double y3 = getYAxis().toNumericValue(p2.getYValue()); 317 318 double x2 = getXAxis().toNumericValue(item.getXValue()); 319 double y2 = getYAxis().toNumericValue(item.getYValue()); 320 if (x2 > x1 && x2 < x3) { 321 // //1. y intercept of the line : y = ((y3-y1)/(x3-x1)) * x2 + (x3y1 - y3x1)/(x3 -x1) 322 double y = ((y3-y1)/(x3-x1)) * x2 + (x3*y1 - y3*x1)/(x3-x1); 323 item.setCurrentX(getXAxis().toRealValue(x2)); 324 item.setCurrentY(getYAxis().toRealValue(y2)); 325 item.setXValue(getXAxis().toRealValue(x2)); 326 item.setYValue(getYAxis().toRealValue(y)); 327 } else { 328 //2. we can simply use the midpoint on the line as well.. 329 double x = (x3 + x1)/2; 330 double y = (y3 + y1)/2; 331 item.setCurrentX(getXAxis().toRealValue(x)); 332 item.setCurrentY(getYAxis().toRealValue(y)); 333 } 334 } else if (itemIndex == 0 && dataListSize > 1) { 335 animate = true; 336 item.setXValue(series.getData().get(0).getXValue()); 337 item.setYValue(series.getData().get(0).getYValue()); 338 } else if (itemIndex == (dataSize - 1) && dataListSize > 1) { 339 animate = true; 340 int last = dataListSize - 1; 341 item.setXValue(series.getData().get(last).getXValue()); 342 item.setYValue(series.getData().get(last).getYValue()); 343 } else if (symbol != null) { 344 // fade out symbol 345 fadeSymbolTransition = new FadeTransition(Duration.millis(500),symbol); 346 fadeSymbolTransition.setToValue(0); 347 fadeSymbolTransition.setOnFinished(actionEvent -> { 348 item.setSeries(null); 349 getPlotChildren().remove(symbol); 350 removeDataItemFromDisplay(series, item); 351 symbol.setOpacity(1.0); 352 }); 353 fadeSymbolTransition.play(); 354 } else { 355 item.setSeries(null); 356 removeDataItemFromDisplay(series, item); 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 @Override 567 LegendItem createLegendItemForSeries(Series<X, Y> series, int seriesIndex) { 568 LegendItem legendItem = new LegendItem(series.getName()); 569 legendItem.getSymbol().getStyleClass().addAll("chart-line-symbol", "series" + seriesIndex, 570 series.defaultColorStyleClass); 571 return legendItem; 572 } 573 574 // -------------- STYLESHEET HANDLING -------------------------------------- 575 576 private static class StyleableProperties { 577 private static final CssMetaData<LineChart<?,?>,Boolean> CREATE_SYMBOLS = 578 new CssMetaData<LineChart<?,?>,Boolean>("-fx-create-symbols", 579 BooleanConverter.getInstance(), Boolean.TRUE) { 580 581 @Override 582 public boolean isSettable(LineChart<?,?> node) { 583 return node.createSymbols == null || !node.createSymbols.isBound(); 584 } 585 586 @Override 587 public StyleableProperty<Boolean> getStyleableProperty(LineChart<?,?> node) { 588 return (StyleableProperty<Boolean>)(WritableValue<Boolean>)node.createSymbolsProperty(); 589 } 590 }; 591 592 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 593 static { 594 final List<CssMetaData<? extends Styleable, ?>> styleables = 595 new ArrayList<CssMetaData<? extends Styleable, ?>>(XYChart.getClassCssMetaData()); 596 styleables.add(CREATE_SYMBOLS); 597 STYLEABLES = Collections.unmodifiableList(styleables); 598 } 599 } 600 601 /** 602 * @return The CssMetaData associated with this class, which may include the 603 * CssMetaData of its superclasses. 604 * @since JavaFX 8.0 605 */ 606 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 607 return StyleableProperties.STYLEABLES; 608 } 609 610 /** 611 * {@inheritDoc} 612 * @since JavaFX 8.0 613 */ 614 @Override 615 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 616 return getClassCssMetaData(); 617 } 618 619 /** 620 * This enum defines a policy for {@link LineChart#axisSortingPolicyProperty()}. 621 * @since JavaFX 8u40 622 */ 623 public static enum SortingPolicy { 624 /** 625 * The data should be left in the order defined by the list in {@link javafx.scene.chart.LineChart#dataProperty()}. 626 */ 627 NONE, 628 /** 629 * The data is ordered by x axis. 630 */ 631 X_AXIS, 632 /** 633 * The data is ordered by y axis. 634 */ 635 Y_AXIS 636 } 637 }