1 /* 2 * Copyright (c) 2011, 2014, 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 if (shouldAnimate()) { 224 Timeline t = createDataRemoveTimeline(item, bar, series); 225 t.setOnFinished(event -> { 226 removeDataItemFromDisplay(series, item); 227 }); 228 t.play(); 229 } else { 230 getPlotChildren().remove(bar); 231 removeDataItemFromDisplay(series, item); 232 } 233 } 234 235 /** @inheritDoc */ 236 @Override protected void dataItemChanged(Data<X, Y> item) { 237 double barVal; 238 double currentVal; 239 if (orientation == Orientation.VERTICAL) { 240 barVal = ((Number) item.getYValue()).doubleValue(); 241 currentVal = ((Number) getCurrentDisplayedYValue(item)).doubleValue(); 242 } else { 243 barVal = ((Number) item.getXValue()).doubleValue(); 244 currentVal = ((Number) getCurrentDisplayedXValue(item)).doubleValue(); 245 } 246 if (currentVal > 0 && barVal < 0) { // going from positive to negative 247 // add style class negative 248 item.getNode().getStyleClass().add("negative"); 249 } else if (currentVal < 0 && barVal > 0) { // going from negative to positive 250 // remove style class negative 251 item.getNode().getStyleClass().remove("negative"); 252 } 253 } 254 255 private void animateDataAdd(Data<X, Y> item, Node bar) { 256 double barVal; 257 if (orientation == Orientation.VERTICAL) { 258 barVal = ((Number) item.getYValue()).doubleValue(); 259 if (barVal < 0) { 260 bar.getStyleClass().add("negative"); 261 } 262 item.setYValue(getYAxis().toRealValue(getYAxis().getZeroPosition())); 263 setCurrentDisplayedYValue(item, getYAxis().toRealValue(getYAxis().getZeroPosition())); 264 getPlotChildren().add(bar); 265 item.setYValue(getYAxis().toRealValue(barVal)); 266 animate(new Timeline( 267 new KeyFrame(Duration.ZERO, new KeyValue(currentDisplayedYValueProperty(item), getCurrentDisplayedYValue(item))), 268 new KeyFrame(Duration.millis(700), new KeyValue(currentDisplayedYValueProperty(item), item.getYValue(), Interpolator.EASE_BOTH))) 269 ); 270 } else { 271 barVal = ((Number) item.getXValue()).doubleValue(); 272 if (barVal < 0) { 273 bar.getStyleClass().add("negative"); 274 } 275 item.setXValue(getXAxis().toRealValue(getXAxis().getZeroPosition())); 276 setCurrentDisplayedXValue(item, getXAxis().toRealValue(getXAxis().getZeroPosition())); 277 getPlotChildren().add(bar); 278 item.setXValue(getXAxis().toRealValue(barVal)); 279 animate(new Timeline( 280 new KeyFrame(Duration.ZERO, new KeyValue(currentDisplayedXValueProperty(item), getCurrentDisplayedXValue(item))), 281 new KeyFrame(Duration.millis(700), new KeyValue(currentDisplayedXValueProperty(item), item.getXValue(), Interpolator.EASE_BOTH))) 282 ); 283 } 284 } 285 286 /** @inheritDoc */ 287 @Override protected void seriesAdded(Series<X, Y> series, int seriesIndex) { 288 String defaultColorStyleClass = "default-color" + (seriesDefaultColorIndex % 8); 289 seriesDefaultColorMap.put(series, defaultColorStyleClass); 290 seriesDefaultColorIndex++; 291 // handle any data already in series 292 // create entry in the map 293 Map<String, List<Data<X, Y>>> categoryMap = new HashMap<String, List<Data<X, Y>>>(); 294 for (int j = 0; j < series.getData().size(); j++) { 295 Data<X, Y> item = series.getData().get(j); 296 Node bar = createBar(series, seriesIndex, item, j); 297 String category; 298 if (orientation == Orientation.VERTICAL) { 299 category = (String) item.getXValue(); 300 } else { 301 category = (String) item.getYValue(); 302 } 303 // list of two item positive and negative 304 List<Data<X, Y>> itemList = categoryMap.get(category) != null ? categoryMap.get(category) : new ArrayList<Data<X, Y>>(); 305 itemList.add(item); 306 categoryMap.put(category, itemList); 307 if (shouldAnimate()) { 308 animateDataAdd(item, bar); 309 } else { 310 double barVal = (orientation == Orientation.VERTICAL) ? ((Number)item.getYValue()).doubleValue() : 311 ((Number)item.getXValue()).doubleValue(); 312 if (barVal < 0) { 313 bar.getStyleClass().add("negative"); 314 } 315 getPlotChildren().add(bar); 316 } 317 } 318 if (categoryMap.size() > 0) { 319 seriesCategoryMap.put(series, categoryMap); 320 } 321 } 322 323 private Timeline createDataRemoveTimeline(Data<X, Y> item, final Node bar, final Series<X, Y> series) { 324 Timeline t = new Timeline(); 325 if (orientation == Orientation.VERTICAL) { 326 item.setYValue(getYAxis().toRealValue(getYAxis().getZeroPosition())); 327 t.getKeyFrames().addAll( 328 new KeyFrame(Duration.ZERO, new KeyValue(currentDisplayedYValueProperty(item), 329 getCurrentDisplayedYValue(item))), 330 new KeyFrame(Duration.millis(700), actionEvent -> { 331 getPlotChildren().remove(bar); 332 }, 333 new KeyValue(currentDisplayedYValueProperty(item), item.getYValue(), Interpolator.EASE_BOTH))); 334 } else { 335 item.setXValue(getXAxis().toRealValue(getXAxis().getZeroPosition())); 336 t.getKeyFrames().addAll( 337 new KeyFrame(Duration.ZERO, new KeyValue(currentDisplayedXValueProperty(item), 338 getCurrentDisplayedXValue(item))), 339 new KeyFrame(Duration.millis(700), actionEvent -> { 340 getPlotChildren().remove(bar); 341 }, 342 new KeyValue(currentDisplayedXValueProperty(item), item.getXValue(), Interpolator.EASE_BOTH))); 343 } 344 return t; 345 } 346 347 @Override protected void seriesRemoved(final Series<X, Y> series) { 348 // Added for RT-40104 349 seriesDefaultColorIndex--; 350 351 // remove all symbol nodes 352 if (shouldAnimate()) { 353 ParallelTransition pt = new ParallelTransition(); 354 pt.setOnFinished(event -> { 355 removeSeriesFromDisplay(series); 356 requestChartLayout(); 357 }); 358 for (Data<X, Y> d : series.getData()) { 359 final Node bar = d.getNode(); 360 // Animate series deletion 361 if (getSeriesSize() > 1) { 362 for (int j = 0; j < series.getData().size(); j++) { 363 Data<X, Y> item = series.getData().get(j); 364 Timeline t = createDataRemoveTimeline(item, bar, series); 365 pt.getChildren().add(t); 366 } 367 } else { 368 // fade out last series 369 FadeTransition ft = new FadeTransition(Duration.millis(700), bar); 370 ft.setFromValue(1); 371 ft.setToValue(0); 372 ft.setOnFinished(actionEvent -> { 373 getPlotChildren().remove(bar); 374 bar.setOpacity(1.0); 375 }); 376 pt.getChildren().add(ft); 377 } 378 } 379 pt.play(); 380 } else { 381 for (Data<X, Y> d : series.getData()) { 382 final Node bar = d.getNode(); 383 getPlotChildren().remove(bar); 384 } 385 removeSeriesFromDisplay(series); 386 requestChartLayout(); 387 } 388 } 389 390 /** @inheritDoc */ 391 @Override protected void updateAxisRange() { 392 // This override is necessary to update axis range based on cumulative Y value for the 393 // Y axis instead of the inherited way where the max value in the data range is used. 394 boolean categoryIsX = categoryAxis == getXAxis(); 395 if (categoryAxis.isAutoRanging()) { 396 List cData = new ArrayList(); 397 for (Series<X, Y> series : getData()) { 398 for (Data<X, Y> data : series.getData()) { 399 if (data != null) cData.add(categoryIsX ? data.getXValue() : data.getYValue()); 400 } 401 } 402 categoryAxis.invalidateRange(cData); 403 } 404 if (valueAxis.isAutoRanging()) { 405 List<Number> vData = new ArrayList<>(); 406 for (String category : categoryAxis.getAllDataCategories()) { 407 double totalXN = 0; 408 double totalXP = 0; 409 Iterator<Series<X, Y>> seriesIterator = getDisplayedSeriesIterator(); 410 while (seriesIterator.hasNext()) { 411 Series<X, Y> series = seriesIterator.next(); 412 for (final Data<X, Y> item : getDataItem(series, category)) { 413 if (item != null) { 414 boolean isNegative = item.getNode().getStyleClass().contains("negative"); 415 Number value = (Number) (categoryIsX ? item.getYValue() : item.getXValue()); 416 if (!isNegative) { 417 totalXP += valueAxis.toNumericValue(value); 418 } else { 419 totalXN += valueAxis.toNumericValue(value); 420 } 421 } 422 } 423 } 424 vData.add(totalXP); 425 vData.add(totalXN); 426 } 427 valueAxis.invalidateRange(vData); 428 } 429 } 430 431 /** @inheritDoc */ 432 @Override protected void layoutPlotChildren() { 433 double catSpace = categoryAxis.getCategorySpacing(); 434 // calculate bar spacing 435 final double availableBarSpace = catSpace - getCategoryGap(); 436 final double barWidth = availableBarSpace; 437 final double barOffset = -((catSpace - getCategoryGap()) / 2); 438 final double lowerBoundValue = valueAxis.getLowerBound(); 439 final double upperBoundValue = valueAxis.getUpperBound(); 440 // update bar positions and sizes 441 for (String category : categoryAxis.getCategories()) { 442 double currentPositiveValue = 0; 443 double currentNegativeValue = 0; 444 Iterator<Series<X, Y>> seriesIterator = getDisplayedSeriesIterator(); 445 while (seriesIterator.hasNext()) { 446 Series<X, Y> series = seriesIterator.next(); 447 for (final Data<X, Y> item : getDataItem(series, category)) { 448 if (item != null) { 449 final Node bar = item.getNode(); 450 final double categoryPos; 451 final double valNumber; 452 final X xValue = getCurrentDisplayedXValue(item); 453 final Y yValue = getCurrentDisplayedYValue(item); 454 if (orientation == Orientation.VERTICAL) { 455 categoryPos = getXAxis().getDisplayPosition(xValue); 456 valNumber = getYAxis().toNumericValue(yValue); 457 } else { 458 categoryPos = getYAxis().getDisplayPosition(yValue); 459 valNumber = getXAxis().toNumericValue(xValue); 460 } 461 double bottom; 462 double top; 463 boolean isNegative = bar.getStyleClass().contains("negative"); 464 if (!isNegative) { 465 bottom = valueAxis.getDisplayPosition(currentPositiveValue); 466 top = valueAxis.getDisplayPosition(currentPositiveValue + valNumber); 467 currentPositiveValue += valNumber; 468 } else { 469 bottom = valueAxis.getDisplayPosition(currentNegativeValue + valNumber); 470 top = valueAxis.getDisplayPosition(currentNegativeValue); 471 currentNegativeValue += valNumber; 472 } 473 474 if (orientation == Orientation.VERTICAL) { 475 bar.resizeRelocate(categoryPos + barOffset, 476 top, barWidth, bottom - top); 477 } else { 478 bar.resizeRelocate(bottom, 479 categoryPos + barOffset, 480 top - bottom, barWidth); 481 } 482 } 483 } 484 } 485 } 486 } 487 488 /** 489 * Computes the size of series linked list 490 * @return size of series linked list 491 */ 492 @Override int getSeriesSize() { 493 int count = 0; 494 Iterator<Series<X, Y>> seriesIterator = getDisplayedSeriesIterator(); 495 while (seriesIterator.hasNext()) { 496 seriesIterator.next(); 497 count++; 498 } 499 return count; 500 } 501 502 /** 503 * This is called whenever a series is added or removed and the legend needs to be updated 504 */ 505 @Override protected void updateLegend() { 506 legend.getItems().clear(); 507 if (getData() != null) { 508 for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) { 509 Series<X,Y> series = getData().get(seriesIndex); 510 Legend.LegendItem legenditem = new Legend.LegendItem(series.getName()); 511 String defaultColorStyleClass = seriesDefaultColorMap.get(series); 512 legenditem.getSymbol().getStyleClass().addAll("chart-bar", "series" + seriesIndex, "bar-legend-symbol", 513 defaultColorStyleClass); 514 legend.getItems().add(legenditem); 515 } 516 } 517 if (legend.getItems().size() > 0) { 518 if (getLegend() == null) { 519 setLegend(legend); 520 } 521 } else { 522 setLegend(null); 523 } 524 } 525 526 private Node createBar(Series series, int seriesIndex, final Data item, int itemIndex) { 527 Node bar = item.getNode(); 528 if (bar == null) { 529 bar = new StackPane(); 530 bar.setAccessibleRole(AccessibleRole.TEXT); 531 bar.setAccessibleRoleDescription("Bar"); 532 bar.focusTraversableProperty().bind(Platform.accessibilityActiveProperty()); 533 item.setNode(bar); 534 } 535 String defaultColorStyleClass = seriesDefaultColorMap.get(series); 536 bar.getStyleClass().setAll("chart-bar", "series" + seriesIndex, "data" + itemIndex, defaultColorStyleClass); 537 return bar; 538 } 539 540 private List<Data<X, Y>> getDataItem(Series<X, Y> series, String category) { 541 Map<String, List<Data<X, Y>>> catmap = seriesCategoryMap.get(series); 542 return catmap != null ? catmap.get(category) != null ? 543 catmap.get(category) : new ArrayList<Data<X, Y>>() : new ArrayList<Data<X, Y>>(); 544 } 545 546 // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------ 547 548 /** 549 * Super-lazy instantiation pattern from Bill Pugh. 550 * @treatAsPrivate implementation detail 551 */ 552 private static class StyleableProperties { 553 554 private static final CssMetaData<StackedBarChart<?,?>,Number> CATEGORY_GAP = 555 new CssMetaData<StackedBarChart<?,?>,Number>("-fx-category-gap", 556 SizeConverter.getInstance(), 10.0) { 557 558 @Override 559 public boolean isSettable(StackedBarChart<?,?> node) { 560 return node.categoryGap == null || !node.categoryGap.isBound(); 561 } 562 563 @Override 564 public StyleableProperty<Number> getStyleableProperty(StackedBarChart<?,?> node) { 565 return (StyleableProperty<Number>)(WritableValue<Number>)node.categoryGapProperty(); 566 } 567 }; 568 569 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 570 static { 571 572 final List<CssMetaData<? extends Styleable, ?>> styleables = 573 new ArrayList<CssMetaData<? extends Styleable, ?>>(XYChart.getClassCssMetaData()); 574 styleables.add(CATEGORY_GAP); 575 STYLEABLES = Collections.unmodifiableList(styleables); 576 } 577 } 578 579 /** 580 * @return The CssMetaData associated with this class, which may include the 581 * CssMetaData of its super classes. 582 * @since JavaFX 8.0 583 */ 584 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 585 return StyleableProperties.STYLEABLES; 586 } 587 588 /** 589 * {@inheritDoc} 590 * @since JavaFX 8.0 591 */ 592 @Override 593 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 594 return getClassCssMetaData(); 595 } 596 597 /** Pseudoclass indicating this is a vertical chart. */ 598 private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE = 599 PseudoClass.getPseudoClass("vertical"); 600 /** Pseudoclass indicating this is a horizontal chart. */ 601 private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE = 602 PseudoClass.getPseudoClass("horizontal"); 603 604 }