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 com.sun.javafx.collections.NonIterableChange;
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.Iterator;
032import java.util.List;
033
034import javafx.animation.Interpolator;
035import javafx.animation.KeyFrame;
036import javafx.animation.KeyValue;
037import javafx.beans.property.BooleanProperty;
038import javafx.beans.property.ObjectProperty;
039import javafx.beans.property.ObjectPropertyBase;
040import javafx.beans.property.ReadOnlyObjectProperty;
041import javafx.beans.property.ReadOnlyObjectWrapper;
042import javafx.beans.property.SimpleObjectProperty;
043import javafx.beans.property.StringProperty;
044import javafx.beans.property.StringPropertyBase;
045import javafx.beans.value.ChangeListener;
046import javafx.beans.value.ObservableValue;
047import javafx.collections.FXCollections;
048import javafx.collections.ListChangeListener;
049import javafx.collections.ListChangeListener.Change;
050import javafx.collections.ObservableList;
051import javafx.geometry.Side;
052import javafx.scene.Group;
053import javafx.scene.Node;
054import javafx.scene.layout.Region;
055import javafx.scene.shape.ClosePath;
056import javafx.scene.shape.Line;
057import javafx.scene.shape.LineTo;
058import javafx.scene.shape.MoveTo;
059import javafx.scene.shape.Path;
060import javafx.scene.shape.Rectangle;
061import javafx.util.Duration;
062import java.util.BitSet;
063import javafx.css.StyleableBooleanProperty;
064import javafx.css.CssMetaData;
065import com.sun.javafx.css.converters.BooleanConverter;
066import java.util.HashMap;
067import java.util.Map;
068import javafx.css.Styleable;
069import javafx.css.StyleableProperty;
070
071/**
072 * Chart base class for all 2 axis charts. It is responsible for drawing the two
073 * axes and the plot content. It contains a list of all content in the plot and
074 * implementations of XYChart can add nodes to this list that need to be rendered.
075 * 
076 * <p>It is possible to install Tooltips on data items / symbols. 
077 * For example the following code snippet installs Tooltip on the 1st data item.
078 * 
079 * <pre><code>
080 *  XYChart.Data item = ( XYChart.Data)series.getData().get(0);
081 *  Tooltip.install(item.getNode(), new Tooltip("Symbol-0"));
082 * </code></pre>
083 *
084 */
085public abstract class XYChart<X,Y> extends Chart {
086
087    // -------------- PRIVATE FIELDS -----------------------------------------------------------------------------------
088
089    // to indicate which colors are being used for the series
090    BitSet colorBits = new BitSet(8); 
091    static String DEFAULT_COLOR = "default-color";
092    Map<Series, Integer> seriesColorMap = new HashMap<Series, Integer>();
093    private boolean rangeValid = false;
094    private final Line verticalZeroLine = new Line();
095    private final Line horizontalZeroLine = new Line();
096    private final Path verticalGridLines = new Path();
097    private final Path horizontalGridLines = new Path();
098    private final Path horizontalRowFill = new Path();
099    private final Path verticalRowFill = new Path();
100    private final Region plotBackground = new Region();
101    private final Group plotArea = new Group(){
102        @Override public void requestLayout() {} // suppress layout requests
103    };
104    private final Group plotContent = new Group();
105    private final Rectangle plotAreaClip = new Rectangle();
106    /* start pointer of a series linked list. */
107    Series<X,Y> begin = null;
108    /** This is called when a series is added or removed from the chart */
109    private final ListChangeListener<Series<X,Y>> seriesChanged = new ListChangeListener<Series<X,Y>>() {
110        @Override public void onChanged(Change<? extends Series<X,Y>> c) {
111            while (c.next()) {
112                if (c.getRemoved().size() > 0) updateLegend();
113                for (Series<X,Y> series : c.getRemoved()) {
114                    series.setChart(null);
115                    seriesRemoved(series);
116//                    seriesDefaultColorIndex --;
117                }
118                for(int i=c.getFrom(); i<c.getTo() && !c.wasPermutated(); i++) {
119                    final Series<X,Y> series = c.getList().get(i);
120                    // add new listener to data
121                    series.setChart(XYChart.this);
122                    // update linkedList Pointers for series
123                    if (XYChart.this.begin == null) {
124                        XYChart.this.begin = getData().get(i);
125                        XYChart.this.begin.next = null;
126                    } else {
127                        if (i == 0) {
128                            getData().get(0).next = XYChart.this.begin;
129                            begin = getData().get(0);
130                        } else {
131                            Series ptr = begin;
132                            for (int j = 0; j < i -1 && ptr!=null ; j++) {
133                                ptr = ptr.next;
134                            }
135                            if (ptr != null) {
136                                getData().get(i).next = ptr.next;
137                                ptr.next = getData().get(i);
138                            }
139
140                        }
141                    }
142                    // update default color style class
143                    int nextClearBit = colorBits.nextClearBit(0);
144                    colorBits.set(nextClearBit, true);
145                    series.defaultColorStyleClass = DEFAULT_COLOR+(nextClearBit%8);
146                    seriesColorMap.put(series, nextClearBit%8);
147                    // inform sub-classes of series added
148                    seriesAdded(series, i);
149                }
150                if (c.getFrom() < c.getTo()) updateLegend();
151                seriesChanged(c);
152                // RT-12069, linked list pointers should update when list is permutated.
153                if (c.wasPermutated() && getData().size() > 0) {
154                    XYChart.this.begin = getData().get(0);
155                    Series<X,Y> ptr = begin;
156                    for(int k = 1; k < getData().size() && ptr != null; k++) {
157                        ptr.next = getData().get(k);
158                        ptr = ptr.next;
159                    }
160                    ptr.next = null;
161                }
162            }
163            // update axis ranges
164            invalidateRange();
165            // lay everything out
166            requestChartLayout();
167        }
168    };
169    
170    // -------------- PUBLIC PROPERTIES --------------------------------------------------------------------------------
171
172    private final Axis<X> xAxis;
173    /** Get the X axis, by default it is along the bottom of the plot */
174    public Axis<X> getXAxis() { return xAxis; }
175
176    private final Axis<Y> yAxis;
177    /** Get the Y axis, by default it is along the left of the plot */
178    public Axis<Y> getYAxis() { return yAxis; }
179
180    /** XYCharts data */
181    private ObjectProperty<ObservableList<Series<X,Y>>> data = new ObjectPropertyBase<ObservableList<Series<X,Y>>>() {
182        private ObservableList<Series<X,Y>> old;
183        @Override protected void invalidated() {
184            final ObservableList<Series<X,Y>> current = getValue();
185            int saveAnimationState = -1;
186            // add remove listeners
187            if(old != null) {
188                old.removeListener(seriesChanged);
189                // Set animated to false so we don't animate both remove and add
190                // at the same time. RT-14163 
191                // RT-21295 - disable animated only when current is also not null. 
192                if (current != null && old.size() > 0) {
193                    saveAnimationState = (old.get(0).getChart().getAnimated()) ? 1 : 2;
194                    old.get(0).getChart().setAnimated(false);
195                }
196            }
197            if(current != null) current.addListener(seriesChanged);
198            // fire series change event if series are added or removed
199            if(old != null || current != null) {
200                final List<Series<X,Y>> removed = (old != null) ? old : Collections.<Series<X,Y>>emptyList();
201                final int toIndex = (current != null) ? current.size() : 0;
202                // let series listener know all old series have been removed and new that have been added
203                if (toIndex > 0 || !removed.isEmpty()) {
204                    seriesChanged.onChanged(new NonIterableChange<Series<X,Y>>(0, toIndex, current){
205                        @Override public List<Series<X,Y>> getRemoved() { return removed; }
206                        @Override protected int[] getPermutation() {
207                            return new int[0];
208                        }
209                    });
210                }
211            } else if (old != null && old.size() > 0) {
212                // let series listener know all old series have been removed
213                seriesChanged.onChanged(new NonIterableChange<Series<X,Y>>(0, 0, current){
214                    @Override public List<Series<X,Y>> getRemoved() { return old; }
215                    @Override protected int[] getPermutation() {
216                        return new int[0];
217                    }
218                });
219            }
220            // restore animated on chart.
221            if (current != null && current.size() > 0 && saveAnimationState != -1) {
222                current.get(0).getChart().setAnimated((saveAnimationState == 1) ? true : false);
223            }
224            old = current;
225        }
226
227        public Object getBean() {
228            return XYChart.this;
229        }
230
231        public String getName() {
232            return "data";
233        }
234    };
235    public final ObservableList<Series<X,Y>> getData() { return data.getValue(); }
236    public final void setData(ObservableList<Series<X,Y>> value) { data.setValue(value); }
237    public final ObjectProperty<ObservableList<Series<X,Y>>> dataProperty() { return data; }
238
239    /** True if vertical grid lines should be drawn */ 
240    private BooleanProperty verticalGridLinesVisible = new StyleableBooleanProperty(true) {
241        @Override protected void invalidated() {
242            requestChartLayout();
243        }
244
245        @Override
246        public Object getBean() {
247            return XYChart.this;
248        }
249
250        @Override
251        public String getName() {
252            return "verticalGridLinesVisible";
253        }
254
255        @Override
256        public CssMetaData<XYChart<?,?>,Boolean> getCssMetaData() {
257            return StyleableProperties.VERTICAL_GRID_LINE_VISIBLE;
258        }
259    };
260    /**
261     * Indicates whether vertical grid lines are visible or not.
262     *
263     * @return true if verticalGridLines are visible else false.
264     * @see #verticalGridLinesVisible
265     */
266    public final boolean getVerticalGridLinesVisible() { return verticalGridLinesVisible.get(); }
267    public final void setVerticalGridLinesVisible(boolean value) { verticalGridLinesVisible.set(value); }
268    public final BooleanProperty verticalGridLinesVisibleProperty() { return verticalGridLinesVisible; }
269
270    /** True if horizontal grid lines should be drawn */
271    private BooleanProperty horizontalGridLinesVisible = new StyleableBooleanProperty(true) {
272        @Override protected void invalidated() {
273            requestChartLayout();
274        }
275
276        @Override
277        public Object getBean() {
278            return XYChart.this;
279        }
280
281        @Override
282        public String getName() {
283            return "horizontalGridLinesVisible";
284        }
285        
286        @Override
287        public CssMetaData<XYChart<?,?>,Boolean> getCssMetaData() {
288            return StyleableProperties.HORIZONTAL_GRID_LINE_VISIBLE;
289        }        
290    };
291    public final boolean isHorizontalGridLinesVisible() { return horizontalGridLinesVisible.get(); }
292    public final void setHorizontalGridLinesVisible(boolean value) { horizontalGridLinesVisible.set(value); }
293    public final BooleanProperty horizontalGridLinesVisibleProperty() { return horizontalGridLinesVisible; }
294
295    /** If true then alternative vertical columns will have fills */
296    private BooleanProperty alternativeColumnFillVisible = new StyleableBooleanProperty(false) {
297        @Override protected void invalidated() {
298            requestChartLayout();
299        }
300
301        @Override
302        public Object getBean() {
303            return XYChart.this;
304        }
305
306        @Override
307        public String getName() {
308            return "alternativeColumnFillVisible";
309        }
310
311        @Override
312        public CssMetaData<XYChart<?,?>,Boolean> getCssMetaData() {
313            return StyleableProperties.ALTERNATIVE_COLUMN_FILL_VISIBLE;
314        }        
315    };
316    public final boolean isAlternativeColumnFillVisible() { return alternativeColumnFillVisible.getValue(); }
317    public final void setAlternativeColumnFillVisible(boolean value) { alternativeColumnFillVisible.setValue(value); }
318    public final BooleanProperty alternativeColumnFillVisibleProperty() { return alternativeColumnFillVisible; }
319
320    /** If true then alternative horizontal rows will have fills */
321    private BooleanProperty alternativeRowFillVisible = new StyleableBooleanProperty(true) {
322        @Override protected void invalidated() {
323            requestChartLayout();
324        }
325
326        @Override
327        public Object getBean() {
328            return XYChart.this;
329        }
330
331        @Override
332        public String getName() {
333            return "alternativeRowFillVisible";
334        }
335
336        @Override
337        public CssMetaData<XYChart<?,?>,Boolean> getCssMetaData() {
338            return StyleableProperties.ALTERNATIVE_ROW_FILL_VISIBLE;
339        }                
340    };
341    public final boolean isAlternativeRowFillVisible() { return alternativeRowFillVisible.getValue(); }
342    public final void setAlternativeRowFillVisible(boolean value) { alternativeRowFillVisible.setValue(value); }
343    public final BooleanProperty alternativeRowFillVisibleProperty() { return alternativeRowFillVisible; }
344
345    /**
346     * If this is true and the vertical axis has both positive and negative values then a additional axis line
347     * will be drawn at the zero point
348     *
349     * @defaultValue true
350     */
351    private BooleanProperty verticalZeroLineVisible = new StyleableBooleanProperty(true) {
352        @Override protected void invalidated() {
353            requestChartLayout();
354        }
355
356        @Override
357        public Object getBean() {
358            return XYChart.this;
359        }
360
361        @Override
362        public String getName() {
363            return "verticalZeroLineVisible";
364        }
365
366        @Override
367        public CssMetaData<XYChart<?,?>,Boolean> getCssMetaData() {
368            return StyleableProperties.VERTICAL_ZERO_LINE_VISIBLE;
369        }                
370    };
371    public final boolean isVerticalZeroLineVisible() { return verticalZeroLineVisible.get(); }
372    public final void setVerticalZeroLineVisible(boolean value) { verticalZeroLineVisible.set(value); }
373    public final BooleanProperty verticalZeroLineVisibleProperty() { return verticalZeroLineVisible; }
374
375    /**
376     * If this is true and the horizontal axis has both positive and negative values then a additional axis line
377     * will be drawn at the zero point
378     *
379     * @defaultValue true
380     */
381    private BooleanProperty horizontalZeroLineVisible = new StyleableBooleanProperty(true) {
382        @Override protected void invalidated() {
383            requestChartLayout();
384        }
385
386        @Override
387        public Object getBean() {
388            return XYChart.this;
389        }
390
391        @Override
392        public String getName() {
393            return "horizontalZeroLineVisible";
394        }
395
396        @Override
397        public CssMetaData<XYChart<?,?>,Boolean> getCssMetaData() {
398            return StyleableProperties.HORIZONTAL_ZERO_LINE_VISIBLE;
399        }                
400    };
401    public final boolean isHorizontalZeroLineVisible() { return horizontalZeroLineVisible.get(); }
402    public final void setHorizontalZeroLineVisible(boolean value) { horizontalZeroLineVisible.set(value); }
403    public final BooleanProperty horizontalZeroLineVisibleProperty() { return horizontalZeroLineVisible; }
404
405    // -------------- PROTECTED PROPERTIES -----------------------------------------------------------------------------
406
407    /**
408     * Modifiable and observable list of all content in the plot. This is where implementations of XYChart should add
409     * any nodes they use to draw their plot.
410     *
411     * @return Observable list of plot children
412     */
413    protected ObservableList<Node> getPlotChildren() {
414        return plotContent.getChildren();
415    }
416
417    // -------------- CONSTRUCTOR --------------------------------------------------------------------------------------
418
419    /**
420     * Constructs a XYChart given the two axes. The initial content for the chart
421     * plot background and plot area that includes vertical and horizontal grid
422     * lines and fills, are added.
423     *
424     * @param xAxis X Axis for this XY chart
425     * @param yAxis Y Axis for this XY chart
426     */
427    public XYChart(Axis<X> xAxis, Axis<Y> yAxis) {
428        this.xAxis = xAxis;
429        if(xAxis.getSide() == null) xAxis.setSide(Side.BOTTOM);
430        this.yAxis = yAxis;
431        if(yAxis.getSide() == null) yAxis.setSide(Side.LEFT);
432        // RT-23123 autoranging leads to charts incorrect appearance.
433        xAxis.autoRangingProperty().addListener(new ChangeListener<Boolean>() {
434            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
435                updateAxisRange();
436            }
437        });
438        yAxis.autoRangingProperty().addListener(new ChangeListener<Boolean>() {
439            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
440                updateAxisRange();
441            }
442        });
443        // add initial content to chart content
444        getChartChildren().addAll(plotBackground,plotArea,xAxis,yAxis);
445        // We don't want plotArea or plotContent to autoSize or do layout
446        plotArea.setAutoSizeChildren(false);
447        plotContent.setAutoSizeChildren(false);
448        // setup clipping on plot area
449        plotAreaClip.setSmooth(false);
450        plotArea.setClip(plotAreaClip);
451        // add children to plot area
452        plotArea.getChildren().addAll(
453                verticalRowFill, horizontalRowFill,
454                verticalGridLines, horizontalGridLines,
455                verticalZeroLine, horizontalZeroLine,
456                plotContent);
457        // setup css style classes
458        plotContent.getStyleClass().setAll("plot-content");
459        plotBackground.getStyleClass().setAll("chart-plot-background");
460        verticalRowFill.getStyleClass().setAll("chart-alternative-column-fill");
461        horizontalRowFill.getStyleClass().setAll("chart-alternative-row-fill");
462        verticalGridLines.getStyleClass().setAll("chart-vertical-grid-lines");
463        horizontalGridLines.getStyleClass().setAll("chart-horizontal-grid-lines");
464        verticalZeroLine.getStyleClass().setAll("chart-vertical-zero-line");
465        horizontalZeroLine.getStyleClass().setAll("chart-horizontal-zero-line");
466        // mark plotContent as unmanaged as its preferred size changes do not effect our layout
467        plotContent.setManaged(false);
468        plotArea.setManaged(false);
469        // listen to animation on/off and sync to axis
470        animatedProperty().addListener(new ChangeListener<Boolean>() {
471            @Override public void changed(ObservableValue<? extends Boolean> valueModel, Boolean oldValue, Boolean newValue) {
472                if(getXAxis() != null) getXAxis().setAnimated(newValue);
473                if(getYAxis() != null) getYAxis().setAnimated(newValue);
474            }
475        });
476    }
477
478    // -------------- METHODS ------------------------------------------------------------------------------------------
479
480    @Override public void requestLayout() {
481        super.requestLayout();
482        // RT-22726 Charts legend does not resize correctly
483        Node legend = getLegend();
484        if (legend != null && legend instanceof Region) {
485            ((Region)legend).requestLayout();
486        }
487    }
488    
489    /**
490     * Gets the size of the data returning 0 if the data is null
491     *
492     * @return The number of items in data, or null if data is null
493     */
494    final int getDataSize() {
495        final ObservableList<Series<X,Y>> data = getData();
496        return (data!=null) ? data.size() : 0;
497    }
498
499    /** Called when a series's name has changed */
500    private void seriesNameChanged() {
501        updateLegend();
502        requestChartLayout();
503    }
504
505    @SuppressWarnings({"UnusedParameters"})
506    private void dataItemsChanged(Series<X,Y> series, List<Data<X,Y>> removed, int addedFrom, int addedTo, boolean permutation) {
507        for (Data<X,Y> item : removed) {
508            dataItemRemoved(item, series);
509        }
510        for(int i=addedFrom; i<addedTo; i++) {
511            Data<X,Y> item = series.getData().get(i);
512            dataItemAdded(series, i, item);
513        }
514        invalidateRange();
515        requestChartLayout();
516    }
517
518    private void dataXValueChanged(Data<X,Y> item) {
519        if(item.getCurrentX() != item.getXValue()) invalidateRange();
520        dataItemChanged(item);
521        if (shouldAnimate()) {
522            animate(
523                    new KeyFrame(Duration.ZERO, new KeyValue(item.currentXProperty(), item.getCurrentX())),
524                    new KeyFrame(Duration.millis(700), new KeyValue(item.currentXProperty(), item.getXValue(), Interpolator.EASE_BOTH))
525            );
526        } else {
527            item.setCurrentX(item.getXValue());
528            requestChartLayout();
529        }
530    }
531
532    private void dataYValueChanged(Data<X,Y> item) {
533        if(item.getCurrentY() != item.getYValue()) invalidateRange();
534        dataItemChanged(item);
535        if (shouldAnimate()) {
536            animate(
537                    new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), item.getCurrentY())),
538                    new KeyFrame(Duration.millis(700), new KeyValue(item.currentYProperty(), item.getYValue(), Interpolator.EASE_BOTH))
539            );
540        } else {
541            item.setCurrentY(item.getYValue());
542            requestChartLayout();
543        }
544    }
545
546    private void dataExtraValueChanged(Data<X,Y> item) {
547        if(item.getCurrentY() != item.getYValue()) invalidateRange();
548        dataItemChanged(item);
549        if (shouldAnimate()) {
550            animate(
551                    new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), item.getCurrentY())),
552                    new KeyFrame(Duration.millis(700), new KeyValue(item.currentYProperty(), item.getYValue(), Interpolator.EASE_BOTH))
553            );
554        } else {
555            item.setCurrentY(item.getYValue());
556            requestChartLayout();
557        }
558    }
559
560    /**
561     * This is called whenever a series is added or removed and the legend needs to be updated
562     */
563    protected void updateLegend(){}
564
565    /**
566     * Called when a data item has been added to a series. This is where implementations of XYChart can create/add new
567     * nodes to getPlotChildren to represent this data item. They also may animate that data add with a fade in or
568     * similar if animated = true.
569     *
570     * @param series    The series the data item was added to
571     * @param itemIndex The index of the new item within the series
572     * @param item      The new data item that was added
573     */
574    protected abstract void dataItemAdded(Series<X,Y> series, int itemIndex, Data<X,Y> item);
575
576    /**
577     * Called when a data item has been removed from data model but it is still visible on the chart. Its still visible
578     * so that you can handle animation for removing it in this method. After you are done animating the data item you
579     * must call removeDataItemFromDisplay() to remove the items node from being displayed on the chart.
580     *
581     * @param item   The item that has been removed from the series
582     * @param series The series the item was removed from
583     */
584    protected abstract void dataItemRemoved(Data<X, Y> item, Series<X, Y> series);
585
586    /**
587     * Called when a data item has changed, ie its xValue, yValue or extraValue has changed. 
588     *
589     * @param item    The data item who was changed
590     */
591    protected abstract void dataItemChanged(Data<X, Y> item);
592    /**
593     * A series has been added to the charts data model. This is where implementations of XYChart can create/add new
594     * nodes to getPlotChildren to represent this series. Also you have to handle adding any data items that are
595     * already in the series. You may simply call dataItemAdded() for each one or provide some different animation for
596     * a whole series being added.
597     *
598     * @param series      The series that has been added
599     * @param seriesIndex The index of the new series
600     */
601    protected abstract void seriesAdded(Series<X, Y> series, int seriesIndex);
602
603    /**
604     * A series has been removed from the data model but it is still visible on the chart. Its still visible
605     * so that you can handle animation for removing it in this method. After you are done animating the data item you
606     * must call removeSeriesFromDisplay() to remove the series from the display list.
607     *
608     * @param series The series that has been removed
609     */
610    protected abstract void seriesRemoved(Series<X,Y> series);
611
612    /** Called when each atomic change is made to the list of series for this chart */
613    protected void seriesChanged(Change<? extends Series> c) {}
614
615    /**
616     * This is called when a data change has happened that may cause the range to be invalid.
617     */
618    private void invalidateRange() {
619        rangeValid = false;
620    }
621
622    /**
623     * This is called when the range has been invalidated and we need to update it. If the axis are auto
624     * ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the
625     * axis passing it that data.
626     */
627    protected void updateAxisRange() {
628        final Axis<X> xa = getXAxis();
629        final Axis<Y> ya = getYAxis();
630        List<X> xData = null;
631        List<Y> yData = null;
632        if(xa.isAutoRanging()) xData = new ArrayList<X>();
633        if(ya.isAutoRanging()) yData = new ArrayList<Y>();
634        if(xData != null || yData != null) {
635            for(Series<X,Y> series : getData()) {
636                for(Data<X,Y> data: series.getData()) {
637                    if(xData != null) xData.add(data.getXValue());
638                    if(yData != null) yData.add(data.getYValue());
639                }
640            }
641            if(xData != null) xa.invalidateRange(xData);
642            if(yData != null) ya.invalidateRange(yData);
643        }
644    }
645
646    /**
647     * Called to update and layout the plot children. This should include all work to updates nodes representing
648     * the plot on top of the axis and grid lines etc. The origin is the top left of the plot area, the plot area with
649     * can be got by getting the width of the x axis and its height from the height of the y axis.
650     */
651    protected abstract void layoutPlotChildren();
652
653    /** @inheritDoc */
654    @Override protected final void layoutChartChildren(double top, double left, double width, double height) {
655        if(getData() == null) return;
656        if (!rangeValid) {
657            rangeValid = true;
658            if(getData() != null) updateAxisRange();
659        }
660        // snap top and left to pixels
661        top = snapPosition(top);
662        left = snapPosition(left);
663        // get starting stuff
664        final Axis<X> xa = getXAxis();
665        final ObservableList<Axis.TickMark<X>> xaTickMarks = xa.getTickMarks();
666        final Axis<Y> ya = getYAxis();
667        final ObservableList<Axis.TickMark<Y>> yaTickMarks = ya.getTickMarks();
668        // check we have 2 axises and know their sides
669        if (xa == null || ya == null || xa.getSide() == null || ya.getSide() == null) return;
670        // try and work out width and height of axises
671        double xAxisWidth = 0;
672        double xAxisHeight = 30; // guess x axis height to start with
673        double yAxisWidth = 0;
674        double yAxisHeight = 0;
675        for (int count=0; count<5; count ++) {
676            yAxisHeight = height-xAxisHeight;
677            yAxisWidth = ya.prefWidth(yAxisHeight);
678            xAxisWidth = width - yAxisWidth;
679            double newXAxisHeight = xa.prefHeight(xAxisWidth);
680            if (newXAxisHeight == xAxisHeight) break;
681            xAxisHeight = newXAxisHeight;
682        }
683        // round axis sizes up to whole integers to snap to pixel
684        xAxisWidth = Math.ceil(xAxisWidth);
685        xAxisHeight = Math.ceil(xAxisHeight);
686        yAxisWidth = Math.ceil(yAxisWidth);
687        yAxisHeight = Math.ceil(yAxisHeight);
688        // calc xAxis height
689        double xAxisY = 0;
690        if (xa.getSide().equals(Side.TOP)) {
691            xa.setVisible(true);
692            xAxisY = top+1;
693            top += xAxisHeight;
694        } else if (xa.getSide().equals(Side.BOTTOM)) {
695            xa.setVisible(true);
696            xAxisY = top + yAxisHeight;
697        } else {
698            // X axis should never be left or right so hide
699            xa.setVisible(false);
700            xAxisHeight = 0;
701        }
702        // calc yAxis width
703        double yAxisX = 0;
704        if (ya.getSide().equals(Side.LEFT)) {
705            ya.setVisible(true);
706            yAxisX = left +1;
707            left += yAxisWidth;
708        } else if (ya.getSide().equals(Side.RIGHT)) {
709            ya.setVisible(true);
710            yAxisX = left + xAxisWidth;
711        } else {
712            // Y axis should never be top or bottom so hide
713            ya.setVisible(false);
714            yAxisWidth = 0;
715        }
716        // resize axises
717        xa.resizeRelocate(left, xAxisY, xAxisWidth, xAxisHeight);
718        ya.resizeRelocate(yAxisX, top, yAxisWidth, yAxisHeight);
719        // When the chart is resized, need to specifically call out the axises
720        // to lay out as they are unmanaged.
721        xa.requestAxisLayout();
722        xa.layout();
723        ya.requestAxisLayout();
724        ya.layout();
725        // layout plot content
726        layoutPlotChildren();
727        // get axis zero points
728        final double xAxisZero = xa.getZeroPosition();
729        final double yAxisZero = ya.getZeroPosition();
730        // position vertical and horizontal zero lines
731        if(Double.isNaN(xAxisZero) || !isVerticalZeroLineVisible()) {
732            verticalZeroLine.setVisible(false);
733        } else {
734            verticalZeroLine.setStartX(left+xAxisZero+0.5);
735            verticalZeroLine.setStartY(top);
736            verticalZeroLine.setEndX(left+xAxisZero+0.5);
737            verticalZeroLine.setEndY(top+yAxisHeight);
738            verticalZeroLine.setVisible(true);
739        }
740        if(Double.isNaN(yAxisZero) || !isHorizontalZeroLineVisible()) {
741            horizontalZeroLine.setVisible(false);
742        } else {
743            horizontalZeroLine.setStartX(left);
744            horizontalZeroLine.setStartY(top+yAxisZero+0.5);
745            horizontalZeroLine.setEndX(left+xAxisWidth);
746            horizontalZeroLine.setEndY(top+yAxisZero+0.5);
747            horizontalZeroLine.setVisible(true);
748        }
749        // layout plot background
750        plotBackground.resizeRelocate(left, top, xAxisWidth, yAxisHeight);
751        // update clip
752        plotAreaClip.setX(left);
753        plotAreaClip.setY(top);
754        plotAreaClip.setWidth(xAxisWidth+1);
755        plotAreaClip.setHeight(yAxisHeight+1);
756//        plotArea.setClip(new Rectangle(left, top, xAxisWidth, yAxisHeight));
757        // position plot group, its origin is the bottom left corner of the plot area
758        plotContent.setLayoutX(left);
759        plotContent.setLayoutY(top);
760        plotContent.requestLayout(); // Note: not sure this is right, maybe plotContent should be resizeable
761        // update vertical grid lines
762        verticalGridLines.getElements().clear();
763        if(getVerticalGridLinesVisible()) {
764            for(int i=0; i < xaTickMarks.size(); i++) {
765                Axis.TickMark<X> tick = xaTickMarks.get(i);
766                double pixelOffset = (i==(xaTickMarks.size()-1)) ? -0.5 : 0.5; 
767                final double x = xa.getDisplayPosition(tick.getValue());
768                if ((x!=xAxisZero || !isVerticalZeroLineVisible()) && x > 0 && x <= xAxisWidth) {
769                    verticalGridLines.getElements().add(new MoveTo(left+x+pixelOffset,top));
770                    verticalGridLines.getElements().add(new LineTo(left+x+pixelOffset,top+yAxisHeight));
771                }
772            }
773        }
774        // update horizontal grid lines
775        horizontalGridLines.getElements().clear();
776        if(isHorizontalGridLinesVisible()) {
777            for(int i=0; i < yaTickMarks.size(); i++) {
778                Axis.TickMark<Y> tick = yaTickMarks.get(i);
779                double pixelOffset = (i==(yaTickMarks.size()-1)) ? -0.5 : 0.5;
780                final double y = ya.getDisplayPosition(tick.getValue());
781                if ((y!=yAxisZero || !isHorizontalZeroLineVisible()) && y >= 0 && y < yAxisHeight) {
782                    horizontalGridLines.getElements().add(new MoveTo(left,top+y+pixelOffset));
783                    horizontalGridLines.getElements().add(new LineTo(left+xAxisWidth,top+y+pixelOffset));
784                }
785            }
786        }
787        // Note: is there a more efficient way to calculate horizontal and vertical row fills?
788        // update vertical row fill
789        verticalRowFill.getElements().clear();
790        if (isAlternativeColumnFillVisible()) {
791            // tick marks are not sorted so get all the positions and sort them
792            final List<Double> tickPositionsPositive = new ArrayList<Double>();
793            final List<Double> tickPositionsNegative = new ArrayList<Double>();
794            for(int i=0; i < xaTickMarks.size(); i++) {
795                double pos = xa.getDisplayPosition((X) xaTickMarks.get(i).getValue());
796                if (pos == xAxisZero) {
797                    tickPositionsPositive.add(pos);
798                    tickPositionsNegative.add(pos);
799                } else if (pos < xAxisZero) {
800                    tickPositionsPositive.add(pos);
801                } else {
802                    tickPositionsNegative.add(pos);
803                }
804            }
805            Collections.sort(tickPositionsPositive);
806            Collections.sort(tickPositionsNegative);
807            // iterate over every pair of positive tick marks and create fill
808            for(int i=1; i < tickPositionsPositive.size(); i+=2) {
809                if((i+1) < tickPositionsPositive.size()) {
810                    final double x1 = tickPositionsPositive.get(i);
811                    final double x2 = tickPositionsPositive.get(i+1);
812                    verticalRowFill.getElements().addAll(
813                            new MoveTo(left+x1,top),
814                            new LineTo(left+x1,top+yAxisHeight),
815                            new LineTo(left+x2,top+yAxisHeight),
816                            new LineTo(left+x2,top),
817                            new ClosePath());
818                }
819            }
820            // iterate over every pair of positive tick marks and create fill
821            for(int i=0; i < tickPositionsNegative.size(); i+=2) {
822                if((i+1) < tickPositionsNegative.size()) {
823                    final double x1 = tickPositionsNegative.get(i);
824                    final double x2 = tickPositionsNegative.get(i+1);
825                    verticalRowFill.getElements().addAll(
826                            new MoveTo(left+x1,top),
827                            new LineTo(left+x1,top+yAxisHeight),
828                            new LineTo(left+x2,top+yAxisHeight),
829                            new LineTo(left+x2,top),
830                            new ClosePath());
831                }
832            }
833        }
834        // update horizontal row fill
835        horizontalRowFill.getElements().clear();
836        if (isAlternativeRowFillVisible()) {
837            // tick marks are not sorted so get all the positions and sort them
838            final List<Double> tickPositionsPositive = new ArrayList<Double>();
839            final List<Double> tickPositionsNegative = new ArrayList<Double>();
840            for(int i=0; i < yaTickMarks.size(); i++) {
841                double pos = ya.getDisplayPosition((Y) yaTickMarks.get(i).getValue());
842                if (pos == yAxisZero) {
843                    tickPositionsPositive.add(pos);
844                    tickPositionsNegative.add(pos);
845                } else if (pos < yAxisZero) {
846                    tickPositionsPositive.add(pos);
847                } else {
848                    tickPositionsNegative.add(pos);
849                }
850            }
851            Collections.sort(tickPositionsPositive);
852            Collections.sort(tickPositionsNegative);
853            // iterate over every pair of positive tick marks and create fill
854            for(int i=1; i < tickPositionsPositive.size(); i+=2) {
855                if((i+1) < tickPositionsPositive.size()) {
856                    final double y1 = tickPositionsPositive.get(i);
857                    final double y2 = tickPositionsPositive.get(i+1);
858                    horizontalRowFill.getElements().addAll(
859                            new MoveTo(left, top + y1),
860                            new LineTo(left + xAxisWidth, top + y1),
861                            new LineTo(left + xAxisWidth, top + y2),
862                            new LineTo(left, top + y2),
863                            new ClosePath());
864                }
865            }
866            // iterate over every pair of positive tick marks and create fill
867            for(int i=0; i < tickPositionsNegative.size(); i+=2) {
868                if((i+1) < tickPositionsNegative.size()) {
869                    final double y1 = tickPositionsNegative.get(i);
870                    final double y2 = tickPositionsNegative.get(i+1);
871                    horizontalRowFill.getElements().addAll(
872                            new MoveTo(left, top + y1),
873                            new LineTo(left + xAxisWidth, top + y1),
874                            new LineTo(left + xAxisWidth, top + y2),
875                            new LineTo(left, top + y2),
876                            new ClosePath());
877                }
878            }
879        }
880//
881    }
882
883    /**
884     * Get the index of the series in the series linked list.
885     *
886     * @param series The series to find index for
887     * @return index of the series in series list
888     */
889    int getSeriesIndex(Series series) {
890        int itemIndex = 0;
891        for (Series s = XYChart.this.begin; s != null; s = s.next) {
892            if (s == series) break;
893            itemIndex++;
894        }
895        return itemIndex;
896    }
897
898    /**
899     * Computes the size of series linked list
900     * @return size of series linked list
901     */
902    int getSeriesSize() {
903        int count = 0;
904        for (Series d = XYChart.this.begin; d != null; d = d.next) {
905            count++;
906        }
907        return count;
908    }
909    
910    /**
911     * This should be called from seriesRemoved() when you are finished with any animation for deleting the series from
912     * the chart. It will remove the series from showing up in the Iterator returned by getDisplayedSeriesIterator().
913     *
914     * @param series The series to remove
915     */
916    protected final void removeSeriesFromDisplay(Series<X, Y> series) {
917        if (begin == series) {
918            begin = series.next;
919        } else {
920            Series ptr = begin;
921            while(ptr != null && ptr.next != series) {
922                ptr = ptr.next;
923            }
924            if (ptr != null)
925            ptr.next = series.next;
926        }
927    }
928
929    /**
930     * XYChart maintains a list of all series currently displayed this includes all current series + any series that
931     * have recently been deleted that are in the process of being faded(animated) out. This creates and returns a
932     * iterator over that list. This is what implementations of XYChart should use when plotting data.
933     *
934     * @return iterator over currently displayed series
935     */
936    protected final Iterator<Series<X,Y>> getDisplayedSeriesIterator() {
937        return new Iterator<Series<X, Y>>() {
938            private boolean start = true;
939            private Series<X,Y> current = begin;
940            @Override public boolean hasNext() {
941                if (start) {
942                    return current != null;
943                } else {
944                    return current.next != null;
945                }
946            }
947            @Override public Series<X, Y> next() {
948                if (start) {
949                    start = false;
950                } else if (current!=null) {
951                    current = current.next;
952                }
953                return current;
954            }
955            @Override public void remove() {
956                throw new UnsupportedOperationException("We don't support removing items from the displayed series list.");
957            }
958        };
959    }
960
961    /**
962     * The current displayed data value plotted on the X axis. This may be the same as xValue or different. It is
963     * used by XYChart to animate the xValue from the old value to the new value. This is what you should plot
964     * in any custom XYChart implementations. Some XYChart chart implementations such as LineChart also use this
965     * to animate when data is added or removed.
966     */
967    protected final X getCurrentDisplayedXValue(Data<X,Y> item) { return item.getCurrentX(); }
968
969    /** Set the current displayed data value plotted on X axis.
970     *
971     * @param item The XYChart.Data item from which the current X axis data value is obtained.
972     * @see #getCurrentDisplayedXValue
973     */
974    protected final void setCurrentDisplayedXValue(Data<X,Y> item, X value) { item.setCurrentX(value); }
975
976    /** The current displayed data value property that is plotted on X axis.
977     *
978     * @param item The XYChart.Data item from which the current X axis data value property object is obtained.
979     * @return The current displayed X data value ObjectProperty.
980     * @see #getCurrentDisplayedXValue
981     */
982    protected final ObjectProperty<X> currentDisplayedXValueProperty(Data<X,Y> item) { return item.currentXProperty(); }
983
984    /**
985     * The current displayed data value plotted on the Y axis. This may be the same as yValue or different. It is
986     * used by XYChart to animate the yValue from the old value to the new value. This is what you should plot
987     * in any custom XYChart implementations. Some XYChart chart implementations such as LineChart also use this
988     * to animate when data is added or removed.
989     */
990    protected final Y getCurrentDisplayedYValue(Data<X,Y> item) { return item.getCurrentY(); }
991    
992    /**
993     * Set the current displayed data value plotted on Y axis.
994     *
995     * @param item The XYChart.Data item from which the current Y axis data value is obtained.
996     * @see #getCurrentDisplayedYValue
997     */
998    protected final void setCurrentDisplayedYValue(Data<X,Y> item, Y value) { item.setCurrentY(value); }
999
1000    /** The current displayed data value property that is plotted on Y axis.
1001     *
1002     * @param item The XYChart.Data item from which the current Y axis data value property object is obtained.
1003     * @return The current displayed Y data value ObjectProperty.
1004     * @see #getCurrentDisplayedYValue
1005     */
1006    protected final ObjectProperty<Y> currentDisplayedYValueProperty(Data<X,Y> item) { return item.currentYProperty(); }
1007
1008    /**
1009     * The current displayed data extra value. This may be the same as extraValue or different. It is
1010     * used by XYChart to animate the extraValue from the old value to the new value. This is what you should plot
1011     * in any custom XYChart implementations.
1012     */
1013    protected final Object getCurrentDisplayedExtraValue(Data<X,Y> item) { return item.getCurrentExtraValue(); }
1014
1015    /**
1016     * Set the current displayed data extra value.
1017     *
1018     * @param item The XYChart.Data item from which the current extra value is obtained.
1019     * @see #getCurrentDisplayedExtraValue
1020     */
1021    protected final void setCurrentDisplayedExtraValue(Data<X,Y> item, Object value) { item.setCurrentExtraValue(value); }
1022
1023    /**
1024     * The current displayed extra value property.
1025     *
1026     * @param item The XYChart.Data item from which the current extra value property object is obtained.
1027     * @return ObjectProperty<Object> The current extra value ObjectProperty
1028     * @see #getCurrentDisplayedExtraValue
1029     */
1030    protected final ObjectProperty<Object> currentDisplayedExtraValueProperty(Data<X,Y> item) { return item.currentExtraValueProperty(); }
1031
1032    /**
1033     * XYChart maintains a list of all items currently displayed this includes all current data + any data items
1034     * recently deleted that are in the process of being faded out. This creates and returns a iterator over
1035     * that list. This is what implementations of XYChart should use when plotting data.
1036     *
1037     * @param series The series to get displayed data for
1038     * @return iterator over currently displayed items from this series
1039     */
1040    protected final Iterator<Data<X,Y>> getDisplayedDataIterator(final Series<X,Y> series) {
1041        return new Iterator<Data<X, Y>>() {
1042            private boolean start = true;
1043            private Data<X,Y> current = series.begin;
1044            @Override public boolean hasNext() {
1045                if (start) {
1046                    return current != null;
1047                } else {
1048                    return current.next != null;
1049                }
1050            }
1051            @Override public Data<X, Y> next() {
1052                if (start) {
1053                    start = false;
1054                } else if (current!=null) {
1055                    current = current.next;
1056                }
1057                return current;
1058            }
1059            @Override public void remove() {
1060                throw new UnsupportedOperationException("We don't support removing items from the displayed data list.");
1061            }
1062        };
1063    }
1064
1065    /**
1066     * This should be called from dataItemRemoved() when you are finished with any animation for deleting the item from the
1067     * chart. It will remove the data item from showing up in the Iterator returned by getDisplayedDataIterator().
1068     *
1069     * @param series The series to remove
1070     * @param item   The item to remove from series's display list
1071     */
1072    protected final void removeDataItemFromDisplay(Series<X, Y> series, Data<X, Y> item) {
1073        series.removeDataItemRef(item);
1074    }
1075
1076    // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------
1077
1078    private static class StyleableProperties {
1079        private static final CssMetaData<XYChart<?,?>,Boolean> HORIZONTAL_GRID_LINE_VISIBLE =
1080            new CssMetaData<XYChart<?,?>,Boolean>("-fx-horizontal-grid-lines-visible",
1081                BooleanConverter.getInstance(), Boolean.TRUE) {
1082
1083            @Override
1084            public boolean isSettable(XYChart<?,?> node) {
1085                return node.horizontalGridLinesVisible == null ||
1086                        !node.horizontalGridLinesVisible.isBound();
1087            }
1088
1089            @Override
1090            public StyleableProperty<Boolean> getStyleableProperty(XYChart<?,?> node) {
1091                return (StyleableProperty<Boolean>)node.horizontalGridLinesVisibleProperty();
1092            }
1093        };
1094        
1095        private static final CssMetaData<XYChart<?,?>,Boolean> HORIZONTAL_ZERO_LINE_VISIBLE =
1096            new CssMetaData<XYChart<?,?>,Boolean>("-fx-horizontal-zero-line-visible",
1097                BooleanConverter.getInstance(), Boolean.TRUE) {
1098
1099            @Override
1100            public boolean isSettable(XYChart<?,?> node) {
1101                return node.horizontalZeroLineVisible == null ||
1102                        !node.horizontalZeroLineVisible.isBound();
1103            }
1104
1105            @Override
1106            public StyleableProperty<Boolean> getStyleableProperty(XYChart<?,?> node) {
1107                return (StyleableProperty<Boolean>)node.horizontalZeroLineVisibleProperty();
1108            }
1109        };
1110        
1111        private static final CssMetaData<XYChart<?,?>,Boolean> ALTERNATIVE_ROW_FILL_VISIBLE =
1112            new CssMetaData<XYChart<?,?>,Boolean>("-fx-alternative-row-fill-visible",
1113                BooleanConverter.getInstance(), Boolean.TRUE) {
1114
1115            @Override
1116            public boolean isSettable(XYChart<?,?> node) {
1117                return node.alternativeRowFillVisible == null ||
1118                        !node.alternativeRowFillVisible.isBound();
1119            }
1120
1121            @Override
1122            public StyleableProperty<Boolean> getStyleableProperty(XYChart<?,?> node) {
1123                return (StyleableProperty<Boolean>)node.alternativeRowFillVisibleProperty();
1124            }
1125        };
1126        
1127        private static final CssMetaData<XYChart<?,?>,Boolean> VERTICAL_GRID_LINE_VISIBLE =
1128            new CssMetaData<XYChart<?,?>,Boolean>("-fx-vertical-grid-lines-visible",
1129                BooleanConverter.getInstance(), Boolean.TRUE) {
1130
1131            @Override
1132            public boolean isSettable(XYChart<?,?> node) {
1133                return node.verticalGridLinesVisible == null ||
1134                        !node.verticalGridLinesVisible.isBound();
1135            }
1136
1137            @Override
1138            public StyleableProperty<Boolean> getStyleableProperty(XYChart<?,?> node) {
1139                return (StyleableProperty<Boolean>)node.verticalGridLinesVisibleProperty();
1140            }
1141        };
1142        
1143        private static final CssMetaData<XYChart<?,?>,Boolean> VERTICAL_ZERO_LINE_VISIBLE =
1144            new CssMetaData<XYChart<?,?>,Boolean>("-fx-vertical-zero-line-visible",
1145                BooleanConverter.getInstance(), Boolean.TRUE) {
1146
1147            @Override
1148            public boolean isSettable(XYChart<?,?> node) {
1149                return node.verticalZeroLineVisible == null ||
1150                        !node.verticalZeroLineVisible.isBound();
1151            }
1152
1153            @Override
1154            public StyleableProperty<Boolean> getStyleableProperty(XYChart<?,?> node) {
1155                return (StyleableProperty<Boolean>)node.verticalZeroLineVisibleProperty();
1156            }
1157        };
1158        
1159        private static final CssMetaData<XYChart<?,?>,Boolean> ALTERNATIVE_COLUMN_FILL_VISIBLE =
1160            new CssMetaData<XYChart<?,?>,Boolean>("-fx-alternative-column-fill-visible",
1161                BooleanConverter.getInstance(), Boolean.TRUE) {
1162
1163            @Override
1164            public boolean isSettable(XYChart<?,?> node) {
1165                return node.alternativeColumnFillVisible == null ||
1166                        !node.alternativeColumnFillVisible.isBound();
1167            }
1168
1169            @Override
1170            public StyleableProperty<Boolean> getStyleableProperty(XYChart<?,?> node) {
1171                return (StyleableProperty<Boolean>)node.alternativeColumnFillVisibleProperty();
1172            }
1173        };
1174
1175        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1176        static {
1177            final List<CssMetaData<? extends Styleable, ?>> styleables = 
1178                new ArrayList<CssMetaData<? extends Styleable, ?>>(Chart.getClassCssMetaData());
1179            styleables.add(HORIZONTAL_GRID_LINE_VISIBLE);
1180            styleables.add(HORIZONTAL_ZERO_LINE_VISIBLE);
1181            styleables.add(ALTERNATIVE_ROW_FILL_VISIBLE);
1182            styleables.add(VERTICAL_GRID_LINE_VISIBLE);
1183            styleables.add(VERTICAL_ZERO_LINE_VISIBLE);
1184            styleables.add(ALTERNATIVE_COLUMN_FILL_VISIBLE);
1185            STYLEABLES = Collections.unmodifiableList(styleables);
1186        }
1187    }
1188 
1189    /**
1190     * @return The CssMetaData associated with this class, which may include the
1191     * CssMetaData of its super classes.
1192     */
1193    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1194        return StyleableProperties.STYLEABLES;
1195    }
1196
1197    /**
1198     * {@inheritDoc}
1199     */
1200    @Override
1201    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
1202        return getClassCssMetaData();
1203    }
1204
1205    // -------------- INNER CLASSES ------------------------------------------------------------------------------------
1206
1207    /**
1208     * A single data item with data for 2 axis charts
1209     */
1210    public final static class Data<X,Y> {
1211        // -------------- PUBLIC PROPERTIES ----------------------------------------
1212
1213        private boolean setToRemove = false;
1214        /** The series this data belongs to */
1215        private Series<X,Y> series;
1216        void setSeries(Series<X,Y> series) {
1217            this.series = series;
1218        }
1219
1220        /** The generic data value to be plotted on the X axis */
1221        private ObjectProperty<X> xValue = new ObjectPropertyBase<X>() {
1222            @Override protected void invalidated() {
1223                // Note: calling get to make non-lazy, replace with change listener when available
1224                get();
1225                if (series!=null) {
1226                    XYChart<X,Y> chart = series.getChart();
1227                    if(chart!=null) chart.dataXValueChanged(Data.this);
1228                } else {
1229                    // data has not been added to series yet :
1230                    // so currentX and X should be the same
1231                    setCurrentX(get());
1232                }
1233            }
1234
1235            @Override
1236            public Object getBean() {
1237                return Data.this;
1238            }
1239
1240            @Override
1241            public String getName() {
1242                return "XValue";
1243            }
1244        };
1245        /**
1246         * Gets the generic data value to be plotted on the X axis.
1247         * @return the generic data value to be plotted on the X axis.
1248         */
1249        public final X getXValue() { return xValue.get(); }
1250        /**
1251         * Sets the generic data value to be plotted on the X axis.
1252         * @param value the generic data value to be plotted on the X axis.
1253         */
1254        public final void setXValue(X value) {
1255            xValue.set(value);
1256            // handle the case where this is a init because the default constructor was used
1257            if (currentX.get() == null) currentX.setValue(value);
1258        }
1259        /** 
1260         * The generic data value to be plotted on the X axis.
1261         * @return The XValue property         
1262         */
1263        public final ObjectProperty<X> XValueProperty() { return xValue; }
1264
1265        /** The generic data value to be plotted on the Y axis */
1266        private ObjectProperty<Y> yValue = new ObjectPropertyBase<Y>() {
1267            @Override protected void invalidated() {
1268                // Note: calling get to make non-lazy, replace with change listener when available
1269                get();
1270                if (series!=null) {
1271                    XYChart<X,Y> chart = series.getChart();
1272                    if(chart!=null) chart.dataYValueChanged(Data.this);
1273                } else {
1274                    // data has not been added to series yet :
1275                    // so currentY and Y should be the same
1276                    setCurrentY(get());
1277                }
1278            }
1279
1280            @Override
1281            public Object getBean() {
1282                return Data.this;
1283            }
1284
1285            @Override
1286            public String getName() {
1287                return "YValue";
1288            }
1289        };
1290        /**
1291         * Gets the generic data value to be plotted on the Y axis.
1292         * @return the generic data value to be plotted on the Y axis.
1293         */
1294        public final Y getYValue() { return yValue.get(); }
1295        /**
1296         * Sets the generic data value to be plotted on the Y axis.
1297         * @param value the generic data value to be plotted on the Y axis.
1298         */
1299        public final void setYValue(Y value) {
1300            yValue.set(value);
1301            // handle the case where this is a init because the default constructor was used
1302            if (currentY.get() == null) currentY.setValue(value);
1303        }
1304        /** 
1305         * The generic data value to be plotted on the Y axis.
1306         * @return the YValue property
1307         */
1308        public final ObjectProperty<Y> YValueProperty() { return yValue; }
1309
1310        /**
1311         * The generic data value to be plotted in any way the chart needs. For example used as the radius
1312         * for BubbleChart.
1313         */
1314        private ObjectProperty<Object> extraValue = new ObjectPropertyBase<Object>() {
1315            @Override protected void invalidated() {
1316                // Note: calling get to make non-lazy, replace with change listener when available
1317                get();
1318                if (series!=null) {
1319                    XYChart<X,Y> chart = series.getChart();
1320                    if(chart!=null) chart.dataExtraValueChanged(Data.this);
1321                }
1322            }
1323
1324            @Override
1325            public Object getBean() {
1326                return Data.this;
1327            }
1328
1329            @Override
1330            public String getName() {
1331                return "extraValue";
1332            }
1333        };
1334        public final Object getExtraValue() { return extraValue.get(); }
1335        public final void setExtraValue(Object value) { extraValue.set(value); }
1336        public final ObjectProperty<Object> extraValueProperty() { return extraValue; }
1337
1338        /**
1339         * The node to display for this data item. You can either create your own node and set it on the data item
1340         * before you add the item to the chart. Otherwise the chart will create a node for you that has the default
1341         * representation for the chart type. This node will be set as soon as the data is added to the chart. You can
1342         * then get it to add mouse listeners etc. Charts will do their best to position and size the node
1343         * appropriately, for example on a Line or Scatter chart this node will be positioned centered on the data
1344         * values position. For a bar chart this is positioned and resized as the bar for this data item.
1345         */
1346        private ObjectProperty<Node> node = new SimpleObjectProperty<Node>(this, "node");
1347        public final Node getNode() { return node.get(); }
1348        public final void setNode(Node value) { node.set(value); }
1349        public final ObjectProperty<Node> nodeProperty() { return node; }
1350
1351        /**
1352         * The current displayed data value plotted on the X axis. This may be the same as xValue or different. It is
1353         * used by XYChart to animate the xValue from the old value to the new value. This is what you should plot
1354         * in any custom XYChart implementations. Some XYChart chart implementations such as LineChart also use this
1355         * to animate when data is added or removed.
1356         */
1357        private ObjectProperty<X> currentX = new SimpleObjectProperty<X>(this, "currentX");
1358        final X getCurrentX() { return currentX.get(); }
1359        final void setCurrentX(X value) { currentX.set(value); }
1360        final ObjectProperty<X> currentXProperty() { return currentX; }
1361
1362        /**
1363         * The current displayed data value plotted on the Y axis. This may be the same as yValue or different. It is
1364         * used by XYChart to animate the yValue from the old value to the new value. This is what you should plot
1365         * in any custom XYChart implementations. Some XYChart chart implementations such as LineChart also use this
1366         * to animate when data is added or removed.
1367         */
1368        private ObjectProperty<Y> currentY = new SimpleObjectProperty<Y>(this, "currentY");
1369        final Y getCurrentY() { return currentY.get(); }
1370        final void setCurrentY(Y value) { currentY.set(value); }
1371        final ObjectProperty<Y> currentYProperty() { return currentY; }
1372
1373        /**
1374         * The current displayed data extra value. This may be the same as extraValue or different. It is
1375         * used by XYChart to animate the extraValue from the old value to the new value. This is what you should plot
1376         * in any custom XYChart implementations.
1377         */
1378        private ObjectProperty<Object> currentExtraValue = new SimpleObjectProperty<Object>(this, "currentExtraValue");
1379        final Object getCurrentExtraValue() { return currentExtraValue.getValue(); }
1380        final void setCurrentExtraValue(Object value) { currentExtraValue.setValue(value); }
1381        final ObjectProperty<Object> currentExtraValueProperty() { return currentExtraValue; }
1382
1383        /**
1384         * Next pointer for the next data item. We maintain a linkedlist of the
1385         * data items so even after the data is deleted from the list,
1386         * we have a reference to it
1387         */
1388         protected Data<X,Y> next = null;
1389
1390        // -------------- CONSTRUCTOR -------------------------------------------------
1391
1392        /**
1393         * Creates an empty XYChart.Data object.
1394         */
1395        public Data() {}
1396
1397        /**
1398         * Creates an instance of XYChart.Data object and initializes the X,Y
1399         * data values.
1400         * 
1401         * @param xValue The X axis data value
1402         * @param yValue The Y axis data value
1403         */
1404        public Data(X xValue, Y yValue) {
1405            setXValue(xValue);
1406            setYValue(yValue);
1407            setCurrentX(xValue);
1408            setCurrentY(yValue);
1409        }
1410
1411        /**
1412         * Creates an instance of XYChart.Data object and initializes the X,Y
1413         * data values and extraValue.
1414         *
1415         * @param xValue The X axis data value.
1416         * @param yValue The Y axis data value.
1417         * @param extraValue Chart extra value.
1418         */
1419        public Data(X xValue, Y yValue, Object extraValue) {
1420            setXValue(xValue);
1421            setYValue(yValue);
1422            setExtraValue(extraValue);
1423            setCurrentX(xValue);
1424            setCurrentY(yValue);
1425            setCurrentExtraValue(extraValue);
1426        }
1427
1428        // -------------- PUBLIC METHODS ----------------------------------------------
1429
1430        /**
1431         * Returns a string representation of this {@code Data} object.
1432         * @return a string representation of this {@code Data} object.
1433         */ 
1434        @Override public String toString() {
1435            return "Data["+getXValue()+","+getYValue()+","+getExtraValue()+"]";
1436        }
1437
1438    }
1439
1440    /**
1441     * A named series of data items
1442     */
1443    public static final class Series<X,Y> {
1444
1445        // -------------- PRIVATE PROPERTIES ----------------------------------------
1446
1447        /** the style class for default color for this series */
1448        String defaultColorStyleClass;
1449
1450        Data<X,Y> begin = null; // start pointer of a data linked list.
1451        /*
1452         * Next pointer for the next series. We maintain a linkedlist of the
1453         * serieses  so even after the series is deleted from the list,
1454         * we have a reference to it - needed by BarChart e.g.
1455         */
1456        Series<X,Y> next = null;
1457
1458        private final ListChangeListener<Data<X,Y>> dataChangeListener = new ListChangeListener<Data<X, Y>>() {
1459            @Override public void onChanged(Change<? extends Data<X, Y>> c) {
1460                while (c.next()) {
1461                    // RT-25187 Probably a sort happened, just reorder the pointers and return.
1462                    if (c.wasPermutated()) {
1463                        Series<X,Y> series = Series.this;
1464                        if (series == null || series.getData() == null) return;
1465                        Data<X,Y> ptr = begin;
1466                        for(int i = 0; i < series.getData().size(); i++) {
1467                            Data<X,Y> item = series.getData().get(i);
1468                            if (i == 0) {
1469                                begin = item;
1470                                ptr = begin;
1471                                begin.next = null;
1472                            } else {
1473                                ptr.next = item;
1474                                item.next = null;
1475                                ptr = item;
1476                            }
1477                        }
1478                        return;
1479                    }
1480                    // update data items reference to series
1481                    for (Data<X,Y> item : c.getRemoved()) {
1482                        item.setToRemove = true;
1483                    }
1484                    if (c.getAddedSize() > 0) {
1485                        for (Data<X,Y> itemPtr = begin; itemPtr != null; itemPtr = itemPtr.next) {
1486                            if (itemPtr.setToRemove) {
1487                                removeDataItemRef(itemPtr);
1488                            }
1489                        }
1490                    }
1491                    for(int i=c.getFrom(); i<c.getTo(); i++) {
1492                        getData().get(i).setSeries(Series.this);
1493                        // update linkedList Pointers for data in this series
1494                        if (begin == null) {
1495                            begin = getData().get(i);
1496                            begin.next = null;
1497                        } else {
1498                            if (i == 0) {
1499                                getData().get(0).next = begin;
1500                                begin = getData().get(0);
1501                            } else {
1502                                Data<X,Y> ptr = begin;
1503                                for (int j = 0; j < i -1 ; j++) {
1504                                    ptr = ptr.next;
1505                                }
1506                                getData().get(i).next = ptr.next;
1507                                ptr.next = getData().get(i);
1508                            }
1509                        }
1510                    }
1511                    // check cycle in the data list
1512                    // if cycle exists, and the data is not set to be removed, 
1513                    // eliminate loop and throw exception stating operation not permitted.
1514                    // RT-28880 : infinite loop when same data is added to two charts.
1515                    Data<X,Y> cycle = checkCycleInList();
1516                    if ( cycle != null) {
1517                        if (!cycle.setToRemove) {
1518                            eliminateLoop(cycle);
1519                            throw new IllegalArgumentException(
1520                                    "Duplicate data added or same data added to more than one chart ");
1521                        }
1522                    }
1523                    // inform chart
1524                    XYChart<X,Y> chart = getChart();
1525                    if(chart!=null) chart.dataItemsChanged(Series.this,
1526                            (List<Data<X,Y>>)c.getRemoved(), c.getFrom(), c.getTo(), c.wasPermutated());
1527                }
1528            }
1529        };
1530        
1531        private Data<X,Y> checkCycleInList() {
1532            Data<X,Y> slow = null;
1533            Data<X,Y> fast = null;
1534            slow = fast = begin;
1535            while (slow != null && fast != null) {
1536                fast = fast.next;
1537                if (fast == slow) return slow;
1538                if (fast == null) return null;
1539                fast = fast.next;
1540                if (fast == slow) return fast;
1541                slow = slow.next;
1542            }
1543            return null;
1544        }
1545        
1546        private void eliminateLoop(Data<X,Y> cycle) {
1547            Data<X,Y> slow = cycle;
1548            // Identify the data that is the start of the loop
1549            Data<X,Y> fast = begin;
1550            //until both the refs are one short of the common element which is the start of the loop
1551            while(fast.next != slow.next) {
1552                fast = fast.next;
1553                slow = slow.next;
1554            }
1555            Data<X,Y>start = fast.next;
1556            //Eliminate loop by setting next pointer of last element to null
1557            fast = start;
1558            while(fast.next != start) {
1559                fast = fast.next;
1560            }
1561            fast.next = null; //break the loop
1562        }
1563
1564        // -------------- PUBLIC PROPERTIES ----------------------------------------
1565
1566        /** Reference to the chart this series belongs to */
1567        private final ReadOnlyObjectWrapper<XYChart<X,Y>> chart = new ReadOnlyObjectWrapper<XYChart<X,Y>>(this, "chart");
1568        public final XYChart<X,Y> getChart() { return chart.get(); }
1569        private void setChart(XYChart<X,Y> value) { chart.set(value); }
1570        public final ReadOnlyObjectProperty<XYChart<X,Y>> chartProperty() { return chart.getReadOnlyProperty(); }
1571
1572        /** The user displayable name for this series */
1573        private final StringProperty name = new StringPropertyBase() {
1574            @Override protected void invalidated() {
1575                get(); // make non-lazy
1576                if(getChart() != null) getChart().seriesNameChanged();
1577            }
1578
1579            @Override
1580            public Object getBean() {
1581                return Series.this;
1582            }
1583
1584            @Override
1585            public String getName() {
1586                return "name";
1587            }
1588        };
1589        public final String getName() { return name.get(); }
1590        public final void setName(String value) { name.set(value); }
1591        public final StringProperty nameProperty() { return name; }
1592
1593        /**
1594         * The node to display for this series. This is created by the chart if it uses nodes to represent the whole
1595         * series. For example line chart uses this for the line but scatter chart does not use it. This node will be
1596         * set as soon as the series is added to the chart. You can then get it to add mouse listeners etc.
1597         */
1598        private ObjectProperty<Node> node = new SimpleObjectProperty<Node>(this, "node");
1599        public final Node getNode() { return node.get(); }
1600        public final void setNode(Node value) { node.set(value); }
1601        public final ObjectProperty<Node> nodeProperty() { return node; }
1602
1603        /** ObservableList of data items that make up this series */
1604        private final ObjectProperty<ObservableList<Data<X,Y>>> data = new ObjectPropertyBase<ObservableList<Data<X,Y>>>() {
1605            private ObservableList<Data<X,Y>> old;
1606            @Override protected void invalidated() {
1607                final ObservableList<Data<X,Y>> current = getValue();
1608                // add remove listeners
1609                if(old != null) old.removeListener(dataChangeListener);
1610                if(current != null) current.addListener(dataChangeListener);
1611                // fire data change event if series are added or removed
1612                if(old != null || current != null) {
1613                    final List<Data<X,Y>> removed = (old != null) ? old : Collections.<Data<X,Y>>emptyList();
1614                    final int toIndex = (current != null) ? current.size() : 0;
1615                    // let data listener know all old data have been removed and new data that has been added
1616                    if (toIndex > 0 || !removed.isEmpty()) {
1617                        dataChangeListener.onChanged(new NonIterableChange<Data<X,Y>>(0, toIndex, current){
1618                            @Override public List<Data<X,Y>> getRemoved() { return removed; }
1619
1620                            @Override protected int[] getPermutation() {
1621                                return new int[0];
1622                            }
1623                        });
1624                    }
1625                } else if (old != null && old.size() > 0) {
1626                    // let series listener know all old series have been removed
1627                    dataChangeListener.onChanged(new NonIterableChange<Data<X,Y>>(0, 0, current){
1628                        @Override public List<Data<X,Y>> getRemoved() { return old; }
1629                        @Override protected int[] getPermutation() {
1630                            return new int[0];
1631                        }
1632                    });
1633                }
1634                old = current;
1635            }
1636
1637            @Override
1638            public Object getBean() {
1639                return Series.this;
1640            }
1641
1642            @Override
1643            public String getName() {
1644                return "data";
1645            }
1646        };
1647        public final ObservableList<Data<X,Y>> getData() { return data.getValue(); }
1648        public final void setData(ObservableList<Data<X,Y>> value) { data.setValue(value); }
1649        public final ObjectProperty<ObservableList<Data<X,Y>>> dataProperty() { return data; }
1650
1651        // -------------- CONSTRUCTORS ----------------------------------------------
1652
1653        /**
1654         * Construct a empty series
1655         */
1656        public Series() {
1657            this(FXCollections.<Data<X,Y>>observableArrayList());
1658        }
1659
1660        /**
1661         * Constructs a Series and populates it with the given {@link ObservableList} data.
1662         *
1663         * @param data ObservableList of XYChart.Data
1664         */
1665        public Series(ObservableList<Data<X,Y>> data) {
1666            setData(data);
1667            for(Data<X,Y> item:data) item.setSeries(this);
1668        }
1669
1670        /**
1671         * Constructs a named Series and populates it with the given {@link ObservableList} data.
1672         *
1673         * @param name a name for the series
1674         * @param data ObservableList of XYChart.Data
1675         */
1676        public Series(String name, ObservableList<Data<X,Y>> data) {
1677            this(data);
1678            setName(name);
1679        }
1680
1681        // -------------- PUBLIC METHODS ----------------------------------------------
1682
1683        /**
1684         * Returns a string representation of this {@code Series} object.
1685         * @return a string representation of this {@code Series} object.
1686         */ 
1687        @Override public String toString() {
1688            return "Series["+getName()+"]";
1689        }
1690
1691        // -------------- PRIVATE/PROTECTED METHODS -----------------------------------
1692
1693        /*
1694         * The following methods are for manipulating the pointers in the linked list
1695         * when data is deleted. 
1696         */
1697        private void removeDataItemRef(Data<X,Y> item) {
1698            if (begin == item) {
1699                begin = item.next;
1700            } else {
1701                Data<X,Y> ptr = begin;
1702                while(ptr != null && ptr.next != item) {
1703                    ptr = ptr.next;
1704                }
1705                if(ptr != null) ptr.next = item.next;
1706            }
1707        }
1708
1709        int getItemIndex(Data<X,Y> item) {
1710            int itemIndex = 0;
1711            for (Data<X,Y> d = begin; d != null; d = d.next) {
1712                if (d == item) break;
1713                itemIndex++;
1714            }
1715            return itemIndex;
1716        }
1717
1718        int getDataSize() {
1719            int count = 0;
1720            for (Data<X,Y> d = begin; d != null; d = d.next) {
1721                count++;
1722            }
1723            return count;
1724        }
1725    }
1726    
1727}