Skip to main content

Overview

This code snippet creates a professional bar chart visualization showing gold prices throughout 2024. It features a Y-axis with labeled tick marks, staggered spring animations for each bar, value labels inside bars, and responsive full-width layout.

Complete Code

import { useCurrentFrame, useVideoConfig, AbsoluteFill, spring } from "remotion";

export const MyAnimation = () => {
  const frame = useCurrentFrame();
  const { fps, height: videoHeight } = useVideoConfig();

  const TITLE = "Gold Price 2024";
  const UNIT = "USD per troy ounce";
  const COLOR_BAR = "#D4AF37";
  const COLOR_TEXT = "#ffffff";
  const COLOR_MUTED = "#888888";
  const COLOR_BG = "#0a0a0a";
  const COLOR_AXIS = "#333333";

  const PADDING = 50;
  const HEADER_HEIGHT = 70;
  const LABEL_HEIGHT = 32;
  const BAR_GAP = 8;
  const BAR_RADIUS = 4;
  const STAGGER_DELAY = 5;
  const BARS_START_FRAME = 10;

  const data = [
    { month: "Jan", price: 2039 },
    { month: "Feb", price: 2024 },
    { month: "Mar", price: 2160 },
    { month: "Apr", price: 2330 },
    { month: "May", price: 2327 },
    { month: "Jun", price: 2339 },
    { month: "Jul", price: 2426 },
    { month: "Aug", price: 2503 },
    { month: "Sep", price: 2634 },
    { month: "Oct", price: 2735 },
    { month: "Nov", price: 2672 },
    { month: "Dec", price: 2650 },
  ];

  const minPrice = 1900;
  const maxPrice = 2800;
  const priceRange = maxPrice - minPrice;
  const chartHeight = videoHeight - (PADDING * 2) - HEADER_HEIGHT - LABEL_HEIGHT;
  const yAxisSteps = [1900, 2100, 2300, 2500, 2700];

  const headerOpacity = spring({
    frame: frame,
    fps,
    config: { damping: 20, stiffness: 100 },
  });

  return (
    <AbsoluteFill
      style={{
        backgroundColor: COLOR_BG,
        padding: PADDING,
        display: "flex",
        flexDirection: "column",
        fontFamily: "Inter, sans-serif",
      }}
    >
      {/* Header */}
      <div style={{ height: HEADER_HEIGHT, opacity: headerOpacity, marginBottom: 10 }}>
        <div style={{ color: COLOR_TEXT, fontSize: 24, fontWeight: 600 }}>{TITLE}</div>
        <div style={{ color: COLOR_MUTED, fontSize: 14, marginTop: 4 }}>{UNIT}</div>
      </div>

      {/* Chart container */}
      <div style={{ display: "flex", alignItems: "flex-end", width: "100%", flex: 1 }}>
        {/* Y-Axis */}
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            justifyContent: "space-between",
            height: chartHeight,
            paddingRight: 12,
            marginBottom: LABEL_HEIGHT,
          }}
        >
          {yAxisSteps.slice().reverse().map((step) => (
            <div
              key={step}
              style={{
                color: COLOR_MUTED,
                fontSize: 12,
                textAlign: "right",
                minWidth: 40,
              }}
            >
              {step.toLocaleString()}
            </div>
          ))}
        </div>

        {/* Chart area */}
        <div
          style={{
            display: "flex",
            alignItems: "flex-end",
            gap: BAR_GAP,
            height: chartHeight,
            flex: 1,
            borderLeft: `1px solid ${COLOR_AXIS}`,
            borderBottom: `1px solid ${COLOR_AXIS}`,
            paddingLeft: 8,
          }}
        >
          {data.map((item, i) => {
            const delay = i * STAGGER_DELAY;
            const progress = spring({
              frame: frame - delay - BARS_START_FRAME,
              fps,
              config: { damping: 18, stiffness: 80 },
            });

            const normalizedHeight = ((item.price - minPrice) / priceRange) * chartHeight;
            const height = normalizedHeight * progress;

            return (
              <div
                key={i}
                style={{
                  textAlign: "center",
                  flex: 1,
                  height: "100%",
                  display: "flex",
                  flexDirection: "column",
                  justifyContent: "flex-end",
                }}
              >
                <div
                  style={{
                    width: "100%",
                    height,
                    backgroundColor: COLOR_BAR,
                    borderRadius: `${BAR_RADIUS}px ${BAR_RADIUS}px 0 0`,
                    display: "flex",
                    justifyContent: "center",
                    alignItems: "flex-start",
                    paddingTop: 6,
                    minHeight: height > 0 ? 4 : 0,
                  }}
                >
                  {height > 30 && (
                    <span
                      style={{
                        color: COLOR_BG,
                        fontSize: 11,
                        fontWeight: 600,
                        opacity: progress,
                      }}
                    >
                      {item.price.toLocaleString()}
                    </span>
                  )}
                </div>
                <div
                  style={{
                    color: COLOR_MUTED,
                    fontSize: 11,
                    marginTop: 8,
                    height: LABEL_HEIGHT - 8,
                  }}
                >
                  {item.month}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </AbsoluteFill>
  );
};

What This Snippet Demonstrates

Includes properly spaced Y-axis labels with thousands separators, making the data easy to read and interpret.
Each bar animates with a 5-frame delay, creating a pleasant cascading effect from left to right.
When bars are tall enough (over 30px), displays the exact value inside with proper opacity fade-in.
Uses flexbox and calculates chart dimensions based on video height, ensuring proper spacing and proportions.

Key Concepts

Data Normalization: Values are mapped from the data range (1900-2800) to pixel heights:
const normalizedHeight = ((item.price - minPrice) / priceRange) * chartHeight;
Spring Physics: Each bar uses spring() with custom damping and stiffness for organic, bouncy entrance animations. Conditional Rendering: Value labels only appear when bars are tall enough to accommodate them without cramping. Axis Design: The Y-axis uses evenly spaced steps (every $200) with right-aligned labels for clean visual alignment.

When to Use This Pattern

  • Financial data visualizations (stock prices, revenue, metrics)
  • Monthly or quarterly performance reports
  • Comparison charts for product analytics
  • Economic indicators and trends
  • Sales or growth presentations

Customization Tips

Replace the data array with your own values. Adjust minPrice and maxPrice to fit your range, and update yAxisSteps accordingly.
For cleaner charts with many bars, consider hiding value labels and only showing them on hover (in an interactive context) or for the highest/lowest values.

Build docs developers (and LLMs) love