Stepper Line Chart

The use of the gradient here is pretty subtle — it does piecewise blending between the two connected values (the two sleep states). Notice that when going from Awake to Light, the gradient skips the light blue of REM.

You can use a variable color encoding to achieve this. I’m sure the code could be simplified but here is my take. You’ll also need a fancier line renderer to have thick horizontal lines with rounded corners, with skinny vertical connectors…

Plot.plot({
  height: 400,
  marginTop: 0,
  style: "background: #292952; color: #ccc; padding: 1rem;",
  x: {
    inset: 10,
    tickSize: -6
  },
  y: {
    padding: 0,
    inset: 20,
    label: null,
    tickSize: 0,
    domain: ["awake", "rem", "light", "deep"]
  },
  marks: [
    () => htl.svg`<defs>
      <linearGradient id="awake-rem" gradientTransform="rotate(90)">
        <stop offset="0%" stop-color="#E24D70" />
        <stop offset="100%" stop-color="#8DC3F9" />
      </linearGradient>
      <linearGradient id="awake-light" gradientTransform="rotate(90)">
        <stop offset="0%" stop-color="#E24D70" />
        <stop offset="100%" stop-color="#538BF7" />
      </linearGradient>
      <linearGradient id="awake-deep" gradientTransform="rotate(90)">
        <stop offset="0%" stop-color="#E24D70" />
        <stop offset="100%" stop-color="#254AA1" />
      </linearGradient>
      <linearGradient id="light-rem" gradientTransform="rotate(90)">
        <stop offset="0%" stop-color="#8DC3F9" />
        <stop offset="100%" stop-color="#538BF7" />
      </linearGradient>
      <linearGradient id="deep-rem" gradientTransform="rotate(90)">
        <stop offset="0%" stop-color="#8DC3F9" />
        <stop offset="100%" stop-color="#254AA1" />
      </linearGradient>
      <linearGradient id="deep-light" gradientTransform="rotate(90)">
        <stop offset="0%" stop-color="#538BF7" />
        <stop offset="100%" stop-color="#254AA1" />
      </linearGradient>
    </defs>`,
    Plot.frame({anchor: "bottom"}),
    Plot.lineY(data, {
      x: "date",
      y: "state",
      z: null,
      curve: "step-after",
      stroke: (d, i, data) => `url(#${[d.state, data[i + 1]?.state].sort().join("-")})`,
      strokeWidth: 2,
      strokeLinecap: "round",
      strokeLinejoin: "round"
    })
  ]
})

https://observablehq.com/d/bd8b0bcccbca5527

3 Likes