# Sparkline

A small line beside a figure that shows how it moved: rising, falling, steady or spiky. The figure carries the meaning.

Block: `smbk-sparkline`. Status: experimental. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `area` | on / off | off | The soft fill under the line. |

Parts: `smbk-sparkline__plot`, `smbk-sparkline__svg`, `smbk-sparkline__area`, `smbk-sparkline__line`, `smbk-sparkline__dot`

## Markup

```html
<span
  data-entrance="load"
  class="smbk-sparkline smbk-sparkline--area"
  aria-hidden="true"
><span class="smbk-sparkline__plot"><svg class="smbk-sparkline__svg" viewBox="0 0 100 32" preserveAspectRatio="none" focusable="false" aria-hidden="true"><path class="smbk-sparkline__area" d="M0 24 C1.15 23.68 2.3 23.05 3.45 23.05 C4.6 23.05 5.75 24 6.9 24 C8.05 24 9.2 22.53 10.34 22.11 C11.49 21.68 12.64 21.16 13.79 21.16 C14.94 21.16 16.09 22.11 17.24 22.11 C18.39 22.11 19.54 20.21 20.69 20.21 C21.84 20.21 22.99 21.16 24.14 21.16 C25.29 21.16 26.44 19.68 27.59 19.26 C28.74 18.84 29.89 18.32 31.03 18.32 C32.18 18.32 33.33 19.26 34.48 19.26 C35.63 19.26 36.78 17.37 37.93 17.37 C39.08 17.37 40.23 18.32 41.38 18.32 C42.53 18.32 43.68 16.84 44.83 16.42 C45.98 16 47.13 15.47 48.28 15.47 C49.43 15.47 50.57 16.42 51.72 16.42 C52.87 16.42 54.02 14.53 55.17 14.53 C56.32 14.53 57.47 15.47 58.62 15.47 C59.77 15.47 60.92 14 62.07 13.58 C63.22 13.16 64.37 12.63 65.52 12.63 C66.67 12.63 67.82 13.58 68.97 13.58 C70.11 13.58 71.26 11.68 72.41 11.68 C73.56 11.68 74.71 12.63 75.86 12.63 C77.01 12.63 78.16 11.16 79.31 10.74 C80.46 10.32 81.61 9.79 82.76 9.79 C83.91 9.79 85.06 10.74 86.21 10.74 C87.36 10.74 88.51 9.26 89.66 8.84 C90.8 8.42 91.95 7.89 93.1 7.89 C94.25 7.89 95.4 8.84 96.55 8.84 C97.7 8.84 98.85 6.95 100 6 L100 32 L0 32 Z"></path><path class="smbk-sparkline__line" d="M0 24 C1.15 23.68 2.3 23.05 3.45 23.05 C4.6 23.05 5.75 24 6.9 24 C8.05 24 9.2 22.53 10.34 22.11 C11.49 21.68 12.64 21.16 13.79 21.16 C14.94 21.16 16.09 22.11 17.24 22.11 C18.39 22.11 19.54 20.21 20.69 20.21 C21.84 20.21 22.99 21.16 24.14 21.16 C25.29 21.16 26.44 19.68 27.59 19.26 C28.74 18.84 29.89 18.32 31.03 18.32 C32.18 18.32 33.33 19.26 34.48 19.26 C35.63 19.26 36.78 17.37 37.93 17.37 C39.08 17.37 40.23 18.32 41.38 18.32 C42.53 18.32 43.68 16.84 44.83 16.42 C45.98 16 47.13 15.47 48.28 15.47 C49.43 15.47 50.57 16.42 51.72 16.42 C52.87 16.42 54.02 14.53 55.17 14.53 C56.32 14.53 57.47 15.47 58.62 15.47 C59.77 15.47 60.92 14 62.07 13.58 C63.22 13.16 64.37 12.63 65.52 12.63 C66.67 12.63 67.82 13.58 68.97 13.58 C70.11 13.58 71.26 11.68 72.41 11.68 C73.56 11.68 74.71 12.63 75.86 12.63 C77.01 12.63 78.16 11.16 79.31 10.74 C80.46 10.32 81.61 9.79 82.76 9.79 C83.91 9.79 85.06 10.74 86.21 10.74 C87.36 10.74 88.51 9.26 89.66 8.84 C90.8 8.42 91.95 7.89 93.1 7.89 C94.25 7.89 95.4 8.84 96.55 8.84 C97.7 8.84 98.85 6.95 100 6"></path></svg><span class="smbk-sparkline__dot" style="left:100%;top:18.75%"></span></span></span>
```

