v1.6.0

<bmx-chart>

Twenty-two kinds of chart, drawn by the library with no charting package: column, bar, line, spline, step, area, scatter, bubble, pie, doughnut, radar, polar area, heatmap, treemap, sankey, gauge, funnel, candlestick, OHLC, waterfall, histogram and box plot - stacked or to a hundred per cent, combined (columns with a line on a second axis), on category, value, time or logarithmic axes.

27 properties · 4 events · 10 methods · 10 parts

Example

Every value can be reached. Focus the chart and use the arrow keys (Up and Down change series), Home and End; Enter chooses a value. Click the legend to hide a series, or Show the data for the table.
Show markup
<div class="row" style="gap: 0.75rem; flex-wrap: wrap; align-items: end">
  <label style="display: grid; gap: 0.25rem">
    <span>Kind of chart</span>
    <select id="ex-ch-type">
      <option value="column">Columns</option>
      <option value="column-stack">Columns, stacked</option>
      <option value="column-percent">Columns, 100%</option>
      <option value="combo">Columns and a line on two axes</option>
      <option value="bar">Bars</option>
      <option value="line">Lines</option>
      <option value="spline">Smooth lines</option>
      <option value="step">Steps</option>
      <option value="area">Areas, stacked</option>
      <option value="time">Time axis, 20,000 points (drag to zoom)</option>
      <option value="scatter">Scatter</option>
      <option value="bubble">Bubbles</option>
      <option value="pie">Pie</option>
      <option value="doughnut">Doughnut</option>
      <option value="radar">Radar</option>
      <option value="polar">Polar area</option>
      <option value="heatmap">Heatmap</option>
      <option value="treemap">Treemap (click to drill in)</option>
      <option value="sankey">Sankey</option>
      <option value="gauge">Gauge</option>
      <option value="funnel">Funnel</option>
      <option value="candlestick">Candlestick</option>
      <option value="ohlc">OHLC</option>
      <option value="waterfall">Waterfall</option>
      <option value="histogram">Histogram</option>
      <option value="boxplot">Box plot</option>
    </select>
  </label>
  <label style="display: flex; gap: 0.375rem; align-items: center"><input type="checkbox" id="ex-ch-patterns" /> Patterns as well as colours</label>
  <label style="display: flex; gap: 0.375rem; align-items: center"><input type="checkbox" id="ex-ch-labels" /> Values on the marks</label>
</div>

<bmx-chart id="ex-ch" lang="en-GB" label="Revenue by quarter" description="Two regions, in pounds" toolbar zoom format="currency:GBP" style="margin-block-start: 1rem; --bmx-chart-height: 20rem"></bmx-chart>

<div class="row" style="margin-block-start: 1rem">
  <span class="note" id="ex-ch-out">
    <strong>Every value can be reached.</strong> Focus the chart and use the arrow keys (Up and Down change series), Home
    and End; Enter chooses a value. Click the legend to hide a series, or Show the data for the table.
  </span>
</div>

