Playground
Draws an Apache ECharts chart from an option your server sends as JSON. The element does the part every page would otherwise write again: it colours the chart from the page's --sb-* tokens and follows theme changes, writes numbers, months and weekdays in the reader's language, keeps a wrapping legend clear of the axes, and animates each new option into place. ECharts itself is vendored with the component and loaded only when a chart comes near the screen.
Size it with CSS (block-size, height); it defaults to 18rem.
Examples
From the server
Send any ECharts option. A string that is exactly var(--token) becomes that colour, so the server can colour a series with the page's own palette.
<sb-echarts option='{
"tooltip": {"trigger": "axis"},
"legend": {"data": ["Launches", "Landings"], "top": 0},
"xAxis": {"type": "category", "data": ["2022", "2023", "2024", "2025", "2026"]},
"yAxis": {"type": "value"},
"series": [
{"name": "Launches", "type": "bar", "data": [61, 96, 134, 165, 190]},
{"name": "Landings", "type": "line", "smooth": true, "data": [55, 90, 127, 159, 186], "itemStyle": {"color": "var(--sb-ok)"}}
]
}'></sb-echarts>
Live
When the option attribute changes (a server morph, or a signal as here), the chart moves to the new state instead of redrawing.
<div data-signals="{_fuel: [82, 64, 91, 48, 73]}">
<sb-echarts style="block-size: 14rem" data-preserve-attr="option"
data-attr:option="JSON.stringify({xAxis: {type: 'category', data: ['Tank A', 'Tank B', 'Tank C', 'Tank D', 'Tank E']}, yAxis: {type: 'value', max: 100, name: '%'}, series: [{type: 'bar', data: $_fuel}]})"></sb-echarts>
<sb-button data-on:click="$_fuel = $_fuel.map(() => Math.round(20 + Math.random() * 80))">Refuel</sb-button>
</div>
Beyond axes
Pies, gauges and the like get no phantom grid.
<sb-echarts style="block-size: 16rem" option='{
"tooltip": {"trigger": "item"},
"series": [{"type": "pie", "radius": ["45%", "70%"], "data": [
{"name": "Hydrogen", "value": 74}, {"name": "Helium", "value": 24}, {"name": "Everything else", "value": 2}
]}]
}'></sb-echarts>
A fallback table
Put the same data inside the element. It shows until the chart has drawn (and without JavaScript), and afterwards it stays for screen readers, who get the numbers rather than a picture of them.
| Q1 | Q2 | Q3 | Q4 |
|---|---|---|---|
| 12 | 19 | 15 | 23 |
<sb-echarts style="block-size: 12rem" option='{"xAxis":{"type":"category","data":["Q1","Q2","Q3","Q4"]},"yAxis":{"type":"value"},"series":[{"type":"line","data":[12,19,15,23]}]}'>
<table>
<caption>Signals received per quarter</caption>
<tr><th>Q1</th><th>Q2</th><th>Q3</th><th>Q4</th></tr>
<tr><td>12</td><td>19</td><td>15</td><td>23</td></tr>
</table>
</sb-echarts>
Clicks
A click on a data item fires sb-chart-click.
<div data-signals="{_picked: ''}">
<sb-echarts style="block-size: 12rem" data-on:sb-chart-click="$_picked = evt.detail.name + ': ' + evt.detail.value"
option='{"xAxis":{"type":"category","data":["Io","Europa","Ganymede","Callisto"]},"yAxis":{"type":"value","name":"km"},"series":[{"type":"bar","data":[3643,3122,5268,4821]}]}'></sb-echarts>
<p data-text="$_picked || 'Click a bar.'"></p>
</div>
Charts whose shape is code
A custom series draws through a renderItem function, which no server can send as JSON. Define the chart once in the page's JavaScript and have the server name it:
import { defineChartKind } from '/c/echarts/echarts.js'
defineChartKind('timeline', (option, { echarts, color, css, formatNumber, lang, width, height }) => ({
xAxis: { type: 'time' },
yAxis: { type: 'category', data: option.rows },
series: [{ type: 'custom', data: option.bars, renderItem: (params, api) => { /* … */ } }],
}))
<sb-echarts option='{"kind": "timeline", "rows": ["Apollo 11"], "bars": [[0, -14182940000, -13402140000]]}'></sb-echarts>
The builder gets the ECharts module (for echarts.graphic and friends), color() to resolve a token or CSS colour, css() to read a token such as a font, the element's number format, language and size. What it returns is themed like any other option.
Numbers
Value axes and axis tooltips format numbers with Intl.NumberFormat in the element's language, unless the option sets a formatter of its own. If your server formats numbers differently (another separator, say), set the same rule for every chart on the page so the two never disagree:
import { setNumberFormat } from '/c/echarts/echarts.js'
setNumberFormat((n, lang) => myFormat(n, lang))
Theming
Colours come from the --sb-* tokens: text, borders and the tooltip surface from the semantic ones, series from --sb-chart-1 to --sb-chart-8 (falling back to brand, accent, ok, warn, danger, info…). Set those on the element or an ancestor to give charts their own palette. A pick on sb-theme-switch, or the system flipping under "auto", redraws the chart in the new colours without starting it again.
Accessibility
With a fallback table in the element, screen readers get the table and the drawing is hidden from them. Without one, ECharts' own description is switched on, which names the chart type and series. With prefers-reduced-motion: reduce nothing animates.
Licence
ECharts is © The Apache Software Foundation, under the Apache License 2.0; it is vendored unmodified from the echarts@6.1.0 npm release (vendor.json), with its licence and notice in vendor/.
Installation
Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.
<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>
<sb-echarts style="block-size: 9rem" option='{"grid":{"top":8,"bottom":20,"left":30,"right":8},"xAxis":{"type":"category","data":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]},"yAxis":{"type":"value"},"series":[{"type":"bar","data":[120,200,150,80,70,110,130]}]}'></sb-echarts>
<!-- In production, pin today's catalog instead of the latest: the browser then
refuses any file that changed. Add "integrity" to the import map above: the
hashes from https://starbase.zweiundeins.gmbh/c/@722e189276ac/importmap.json and Datastar's, below. -->
<!--
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" },
"integrity": { "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js": "sha384-vUxZojLrF1Ar3de5h7VINqhJXBgjyZS4U49pHvGa87kum6j5Xn6JRziuARNw5ELG", "…": "…from importmap.json" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/@722e189276ac/autoloader.js" integrity="sha384-scFa7/4jztDOx1YZT9/1g1ULYm9LsDlQn/8hMTyyD4IkPnZV2pfhHytoNTWTJG6b"></script>
-->
<!-- Or load just this component, pinned to this version. The minified module
is what the autoloader uses; the readable source is the same URL without .min. -->
<!-- <script type="module" src="https://starbase.zweiundeins.gmbh/c/echarts@c7ec5c49ba49/echarts.min.js" integrity="sha384-+0PdlcKv3ElWagbArdXBGRWw6wsQZAGXXfPU5iCn4dwPXkM2kL1XyB8zpndjsOrA"></script> -->Size
Each file compressed on its own, the way it is served (gzip -9, brotli -11). The autoloader loads the minified files (esbuild), so that last column is what a page downloads; the readable source is always there too. Datastar and Rocket are shared by every component and not counted.
| File | Original | gzip | brotli | minified |
|---|---|---|---|---|
echarts.js | 20.3 kB | 8.0 kB | 6.9 kB | 4.3 kB |
vendor/echarts.esm.min.js | 1121.7 kB | 367.3 kB | 298.8 kB | 298.8 kB |
<sb-theme-switch> renders it | 11.5 kB | 4.5 kB | 3.8 kB | 3.1 kB |
| Total | 1153.5 kB | 379.8 kB | 309.5 kB | 306.2 kB |
API reference
Props
| Attribute | Type | Default | Description |
|---|---|---|---|
option | custom | — | The ECharts option, as JSON. Strings that are exactly var(--token) become that colour. { "kind": "name", ... } is expanded by a builder the page defined with defineChartKind(). |
renderer | "canvas" | "svg" | "canvas" | Draw on a canvas, or as SVG (sharper when printed, slower with many points). |
lang | string | "" | Locale for numbers, months and weekdays (default: the page's lang, then the browser's). |
Slots
| Name | Description |
|---|---|
| A fallback for readers without the chart: shown until ECharts has drawn, kept for screen readers after that. A table of the same data is ideal. |
Events
| Name | Description |
|---|---|
sb-chart-click | A click on a data item. detail: { seriesName, seriesIndex, name, value, dataIndex }. |