当前位置: 首页 > 知识库问答 >
问题:

当XYSplineRenderer用作渲染时,JFreeChart setAutoRange

萧卜霸
2023-03-14

图表需要以平滑的形状绘制,因此使用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(每日数据),周末没有值

java prettyprint-override">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);
        }
    }

编辑

下面的图来自上面的代码,只是更改了XYSplineRenderer的精度。

如javadoc中所定义:

XYSplineRenderer:一种渲染器,用于将数据点与自然三次样条曲线连接起来和/或在每个数据点绘制形状。

公共xysplinerder(整数精度)

创建具有指定精度的新渲染器,并且不填充样条线“下”(介于“0”和“0”之间)的区域。

参数:精度-数据项之间的点数。

这意味着自然三次样条是根据数据点计算的。

另一方面,精度用于定义每对数据点之间的插值点数。

精度=N-1,其中N=每个数据点段之间的插值点数

我只看到两种选择:

  1. XYSplineRenderer应该有一个返回自然三次样条集的方法,这样可以计算每个线段的最大值,从而相应地设置自动范围
  2. JFreeChart应该实现基于NURBS(非均匀有理基样条曲线)的渲染器,而不是自然三次样条曲线,该渲染器使用一组控制点控制曲线的形状(请参见)

编辑2

当没有可用数据(周末)时,问题会增加,并且DateAxis在周五和周一之间插入两天:值之间的差距更大,因此样条线也更长。

共有1个答案

凌善
2023-03-14

正如JFreeChart在实际值之外添加趋势线时所指出的,对于不是严格单调的函数,这种异常是不可避免的。如果没有更详细的样条曲线控制,则可以通过在有问题的轴上启用“自动范围”(默认值)并根据经验调整轴边距来获得更好的结果。

rangeAxis.setAutoRange(true); // true by default
rangeAxis.setLowerMargin(0.08); // 8% lower margin

在下面的变体中,请注意以下内容:

>

setNumberFormatOverride()setDateFormatOverride用于设置勾号标签的格式。

XYSplineRenader是以15的任意精度构建的。

deriveFont()用于更改轴刻度标签字体属性。

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYSplineRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/**
 * @see https://stackoverflow.com/q/70021577/230513
 */
public class SplineTest {

    public static void main(String[] args) throws ParseException, IOException {
        SimpleDateFormat formatyyyy_MM_dd = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat formatdd_MM_yyyy = new SimpleDateFormat("dd-MM-yyyy");
        double[] yData = new double[]{0.67, 0.67, 0.69, 0.70, 0.70, 0.71, 0.71};
        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"};
        XYSeries series = new XYSeries("Series");
        for (int i = 0; i < yData.length; i++) {
            Date date = formatyyyy_MM_dd.parse(labels[i]);
            series.add(date.getTime(), yData[i]);
        }
        NumberAxis rangeAxis = new NumberAxis(null);
        NumberFormat numberFormat = NumberFormat.getInstance();
        numberFormat.setRoundingMode(RoundingMode.HALF_DOWN);
        numberFormat.setMinimumFractionDigits(2);
        numberFormat.setMaximumFractionDigits(2);
        rangeAxis.setNumberFormatOverride(numberFormat);
        rangeAxis.setTickMarksVisible(true);
        rangeAxis.setAutoRange(true); // true by default
        rangeAxis.setLowerMargin(0.08); // 8% lower margin
        rangeAxis.setAutoRangeIncludesZero(false);
        DateAxis domainAxis = new DateAxis(null);
        domainAxis.setDateFormatOverride(formatdd_MM_yyyy);
        domainAxis.setVerticalTickLabels(true);
        Font font = domainAxis.getTickLabelFont().deriveFont(Font.BOLD, 10);
        domainAxis.setTickLabelFont(font);
        rangeAxis.setTickLabelFont(font);
        XYSplineRenderer r = new XYSplineRenderer(15);
        r.setSeriesPaint(0, new Color(255, 152, 0));
        r.setDefaultShapesVisible(true);
        r.setSeriesStroke(0, new BasicStroke(3.0f));
        XYDataset dataset = new XYSeriesCollection(series);
        XYPlot xyplot = new XYPlot(dataset, domainAxis, rangeAxis, r);
        xyplot.setDomainGridlinesVisible(false);
        xyplot.setBackgroundImage(null);
        xyplot.setBackgroundPaint(Color.WHITE);
        JFreeChart chart = new JFreeChart(null, null, xyplot, false);
        chart.setBackgroundPaint(Color.WHITE);
        ChartUtils.saveChartAsPNG(new File("temp.png"), chart, 400, 300);
    }
}
 类似资料:
  • 今天遇到了钩子的问题。我知道有一个类似的帖子,我阅读了使用钩子的规则。现在,当我发布表单时,它给了我这个错误。我知道这是因为我的钩子在if语句里面。但是我怎样才能把它弄出来呢?我不知道如果它不在函数或语句中,那么如何使用这个钩子。任何建议将不胜感激。代码如下: 自定义挂钩:

  • 我有一个带有对象数组的组件,其中我正在根据字符串进行过滤。问题是当我尝试将此过滤器的返回设置为本地状态时,它会抛出错误,我不太理解原因。 所以,因为我希望这个数组处于我的状态,所以我决定这样做: 插入这一行后发生的事情是这样的: 它开始多次渲染。我假设,每次状态改变时,它都会重新渲染组件(如果我错了,请纠正我)。不过,我不知道它为什么要多次这样做。 因此,我想过使用 useEffect 来实现此处

  • 图片

  • 在 Hexo 中,有两个方法可用于渲染文件或字符串,分别是非同步的 hexo.render.render 和同步的 hexo.render.renderSync,这两个方法的使用方式十分类似,因此以下仅以非同步的 hexo.render.render 为例。 渲染字符串 在渲染字符串时,您必须指定 engine,如此一来 Hexo 才知道该使用哪个渲染引擎来渲染。 hexo.render.rend

  • 6.1 渲染模板 一旦你拥有一个模版文件,你可以通过给一个map来给它传递数据。 map是一个变量及赋予的值的集合,模板使用它来得到变量的值,或者对于块标签求值。 它的渲染函数有一个可选的变量键值对map 通过 ctx.Render() 方法来渲染模板,例如: func (r *Render) Serve(ctx *faygo.Context) error { return ctx.Ren

  • 我试图将表示组件与容器组件分开。我有一个和一个。容器负责触发redux操作,以基于当前用户获取适当的站点。 问题在于,在容器组件最初呈现之后,当前用户是异步获取的。这意味着容器组件不知道需要在其函数中重新执行代码,该函数将更新数据以发送到。我想当容器组件的一个道具(用户)发生变化时,我需要重新渲染它。如何正确地执行此操作?