Render interactive line chart with brushX selection

by Anders Kaseorg

HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>brushX drops a value when dragged to an edge</title>
</head>
<body>
<p>Drag the selection all the way to the left or right.</p>
<div id="chart"></div>
<p>Range: <output id="range"></output></p>
</body>
</html>

JavaScript

import { defineChart, dot } from 'https://esm.sh/@tanstack/charts@1.0.0';
import { mountChart } from 'https://esm.sh/@tanstack/charts@1.0.0/dom';
import { brushX } from 'https://esm.sh/@tanstack/charts@1.0.0/interaction/brush';
import { controlledSignal } from 'https://esm.sh/@tanstack/charts@1.0.0/interaction/signal';
import { scaleLinear, scaleUtc } from 'https://esm.sh/d3-scale@4';

const day = 86_400_000;
const days = [1, 2, 3, 4, 5].map((d) => new Date(Date.UTC(2024, 0, d)));
const rows = days.map((date, value) => ({ date, value }));
const domain = [
  new Date(days[0].getTime() - day / 2),
  new Date(days[4].getTime() + day / 2),
];
const format = (date) => date.toISOString().slice(0, 10);

let range = { start: days[1], end: days[3] };
const options = () => ({
  definition: defineChart({
    marks: [dot(rows, { x: 'date', y: 'value' })],
    scales: {
      x: { scale: scaleUtc().domain(domain) },
      y: { scale: scaleLinear },
    },
    controls: [
      brushX({
        range: controlledSignal(range, (next) => {
          range = next;
          render();
        }),
        values: days,
        format,
      }),
    ],
  }),
  width: 750,
  height: 160,
  ariaLabel: 'Daily values',
});
const host = mountChart(document.getElementById('chart'), options());
function render() {
  host.update(options());
  document.getElementById('range').value =
    `${format(range.start)} to ${format(range.end)}`;
}
render();