1 /* 2 * Copyright (c) 2011, 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 29 import java.util.*; 30 31 import javafx.animation.*; 32 import javafx.application.Platform; 33 import javafx.beans.NamedArg; 34 import javafx.beans.property.DoubleProperty; 35 import javafx.beans.value.WritableValue; 36 import javafx.collections.FXCollections; 37 import javafx.collections.ObservableList; 38 import javafx.geometry.Orientation; 39 import javafx.scene.AccessibleRole; 40 import javafx.scene.Node; 41 import javafx.scene.layout.StackPane; 42 import javafx.util.Duration; 43 44 import com.sun.javafx.charts.Legend; 45 46 import javafx.css.StyleableDoubleProperty; 47 import javafx.css.CssMetaData; 48 import javafx.css.PseudoClass; 49 50 import javafx.css.converter.SizeConverter; 51 52 import javafx.collections.ListChangeListener; 53 import javafx.css.Styleable; 54 import javafx.css.StyleableProperty; 55 56 57 /** 58 * StackedBarChart is a variation of {@link BarChart} that plots bars indicating 59 * data values for a category. The bars can be vertical or horizontal depending 60 * on which axis is a category axis. 61 * The bar for each series is stacked on top of the previous series. 62 * @since JavaFX 2.1 63 */ 64 public class StackedBarChart<X, Y> extends XYChart<X, Y> { 65 66 // -------------- PRIVATE FIELDS ------------------------------------------- 67 private Map<Series, Map<String, List<Data<X, Y>>>> seriesCategoryMap = 68 new HashMap<Series, Map<String, List<Data<X, Y>>>>(); 69 private Legend legend = new Legend(); 70 private final Orientation orientation; 71 private CategoryAxis categoryAxis; 72 private ValueAxis valueAxis; 73 // RT-23125 handling data removal when a category is removed. 74 private ListChangeListener<String> categoriesListener = new ListChangeListener<String>() { 75 @Override public void onChanged(ListChangeListener.Change<? extends String> c) { 76 while (c.next()) { 77 for(String cat : c.getRemoved()) { 78 for (Series<X,Y> series : getData()) { 79 for (Data<X, Y> data : series.getData()) { 80 if ((cat).equals((orientation == orientation.VERTICAL) ? 81 data.getXValue() : data.getYValue())) { 82 boolean animatedOn = getAnimated(); 83 setAnimated(false); 84 dataItemRemoved(data, series); 85 setAnimated(animatedOn); 86 } 87 } 88 } 89 requestChartLayout(); 90 } 91 } 92 } 93 }; 94 95 // -------------- PUBLIC PROPERTIES ---------------------------------------- 96 /** The gap to leave between bars in separate categories */ 97 private DoubleProperty categoryGap = new StyleableDoubleProperty(10) { 98 @Override protected void invalidated() { 99 get(); 100 requestChartLayout(); 101 } 102 103 @Override 104 public Object getBean() { 105 return StackedBarChart.this; 106 } 107 108 @Override 109 public String getName() { 110 return "categoryGap"; 111 } 112 113 public CssMetaData<StackedBarChart<?,?>,Number> getCssMetaData() { 114 return StackedBarChart.StyleableProperties.CATEGORY_GAP; 115 } 116 }; 117 118 public double getCategoryGap() { 119 return categoryGap.getValue(); 120 } 121 122 public void setCategoryGap(double value) { 123 categoryGap.setValue(value); 124 } 125 126 public DoubleProperty categoryGapProperty() { 127 return categoryGap; 128 } 129 130 // -------------- CONSTRUCTOR ---------------------------------------------- 131 /** 132 * Construct a new StackedBarChart with the given axis. The two axis should be a ValueAxis/NumberAxis and a CategoryAxis, 133 * they can be in either order depending on if you want a horizontal or vertical bar chart. 134 * 135 * @param xAxis The x axis to use 136 * @param yAxis The y axis to use 137 */ 138 public StackedBarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) { 139 this(xAxis, yAxis, FXCollections.<Series<X, Y>>observableArrayList()); 140 } 141 142 /** 143 * Construct a new StackedBarChart with the given axis and data. The two axis should be a ValueAxis/NumberAxis and a 144 * CategoryAxis, they can be in either order depending on if you want a horizontal or vertical bar chart. 145 * 146 * @param xAxis The x axis to use 147 * @param yAxis The y axis to use 148 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 149 */ 150 public StackedBarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X, Y>> data) { 151 super(xAxis, yAxis); 152 getStyleClass().add("stacked-bar-chart"); 153 setLegend(legend); 154 if (!((xAxis instanceof ValueAxis && yAxis instanceof CategoryAxis) 155 || (yAxis instanceof ValueAxis && xAxis instanceof CategoryAxis))) { 156 throw new IllegalArgumentException("Axis type incorrect, one of X,Y should be CategoryAxis and the other NumberAxis"); 157 } 158 if (xAxis instanceof CategoryAxis) { 159 categoryAxis = (CategoryAxis) xAxis; 160 valueAxis = (ValueAxis) yAxis; 161 orientation = Orientation.VERTICAL; 162 } else { 163 categoryAxis = (CategoryAxis) yAxis; 164 valueAxis = (ValueAxis) xAxis; 165 orientation = Orientation.HORIZONTAL; 166 } 167 // update css 168 pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, orientation == Orientation.HORIZONTAL); 169 pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE, orientation == Orientation.VERTICAL); 170 setData(data); 171 categoryAxis.getCategories().addListener(categoriesListener); 172 } 173 174 /** 175 * Construct a new StackedBarChart with the given axis and data. The two axis should be a ValueAxis/NumberAxis and a 176 * CategoryAxis, they can be in either order depending on if you want a horizontal or vertical bar chart. 177 * 178 * @param xAxis The x axis to use 179 * @param yAxis The y axis to use 180 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 181 * @param categoryGap The gap to leave between bars in separate categories 182 */ 183 public StackedBarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X, Y>> data, @NamedArg("categoryGap") double categoryGap) { 184 this(xAxis, yAxis); 185 setData(data); 186 setCategoryGap(categoryGap); 187 } 188 189 // -------------- METHODS -------------------------------------------------- 190 @Override protected void dataItemAdded(Series<X, Y> series, int itemIndex, Data<X, Y> item) { 191 String category; 192 if (orientation == Orientation.VERTICAL) { 193 category = (String) item.getXValue(); 194 } else { 195 category = (String) item.getYValue(); 196 } 197 // Don't plot if category does not already exist ? 198 // if (!categoryAxis.getCategories().contains(category)) return; 199 200 Map<String, List<Data<X, Y>>> categoryMap = seriesCategoryMap.get(series); 201 202 if (categoryMap == null) { 203 categoryMap = new HashMap<String, List<Data<X, Y>>>(); 204 seriesCategoryMap.put(series, categoryMap); 205 } 206 // list to hold more that one bar "positive and negative" 207 List<Data<X, Y>> itemList = categoryMap.get(category) != null ? categoryMap.get(category) : new ArrayList<Data<X, Y>>(); 208 itemList.add(item); 209 categoryMap.put(category, itemList); 210 // categoryMap.put(category, item); 211 Node bar = createBar(series, getData().indexOf(series), item, itemIndex); 212 if (shouldAnimate()) { 213 animateDataAdd(item, bar); 214 } else { 215 getPlotChildren().add(bar); 216 } 217 } 218 219 @Override protected void dataItemRemoved(final Data<X, Y> item, final Series<X, Y> series) { 220 final Node bar = item.getNode(); 221 222 if (bar != null) { 223 bar.focusTraversableProperty().unbind(); 224 } 225 226 if (shouldAnimate()) { 227 Timeline t = createDataRemoveTimeline(item, bar, series); 228 t.setOnFinished(event -> { 229 removeDataItemFromDisplay(series, item); 230 }); 231 t.play(); 232 } else { 233 getPlotChildren().remove(bar); 234 removeDataItemFromDisplay(series, item); 235 } 236 } 237 238 /** @inheritDoc */ 239 @Override protected void dataItemChanged(Data<X, Y> item) { 240 double barVal; 241 double currentVal; 242 if (orientation == Orientation.VERTICAL) { 243 barVal = ((Number) item.getYValue()).doubleValue(); 244 currentVal = ((Number) getCurrentDisplayedYValue(item)).doubleValue(); 245 } else { 246 barVal = ((Number) item.getXValue()).doubleValue(); 247 currentVal = ((Number) getCurrentDisplayedXValue(item)).doubleValue(); 248 } 249 if (currentVal > 0 && barVal < 0) { // going from positive to negative 250 // add style class negative 251 item.getNode().getStyleClass().add("negative"); 252 } else if (currentVal < 0 && barVal > 0) { // going from negative to positive 253 // remove style class negative 254 item.getNode().getStyleClass().remove("negative"); 255 } 256 } 257 258 private void animateDataAdd(Data<X, Y> item, Node bar) { 259 double barVal; 260 if (orientation == Orientation.VERTICAL) { 261 barVal = ((Number) item.getYValue()).doubleValue(); 262 if (barVal < 0) { 263 bar.getStyleClass().add("negative"); 264 } 265 item.setYValue(getYAxis().toRealValue(getYAxis().getZeroPosition())); 266 setCurrentDisplayedYValue(item, getYAxis().toRealValue(getYAxis().getZeroPosition())); 267 getPlotChildren().add(bar); 268 item.setYValue(getYAxis().toRealValue(barVal)); 269 animate(new Timeline( 270 new KeyFrame(Duration.ZERO, new KeyValue(currentDisplayedYValueProperty(item), getCurrentDisplayedYValue(item))), 271 new KeyFrame(Duration.millis(700), new KeyValue(currentDisplayedYValueProperty(item), item.getYValue(), Interpolator.EASE_BOTH))) 272 ); 273 } else { 274 barVal = ((Number) item.getXValue()).doubleValue(); 275 if (barVal < 0) { 276 bar.getStyleClass().add("negative"); 277 } 278 item.setXValue(getXAxis().toRealValue(getXAxis().getZeroPosition())); 279 setCurrentDisplayedXValue(item, getXAxis().toRealValue(getXAxis().getZeroPosition())); 280 getPlotChildren().add(bar); 281 item.setXValue(getXAxis().toRealValue(barVal)); 282 animate(new Timeline( 283 new KeyFrame(Duration.ZERO, new KeyValue(currentDisplayedXValueProperty(item), getCurrentDisplayedXValue(item))), 284 new KeyFrame(Duration.millis(700), new KeyValue(currentDisplayedXValueProperty(item), item.getXValue(), Interpolator.EASE_BOTH))) 285 ); 286 } 287 } 288 289 @Override protected void seriesChanged(ListChangeListener.Change<? extends Series> c) { 290 // Update style classes for all series lines and symbols 291 // Note: is there a more efficient way of doing this? 292 for (int i = 0; i < getDataSize(); i++) { 293 final Series<X,Y> series = getData().get(i); 294 for (int j=0; j<series.getData().size(); j++) { 295 Data<X,Y> item = series.getData().get(j); 296 Node bar = item.getNode(); 297 bar.getStyleClass().setAll("chart-bar", "series" + i, "data" + j, series.defaultColorStyleClass); 298 } 299 } 300 } 301 302 /** @inheritDoc */ 303 @Override protected void seriesAdded(Series<X, Y> series, int seriesIndex) { 304 // handle any data already in series 305 // create entry in the map 306 Map<String, List<Data<X, Y>>> categoryMap = new HashMap<String, List<Data<X, Y>>>(); 307 for (int j = 0; j < series.getData().size(); j++) { 308 Data<X, Y> item = series.getData().get(j); 309 Node bar = createBar(series, seriesIndex, item, j); 310 String category; 311 if (orientation == Orientation.VERTICAL) { 312 category = (String) item.getXValue(); 313 } else { 314 category = (String) item.getYValue(); 315 } 316 // list of two item positive and negative 317 List<Data<X, Y>> itemList = categoryMap.get(category) != null ? categoryMap.get(category) : new ArrayList<Data<X, Y>>(); 318 itemList.add(item); 319 categoryMap.put(category, itemList); 320 if (shouldAnimate()) { 321 animateDataAdd(item, bar); 322 } else { 323 double barVal = (orientation == Orientation.VERTICAL) ? ((Number)item.getYValue()).doubleValue() : 324 ((Number)item.getXValue()).doubleValue(); 325 if (barVal < 0) { 326 bar.getStyleClass().add("negative"); 327 } 328 getPlotChildren().add(bar); 329 } 330 } 331 if (categoryMap.size() > 0) { 332 seriesCategoryMap.put(series, categoryMap); 333 } 334 } 335 336 private Timeline createDataRemoveTimeline(Data<X, Y> item, final Node bar, final Series<X, Y> series) { 337 Timeline t = new Timeline(); 338 if (orientation == Orientation.VERTICAL) { 339 item.setYValue(getYAxis().toRealValue(getYAxis().getZeroPosition())); 340 t.getKeyFrames().addAll( 341 new KeyFrame(Duration.ZERO, new KeyValue(currentDisplayedYValueProperty(item), 342 getCurrentDisplayedYValue(item))), 343 new KeyFrame(Duration.millis(700), actionEvent -> { 344 getPlotChildren().remove(bar); 345 }, 346 new KeyValue(currentDisplayedYValueProperty(item), item.getYValue(), Interpolator.EASE_BOTH))); 347 } else { 348 item.setXValue(getXAxis().toRealValue(getXAxis().getZeroPosition())); 349 t.getKeyFrames().addAll( 350 new KeyFrame(Duration.ZERO, new KeyValue(currentDisplayedXValueProperty(item), 351 getCurrentDisplayedXValue(item))), 352 new KeyFrame(Duration.millis(700), actionEvent -> { 353 getPlotChildren().remove(bar); 354 }, 355 new KeyValue(currentDisplayedXValueProperty(item), item.getXValue(), Interpolator.EASE_BOTH))); 356 } 357 return t; 358 } 359 360 @Override protected void seriesRemoved(final Series<X, Y> series) { 361 // remove all symbol nodes 362 if (shouldAnimate()) { 363 ParallelTransition pt = new ParallelTransition(); 364 pt.setOnFinished(event -> { 365 removeSeriesFromDisplay(series); 366 requestChartLayout(); 367 }); 368 for (Data<X, Y> d : series.getData()) { 369 final Node bar = d.getNode(); 370 // Animate series deletion 371 if (getSeriesSize() > 1) { 372 for (int j = 0; j < series.getData().size(); j++) { 373 Data<X, Y> item = series.getData().get(j); 374 Timeline t = createDataRemoveTimeline(item, bar, series); 375 pt.getChildren().add(t); 376 } 377 } else { 378 // fade out last series 379 FadeTransition ft = new FadeTransition(Duration.millis(700), bar); 380 ft.setFromValue(1); 381 ft.setToValue(0); 382 ft.setOnFinished(actionEvent -> { 383 getPlotChildren().remove(bar); 384 bar.setOpacity(1.0); 385 }); 386 pt.getChildren().add(ft); 387 } 388 } 389 pt.play(); 390 } else { 391 for (Data<X, Y> d : series.getData()) { 392 final Node bar = d.getNode(); 393 getPlotChildren().remove(bar); 394 } 395 removeSeriesFromDisplay(series); 396 requestChartLayout(); 397 } 398 } 399 400 /** @inheritDoc */ 401 @Override protected void updateAxisRange() { 402 // This override is necessary to update axis range based on cumulative Y value for the 403 // Y axis instead of the inherited way where the max value in the data range is used. 404 boolean categoryIsX = categoryAxis == getXAxis(); 405 if (categoryAxis.isAutoRanging()) { 406 List cData = new ArrayList(); 407 for (Series<X, Y> series : getData()) { 408 for (Data<X, Y> data : series.getData()) { 409 if (data != null) cData.add(categoryIsX ? data.getXValue() : data.getYValue()); 410 } 411 } 412 categoryAxis.invalidateRange(cData); 413 } 414 if (valueAxis.isAutoRanging()) { 415 List<Number> vData = new ArrayList<>(); 416 for (String category : categoryAxis.getAllDataCategories()) { 417 double totalXN = 0; 418 double totalXP = 0; 419 Iterator<Series<X, Y>> seriesIterator = getDisplayedSeriesIterator(); 420 while (seriesIterator.hasNext()) { 421 Series<X, Y> series = seriesIterator.next(); 422 for (final Data<X, Y> item : getDataItem(series, category)) { 423 if (item != null) { 424 boolean isNegative = item.getNode().getStyleClass().contains("negative"); 425 Number value = (Number) (categoryIsX ? item.getYValue() : item.getXValue()); 426 if (!isNegative) { 427 totalXP += valueAxis.toNumericValue(value); 428 } else { 429 totalXN += valueAxis.toNumericValue(value); 430 } 431 } 432 } 433 } 434 vData.add(totalXP); 435 vData.add(totalXN); 436 } 437 valueAxis.invalidateRange(vData); 438 } 439 } 440 441 /** @inheritDoc */ 442 @Override protected void layoutPlotChildren() { 443 double catSpace = categoryAxis.getCategorySpacing(); 444 // calculate bar spacing 445 final double availableBarSpace = catSpace - getCategoryGap(); 446 final double barWidth = availableBarSpace; 447 final double barOffset = -((catSpace - getCategoryGap()) / 2); 448 final double lowerBoundValue = valueAxis.getLowerBound(); 449 final double upperBoundValue = valueAxis.getUpperBound(); 450 // update bar positions and sizes 451 for (String category : categoryAxis.getCategories()) { 452 double currentPositiveValue = 0; 453 double currentNegativeValue = 0; 454 Iterator<Series<X, Y>> seriesIterator = getDisplayedSeriesIterator(); 455 while (seriesIterator.hasNext()) { 456 Series<X, Y> series = seriesIterator.next(); 457 for (final Data<X, Y> item : getDataItem(series, category)) { 458 if (item != null) { 459 final Node bar = item.getNode(); 460 final double categoryPos; 461 final double valNumber; 462 final X xValue = getCurrentDisplayedXValue(item); 463 final Y yValue = getCurrentDisplayedYValue(item); 464 if (orientation == Orientation.VERTICAL) { 465 categoryPos = getXAxis().getDisplayPosition(xValue); 466 valNumber = getYAxis().toNumericValue(yValue); 467 } else { 468 categoryPos = getYAxis().getDisplayPosition(yValue); 469 valNumber = getXAxis().toNumericValue(xValue); 470 } 471 double bottom; 472 double top; 473 boolean isNegative = bar.getStyleClass().contains("negative"); 474 if (!isNegative) { 475 bottom = valueAxis.getDisplayPosition(currentPositiveValue); 476 top = valueAxis.getDisplayPosition(currentPositiveValue + valNumber); 477 currentPositiveValue += valNumber; 478 } else { 479 bottom = valueAxis.getDisplayPosition(currentNegativeValue + valNumber); 480 top = valueAxis.getDisplayPosition(currentNegativeValue); 481 currentNegativeValue += valNumber; 482 } 483 484 if (orientation == Orientation.VERTICAL) { 485 bar.resizeRelocate(categoryPos + barOffset, 486 top, barWidth, bottom - top); 487 } else { 488 bar.resizeRelocate(bottom, 489 categoryPos + barOffset, 490 top - bottom, barWidth); 491 } 492 } 493 } 494 } 495 } 496 } 497 498 /** 499 * Computes the size of series linked list 500 * @return size of series linked list 501 */ 502 @Override int getSeriesSize() { 503 int count = 0; 504 Iterator<Series<X, Y>> seriesIterator = getDisplayedSeriesIterator(); 505 while (seriesIterator.hasNext()) { 506 seriesIterator.next(); 507 count++; 508 } 509 return count; 510 } 511 512 /** 513 * This is called whenever a series is added or removed and the legend needs to be updated 514 */ 515 @Override protected void updateLegend() { 516 legend.getItems().clear(); 517 if (getData() != null) { 518 for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) { 519 Series<X,Y> series = getData().get(seriesIndex); 520 Legend.LegendItem legenditem = new Legend.LegendItem(series.getName()); 521 legenditem.getSymbol().getStyleClass().addAll("chart-bar", "series" + seriesIndex, "bar-legend-symbol", 522 series.defaultColorStyleClass); 523 legend.getItems().add(legenditem); 524 } 525 } 526 if (legend.getItems().size() > 0) { 527 if (getLegend() == null) { 528 setLegend(legend); 529 } 530 } else { 531 setLegend(null); 532 } 533 } 534 535 private Node createBar(Series series, int seriesIndex, final Data item, int itemIndex) { 536 Node bar = item.getNode(); 537 if (bar == null) { 538 bar = new StackPane(); 539 bar.setAccessibleRole(AccessibleRole.TEXT); 540 bar.setAccessibleRoleDescription("Bar"); 541 bar.focusTraversableProperty().bind(Platform.accessibilityActiveProperty()); 542 item.setNode(bar); 543 } 544 bar.getStyleClass().setAll("chart-bar", "series" + seriesIndex, "data" + itemIndex, series.defaultColorStyleClass); 545 return bar; 546 } 547 548 private List<Data<X, Y>> getDataItem(Series<X, Y> series, String category) { 549 Map<String, List<Data<X, Y>>> catmap = seriesCategoryMap.get(series); 550 return catmap != null ? catmap.get(category) != null ? 551 catmap.get(category) : new ArrayList<Data<X, Y>>() : new ArrayList<Data<X, Y>>(); 552 } 553 554 // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------ 555 556 /* 557 * Super-lazy instantiation pattern from Bill Pugh. 558 */ 559 private static class StyleableProperties { 560 561 private static final CssMetaData<StackedBarChart<?,?>,Number> CATEGORY_GAP = 562 new CssMetaData<StackedBarChart<?,?>,Number>("-fx-category-gap", 563 SizeConverter.getInstance(), 10.0) { 564 565 @Override 566 public boolean isSettable(StackedBarChart<?,?> node) { 567 return node.categoryGap == null || !node.categoryGap.isBound(); 568 } 569 570 @Override 571 public StyleableProperty<Number> getStyleableProperty(StackedBarChart<?,?> node) { 572 return (StyleableProperty<Number>)(WritableValue<Number>)node.categoryGapProperty(); 573 } 574 }; 575 576 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 577 static { 578 579 final List<CssMetaData<? extends Styleable, ?>> styleables = 580 new ArrayList<CssMetaData<? extends Styleable, ?>>(XYChart.getClassCssMetaData()); 581 styleables.add(CATEGORY_GAP); 582 STYLEABLES = Collections.unmodifiableList(styleables); 583 } 584 } 585 586 /** 587 * @return The CssMetaData associated with this class, which may include the 588 * CssMetaData of its super classes. 589 * @since JavaFX 8.0 590 */ 591 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 592 return StyleableProperties.STYLEABLES; 593 } 594 595 /** 596 * {@inheritDoc} 597 * @since JavaFX 8.0 598 */ 599 @Override 600 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 601 return getClassCssMetaData(); 602 } 603 604 /** Pseudoclass indicating this is a vertical chart. */ 605 private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE = 606 PseudoClass.getPseudoClass("vertical"); 607 /** Pseudoclass indicating this is a horizontal chart. */ 608 private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE = 609 PseudoClass.getPseudoClass("horizontal"); 610 611 }