## React

```tsx
import { Sparkline } from './design-system/components/sparkline/sparkline.js';

<Sparkline values={[12, 13, 12, 14, 15, 14, 16, 15, 17, 18, 17, 19, 18, 20, 21, 20, 22, 21, 23, 24, 23, 25, 24, 26, 27, 26, 28, 29, 28, 31]} area />
```

## Stylesheet

```css
/* smbk-sparkline — Sparkline. Figma "Sparkline" 7580:152.
 * A small line in the series color, drawn from its values with no axes, ending in a dot on the last value;
 * with the area flag, a soft fill of the same color under it. It fills the width it is given and is as tall as
 * a small control. The SVG stretches to fit; the line keeps its width and the dot stays round, because the dot
 * is drawn outside the SVG. The plot holds the SVG and the dot, the same size as the block. The first time it
 * comes into view it is uncovered from left to right; nothing else moves. */

.smbk-sparkline {
  position: relative;
  display: block;
  width: 100%;
  min-width: 0;
  height: var(--smbk-size-32);
}

/* The plot is the block's own box again, so its entrance clip never clips the block: Chromium's intersection
   observer counts a clip on the element it watches, and a block clipped to nothing never came into view. */
.smbk-sparkline__plot {
  position: relative;
  display: block;
  height: 100%;
}

.smbk-sparkline__svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.smbk-sparkline__line {
  fill: none;
  stroke: var(--smbk-color-data-series);
  stroke-width: var(--smbk-border-width-emphasis);
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.smbk-sparkline__area {
  fill: var(--smbk-color-data-series);
  fill-opacity: var(--smbk-opacity-ghost);
  stroke: none;
}

/* The end dot sits on the last value, centred on it, half past the right edge as Figma draws it. */
.smbk-sparkline__dot {
  position: absolute;
  width: var(--smbk-size-6);
  height: var(--smbk-size-6);
  border-radius: var(--smbk-radius-full);
  background: var(--smbk-color-data-series);
  translate: -50% -50%;
}

/* ---- motion: the entrance ----
   The wrapper marks the sparkline `data-entrance` the first time it comes into view (simbolik/components/entrance.ts):
   waiting while it is out of view, hidden; playing once it is seen, the line, its area and the end dot uncovered
   from left to right with the chart entrance purpose, as one edge moving across. The clip is on the plot, never on
   the block, which the entrance watches. The edges run a little past the sparkline, so the dot half past its end
   is never cut once uncovered. Without the mark (markup written by hand,
   the rendered pictures, after the entrance) it is simply drawn, and under reduced motion nothing moves. Only the
   clip changes, so nothing shifts. */

@media screen and (prefers-reduced-motion: no-preference) {
  .smbk-sparkline[data-entrance="waiting"] .smbk-sparkline__plot {
    clip-path: inset(-50% calc(100% + var(--smbk-size-6)) -50% calc(-1 * var(--smbk-size-6)));
  }

  .smbk-sparkline:is([data-entrance="load"], [data-entrance="playing"]) .smbk-sparkline__plot {
    animation: smbk-sparkline-reveal var(--smbk-motion-chart-entrance-reveal-duration) var(--smbk-motion-chart-entrance-easing) backwards;
  }
}

@keyframes smbk-sparkline-reveal {
  from {
    clip-path: inset(-50% calc(100% + var(--smbk-size-6)) -50% calc(-1 * var(--smbk-size-6)));
  }

  to {
    clip-path: inset(-50% calc(-1 * var(--smbk-size-6)) -50% calc(-1 * var(--smbk-size-6)));
  }
}

/* ---- high contrast (forced colors) ---- */
/* The browser would drop the line's color; the line and the dot take the text color instead, and the area goes. */

@media (forced-colors: active) {
  .smbk-sparkline__line {
    stroke: CanvasText;
  }

  .smbk-sparkline__area {
    display: none;
  }

  .smbk-sparkline__dot {
    forced-color-adjust: none;
    background: CanvasText;
  }
}
```

