<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
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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
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
| Event | Detail | Description |
|---|---|---|
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
| Method | Signature | Description |
|---|---|---|
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
| Part | Description |
|---|---|
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
| Property | Description |
|---|---|
--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. |