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;
50 import com.sun.javafx.charts.Legend.LegendItem;
51
52 import javafx.css.StyleableDoubleProperty;
53 import javafx.css.CssMetaData;
54 import javafx.css.PseudoClass;
55
56 import javafx.css.converter.SizeConverter;
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 Legend legend = new Legend();
72 private final Orientation orientation;
73 private CategoryAxis categoryAxis;
74 private ValueAxis valueAxis;
75 private Timeline dataRemoveTimeline;
76 private double bottomPos = 0;
250 double barVal;
251 double currentVal;
252 if (orientation == Orientation.VERTICAL) {
253 barVal = ((Number)item.getYValue()).doubleValue();
254 currentVal = ((Number)item.getCurrentY()).doubleValue();
255 } else {
256 barVal = ((Number)item.getXValue()).doubleValue();
257 currentVal = ((Number)item.getCurrentX()).doubleValue();
258 }
259 if (currentVal > 0 && barVal < 0) { // going from positive to negative
260 // add style class negative
261 item.getNode().getStyleClass().add(NEGATIVE_STYLE);
262 } else if (currentVal < 0 && barVal > 0) { // going from negative to positive
263 // remove style class negative
264 // RT-21164 upside down bars: was adding NEGATIVE_STYLE styleclass
265 // instead of removing it; when going from negative to positive
266 item.getNode().getStyleClass().remove(NEGATIVE_STYLE);
267 }
268 }
269
270 @Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) {
271 // handle any data already in series
272 // create entry in the map
273 Map<String, Data<X,Y>> categoryMap = new HashMap<String, Data<X,Y>>();
274 for (int j=0; j<series.getData().size(); j++) {
275 Data<X,Y> item = series.getData().get(j);
276 Node bar = createBar(series, seriesIndex, item, j);
277 String category;
278 if (orientation == Orientation.VERTICAL) {
279 category = (String)item.getXValue();
280 } else {
281 category = (String)item.getYValue();
282 }
283 categoryMap.put(category, item);
284 if (shouldAnimate()) {
285 animateDataAdd(item, bar);
286 } else {
287 // RT-21164 check if bar value is negative to add NEGATIVE_STYLE style class
288 double barVal = (orientation == Orientation.VERTICAL) ? ((Number)item.getYValue()).doubleValue() :
289 ((Number)item.getXValue()).doubleValue();
290 if (barVal < 0) {
291 bar.getStyleClass().add(NEGATIVE_STYLE);
292 }
293 getPlotChildren().add(bar);
294 }
295 }
296 if (categoryMap.size() > 0) seriesCategoryMap.put(series, categoryMap);
297 }
298
299 @Override protected void seriesRemoved(final Series<X,Y> series) {
300 updateDefaultColorIndex(series);
301 // remove all symbol nodes
302 if (shouldAnimate()) {
303 pt = new ParallelTransition();
304 pt.setOnFinished(event -> {
305 removeSeriesFromDisplay(series);
306 });
307
308 boolean lastSeries = (getSeriesSize() > 1) ? false : true;
309 XYValueMap.clear();
310 for (final Data<X,Y> d : series.getData()) {
311 final Node bar = d.getNode();
312 // Animate series deletion
313 if (!lastSeries) {
314 Timeline t = createDataRemoveTimeline(d, bar, series);
315 pt.getChildren().add(t);
316 } else {
317 // fade out last series
318 FadeTransition ft = new FadeTransition(Duration.millis(700),bar);
319 ft.setFromValue(1);
320 ft.setToValue(0);
524 if (pt!= null) {
525 if (!pt.getChildren().isEmpty()) {
526 for (Animation a : pt.getChildren()) {
527 a.setOnFinished(null);
528 }
529 }
530 for (Data<X,Y> item : series.getData()) {
531 processDataRemove(series, item);
532 if (!lastSeries) {
533 restoreDataValues(item);
534 }
535 }
536 XYValueMap.clear();
537 pt.setOnFinished(null);
538 pt.getChildren().clear();
539 pt.stop();
540 removeSeriesFromDisplay(series);
541 }
542 }
543
544 private void updateDefaultColorIndex(final Series<X,Y> series) {
545 int clearIndex = seriesColorMap.get(series);
546 for (Data<X,Y> d : series.getData()) {
547 final Node bar = d.getNode();
548 if (bar != null) {
549 bar.getStyleClass().remove(DEFAULT_COLOR+clearIndex);
550 }
551 }
552 }
553
554 private Node createBar(Series<X,Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) {
555 Node bar = item.getNode();
556 if (bar == null) {
557 bar = new StackPane();
558 bar.setAccessibleRole(AccessibleRole.TEXT);
559 bar.setAccessibleRoleDescription("Bar");
560 bar.focusTraversableProperty().bind(Platform.accessibilityActiveProperty());
561 item.setNode(bar);
562 }
563 bar.getStyleClass().addAll("chart-bar", "series" + seriesIndex, "data" + itemIndex,series.defaultColorStyleClass);
564 return bar;
565 }
566
567 private Data<X,Y> getDataItem(Series<X,Y> series, int seriesIndex, int itemIndex, String category) {
568 Map<String, Data<X,Y>> catmap = seriesCategoryMap.get(series);
569 return (catmap != null) ? catmap.get(category) : null;
570 }
571
572 // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------
573
574 /*
575 * Super-lazy instantiation pattern from Bill Pugh.
576 */
577 private static class StyleableProperties {
578 private static final CssMetaData<BarChart<?,?>,Number> BAR_GAP =
579 new CssMetaData<BarChart<?,?>,Number>("-fx-bar-gap",
580 SizeConverter.getInstance(), 4.0) {
581
582 @Override
583 public boolean isSettable(BarChart<?,?> node) {
|
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;
50 import com.sun.javafx.charts.Legend.LegendItem;
51
52 import javafx.css.StyleableDoubleProperty;
53 import javafx.css.CssMetaData;
54 import javafx.css.PseudoClass;
55
56 import javafx.css.converter.SizeConverter;
57 import javafx.collections.ListChangeListener;
58
59 import javafx.css.Styleable;
60 import javafx.css.StyleableProperty;
61
62 /**
63 * A chart that plots bars indicating data values for a category. The bars can be vertical or horizontal depending on
64 * which axis is a category axis.
65 * @since JavaFX 2.0
66 */
67 public class BarChart<X,Y> extends XYChart<X,Y> {
68
69 // -------------- PRIVATE FIELDS -------------------------------------------
70
71 private Map<Series<X,Y>, Map<String, Data<X,Y>>> seriesCategoryMap = new HashMap<>();
72 private Legend legend = new Legend();
73 private final Orientation orientation;
74 private CategoryAxis categoryAxis;
75 private ValueAxis valueAxis;
76 private Timeline dataRemoveTimeline;
77 private double bottomPos = 0;
251 double barVal;
252 double currentVal;
253 if (orientation == Orientation.VERTICAL) {
254 barVal = ((Number)item.getYValue()).doubleValue();
255 currentVal = ((Number)item.getCurrentY()).doubleValue();
256 } else {
257 barVal = ((Number)item.getXValue()).doubleValue();
258 currentVal = ((Number)item.getCurrentX()).doubleValue();
259 }
260 if (currentVal > 0 && barVal < 0) { // going from positive to negative
261 // add style class negative
262 item.getNode().getStyleClass().add(NEGATIVE_STYLE);
263 } else if (currentVal < 0 && barVal > 0) { // going from negative to positive
264 // remove style class negative
265 // RT-21164 upside down bars: was adding NEGATIVE_STYLE styleclass
266 // instead of removing it; when going from negative to positive
267 item.getNode().getStyleClass().remove(NEGATIVE_STYLE);
268 }
269 }
270
271 @Override protected void seriesChanged(ListChangeListener.Change<? extends Series> c) {
272 // Update style classes for all series lines and symbols
273 // Note: is there a more efficient way of doing this?
274 for (int i = 0; i < getDataSize(); i++) {
275 final Series<X,Y> series = getData().get(i);
276 for (int j=0; j<series.getData().size(); j++) {
277 Data<X,Y> item = series.getData().get(j);
278 Node bar = item.getNode();
279 bar.getStyleClass().setAll("chart-bar", "series" + i, "data" + j, series.defaultColorStyleClass);
280 }
281 }
282 }
283
284 @Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) {
285 // handle any data already in series
286 // create entry in the map
287 Map<String, Data<X,Y>> categoryMap = new HashMap<String, Data<X,Y>>();
288 for (int j=0; j<series.getData().size(); j++) {
289 Data<X,Y> item = series.getData().get(j);
290 Node bar = createBar(series, seriesIndex, item, j);
291 String category;
292 if (orientation == Orientation.VERTICAL) {
293 category = (String)item.getXValue();
294 } else {
295 category = (String)item.getYValue();
296 }
297 categoryMap.put(category, item);
298 if (shouldAnimate()) {
299 animateDataAdd(item, bar);
300 } else {
301 // RT-21164 check if bar value is negative to add NEGATIVE_STYLE style class
302 double barVal = (orientation == Orientation.VERTICAL) ? ((Number)item.getYValue()).doubleValue() :
303 ((Number)item.getXValue()).doubleValue();
304 if (barVal < 0) {
305 bar.getStyleClass().add(NEGATIVE_STYLE);
306 }
307 getPlotChildren().add(bar);
308 }
309 }
310 if (categoryMap.size() > 0) seriesCategoryMap.put(series, categoryMap);
311 }
312
313 @Override protected void seriesRemoved(final Series<X,Y> series) {
314 // remove all symbol nodes
315 if (shouldAnimate()) {
316 pt = new ParallelTransition();
317 pt.setOnFinished(event -> {
318 removeSeriesFromDisplay(series);
319 });
320
321 boolean lastSeries = (getSeriesSize() > 1) ? false : true;
322 XYValueMap.clear();
323 for (final Data<X,Y> d : series.getData()) {
324 final Node bar = d.getNode();
325 // Animate series deletion
326 if (!lastSeries) {
327 Timeline t = createDataRemoveTimeline(d, bar, series);
328 pt.getChildren().add(t);
329 } else {
330 // fade out last series
331 FadeTransition ft = new FadeTransition(Duration.millis(700),bar);
332 ft.setFromValue(1);
333 ft.setToValue(0);
537 if (pt!= null) {
538 if (!pt.getChildren().isEmpty()) {
539 for (Animation a : pt.getChildren()) {
540 a.setOnFinished(null);
541 }
542 }
543 for (Data<X,Y> item : series.getData()) {
544 processDataRemove(series, item);
545 if (!lastSeries) {
546 restoreDataValues(item);
547 }
548 }
549 XYValueMap.clear();
550 pt.setOnFinished(null);
551 pt.getChildren().clear();
552 pt.stop();
553 removeSeriesFromDisplay(series);
554 }
555 }
556
557 private Node createBar(Series<X,Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) {
558 Node bar = item.getNode();
559 if (bar == null) {
560 bar = new StackPane();
561 bar.setAccessibleRole(AccessibleRole.TEXT);
562 bar.setAccessibleRoleDescription("Bar");
563 bar.focusTraversableProperty().bind(Platform.accessibilityActiveProperty());
564 item.setNode(bar);
565 }
566 bar.getStyleClass().setAll("chart-bar", "series" + seriesIndex, "data" + itemIndex,series.defaultColorStyleClass);
567 return bar;
568 }
569
570 private Data<X,Y> getDataItem(Series<X,Y> series, int seriesIndex, int itemIndex, String category) {
571 Map<String, Data<X,Y>> catmap = seriesCategoryMap.get(series);
572 return (catmap != null) ? catmap.get(category) : null;
573 }
574
575 // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------
576
577 /*
578 * Super-lazy instantiation pattern from Bill Pugh.
579 */
580 private static class StyleableProperties {
581 private static final CssMetaData<BarChart<?,?>,Number> BAR_GAP =
582 new CssMetaData<BarChart<?,?>,Number>("-fx-bar-gap",
583 SizeConverter.getInstance(), 4.0) {
584
585 @Override
586 public boolean isSettable(BarChart<?,?> node) {
|