Chart integration component powered by the PhiaChart vanilla JS hook.
phia_chart/1 renders a container <div> wired to the PhiaChart hook,
which initialises Apache ECharts (or Chart.js as a fallback) in the
browser. Chart options and series data are passed as JSON via data-config
and data-series HTML attributes — the component itself is pure server-side
HEEx with zero client-side Elixir knowledge.
Architecture
The hook detects which library is loaded:
window.echarts → uses Apache ECharts
window.Chart → falls back to Chart.jsChart configuration is split into two attributes:
data-config— ECharts option object (axes, tooltip, series type). Built server-side from:typeand:labelsbybuild_config/2.data-series— array of series objects[%{name: ..., data: [...]}]. Passed as-is viaJason.encode!/1.
This split allows the hook to merge updated series data without replacing
the entire chart configuration on push_event.
Chart types
:type | ECharts series type | Notes |
|---|---|---|
:line | "line" | Standard line chart |
:bar | "bar" | Vertical bar / column chart |
:area | "line" + area | Line chart with areaStyle: {} fill |
:pie | "pie" | Pie/donut; labels become data point names |
Basic example
<.phia_chart
id="revenue-chart"
type={:area}
title="Monthly Revenue"
description="Last 12 months"
series={[%{name: "MRR", data: [10_000, 12_500, 11_800, 14_200, 15_600]}]}
labels={["Jan", "Feb", "Mar", "Apr", "May"]}
height="350px"
/>Without ChartShell wrapper
Omit :title and :description to render only the raw chart canvas, useful
when embedding inside an existing card or layout:
<.phia_chart
id="sparkline"
type={:line}
series={[%{name: "Sessions", data: @daily_sessions}]}
labels={@day_labels}
height="80px"
class="w-full"
/>Multiple series
<.phia_chart
id="acquisition-chart"
type={:bar}
title="User Acquisition"
series={[
%{name: "Organic", data: [120, 145, 132, 178, 201]},
%{name: "Paid", data: [80, 95, 110, 88, 130]},
%{name: "Referral", data: [30, 28, 45, 52, 40]}
]}
labels={["Jan", "Feb", "Mar", "Apr", "May"]}
height="400px"
/>Pie chart
For pie charts, labels become the data point names and series should
provide a single series whose data values correspond positionally to labels:
<.phia_chart
id="traffic-sources"
type={:pie}
title="Traffic Sources"
series={[%{name: "Sources", data: [45, 30, 15, 10]}]}
labels={["Organic", "Direct", "Referral", "Social"]}
height="300px"
/>Live updates via push_event
Send updated series data from the server without a full re-render. The hook
listens for "update-chart-{id}" events:
# In your LiveView handle_info or handle_event:
socket = push_event(socket, "update-chart-revenue-chart", %{
series: [%{name: "MRR", data: [50_000, 55_000, 62_000]}]
})The hook calls chart.setOption/1 with a merge, so static configuration
(colors, axis formatting, tooltip) is preserved.
Dark mode
PhiaChart listens for the phia:theme-changed custom DOM event (fired by
the PhiaDarkMode hook). When the event fires, the hook re-initialises the
chart with ECharts' dark theme applied automatically.
Setup
Copy the hook with
mix phia.add chart(or copy manually frompriv/templates/js/hooks/chart.js).Register the hook in your
app.js:import PhiaChart from "./hooks/chart" let liveSocket = new LiveSocket("/live", Socket, { hooks: { PhiaChart } })Install ECharts (recommended):
npm install echartsOr load from CDN in
root.html.heex:<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
Summary
Functions
Renders a chart container wired to the PhiaChart hook.
Functions
Renders a chart container wired to the PhiaChart hook.
If :title is provided, the chart is wrapped in a chart_shell/1 card.
Otherwise, the raw <div phx-hook="PhiaChart"> is rendered directly.
Chart options are encoded to JSON at render time via Jason.encode!/1 and
stored in data-config and data-series attributes. The hook reads these
attributes on mount to initialise the chart.
Example (with card shell)
<.phia_chart
id="mrr"
type={:area}
title="MRR"
series={[%{name: "Revenue", data: @mrr_data}]}
labels={@month_labels}
height="350px"
/>Example (raw canvas)
<.phia_chart
id="sparkline-mrr"
type={:line}
series={[%{name: "MRR", data: @sparkline}]}
labels={@sparkline_labels}
height="60px"
/>Attributes
id(:string) (required) - Unique element ID for the chart container. Also used as the suffix forpush_eventtargeting: to update this chart from the server, sendpush_event(socket, "update-chart-{id}", %{series: [...]}).type(:atom) - Chart type. Controls the ECharts series type and the base config generated bybuild_config/2. See the module docs for the full type matrix.Defaults to
:line. Must be one of:line,:bar,:pie, or:area.series(:list) - List of series maps. Each map must have at least a"data"key (list of values). A"name"key is recommended for tooltips and legends:[%{name: "Revenue", data: [100, 200, 150]}, %{name: "Costs", data: [80, 120, 90]}]For pie charts,
datavalues correspond positionally tolabels.Defaults to
[].labels(:list) - X-axis category labels (or pie slice names for:piecharts). Must match the length of each series'datalist:labels={["Jan", "Feb", "Mar", "Apr", "May"]}Defaults to
[].height(:string) - CSS height of the chart container. Accepts any CSS length:"300px","20rem","50vh". The chart hook reads this from the container's computed style to size the ECharts instance correctly.Defaults to
"300px".title(:string) - When provided, wraps the chart in achart_shell/1card with this title in the header. Whennil, renders only the raw chart canvas (no card wrapper).Defaults to
nil.description(:string) - Optional description shown in thechart_shellheader below the title. Only relevant when:titleis also provided. Ignored when:titleis nil.Defaults to
nil.class(:string) - Additional CSS classes for the chart container. Defaults tonil.Global attributes are accepted. HTML attributes forwarded to the chart container or card element.