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