Area chart
The custom element is <hui-area-chart>.
Overview
A curve with its area filled beneath, drawn in SVG from the server's JSON: one series or several, overlapping or stacked, solid or in a gradient. The curves are d3-shape's, as Recharts', so a monotone or natural line matches shadcn's to the control point.
Example
Source
<hui-area-chart label="Share price, this week" curve="monotone" fill="gradient" fill-opacity="0.18" grid hide-category-axis max="600" hide-tooltip-label style="height:200px"> <script type="application/json">{"series":[{"key":"price","label":"Price","colour":"var(--hui-chart-1)"}],"rows":[{"label":"Mon","price":412},{"label":"Tue","price":438},{"label":"Wed","price":395},{"label":"Thu","price":450},{"label":"Fri","price":420},{"label":"Sat","price":462}]}</script></hui-area-chart>The Go template that renders it:
<hui-area-chart label="Share price, this week" curve="monotone" fill="gradient" fill-opacity="0.18" grid hide-category-axis max="600" hide-tooltip-label style="height:200px"> <script type="application/json">{{ .Prices }}</script></hui-area-chart>On a phone
The same thinning as the bar chart: labels that would collide are left out, the last is always kept, and none reaches past the element’s box.
Source
<hui-area-chart index="month" label="Visitors by month" curve="natural" grid style="height:220px"> <script type="application/json">{"series":[{"key":"desktop","label":"Desktop"}],"rows":[{"month":"January","desktop":186},{"month":"February","desktop":305},{"month":"March","desktop":237},{"month":"April","desktop":73},{"month":"May","desktop":209},{"month":"June","desktop":214},{"month":"July","desktop":190},{"month":"August","desktop":260},{"month":"September","desktop":240},{"month":"October","desktop":310},{"month":"November","desktop":280},{"month":"December","desktop":330}]}</script></hui-area-chart>API
Attributes23
| Name | Type | Default | Description |
|---|---|---|---|
label | string | "" | Required. The chart's accessible name, and the caption of the data table. |
index | string | "label" | The row property that holds each category. |
curve | "natural" | "monotone" | "linear" | "step" | "natural" | The interpolation between points: d3-shape's curves, as Recharts uses them. monotone never overshoots the data. |
stroke-width | number | 2 | The line's width, in pixels. |
stacked | boolean | false | Series stack, each over the one before; otherwise they overlap. |
fill | "solid" | "gradient" | "solid" | A flat tint, or a vertical gradient from the tint down to a sixth of it. |
fill-opacity | number | 0.4 | The fill's strength: all of a solid fill, the top of a gradient. |
grid | boolean | false | Dashed gridlines at each value tick. |
value-axis | boolean | false | Value labels along the left. |
hide-category-axis | boolean | false | Leaves the category labels out and keeps everything else. |
bare | boolean | false | No axes, no grid, no margin: a sparkline. |
min | number | - | The value axis' low end. |
max | number | - | The value axis' high end. With grid, the lines space evenly across the fixed domain. |
margin | string | "8 0 0 0" | The plot's margin in pixels, CSS order. |
tick-margin | number | 8 | Space between the plot and its labels. |
tick-length | number | - | Shortens axis labels to this many characters; the tooltip and table keep them whole. |
tooltip | "index" | "item" | "none" | "index" | Every series at the category, the series nearest the pointer, or nothing. |
indicator | "dot" | "line" | "dashed" | "dot" | The marker beside each tooltip row. |
hide-tooltip-label | boolean | false | Leaves the category heading out of the tooltip. |
legend | boolean | false | A legend under the plot. |
cursor | boolean | false | A vertical rule at the active category. |
format | string | - | Intl.NumberFormat options as JSON. |
locale | string | the document's lang | The locale values are formatted in. |
Properties23
| Name | Type | Default | Description |
|---|---|---|---|
label | string | as <code>label</code> | Reflects to the label attribute. |
index | string | as <code>index</code> | Reflects to the index attribute. |
curve | "natural" | "monotone" | "linear" | "step" | as <code>curve</code> | Reflects to the curve attribute. |
strokeWidth | number | as <code>stroke-width</code> | Reflects to the stroke-width attribute. |
stacked | boolean | as <code>stacked</code> | Reflects to the stacked attribute. |
fill | "solid" | "gradient" | as <code>fill</code> | Reflects to the fill attribute. |
fillOpacity | number | as <code>fill-opacity</code> | Reflects to the fill-opacity attribute. |
grid | boolean | as <code>grid</code> | Reflects to the grid attribute. |
valueAxis | boolean | as <code>value-axis</code> | Reflects to the value-axis attribute. |
hideCategoryAxis | boolean | as <code>hide-category-axis</code> | Reflects to the hide-category-axis attribute. |
bare | boolean | as <code>bare</code> | Reflects to the bare attribute. |
min | number | as <code>min</code> | Reflects to the min attribute. |
max | number | as <code>max</code> | Reflects to the max attribute. |
margin | string | as <code>margin</code> | Reflects to the margin attribute. |
tickMargin | number | as <code>tick-margin</code> | Reflects to the tick-margin attribute. |
tickLength | number | as <code>tick-length</code> | Reflects to the tick-length attribute. |
tooltip | "index" | "item" | "none" | as <code>tooltip</code> | Reflects to the tooltip attribute. |
indicator | "dot" | "line" | "dashed" | as <code>indicator</code> | Reflects to the indicator attribute. |
hideTooltipLabel | boolean | as <code>hide-tooltip-label</code> | Reflects to the hide-tooltip-label attribute. |
legend | boolean | as <code>legend</code> | Reflects to the legend attribute. |
cursor | boolean | as <code>cursor</code> | Reflects to the cursor attribute. |
format | string | as <code>format</code> | Reflects to the format attribute. |
locale | string | as <code>locale</code> | Reflects to the locale attribute. |
Events1
| Name | Description |
|---|---|
hui-chart-focus | The reader moved to a category, by pointer or keyboard: its position, and the row as the server sent it. It bubbles and is composed. Announced, never stored: a host can mirror it elsewhere on the page. |
Slots1
| Name | Description |
|---|---|
(default) | One <script type="application/json"> holding series and rows. Replacing it, or its text, redraws the chart. |
CSS custom properties2
| Name | Default | Description |
|---|---|---|
--hui-chart-1 … --hui-chart-5 | the preset's violet | Series colours, by position, repeating after the fifth. |
--hui-series-<key> | - | One series' colour, set from the host's stylesheet: --hui-series-mobile: var(--hui-chart-4). A colour field in the JSON wins over it. |
::part() hooks13
| Name | Description |
|---|---|
figure | The wrapper carrying role="figure" and the label. |
plot | The <svg>, hidden from assistive technology. |
grid | The gridlines group. |
axis-x | The labels under the plot. |
axis-y | The value labels beside it. |
series | One group per series, with data-key. |
area | A series' filled area, with data-series-index. |
line | A series' line, with data-series-index. |
active-dot | The 4px dot on the active point. |
table | The visually hidden data table. |
tooltip | The tooltip, a manual popover. |
legend | The legend row. |
announcer | The visually hidden live region. |
States
Natural, solid, with its months
One series against a fixed max, with fill-opacity making the fill flat rather than a gradient and tick-length shortening the month names.
Source
<hui-area-chart index="month" label="Desktop visitors" fill-opacity="0.15" max="320" tick-length="3" style="height:200px"> <script type="application/json">{"series":[{"key":"desktop","label":"Desktop"}],"rows":[{"month":"January","desktop":186},{"month":"February","desktop":305},{"month":"March","desktop":237},{"month":"April","desktop":73},{"month":"May","desktop":209},{"month":"June","desktop":214}]}</script></hui-area-chart>Stacked
stacked puts the series on top of one another, so the upper edge is the total, with a legend naming them.
Source
<hui-area-chart index="month" label="Desktop and mobile visitors" stacked legend tick-length="3" style="height:200px"> <script type="application/json">{"series":[{"key":"desktop","label":"Desktop"},{"key":"mobile","label":"Mobile"}],"rows":[{"month":"January","desktop":186,"mobile":80},{"month":"February","desktop":305,"mobile":200},{"month":"March","desktop":237,"mobile":120},{"month":"April","desktop":73,"mobile":190},{"month":"May","desktop":209,"mobile":130},{"month":"June","desktop":214,"mobile":140}]}</script></hui-area-chart>Server-side mechanics
The server writes the series and rows as JSON into the element's <script type="application/json"> child, in the order they are drawn - the chart never sorts, filters or aggregates. html/template escapes a Go value into that script: {{ .Chart }}. Replacing the child redraws the same element without animating; replacing the element draws the new one. Nothing the reader did survives a swap.
Accessibility
- The chart is a
figurenamed bylabel, and its data is a visually hidden table with that label as its caption; the SVG is hidden from the accessibility tree. - The plot is one tab stop: the arrow keys step the points with the tooltip following, and a polite live region reads each move.
- A
nullbreaks the line rather than joining across a missing value, and reads "No data" in the tooltip and table. - Under forced colours lines draw in
CanvasText, each series after the first with its own dash pattern. - The first draw reveals left to right over 1500ms; under
prefers-reduced-motion: reduceit is simply there, and no redraw after a swap animates.
Keyboard
| Keys | Action |
|---|---|
Tab | Focuses the plot, one stop per chart, and shows the tooltip at the category the keyboard was last on. |
Arrow Right, Arrow Down | The next category. |
Arrow Left, Arrow Up | The previous category. |
Home, End | The first and the last category. |
Escape | Hides the tooltip; focus stays on the plot. |
Gotchas
- Areas overlap by default, later series over earlier, so a translucent fill lets the earlier show through. Stack them with
stackedwhen they are parts of one total. - A gradient's ids repeat from chart to chart, which is safe: each chart's are inside its own shadow root.
- Like every chart, it ships in
home-ui-charts.js, nothome-ui.js.