Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.chart; 027 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032import java.util.ArrayList; 033import java.util.Collections; 034 035import javafx.animation.Animation; 036import javafx.animation.FadeTransition; 037import javafx.animation.Interpolator; 038import javafx.animation.KeyFrame; 039import javafx.animation.KeyValue; 040import javafx.animation.ParallelTransition; 041import javafx.animation.Timeline; 042import javafx.beans.property.DoubleProperty; 043import javafx.collections.FXCollections; 044import javafx.collections.ObservableList; 045import javafx.event.ActionEvent; 046import javafx.event.EventHandler; 047import javafx.geometry.Orientation; 048import javafx.scene.Node; 049import javafx.scene.layout.StackPane; 050import javafx.util.Duration; 051 052import com.sun.javafx.charts.Legend; 053import com.sun.javafx.charts.Legend.LegendItem; 054import javafx.css.StyleableDoubleProperty; 055import javafx.css.CssMetaData; 056import javafx.css.PseudoClass; 057import com.sun.javafx.css.converters.SizeConverter; 058import javafx.css.Styleable; 059import javafx.css.StyleableProperty; 060import javafx.event.EventType; 061import static javafx.scene.chart.XYChart.DEFAULT_COLOR; 062 063/** 064 * A chart that plots bars indicating data values for a category. The bars can be vertical or horizontal depending on 065 * which axis is a category axis. 066 */ 067public class BarChart<X,Y> extends XYChart<X,Y> { 068 069 // -------------- PRIVATE FIELDS ------------------------------------------- 070 071 private Map<Series, Map<String, Data<X,Y>>> seriesCategoryMap = 072 new HashMap<Series, Map<String, Data<X,Y>>>(); 073 private Legend legend = new Legend(); 074 private boolean seriesRemove = false; 075 private final Orientation orientation; 076 private CategoryAxis categoryAxis; 077 private ValueAxis valueAxis; 078 private Timeline dataRemoveTimeline; 079 private Data<X,Y> dataItemBeingRemoved = null; 080 private Series<X,Y> seriesOfDataRemoved = null; 081 private double bottomPos = 0; 082 private static String NEGATIVE_STYLE = "negative"; 083 // -------------- PUBLIC PROPERTIES ---------------------------------------- 084 085 /** The gap to leave between bars in the same category */ 086 private DoubleProperty barGap = new StyleableDoubleProperty(4) { 087 @Override protected void invalidated() { 088 get(); 089 requestChartLayout(); 090 } 091 092 public Object getBean() { 093 return BarChart.this; 094 } 095 096 public String getName() { 097 return "barGap"; 098 } 099 100 public CssMetaData<BarChart<?,?>,Number> getCssMetaData() { 101 return StyleableProperties.BAR_GAP; 102 } 103 }; 104 public final double getBarGap() { return barGap.getValue(); } 105 public final void setBarGap(double value) { barGap.setValue(value); } 106 public final DoubleProperty barGapProperty() { return barGap; } 107 108 /** The gap to leave between bars in separate categories */ 109 private DoubleProperty categoryGap = new StyleableDoubleProperty(10) { 110 @Override protected void invalidated() { 111 get(); 112 requestChartLayout(); 113 } 114 115 @Override 116 public Object getBean() { 117 return BarChart.this; 118 } 119 120 @Override 121 public String getName() { 122 return "categoryGap"; 123 } 124 125 public CssMetaData<BarChart<?,?>,Number> getCssMetaData() { 126 return StyleableProperties.CATEGORY_GAP; 127 } 128 }; 129 public final double getCategoryGap() { return categoryGap.getValue(); } 130 public final void setCategoryGap(double value) { categoryGap.setValue(value); } 131 public final DoubleProperty categoryGapProperty() { return categoryGap; } 132 133 // -------------- CONSTRUCTOR ---------------------------------------------- 134 135 /** 136 * Construct a new BarChart with the given axis. The two axis should be a ValueAxis/NumberAxis and a CategoryAxis, 137 * they can be in either order depending on if you want a horizontal or vertical bar chart. 138 * 139 * @param xAxis The x axis to use 140 * @param yAxis The y axis to use 141 */ 142 public BarChart(Axis<X> xAxis, Axis<Y> yAxis) { 143 this(xAxis, yAxis, FXCollections.<Series<X, Y>>observableArrayList()); 144 } 145 146 /** 147 * Construct a new BarChart with the given axis and data. The two axis should be a ValueAxis/NumberAxis and a 148 * CategoryAxis, they can be in either order depending on if you want a horizontal or vertical bar chart. 149 * 150 * @param xAxis The x axis to use 151 * @param yAxis The y axis to use 152 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 153 */ 154 public BarChart(Axis<X> xAxis, Axis<Y> yAxis, ObservableList<Series<X,Y>> data) { 155 super(xAxis, yAxis); 156 getStyleClass().add("bar-chart"); 157 setLegend(legend); 158 if (!((xAxis instanceof ValueAxis && yAxis instanceof CategoryAxis) || 159 (yAxis instanceof ValueAxis && xAxis instanceof CategoryAxis))) { 160 throw new IllegalArgumentException("Axis type incorrect, one of X,Y should be CategoryAxis and the other NumberAxis"); 161 } 162 if (xAxis instanceof CategoryAxis) { 163 categoryAxis = (CategoryAxis)xAxis; 164 valueAxis = (ValueAxis)yAxis; 165 orientation = Orientation.VERTICAL; 166 } else { 167 categoryAxis = (CategoryAxis)yAxis; 168 valueAxis = (ValueAxis)xAxis; 169 orientation = Orientation.HORIZONTAL; 170 } 171 // update css 172 pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, orientation == Orientation.HORIZONTAL); 173 pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE, orientation == Orientation.VERTICAL); 174 setData(data); 175 } 176 177 /** 178 * Construct a new BarChart with the given axis and data. The two axis should be a ValueAxis/NumberAxis and a 179 * CategoryAxis, they can be in either order depending on if you want a horizontal or vertical bar chart. 180 * 181 * @param xAxis The x axis to use 182 * @param yAxis The y axis to use 183 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 184 * @param categoryGap The gap to leave between bars in separate categories 185 */ 186 public BarChart(Axis<X> xAxis, Axis<Y> yAxis, ObservableList<Series<X,Y>> data, double categoryGap) { 187 this(xAxis, yAxis); 188 setData(data); 189 setCategoryGap(categoryGap); 190 } 191 192 // -------------- PROTECTED METHODS ---------------------------------------- 193 194 @Override protected void dataItemAdded(Series<X,Y> series, int itemIndex, Data<X,Y> item) { 195 String category; 196 if (orientation == Orientation.VERTICAL) { 197 category = (String)item.getXValue(); 198 } else { 199 category = (String)item.getYValue(); 200 } 201 Map<String, Data<X,Y>> categoryMap = seriesCategoryMap.get(series); 202 203 if (categoryMap == null) { 204 categoryMap = new HashMap<String, Data<X,Y>>(); 205 seriesCategoryMap.put(series, categoryMap); 206 } 207 // check if category is already present 208 if (!categoryAxis.getCategories().contains(category)) { 209 // note: cat axis categories can be updated only when autoranging is true. 210 categoryAxis.getCategories().add(itemIndex, category); 211 } else if (categoryMap.containsKey(category)){ 212 // RT-21162 : replacing the previous data, first remove the node from scenegraph. 213 Data data = categoryMap.get(category); 214 getPlotChildren().remove(data.getNode()); 215 removeDataItemFromDisplay(series, data); 216 requestChartLayout(); 217 categoryMap.remove(category); 218 } 219 categoryMap.put(category, item); 220 Node bar = createBar(series, getData().indexOf(series), item, itemIndex); 221 if (shouldAnimate()) { 222 if (dataRemoveTimeline != null && dataRemoveTimeline.getStatus().equals(Animation.Status.RUNNING)) { 223 if (dataItemBeingRemoved != null && dataItemBeingRemoved == item) { 224 dataRemoveTimeline.stop(); 225 getPlotChildren().remove(bar); 226 removeDataItemFromDisplay(seriesOfDataRemoved, item); 227 dataItemBeingRemoved = null; 228 seriesOfDataRemoved = null; 229 } 230 } 231 animateDataAdd(item, bar); 232 } else { 233 getPlotChildren().add(bar); 234 } 235 } 236 237 @Override protected void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) { 238 final Node bar = item.getNode(); 239 if (shouldAnimate()) { 240 dataRemoveTimeline = createDataRemoveTimeline(item, bar, series); 241 dataItemBeingRemoved = item; 242 seriesOfDataRemoved = series; 243 dataRemoveTimeline.setOnFinished(new EventHandler<ActionEvent>() { 244 public void handle(ActionEvent event) { 245 item.setSeries(null); 246 getPlotChildren().remove(bar); 247 removeDataItemFromDisplay(series, item); 248 dataItemBeingRemoved = null; 249 updateMap(series, item); 250 } 251 }); 252 dataRemoveTimeline.play(); 253 } else { 254 item.setSeries(null); 255 getPlotChildren().remove(bar); 256 removeDataItemFromDisplay(series, item); 257 updateMap(series, item); 258 } 259 } 260 261 /** @inheritDoc */ 262 @Override protected void dataItemChanged(Data<X, Y> item) { 263 double barVal; 264 double currentVal; 265 if (orientation == Orientation.VERTICAL) { 266 barVal = ((Number)item.getYValue()).doubleValue(); 267 currentVal = ((Number)item.getCurrentY()).doubleValue(); 268 } else { 269 barVal = ((Number)item.getXValue()).doubleValue(); 270 currentVal = ((Number)item.getCurrentX()).doubleValue(); 271 } 272 if (currentVal > 0 && barVal < 0) { // going from positive to negative 273 // add style class negative 274 item.getNode().getStyleClass().add(NEGATIVE_STYLE); 275 } else if (currentVal < 0 && barVal > 0) { // going from negative to positive 276 // remove style class negative 277 // RT-21164 upside down bars: was adding NEGATIVE_STYLE styleclass 278 // instead of removing it; when going from negative to positive 279 item.getNode().getStyleClass().remove(NEGATIVE_STYLE); 280 } 281 } 282 283 @Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) { 284 // handle any data already in series 285 // create entry in the map 286 Map<String, Data<X,Y>> categoryMap = new HashMap<String, Data<X,Y>>(); 287 for (int j=0; j<series.getData().size(); j++) { 288 Data<X,Y> item = series.getData().get(j); 289 Node bar = createBar(series, seriesIndex, item, j); 290 String category; 291 if (orientation == Orientation.VERTICAL) { 292 category = (String)item.getXValue(); 293 } else { 294 category = (String)item.getYValue(); 295 } 296 categoryMap.put(category, item); 297 if (shouldAnimate()) { 298 animateDataAdd(item, bar); 299 } else { 300 // RT-21164 check if bar value is negative to add NEGATIVE_STYLE style class 301 double barVal = (orientation == Orientation.VERTICAL) ? ((Number)item.getYValue()).doubleValue() : 302 ((Number)item.getXValue()).doubleValue(); 303 if (barVal < 0) { 304 bar.getStyleClass().add(NEGATIVE_STYLE); 305 } 306 getPlotChildren().add(bar); 307 } 308 } 309 if (categoryMap.size() > 0) seriesCategoryMap.put(series, categoryMap); 310 } 311 312 @Override protected void seriesRemoved(final Series<X,Y> series) { 313 updateDefaultColorIndex(series); 314 // remove all symbol nodes 315 if (shouldAnimate()) { 316 ParallelTransition pt = new ParallelTransition(); 317 pt.setOnFinished(new EventHandler<ActionEvent>() { 318 public void handle(ActionEvent event) { 319 removeSeriesFromDisplay(series); 320 } 321 }); 322 for (final Data<X,Y> d : series.getData()) { 323 final Node bar = d.getNode(); 324 seriesRemove = true; 325 // Animate series deletion 326 if (getSeriesSize() > 1) { 327 for (int j=0; j< series.getData().size(); j++) { 328 Data<X,Y> item = series.getData().get(j); 329 Timeline t = createDataRemoveTimeline(item, bar, series); 330 pt.getChildren().add(t); 331 } 332 } else { 333 // fade out last series 334 FadeTransition ft = new FadeTransition(Duration.millis(700),bar); 335 ft.setFromValue(1); 336 ft.setToValue(0); 337 ft.setOnFinished(new EventHandler<ActionEvent>() { 338 @Override public void handle(ActionEvent actionEvent) { 339 getPlotChildren().remove(bar); 340 updateMap(series, d); 341 } 342 }); 343 pt.getChildren().add(ft); 344 } 345 } 346 pt.play(); 347 } else { 348 for (Data<X,Y> d : series.getData()) { 349 final Node bar = d.getNode(); 350 getPlotChildren().remove(bar); 351 updateMap(series, d); 352 } 353 removeSeriesFromDisplay(series); 354 } 355 } 356 357 /** @inheritDoc */ 358 @Override protected void layoutPlotChildren() { 359 double catSpace = categoryAxis.getCategorySpacing(); 360 // calculate bar spacing 361 final double avilableBarSpace = catSpace - (getCategoryGap() + getBarGap()); 362 final double barWidth = (avilableBarSpace / getSeriesSize()) - getBarGap(); 363 final double barOffset = -((catSpace - getCategoryGap()) / 2); 364 final double zeroPos = (valueAxis.getLowerBound() > 0) ? 365 valueAxis.getDisplayPosition(valueAxis.getLowerBound()) : valueAxis.getZeroPosition(); 366 // update bar positions and sizes 367 int catIndex = 0; 368 for (String category : categoryAxis.getCategories()) { 369 int index = 0; 370 for (Series<X,Y> series = begin; series != null; series = series.next) { 371 final Data<X,Y> item = getDataItem(series, index, catIndex, category); 372 if (item != null) { 373 final Node bar = item.getNode(); 374 final double categoryPos; 375 final double valPos; 376 if (orientation == Orientation.VERTICAL) { 377 categoryPos = getXAxis().getDisplayPosition(item.getCurrentX()); 378 valPos = getYAxis().getDisplayPosition(item.getCurrentY()); 379 } else { 380 categoryPos = getYAxis().getDisplayPosition(item.getCurrentY()); 381 valPos = getXAxis().getDisplayPosition(item.getCurrentX()); 382 } 383 final double bottom = Math.min(valPos,zeroPos); 384 final double top = Math.max(valPos,zeroPos); 385 bottomPos = bottom; 386 if (orientation == Orientation.VERTICAL) { 387 bar.resizeRelocate( categoryPos + barOffset + (barWidth + getBarGap()) * index, 388 bottom, barWidth, top-bottom); 389 } else { 390 //noinspection SuspiciousNameCombination 391 bar.resizeRelocate( bottom, categoryPos + barOffset + (barWidth + getBarGap()) * index, 392 top-bottom, barWidth); 393 } 394 395 index++; 396 } 397 } 398 catIndex++; 399 } 400 } 401 402 /** 403 * This is called whenever a series is added or removed and the legend needs to be updated 404 */ 405 @Override protected void updateLegend() { 406 legend.getItems().clear(); 407 if (getData() != null) { 408 for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex++) { 409 Series series = getData().get(seriesIndex); 410 LegendItem legenditem = new LegendItem(series.getName()); 411 legenditem.getSymbol().getStyleClass().addAll("chart-bar","series"+seriesIndex,"bar-legend-symbol", 412 series.defaultColorStyleClass); 413 legend.getItems().add(legenditem); 414 } 415 } 416 if (legend.getItems().size() > 0) { 417 if (getLegend() == null) { 418 setLegend(legend); 419 } 420 } else { 421 setLegend(null); 422 } 423 } 424 425 // -------------- PRIVATE METHODS ------------------------------------------ 426 427 private void updateMap(Series series, Data item) { 428 final String category = (orientation == Orientation.VERTICAL) ? (String)item.getXValue() : 429 (String)item.getYValue(); 430 Map<String, Data<X,Y>> categoryMap = seriesCategoryMap.get(series); 431 if (categoryMap != null) { 432 categoryMap.remove(category); 433 if (categoryMap.isEmpty()) seriesCategoryMap.remove(series); 434 } 435 if (seriesCategoryMap.isEmpty() && categoryAxis.isAutoRanging()) categoryAxis.getCategories().clear(); 436 } 437 private void animateDataAdd(Data<X,Y> item, Node bar) { 438 double barVal; 439 if (orientation == Orientation.VERTICAL) { 440 barVal = ((Number)item.getYValue()).doubleValue(); 441 if (barVal < 0) { 442 bar.getStyleClass().add(NEGATIVE_STYLE); 443 } 444 item.setCurrentY(getYAxis().toRealValue((barVal < 0) ? -bottomPos : bottomPos)); 445 getPlotChildren().add(bar); 446 item.setYValue(getYAxis().toRealValue(barVal)); 447 animate( 448 new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), 449 item.getCurrentY())), 450 new KeyFrame(Duration.millis(700), 451 new KeyValue(item.currentYProperty(), item.getYValue(), Interpolator.EASE_BOTH)) 452 ); 453 } else { 454 barVal = ((Number)item.getXValue()).doubleValue(); 455 if (barVal < 0) { 456 bar.getStyleClass().add(NEGATIVE_STYLE); 457 } 458 item.setCurrentX(getXAxis().toRealValue((barVal < 0) ? -bottomPos : bottomPos)); 459 getPlotChildren().add(bar); 460 item.setXValue(getXAxis().toRealValue(barVal)); 461 animate( 462 new KeyFrame(Duration.ZERO, new KeyValue(item.currentXProperty(), 463 item.getCurrentX())), 464 new KeyFrame(Duration.millis(700), 465 new KeyValue(item.currentXProperty(), item.getXValue(), Interpolator.EASE_BOTH)) 466 ); 467 } 468 } 469 470 private Timeline createDataRemoveTimeline(final Data<X,Y> item, final Node bar, final Series<X,Y> series) { 471 Timeline t = new Timeline(); 472 if (orientation == Orientation.VERTICAL) { 473// item.setYValue(getYAxis().toRealValue(getYAxis().getZeroPosition())); 474 item.setYValue(getYAxis().toRealValue(bottomPos)); 475 t.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, 476 new KeyValue(item.currentYProperty(), item.getCurrentY())), 477 new KeyFrame(Duration.millis(700), new EventHandler<ActionEvent>() { 478 @Override public void handle(ActionEvent actionEvent) { 479 getPlotChildren().remove(bar); 480 updateMap(series, item); 481 } 482 }, 483 new KeyValue(item.currentYProperty(), item.getYValue(), 484 Interpolator.EASE_BOTH) )); 485 } else { 486 item.setXValue(getXAxis().toRealValue(getXAxis().getZeroPosition())); 487 t.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, new KeyValue(item.currentXProperty(), item.getCurrentX())), 488 new KeyFrame(Duration.millis(700), new EventHandler<ActionEvent>() { 489 @Override public void handle(ActionEvent actionEvent) { 490 getPlotChildren().remove(bar); 491 updateMap(series, item); 492 } 493 }, 494 new KeyValue(item.currentXProperty(), item.getXValue(), 495 Interpolator.EASE_BOTH) )); 496 } 497 return t; 498 } 499 500 private void updateDefaultColorIndex(final Series<X,Y> series) { 501 int clearIndex = seriesColorMap.get(series); 502 colorBits.clear(clearIndex); 503 for (Data<X,Y> d : series.getData()) { 504 final Node bar = d.getNode(); 505 if (bar != null) { 506 bar.getStyleClass().remove(DEFAULT_COLOR+clearIndex); 507 colorBits.clear(clearIndex); 508 } 509 } 510 seriesColorMap.remove(series); 511 } 512 513 private Node createBar(Series series, int seriesIndex, final Data item, int itemIndex) { 514 Node bar = item.getNode(); 515 if (bar == null) { 516 bar = new StackPane(); 517 item.setNode(bar); 518 } 519 bar.getStyleClass().addAll("chart-bar", "series" + seriesIndex, "data" + itemIndex,series.defaultColorStyleClass); 520 return bar; 521 } 522 523 private Data<X,Y> getDataItem(Series<X,Y> series, int seriesIndex, int itemIndex, String category) { 524 Map<String, Data<X,Y>> catmap = seriesCategoryMap.get(series); 525 return (catmap != null) ? catmap.get(category) : null; 526 } 527 528 // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------ 529 530 /** 531 * Super-lazy instantiation pattern from Bill Pugh. 532 * @treatAsPrivate implementation detail 533 */ 534 private static class StyleableProperties { 535 private static final CssMetaData<BarChart<?,?>,Number> BAR_GAP = 536 new CssMetaData<BarChart<?,?>,Number>("-fx-bar-gap", 537 SizeConverter.getInstance(), 4.0) { 538 539 @Override 540 public boolean isSettable(BarChart<?,?> node) { 541 return node.barGap == null || !node.barGap.isBound(); 542 } 543 544 @Override 545 public StyleableProperty<Number> getStyleableProperty(BarChart<?,?> node) { 546 return (StyleableProperty<Number>)node.barGapProperty(); 547 } 548 }; 549 550 private static final CssMetaData<BarChart<?,?>,Number> CATEGORY_GAP = 551 new CssMetaData<BarChart<?,?>,Number>("-fx-category-gap", 552 SizeConverter.getInstance(), 10.0) { 553 554 @Override 555 public boolean isSettable(BarChart<?,?> node) { 556 return node.categoryGap == null || !node.categoryGap.isBound(); 557 } 558 559 @Override 560 public StyleableProperty<Number> getStyleableProperty(BarChart<?,?> node) { 561 return (StyleableProperty<Number>)node.categoryGapProperty(); 562 } 563 }; 564 565 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 566 static { 567 568 final List<CssMetaData<? extends Styleable, ?>> styleables = 569 new ArrayList<CssMetaData<? extends Styleable, ?>>(XYChart.getClassCssMetaData()); 570 styleables.add(BAR_GAP); 571 styleables.add(CATEGORY_GAP); 572 STYLEABLES = Collections.unmodifiableList(styleables); 573 } 574 } 575 576 /** 577 * @return The CssMetaData associated with this class, which may include the 578 * CssMetaData of its super classes. 579 */ 580 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 581 return StyleableProperties.STYLEABLES; 582 } 583 584 /** 585 * {@inheritDoc} 586 */ 587 @Override 588 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 589 return getClassCssMetaData(); 590 } 591 592 /** Pseudoclass indicating this is a vertical chart. */ 593 private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE = 594 PseudoClass.getPseudoClass("vertical"); 595 596 /** Pseudoclass indicating this is a horizontal chart. */ 597 private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE = 598 PseudoClass.getPseudoClass("horizontal"); 599 600}