You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JFreeChart使用XYSplineRenderer时自动量程无法完整显示样条曲线问题

JFreeChart XYSplineRenderer样条曲线超出自动量程显示不全问题

问题描述

需要绘制平滑形态的图表,因此选用了XYSplineRenderer作为渲染器,同时要求NumberAxis针对数据段自动设置量程。但部分场景下计算生成的样条曲线值会超出自动量程范围,导致曲线无法完整显示。
样条曲线显示不全示例

经排查,自动量程的计算时机早于样条曲线生成时机是问题诱因。曾尝试按比例扩大纵轴量程缓解该问题,代码如下:

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));

但该方案存在明显缺陷:根据输入数据不同,量程扩展比例最高需达25%,会导致图表曲线适配精度下降。

上述代码生成的图表可复现前两个数据点之间曲线显示不全的问题,注:域轴为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);
        }
    }

补充更新1

以下图表均为上述代码仅修改XYSplineRenderer的precision参数生成,根据官方Javadoc定义:

XYSplineRenderer: 一款使用自然三次样条连接数据点、且/或在每个数据点绘制形状的渲染器。
public XYSplineRenderer(int precision)
使用指定精度创建新渲染器,不对样条下方(0到样条之间)区域进行填充。
参数:precision - 数据点之间的插值点数量。

也就是说自然三次样条仅基于原始数据点计算,precision参数用于定义每对数据点之间的插值点数量,对应公式为:Precision = N - 1,其中N为每个数据段之间的插值点总数。

目前有两种可行优化方案:

  • XYSplineRenderer新增返回自然三次样条集合的方法,计算各分段极值后对应设置AutoRange;
  • JFreeChart新增基于NURBS(非均匀有理B样条)的渲染器,通过控制点控制曲线形态避免超量程。

不同precision参数效果1
不同precision参数效果2
不同precision参数效果3

补充更新2

当存在数据空缺(如周末无数据)时问题会进一步加剧,DateAxis会在周五和周一之间插入两天间隔,数据点间距更大,样条曲线超出量程的长度也会更长。
数据空缺时问题示例

内容的提问来源于stack exchange,提问作者J Robes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 16:45:07