📊 Gantt Chart Builder
Add tasks, set durations, define dependencies, mark milestones — visualize your project schedule.
No tasks yet. Add your first task above or load a sample project.
Inside the Gantt Chart: How Project Timelines Actually Work
Every software project, construction job, product launch, or event has one invisible enemy: time slipping through the cracks between tasks. The Gantt chart, invented by Henry Gantt around 1910, solved this with brutal simplicity — put tasks on a horizontal timeline, show their length as bars, and suddenly the whole project becomes a picture you can read at a glance. Over a century later, the underlying logic hasn't changed. What has changed is how we build, maintain, and interact with these charts.
The Anatomy of a Gantt Bar
A Gantt bar is deceptively simple: it starts at a horizontal position representing a start date, and extends rightward for a width proportional to its duration. But underneath that bar sits a surprisingly dense data structure. You need the task name, the start day (or date), the duration in work units, the color or category, and — critically — a list of predecessor task IDs that this task depends on.
That last field is where most lightweight Gantt tools fail. They let you draw pretty bars but skip dependency relationships, which are the actual reason you draw a Gantt chart in the first place. A dependency edge between Task A and Task B means B cannot start until A finishes (finish-to-start is the most common type). In more complex projects, you'll also encounter start-to-start (both begin simultaneously), finish-to-finish (both must complete at the same time), and lead/lag offsets. For the vast majority of real-world planning, finish-to-start edges cover 90% of what you need.
Rendering Dependencies Without a Graphics Library
Drawing curved arrows between tasks on a timeline is a classic SVG problem. The approach that works cleanly for Gantt charts is a cubic Bezier curve. Given the right edge of the predecessor bar at coordinates (x1, y1) and the left edge of the successor bar at (x2, y2), you define two control points: (midX, y1) and (midX, y2), where midX is the horizontal midpoint. This creates an S-curve or C-curve that flows naturally across rows without crossing neighboring bars unless the layout itself has overlaps.
The SVG marker element handles arrowheads. You define a triangle path inside a marker, attach it to the line with marker-end, and the browser automatically rotates it to match the path's terminal direction. The key detail is setting orient="auto" so the arrowhead tracks the curve's exit angle rather than pointing purely rightward.
Milestones Are a Different Beast
A milestone is a zero-duration event — a checkpoint, a deliverable date, a launch moment. Rendering it as a bar with width zero collapses it to a line, which is invisible. The traditional solution is a diamond shape rotated 45 degrees. In CSS this is a fixed-size square with transform: rotate(45deg), positioned on the timeline at the milestone's day. The diamond carries no "duration drag" handle because its definition is an instant in time, not a span.
Milestones are particularly important for stakeholder communication. In a Gantt chart sent to a client, they rarely care about the exact day backend integration starts — but they do care that "Design Sign-Off" happens before "Development Begins," and that "Beta Release" hits before a conference they've booked. Milestones make those commitments visible and unambiguous.
Drag-and-Drop in Pure JavaScript
Moving bars by dragging is a common expectation for any interactive Gantt tool. The implementation without a framework comes down to three mouse events: mousedown captures the initial click position and the task's original start day, mousemove computes how many days the cursor has traveled (pixels divided by cell width, rounded to the nearest integer), and mouseup commits the new position and cleans up state.
The snap-to-grid behavior — where bars jump in whole-day increments rather than sliding freely — comes from Math.round(deltaPixels / cellWidth). This single rounding call is what makes drag feel like you're working with real calendar units rather than pixel coordinates. Resize handles follow identical logic, only they modify duration rather than start position.
One implementation detail that catches developers off guard: mouse events during drag should be attached to document, not the bar element. If the user moves the mouse faster than the bar can re-render, the cursor leaves the element's bounding box and the drag silently breaks. Listening on document ensures you capture every movement regardless of where the pointer drifts.
The Critical Path and Why It Matters
Once you have tasks and dependency edges, the critical path is the longest chain of dependent tasks that determines the earliest possible project completion date. Any delay in a critical path task delays the whole project by exactly that amount. Tasks not on the critical path have "float" — they can slip by days or weeks without affecting the end date.
Computing the critical path requires two passes over the task graph. The forward pass propagates earliest start and finish times from the project start. The backward pass propagates latest allowable start and finish times from the project end. A task's float is the difference between its latest and earliest start times. Zero float means it's on the critical path.
Highlighting critical path tasks in red or a distinct color in the Gantt view gives project managers an instant read on where risk concentrates. This transforms the chart from a static schedule display into an active risk map.
Zoom and Scale: Making the Timeline Readable
A project spanning six months cannot be displayed at one pixel per day across a 1200px screen — that collapses everything into invisibility. Conversely, a two-week sprint displayed at 80px per day requires horizontal scrolling to see the full picture. The zoom level in a Gantt chart is a user preference tied to cognitive task: zoomed in for precision work, zoomed out for overview.
Programmatically, zoom is a single variable: the pixel width per day unit. Every bar's left position is (startDay - 1) * cellWidth, and its width is duration * cellWidth. The day header labels adapt — at wide cell widths you show full dates with month names, at narrow widths you show only significant dates (every 5th day, month starts). Weekend shading persists across zoom levels as a reference grid that helps readers intuitively locate "Monday through Friday" work weeks.
Where Gantt Charts Break Down
Gantt charts are excellent at showing what should happen and when. They are poor at showing why something changed, what the resource load is across team members, or how much work remains. A bar covers 10 days — but is that three hours of work spread thin, or 80 hours crammed into two weeks?
This is why professional project tools pair the Gantt view with resource histograms, burn-down charts, and issue trackers. The Gantt is the spatial-temporal backbone; other views add the contextual flesh. For personal planning, freelance project tracking, or small-team sprint mapping, a Gantt chart alone is usually exactly enough. For enterprise-scale portfolio management, treat it as one panel in a larger dashboard rather than the source of truth.
Building your own Gantt tool also teaches you something hard-won: the difference between a schedule and a plan. A schedule says when. A plan says how. The Gantt chart, for all its centuries of use, only ever answers the first question. The second one stays in your head — and that division of labor is, quietly, its greatest strength.