Chart
v0.0.10
mix mishka.ui.gen.headless chart
Overview of the Phoenix and LiveView Headless Chart Component
The Mishka Chelekom Headless Chart is a Phoenix function component that wraps a JavaScript charting engine. Apache ECharts is the default, and the generator can install Chart.js or billboard.js instead with --lib chartjs or --lib billboard. You write the engine's option object as an Elixir map and pass it to option. The component JSON-encodes it and a single Chart JS hook hands it to the engine, so there is no per-chart JavaScript and the whole chart spec stays on the server, travelling over LiveView as data.
The root div takes class, width and height. Inside it, a surface part carries role="img", the hook and phx-update="ignore", because the engine owns that subtree and draws its canvas or SVG there. Series colors come from CSS custom properties, so a chart follows your light and dark themes. The component sets no colors, spacing or typography of its own.
Base UI Chart example - revenue dashboard (Phoenix headless)
Base UI Chart example - revenue dashboard (Phoenix headless)
<.chart
id="baseui-chart-dashboard"
height="20rem"
class="w-full max-w-xl"
aria_label="Monthly revenue over a year"
option={
%{
grid: %{left: 8, right: 16, top: 24, bottom: 28, containLabel: true},
tooltip: %{trigger: "axis"},
xAxis: %{
type: "category",
boundaryGap: false,
data: ~w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
},
yAxis: %{type: "value", axisLabel: %{formatter: "chelekom:currency:USD"}},
series: [
%{
name: "Revenue",
type: "line",
smooth: true,
areaStyle: %{color: "chelekom:fade"},
data: [
8200,
9320,
9010,
12_340,
12_900,
13_300,
14_100,
15_600,
14_800,
16_200,
17_400,
19_100
]
}
]
}
}
/>Base UI Chart example - traffic breakdown (Phoenix headless)
Base UI Chart example - traffic breakdown (Phoenix headless)
<.chart
id="baseui-chart-breakdown"
height="20rem"
class="w-full max-w-md"
aria_label="Traffic by source"
option={
%{
tooltip: %{trigger: "item"},
legend: %{bottom: 0},
series: [
%{
name: "Source",
type: "pie",
radius: ["48%", "72%"],
data: [
%{value: 1048, name: "Search"},
%{value: 735, name: "Direct"},
%{value: 580, name: "Referral"},
%{value: 484, name: "Social"},
%{value: 300, name: "Email"}
]
}
]
}
}
/>The option map
option is passed to the engine as it is, so its shape is whichever engine you generated. For ECharts it is the ECharts option. For Chart.js it is the Chart.js config, for example %{type: "line", data: %{labels: ~w(Mon Tue Wed), datasets: [%{label: "Sales", data: [120, 200, 150]}]}}. For billboard.js it is the billboard.js config without bindto, because the hook binds it, for example %{data: %{columns: [["Sales", 120, 200, 150]], type: "bar"}}.
Keys can be atoms or strings. A function cannot cross the LiveView wire, so formatters are strings, described below. An option string that is already JSON is accepted as well, and an empty option is fine when you fill the chart later with a push. The generator installs the engine's npm package for you: echarts, chart.js or billboard.js.
Attributes
id is required and is also what live updates target. aria_label names the chart for assistive technology. theme forces "light" or "dark", and without it the chart follows the page theme. group syncs the tooltip and zoom of charts that share the name, and only ECharts supports it. on_click names an event pushed when a data point is clicked. Its payload has name, value, seriesName, seriesIndex, dataIndex and componentType.
height and width are CSS lengths for the chart box and default to 20rem and 100%. class styles the root and stays live. surface_class styles the surface, but the surface is ignored by LiveView after the first render, so that class does not change later. The surface clips its overflow so a chart shrinks to fit its container, and for ECharts the hook turns on tooltip.confine so tooltips stay inside the box. Set confine in option to override it.
Theming and formatters
The hook reads series colors from the CSS custom properties --chart-1 to --chart-8 on the chart element, and axis, grid and label colors from --chart-axis, --chart-grid and --chart-text. Define them in your stylesheet for light and dark, and every chart is recolored when the page theme changes. Anything you set in option wins over these defaults. A default palette ships in chart_extensions.js, a file the generator writes once and never overwrites, so charts look right before you theme anything.
Formatters are sentinel strings that the hook swaps for a real function: "chelekom:number", "chelekom:compact", "chelekom:percent" and "chelekom:currency:USD" with any ISO 4217 code. "chelekom:fade" as an area color becomes a gradient from the series color to transparent. Add your own formatters in chart_extensions.js.
Live updates
Because the surface is ignored by LiveView, change a chart by pushing a new option instead of re-rendering it. The id is the root id. Add merge: false to the payload to replace the option instead of merging into it, and resize: true to force a resize. Assigning a new option also works, since it arrives in data-option, but a push avoids resending a large dataset in the page.
def handle_info({:revenue_updated, option}, socket) do
{:noreply, push_event(socket, "chelekom:chart", %{id: "baseui-chart-dashboard", option: option})}
endAccessibility
A canvas or SVG chart is opaque to a screen reader, so give every chart an aria_label. The surface has role="img" and no keyboard behavior of its own. For ECharts you can also set the engine's own aria option inside option to get a generated description of the data.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id; also the id `push_event` targets for live updates |
option
|
any
|
%{}
| — | No | The engine-native chart spec as an Elixir map (atom or string keys), JSON-encoded and passed through raw. A pre-encoded JSON string is accepted too. May be empty and filled later via `push_event` |
theme
|
string
| — | — | No | — |
height
|
string
|
20rem
| — | No | CSS height of the chart box. A chart needs an explicit height, or it collapses to 0 and draws nothing |
width
|
string
|
100%
| — | No | CSS width of the chart box |
group
|
string
| — | — | No | Sync tooltip and zoom across charts that share this group name (ECharts `connect`; ignored by the other engines) |
aria_label
|
string
| — | — | No | Accessible name for the chart (strongly recommended) |
on_click
|
string
| — | — | No | LiveView event pushed when a data point is clicked, with a small serializable payload |
hook
|
string
|
Chart
| — | No | Advanced: the JS hook to mount. Every engine registers `Chart`, so this only matters if you have hand-vendored a second engine under another name |
class
|
any
| — | — | No | Extra classes for the root (stays live) |
surface_class
|
any
| — | — | No | Extra classes for the chart surface. Frozen after first render (it is ignored) |
rest
|
global
| — | — | No | — |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
chart
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless chart