Composed Chart
A composed chart layering bars, lines, and areas on shared axes.
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", visitors: 320, trend: 240 },
{ month: "February", visitors: 410, trend: 290 },
{ month: "March", visitors: 380, trend: 320 },
{ month: "April", visitors: 250, trend: 300 },
{ month: "May", visitors: 445, trend: 340 },
{ month: "June", visitors: 480, trend: 390 }
]
config = { visitors: { label: "Visitors", color: "var(--chart-1)" },
trend: { label: "Trend", color: "var(--chart-3)" } } %>
<div class="w-full max-w-xl">
<%= poetry_chart(:composed, data: data, config: config, id: "composed-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_bar(data_key: :visitors, radius: 4) %>
<% chart.with_line(data_key: :trend, stroke_width: 2, dots: true) %>
<% chart.with_tooltip %>
<% end %>
</div>
Full mix
<%# Mix marks freely: with_area / with_bar / with_line -
declaration order is paint order. %>
<% data = [
{ month: "January", visitors: 320, revenue: 214, trend: 240 },
{ month: "February", visitors: 410, revenue: 305, trend: 290 },
{ month: "March", visitors: 380, revenue: 237, trend: 320 },
{ month: "April", visitors: 250, revenue: 173, trend: 300 },
{ month: "May", visitors: 445, revenue: 209, trend: 340 },
{ month: "June", visitors: 480, revenue: 264, trend: 390 }
]
config = { visitors: { label: "Visitors", color: "var(--chart-1)" },
revenue: { label: "Revenue", color: "var(--chart-2)" },
trend: { label: "Trend", color: "var(--chart-3)" } } %>
<div class="w-full max-w-xl">
<%= poetry_chart(:composed, data: data, config: config, id: "composed-mix",
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_area(data_key: :revenue, fill_opacity: 0.25) %>
<% chart.with_bar(data_key: :visitors, radius: [4, 4, 0, 0]) %>
<% chart.with_line(data_key: :trend, curve: :monotone_x, stroke_width: 2) %>
<% chart.with_legend %>
<% chart.with_tooltip %>
<% end %>
</div>
API
Poetry::Charts::ComposedChart::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 |
|---|---|---|---|
| bar_category_gap: | String | defaults to "10%" |
Band trim on each side: a percent string of the band width, or a bare pixel number. |
| bar_gap: | Integer | defaults to 4 |
Pixels between side-by-side bars inside one category band. |
| 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_area | An area mark bound to data_key:; areas sharing a stack: id pile up (area stacks never join bar stacks). |
| with_bar | A bar mark bound to data_key:; radius: rounds corners; bars sharing a stack: id pile up within the bar marks. |
| with_line | A line mark bound to data_key:; dots: marks each point. |
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-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-motion-reveal] | The entrance clipPath rect (the ported area reveal) - the motion stylesheet scales it 0 -> 1; only when animate |
| [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-areas] | The area-mark group - a fill path plus top-curve stroke per series, clipped by the reveal rect while animating |
| [data-slot=chart-area] | One series' fill path (var(--color-<key>) or its gradient) |
| [data-slot=chart-area-stroke] | One series' top-curve stroke path (the source strokes the curve, never the area outline) |
| [data-slot=chart-bar-series] | One series' bar group |
| [data-slot=chart-bar] | One bar cell (a per-corner rounded-rect path) |
| [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-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-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-area | data-key | the series key | — |
| chart-area-stroke | data-key | the series key | — |
| chart-bar-series | data-key | the series key | — |
| chart-bar | data-key | the series key | — |
| chart-bar | data-index | the datum index | — |
| chart-bar | data-active | runtime - the tooltip controller marks the hovered index | — |
| chart-bar | data-motion-origin | when animate - the zero edge the entrance grows from | bottom · top |
| chart-line | data-key | the series key | — |
| chart-dots | 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 |
| svg | poetry--charts--tooltip (if tooltip?) | move on pointermove · leave on pointerleave · focus on focus · blur on blur · keydown on keydown · target svg |
| coordinates | poetry--charts--tooltip (if tooltip?) | target data |
| tooltip_layer | poetry--charts--tooltip (if tooltip?) | target tooltip |