javafx.scene.chart.XYChart Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openjfx-78-backport Show documentation
Show all versions of openjfx-78-backport Show documentation
This is a backport of OpenJFX 8 to run on Java 7.
The newest version!
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.chart;
import com.sun.javafx.collections.NonIterableChange;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.geometry.Side;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import java.util.BitSet;
import javafx.css.StyleableBooleanProperty;
import javafx.css.CssMetaData;
import com.sun.javafx.css.converters.BooleanConverter;
import java.util.HashMap;
import java.util.Map;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
/**
* Chart base class for all 2 axis charts. It is responsible for drawing the two
* axes and the plot content. It contains a list of all content in the plot and
* implementations of XYChart can add nodes to this list that need to be rendered.
*
* It is possible to install Tooltips on data items / symbols.
* For example the following code snippet installs Tooltip on the 1st data item.
*
*
* XYChart.Data item = ( XYChart.Data)series.getData().get(0);
* Tooltip.install(item.getNode(), new Tooltip("Symbol-0"));
*
*
* @since JavaFX 2.0
*/
public abstract class XYChart extends Chart {
// -------------- PRIVATE FIELDS -----------------------------------------------------------------------------------
// to indicate which colors are being used for the series
BitSet colorBits = new BitSet(8);
static String DEFAULT_COLOR = "default-color";
Map seriesColorMap = new HashMap();
private boolean rangeValid = false;
private final Line verticalZeroLine = new Line();
private final Line horizontalZeroLine = new Line();
private final Path verticalGridLines = new Path();
private final Path horizontalGridLines = new Path();
private final Path horizontalRowFill = new Path();
private final Path verticalRowFill = new Path();
private final Region plotBackground = new Region();
private final Group plotArea = new Group(){
@Override public void requestLayout() {} // suppress layout requests
};
private final Group plotContent = new Group();
private final Rectangle plotAreaClip = new Rectangle();
/* start pointer of a series linked list. */
Series begin = null;
/** This is called when a series is added or removed from the chart */
private final ListChangeListener> seriesChanged = new ListChangeListener>() {
@Override public void onChanged(Change extends Series> c) {
while (c.next()) {
if (c.getRemoved().size() > 0) updateLegend();
for (Series series : c.getRemoved()) {
series.setChart(null);
seriesRemoved(series);
// seriesDefaultColorIndex --;
}
for(int i=c.getFrom(); i series = c.getList().get(i);
// add new listener to data
series.setChart(XYChart.this);
// update linkedList Pointers for series
if (XYChart.this.begin == null) {
XYChart.this.begin = getData().get(i);
XYChart.this.begin.next = null;
} else {
if (i == 0) {
getData().get(0).next = XYChart.this.begin;
begin = getData().get(0);
} else {
Series ptr = begin;
for (int j = 0; j < i -1 && ptr!=null ; j++) {
ptr = ptr.next;
}
if (ptr != null) {
getData().get(i).next = ptr.next;
ptr.next = getData().get(i);
}
}
}
// update default color style class
int nextClearBit = colorBits.nextClearBit(0);
colorBits.set(nextClearBit, true);
series.defaultColorStyleClass = DEFAULT_COLOR+(nextClearBit%8);
seriesColorMap.put(series, nextClearBit%8);
// inform sub-classes of series added
seriesAdded(series, i);
}
if (c.getFrom() < c.getTo()) updateLegend();
seriesChanged(c);
// RT-12069, linked list pointers should update when list is permutated.
if (c.wasPermutated() && getData().size() > 0) {
XYChart.this.begin = getData().get(0);
Series ptr = begin;
for(int k = 1; k < getData().size() && ptr != null; k++) {
ptr.next = getData().get(k);
ptr = ptr.next;
}
ptr.next = null;
}
}
// update axis ranges
invalidateRange();
// lay everything out
requestChartLayout();
}
};
// -------------- PUBLIC PROPERTIES --------------------------------------------------------------------------------
private final Axis xAxis;
/** Get the X axis, by default it is along the bottom of the plot */
public Axis getXAxis() { return xAxis; }
private final Axis yAxis;
/** Get the Y axis, by default it is along the left of the plot */
public Axis getYAxis() { return yAxis; }
/** XYCharts data */
private ObjectProperty>> data = new ObjectPropertyBase>>() {
private ObservableList> old;
@Override protected void invalidated() {
final ObservableList> current = getValue();
int saveAnimationState = -1;
// add remove listeners
if(old != null) {
old.removeListener(seriesChanged);
// Set animated to false so we don't animate both remove and add
// at the same time. RT-14163
// RT-21295 - disable animated only when current is also not null.
if (current != null && old.size() > 0) {
saveAnimationState = (old.get(0).getChart().getAnimated()) ? 1 : 2;
old.get(0).getChart().setAnimated(false);
}
}
if(current != null) current.addListener(seriesChanged);
// fire series change event if series are added or removed
if(old != null || current != null) {
final List> removed = (old != null) ? old : Collections.>emptyList();
final int toIndex = (current != null) ? current.size() : 0;
// let series listener know all old series have been removed and new that have been added
if (toIndex > 0 || !removed.isEmpty()) {
seriesChanged.onChanged(new NonIterableChange>(0, toIndex, current){
@Override public List> getRemoved() { return removed; }
@Override protected int[] getPermutation() {
return new int[0];
}
});
}
} else if (old != null && old.size() > 0) {
// let series listener know all old series have been removed
seriesChanged.onChanged(new NonIterableChange>(0, 0, current){
@Override public List> getRemoved() { return old; }
@Override protected int[] getPermutation() {
return new int[0];
}
});
}
// restore animated on chart.
if (current != null && current.size() > 0 && saveAnimationState != -1) {
current.get(0).getChart().setAnimated((saveAnimationState == 1) ? true : false);
}
old = current;
}
public Object getBean() {
return XYChart.this;
}
public String getName() {
return "data";
}
};
public final ObservableList> getData() { return data.getValue(); }
public final void setData(ObservableList> value) { data.setValue(value); }
public final ObjectProperty>> dataProperty() { return data; }
/** True if vertical grid lines should be drawn */
private BooleanProperty verticalGridLinesVisible = new StyleableBooleanProperty(true) {
@Override protected void invalidated() {
requestChartLayout();
}
@Override
public Object getBean() {
return XYChart.this;
}
@Override
public String getName() {
return "verticalGridLinesVisible";
}
@Override
public CssMetaData,Boolean> getCssMetaData() {
return StyleableProperties.VERTICAL_GRID_LINE_VISIBLE;
}
};
/**
* Indicates whether vertical grid lines are visible or not.
*
* @return true if verticalGridLines are visible else false.
* @see #verticalGridLinesVisible
*/
public final boolean getVerticalGridLinesVisible() { return verticalGridLinesVisible.get(); }
public final void setVerticalGridLinesVisible(boolean value) { verticalGridLinesVisible.set(value); }
public final BooleanProperty verticalGridLinesVisibleProperty() { return verticalGridLinesVisible; }
/** True if horizontal grid lines should be drawn */
private BooleanProperty horizontalGridLinesVisible = new StyleableBooleanProperty(true) {
@Override protected void invalidated() {
requestChartLayout();
}
@Override
public Object getBean() {
return XYChart.this;
}
@Override
public String getName() {
return "horizontalGridLinesVisible";
}
@Override
public CssMetaData,Boolean> getCssMetaData() {
return StyleableProperties.HORIZONTAL_GRID_LINE_VISIBLE;
}
};
public final boolean isHorizontalGridLinesVisible() { return horizontalGridLinesVisible.get(); }
public final void setHorizontalGridLinesVisible(boolean value) { horizontalGridLinesVisible.set(value); }
public final BooleanProperty horizontalGridLinesVisibleProperty() { return horizontalGridLinesVisible; }
/** If true then alternative vertical columns will have fills */
private BooleanProperty alternativeColumnFillVisible = new StyleableBooleanProperty(false) {
@Override protected void invalidated() {
requestChartLayout();
}
@Override
public Object getBean() {
return XYChart.this;
}
@Override
public String getName() {
return "alternativeColumnFillVisible";
}
@Override
public CssMetaData,Boolean> getCssMetaData() {
return StyleableProperties.ALTERNATIVE_COLUMN_FILL_VISIBLE;
}
};
public final boolean isAlternativeColumnFillVisible() { return alternativeColumnFillVisible.getValue(); }
public final void setAlternativeColumnFillVisible(boolean value) { alternativeColumnFillVisible.setValue(value); }
public final BooleanProperty alternativeColumnFillVisibleProperty() { return alternativeColumnFillVisible; }
/** If true then alternative horizontal rows will have fills */
private BooleanProperty alternativeRowFillVisible = new StyleableBooleanProperty(true) {
@Override protected void invalidated() {
requestChartLayout();
}
@Override
public Object getBean() {
return XYChart.this;
}
@Override
public String getName() {
return "alternativeRowFillVisible";
}
@Override
public CssMetaData,Boolean> getCssMetaData() {
return StyleableProperties.ALTERNATIVE_ROW_FILL_VISIBLE;
}
};
public final boolean isAlternativeRowFillVisible() { return alternativeRowFillVisible.getValue(); }
public final void setAlternativeRowFillVisible(boolean value) { alternativeRowFillVisible.setValue(value); }
public final BooleanProperty alternativeRowFillVisibleProperty() { return alternativeRowFillVisible; }
/**
* If this is true and the vertical axis has both positive and negative values then a additional axis line
* will be drawn at the zero point
*
* @defaultValue true
*/
private BooleanProperty verticalZeroLineVisible = new StyleableBooleanProperty(true) {
@Override protected void invalidated() {
requestChartLayout();
}
@Override
public Object getBean() {
return XYChart.this;
}
@Override
public String getName() {
return "verticalZeroLineVisible";
}
@Override
public CssMetaData,Boolean> getCssMetaData() {
return StyleableProperties.VERTICAL_ZERO_LINE_VISIBLE;
}
};
public final boolean isVerticalZeroLineVisible() { return verticalZeroLineVisible.get(); }
public final void setVerticalZeroLineVisible(boolean value) { verticalZeroLineVisible.set(value); }
public final BooleanProperty verticalZeroLineVisibleProperty() { return verticalZeroLineVisible; }
/**
* If this is true and the horizontal axis has both positive and negative values then a additional axis line
* will be drawn at the zero point
*
* @defaultValue true
*/
private BooleanProperty horizontalZeroLineVisible = new StyleableBooleanProperty(true) {
@Override protected void invalidated() {
requestChartLayout();
}
@Override
public Object getBean() {
return XYChart.this;
}
@Override
public String getName() {
return "horizontalZeroLineVisible";
}
@Override
public CssMetaData,Boolean> getCssMetaData() {
return StyleableProperties.HORIZONTAL_ZERO_LINE_VISIBLE;
}
};
public final boolean isHorizontalZeroLineVisible() { return horizontalZeroLineVisible.get(); }
public final void setHorizontalZeroLineVisible(boolean value) { horizontalZeroLineVisible.set(value); }
public final BooleanProperty horizontalZeroLineVisibleProperty() { return horizontalZeroLineVisible; }
// -------------- PROTECTED PROPERTIES -----------------------------------------------------------------------------
/**
* Modifiable and observable list of all content in the plot. This is where implementations of XYChart should add
* any nodes they use to draw their plot.
*
* @return Observable list of plot children
*/
protected ObservableList getPlotChildren() {
return plotContent.getChildren();
}
// -------------- CONSTRUCTOR --------------------------------------------------------------------------------------
/**
* Constructs a XYChart given the two axes. The initial content for the chart
* plot background and plot area that includes vertical and horizontal grid
* lines and fills, are added.
*
* @param xAxis X Axis for this XY chart
* @param yAxis Y Axis for this XY chart
*/
public XYChart(Axis xAxis, Axis yAxis) {
this.xAxis = xAxis;
if(xAxis.getSide() == null) xAxis.setSide(Side.BOTTOM);
this.yAxis = yAxis;
if(yAxis.getSide() == null) yAxis.setSide(Side.LEFT);
// RT-23123 autoranging leads to charts incorrect appearance.
xAxis.autoRangingProperty().addListener(new ChangeListener() {
public void changed(ObservableValue extends Boolean> ov, Boolean t, Boolean t1) {
updateAxisRange();
}
});
yAxis.autoRangingProperty().addListener(new ChangeListener() {
public void changed(ObservableValue extends Boolean> ov, Boolean t, Boolean t1) {
updateAxisRange();
}
});
// add initial content to chart content
getChartChildren().addAll(plotBackground,plotArea,xAxis,yAxis);
// We don't want plotArea or plotContent to autoSize or do layout
plotArea.setAutoSizeChildren(false);
plotContent.setAutoSizeChildren(false);
// setup clipping on plot area
plotAreaClip.setSmooth(false);
plotArea.setClip(plotAreaClip);
// add children to plot area
plotArea.getChildren().addAll(
verticalRowFill, horizontalRowFill,
verticalGridLines, horizontalGridLines,
verticalZeroLine, horizontalZeroLine,
plotContent);
// setup css style classes
plotContent.getStyleClass().setAll("plot-content");
plotBackground.getStyleClass().setAll("chart-plot-background");
verticalRowFill.getStyleClass().setAll("chart-alternative-column-fill");
horizontalRowFill.getStyleClass().setAll("chart-alternative-row-fill");
verticalGridLines.getStyleClass().setAll("chart-vertical-grid-lines");
horizontalGridLines.getStyleClass().setAll("chart-horizontal-grid-lines");
verticalZeroLine.getStyleClass().setAll("chart-vertical-zero-line");
horizontalZeroLine.getStyleClass().setAll("chart-horizontal-zero-line");
// mark plotContent as unmanaged as its preferred size changes do not effect our layout
plotContent.setManaged(false);
plotArea.setManaged(false);
// listen to animation on/off and sync to axis
animatedProperty().addListener(new ChangeListener() {
@Override public void changed(ObservableValue extends Boolean> valueModel, Boolean oldValue, Boolean newValue) {
if(getXAxis() != null) getXAxis().setAnimated(newValue);
if(getYAxis() != null) getYAxis().setAnimated(newValue);
}
});
}
// -------------- METHODS ------------------------------------------------------------------------------------------
@Override public void requestLayout() {
super.requestLayout();
// RT-22726 Charts legend does not resize correctly
Node legend = getLegend();
if (legend != null && legend instanceof Region) {
((Region)legend).requestLayout();
}
}
/**
* Gets the size of the data returning 0 if the data is null
*
* @return The number of items in data, or null if data is null
*/
final int getDataSize() {
final ObservableList> data = getData();
return (data!=null) ? data.size() : 0;
}
/** Called when a series's name has changed */
private void seriesNameChanged() {
updateLegend();
requestChartLayout();
}
@SuppressWarnings({"UnusedParameters"})
private void dataItemsChanged(Series series, List> removed, int addedFrom, int addedTo, boolean permutation) {
for (Data item : removed) {
dataItemRemoved(item, series);
}
for(int i=addedFrom; i item = series.getData().get(i);
dataItemAdded(series, i, item);
}
invalidateRange();
requestChartLayout();
}
private void dataXValueChanged(Data item) {
if(item.getCurrentX() != item.getXValue()) invalidateRange();
dataItemChanged(item);
if (shouldAnimate()) {
animate(
new KeyFrame(Duration.ZERO, new KeyValue(item.currentXProperty(), item.getCurrentX())),
new KeyFrame(Duration.millis(700), new KeyValue(item.currentXProperty(), item.getXValue(), Interpolator.EASE_BOTH))
);
} else {
item.setCurrentX(item.getXValue());
requestChartLayout();
}
}
private void dataYValueChanged(Data item) {
if(item.getCurrentY() != item.getYValue()) invalidateRange();
dataItemChanged(item);
if (shouldAnimate()) {
animate(
new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), item.getCurrentY())),
new KeyFrame(Duration.millis(700), new KeyValue(item.currentYProperty(), item.getYValue(), Interpolator.EASE_BOTH))
);
} else {
item.setCurrentY(item.getYValue());
requestChartLayout();
}
}
private void dataExtraValueChanged(Data item) {
if(item.getCurrentY() != item.getYValue()) invalidateRange();
dataItemChanged(item);
if (shouldAnimate()) {
animate(
new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(), item.getCurrentY())),
new KeyFrame(Duration.millis(700), new KeyValue(item.currentYProperty(), item.getYValue(), Interpolator.EASE_BOTH))
);
} else {
item.setCurrentY(item.getYValue());
requestChartLayout();
}
}
/**
* This is called whenever a series is added or removed and the legend needs to be updated
*/
protected void updateLegend(){}
/**
* Called when a data item has been added to a series. This is where implementations of XYChart can create/add new
* nodes to getPlotChildren to represent this data item. They also may animate that data add with a fade in or
* similar if animated = true.
*
* @param series The series the data item was added to
* @param itemIndex The index of the new item within the series
* @param item The new data item that was added
*/
protected abstract void dataItemAdded(Series series, int itemIndex, Data item);
/**
* Called when a data item has been removed from data model but it is still visible on the chart. Its still visible
* so that you can handle animation for removing it in this method. After you are done animating the data item you
* must call removeDataItemFromDisplay() to remove the items node from being displayed on the chart.
*
* @param item The item that has been removed from the series
* @param series The series the item was removed from
*/
protected abstract void dataItemRemoved(Data item, Series series);
/**
* Called when a data item has changed, ie its xValue, yValue or extraValue has changed.
*
* @param item The data item who was changed
*/
protected abstract void dataItemChanged(Data item);
/**
* A series has been added to the charts data model. This is where implementations of XYChart can create/add new
* nodes to getPlotChildren to represent this series. Also you have to handle adding any data items that are
* already in the series. You may simply call dataItemAdded() for each one or provide some different animation for
* a whole series being added.
*
* @param series The series that has been added
* @param seriesIndex The index of the new series
*/
protected abstract void seriesAdded(Series series, int seriesIndex);
/**
* A series has been removed from the data model but it is still visible on the chart. Its still visible
* so that you can handle animation for removing it in this method. After you are done animating the data item you
* must call removeSeriesFromDisplay() to remove the series from the display list.
*
* @param series The series that has been removed
*/
protected abstract void seriesRemoved(Series series);
/** Called when each atomic change is made to the list of series for this chart */
protected void seriesChanged(Change extends Series> c) {}
/**
* This is called when a data change has happened that may cause the range to be invalid.
*/
private void invalidateRange() {
rangeValid = false;
}
/**
* This is called when the range has been invalidated and we need to update it. If the axis are auto
* ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the
* axis passing it that data.
*/
protected void updateAxisRange() {
final Axis xa = getXAxis();
final Axis ya = getYAxis();
List xData = null;
List yData = null;
if(xa.isAutoRanging()) xData = new ArrayList();
if(ya.isAutoRanging()) yData = new ArrayList();
if(xData != null || yData != null) {
for(Series series : getData()) {
for(Data data: series.getData()) {
if(xData != null) xData.add(data.getXValue());
if(yData != null) yData.add(data.getYValue());
}
}
if(xData != null) xa.invalidateRange(xData);
if(yData != null) ya.invalidateRange(yData);
}
}
/**
* Called to update and layout the plot children. This should include all work to updates nodes representing
* 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
* can be got by getting the width of the x axis and its height from the height of the y axis.
*/
protected abstract void layoutPlotChildren();
/** @inheritDoc */
@Override protected final void layoutChartChildren(double top, double left, double width, double height) {
if(getData() == null) return;
if (!rangeValid) {
rangeValid = true;
if(getData() != null) updateAxisRange();
}
// snap top and left to pixels
top = snapPosition(top);
left = snapPosition(left);
// get starting stuff
final Axis xa = getXAxis();
final ObservableList> xaTickMarks = xa.getTickMarks();
final Axis ya = getYAxis();
final ObservableList> yaTickMarks = ya.getTickMarks();
// check we have 2 axises and know their sides
if (xa == null || ya == null || xa.getSide() == null || ya.getSide() == null) return;
// try and work out width and height of axises
double xAxisWidth = 0;
double xAxisHeight = 30; // guess x axis height to start with
double yAxisWidth = 0;
double yAxisHeight = 0;
for (int count=0; count<5; count ++) {
yAxisHeight = height-xAxisHeight;
yAxisWidth = ya.prefWidth(yAxisHeight);
xAxisWidth = width - yAxisWidth;
double newXAxisHeight = xa.prefHeight(xAxisWidth);
if (newXAxisHeight == xAxisHeight) break;
xAxisHeight = newXAxisHeight;
}
// round axis sizes up to whole integers to snap to pixel
xAxisWidth = Math.ceil(xAxisWidth);
xAxisHeight = Math.ceil(xAxisHeight);
yAxisWidth = Math.ceil(yAxisWidth);
yAxisHeight = Math.ceil(yAxisHeight);
// calc xAxis height
double xAxisY = 0;
if (xa.getSide().equals(Side.TOP)) {
xa.setVisible(true);
xAxisY = top+1;
top += xAxisHeight;
} else if (xa.getSide().equals(Side.BOTTOM)) {
xa.setVisible(true);
xAxisY = top + yAxisHeight;
} else {
// X axis should never be left or right so hide
xa.setVisible(false);
xAxisHeight = 0;
}
// calc yAxis width
double yAxisX = 0;
if (ya.getSide().equals(Side.LEFT)) {
ya.setVisible(true);
yAxisX = left +1;
left += yAxisWidth;
} else if (ya.getSide().equals(Side.RIGHT)) {
ya.setVisible(true);
yAxisX = left + xAxisWidth;
} else {
// Y axis should never be top or bottom so hide
ya.setVisible(false);
yAxisWidth = 0;
}
// resize axises
xa.resizeRelocate(left, xAxisY, xAxisWidth, xAxisHeight);
ya.resizeRelocate(yAxisX, top, yAxisWidth, yAxisHeight);
// When the chart is resized, need to specifically call out the axises
// to lay out as they are unmanaged.
xa.requestAxisLayout();
xa.layout();
ya.requestAxisLayout();
ya.layout();
// layout plot content
layoutPlotChildren();
// get axis zero points
final double xAxisZero = xa.getZeroPosition();
final double yAxisZero = ya.getZeroPosition();
// position vertical and horizontal zero lines
if(Double.isNaN(xAxisZero) || !isVerticalZeroLineVisible()) {
verticalZeroLine.setVisible(false);
} else {
verticalZeroLine.setStartX(left+xAxisZero+0.5);
verticalZeroLine.setStartY(top);
verticalZeroLine.setEndX(left+xAxisZero+0.5);
verticalZeroLine.setEndY(top+yAxisHeight);
verticalZeroLine.setVisible(true);
}
if(Double.isNaN(yAxisZero) || !isHorizontalZeroLineVisible()) {
horizontalZeroLine.setVisible(false);
} else {
horizontalZeroLine.setStartX(left);
horizontalZeroLine.setStartY(top+yAxisZero+0.5);
horizontalZeroLine.setEndX(left+xAxisWidth);
horizontalZeroLine.setEndY(top+yAxisZero+0.5);
horizontalZeroLine.setVisible(true);
}
// layout plot background
plotBackground.resizeRelocate(left, top, xAxisWidth, yAxisHeight);
// update clip
plotAreaClip.setX(left);
plotAreaClip.setY(top);
plotAreaClip.setWidth(xAxisWidth+1);
plotAreaClip.setHeight(yAxisHeight+1);
// plotArea.setClip(new Rectangle(left, top, xAxisWidth, yAxisHeight));
// position plot group, its origin is the bottom left corner of the plot area
plotContent.setLayoutX(left);
plotContent.setLayoutY(top);
plotContent.requestLayout(); // Note: not sure this is right, maybe plotContent should be resizeable
// update vertical grid lines
verticalGridLines.getElements().clear();
if(getVerticalGridLinesVisible()) {
for(int i=0; i < xaTickMarks.size(); i++) {
Axis.TickMark tick = xaTickMarks.get(i);
double pixelOffset = (i==(xaTickMarks.size()-1)) ? -0.5 : 0.5;
final double x = xa.getDisplayPosition(tick.getValue());
if ((x!=xAxisZero || !isVerticalZeroLineVisible()) && x > 0 && x <= xAxisWidth) {
verticalGridLines.getElements().add(new MoveTo(left+x+pixelOffset,top));
verticalGridLines.getElements().add(new LineTo(left+x+pixelOffset,top+yAxisHeight));
}
}
}
// update horizontal grid lines
horizontalGridLines.getElements().clear();
if(isHorizontalGridLinesVisible()) {
for(int i=0; i < yaTickMarks.size(); i++) {
Axis.TickMark tick = yaTickMarks.get(i);
double pixelOffset = (i==(yaTickMarks.size()-1)) ? -0.5 : 0.5;
final double y = ya.getDisplayPosition(tick.getValue());
if ((y!=yAxisZero || !isHorizontalZeroLineVisible()) && y >= 0 && y < yAxisHeight) {
horizontalGridLines.getElements().add(new MoveTo(left,top+y+pixelOffset));
horizontalGridLines.getElements().add(new LineTo(left+xAxisWidth,top+y+pixelOffset));
}
}
}
// Note: is there a more efficient way to calculate horizontal and vertical row fills?
// update vertical row fill
verticalRowFill.getElements().clear();
if (isAlternativeColumnFillVisible()) {
// tick marks are not sorted so get all the positions and sort them
final List tickPositionsPositive = new ArrayList();
final List tickPositionsNegative = new ArrayList();
for(int i=0; i < xaTickMarks.size(); i++) {
double pos = xa.getDisplayPosition((X) xaTickMarks.get(i).getValue());
if (pos == xAxisZero) {
tickPositionsPositive.add(pos);
tickPositionsNegative.add(pos);
} else if (pos < xAxisZero) {
tickPositionsPositive.add(pos);
} else {
tickPositionsNegative.add(pos);
}
}
Collections.sort(tickPositionsPositive);
Collections.sort(tickPositionsNegative);
// iterate over every pair of positive tick marks and create fill
for(int i=1; i < tickPositionsPositive.size(); i+=2) {
if((i+1) < tickPositionsPositive.size()) {
final double x1 = tickPositionsPositive.get(i);
final double x2 = tickPositionsPositive.get(i+1);
verticalRowFill.getElements().addAll(
new MoveTo(left+x1,top),
new LineTo(left+x1,top+yAxisHeight),
new LineTo(left+x2,top+yAxisHeight),
new LineTo(left+x2,top),
new ClosePath());
}
}
// iterate over every pair of positive tick marks and create fill
for(int i=0; i < tickPositionsNegative.size(); i+=2) {
if((i+1) < tickPositionsNegative.size()) {
final double x1 = tickPositionsNegative.get(i);
final double x2 = tickPositionsNegative.get(i+1);
verticalRowFill.getElements().addAll(
new MoveTo(left+x1,top),
new LineTo(left+x1,top+yAxisHeight),
new LineTo(left+x2,top+yAxisHeight),
new LineTo(left+x2,top),
new ClosePath());
}
}
}
// update horizontal row fill
horizontalRowFill.getElements().clear();
if (isAlternativeRowFillVisible()) {
// tick marks are not sorted so get all the positions and sort them
final List tickPositionsPositive = new ArrayList();
final List tickPositionsNegative = new ArrayList();
for(int i=0; i < yaTickMarks.size(); i++) {
double pos = ya.getDisplayPosition((Y) yaTickMarks.get(i).getValue());
if (pos == yAxisZero) {
tickPositionsPositive.add(pos);
tickPositionsNegative.add(pos);
} else if (pos < yAxisZero) {
tickPositionsPositive.add(pos);
} else {
tickPositionsNegative.add(pos);
}
}
Collections.sort(tickPositionsPositive);
Collections.sort(tickPositionsNegative);
// iterate over every pair of positive tick marks and create fill
for(int i=1; i < tickPositionsPositive.size(); i+=2) {
if((i+1) < tickPositionsPositive.size()) {
final double y1 = tickPositionsPositive.get(i);
final double y2 = tickPositionsPositive.get(i+1);
horizontalRowFill.getElements().addAll(
new MoveTo(left, top + y1),
new LineTo(left + xAxisWidth, top + y1),
new LineTo(left + xAxisWidth, top + y2),
new LineTo(left, top + y2),
new ClosePath());
}
}
// iterate over every pair of positive tick marks and create fill
for(int i=0; i < tickPositionsNegative.size(); i+=2) {
if((i+1) < tickPositionsNegative.size()) {
final double y1 = tickPositionsNegative.get(i);
final double y2 = tickPositionsNegative.get(i+1);
horizontalRowFill.getElements().addAll(
new MoveTo(left, top + y1),
new LineTo(left + xAxisWidth, top + y1),
new LineTo(left + xAxisWidth, top + y2),
new LineTo(left, top + y2),
new ClosePath());
}
}
}
//
}
/**
* Get the index of the series in the series linked list.
*
* @param series The series to find index for
* @return index of the series in series list
*/
int getSeriesIndex(Series series) {
int itemIndex = 0;
for (Series s = XYChart.this.begin; s != null; s = s.next) {
if (s == series) break;
itemIndex++;
}
return itemIndex;
}
/**
* Computes the size of series linked list
* @return size of series linked list
*/
int getSeriesSize() {
int count = 0;
for (Series d = XYChart.this.begin; d != null; d = d.next) {
count++;
}
return count;
}
/**
* This should be called from seriesRemoved() when you are finished with any animation for deleting the series from
* the chart. It will remove the series from showing up in the Iterator returned by getDisplayedSeriesIterator().
*
* @param series The series to remove
*/
protected final void removeSeriesFromDisplay(Series series) {
if (begin == series) {
begin = series.next;
} else {
Series ptr = begin;
while(ptr != null && ptr.next != series) {
ptr = ptr.next;
}
if (ptr != null)
ptr.next = series.next;
}
}
/**
* XYChart maintains a list of all series currently displayed this includes all current series + any series that
* have recently been deleted that are in the process of being faded(animated) out. This creates and returns a
* iterator over that list. This is what implementations of XYChart should use when plotting data.
*
* @return iterator over currently displayed series
*/
protected final Iterator> getDisplayedSeriesIterator() {
return new Iterator>() {
private boolean start = true;
private Series current = begin;
@Override public boolean hasNext() {
if (start) {
return current != null;
} else {
return current.next != null;
}
}
@Override public Series next() {
if (start) {
start = false;
} else if (current!=null) {
current = current.next;
}
return current;
}
@Override public void remove() {
throw new UnsupportedOperationException("We don't support removing items from the displayed series list.");
}
};
}
/**
* The current displayed data value plotted on the X axis. This may be the same as xValue or different. It is
* used by XYChart to animate the xValue from the old value to the new value. This is what you should plot
* in any custom XYChart implementations. Some XYChart chart implementations such as LineChart also use this
* to animate when data is added or removed.
*/
protected final X getCurrentDisplayedXValue(Data item) { return item.getCurrentX(); }
/** Set the current displayed data value plotted on X axis.
*
* @param item The XYChart.Data item from which the current X axis data value is obtained.
* @see #getCurrentDisplayedXValue
*/
protected final void setCurrentDisplayedXValue(Data item, X value) { item.setCurrentX(value); }
/** The current displayed data value property that is plotted on X axis.
*
* @param item The XYChart.Data item from which the current X axis data value property object is obtained.
* @return The current displayed X data value ObjectProperty.
* @see #getCurrentDisplayedXValue
*/
protected final ObjectProperty currentDisplayedXValueProperty(Data item) { return item.currentXProperty(); }
/**
* The current displayed data value plotted on the Y axis. This may be the same as yValue or different. It is
* used by XYChart to animate the yValue from the old value to the new value. This is what you should plot
* in any custom XYChart implementations. Some XYChart chart implementations such as LineChart also use this
* to animate when data is added or removed.
*/
protected final Y getCurrentDisplayedYValue(Data item) { return item.getCurrentY(); }
/**
* Set the current displayed data value plotted on Y axis.
*
* @param item The XYChart.Data item from which the current Y axis data value is obtained.
* @see #getCurrentDisplayedYValue
*/
protected final void setCurrentDisplayedYValue(Data item, Y value) { item.setCurrentY(value); }
/** The current displayed data value property that is plotted on Y axis.
*
* @param item The XYChart.Data item from which the current Y axis data value property object is obtained.
* @return The current displayed Y data value ObjectProperty.
* @see #getCurrentDisplayedYValue
*/
protected final ObjectProperty currentDisplayedYValueProperty(Data item) { return item.currentYProperty(); }
/**
* The current displayed data extra value. This may be the same as extraValue or different. It is
* used by XYChart to animate the extraValue from the old value to the new value. This is what you should plot
* in any custom XYChart implementations.
*/
protected final Object getCurrentDisplayedExtraValue(Data item) { return item.getCurrentExtraValue(); }
/**
* Set the current displayed data extra value.
*
* @param item The XYChart.Data item from which the current extra value is obtained.
* @see #getCurrentDisplayedExtraValue
*/
protected final void setCurrentDisplayedExtraValue(Data item, Object value) { item.setCurrentExtraValue(value); }
/**
* The current displayed extra value property.
*
* @param item The XYChart.Data item from which the current extra value property object is obtained.
* @return ObjectProperty