Server-rendered SVG chart primitives — no JavaScript, no external libraries. Charts re-render whenever LiveView assigns change, so they are live by construction. (They snap rather than tween: SVG geometry attributes are not CSS-transitionable.)
All charts draw in currentColor: set a text colour on the component
(class="text-primary") and stroke, fill and gradient all follow it in
light and dark themes.
Sizing
Each chart fills its container. The SVG uses preserveAspectRatio="none",
so it stretches to whatever box you give it while strokes stay crisp via
vector-effect="non-scaling-stroke". Give the wrapper a height —
otherwise you inherit the viewBox's intrinsic ratio, which is rarely what
you want:
<div class="h-48 w-full">
<.line_chart id="p" data={@points} class="text-primary" />
</div>Heavy stretching still distorts anything measured in user space: dash patterns and the bars' rounded corners. Strokes are protected; those are not.
Data
Values are plain numbers — callers map their domain (time, money, watts)
to x/y first, which is what keeps these primitives generic: a price
curve, a CPU graph and a signup funnel are all just {x, y} pairs.
Points may be {x, y} tuples or %{x: x, y: y} maps, and Decimal
values are converted automatically (money and metrics arrive that way
from Ecto). Points that aren't numeric are dropped rather than crashing
the page; if nothing usable is left, the :empty slot renders.
Series are sorted by x, so out-of-order data plots correctly instead of zigzagging.
Origin: extracted from NordSwitch's live price charts; also intended as
the drawing layer for phoenix_kit_dashboards widgets.
Summary
Functions
A vertical bar chart, measured from a zero baseline.
A line chart for a numeric series, optionally filled.
A minimal inline sparkline for a list of values.
Functions
A vertical bar chart, measured from a zero baseline.
Examples
<.bar_chart
id="daily-kwh"
data={Enum.map(@days, &%{label: &1.date, value: &1.kwh})}
aria_label="Daily consumption"
class="text-secondary"
/>Bars carry a native <title>, so hovering one shows its label and value
with no JS. Negative values hang below the baseline instead of
disappearing. A datum may set class: to colour one bar differently
(%{label: "Fri", value: 22, class: "text-error"}).
Attributes
id(:string) (required) - Unique id (applied to the SVG element).data(:list) (required) - List of %{label: term, value: number, class: optional_string}.width(:integer) - Defaults to960.height(:integer) - Defaults to200.show_labels(:boolean) - Render labels beneath the bars. Defaults tofalse.baseline(:boolean) - Draw the zero line when any value is negative (its position is not otherwise implied). Defaults totrue.class(:any) - Classes for the wrapper (set text-* for colour). Defaults tonil.aria_label(:string) - Accessible name for the chart. Defaults tonil.value_format(:any) - 1-arity fun formatting a value for its tooltip. No generic chart can infer units or locale, so pass your own. It receives the NUMBER, not your original struct — non-numeric values are dropped before this runs — so write&"$#{&1}", not&Money.to_string/1. Defaults to a compact numeric rendering. Defaults tonil.- Global attributes are accepted. Extra attributes for the wrapper.
Slots
empty- Rendered instead of the SVG when there is no usable data.
A line chart for a numeric series, optionally filled.
Examples
<%!-- a day of prices: x = minute of day, y = EUR/MWh --%>
<.line_chart
id="price-day"
data={Enum.map(@slots, &{minute_of_day(&1.starts_at), &1.eur_mwh})}
x_domain={{0, 1440}}
step
marker_x={minute_of_day(@now)}
aria_label="Electricity price today"
class="text-primary"
/>
<%!-- a bare line, auto domains --%>
<.line_chart id="signups" data={@signups} area={false} />With step each point holds its y until the next x (right-open steps —
the correct reading for slot/interval data like prices). marker_x
draws a dashed vertical "now" line. Domains default to the data's
min/max, y padded by 10%; a reversed domain is normalised.
Attributes
id(:string) (required) - Unique id (namespaces the SVG gradient defs).data(:list) (required) - Points as {x, y} tuples or %{x: , y: } maps. Non-numeric points are dropped.step(:boolean) - Step interpolation (interval data). Defaults tofalse.area(:boolean) - Fill a gradient area under the line. Defaults totrue.x_domain(:any) - {min, max} for x, or nil to fit data. Defaults tonil.y_domain(:any) - {min, max} for y, or nil to fit data (10% padded). Defaults tonil.marker_x(:any) - x position for a dashed vertical marker, or nil. Defaults tonil.width(:integer) - viewBox width (the SVG scales to its container). Defaults to960.height(:integer) - viewBox height. Defaults to240.gridlines(:integer) - Number of horizontal gridlines (0 disables). Defaults to3.class(:any) - Classes for the wrapper (set text-* for colour). Defaults tonil.aria_label(:string) - Accessible name.role="img"without one is an unlabelled graphic to a screen reader; pass what the chart shows. Unlabelled charts are marked decorative. Defaults tonil.- Global attributes are accepted. Extra attributes for the wrapper.
Slots
empty- Rendered instead of the SVG when there is no usable data.
A minimal inline sparkline for a list of values.
Examples
<.sparkline values={Enum.map(@upcoming, & &1.eur_mwh)} class="w-full h-12 text-info" />A single value draws a flat line rather than nothing — one sample is a real state, and an empty box reads as a rendering bug.
Attributes
values(:list) (required) - Numbers, in order. Non-numeric values are dropped.width(:integer) - viewBox width. Defaults to200.height(:integer) - viewBox height. Defaults to48.class(:any) - Classes for the wrapper (set text-* for colour). Defaults tonil.aria_label(:string) - Accessible name. Left unset the sparkline is marked decorative. Defaults tonil.- Global attributes are accepted. Extra attributes for the wrapper.
Slots
empty- Rendered instead of the SVG when there is no usable data.