Adapter Chart
The bring-your-own-engine path: a themed mount and chart-spec JSON for a client library (Chart.js, etc.) to draw, instead of poetry's server-side SVG.
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
Bring your own engine
The default engine renders every chart as server-side SVG, themed live by
CSS variables. When you need a different engine (typically canvas for very
large datasets, or a library your team already owns), pass
engine: and the same
poetry_chart call routes to the adapter
path, as the example below shows. The server validates and embeds the frozen
chart-spec v1 (a closed vocabulary; unknown keys raise at render) next to a
mount element, and the adapter registered under that name draws into the
mount. The controller owns the lifecycle: render on connect, destroy on
disconnect, repaint on theme flips. One trade to know: the adapter path
takes series: and
axes: arguments, not the compositional
slots; the slot grammar belongs to the default engine.
Poetry ships the reference adapter as a factory and zero dependencies; you import Chart.js yourself and hand the constructor in:
import Chart from "chart.js/auto"
import { registerChartAdapter, createChartJsAdapter } from "@poetry/charts"
registerChartAdapter("chartjs", createChartJsAdapter(Chart))
Its degradations are declared up front (the canvas tax): tooltips and
legends fall back to Chart.js built-ins, CSS variables resolve to frozen
pixels at paint so theme flips repaint the whole chart, per-part
data-slot styling does not exist on a
bitmap, and radial charts are unsupported.
Any engine fits the same seam. Register an object implementing the adapter protocol over the spec:
registerChartAdapter("myengine", {
supports: ["line", "bar"], // optional type allowlist
degradations: ["..."], // declared, never discovered
render(el, spec, { resolveColor }) { ... }, // required -> instance
destroy(instance, el) { ... }, // required
update(instance, spec) { ... }, // optional: live data updates
themeChanged(instance, spec, helpers) { ... } // optional: dark-mode repaint
})
resolveColor("var(--color-desktop)")
returns the concrete value at the chart's scope, the helper that closes the
gap between canvas engines and CSS-variable theming. And for markup that
needs no adapter at all,
poetry_chart_container(config:) is
engine-agnostic: it scopes the chart id, emits the per-series
--color-<key> variables for both
color modes, and carries the aspect-ratio chrome, so anything you place
inside it (hand-rolled SVG, a third-party widget) keeps the theming
contract.
Default
poetry draws charts as server-side SVG. This adapter is the opt-in path for a client engine (Chart.js, etc.) — it emits a themed mount and chart-spec for your registered adapter to draw into. These docs don't load a client engine, so the mount is empty here; the Code tab shows the mount and spec.
<%# The BYO-engine mount: engine: routes the same
poetry_chart call to the adapter path, which renders the Container
frame, a mount element, and the frozen chart-spec v1 as embedded JSON.
A registered client adapter draws inside the mount. This docs site
does not load registerChartAdapter or Chart.js yet, so the mount stays
empty here - the server-side seam (mount + spec) is what this example
shows, and it is fully inspectable in the page source. %>
<%# The adapter path takes series:/axes: ARGUMENTS (the closed spec), not
slots. The spec travels as JSON, so the data carries pre-formatted
month names - formatter lambdas cannot ride the payload. %>
<% data = [
{ month: "Jan", desktop: 186, mobile: 80 },
{ month: "Feb", desktop: 305, mobile: 200 },
{ month: "Mar", desktop: 237, mobile: 120 },
{ month: "Apr", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "Jun", 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_chart :bar, engine: "chartjs", data: data, config: config,
id: "adapter-default",
series: [{ data_key: :desktop }, { data_key: :mobile }],
axes: { x: { data_key: :month } },
label: "Bar chart (Chart.js adapter)" %>
</div>
API
Poetry::Charts::AdapterChart::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 |
|---|---|---|---|
| axes: | Object | The axis config ({ x:, y: } hashes), also spec-carried. | |
| config: | Object | required | The series config - key => { label:, color: } - naming and coloring every series. |
| data: | Object | required | The rows to plot, serialized into the spec. |
| engine: | String | required | The registered adapter's name; the controller hands it the mount and the spec. |
| id: | String | Explicit DOM id token, stable across renders; otherwise the chart gets a unique per-render id. | |
| label: | String | Accessible name for the mount; defaults to one built from the type and engine. | |
| series: | Object | required | The series list ({ data_key:, ... } hashes) - the closed spec's replacement for slots. |
| type: | Symbol | one of: area, bar, line, pie, radar, radial; required |
The chart type carried in the spec (see Spec::TYPES). |
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-adapter-mount] | The engine's drawing surface (role=img carrying the accessible label) - the registered adapter renders into it |
| [data-slot=chart-spec] | The frozen chart-spec v1, served as JSON in an application/json script for the adapter to consume |
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--adapter | registers · value engine |
| mount | poetry--charts--adapter | target mount |
| spec | poetry--charts--adapter | target spec |