## Tokens it reads

```json
{
  "size": {
    "6": {
      "$type": "dimension",
      "$value": "6px",
      "$description": "Raw size step, 6 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "32": {
      "$type": "dimension",
      "$value": "32px",
      "$description": "Raw size step, 32 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    }
  },
  "color": {
    "data": {
      "series": {
        "$type": "color",
        "$value": "{{palette.light.series}}",
        "$description": "The single series colour; the same as categorical 1, so one series and the first of many agree. Light blue/600, dark blue/400. Marks reach 3:1 on the plot background; never colour alone: direct labels, markers or patterns too."
      }
    }
  },
  "border-width": {
    "emphasis": {
      "$type": "dimension",
      "$value": "{{border-width.2}}",
      "$description": "Two-pixel edges for emphasis: a chosen card, a focus ring."
    }
  },
  "opacity": {
    "ghost": {
      "$type": "number",
      "$value": 0.1,
      "$description": "The faintest see-through level: a hint of a fill. 10%."
    }
  },
  "radius": {
    "full": {
      "$type": "dimension",
      "$value": "{{radius.9999}}",
      "$description": "Capsule: pills, chips, switches."
    }
  },
  "motion": {
    "chart-entrance": {
      "reveal-duration": {
        "$type": "duration",
        "$value": "{{duration.1000}}",
        "$description": "How long a line takes to be uncovered across the plot, or a ring to sweep round."
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.steep-out}}"
      }
    }
  }
}
```

## Purpose

Shows at a glance how a figure has moved: rising, falling, steady or spiky. It is the smallest chart, a line with no axes, drawn beside the figure it belongs to, which carries the meaning. It differs from a line chart, which has axes, a legend and values a reader can read off.

## Anatomy

- The block, as wide as the space it is given and as tall as a small control.
- The plot part, as big as the block, holding the svg and dot parts.
- The svg part, holding the area part, a soft fill of the line's color down to the foot, shown with the area flag, and the line part, through the values from the oldest at the left to the newest at the right.
- The dot part, on the last value, which stays round however wide the sparkline is.

## When to use

- Beside a figure on a card or in a table row, to show its trend over the last days or weeks.
- Several figures side by side, each with its own sparkline on one shared scale.

## When not to use

- When the reader needs to read values off the line, compare series or see dates. Use a [line chart](line-chart.md).
- When the figure has no history worth showing. Leave it out rather than draw a flat line.
- As the only place a trend is told. Say it in words too, such as "up 12% on last week".

## Behavior

The wrapper draws the line from the values it is given, as a smooth curve that never swings past a value, scaled from the lowest to the highest value unless a shared scale is given. Values that are all the same draw a level line halfway up. The line keeps its width and the dot stays round as the sparkline stretches. The first time it comes into view the line is uncovered from left to right, and later changes to the values land at once. Under reduced motion, and in markup written by hand, it appears at once. Nothing takes focus.

## Accessibility
### What it does for you

- The figure beside it carries the meaning, so the sparkline is hidden from screen readers unless it is given a summary, such as "Visits rising over 30 days", which it then carries as an image's name.
- Nothing about it takes focus.
- In high contrast mode the line and the dot take the text color and the area is left out.

### What you must do

- Say the trend in words beside the figure, so a reader who cannot see the line still has it.

### How to test

- With a screen reader, a sparkline with no summary is skipped, and one with a summary is read as an image by those words.
- Tab through the page: focus never lands on the sparkline.
- In a forced-color mode, the line and the dot show in the text color.

## Usage rules

### Do

- Put the figure first and the sparkline beside or under it.
- Give sparklines side by side one shared scale, so their slopes compare.
- Keep the area for a single sparkline that stands on its own; side by side, the lines alone read cleaner.

### Don't

- Leave the sparkline to tell the trend alone.
- Draw a sparkline from two or three values; a figure and its change in words say it better.