<script type="module">
  await customElements.whenDefined('bmx-chart');

  const chart = document.getElementById('ex-ch');
  const out = document.getElementById('ex-ch-out');
  const quarters = ['Q1 2025', 'Q2 2025', 'Q3 2025', 'Q4 2025', 'Q1 2026', 'Q2 2026'];
  const regions = { categories: quarters, series: [{ name: 'North', values: [182, 205, 231, 266, 248, 291] }, { name: 'South', values: [140, 152, 149, 188, 201, 214] }] };

  let seed = 7;
  const random = () => (seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648;
  const day = 86_400_000;
  const start = Date.UTC(2026, 0, 1);
  let level = 100;
  const prices = Array.from({ length: 40 }, (_, i) => {
    const open = level;
    const close = open + (random() - 0.48) * 6;
    level = close;
    return { x: new Date(start + i * day).toISOString().slice(0, 10), open, close, high: Math.max(open, close) + random() * 3, low: Math.min(open, close) - random() * 3 };
  });
  let walk = 0;
  const readings = Array.from({ length: 20000 }, (_, i) => ({ x: new Date(start + i * 60_000 * 15), y: (walk += random() - 0.5) }));

  const kinds = {
    column: { data: regions },
    'column-stack': { type: 'column', stack: 'normal', data: regions },
    'column-percent': { type: 'column', stack: 'percent', data: regions },
    combo: { type: 'column', data: { categories: quarters, series: [{ name: 'Orders', values: [410, 452, 498, 560, 530, 610] }, { name: 'Margin', type: 'line', axis: 'y2', values: [0.21, 0.23, 0.22, 0.26, 0.25, 0.27], format: { style: 'percent' } }] }, format: '', y2Axis: { title: 'Margin' } },
    bar: { data: regions },
    line: { data: regions, annotations: [{ value: 250, label: 'Target', tone: 'success' }] },
    spline: { data: regions },
    step: { data: regions },
    area: { type: 'area', stack: 'normal', data: regions },
    time: { type: 'line', data: { series: [{ name: 'Sensor', points: readings }] }, format: 'decimal:2', label: 'A sensor every 15 minutes', description: 'Drag across the chart to zoom; Ctrl and the wheel too' },
    scatter: { data: { series: [{ name: 'Orders', points: Array.from({ length: 120 }, () => ({ x: Math.round(random() * 100), y: Math.round(random() * 400) })) }] }, format: '' },
    bubble: { data: { series: [{ name: 'Cities', points: [{ x: 12, y: 40, size: 9, label: 'Leeds' }, { x: 30, y: 25, size: 22, label: 'Manchester' }, { x: 45, y: 60, size: 90, label: 'London' }, { x: 20, y: 70, size: 6, label: 'York' }] }] }, format: '' },
    pie: { data: regions },
    doughnut: { data: regions },
    radar: { data: { categories: ['Speed', 'Range', 'Comfort', 'Price', 'Safety'], series: [{ name: 'Model A', values: [8, 6, 7, 5, 9] }, { name: 'Model B', values: [6, 9, 6, 7, 8] }] }, format: '' },
    polar: { data: regions },
    heatmap: { data: { categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], series: ['Morning', 'Afternoon', 'Evening'].map(name => ({ name, values: Array.from({ length: 7 }, () => Math.round(random() * 100)) })) }, format: '' },
    treemap: { data: { tree: [{ name: 'Europe', children: [{ name: 'UK', value: 340 }, { name: 'France', value: 220 }, { name: 'Germany', value: 260 }] }, { name: 'Asia', children: [{ name: 'India', value: 410 }, { name: 'Japan', value: 180 }, { name: 'Singapore', value: 90 }] }, { name: 'Americas', children: [{ name: 'USA', value: 380 }, { name: 'Brazil', value: 120 }] }] } },
    sankey: { data: { links: [{ source: 'Salary', target: 'Budget', value: 3200 }, { source: 'Freelance', target: 'Budget', value: 900 }, { source: 'Budget', target: 'Rent', value: 1400 }, { source: 'Budget', target: 'Food', value: 600 }, { source: 'Budget', target: 'Savings', value: 1300 }, { source: 'Budget', target: 'Travel', value: 800 }] } },
    gauge: { data: { value: 0.78, min: 0, max: 1, target: 0.85, bands: [{ to: 0.5, label: 'Poor', tone: 'danger' }, { to: 0.75, label: 'Fair', tone: 'warning' }, { to: 1, label: 'Good', tone: 'success' }] }, format: 'percent' },
    funnel: { data: { categories: ['Visited', 'Viewed a product', 'Added to basket', 'Checked out', 'Paid'], series: [{ name: 'Visitors', values: [12000, 6400, 2100, 1300, 1120] }] }, format: '' },
    candlestick: { data: { series: [{ name: 'BMX', points: prices }] } },
    ohlc: { data: { series: [{ name: 'BMX', points: prices }] } },
    waterfall: { data: { categories: ['Opening', 'Sales', 'Services', 'Salaries', 'Rent', 'Closing'], series: [{ name: 'Cash', values: [1200, 860, 340, -720, -210, null] }], totals: ['Closing'] } },
    histogram: { data: { series: [{ name: 'Delivery days', values: Array.from({ length: 400 }, () => Math.round((random() + random() + random()) * 4)) }] }, format: '' },
    boxplot: { data: { categories: ['Leeds', 'York', 'Hull'], series: [{ name: 'Minutes', samples: [0, 1, 2].map(() => Array.from({ length: 40 }, () => Math.round(10 + random() * 30 + (random() > 0.95 ? 40 : 0)))) }] }, format: '' },
  };

  const show = value => {
    const kind = kinds[value];
    chart.type = kind.type ?? value;
    chart.stack = kind.stack ?? 'none';
    chart.format = kind.format ?? 'currency:GBP';
    chart.y2Axis = kind.y2Axis;
    chart.annotations = kind.annotations ?? [];
    chart.label = kind.label ?? 'Revenue by quarter';
    chart.description = kind.description ?? '';
    chart.data = kind.data;
  };
  const select = document.getElementById('ex-ch-type');
  select.addEventListener('change', () => show(select.value));
  document.getElementById('ex-ch-patterns').addEventListener('change', e => (chart.patterns = e.target.checked));
  document.getElementById('ex-ch-labels').addEventListener('change', e => (chart.dataLabels = e.target.checked));
  chart.addEventListener('bmxChartSelect', event => {
    const { label, series, value } = event.detail;
    out.textContent = `Chose ${label}${series ? `, ${series}` : ''}: ${value}`;
  });
  show('column');
