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

如何用JFreeChart甘特图实现子任务重叠可视化?

问题

需要可视化单个任务(Person1)的子任务重叠情况,子任务包括SignIn、SignOut、Click(短时)、Call(长时)。核心需求是清晰展示两个Call任务的重叠区域,以及短时任务和长时任务的叠加效果。

已尝试两种思路但未实现预期效果:

  • 降低子任务透明度,让重叠区域显示更深色,但修改getItemPaint方法设置固定透明度后效果不佳:
Color rbg = clut.get(clutIndex);
return new Color(rbg.getRed(), rbg.getGreen(), rbg.getBlue(), 150);
  • 想把短时任务渲染在长时任务的叠加层上方,但未完成代码逻辑。

当前数据集创建代码如下:

private @NotNull IntervalCategoryDataset createDataset() {

    final TaskSeries s1 = new TaskSeries("Scheduled");
    Task person1 = new Task("Person1",
            new SimpleTimePeriod(date(1, Calendar.APRIL, 2001),
                    date(30, Calendar.APRIL, 2001)));

    // Subtasks
    Task subTask1 = new Task("SignIn",
            new SimpleTimePeriod(date(1, Calendar.APRIL, 2001),
                    date(2, Calendar.APRIL, 2001))); // no overlap
    Task subTask2 = new Task("Call1",
            new SimpleTimePeriod(date(5, Calendar.APRIL, 2001),
                    date(20, Calendar.APRIL, 2001))); // day 5 - 20
    Task subTask3 = new Task("Click",
            new SimpleTimePeriod(date(16, Calendar.APRIL, 2001),
                    date(17, Calendar.APRIL, 2001))); // overlap with call1 and call2
    Task subTask4 = new Task("Call2",
            new SimpleTimePeriod(date(15, Calendar.APRIL, 2001),
                    date(30, Calendar.APRIL, 2001))); // day 15 - 30

    person1.addSubtask(subTask1);
    person1.addSubtask(subTask2);
    person1.addSubtask(subTask3);
    person1.addSubtask(subTask4);
    s1.add(person1);
    final TaskSeriesCollection collection = new TaskSeriesCollection();
    collection.add(s1);

    return collection;
}

解决方案

思路1:优化透明度实现重叠深色效果

固定透明度无法体现重叠差异,可通过动态计算重叠次数调整透明度,重叠次数越多,颜色越深:

@Override
public Paint getItemPaint(int row, int column) {
    TaskSeriesCollection dataset = (TaskSeriesCollection) getPlot().getDataset();
    TaskSeries series = dataset.getSeries(row);
    Task currentTask = series.get(column);
    
    // 统计当前任务的重叠次数
    int overlapCount = 1;
    for (Task task : series.getTasks()) {
        if (task != currentTask && currentTask.getDuration().overlaps(task.getDuration())) {
            overlapCount++;
        }
    }
    
    // 按任务类型分配基础颜色
    Color baseColor;
    if (currentTask.getDescription().startsWith("Call")) {
        baseColor = Color.BLUE;
    } else if (currentTask.getDescription().equals("Click")) {
        baseColor = Color.GREEN;
    } else {
        baseColor = Color.GRAY;
    }
    
    // 重叠越多,alpha值越小(颜色越深),最低保留50避免完全透明
    int alpha = Math.max(50, 200 - (overlapCount * 50));
    return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), alpha);
}

思路2:将短时任务置于叠加层上方

通过调整绘制顺序让短时任务后绘制(后绘制的元素会覆盖先绘制的),有两种实现方式:

方法1:调整数据集添加顺序

直接修改createDataset中的子任务添加顺序,先加时长任务,再加短时任务:

// 先添加长时Call任务
person1.addSubtask(subTask2); // Call1
person1.addSubtask(subTask4); // Call2
// 再添加短时任务
person1.addSubtask(subTask1); // SignIn
person1.addSubtask(subTask3); // Click

方法2:自定义渲染器控制绘制顺序

如果无法修改数据集顺序,可重写渲染器逻辑,优先绘制长时任务,再绘制短时任务:

public class CustomTaskRenderer extends BarRenderer {
    @Override
    public void drawItem(Graphics2D g2, CategoryItemRendererState state,
                         Rectangle2D dataArea, CategoryPlot plot,
                         CategoryAxis domainAxis, ValueAxis rangeAxis,
                         CategoryDataset dataset, int row, int column,
                         int pass) {
        TaskSeriesCollection taskDataset = (TaskSeriesCollection) dataset;
        Task task = taskDataset.getSeries(row).get(column);
        // 只绘制长时Call任务
        if (task.getDescription().startsWith("Call")) {
            super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, dataset, row, column, pass);
        }
    }
    
    @Override
    public void drawPass(Graphics2D g2, CategoryItemRendererState state,
                         Rectangle2D dataArea, CategoryPlot plot,
                         CategoryAxis domainAxis, ValueAxis rangeAxis,
                         CategoryDataset dataset, int pass) {
        // 先绘制所有长时任务
        super.drawPass(g2, state, dataArea, plot, domainAxis, rangeAxis, dataset, pass);
        // 再绘制短时任务
        TaskSeriesCollection taskDataset = (TaskSeriesCollection) dataset;
        TaskSeries series = taskDataset.getSeries(0);
        for (int i = 0; i < series.getItemCount(); i++) {
            Task task = series.get(i);
            if (!task.getDescription().startsWith("Call")) {
                super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, dataset, 0, i, pass);
            }
        }
    }
}

更优方案:结合两种思路

同时使用动态透明度+分层绘制,既体现重叠区域的颜色差异,又保证短时任务不被完全遮挡:

  1. 长时任务基础透明度设为120,根据重叠次数动态降低;
  2. 短时任务透明度设为180,保证视觉突出;
  3. 先绘制长时任务,再绘制短时任务。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 11:30:47