Chartist.js Animated Bar Race — Growing Bars & update() Snippet

Chartist.js Animated Bar Race · Charts · Plain HTML, CSS & JS · Live preview

What's included

Features

Bars grow in from zero on load
x2 animates from the axis baseline (x1) out to its real value.
Baseline read from data, not hardcoded
Using data.x1 keeps the animation correct regardless of axis offset or padding.
Built-in update-transition
chart.update() re-fires draw automatically, replaying the same growth animation with no extra code.
Bars animate between old and new length
A shuffle grows or shrinks each bar relative to its previous value, not from zero every time.
Horizontal bar-race layout
Labels on the left, bars extending right — the standard "race" visual convention.
Randomized shuffle button
Demonstrates the animation repeatedly with fresh data on demand.
Eased growth curve
Chartist.Svg.Easing.easeOutQuint gives bars a natural deceleration as they land.
Fixed value axis range
high/low keep the scale stable across shuffles so bar lengths stay comparable.

About this UI Snippet

Chartist.js Animated Bar Race — Growing Bars and the update() Transition

Screenshot of the Chartist.js Animated Bar Race snippet rendered live

The line and donut snippets in this library animate a static chart once, on load. This one adds a second dimension: what happens when the *data itself* changes after the chart already exists, via chart.update() — and it turns out Chartist has that transition built in almost for free, as long as your draw handler is written the right way.

What a horizontal bar actually is, geometrically

For a Chartist.BarChart with horizontalBars: true, each bar's draw event carries data.x1 and data.x2 — the bar's start and end coordinates along the horizontal value axis (plus y1/y2, which stay constant for a given row since the bar's vertical position doesn't change). x1 is always the axis baseline; x2 is where the bar's value places its far end. A bar's on-screen length is nothing more than x2 - x1.

Growing from zero: animate x2 from x1

js data.element.animate({ x2: { dur: 800, from: data.x1, to: data.x2, easing: ... } });

Setting the animation's from value to data.x1 — the bar's own baseline, not a hardcoded zero — makes the bar start with zero visible length (its start and end coincide) and grow out to its real length. Using data.x1 rather than a literal 0 matters because the baseline position depends on axisY's offset and the chart's padding; hardcoding 0 would grow every bar from the literal left edge of the SVG instead of from the actual axis line, which is wrong the moment the axis has any offset at all.

Why chart.update() replays the animation without extra code

This is the part that looks like magic until you see the mechanism: Chartist doesn't destroy and recreate the SVG on update(). It reuses the existing DOM elements where the shape hasn't structurally changed, and re-runs its internal draw pass, updating each bar's x2 coordinate and — critically — firing the `draw` event again for every element, exactly as it did on the first render. Because this snippet's chart.on('draw', ...) handler is attached once, outside of any specific render, it doesn't need to be re-attached or re-triggered manually after update() — Chartist calls it again on its own.

The one behavioral difference: on the very first render, data.x1 === the baseline and every bar visibly grows from zero. On a subsequent update(), the bar element already has a real x2 from before the shuffle — but the draw handler still reads whatever data.x1/data.x2 the *new* draw pass computes, and animates between them the same way. In practice this means each shuffle animates every bar from its previous length directly to its new length, which is the actual "race" feeling: bars don't reset to zero and regrow each time, they visibly grow or shrink relative to where they just were.

Why horizontalBars: true suits a "race" layout

Vertical bars racing would need height changes read off y1/y2 instead, but the visual metaphor of a "bar race" — labels on the left, bars extending rightward by value — is a horizontal-bar convention. axisY: { offset: 90 } reserves enough left-hand space for the product name labels to render without truncation.

Build with AI

Build, Understand, Optimize, and Extend It With AI

This snippet is a good one to have an AI trace through Chartist's update lifecycle rather than just its animation code. Paste it into an assistant like Claude and ask it to explain exactly why calling chart.update() with new data replays the same growth animation without the draw event listener being re-attached anywhere — the answer should describe Chartist re-running its internal draw pass and reusing existing DOM elements rather than tearing the chart down. Then ask what would go wrong if data.x1 were replaced with a hardcoded 0 in the animate() call, and under what axis configuration that mistake would become visible (any nonzero axisY offset). To extend it: ask for a version that sorts bars by value and animates their vertical reordering on each shuffle (a true "race" reordering effect, which Chartist doesn't do automatically), a version that polls a real API on an interval instead of a manual shuffle button, or a version using vertical bars where the growth is read from y1/y2 instead of x1/x2.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a horizontal "bar race" chart using Chartist.js (v1.3.0, from a CDN, both the JS and its index.css) in plain HTML, CSS, and JavaScript, where bars grow in on load and re-animate whenever the data changes.

