# `PhoenixKitWeb.Components.Core.Chart`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.21.1/lib/phoenix_kit_web/components/core/chart.ex#L1)

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.

# `bar_chart`

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 to `960`.
* `height` (`:integer`) - Defaults to `200`.
* `show_labels` (`:boolean`) - Render labels beneath the bars. Defaults to `false`.
* `baseline` (`:boolean`) - Draw the zero line when any value is negative (its position is not otherwise implied). Defaults to `true`.
* `class` (`:any`) - Classes for the wrapper (set text-* for colour). Defaults to `nil`.
* `aria_label` (`:string`) - Accessible name for the chart. Defaults to `nil`.
* `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 to `nil`.
* Global attributes are accepted. Extra attributes for the wrapper.
## Slots

* `empty` - Rendered instead of the SVG when there is no usable data.

# `line_chart`

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 to `false`.
* `area` (`:boolean`) - Fill a gradient area under the line. Defaults to `true`.
* `x_domain` (`:any`) - {min, max} for x, or nil to fit data. Defaults to `nil`.
* `y_domain` (`:any`) - {min, max} for y, or nil to fit data (10% padded). Defaults to `nil`.
* `marker_x` (`:any`) - x position for a dashed vertical marker, or nil. Defaults to `nil`.
* `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 to `false`.
* `width` (`:integer`) - viewBox width (the SVG scales to its container). Defaults to `960`.
* `height` (`:integer`) - viewBox height. Defaults to `240`.
* `gridlines` (`:integer`) - Number of horizontal gridlines (0 disables). Defaults to `3`.
* `class` (`:any`) - Classes for the wrapper (set text-* for colour). Defaults to `nil`.
* `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 to `nil`.
* Global attributes are accepted. Extra attributes for the wrapper.
## Slots

* `empty` - Rendered instead of the SVG when there is no usable data.

# `sparkline`

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 to `200`.
* `height` (`:integer`) - viewBox height. Defaults to `48`.
* `y_invert` (`:boolean`) - Flip the y axis so SMALLER values plot higher — rank-like data. Defaults to `false`.
* `class` (`:any`) - Classes for the wrapper (set text-* for colour). Defaults to `nil`.
* `aria_label` (`:string`) - Accessible name. Left unset the sparkline is marked decorative. Defaults to `nil`.
* Global attributes are accepted. Extra attributes for the wrapper.
## Slots

* `empty` - Rendered instead of the SVG when there is no usable data.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
