# Chart Recipes

> Copy-paste chart recipes: a stat tile, a period switcher, a chart from a form, data from JSON, a budget donut, color palettes and a live line chart.

Source: https://www.motion-components.dev/docs/recipes/charts/

Everyday data patterns built with [`motion-chart`](https://www.motion-components.dev/docs/charts/motion-chart/), [`motion-pie`](https://www.motion-components.dev/docs/charts/motion-pie/) and [`motion-sparkline`](https://www.motion-components.dev/docs/charts/motion-sparkline/). Each snippet is complete: copy it into a page that loads the library.

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/+esm"></script>
```

## Stat tile

A number that counts up above its trend. `motion-counter` animates the value, `motion-sparkline` draws the trend.

```html
<div class="stat">
  <span class="stat-label">Revenue this month</span>
  <motion-counter to="48.2" decimals="1" prefix="€" suffix="K"></motion-counter>
  <motion-sparkline values="31, 35, 33, 38, 41, 39, 45, 48" area></motion-sparkline>
</div>

<style>
  .stat {
    display: grid;
    gap: 0.25rem;
    width: 16rem;
    font-size: 2rem;
    font-weight: 700;
  }

  .stat-label {
    font-size: 0.85rem;
    font-weight: 500;
    opacity: 0.7;
  }

  .stat motion-sparkline {
    --mc-sparkline-width: 100%;
    --mc-sparkline-height: 2.5rem;
    color: #2a78d6;
  }
</style>
```

## Week, month and year switcher

Buttons swap the data and the color, and the bars spring to the new values while the axis follows. Click quickly between periods: the bars turn around from wherever they are.

```html
<div class="period-chart">
  <div class="periods" role="group" aria-label="Period">
    <button aria-pressed="true" data-period="week">Week</button>
    <button aria-pressed="false" data-period="month">Month</button>
    <button aria-pressed="false" data-period="year">Year</button>
  </div>
  <motion-chart id="visits" label="Visits" labels="Mon, Tue, Wed, Thu, Fri, Sat, Sun" values="320, 410, 380, 460, 520, 290, 250"></motion-chart>
</div>

<script type="module">
  const chart = document.querySelector('#visits')
  const periods = {
    week: { color: '#2a78d6', labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], values: [320, 410, 380, 460, 520, 290, 250] },
    month: { color: '#1baf7a', labels: ['W1', 'W2', 'W3', 'W4'], values: [2400, 2900, 2650, 3100] },
    year: { color: '#8b5cf6', labels: ['Q1', 'Q2', 'Q3', 'Q4'], values: [9800, 11200, 10400, 12900] },
  }

  document.querySelectorAll('[data-period]').forEach((button) => {
    button.addEventListener('click', () => {
      document.querySelectorAll('[data-period]').forEach((b) => b.setAttribute('aria-pressed', b === button))
      const { color, labels, values } = periods[button.dataset.period]
      chart.style.setProperty('--mc-chart-1', color)
      chart.data = { labels, series: [{ name: 'Visits', values }] }
    })
  })
</script>

<style>
  .period-chart {
    display: grid;
    gap: 1rem;
    width: 100%;
  }

  .periods {
    display: flex;
    gap: 0.5rem;
  }
</style>
```

## Chart from a form

Type a number and the matching bar springs to it. The inputs are the data, so the chart never needs a separate update call.

```html
<div class="form-chart">
  <div class="inputs">
    <label>North <input type="number" value="8" data-region></label>
    <label>East <input type="number" value="14" data-region></label>
    <label>South <input type="number" value="11" data-region></label>
    <label>West <input type="number" value="19" data-region></label>
  </div>
  <motion-chart id="regions" label="Sales by region" labels="North, East, South, West" values="8, 14, 11, 19"></motion-chart>
</div>

<script type="module">
  const chart = document.querySelector('#regions')
  const inputs = [...document.querySelectorAll('[data-region]')]

  for (const input of inputs) {
    input.addEventListener('input', () => {
      chart.values = inputs.map((i) => Number(i.value) || 0).join(', ')
    })
  }
</script>

<style>
  .form-chart {
    display: grid;
    gap: 1rem;
    width: 100%;
  }

  .inputs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
  }

  .inputs label {
    display: grid;
    gap: 0.25rem;
    font-size: 0.85rem;
  }
</style>
```

## Load data from a JSON file

Fetch the data and set `data`. The chart waits with empty axes, then animates in once the data arrives. This demo loads `/data/sales.json` from this site.

```html
<motion-chart id="sales" type="line" label="Sales" format="currency:EUR"></motion-chart>