Requirements:
- A horizontal bar chart for 5 labeled categories, created with new Chartist.BarChart('#selector', data, { horizontalBars: true, axisY: { offset: 90 }, high: 100, low: 0, ... }), styled to sit in a dark card panel with labels rendered fully (no truncation) via sufficient axisY offset.
- Attach chart.on('draw', function(data) { if (data.type === 'bar') { ... } }). Inside it, animate the bar's x2 coordinate from data.x1 (the real axis baseline, read from the event data, NOT a hardcoded 0) to data.x2 (the bar's real end position), over roughly 800ms with an eased curve like Chartist.Svg.Easing.easeOutQuint. Add a comment explaining why data.x1 must be used instead of a literal 0 — because the baseline's actual coordinate depends on axis offset and chart padding.
- Add a "Shuffle data" button that generates new random values for the same 5 categories and calls chart.update() with them. Do NOT write any new animation code for this button — rely entirely on the fact that update() re-fires the same draw event handler already attached, and add a comment explaining that Chartist reuses existing bar elements on update() rather than recreating them, so each shuffle animates every bar from its previous rendered length to its new one rather than resetting to zero each time.
- Keep the value axis range fixed (high/low in options) so bar lengths stay visually comparable across shuffles instead of auto-rescaling to each new dataset's max.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="cbr-stage">
  <div class="cbr-head">
    <span class="cbr-tag">Chartist.js · draw event · bar</span>
    <h2>Top Products by Revenue</h2>
    <p>Bars grow in from zero on load, and chart.update() replays the same animation with a fresh shuffle.</p>
  </div>
  <div class="cbr-chart" id="cbrChart"></div>
  <button class="cbr-btn" id="cbrShuffle">Shuffle data</button>
</div>

Step by step

How to Use

  1. 1
    Add both Chartist CDN filesThe JS bundle and index.css.
  2. 2
    Create a horizontal BarChartnew Chartist.BarChart(selector, data, { horizontalBars: true, ... }).
  3. 3
    Animate x2 from x1 on drawdata.type === "bar" gives x1 (baseline) and x2 (real end) to animate between.
  4. 4
    Use data.x1, not a literal 0, as the start valueThe baseline position depends on axis offset and padding, so it must be read, not assumed.
  5. 5
    Call chart.update() with new dataNo extra animation code is needed — the same draw handler fires again automatically.
  6. 6
    Wire a shuffle buttonGenerates fresh random values and calls update(), demonstrating the built-in transition.

Real-world uses

Common Use Cases

Leaderboards and rankings
A product, team, or player ranking chart that visibly reacts to new data.
Live dashboards
Poll an API and call chart.update() on an interval to get automatic re-animation for free.
Teaching Chartist's update lifecycle
A clear demonstration that update() reuses draw handlers rather than requiring new ones.
Sales and revenue snapshots
A top-N chart that feels alive each time the underlying numbers refresh.

Got questions?

Frequently Asked Questions

data.x1 is the bar's actual axis baseline in the chart's current coordinate space, which shifts depending on axisY offset and chart padding. Hardcoding 0 would grow bars from the literal edge of the SVG rather than from the real axis line, which looks wrong as soon as there's any axis offset.

No. The draw event handler attached once with chart.on("draw", ...) fires again automatically every time update() re-runs Chartist's internal render pass, so the exact same x1-to-x2 growth animation replays without any extra setup.

Chartist reuses existing DOM elements on update() rather than destroying and recreating them. The draw handler reads whatever x1/x2 the new render pass computes and animates between those, so a bar that already has a rendered length animates from that length to its new one, not from zero.

For a horizontal bar chart, x1/x2 represent the bar's start and end along the value axis (what changes with the data), while y1/y2 represent its position along the category axis (which stays fixed for a given row). A vertical bar chart would swap which pair encodes the growing length.

The bar-race visual convention is labels on the left with bars extending rightward by value, which is what horizontalBars: true produces. It also makes long product names easier to read than rotated axis labels under vertical bars would.

Auto-scaling recalculates the axis range based on the current data's max value, which would make the same underlying value draw a different bar length on every shuffle. A fixed high/low keeps the scale stable so bar lengths stay visually comparable across updates.