1 /*
   2  * Copyright (c) 2010, 2015, 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 javafx.animation.FadeTransition;
  29 import javafx.animation.ParallelTransition;
  30 import javafx.application.Platform;
  31 import javafx.beans.NamedArg;
  32 import javafx.collections.FXCollections;
  33 import javafx.collections.ObservableList;
  34 import javafx.scene.AccessibleRole;
  35 import javafx.scene.Node;
  36 import javafx.scene.layout.StackPane;
  37 import javafx.util.Duration;
  38 
  39 import com.sun.javafx.charts.Legend.LegendItem;
  40 
  41 import java.util.Iterator;
  42 
  43 /**
  44  * Chart type that plots symbols for the data points in a series.
  45  * @since JavaFX 2.0
  46  */
  47 public class ScatterChart<X,Y> extends XYChart<X,Y> {
  48 
  49     // -------------- CONSTRUCTORS ----------------------------------------------
  50 
  51     /**
  52      * Construct a new ScatterChart with the given axis and data.
  53      *
  54      * @param xAxis The x axis to use
  55      * @param yAxis The y axis to use
  56      */
  57     public ScatterChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) {
  58         this(xAxis, yAxis, FXCollections.<Series<X, Y>>observableArrayList());
  59     }
  60 
  61     /**
  62      * Construct a new ScatterChart with the given axis and data.
  63      *
  64      * @param xAxis The x axis to use
  65      * @param yAxis The y axis to use
  66      * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart
  67      */
  68     public ScatterChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X,Y>> data) {
  69         super(xAxis,yAxis);
  70         setData(data);
  71     }
  72 
  73     // -------------- METHODS ------------------------------------------------------------------------------------------
  74 
  75     /** @inheritDoc */
  76     @Override protected void dataItemAdded(Series<X,Y> series, int itemIndex, Data<X,Y> item) {
  77         Node symbol = item.getNode();
  78         // check if symbol has already been created
  79         if (symbol == null) {
  80             symbol = new StackPane();
  81             symbol.setAccessibleRole(AccessibleRole.TEXT);
  82             symbol.setAccessibleRoleDescription("Point");
  83             symbol.focusTraversableProperty().bind(Platform.accessibilityActiveProperty());
  84             item.setNode(symbol);
  85         }
  86         // set symbol styles
  87         symbol.getStyleClass().setAll("chart-symbol", "series" + getData().indexOf(series), "data" + itemIndex,
  88                 series.defaultColorStyleClass);
  89         // add and fade in new symbol if animated
  90         if (shouldAnimate()) {
  91             symbol.setOpacity(0);
  92             getPlotChildren().add(symbol);
  93             FadeTransition ft = new FadeTransition(Duration.millis(500),symbol);
  94             ft.setToValue(1);
  95             ft.play();
  96         } else {
  97             getPlotChildren().add(symbol);
  98         }
  99     }
 100 
 101     /** @inheritDoc */
 102     @Override protected  void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) {
 103         final Node symbol = item.getNode();
 104 
 105         if (symbol != null) {
 106             symbol.focusTraversableProperty().unbind();
 107         }
 108 
 109         if (shouldAnimate()) {
 110             // fade out old symbol
 111             FadeTransition ft = new FadeTransition(Duration.millis(500),symbol);
 112             ft.setToValue(0);
 113             ft.setOnFinished(actionEvent -> {
 114                 getPlotChildren().remove(symbol);
 115                 removeDataItemFromDisplay(series, item);
 116                 symbol.setOpacity(1.0);
 117             });
 118             ft.play();
 119         } else {
 120             getPlotChildren().remove(symbol);
 121             removeDataItemFromDisplay(series, item);
 122         }
 123     }
 124 
 125     /** @inheritDoc */
 126     @Override protected void dataItemChanged(Data<X, Y> item) {
 127     }
 128 
 129     /** @inheritDoc */
 130     @Override protected  void seriesAdded(Series<X,Y> series, int seriesIndex) {
 131         // handle any data already in series
 132         for (int j=0; j<series.getData().size(); j++) {
 133             dataItemAdded(series,j,series.getData().get(j));
 134         }
 135     }
 136 
 137     /** @inheritDoc */
 138     @Override protected  void seriesRemoved(final Series<X,Y> series) {
 139         // remove all symbol nodes
 140         if (shouldAnimate()) {
 141             ParallelTransition pt = new ParallelTransition();
 142             pt.setOnFinished(event -> {
 143                 removeSeriesFromDisplay(series);
 144             });
 145             for (final Data<X,Y> d : series.getData()) {
 146                 final Node symbol = d.getNode();
 147                 // fade out old symbol
 148                 FadeTransition ft = new FadeTransition(Duration.millis(500),symbol);
 149                 ft.setToValue(0);
 150                 ft.setOnFinished(actionEvent -> {
 151                     getPlotChildren().remove(symbol);
 152                     symbol.setOpacity(1.0);
 153                 });
 154                 pt.getChildren().add(ft);
 155             }
 156             pt.play();
 157         } else {
 158              for (final Data<X,Y> d : series.getData()) {
 159                 final Node symbol = d.getNode();
 160                 getPlotChildren().remove(symbol);
 161             }
 162              removeSeriesFromDisplay(series);
 163         }
 164     }
 165 
 166     /** @inheritDoc */
 167     @Override protected void layoutPlotChildren() {
 168         // update symbol positions
 169         for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) {
 170             Series<X,Y> series = getData().get(seriesIndex);
 171             for (Iterator<Data<X, Y>> it = getDisplayedDataIterator(series); it.hasNext(); ) {
 172                 Data<X, Y> item = it.next();
 173                 double x = getXAxis().getDisplayPosition(item.getCurrentX());
 174                 double y = getYAxis().getDisplayPosition(item.getCurrentY());
 175                 if (Double.isNaN(x) || Double.isNaN(y)) {
 176                     continue;
 177                 }
 178                 Node symbol = item.getNode();
 179                 if (symbol != null) {
 180                     final double w = symbol.prefWidth(-1);
 181                     final double h = symbol.prefHeight(-1);
 182                     symbol.resizeRelocate(snapPositionX(x)-(w/2), snapPositionY(y)-(h/2),w,h);
 183                 }
 184             }
 185         }
 186     }
 187 
 188     @Override
 189     LegendItem createLegendItemForSeries(Series<X, Y> series, int seriesIndex) {
 190         LegendItem legendItem = new LegendItem(series.getName());
 191         Node node = series.getData().isEmpty() ? null : series.getData().get(0).getNode();
 192         if (node != null) {
 193             legendItem.getSymbol().getStyleClass().addAll(node.getStyleClass());
 194         }
 195         return legendItem;
 196     }
 197 }