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

如何创建带时间轴的堆叠条形图?X轴分类渲染异常求助

解决JFreeChart水平堆叠时间条形图X轴渲染异常问题

我帮你梳理下代码里导致X轴(Category轴)异常的核心问题,以及对应的修正方案:

核心问题分析

  1. 数据集构建逻辑错误

    • 代码里createDataset方法存在无意义的嵌套循环(外层for (int i = 0; i < testData.size(); i++)完全多余),会导致数据被重复添加,造成图表渲染混乱。
    • 堆叠条形图需要的是每个状态的持续时间长度,但你现在直接添加的是单个时间戳的值,这不符合堆叠图的逻辑,无法正确展示时间段。
  2. 轴与数据的适配问题

    • 水平堆叠图中,Category轴(Domain轴)应该对应机器名称(比如Mach-1),Date轴(Range轴)对应时间范围,但原代码没有将状态的持续时间映射为数值,导致轴渲染异常。

修正后的完整代码

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.StackedBarRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.RefineryUtilities;

import javax.swing.*;
import java.awt.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

public class StackedTimeBarChart extends JFrame {
    private static final String STANDBY_SERIES = "STANDBY";
    private static final String HEATING_SERIES = "HEATING";
    private static final String HOLDING_SERIES = "HOLDING";
    private static final String COOLING_SERIES = "COOLING";
    private static final String LOWERING_SERIES = "LOWERING";
    private static final int STANDBY_SERIES_INDEX = 0;
    private static final int HEATING_SERIES_INDEX = 1;
    private static final int HOLDING_SERIES_INDEX = 2;
    private static final int COOLING_SERIES_INDEX = 3;
    private static final int LOWERING_SERIES_INDEX = 4;
    private static final Color STANDBY_COLOR = Color.DARK_GRAY;
    private static final Color HEATING_COLOR = Color.ORANGE;
    private static final Color HOLDING_COLOR = Color.YELLOW;
    private static final Color COOLING_COLOR = Color.CYAN;
    private static final Color LOWERING_COLOR = Color.GREEN;
    ArrayList<EventStatus> testData = null;
    CategoryPlot plot;

    private Long Datetolong(String date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm");
        Date inputDate = null;
        try {
            inputDate = simpleDateFormat.parse(date);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return inputDate != null ? inputDate.getTime() : 0L;
    }

    public StackedTimeBarChart(String title) {
        super(title);
        // 初始化测试数据
        initData();
        // 创建图表核心组件
        plot = new CategoryPlot();
        plot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
        plot.setOrientation(PlotOrientation.HORIZONTAL);

        // 创建正确的数据集
        CategoryDataset dataset = createDataset();
        // 配置轴
        CategoryAxis catAxis = new CategoryAxis("设备名称");
        DateAxis dateAxis = new DateAxis("时间");
        dateAxis.setVerticalTickLabels(true);
        dateAxis.setDateFormatOverride(new SimpleDateFormat("dd/MM/yyyy HH:mm"));

        // 配置渲染器
        StackedBarRenderer rend = new StackedBarRenderer();
        rend.setSeriesPaint(STANDBY_SERIES_INDEX, STANDBY_COLOR);
        rend.setSeriesPaint(HEATING_SERIES_INDEX, HEATING_COLOR);
        rend.setSeriesPaint(HOLDING_SERIES_INDEX, HOLDING_COLOR);
        rend.setSeriesPaint(COOLING_SERIES_INDEX, COOLING_COLOR);
        rend.setSeriesPaint(LOWERING_SERIES_INDEX, LOWERING_COLOR);

        // 组装图表
        plot.setDataset(dataset);
        plot.setDomainAxis(catAxis);
        plot.setRangeAxis(dateAxis);
        plot.setRenderer(rend);

        JFreeChart chart = new JFreeChart("设备状态时间轴", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new Dimension(800, 450));
        setContentPane(chartPanel);
    }

    private CategoryDataset createDataset() {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        if (testData == null || testData.size() < 2) {
            plot.setNoDataMessage("NO DATA AVAILABLE");
            return dataset;
        }

        // 计算每个状态的持续时间(从当前时间点到下一个时间点的间隔)
        for (int j = 0; j < testData.size() - 1; j++) {
            EventStatus current = testData.get(j);
            EventStatus next = testData.get(j + 1);
            long duration = next.getTime() - current.getTime(); // 持续时间(毫秒)

            String seriesName = getSeriesNameByStatus(current.getStatus());
            // 将持续时间作为值添加,Category是设备名称
            dataset.addValue(duration, seriesName, current.getName());
        }

        // 处理最后一个状态(如果需要可以假设到当前时间,这里暂时忽略)
        return dataset;
    }

    private String getSeriesNameByStatus(int status) {
        return switch (status) {
            case STANDBY_SERIES_INDEX -> STANDBY_SERIES;
            case HEATING_SERIES_INDEX -> HEATING_SERIES;
            case HOLDING_SERIES_INDEX -> HOLDING_SERIES;
            case COOLING_SERIES_INDEX -> COOLING_SERIES;
            case LOWERING_SERIES_INDEX -> LOWERING_SERIES;
            default -> STANDBY_SERIES;
        };
    }

    public static void main(String[] args) {
        StackedTimeBarChart demo = new StackedTimeBarChart("设备状态堆叠时间图");
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        demo.setVisible(true);
    }

    private void initData() {
        testData = new ArrayList<EventStatus>();
        testData.add(new EventStatus("Mach-1", Datetolong("03/04/2020 08:00"), 1));
        testData.add(new EventStatus("Mach-1", Datetolong("03/04/2020 09:00"), 2));
        testData.add(new EventStatus("Mach-1", Datetolong("03/04/2020 09:20"), 4));
        testData.add(new EventStatus("Mach-1", Datetolong("03/04/2020 10:00"), 3));
    }

    // 状态事件实体类
    private class EventStatus {
        private String name;
        private long time;
        private int status;

        public EventStatus(String name, long time, int status) {
            this.name = name;
            this.time = time;
            this.status = status;
        }

        public String getName() {
            return name;
        }

        public long getTime() {
            return time;
        }

        public int getStatus() {
            return status;
        }
    }

关键修正点说明

  1. 移除冗余嵌套循环:删除了createDataset中外层无意义的for (int i = 0; i < testData.size(); i++)循环,避免数据重复添加。
  2. 计算状态持续时间:改为计算相邻状态时间点的间隔作为堆叠值,这样堆叠条形图会正确展示每个状态的持续时间段。
  3. 优化轴的命名:给Category轴添加了“设备名称”的标签,让图表更易读。
  4. 完善空数据处理:增加了数据不足时的判断逻辑,避免空指针异常。
  5. 使用switch简化状态映射:用switch表达式替代冗长的if-else,代码更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 10:47:38