<script type="module">
  const chart = document.querySelector('#sales')
  const response = await fetch('/data/sales.json')
  chart.data = await response.json()
</script>

<style>
  #sales {
    width: 100%;
  }
</style>
```

`sales.json` has the same shape as `data`: `{ "labels": [...], "series": [{ "name": "Online", "values": [...] }] }`.

## Budget donut by month

A select changes the month, and the slices spring to their new sizes. The center shows the total, or the slice you hover.

```html
<div class="budget">
  <select id="month" aria-label="Month">
    <option value="jan">January</option>
    <option value="feb">February</option>
    <option value="mar">March</option>
  </select>
  <motion-pie id="budget" donut label="Monthly budget" format="currency:EUR" labels="Rent, Food, Transport, Fun" values="1450, 620, 380, 240"></motion-pie>
</div>

<script type="module">
  const pie = document.querySelector('#budget')
  const months = {
    jan: [1450, 620, 380, 240],
    feb: [1450, 540, 310, 420],
    mar: [1450, 700, 260, 180],
  }

  document.querySelector('#month').addEventListener('change', (event) => {
    pie.values = months[event.target.value].join(', ')
  })
</script>

<style>
  .budget {
    display: grid;
    justify-items: start;
    gap: 1rem;
    width: 100%;
  }

  #budget {
    width: 100%;
  }
</style>
```

## Color palettes

The colors are CSS custom properties, `--mc-chart-1` to `--mc-chart-8`. Set them on a wrapper, and every chart inside takes them. These buttons switch palettes for a bar chart and a donut at once.

```html
<div id="themed" class="themed-charts">
  <div class="palettes" role="group" aria-label="Color palette">
    <button aria-pressed="true" data-palette="default">Default</button>
    <button aria-pressed="false" data-palette="ocean">Ocean</button>
    <button aria-pressed="false" data-palette="sunset">Sunset</button>
    <button aria-pressed="false" data-palette="mono">Mono</button>
  </div>
  <motion-chart label="Sales by channel" labels="Q1, Q2, Q3, Q4" series="Web, Store, Partners" values="42, 55, 61, 70; 30, 38, 35, 41; 12, 18, 25, 31"></motion-chart>
  <motion-pie donut label="Sales by channel" labels="Web, Store, Partners" values="228, 144, 86"></motion-pie>
</div>

<script type="module">
  const charts = document.querySelector('#themed')
  const palettes = {
    default: [],
    ocean: ['#0369a1', '#0ea5e9', '#14b8a6'],
    sunset: ['#c2410c', '#f59e0b', '#be185d'],
    mono: ['#18181b', '#71717a', '#c4c4cc'],
  }

  document.querySelectorAll('[data-palette]').forEach((button) => {
    button.addEventListener('click', () => {
      document.querySelectorAll('[data-palette]').forEach((b) => b.setAttribute('aria-pressed', b === button))
      const colors = palettes[button.dataset.palette]
      for (let i = 1; i <= 3; i++) {
        if (colors[i - 1]) charts.style.setProperty(`--mc-chart-${i}`, colors[i - 1])
        else charts.style.removeProperty(`--mc-chart-${i}`)
      }
    })
  })
</script>

<style>
  .themed-charts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 1.5rem;
    width: 100%;
  }

  .palettes {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    grid-column: 1 / -1;
  }
</style>
```

Without JavaScript, set the palette in CSS: `.my-charts { --mc-chart-1: #0369a1; --mc-chart-2: #0ea5e9; }`. The default palette is chosen so the colors stay apart for readers with color blindness. When you pick your own, make the colors differ in lightness too, as the Mono palette does.

## Live line chart

A new point every 1.5 seconds. The oldest point drops off, and the whole line springs into its new shape.

```html
<motion-chart id="live-cpu" type="line" label="CPU load" format="percent" min="0" max="100" values="42, 48, 45, 51, 49, 55, 53, 58"></motion-chart>

<script type="module">
  const chart = document.querySelector('#live-cpu')

  setInterval(() => {
    const values = chart.values.split(',').map(Number)
    const next = Math.min(100, Math.max(0, values.at(-1) + Math.round(Math.random() * 14 - 7)))
    chart.values = [...values.slice(1), next].join(', ')
  }, 1500)
</script>

<style>
  #live-cpu {
    width: 100%;
  }
</style>
```
