I have swing application which has panel that contains several JavaFX AreaCharts (using javafx.embed.swing.JFXPanel) with custom styles. We had used a jre 8u20 and jre 8u25 and all worked fine, now I had to update to jre 8u66 and my charts are looks different.
That question describes opposite to my situation: How to add negative values to JavaFx Area Chart?. I've used chart series to color the background for chart based on data (for example I need to color in red background for axis X intervals where the data is absent). The JavaFX 8u20 area chart background was colored to the lower bound of chart line, after update area chart fill background only to axis ignoring part under or above axis.
Now JavaFX area chart works like described in the documentation:
How I can return javaFX area chart the old behavior to get something like that using jre 8u66:
Edit:
So I've found the commit which fixed the negative value background fill http://hg.openjdk.java.net/openjfx/8u60/rt/rev/a57b8ba039d0?revcount=480.
It's only a few lines in method and my first idea was to make a quick fix: override this one method in my own class, but I've got problems doing that, the JavaFX classes is not friendly to such modifications many required fields and methods are private or package-private :(
Here is the class I tried to made to alter AreaChart behavior:
public class NegativeBGAreaChart<X,Y> extends AreaChart<X, Y> {
public NegativeBGAreaChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) {
this(xAxis,yAxis, FXCollections.<Series<X,Y>>observableArrayList());
}
public NegativeBGAreaChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X,Y>> data) {
super(xAxis,yAxis, data);
}
@Override
protected void layoutPlotChildren() {
List<LineTo> constructedPath = new ArrayList<>(getDataSize());
for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) {
Series<X, Y> series = getData().get(seriesIndex);
DoubleProperty seriesYAnimMultiplier = seriesYMultiplierMap.get(series);
double lastX = 0;
final ObservableList<Node> children = ((Group) series.getNode()).getChildren();
ObservableList<PathElement> seriesLine = ((Path) children.get(1)).getElements();
ObservableList<PathElement> fillPath = ((Path) children.get(0)).getElements();
seriesLine.clear();
fillPath.clear();
constructedPath.clear();
for (Iterator<Data<X, Y>> it = getDisplayedDataIterator(series); it.hasNext(); ) {
Data<X, Y> item = it.next();
double x = getXAxis().getDisplayPosition(item.getCurrentX());
double y = getYAxis().getDisplayPosition( getYAxis().toRealValue(getYAxis().toNumericValue(item.getCurrentY()) * seriesYAnimMultiplier.getValue()));
constructedPath.add(new LineTo(x, y));
if (Double.isNaN(x) || Double.isNaN(y)) {
continue;
}
lastX = x;
Node symbol = item.getNode();
if (symbol != null) {
final double w = symbol.prefWidth(-1);
final double h = symbol.prefHeight(-1);
symbol.resizeRelocate(x-(w/2), y-(h/2),w,h);
}
}
if (!constructedPath.isEmpty()) {
Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getX(), e2.getX()));
LineTo first = constructedPath.get(0);
final double displayYPos = first.getY();
final double numericYPos = getYAxis().toNumericValue(getYAxis().getValueForDisplay(displayYPos));
// RT-34626: We can't always use getZeroPosition(), as it may be the case
// that the zero position of the y-axis is not visible on the chart. In these
// cases, we need to use the height between the point and the y-axis line.
final double yAxisZeroPos = getYAxis().getZeroPosition();
final boolean isYAxisZeroPosVisible = !Double.isNaN(yAxisZeroPos);
final double yAxisHeight = getYAxis().getHeight();
final double yFillPos = isYAxisZeroPosVisible ? yAxisZeroPos : numericYPos < 0 ? numericYPos - yAxisHeight : yAxisHeight;
seriesLine.add(new MoveTo(first.getX(), displayYPos));
fillPath.add(new MoveTo(first.getX(), yFillPos));
seriesLine.addAll(constructedPath);
fillPath.addAll(constructedPath);
fillPath.add(new LineTo(lastX, yFillPos));
fillPath.add(new ClosePath());
}
}
}
}
The problems are:
Field of original AreaChart is private and inaccessible
private Map, DoubleProperty> seriesYMultiplierMap = new HashMap<>();
Some methods are package-private, for example
javafx.scene.chart.XYChart.Data.getCurrentX()
javafx.scene.chart.XYChart.Data.getCurrentY()
javafx.scene.chart.XYChart.getDataSize()