Line Chart
A line chart for trends over a continuous axis.
Installation
Charts ship in the separate, optional poetry-charts
gem — not bundled with poetry-ui. If you
don't have it yet, add it and wire it into your app:
bundle add poetry-charts
bin/rails g poetry:install --charts
Default
<% data = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 }
]
config = { desktop: { label: "Desktop", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_line_chart(data: data, config: config, id: "line-default",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] }) %>
<% chart.with_line(data_key: :desktop) %>
<% chart.with_tooltip(hide_label: true) %>
<% end %>
</div>
Dots
<% data = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 }
]
config = { desktop: { label: "Desktop", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_line_chart(data: data, config: config, id: "line-dots",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] }) %>
<% chart.with_line(data_key: :desktop, dots: true) %>
<% end %>
</div>
Dots colors
<%# Per-point dot colors: each row carries its own fill via dot_color_key,
resolved through the config's --color-* variables. %>
<% data = [
{ browser: "chrome", visitors: 275, fill: "var(--color-chrome)" },
{ browser: "safari", visitors: 200, fill: "var(--color-safari)" },
{ browser: "firefox", visitors: 187, fill: "var(--color-firefox)" },
{ browser: "edge", visitors: 173, fill: "var(--color-edge)" },
{ browser: "other", visitors: 90, fill: "var(--color-other)" }
]
config = { visitors: { label: "Visitors", color: "var(--chart-2)" },
chrome: { label: "Chrome", color: "var(--chart-1)" },
safari: { label: "Safari", color: "var(--chart-2)" },
firefox: { label: "Firefox", color: "var(--chart-3)" },
edge: { label: "Edge", color: "var(--chart-4)" },
other: { label: "Other", color: "var(--chart-5)" } } %>
<div class="w-full max-w-xl">
<%= poetry_line_chart(data: data, config: config, id: "line-dots-colors",
margin: { top: 24, left: 24, right: 24 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_line(data_key: :visitors, dots: true, dot_radius: 5, dot_color_key: :fill) %>
<% end %>
</div>
Label
<% data = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 }
]
config = { desktop: { label: "Desktop", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_line_chart(data: data, config: config, id: "line-label",
margin: { top: 20, left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] }) %>
<% chart.with_line(data_key: :desktop, dots: true, labels: true) %>
<% end %>
</div>
Linear
<% data = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 }
]
config = { desktop: { label: "Desktop", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_line_chart(data: data, config: config, id: "line-linear",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] }) %>
<% chart.with_line(data_key: :desktop, curve: :linear) %>
<% end %>
</div>
Multiple
<% data = [
{ 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 }
]
config = { desktop: { label: "Desktop", color: "var(--chart-1)" },
mobile: { label: "Mobile", color: "var(--chart-2)" } } %>
<div class="w-full max-w-xl">
<%= poetry_line_chart(data: data, config: config, id: "line-multiple",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] }) %>
<% chart.with_line(data_key: :desktop) %>
<% chart.with_line(data_key: :mobile) %>
<% end %>
</div>
References and errors
<%# Error bars ride each row as err: [below, above]; reference line and
reference area mark thresholds against the y scale. %>
<% data = [
{ month: "January", desktop: 186, err: [12, 20] },
{ month: "February", desktop: 305, err: [15, 19] },
{ month: "March", desktop: 237, err: [18, 18] },
{ month: "April", desktop: 73, err: [21, 17] },
{ month: "May", desktop: 209, err: [24, 16] },
{ month: "June", desktop: 214, err: [27, 15] }
]
config = { desktop: { label: "Desktop", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_line_chart(data: data, config: config, id: "line-refs",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] }) %>
<% chart.with_line(data_key: :desktop, error_key: :err) %>
<% chart.with_reference_line(y: 204, label: "avg") %>
<% chart.with_reference_area(y1: 280, y2: 320, label: "goal") %>
<% chart.with_tooltip %>
<% end %>
</div>
Step
<% data = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 }
]
config = { desktop: { label: "Desktop", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_line_chart(data: data, config: config, id: "line-step",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] }) %>
<% chart.with_line(data_key: :desktop, curve: :step) %>
<% end %>
</div>
API
Poetry::Charts::LineChart::Component — options are
constructor keywords (the poetry_* helper forwards them);
slots are composed inside the block. Generated from the gem's source documentation.
| Option | Type | Details | Description |
|---|---|---|---|
| config: | Object | required | The series config - key => { label:, color: } - naming and coloring every series. |
| data: | Object | required | The rows to plot: an array of hashes, one per x category. |
| height: | Integer | defaults to 360 |
ViewBox height in pixels. |
| id: | String | Explicit DOM id token, stable across renders; otherwise the chart gets a unique per-render id. | |
| label: | String | Accessible name for the chart SVG; defaults to one built from the configured series. | |
| margin: | Object | Plot margin overrides ({ top:, right:, bottom:, left: }), merged over the defaults. | |
| width: | Integer | defaults to 640 |
ViewBox width in pixels; the rendered chart scales to its container. |
Slots
| Writer | Description |
|---|---|
| with_line | A line series bound to data_key:. dots: marks each point; dot_color_key: reads per-point dot colors from the row; labels: stamps each value above its point; error_key: adds error whiskers. |
Styling
Every part carries a stable data-slot attribute — target
[data-slot=…] from your own CSS to restyle it. State rides
data attributes on the parts below. This contract is verified against rendered DOM in CI.
| Part | Description |
|---|---|
| [data-slot=chart-brush] | The brush strip group (with_brush): track + window + two handles below the x axis |
| [data-slot=chart-brush-track] | The full-width brush rail |
| [data-slot=chart-brush-window] | The selected-range rect the drag moves |
| [data-slot=chart-brush-handle] | One draggable window edge |
| [data-slot=chart-zoom-selection] | The zoom drag-selection overlay (zoom: true), hidden until a drag starts |
| [data-slot=chart-live-payload] | The embedded {spec, frame} JSON the live renderer recomputes geometry from |
| [data-slot=chart-svg] | The chart canvas (<svg>) - server-computed geometry in a fixed viewBox; role=img, or the focusable role=application accessibilityLayer when the tooltip attaches |
| [data-slot=chart-grid] | The gridline group (with_grid) - horizontal and/or vertical rules across the plot |
| [data-slot=chart-cursor] | The hover cursor, hidden until the tooltip controller positions and reveals it at the active index - a vertical rule or a translucent band rect (bar charts) |
| [data-slot=chart-lines] | The line-mark group - each series' curve plus its companion marks |
| [data-slot=chart-line] | One series' stroked curve - pathLength=1 when animating so the dash draw-in needs no measurement |
| [data-slot=chart-dots] | One series' point-dot group (dots: true) |
| [data-slot=chart-dot] | One point dot (<circle>) |
| [data-slot=chart-error-bars] | One series' error-whisker group (error_key:) - cap-stem-cap paths in the foreground color |
| [data-slot=chart-labels] | One series' value-label group (labels: true) |
| [data-slot=chart-reference] | The reference-mark group (with_reference_line/_area/_dot), painted above the series |
| [data-slot=chart-active-dots] | The hover-marker group (with_tooltip) - pre-rendered hidden circles for every series x index |
| [data-slot=chart-active-dot] | One hover marker - display=none until the tooltip controller reveals the active index's dot |
| [data-slot=chart-x-axis] | The x-axis tick-label group (with_x_axis) |
| [data-slot=chart-coordinates] | The embedded per-index geometry payload (<script type=application/json>) the tooltip controller reads - zero chart math in the browser |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| chart-brush-handle | data-edge | always - which edge | start · end |
| chart-svg | data-animate | present when animate (the default) - the motion stylesheet and controller key the entrance off it | — |
| chart-svg | data-motion | runtime - the motion rig stamps the animation lifecycle (entrance/morph, then settled) | entrance · morph · settled |
| chart-line | data-key | the series key | — |
| chart-dots | data-key | the series key | — |
| chart-error-bars | data-key | the series key | — |
| chart-labels | data-key | the series key | — |
| chart-active-dot | data-key | the series key | — |
| chart-active-dot | data-index | the datum index | — |
| chart-active-dot | data-active | runtime - rides the marker while its index is the active one | — |
CSS variables
| Part | Variable | Description |
|---|---|---|
| chart-svg | --poetry-motion-duration | the entrance/morph duration (animation_duration, ms) |
| chart-svg | --poetry-motion-easing | the animation easing keyword (animation_easing) |
| chart-svg | --poetry-motion-delay | the pre-animation hold (animation_begin, ms) |
Wiring
The Stimulus surface each element carries — declared in the component, verified against rendered DOM in CI. Bare actions fire on the element's default event.
| Element | Controller | Wiring |
|---|---|---|
| frame | poetry--charts--tooltip (if tooltip?) | registers · value sync (if) |
| frame | poetry--charts--motion (if animate?) | registers |
| frame | poetry--charts--live (if live?) | registers · receive on poetry-chart:update |
| frame | poetry--charts--tooltip (if) | refresh on poetry--charts--live:updated |
| frame | poetry--charts--window (if window_features?) | registers · value zoom · value plot · value brush (if) |
| svg | poetry--charts--tooltip (if tooltip?) | move on pointermove · leave on pointerleave · focus on focus · blur on blur · keydown on keydown · target svg |
| svg | poetry--charts--window (if zoom?) | startZoom on pointerdown · reset on dblclick |
| coordinates | poetry--charts--tooltip (if tooltip?) | target data |
| tooltip_layer | poetry--charts--tooltip (if tooltip?) | target tooltip |
| live_payload | poetry--charts--live | target payload |
| brush | poetry--charts--window (if) | startBrush on pointerdown |