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.ArrayList; 029import java.util.Iterator; 030import java.util.List; 031 032import javafx.animation.FadeTransition; 033import javafx.animation.ParallelTransition; 034import javafx.collections.FXCollections; 035import javafx.collections.ObservableList; 036import javafx.event.ActionEvent; 037import javafx.event.EventHandler; 038import javafx.scene.Node; 039import javafx.scene.layout.StackPane; 040import javafx.scene.shape.Ellipse; 041import javafx.util.Duration; 042 043import com.sun.javafx.charts.Legend; 044import com.sun.javafx.charts.Legend.LegendItem; 045 046/** 047 * Chart type that plots bubbles for the data points in a series. The extra value property of Data is used to represent 048 * the radius of the bubble it should be a java.lang.Number. 049 */ 050public class BubbleChart<X,Y> extends XYChart<X,Y> { 051 052 // -------------- PRIVATE FIELDS ------------------------------------------ 053 054 private Legend legend = new Legend(); 055 056 // -------------- CONSTRUCTORS ---------------------------------------------- 057 058 /** 059 * Construct a new BubbleChart with the given axis. BubbleChart does not use a Category Axis. 060 * Both X and Y axes should be of type NumberAxis. 061 * 062 * @param xAxis The x axis to use 063 * @param yAxis The y axis to use 064 */ 065 public BubbleChart(Axis<X> xAxis, Axis<Y> yAxis) { 066 this(xAxis, yAxis, FXCollections.<Series<X, Y>>observableArrayList()); 067 } 068 069 /** 070 * Construct a new BubbleChart with the given axis and data. BubbleChart does not 071 * use a Category Axis. Both X and Y axes should be of type NumberAxis. 072 * 073 * @param xAxis The x axis to use 074 * @param yAxis The y axis to use 075 * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart 076 */ 077 public BubbleChart(Axis<X> xAxis, Axis<Y> yAxis, ObservableList<Series<X,Y>> data) { 078 super(xAxis, yAxis); 079 setLegend(legend); 080 if (!(xAxis instanceof ValueAxis && yAxis instanceof ValueAxis)) { 081 throw new IllegalArgumentException("Axis type incorrect, X and Y should both be NumberAxis"); 082 } 083 setData(data); 084 } 085 086 // -------------- METHODS ------------------------------------------------------------------------------------------ 087 088 /** 089 * Used to get a double value from a object that can be a Number object or null 090 * 091 * @param number Object possibly a instance of Number 092 * @param nullDefault What value to return if the number object is null or not a Number 093 * @return number converted to double or nullDefault 094 */ 095 private static double getDoubleValue(Object number, double nullDefault) { 096 return !(number instanceof Number) ? nullDefault : ((Number)number).doubleValue(); 097 } 098 099 /** @inheritDoc */ 100 @Override protected void layoutPlotChildren() { 101 // update bubble positions 102 for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) { 103 Series<X,Y> series = getData().get(seriesIndex); 104// for (Data<X,Y> item = series.begin; item != null; item = item.next) { 105 Iterator<Data<X,Y>> iter = getDisplayedDataIterator(series); 106 while(iter.hasNext()) { 107 Data<X,Y> item = iter.next(); 108 double x = getXAxis().getDisplayPosition(item.getCurrentX()); 109 double y = getYAxis().getDisplayPosition(item.getCurrentY()); 110 Node bubble = item.getNode(); 111 Ellipse ellipse; 112 if (bubble != null) { 113 if (bubble instanceof StackPane) { 114 StackPane region = (StackPane)item.getNode(); 115 if (region.getShape() == null) { 116 ellipse = new Ellipse(getDoubleValue(item.getExtraValue(), 1), getDoubleValue(item.getExtraValue(), 1)); 117 } else if (region.getShape() instanceof Ellipse) { 118 ellipse = (Ellipse)region.getShape(); 119 } else { 120 return; 121 } 122 ellipse.setRadiusX(getDoubleValue(item.getExtraValue(), 1) * ((getXAxis() instanceof NumberAxis) ? Math.abs(((NumberAxis)getXAxis()).getScale()) : 1)); 123 ellipse.setRadiusY(getDoubleValue(item.getExtraValue(), 1) * ((getYAxis() instanceof NumberAxis) ? Math.abs(((NumberAxis)getYAxis()).getScale()) : 1)); 124 // Note: workaround for RT-7689 - saw this in ProgressControlSkin 125 // The region doesn't update itself when the shape is mutated in place, so we 126 // null out and then restore the shape in order to force invalidation. 127 region.setShape(null); 128 region.setShape(ellipse); 129 region.setScaleShape(false); 130 region.setCenterShape(false); 131 region.setCacheShape(false); 132 // position the bubble 133 bubble.setLayoutX(x); 134 bubble.setLayoutY(y); 135 } 136 } 137 } 138 } 139 } 140 141 @Override protected void dataItemAdded(Series<X,Y> series, int itemIndex, Data<X,Y> item) { 142 Node bubble = createBubble(series, getData().indexOf(series), item, itemIndex); 143 if (shouldAnimate()) { 144 bubble.setOpacity(0); 145 getPlotChildren().add(bubble); 146 // fade in new bubble 147 FadeTransition ft = new FadeTransition(Duration.millis(500),bubble); 148 ft.setToValue(1); 149 ft.play(); 150 } else { 151 getPlotChildren().add(bubble); 152 } 153 } 154 155 @Override protected void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) { 156 final Node bubble = item.getNode(); 157 if (shouldAnimate()) { 158 // fade out old bubble 159 FadeTransition ft = new FadeTransition(Duration.millis(500),bubble); 160 ft.setToValue(0); 161 ft.setOnFinished(new EventHandler<ActionEvent>() { 162 @Override public void handle(ActionEvent actionEvent) { 163 getPlotChildren().remove(bubble); 164 removeDataItemFromDisplay(series, item); 165 } 166 }); 167 ft.play(); 168 } else { 169 getPlotChildren().remove(bubble); 170 removeDataItemFromDisplay(series, item); 171 } 172 } 173 174 /** @inheritDoc */ 175 @Override protected void dataItemChanged(Data<X, Y> item) { 176 } 177 178 @Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) { 179 // handle any data already in series 180 for (int j=0; j<series.getData().size(); j++) { 181 Data item = series.getData().get(j); 182 Node bubble = createBubble(series, seriesIndex, item, j); 183 if (shouldAnimate()) { 184 bubble.setOpacity(0); 185 getPlotChildren().add(bubble); 186 // fade in new bubble 187 FadeTransition ft = new FadeTransition(Duration.millis(500),bubble); 188 ft.setToValue(1); 189 ft.play(); 190 } else { 191 getPlotChildren().add(bubble); 192 } 193 } 194 } 195 196 @Override protected void seriesRemoved(final Series<X,Y> series) { 197 // remove all bubble nodes 198 if (shouldAnimate()) { 199 ParallelTransition pt = new ParallelTransition(); 200 pt.setOnFinished(new EventHandler<ActionEvent>() { 201 public void handle(ActionEvent event) { 202 removeSeriesFromDisplay(series); 203 } 204 }); 205 for (XYChart.Data<X,Y> d : series.getData()) { 206 final Node bubble = d.getNode(); 207 // fade out old bubble 208 FadeTransition ft = new FadeTransition(Duration.millis(500),bubble); 209 ft.setToValue(0); 210 ft.setOnFinished(new EventHandler<ActionEvent>() { 211 @Override public void handle(ActionEvent actionEvent) { 212 getPlotChildren().remove(bubble); 213 } 214 }); 215 pt.getChildren().add(ft); 216 } 217 pt.play(); 218 } else { 219 for (XYChart.Data<X,Y> d : series.getData()) { 220 final Node bubble = d.getNode(); 221 getPlotChildren().remove(bubble); 222 } 223 removeSeriesFromDisplay(series); 224 } 225 226 } 227 228 /** 229 * Create a Bubble for a given data item if it doesn't already have a node 230 * 231 * 232 * @param series 233 * @param seriesIndex The index of the series containing the item 234 * @param item The data item to create node for 235 * @param itemIndex The index of the data item in the series 236 * @return Node used for given data item 237 */ 238 private Node createBubble(Series<X, Y> series, int seriesIndex, final Data item, int itemIndex) { 239 Node bubble = item.getNode(); 240 // check if bubble has already been created 241 if (bubble == null) { 242 bubble = new StackPane(); 243 item.setNode(bubble); 244 } 245 // set bubble styles 246 bubble.getStyleClass().setAll("chart-bubble", "series" + seriesIndex, "data" + itemIndex, 247 series.defaultColorStyleClass); 248 return bubble; 249 } 250 251 /** 252 * This is called when the range has been invalidated and we need to update it. If the axis are auto 253 * ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the 254 * axis passing it that data. 255 */ 256 @Override protected void updateAxisRange() { 257 // For bubble chart we need to override this method as we need to let the axis know that they need to be able 258 // to cover the whole area occupied by the bubble not just its center data value 259 final Axis<X> xa = getXAxis(); 260 final Axis<Y> ya = getYAxis(); 261 List<X> xData = null; 262 List<Y> yData = null; 263 if(xa.isAutoRanging()) xData = new ArrayList<X>(); 264 if(ya.isAutoRanging()) yData = new ArrayList<Y>(); 265 final boolean xIsCategory = xa instanceof CategoryAxis; 266 final boolean yIsCategory = ya instanceof CategoryAxis; 267 if(xData != null || yData != null) { 268 for(Series<X,Y> series : getData()) { 269 for(Data<X,Y> data: series.getData()) { 270 if(xData != null) { 271 if(xIsCategory) { 272 xData.add(data.getXValue()); 273 } else { 274 xData.add(xa.toRealValue(xa.toNumericValue(data.getXValue()) + getDoubleValue(data.getExtraValue(), 0))); 275 xData.add(xa.toRealValue(xa.toNumericValue(data.getXValue()) - getDoubleValue(data.getExtraValue(), 0))); 276 } 277 } 278 if(yData != null){ 279 if(yIsCategory) { 280 yData.add(data.getYValue()); 281 } else { 282 yData.add(ya.toRealValue(ya.toNumericValue(data.getYValue()) + getDoubleValue(data.getExtraValue(), 0))); 283 yData.add(ya.toRealValue(ya.toNumericValue(data.getYValue()) - getDoubleValue(data.getExtraValue(), 0))); 284 } 285 } 286 } 287 } 288 if(xData != null) xa.invalidateRange(xData); 289 if(yData != null) ya.invalidateRange(yData); 290 } 291 } 292 293 /** 294 * This is called whenever a series is added or removed and the legend needs to be updated 295 */ 296 @Override protected void updateLegend() { 297 legend.getItems().clear(); 298 if (getData() != null) { 299 for (int seriesIndex=0; seriesIndex< getData().size(); seriesIndex++) { 300 Series<X,Y> series = getData().get(seriesIndex); 301 LegendItem legenditem = new LegendItem(series.getName()); 302 legenditem.getSymbol().getStyleClass().addAll("series"+seriesIndex,"chart-bubble", 303 "bubble-legend-symbol", series.defaultColorStyleClass); 304 legend.getItems().add(legenditem); 305 } 306 } 307 if (legend.getItems().size() > 0) { 308 if (getLegend() == null) { 309 setLegend(legend); 310 } 311 } else { 312 setLegend(null); 313 } 314 } 315}