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

在JFreeChart中生成平滑温湿度曲线:XYBezierRenderer效果不佳求解决方案

温湿度数据曲线绘制问题(JFreeChart)

我尝试用JFreeChart绘制温湿度数据图表,最初使用XYSplineRenderer时出现了越界问题。改用XYBezierRenderer后,即使设置precision=5、tension=0.05,曲线仍存在尖锐转折,效果不尽人意。

我试过调整precision和tension的各类参数,最终XYBezierRenderer(5,0.05)时效果稍好,但还是不理想。另外测试过自定义类XYCardinalSplineRenderer(设置tension=0.05、segments=16)效果稍好,但该类并未包含在JFreeChart官方包中,恳请提供解决方案。

创建图表的代码

private JFreeChart createMultiAxisChart(List<EntityAuditDto> reportsData, Map<String, String> attributes, TemperatureStatistics temperatureStats, HumidityStatistics humidityStatistics) {
    List<EntityAuditDto> reportData = generateDummyData();
    XYDataset temperatureDataset = createMultiTemperatureDataset(reportData);
    JFreeChart chart = ChartFactory.createTimeSeriesChart(
                            "Temperature & Humidity",
                            "Time",
                             "Temperature (°C)",
                             temperatureDataset,true,true,false);
    XYPlot plot = chart.getXYPlot();
    plot.setBackgroundPaint(Color.WHITE);
    plot.setDomainGridlinesVisible(true);
    plot.setRangeGridlinesVisible(true);
    plot.setDomainGridlinePaint(Color.GRAY);
    plot.setRangeGridlinePaint(Color.GRAY);

    Font axisLabelFont = new Font("SansSerif", Font.BOLD, 12);
    Font tickLabelFont = new Font("SansSerif", Font.BOLD, 10);
    Color axisLabelColor = Color.BLACK;

    DateAxis dateAxis = new DateAxis("Time");
    dateAxis.setDateFormatOverride(new SimpleDateFormat("yyyy-MM-dd"));
    dateAxis.setAutoTickUnitSelection(true);
    dateAxis.setAutoRange(true);
    dateAxis.setLabelFont(axisLabelFont);
    dateAxis.setTickLabelFont(tickLabelFont);
    dateAxis.setLabelPaint(axisLabelColor);
    dateAxis.setTickLabelPaint(axisLabelColor);
    plot.setDomainAxis(dateAxis);

    XYBezierRenderer tempRenderer = new XYBezierRenderer(5,0.05);
    for (int i = 0; i < temperatureDataset.getSeriesCount(); i++) {
        tempRenderer.setSeriesPaint(i, getSeriesColor(i));
        tempRenderer.setSeriesShapesVisible(i, false);
    }
    plot.setRenderer(0, tempRenderer);

    NumberAxis tempAxis = (NumberAxis) plot.getRangeAxis();
    tempAxis.setLabelFont(axisLabelFont);
    tempAxis.setTickLabelFont(tickLabelFont);
    tempAxis.setLabelPaint(axisLabelColor);
    tempAxis.setTickLabelPaint(axisLabelColor);
    double minTemperature = calculateMinTemperature(temperatureStats);
    double maxTemperature = calculateMaxTemperature(temperatureStats);
    tempAxis.setRange(0, 100);
    tempAxis.setAutoRangeIncludesZero(false);
    tempAxis.setLowerMargin(0.05);
    tempAxis.setUpperMargin(0.05);


    XYDataset humidityDataset = createMultiHumidityDataset(reportData);
    NumberAxis humidityAxis = new NumberAxis("Humidity (%)");

    double minHumidity = calculateMinHumidity(humidityStatistics);
    double maxHumidity = calculateMaxHumidity(humidityStatistics);
    humidityAxis.setLabelFont(axisLabelFont);
    humidityAxis.setTickLabelFont(tickLabelFont);
    humidityAxis.setLabelPaint(axisLabelColor);
    humidityAxis.setTickLabelPaint(axisLabelColor);
    humidityAxis.setRange(0, 100);
    humidityAxis.setTickUnit(new NumberTickUnit(10));
    humidityAxis.setUpperMargin(0.05);
    humidityAxis.setLowerMargin(0.05);
    humidityAxis.setAutoRangeIncludesZero(false);

    plot.setRangeAxis(1, humidityAxis);
    plot.setDataset(1, humidityDataset);
    plot.mapDatasetToRangeAxis(1, 1);

    XYBezierRenderer humidityRenderer = new XYBezierRenderer(5,0.05);
    for (int i = 0; i < humidityDataset.getSeriesCount(); i++) {
        humidityRenderer.setSeriesPaint(i, getSeriesColor(i + temperatureDataset.getSeriesCount()));
        humidityRenderer.setSeriesShapesVisible(i, false);
    }
    plot.setRenderer(1, humidityRenderer);

    LegendItemCollection legendItems = new LegendItemCollection();
    XYItemRenderer renderer0 = plot.getRenderer(0);
    XYItemRenderer renderer1 = plot.getRenderer(1);

    for (int i = 0; i < temperatureDataset.getSeriesCount(); i++) {
        Paint seriesPaint = renderer0.getSeriesPaint(i);
        LegendItem legendItem = new LegendItem(
                temperatureDataset.getSeriesKey(i).toString(),
                "",
                "",
                "",
                new Rectangle(10, 10),
                seriesPaint
        );
        legendItem.setLabelPaint(seriesPaint);
        legendItems.add(legendItem);
    }

    for (int i = 0; i < humidityDataset.getSeriesCount(); i++) {
        Paint seriesPaint = renderer1.getSeriesPaint(i);
        LegendItem legendItem = new LegendItem(
                humidityDataset.getSeriesKey(i).toString(),
                "",
                "",
                "",
                new Rectangle(10, 10),
                seriesPaint
        );
        legendItem.setLabelPaint(seriesPaint);
        legendItems.add(legendItem);
    }

    plot.setFixedLegendItems(legendItems);
    return chart;
}

