如何用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); } } } }
更优方案:结合两种思路
同时使用动态透明度+分层绘制,既体现重叠区域的颜色差异,又保证短时任务不被完全遮挡:
- 长时任务基础透明度设为120,根据重叠次数动态降低;
- 短时任务透明度设为180,保证视觉突出;
- 先绘制长时任务,再绘制短时任务。
内容的提问来源于stack exchange,提问作者Lacie
相关产品推荐
相关产品推荐

