图表需要绘制出平滑的形状,因此使用 XYSplineRenderer。
此外,NumberAxis 需要根据数据段自动调整范围。
但在某些情况下,计算样条曲线时,某些样条曲线值超出自动量程段,并且未完整绘制曲线。
似乎在计算样条线之前评估自动范围。
为了缓解这个问题,我通过按范围限制的百分比增加该范围来调整垂直轴的范围。但这会导致图表的曲线拟合不准确,因为根据数据输入,百分比可能高达 25%。
double percentOverRange = 0.05;//2%
double initalRange = series.getMaxY() - series.getMinY();
double increase = initalRange*percentOverRange;
verticalAxis.setRange(new Range(series.getMinY()-increase, series.getMaxY()+increase));
此代码创建了上面的图片,并演示了曲线如何不完全绘制在前两个数据点之间。请注意,域轴是 DateAxis(每日数据),没有周末值
public class MyPlotChart {
private static Color MetalColor = new Color(255, 152, 0);
static double[] yData = new double[] { 0.67, 0.67, 0.69, 0.70, 0.70, 0.71, 0.71 };
static String[] labels = new String[] { "2021-11-09", "2021-11-10", "2021-11-11", "2021-11-12", "2021-11-15", "2021-11-16", "2021-11-17" };
public static void plot(String metal, int samples) throws IOException, ParseException {
SimpleDateFormat dateformatyyyy_MM_dd = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat dateformatdd_MM_yyyy = new SimpleDateFormat("dd-MM-yyyy");
XYSeries series = new XYSeries(metal);
for (int i = 0; i < yData.length; i++) {
Date date = dateformatyyyy_MM_dd.parse(labels[i]);
series.add(date.getTime(), yData[i]);
}
//Configure Vertical Axis
NumberAxis verticalAxis = new NumberAxis(null);
NumberFormat numberFormat = NumberFormat.getInstance(Locale.getDefault());
numberFormat.setRoundingMode(RoundingMode.HALF_DOWN);
numberFormat.setMinimumFractionDigits(2);
numberFormat.setMaximumFractionDigits(2);
double vericalTickUnit = (series.getMaxY() - series.getMinY()) / 7;
NumberTickUnit nt = new NumberTickUnit(vericalTickUnit, numberFormat);
verticalAxis.setTickUnit(nt);
double percentOverRange = 0.05;// 2%
double initalRange = series.getMaxY() - series.getMinY();
double increase = initalRange * percentOverRange;
verticalAxis.setRange(new Range(series.getMinY()-increase, series.getMaxY()+increase));
verticalAxis.setAutoRange(true);
verticalAxis.setAutoRangeIncludesZero(false);
verticalAxis.setTickMarksVisible(true);
verticalAxis.setTickMarkInsideLength(3f);
//Configure Domain Axis
DateAxis domainAxis = new DateAxis(null);
domainAxis.setTickUnit(new DateTickUnit(DateTickUnitType.DAY, 1, dateformatdd_MM_yyyy));
//Configure Renderer
XYSplineRenderer r = new XYSplineRenderer(10);
r.setSeriesPaint(0, MetalColor);
r.setDefaultShapesVisible(true);
r.setSeriesStroke(0, new BasicStroke(3.0f));
XYDataset dataset = new XYSeriesCollection(series);
XYPlot xyplot = new XYPlot(dataset, domainAxis, verticalAxis, r);
xyplot.getDomainAxis().setVerticalTickLabels(true);
xyplot.setDomainGridlinesVisible(false);
xyplot.setBackgroundImage(null);
xyplot.setBackgroundPaint(Color.WHITE);
Font font = xyplot.getDomainAxis().getTickLabelFont();
Font fontnew = new Font(font.getName(), Font.BOLD, 14);
xyplot.getDomainAxis().setTickLabelFont(fontnew);
xyplot.getRangeAxis().setTickLabelFont(fontnew);
JFreeChart chart = new JFreeChart(xyplot);
chart.removeLegend();// Remove legend
chart.setBackgroundPaint(Color.WHITE);
String fileName = "myChart" + metal + samples + "TEST.png";
ChartUtils.saveChartAsPNG(new File(fileName), chart, 600, 600);
}
public static void main(String[] args) throws IOException, ParseException {
MyPlotChart.plot("metal", 7);
}
}
EDIT
下面的图来自上面的代码,只是更改了 XYSplineRenderer 的精度。
正如 javadoc 中定义的:
XYSplineRenderer:将数据点与自然连接的渲染器
在每个数据点绘制三次样条和/或绘制形状。
公共 XYSplineRenderer(int 精度)
创建一个具有指定精度且不填充的新渲染器
样条曲线“下方”(“0”和“0”之间)的区域。
参数:
精度 - 数据项之间的点数。
这意味着自然三次样条仅根据数据点计算。
另一方面,精度用于定义每对数据点之间的插值点的数量。
精度 = N - 1,其中 N = 每个数据点段之间的插值点数
我只能看到两个选项:
- XYSplineRenderer 应该有一个返回自然三次样条集的方法,因此可以计算每个段的最大值,从而可以相应地设置 AutoRange
- JFreeChart 应该实现一个基于 NURBS(非均匀有理基样条)的渲染器,而不是自然的三次样条,它通过一组控制点(see https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
EDIT 2
当没有可用数据(周末)并且 DateAxis 在周五和周一之间插入两天时,问题会加剧:值之间的差距更大,因此样条线也更长。