</script>

Or give it records and name the columns: chart.data = rows with x-field="month" y-fields="revenue,cost".

Large series stay quick: a line keeps the points that hold its shape (zoom in for every one), and past a few thousand points the series are painted on a canvas under the SVG. The keyboard reads every value (arrow keys, Home, End, Enter), the legend switches series on and off, the data is also a table for screen readers (and for anyone, with table-toggle), and patterns can tell series apart without colour.

Properties

PropertyAttributeTypeDefaultDescription
animated animated boolean true Draw the marks in when the data arrives. Off when the reader asks for less motion.
annotations annotations readonly BmxChartAnnotation[] | string — Lines and bands across the plot: [{ value, label }], [{ type: 'band', axis: 'x', from, to }]. JSON in markup.
bins bins number — A histogram's number of bins. Default: Sturges' rule.
data data BmxChartData | readonly Record<string, unknown>[] | string — The numbers: { categories, series: [{ name, values }] }, series of points (x a number or a date), tree, nodes and links, or a gauge's value. Or an array of records, with xField and yFields. JSON in markup.
dataLabels data-labels boolean false Write the values on the bars, points and slices.
depth depth number 2 A treemap's levels shown at once. Default 2.
description description string — A line under the title, also read with the chart.
format format BmxChartNumberFormat | string — How values are written unless a series says otherwise: percent, currency:EUR, decimal:2, compact, or { style, currency, decimals, prefix, suffix }.
innerRadius inner-radius number 0.6 A doughnut's hole, as a share of its radius. Default 0.6.
label label string — A title over the chart, and its accessible name.
legend legend 'bottom' | 'top' | 'end' | 'none' 'bottom' Where the legend goes: bottom (default), top, end or none.
locale locale string — A BCP 47 locale for numbers and dates. Default: the page's lang.
patterns patterns boolean false Fill each series with a pattern as well as a colour, so they can be told apart without colour.
renderer renderer 'auto' | 'svg' | 'canvas' 'auto' auto (default: a canvas past a few thousand points), svg or canvas.
seriesField series-field string — With records: a column whose values each become a series (of the first of yFields).
stack stack 'none' | 'normal' | 'percent' 'none' Stack columns, bars and areas: none (default), normal, or percent (each to a hundred per cent).
strings strings Partial<BmxChartStrings> | string — Words the chart shows or says, to translate or reword: any of BmxChartStrings. JSON in markup.
timeZone time-zone string — The IANA time zone a time axis is read in. Default: the reader's.
toolbar toolbar boolean false Show buttons over the chart: switch to the data table, download PNG, SVG or CSV.
tooltip tooltip 'auto' | 'item' | 'axis' | 'none' 'auto' auto (default: one value for points and bars, every series at once on a line or area), item, axis or none.
type type BmxChartType 'column' What kind of chart. Default column.
xAxis x-axis BmxChartAxisOptions | string — The x axis: { title, type, min, max, grid, hidden }. JSON in markup.
xField x-field string — With records: the column along the x axis.
y2Axis y-2-axis BmxChartAxisOptions | string — The second value axis, on the far side, for series with axis: 'y2'.
yAxis y-axis BmxChartAxisOptions | string — The value axis: { title, min, max, log, zero, grid, hidden }. JSON in markup.
yFields y-fields readonly string[] | string — With records: the columns to chart, each a series (a list, or comma-separated in markup).
zoom zoom boolean false Drag across the plot to zoom in on that stretch of the x axis (lines, areas, columns, scatter, candles). Ctrl and the wheel zoom too.

