DEV Community

RobustTrueTry
RobustTrueTry

Posted on

JavaFX Gantt Charts Without the Memory Wall

The Problem With Drawing 10,000 Bars

If you've ever tried to render a real project schedule in JavaFX, you've met the wall. A Gantt chart isn't a dashboard with twelve widgets. It's a long horizontal canvas where every day is a column and every row is a task. Once you cross a few thousand bars, the scene graph starts to choke. Each task is a Node, each Node carries layout, each layout triggers a relayout, and your scrollbar becomes a slideshow.

Dirk Lemmerman's FlexGanttFX is now open source under the AGPL, and the interesting part isn't the Gantt features. It's the choice he made to stop using Nodes for the bars and draw them on a Canvas instead. That single decision is worth understanding before you reach for the library.

What you'll learn:

  • Why the standard JavaFX scene graph hurts on timeline UIs
  • How Canvas-based rendering flips the tradeoff
  • A minimal FlexGanttFX setup you can run today
  • Where Canvas rendering quietly breaks, and what to do about it

The Scene Graph Tax

Every Rectangle you place in a JavaFX scene is a participant in layout, CSS, and event dispatch. The runtime treats it as a first-class citizen. That's lovely for a button. It's expensive for ten thousand task bars in a row.

Concretely, the costs you pay are:

  • Layout passes that walk the parent chain on every change
  • CSS resolution per node, which scales with node count
  • Pick-to-hit testing during mouse events that touches every node under the cursor
  • Memory overhead per node, including transform and effect state

For a Gantt chart, none of this matters. The user doesn't style each bar independently. They don't hit-test individual pixels. They want to scroll fast and click on a row.

What FlexGanttFX Does Differently

FlexGanttFX renders the timeline grid and the bars on a JavaFX Canvas. A Canvas is a single Node backed by a GraphicsContext. You draw into it with imperative calls, the way you would with a 2D graphics API. The scene graph sees one Node, not ten thousand.

The library still uses real Nodes for things users actually interact with: the row header, the dependency arrows, the editing handles. The heavy, repetitive content (the grid lines and the bar fills) lives on the canvas. That split is the trick.

A Minimal Setup You Can Run

FlexGanttFX is published on Maven Central. Add it to a Gradle project:

dependencies {
    implementation 'com.dlsc.flexganttfx:flexganttfx:1.0'
}
Enter fullscreen mode Exit fullscreen mode

Drop a GanttChart into a BorderPane and feed it some tasks:

GanttChart<LocalDate> chart = new GanttChart<>();

Task<LocalDate> design = new Task<>("Design",
        LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 14));
Task<LocalDate> build = new Task<>("Build",
        LocalDate.of(2026, 3, 8), LocalDate.of(2026, 3, 28));
build.getDependencies().add(design);

chart.getTasks().addAll(design, build);

BorderPane root = new BorderPane(chart);
Scene scene = new Scene(root, 1000, 600);
primaryStage.setScene(scene);
primaryStage.show();
Enter fullscreen mode Exit fullscreen mode

That code gives you a working Gantt with a dependency arrow from Design to Build. Editing handles appear when you select a task. The grid and the bar fills come from the canvas, so scrolling stays smooth into the thousands.

Tradeoffs Worth Knowing

Canvas buys you speed. It costs you a few things the scene graph gives you for free.

  • No accessibility tree for the drawn content. Screen readers see the Canvas as one opaque region. Expose row data through your own accessible properties if that matters.
  • No built-in hover effects per bar. You draw pixels, not Nodes. If you want a tooltip on hover, you handle mouse events yourself and map coordinates back to the task under the cursor.
  • Redraws are manual. Change a task's start date and the canvas won't repaint on its own. Call chart.requestLayout() or trigger the repaint hook the library exposes.
  • CSS theming is limited. You can theme the Nodes (headers, handles, arrows) with CSS. The canvas-drawn bars follow a styled object you configure in code.

If your chart is small (under a few hundred bars), the standard scene-graph approach is honestly fine, and you get the accessibility and theming for free. FlexGanttFX pays off when your schedule outgrows a single screen of widgets.

When Canvas Rendering Quietly Breaks

There are two failure modes worth knowing before you ship.

The first is device pixel ratio. A Canvas drawn at logical coordinates looks blurry on high-DPI screens unless you size the underlying image buffer to match the screen's pixel scale. FlexGanttFX handles this for you, but if you fork the rendering code, copy the same scaling pattern.

The second is selection feedback during drag. When a user drags a bar to reschedule it, the canvas needs to repaint fast enough to follow the cursor. If your repaint work includes more than the bar's old and new rectangles (say, dependency arrows that need to recompute), you'll see lag. The fix is to limit the drag repaint to the bar itself and recompute arrows on drop.

Key Takeaways

  • JavaFX's scene graph is the wrong tool for thousands of repetitive, non-interactive shapes like Gantt bars.
  • FlexGanttFX splits the problem: canvas for the heavy visual content, Nodes for the parts users touch.
  • The library is AGPL, which matters if you plan to ship a closed-source product on top of it.
  • You give up per-bar accessibility and CSS theming in exchange for smooth scrolling at scale.
  • Repaint logic becomes your responsibility once you start customizing the canvas.

Source

FlexGanttFX is Open Source — covered the release and feature set. This article added a minimal Gradle setup, a discussion of the scene-graph vs Canvas tradeoff, and the failure modes (DPI scaling, drag repaint) that the news write-up did not address.

Top comments (0)