Back to Dictionary
Data & Content Visualization
●●○Customize
Context dependent

Racing Bar Chart

A horizontal bar chart representing timeseries data where the bars continually sort themselves on the Y-axis as their X-values increase/decrease over time.

Use

Visualizing ranking changes over time (e.g., most popular languages per year)

Avoid

Static data sets

Safer Alternative

No safer default listed.

Risk Level

Low risk

Live Demo

React95
Vue72
Svelte48
Angular36

What It Is

A horizontal bar chart representing timeseries data where the bars continually sort themselves on the Y-axis as their X-values increase/decrease over time.

When to Use

  • Visualizing ranking changes over time (e.g., most popular languages per year)
  • Gamified leaderboards

When NOT to Use

  • Static data sets

Configuration Tips

  • 01Use FLIP (First, Last, Invert, Play) animation techniques or Framer Motion layout animations to handle the Y-axis sorting smoothly

You've Seen It In

DataIsBeautiful RedditFlourish

AI Implementation Prompts

Move from a fast scaffold to production details, then tune timing and edge states.

Create a racing bar chart where items physically swap vertical positions as their values overtake each other.

Reference Implementation

The same implementation approach used by the live preview, kept compact enough to inspect and adapt.

"use client";
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';

const initialData = [
    { id: '1', label: 'React', color: 'bg-sky-500' },
    { id: '2', label: 'Vue', color: 'bg-emerald-500' },
    { id: '3', label: 'Svelte', color: 'bg-orange-500' },
    { id: '4', label: 'Angular', color: 'bg-red-600' }
];

const frames = [
    [
        { id: '1', width: 80, score: 95 },
        { id: '2', width: 60, score: 72 },
        { id: '3', width: 40, score: 48 },
        { id: '4', width: 30, score: 36 }
    ],
    [
        { id: '1', width: 85, score: 98 },
        { id: '2', width: 75, score: 88 },
        { id: '4', width: 60, score: 70 },
        { id: '3', width: 45, score: 52 }
    ],
    [
        { id: '2', width: 95, score: 110 },
        { id: '1', width: 90, score: 105 },
        { id: '3', width: 70, score: 80 },
        { id: '4', width: 65, score: 75 }
    ],
    [
        { id: '2', width: 100, score: 120 },
        { id: '3', width: 90, score: 108 },
        { id: '1', width: 85, score: 106 },
        { id: '4', width: 70, score: 82 }
    ],
];

export function RacingBarChartDemo({ isPlaying = false }: { isPlaying?: boolean }) {
    const [frameIndex, setFrameIndex] = useState(0);

    useEffect(() => {
        if (!isPlaying) {
            setFrameIndex(0);
            return;
        }

        const interval = setInterval(() => {
            setFrameIndex((prev) => (prev + 1) % frames.length);
        }, 1500);

        return () => clearInterval(interval);
    }, [isPlaying]);

    const currentFrameState = frames[frameIndex].map((frameItem, i) => {
        const baseItem = initialData.find(d => d.id === frameItem.id)!;
        return {
            ...baseItem,
            width: frameItem.width,
            score: frameItem.score,
            rank: i "text-stone-400 italic">// Target Y position based on current sorted array index
        };
    });

    return (
        <div className="flex h-64 w-full items-center justify-center overflow-hidden rounded-2xl bg-white p-4 shadow-sm ring-1 ring-stone-200">
            <div className="w-full max-w-sm h-48 relative border-l border-b border-stone-200 pb-2 pl-2">
                <AnimatePresence>
                    {currentFrameState.map((item) => (
                        <motion.div
                            key={item.id}
                            layout
                            initial={false}
                            animate={{
                                y: item.rank * 42, "text-stone-400 italic">// spacing
                                width: `${item.width}%`
                            }}
                            transition={{
                                type: "spring",
                                stiffness: 80,
                                damping: 20
                            }}
                            className={`absolute left-0 h-8 rounded-r-md flex items-center justify-between px-3 min-w-[100px] shadow-sm ${item.color}`}
                        >
                            <span className="text-white text-xs font-bold tracking-wider">{item.label}</span>
                            <span className="text-white/90 text-[10px] font-mono font-bold tabular-nums">{item.score}</span>
                        </motion.div>
                    ))}
                </AnimatePresence>
            </div>
        </div>
    );
}