Declarative charts powered by Apache ECharts, themed by your design tokens.
The chart is described by a plain Elixir map — the ECharts
option object — so the whole
spec lives server-side and travels the LiveView wire as data. Updating the
assign that feeds option patches the chart in place with an animated
transition; no JavaScript is written per chart.
<.chart
id="revenue"
option={%{
xAxis: %{type: "category", data: ~w(Jan Feb Mar Apr)},
yAxis: %{type: "value"},
series: [%{type: "line", smooth: true, data: [820, 932, 901, 1290]}]
}}
/>Bring your own ECharts
Like Alpine, the library does not bundle the engine. Add ECharts to your app (either is fine):
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>or npm i echarts and import * as echarts from "echarts"; window.echarts = echarts;
in your app.js. The PetalChart hook (in the bundled JS) picks it up from
window.echarts and warns if it is missing.
Theming
Colors are resolved from your CSS tokens at mount, so charts follow the same dial as every other component — light and dark:
- Series palette —
--pc-chart-1…--pc-chart-8if you define them, otherwise a default palette derived from your semantic ramps (primary, info, success, warning, danger, secondary). - Axes, labels, gridlines, tooltips — derived from the
grayramp, using the same ghost-material alphas as the rest of the library in dark mode.
Anything you set explicitly in option wins over the derived theme.
A few conveniences fill the gap between a server-side spec and things only the client knows (resolved colors, number formatting functions):
areaStyle: %{color: "petal:fade"}on a line series renders the soft vertical gradient fade (the series' own color at 35% fading to transparent) without you having to know the resolved palette.When falling back to the semantic ramps, ramps whose hue collides with an earlier pick (success green under an emerald primary, say) are pushed to the back of the palette so adjacent series stay distinct.
Named formatters - ECharts formats numbers via JavaScript callback functions, which can't travel the wire. Anywhere ECharts accepts a formatter (
axisLabel,tooltip.valueFormatter, series labels), pass one of these strings instead and the hook substitutes anIntl.NumberFormat-backed function:"petal:number"- compact: 1234 → "1.2K""petal:percent"- value as given plus %: 8.3 → "8.3%""petal:currency:USD"- full: 12480 → "$12,480" (any ISO code)"petal:currency-compact:USD"- axes: 12480 → "$12K"
For example:
yAxis: %{axisLabel: %{formatter: "petal:currency-compact:USD"}}, tooltip: %{trigger: "axis", valueFormatter: "petal:currency:USD"}.
The theme re-derives automatically when dark mode flips or theme attributes
change anywhere above the chart (class, data-theme, data-primary,
data-gray, ...). If your app rethemes some other way, dispatch
window.dispatchEvent(new Event("petal:retheme")) after changing tokens.
Live updates
Change the assign and the chart animates — this is the primary API:
def handle_info({:tick, points}, socket) do
{:noreply, assign(socket, option: put_in(socket.assigns.option, [:series, Access.at(0), :data], points))}
endFor high-frequency streams where re-serializing the full option is wasteful,
push a partial option instead; it is merged via setOption:
push_event(socket, "chart:update:revenue", %{option: %{series: [%{data: points}]}})
Summary
Functions
Renders an ECharts chart bound to the PetalChart hook.
Functions
Renders an ECharts chart bound to the PetalChart hook.
Attributes
id(:string) (required)option(:map) (required) - the ECharts option object as an Elixir map (atom or string keys).height(:string) - any CSS height; the chart fills its container's width. Defaults to"20rem".renderer(:string) - svg renders crisp at any zoom and is print-friendly; canvas suits large data. Defaults to"canvas". Must be one of"canvas", or"svg".group(:string) - charts sharing a group name get connected tooltips/zoom (echarts.connect). Defaults tonil.loading(:boolean) - shows a theme-colored loading spinner over the chart while your data is on its way. Defaults tofalse.class(:any) - Defaults tonil.- Global attributes are accepted.