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