When using jfreechart-fx to render a time series chart with vertical tick labels, the labels unexpectedly overlap the domain axis and sometimes change on resize. I can't reproduce this with Swing or pure Java2D, shown here. I'd welcome any guidance.
import java.awt.BasicStroke;
import java.awt.Color;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.fx.ChartViewer;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYSplineRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
/**
* @see https://stackoverflow.com/q/70021577/230513
*/
public class SplineTest extends Application {
@Override
public void start(Stage stage) throws ParseException {
SimpleDateFormat inFormat = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat outFormat = new SimpleDateFormat("dd-MM-yyyy");
// data
double[] values = new double[]{0.67, 0.67, 0.69, 0.70, 0.70, 0.71, 0.71};
String[] dates = new String[]{"2021-11-09", "2021-11-10", "2021-11-11",
"2021-11-12", "2021-11-15", "2021-11-16", "2021-11-17"};
TimeSeries series = new TimeSeries("Time Series");
for (int i = 0; i < values.length; i++) {
Date date = inFormat.parse(dates[i]);
series.add(new Day(date), values[i]);
}
TimeSeriesCollection dataset = new TimeSeriesCollection(series);
// axes
NumberAxis rangeAxis = new NumberAxis("Value");
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setMinimumFractionDigits(2);
numberFormat.setMaximumFractionDigits(2);
rangeAxis.setNumberFormatOverride(numberFormat);
rangeAxis.setLowerMargin(0.08); // 8% lower margin
rangeAxis.setAutoRangeIncludesZero(false);
DateAxis domainAxis = new DateAxis("Date");
domainAxis.setDateFormatOverride(outFormat);
domainAxis.setVerticalTickLabels(true);
// renderer, plot, chart
XYSplineRenderer r = new XYSplineRenderer(15);
XYPlot xyplot = new XYPlot(dataset, domainAxis, rangeAxis, r);
JFreeChart chart = new JFreeChart(null, xyplot);
r.setSeriesPaint(0, new Color(255, 152, 0));
r.setSeriesStroke(0, new BasicStroke(2.0f));
// display
Scene scene = new Scene(new ChartViewer(chart));
stage.setTitle("SplineTest");
stage.setScene(scene);
stage.setWidth(600);
stage.setHeight(400);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The example cited uses a renderer for which no ChartFactory
exists. These factories illustrate typical use cases; all end by applying a ChartTheme
to the chart
. The variation below applies a StandardChartTheme
, which establishes coherent defaults for elements that affect geometry, such as font metrics and axis offsets; the defaults can be customized as shown.
The issue does not arise in pure Java2D, which runs on the initial thread. A similar Swing program is typically scheduled on the EventQueue
. A jfreechart-fx program relies on a custom GraphicsContext
; applying a ChartTheme
ensures that the required defaults are present on the first update.
In exceptional cases, the chart can be made to redraw itself explicitly via fireChartChanged()
. The Canvas
held by the ChartViewer
can do the same via its chartChanged()
method.
import java.awt.BasicStroke;
import java.awt.Color;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.fx.ChartViewer;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYSplineRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
/**
* @see https://stackoverflow.com/a/70058016/230513
* @see https://stackoverflow.com/q/70021577/230513
*/
public class SplineTest extends Application {
@Override
public void start(Stage stage) throws ParseException {
SimpleDateFormat inFormat = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat outFormat = new SimpleDateFormat("dd-MM-yyyy");
// data
double[] values = new double[]{0.67, 0.67, 0.69, 0.70, 0.70, 0.71, 0.71};
String[] dates = new String[]{"2021-11-09", "2021-11-10", "2021-11-11",
"2021-11-12", "2021-11-15", "2021-11-16", "2021-11-17"};
TimeSeries series = new TimeSeries("Time Series");
for (int i = 0; i < values.length; i++) {
Date date = inFormat.parse(dates[i]);
series.add(new Day(date), values[i]);
}
TimeSeriesCollection dataset = new TimeSeriesCollection(series);
// axes
NumberAxis rangeAxis = new NumberAxis("Value");
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setMinimumFractionDigits(2);
numberFormat.setMaximumFractionDigits(2);
rangeAxis.setNumberFormatOverride(numberFormat);
rangeAxis.setLowerMargin(0.08); // 8% lower margin
rangeAxis.setAutoRangeIncludesZero(false);
DateAxis domainAxis = new DateAxis("Date");
domainAxis.setDateFormatOverride(outFormat);
domainAxis.setVerticalTickLabels(true);
// renderer, plot, chart
XYSplineRenderer r = new XYSplineRenderer(15);
XYPlot xyplot = new XYPlot(dataset, domainAxis, rangeAxis, r);
JFreeChart chart = new JFreeChart(null, xyplot);
StandardChartTheme theme = new StandardChartTheme("Custom");
theme.setPlotBackgroundPaint(Color.WHITE);
theme.setDomainGridlinePaint(Color.LIGHT_GRAY);
theme.setRangeGridlinePaint(Color.LIGHT_GRAY);
theme.apply(chart);
r.setSeriesPaint(0, new Color(255, 152, 0));
r.setSeriesStroke(0, new BasicStroke(2.0f));
// display
Scene scene = new Scene(new ChartViewer(chart));
stage.setTitle("SplineTest");
stage.setScene(scene);
stage.setWidth(600);
stage.setHeight(400);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}