模拟数据生成代码

private List<EntityAuditDto> generateDummyData() {
    List<EntityAuditDto> dummyData = new ArrayList<>();
    Random random = new Random();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd   HH:mm:ss");
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DAY_OF_YEAR, -30); // Start 30 days ago    
    // Generate data every 7.5 hours (96 points over 30 days)
    int hoursPerReading = 7;
    int minutesPerReading = 30;

    for (int i = 0; i < 96; i++) {
        EntityAuditDto dto = new EntityAuditDto();

        // Generate timestamp
        calendar.add(Calendar.HOUR, hoursPerReading);
        calendar.add(Calendar.MINUTE, minutesPerReading);
        dto.setDateTime(dateFormat.format(calendar.getTime()));

        // Generate data with realistic variations
        double timeProgress = i / 95.0; // Normalized time progress (0 to 1)
        double dailyCycle = Math.sin(2 * Math.PI * timeProgress * 4); // 4 complete cycles over the period
        double weeklyCycle = Math.sin(2 * Math.PI * timeProgress * (4.0/7)); // 4/7 cycles (4 weeks) over the period

        // Generate values for each sensor within specified ranges
        double t1Base = 55 + 35 * dailyCycle + 5 * weeklyCycle; // 10 to 100
        double t2Base = 45 + 10 * dailyCycle + 3 * weeklyCycle; // 30 to 60
        double t3Base = 15 + 10 * dailyCycle + 3 * weeklyCycle; // 0 to 30
        double h1Base = 75 + 20 * dailyCycle + 3 * weeklyCycle; // 50 to 100
        double h2Base = 35 + 10 * dailyCycle + 3 * weeklyCycle; // 20 to 50

        // Add random noise and ensure values are within specified ranges
        dto.setT1(String.format("%.2f", clamp(t1Base + random.nextGaussian() * 3, 10, 100)));
        dto.setT2(String.format("%.2f", clamp(t2Base + random.nextGaussian() * 2, 30, 60)));
        dto.setT3(String.format("%.2f", clamp(t3Base + random.nextGaussian() * 2, 0, 30)));
        dto.setH1(String.format("%.2f", clamp(h1Base + random.nextGaussian() * 3, 50, 100)));
        dto.setH2(String.format("%.2f", clamp(h2Base + random.nextGaussian() * 2, 20, 50)));

        dummyData.add(dto);
    }

    return dummyData;
}

// Helper method to clamp values between min and max
private double clamp(double value, double min, double max) {
    return Math.max(min, Math.min(max, value));
}

当前图表效果

当前代码生成的图表效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 09:12:03