155 int nextClearBit = colorBits.nextClearBit(0);
156 colorBits.set(nextClearBit, true);
157 s.defaultColorStyleClass = DEFAULT_COLOR+(nextClearBit%8);
158 seriesColorMap.put(s, nextClearBit%8);
159 // inform sub-classes of series added
160 seriesAdded(s, i);
161 }
162 if (c.getFrom() < c.getTo()) updateLegend();
163 seriesChanged(c);
164
165 }
166 // update axis ranges
167 invalidateRange();
168 // lay everything out
169 requestChartLayout();
170 };
171
172 // -------------- PUBLIC PROPERTIES --------------------------------------------------------------------------------
173
174 private final Axis<X> xAxis;
175 /** Get the X axis, by default it is along the bottom of the plot */
176 public Axis<X> getXAxis() { return xAxis; }
177
178 private final Axis<Y> yAxis;
179 /** Get the Y axis, by default it is along the left of the plot */
180 public Axis<Y> getYAxis() { return yAxis; }
181
182 /** XYCharts data */
183 private ObjectProperty<ObservableList<Series<X,Y>>> data = new ObjectPropertyBase<ObservableList<Series<X,Y>>>() {
184 private ObservableList<Series<X,Y>> old;
185 @Override protected void invalidated() {
186 final ObservableList<Series<X,Y>> current = getValue();
187 if (current == old) return;
188 int saveAnimationState = -1;
189 // add remove listeners
190 if(old != null) {
191 old.removeListener(seriesChanged);
192 // Set animated to false so we don't animate both remove and add
193 // at the same time. RT-14163
194 // RT-21295 - disable animated only when current is also not null.
195 if (current != null && old.size() > 0) {
196 saveAnimationState = (old.get(0).getChart().getAnimated()) ? 1 : 2;
197 old.get(0).getChart().setAnimated(false);
198 }
199 }
595 /**
596 * A series has been added to the charts data model. This is where implementations of XYChart can create/add new
597 * nodes to getPlotChildren to represent this series. Also you have to handle adding any data items that are
598 * already in the series. You may simply call dataItemAdded() for each one or provide some different animation for
599 * a whole series being added.
600 *
601 * @param series The series that has been added
602 * @param seriesIndex The index of the new series
603 */
604 protected abstract void seriesAdded(Series<X, Y> series, int seriesIndex);
605
606 /**
607 * A series has been removed from the data model but it is still visible on the chart. Its still visible
608 * so that you can handle animation for removing it in this method. After you are done animating the data item you
609 * must call removeSeriesFromDisplay() to remove the series from the display list.
610 *
611 * @param series The series that has been removed
612 */
613 protected abstract void seriesRemoved(Series<X,Y> series);
614
615 /** Called when each atomic change is made to the list of series for this chart */
616 protected void seriesChanged(Change<? extends Series> c) {}
617
618 /**
619 * This is called when a data change has happened that may cause the range to be invalid.
620 */
621 private void invalidateRange() {
622 rangeValid = false;
623 }
624
625 /**
626 * This is called when the range has been invalidated and we need to update it. If the axis are auto
627 * ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the
628 * axis passing it that data.
629 */
630 protected void updateAxisRange() {
631 final Axis<X> xa = getXAxis();
632 final Axis<Y> ya = getYAxis();
633 List<X> xData = null;
634 List<Y> yData = null;
635 if(xa.isAutoRanging()) xData = new ArrayList<X>();
636 if(ya.isAutoRanging()) yData = new ArrayList<Y>();
637 if(xData != null || yData != null) {
638 for(Series<X,Y> series : getData()) {
639 for(Data<X,Y> data: series.getData()) {
640 if(xData != null) xData.add(data.getXValue());
641 if(yData != null) yData.add(data.getYValue());
642 }
643 }
644 if(xData != null) xa.invalidateRange(xData);
645 if(yData != null) ya.invalidateRange(yData);
646 }
647 }
648
649 /**
650 * Called to update and layout the plot children. This should include all work to updates nodes representing
651 * 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
652 * can be got by getting the width of the x axis and its height from the height of the y axis.
653 */
654 protected abstract void layoutPlotChildren();
655
656 /** @inheritDoc */
657 @Override protected final void layoutChartChildren(double top, double left, double width, double height) {
658 if(getData() == null) return;
659 if (!rangeValid) {
660 rangeValid = true;
661 if(getData() != null) updateAxisRange();
662 }
663 // snap top and left to pixels
664 top = snapPositionY(top);
665 left = snapPositionX(left);
666 // get starting stuff
667 final Axis<X> xa = getXAxis();
668 final ObservableList<Axis.TickMark<X>> xaTickMarks = xa.getTickMarks();
669 final Axis<Y> ya = getYAxis();
670 final ObservableList<Axis.TickMark<Y>> yaTickMarks = ya.getTickMarks();
671 // check we have 2 axises and know their sides
672 if (xa == null || ya == null) return;
673 // try and work out width and height of axises
674 double xAxisWidth = 0;
675 double xAxisHeight = 30; // guess x axis height to start with
676 double yAxisWidth = 0;
946 KeyValue[] startValues = new KeyValue[nodes.size()];
947 KeyValue[] endValues = new KeyValue[nodes.size()];
948 for (int j = 0; j < nodes.size(); j++) {
949 startValues[j] = new KeyValue(nodes.get(j).opacityProperty(), 1);
950 endValues[j] = new KeyValue(nodes.get(j).opacityProperty(), 0);
951 }
952 return new KeyFrame[] {
953 new KeyFrame(Duration.ZERO, startValues),
954 new KeyFrame(Duration.millis(fadeOutTime), actionEvent -> {
955 getPlotChildren().removeAll(nodes);
956 removeSeriesFromDisplay(series);
957 }, endValues)
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 Collections.unmodifiableList(series.displayedData).iterator();
1042 }
1043
1044 /**
1045 * This should be called from dataItemRemoved() when you are finished with any animation for deleting the item from the
1046 * chart. It will remove the data item from showing up in the Iterator returned by getDisplayedDataIterator().
1047 *
|
155 int nextClearBit = colorBits.nextClearBit(0);
156 colorBits.set(nextClearBit, true);
157 s.defaultColorStyleClass = DEFAULT_COLOR+(nextClearBit%8);
158 seriesColorMap.put(s, nextClearBit%8);
159 // inform sub-classes of series added
160 seriesAdded(s, i);
161 }
162 if (c.getFrom() < c.getTo()) updateLegend();
163 seriesChanged(c);
164
165 }
166 // update axis ranges
167 invalidateRange();
168 // lay everything out
169 requestChartLayout();
170 };
171
172 // -------------- PUBLIC PROPERTIES --------------------------------------------------------------------------------
173
174 private final Axis<X> xAxis;
175 /**
176 * Get the X axis, by default it is along the bottom of the plot
177 * @return the X axis of the chart
178 */
179 public Axis<X> getXAxis() { return xAxis; }
180
181 private final Axis<Y> yAxis;
182 /**
183 * Get the Y axis, by default it is along the left of the plot
184 * @return the Y axis of this chart
185 */
186 public Axis<Y> getYAxis() { return yAxis; }
187
188 /** XYCharts data */
189 private ObjectProperty<ObservableList<Series<X,Y>>> data = new ObjectPropertyBase<ObservableList<Series<X,Y>>>() {
190 private ObservableList<Series<X,Y>> old;
191 @Override protected void invalidated() {
192 final ObservableList<Series<X,Y>> current = getValue();
193 if (current == old) return;
194 int saveAnimationState = -1;
195 // add remove listeners
196 if(old != null) {
197 old.removeListener(seriesChanged);
198 // Set animated to false so we don't animate both remove and add
199 // at the same time. RT-14163
200 // RT-21295 - disable animated only when current is also not null.
201 if (current != null && old.size() > 0) {
202 saveAnimationState = (old.get(0).getChart().getAnimated()) ? 1 : 2;
203 old.get(0).getChart().setAnimated(false);
204 }
205 }
601 /**
602 * A series has been added to the charts data model. This is where implementations of XYChart can create/add new
603 * nodes to getPlotChildren to represent this series. Also you have to handle adding any data items that are
604 * already in the series. You may simply call dataItemAdded() for each one or provide some different animation for
605 * a whole series being added.
606 *
607 * @param series The series that has been added
608 * @param seriesIndex The index of the new series
609 */
610 protected abstract void seriesAdded(Series<X, Y> series, int seriesIndex);
611
612 /**
613 * A series has been removed from the data model but it is still visible on the chart. Its still visible
614 * so that you can handle animation for removing it in this method. After you are done animating the data item you
615 * must call removeSeriesFromDisplay() to remove the series from the display list.
616 *
617 * @param series The series that has been removed
618 */
619 protected abstract void seriesRemoved(Series<X,Y> series);
620
621 /**
622 * Called when each atomic change is made to the list of series for this chart
623 * @param c The series that has been changed
624 */
625 protected void seriesChanged(Change<? extends Series> c) {}
626
627 /**
628 * This is called when a data change has happened that may cause the range to be invalid.
629 */
630 private void invalidateRange() {
631 rangeValid = false;
632 }
633
634 /**
635 * This is called when the range has been invalidated and we need to update it. If the axis are auto
636 * ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the
637 * axis passing it that data.
638 */
639 protected void updateAxisRange() {
640 final Axis<X> xa = getXAxis();
641 final Axis<Y> ya = getYAxis();
642 List<X> xData = null;
643 List<Y> yData = null;
644 if(xa.isAutoRanging()) xData = new ArrayList<X>();
645 if(ya.isAutoRanging()) yData = new ArrayList<Y>();
646 if(xData != null || yData != null) {
647 for(Series<X,Y> series : getData()) {
648 for(Data<X,Y> data: series.getData()) {
649 if(xData != null) xData.add(data.getXValue());
650 if(yData != null) yData.add(data.getYValue());
651 }
652 }
653 if(xData != null) xa.invalidateRange(xData);
654 if(yData != null) ya.invalidateRange(yData);
655 }
656 }
657
658 /**
659 * Called to update and layout the plot children. This should include all work to updates nodes representing
660 * 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
661 * can be got by getting the width of the x axis and its height from the height of the y axis.
662 */
663 protected abstract void layoutPlotChildren();
664
665 /** {@inheritDoc} */
666 @Override protected final void layoutChartChildren(double top, double left, double width, double height) {
667 if(getData() == null) return;
668 if (!rangeValid) {
669 rangeValid = true;
670 if(getData() != null) updateAxisRange();
671 }
672 // snap top and left to pixels
673 top = snapPositionY(top);
674 left = snapPositionX(left);
675 // get starting stuff
676 final Axis<X> xa = getXAxis();
677 final ObservableList<Axis.TickMark<X>> xaTickMarks = xa.getTickMarks();
678 final Axis<Y> ya = getYAxis();
679 final ObservableList<Axis.TickMark<Y>> yaTickMarks = ya.getTickMarks();
680 // check we have 2 axises and know their sides
681 if (xa == null || ya == null) return;
682 // try and work out width and height of axises
683 double xAxisWidth = 0;
684 double xAxisHeight = 30; // guess x axis height to start with
685 double yAxisWidth = 0;
955 KeyValue[] startValues = new KeyValue[nodes.size()];
956 KeyValue[] endValues = new KeyValue[nodes.size()];
957 for (int j = 0; j < nodes.size(); j++) {
958 startValues[j] = new KeyValue(nodes.get(j).opacityProperty(), 1);
959 endValues[j] = new KeyValue(nodes.get(j).opacityProperty(), 0);
960 }
961 return new KeyFrame[] {
962 new KeyFrame(Duration.ZERO, startValues),
963 new KeyFrame(Duration.millis(fadeOutTime), actionEvent -> {
964 getPlotChildren().removeAll(nodes);
965 removeSeriesFromDisplay(series);
966 }, endValues)
967 };
968 }
969
970 /**
971 * The current displayed data value plotted on the X axis. This may be the same as xValue or different. It is
972 * used by XYChart to animate the xValue from the old value to the new value. This is what you should plot
973 * in any custom XYChart implementations. Some XYChart chart implementations such as LineChart also use this
974 * to animate when data is added or removed.
975 * @param item The XYChart.Data item from which the current X axis data value is obtained
976 * @return The current displayed X data value
977 */
978 protected final X getCurrentDisplayedXValue(Data<X,Y> item) { return item.getCurrentX(); }
979
980 /** Set the current displayed data value plotted on X axis.
981 *
982 * @param item The XYChart.Data item from which the current X axis data value is obtained.
983 * @param value The X axis data value
984 * @see #getCurrentDisplayedXValue
985 */
986 protected final void setCurrentDisplayedXValue(Data<X,Y> item, X value) { item.setCurrentX(value); }
987
988 /** The current displayed data value property that is plotted on X axis.
989 *
990 * @param item The XYChart.Data item from which the current X axis data value property object is obtained.
991 * @return The current displayed X data value ObjectProperty.
992 * @see #getCurrentDisplayedXValue
993 */
994 protected final ObjectProperty<X> currentDisplayedXValueProperty(Data<X,Y> item) { return item.currentXProperty(); }
995
996 /**
997 * The current displayed data value plotted on the Y axis. This may be the same as yValue or different. It is
998 * used by XYChart to animate the yValue from the old value to the new value. This is what you should plot
999 * in any custom XYChart implementations. Some XYChart chart implementations such as LineChart also use this
1000 * to animate when data is added or removed.
1001 * @param item The XYChart.Data item from which the current Y axis data value is obtained
1002 * @return The current displayed Y data value
1003 */
1004 protected final Y getCurrentDisplayedYValue(Data<X,Y> item) { return item.getCurrentY(); }
1005
1006 /**
1007 * Set the current displayed data value plotted on Y axis.
1008 *
1009 * @param item The XYChart.Data item from which the current Y axis data value is obtained.
1010 * @param value The Y axis data value
1011 * @see #getCurrentDisplayedYValue
1012 */
1013 protected final void setCurrentDisplayedYValue(Data<X,Y> item, Y value) { item.setCurrentY(value); }
1014
1015 /** The current displayed data value property that is plotted on Y axis.
1016 *
1017 * @param item The XYChart.Data item from which the current Y axis data value property object is obtained.
1018 * @return The current displayed Y data value ObjectProperty.
1019 * @see #getCurrentDisplayedYValue
1020 */
1021 protected final ObjectProperty<Y> currentDisplayedYValueProperty(Data<X,Y> item) { return item.currentYProperty(); }
1022
1023 /**
1024 * The current displayed data extra value. This may be the same as extraValue or different. It is
1025 * used by XYChart to animate the extraValue from the old value to the new value. This is what you should plot
1026 * in any custom XYChart implementations.
1027 * @param item The XYChart.Data item from which the current extra value is obtained
1028 * @return The current extra value
1029 */
1030 protected final Object getCurrentDisplayedExtraValue(Data<X,Y> item) { return item.getCurrentExtraValue(); }
1031
1032 /**
1033 * Set the current displayed data extra value.
1034 *
1035 * @param item The XYChart.Data item from which the current extra value is obtained.
1036 * @param value The extra value
1037 * @see #getCurrentDisplayedExtraValue
1038 */
1039 protected final void setCurrentDisplayedExtraValue(Data<X,Y> item, Object value) { item.setCurrentExtraValue(value); }
1040
1041 /**
1042 * The current displayed extra value property.
1043 *
1044 * @param item The XYChart.Data item from which the current extra value property object is obtained.
1045 * @return {@literal ObjectProperty<Object> The current extra value ObjectProperty}
1046 * @see #getCurrentDisplayedExtraValue
1047 */
1048 protected final ObjectProperty<Object> currentDisplayedExtraValueProperty(Data<X,Y> item) { return item.currentExtraValueProperty(); }
1049
1050 /**
1051 * XYChart maintains a list of all items currently displayed this includes all current data + any data items
1052 * recently deleted that are in the process of being faded out. This creates and returns a iterator over
1053 * that list. This is what implementations of XYChart should use when plotting data.
1054 *
1055 * @param series The series to get displayed data for
1056 * @return iterator over currently displayed items from this series
1057 */
1058 protected final Iterator<Data<X,Y>> getDisplayedDataIterator(final Series<X,Y> series) {
1059 return Collections.unmodifiableList(series.displayedData).iterator();
1060 }
1061
1062 /**
1063 * This should be called from dataItemRemoved() when you are finished with any animation for deleting the item from the
1064 * chart. It will remove the data item from showing up in the Iterator returned by getDisplayedDataIterator().
1065 *
|