Events

EventDetailDescription
bmxChartDrill BmxChartDrillDetail A treemap was drilled into, or back out.
bmxChartLegendToggle BmxChartLegendToggleDetail A series or category was switched on or off.
bmxChartSelect BmxChartSelectDetail A mark was chosen.
bmxChartZoom BmxChartZoomDetail The x axis was zoomed, or the zoom reset.

Methods

MethodSignatureDescription
download download(format?: "png" | "svg" | "csv", filename?: string) => Promise<void> Saves the chart as png, svg or csv, under filename (default: the label).
drillDown drillDown(path: readonly string[]) => Promise<void> Drills a treemap to a path of names ([] for the top).
getTable getTable() => Promise<BmxChartTable> The numbers as a table: { headers, rows, total }.
resetZoom resetZoom() => Promise<void> Shows the whole x axis again.
setFocus setFocus() => Promise<void> Moves focus to the chart.
setSeriesVisible setSeriesVisible(index: number, visible: boolean) => Promise<void> Shows or hides a series, as its legend entry does.
setZoom setZoom(from: number | string | Date, to: number | string | Date) => Promise<void> Zooms the x axis to from-to, in data units (a category's index, a number, or an instant or ISO date).
toCSV toCSV() => Promise<string> The numbers as CSV.
toPNG toPNG(scale?: number) => Promise<Blob> The chart as a PNG, scale times its size on screen (default 2).
toSVG toSVG() => Promise<string> The chart as a standalone SVG document, its colours and fonts written in, with its title and legend.

CSS shadow parts

PartDescription
breadcrumb a treemap's path when drilled in.
caption its title.
chart the whole chart.
description the line under the title.
legend the series.
legend-item one series or category, a button that shows or hides it.
plot the drawing.
table the data table, when it is shown.
toolbar the buttons above the plot.
tooltip the values under the pointer.

CSS custom properties

PropertyDescription
--bmx-chart-area-opacity How solid an area is. Default 0.22.
--bmx-chart-background Behind the chart.
--bmx-chart-color-0 The first series (or slice). Colours 0 to 9 are used in turn.
--bmx-chart-color-1 The second series.
--bmx-chart-color-2 The third series.
--bmx-chart-color-3 The fourth series.
--bmx-chart-color-4 The fifth series.
--bmx-chart-color-5 The sixth series.
--bmx-chart-color-6 The seventh series.
--bmx-chart-color-7 The eighth series.
--bmx-chart-color-8 The ninth series.
--bmx-chart-color-9 The tenth series.
--bmx-chart-down A falling candle, and a waterfall's decrease.
--bmx-chart-grid The grid lines.
--bmx-chart-heat-high A heatmap's highest value.
--bmx-chart-heat-low A heatmap's lowest value.
--bmx-chart-height How tall the drawing is. Default 20rem.
--bmx-chart-up A rising candle, and a waterfall's increase.