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.y_invert(:boolean) - Flip the y axis so SMALLER values plot higher. For rank-like data (search position, race place, leaderboard) where 1 is the best result and belongs at the top. Without it callers had to negate their own values, which flips the sign of every domain and marker they pass too. Defaults tofalse.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.y_invert(:boolean) - Flip the y axis so SMALLER values plot higher — rank-like data. Defaults tofalse.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.