#poetry_area_chart(**, &)
Renders an area chart - filled trends over an ordered axis, optionally stacked.
<%= poetry_area_chart(data: data, config: config) do |c| %>
<% c.with_area data_key: :desktop %>
<% end %>
The charts gem's Ruby surface: the chart helpers, the shared chart chassis, and the geometry layer (scales, curves, ticks, paths).
Generated from the gem's source documentation. Component classes are documented on their own gallery pages — this page carries the framework surface.
module · app/helpers/poetry/charts/components_helper.rb
The poetry_chart_* view helpers - the agent-facing chart surface, from the chart-root dispatcher down to the per-family helpers.
| Constant | Description |
|---|---|
| CHART_TYPES | Chart type symbols mapped to their component class names - the dispatch table behind poetry_chart. |
Renders an area chart - filled trends over an ordered axis, optionally stacked.
<%= poetry_area_chart(data: data, config: config) do |c| %>
<% c.with_area data_key: :desktop %>
<% end %>
Renders a bar chart - grouped or stacked rectangles per category.
<%= poetry_bar_chart(data: data, config: config) do |c| %>
<% c.with_bar data_key: :desktop %>
<% end %>
Renders a chart of the given type through one dispatcher; the block receives the chart component for slot composition. Unknown types raise with the list of known ones.
type
(Symbol, String)
— one of the CHART_TYPES keys
engine
(Symbol, nil)
— when set, routes the call to the
client-side adapter mount, which takes series:/axes: arguments
(the closed spec) instead of slots
<%= poetry_chart(:area, data: data, config: config) do |c| %>
<% c.with_area data_key: :desktop %>
<% end %>
Renders the chart container - the sized, theme-scoped wrapper that emits var(--color-<key>) for every configured series and hosts the chart plus its tooltip and legend.
<%= poetry_chart_container(config: config, class: "h-64") do %>
<%= poetry_area_chart(data: data, config: config) %>
<% end %>
Renders a standalone legend for the configured series - a swatch plus label per entry.
<%= poetry_chart_legend_content(config: config) %>
Renders the tooltip panel a chart's hover layer positions and fills - place it inside the container alongside the chart.
<%= poetry_chart_tooltip_content(indicator: :line) %>
Renders a line chart - one stroked curve per series.
<%= poetry_line_chart(data: data, config: config) do |c| %>
<% c.with_line data_key: :desktop %>
<% end %>
class · lib/poetry/charts/config.rb
The chart config contract: series key -> { label:, icon:, color: } or { label:, icon:, theme: { light:, dark: } }. The config is the SINGLE place series get their human labels and colors; the container's <style> emission, the tooltip chrome, and the legend chrome all resolve through it.
Values land inside a <style> element and inline style attributes, so every color (and key) is validated against a conservative character set at wrap time - a config can never smuggle CSS out of its block.
config = Poetry::Charts::Config.new(
revenue: { label: "Revenue", color: "var(--chart-1)" }
)
config.label_for("revenue") # => "Revenue"
| Constant | Description |
|---|---|
| COLOR | Any CSS color form (var(--chart-1), #hex, oklch(...), named...) minus everything that could terminate a declaration or escape the block. |
| KEY | A series key becomes a --color-<key> custom property. |
| THEMES | The theme map's allowed modes. |
Accepts a Config (pass-through) or a Hash keyed by series name.
source
(Config, Hash, nil)
—
The entry for a series key.
key
(String, Symbol)
—
The entries that carry a color (flat or themed) - the set the container's <style> emission covers, in config order.
Returns the value of attribute entries.
Builds and validates entries from a Hash keyed by series name; a CSS-unsafe key or color raises here, at wrap time.
hash
(Hash)
—
Returns (Config) — a new instance of Config
Every series key, in config order.
The label for a series key, falling back to the key itself - the tooltip/legend resolution rule.
key
(String, Symbol)
— the series key
fallback
(String, nil)
— preferred over the key when no label is set
The config back as a plain Hash (label/color/theme per key), compacted.
class · lib/poetry/charts/spec.rb
The chart-spec: the CLOSED, VERSIONED description every poetry chart compiles to. The server engine consumes it Ruby-side; swappable adapters consume the same spec JSON-side through the duck-typed protocol (render(el, spec) / update / destroy). No engine-specific key ever enters this schema - engine styling lives in the adapter, declared; a pass-through options bag would tie call sites to one engine and the spec could never close.
Keys ride the wire camelCased (dataKey, stackId) - one vocabulary on both sides of the seam.
Poetry::Charts::Spec.new(
type: :bar, data: rows, series: [{ data_key: :revenue }]
).to_json
| Constant | Description |
|---|---|
| AXIS_KEYS | The closed key set an axis entry may carry. |
| SERIES_KEYS | The closed key set a series entry may carry. |
| TYPES | The chart types the spec can describe. |
| VERSION | The spec schema version stamped into every payload. |
Returns the value of attribute axes.
Returns the value of attribute config.
Returns the value of attribute data.
type
(Symbol, String)
— one of TYPES
data
(Array<Hash>)
— the rows, one hash per data point
series
(Array<Hash>)
— series entries; data_key: is required
axes
(Hash)
— per-axis settings (SERIES_KEYS/AXIS_KEYS closed sets)
config
(Config, Hash, nil)
— label/color config, wrapped via Config.wrap
Returns (Spec) — a new instance of Spec
Returns the value of attribute series.
The wire form: string keys, camelCased entry keys, the version stamped in.
The wire form serialized - what the spec <script> embeds.
Returns the value of attribute type.
module · lib/poetry/charts.rb
The poetry component family's shared root namespace.
module · lib/poetry/charts.rb
poetry's chart tier: charts as server-rendered SVG. Ruby runs the whole geometry pipeline - data -> domains -> scales -> ticks -> points -> paths, with decimal-exact nice ticks - and the finished chart ships in the initial HTML: no-JS/print/email valid, themed by CSS variables (--chart-1..5 + per-chart --color-<key>), dark mode with zero re-render. Stimulus chrome adds tooltip/legend/active interactivity by reading SERVER-EMBEDDED coordinates - no chart math in the browser.
Engines stay swappable (three doors): the container contract is engine-agnostic; every chart also compiles to a closed, VERSIONED chart-spec consumed by duck-typed adapters (render/update/destroy - a canvas adapter ships as the reference); client-rendered chart libraries remain reachable through a Stimulus-mounted island.
poetry_chart :bar, data: rows, series: [{ data_key: :revenue }]
| Constant | Description |
|---|---|
| CURVES | The curve interpolation whitelist every series slot validates against. |
| POSITIONAL_PARAM_KINDS | Parameter kinds that count toward a helper's positional arity. |
| VERSION | The gem version. |
The tooltip display string shared by every chart family (matches TooltipContent's Row: delimited numerics from RAW values so integers stay integers, verbatim strings, nil for missing).
The registry builder this gem commits from (the poetry-ui shared- builder rule: rake registry:generate/verify and the sync test share ONE construction). helper_args carries each poetry_* helper's max positional arity from its real signature - poetry_chart(type, ...) legitimately takes one, which is exactly why arity is an emitted per-helper fact and never a convention.
The editorial per-chart descriptions merged into the registry (component_path => one-liner, from config/component_descriptions.yml). Absent file -> nil, so the registry stays lint-identical without it.
The installable-item projection, boot-free from the COMMITTED registry - the docs site aggregates this with poetry-ui's for /r/*.json.
Gem root (the directory containing lib/, app/, config/).
module · app/components/poetry/charts/adapter_chart/component.rb
The bring-your-own-engine chart mount.
module · app/components/poetry/charts/area_chart/component.rb
The area chart family.
class < Data · lib/poetry/charts.rb
The axis capture shape the cartesian family slots accumulate into (scatter carries its own - both axes numeric, different fields).
Returns the value of attribute data_key
Returns (Object) — the current value of data_key
Returns the value of attribute tick_count
Returns (Object) — the current value of tick_count
Returns the value of attribute tick_formatter
Returns (Object) — the current value of tick_formatter
Returns the value of attribute tick_margin
Returns (Object) — the current value of tick_margin
module · app/components/poetry/charts/bar_chart/component.rb
The bar chart family.
class · lib/poetry/charts/cartesian.rb
The server-side cartesian layout pipeline: data + series -> plot rectangle, scales, ticks, and per-series pixel points - computed top-down in one pass, everything a renderer needs before writing a path.
Layout conventions: default margin 5 on every side, x-axis strip height 30 at the bottom when shown, category x positions from a zero-padding point scale (first/last categories AT the plot edges - add left/right margin 12 when edge labels need room), numeric y domain [0, auto] niced decimal-exactly (implicit tick count 5), stacking per stack id with :none/:expand offsets.
plot = Poetry::Charts::Cartesian.new(
data: rows, series: series, width: 600, height: 300
)
plot.points(series.first) # => [{x:, y0:, y1:, value:}, ...]
| Constant | Description |
|---|---|
| DEFAULT_MARGIN | The default plot margin - a slim, even inset on all sides. |
| LAYOUTS | The chart orientations: :vertical columns, or :horizontal bars. |
| OFFSETS | The stack baseline modes: raw values, or 100%-normalized. |
| X_AXIS_HEIGHT | The bottom strip reserved for the category axis when shown. |
| Y_AXIS_WIDTH | The reserved left strip - where a visible value axis's labels (or the horizontal layout's category labels) live. |
One category band's width (0 on a point scale).
The area baseline: the y pixel of 0, clamped into the domain so an all-positive domain pins the baseline at its bottom edge.
The category values: the x_key column, else bare row indexes.
Point for line/area (categories AT the edges); band for bars (a zero-padding band - the bar gaps come from the gap math inside the band, not scale padding). Horizontal layout runs the category scale down the Y side.
Compact per-series pixel coordinates the tooltip controller reads - no chart math in the browser.
Returns the value of attribute height.
Whether categories run down the Y side (bars growing rightward).
data
(Array<Hash>)
— the rows, one hash per category
series
(Array<#key>)
— series entries (key + optional stack id)
width
(Numeric)
— outer SVG width in pixels
height
(Numeric)
— outer SVG height in pixels
x_key
(String, Symbol, nil)
— the category key; nil indexes rows
margin
(Hash)
— per-side overrides merged over DEFAULT_MARGIN
category_axis
(Boolean)
— reserve the category-axis strip
value_axis
(Boolean)
— a visible value axis reserves the left strip
y_tick_count
(Integer)
— requested tick count for the niced domain
offset
(Symbol)
— :none, or :expand for 100%-stacked
x_scale_type
(Symbol)
— :point (line/area) or :band (bars)
layout
(Symbol)
— :vertical (values up y) or :horizontal
Returns (Cartesian) — a new instance of Cartesian
Returns the value of attribute layout.
Returns the value of attribute margin.
Returns the value of attribute offset.
The plot rect's bottom edge, inset for the category axis strip.
The plot rect's left edge, inset for a reserved left strip.
The plot rect's right edge.
The plot rect's top edge.
[{x:, y0:, y1:, value:}] for one series entry - stacked entries ride their stack group's offsets; independent entries base on the baseline. NaN values (missing data) flow through as NaN, which the generators' defined-gap machinery turns into path gaps.
entry
(#key)
— a series entry (key + optional stack id)
Returns (Array<Hash>) — one orientation-aware point hash per category
Returns the value of attribute width.
The per-category CENTER - where ticks, vertical grid lines, and the tooltip's hit columns sit (band centers; point positions verbatim).
Each category's scale position (a band's leading edge).
The category scale over the category range (point or band).
The niced value domain - the first and last tick.
The value scale: y in the vertical layout (inverted - SVG y grows down), x in the horizontal one.
Returns the value of attribute y_tick_count.
The niced value ticks ([0, 1] when 100%-stacked).
module · app/components/poetry/charts/cartesian_family.rb
The shared cartesian slot grammar (area/line/bar/composed): the axis/grid/legend/tooltip slots plus the readers that force their lazy evaluation. Lambda slots accumulate config into ivars and return nil (the breadcrumb pattern - slot wrappers do not delegate to lambda return values); readers force evaluation via the slot predicate (slots evaluate lazily).
Slots register at the include site because declaration order IS the registry's slot order - include this AFTER the family's mark slots. The value axis is a class-method hook: a family whose Y side diverges (bar - the horizontal orientation moves the category axis there) defines its own value_axis_slot before including.
class StepChart::Component < Poetry::Core::Component
include Poetry::Charts::ChartFamily
# Mark slots first - declaration order is the registry's slot order.
renders_many :steps, lambda { |data_key:| ... }
include Poetry::Charts::CartesianFamily
end
module · app/components/poetry/charts/cartesian_family.rb
The class-level hooks the include site drives.
Declares the default numeric Y axis slot (tick count 3, no data key). Families whose value axis diverges override this before including the concern.
module · app/components/poetry/charts/chart_family.rb
The identity chassis every chart family shares: the data-chart id scope, the wrapped config, the accessible SVG name, and the SVG number formatter. Families supply svg_label_prefix (the accessible name's chart-type lead-in); a family whose default name reads from a different surface (scatter: series keys through the config) overrides svg_label itself.
class Sparkline::Component < Poetry::Core::Component
include Poetry::Charts::ChartFamily
option :config, ActiveModel::Type::Value.new, required: true
option :id, :string
option :label, :string
# The accessible name's chart-type lead-in.
def svg_label_prefix = "Sparkline"
end
The config: option wrapped as a {Poetry::Charts::Config} - series entries with labels and colors.
The data-chart scope: explicit id when given (stable for tests / multiple charts), else unique per render.
The accessible name for the role=img SVG: explicit label: or a sensible default from the configured series.
module · app/components/poetry/charts/composed_chart/component.rb
The composed chart family.
class < Data · lib/poetry/charts/config.rb
One validated config entry: a series key with its label, icon, and flat or themed color.
Returns the value of attribute color
Returns (Object) — the current value of color
The per-theme color: the flat color, or the theme map's value.
Whether the entry carries any color (flat or themed).
Returns the value of attribute icon
Returns (Object) — the current value of icon
Returns the value of attribute key
Returns (Object) — the current value of key
Returns the value of attribute label
Returns (Object) — the current value of label
Returns the value of attribute theme
Returns (Object) — the current value of theme
module · app/components/poetry/charts/container/component.rb
The chart container.
class < Rails::Engine · lib/poetry/charts/engine.rb
The Rails engine: wires poetry-charts into the host app - component autoload paths, the Stimulus controllers manifest, the view helper, preview and asset paths, and the importmap pins. Loading the gem is the only integration step.
gem "poetry-charts"
module · lib/poetry/charts/geometry.rb
The geometry core: the tick, scale, and shape math the charts stand on - array ticks, linear/band/point scales, line/area/curve generators, stack layout, and decimal-exact nice-ticks. Every piece is oracle-tested: committed fixtures (test/support/generate_geometry_fixtures.mjs) pin the expected outputs byte-for-byte, alongside translated spec cases.
JS number semantics are part of the contract - path strings must match the fixture output byte-for-byte - so rounding and stringification go through js_round / js_number below, never through Ruby defaults (Ruby rounds half away from zero and prints "80.0"; JS floors x+0.5 and prints "80").
Poetry::Charts::Geometry.js_number(80.0) # => "80"
JS Number#toString for the values that appear in SVG path data: integral doubles print bare ("80", not "80.0"), -0 prints "0", and everything else uses shortest round-trip decimal (Ruby and V8 agree on shortest-repr in the post-rounding magnitude range; the exponent guard covers the sub-1e-4 corner where Ruby switches early).
JS Math.round: floor(x + 0.5) - differs from Float#round at negative halves (JS rounds -2.5 to -2; Ruby to -3).
JS truthiness for the curve state machines (the line state flag runs nil | 0 | 1 | NaN): nil, 0, and NaN are falsy.
class · lib/poetry/charts/geometry/area.rb
The area generator: the filled band between a top line (x/x1, y1) and a baseline (x0, y0), walked forward along the top and BACKWARD along the buffered baseline per defined-segment (the x0z/y0z buffers). Stacked areas feed y0/y1 from Stack series; simple areas use a constant y0 (the axis line).
Poetry::Charts::Geometry::Area.new(y0: 250.0).path(points)
Returns (Area) — a new instance of Area
The SVG path for the data (nil when nothing was defined).
data
(Enumerable)
—
module · lib/poetry/charts/geometry/curve.rb
The curve state machines - including the _line undefined/0/1/NaN dance that decides where subpaths close (JS truthiness via Geometry.js_truthy?; 1 - undefined becomes NaN via js_flip). The set matches the families' curve: whitelist: linear, step (+before/after), natural, and monotone_x.
Poetry::Charts::Geometry::Curve.build(:natural, Path.new)
| Constant | Description |
|---|---|
| REGISTRY | Curve name -> builder for its state machine. |
A curve state machine writing into the given path context.
JS 1 - line where line may be undefined (nil) or NaN.
class · lib/poetry/charts/geometry/line.rb
The line generator: data -> SVG path string through a curve state machine, with defined-gaps starting new subpaths (a single toggle loop). Accessors are lambdas (d, i), symbols/strings (hash key lookup), or numeric constants; x/y default to the [x, y] pair convention.
Poetry::Charts::Geometry::Line.new(curve: :monotone_x).path(points)
Returns (Line) — a new instance of Line
The SVG path for the data (nil when nothing was defined).
data
(Enumerable)
—
module · lib/poetry/charts/geometry/line.rb
Accessor coercion shared by the generators.
A (d, i) lambda from a lambda, key, constant, or the default.
module · lib/poetry/charts/geometry/nice_ticks.rb
Adapted from an MIT-licensed source (source and license in THIRD_PARTY_NOTICES.md).
The nice-ticks algorithm on BigDecimal, so the tick values stay decimal-exact. Every operation is decimal, not binary: values construct from the double's shortest decimal string, remainders truncate with the dividend's sign (#remainder), division carries 20 significant digits (PRECISION), and digit counts come from BigDecimal#exponent (exactly floor(log10) + 1).
Oracle: translated spec cases pin the expected tick values.
Poetry::Charts::Geometry::NiceTicks.nice_ticks([0, 97], 5)
| Constant | Description |
|---|---|
| PRECISION | Significant digits carried through every division. |
| SNAP_STEPS | The step multiples the :snap125 mode snaps to. |
The default step function: amend the rough step to a value that reads well at its order of magnitude.
The step + tick bounds for an interval (recursive: a correction factor grows the step until tickCount ticks cover the interval).
Coerce to BigDecimal via the double's shortest decimal string.
Digit count: 1 for [1,10), 0 for [0.1,1), -1 for [0.01,0.1)... BigDecimal#exponent IS floor(log10(|v|)) + 1, exactly.
Nice-stepped ticks CONSTRAINED to [min, max] - the domain boundary always closes the list.
Nice ticks for [min, max] - ticks may run OUTSIDE the interval to stay round.
[start, end) with a fixed decimal step.
The opt-in snap125 step: snap to 1 / 2 / 2.5 / 5 at each order of magnitude.
The step function a mode selects.
Ticks when min == max: center a window of tickCount steps on the value.
The interval sorted ascending.
class · lib/poetry/charts/geometry/path.rb
The path buffer the shape generators write into: move_to / line_to / bezier_curve_to / quadratic_curve_to / close_path. Numbers are rounded to digits decimals with JS Math.round semantics and stringified as JS does (digits defaults to 3) - the contract that keeps poetry's path strings byte-equal to the geometry fixtures. Internal cursor state keeps FULL precision (rounding is output-formatting only).
The arc/sector verbs are deliberately absent - polar sector paths are built by Polar, not through this class.
path = Path.new
path.move_to(0, 0)
path.line_to(10, 20.5)
path.to_s # => "M0,0L10,20.5"
A cubic curve to (x, y) with two control points.
Closes the current subpath back to its start (a no-op before any move).
Whether nothing has been written yet.
digits
(Integer, nil)
— output rounding decimals; nil disables
rounding (full-precision output)
Returns (Path) — a new instance of Path
A straight segment to (x, y).
Starts a new subpath at (x, y).
A quadratic curve to (x, y) with one control point.
The accumulated SVG path data.
module · lib/poetry/charts/geometry/scale/band.rb
The scale namespace: Linear, Band, and Point.
class · lib/poetry/charts/geometry/scale/band.rb
A band scale: categorical domain -> evenly stepped positions with inner/outer padding and alignment. Point is band with padding_inner = 1 (bandwidth 0), where padding: drives the outer padding - the axis shape line/area charts position categories with.
Poetry::Charts::Geometry::Scale::Band
.new(domain: %w[a b c], range: [0, 300]).positions
Convenience for the common single padding: knob - one value sets inner AND outer padding.
The band's leading-edge position for a category (nil when the category is unknown).
Returns the value of attribute align.
Returns the value of attribute bandwidth.
The band's leading-edge position for a category (nil when the category is unknown).
Returns the value of attribute domain.
Returns (Band) — a new instance of Band
Returns the value of attribute padding_inner.
Returns the value of attribute padding_outer.
Returns the value of attribute positions.
Returns the value of attribute range.
Returns the value of attribute step.
class · lib/poetry/charts/geometry/scale/linear.rb
A linear scale reduced to the closed poetry surface: a two-point numeric domain/range with bimap normalization (descending domains supported), ticks via Geometry::Ticks, and the nice() domain extension. Degenerate domains map every input to the range midpoint.
Poetry::Charts::Geometry::Scale::Linear
.new(domain: [0, 100], range: [300, 5]).call(50)
The range value for a domain value.
value
(Numeric)
—
The range value for a domain value.
value
(Numeric)
—
Returns the value of attribute domain.
domain
(Array<Numeric>)
— the two-point input interval
range
(Array<Numeric>)
— the two-point output interval
Returns (Linear) — a new instance of Linear
The domain value for a range value - the inverse map.
value
(Numeric)
—
Extends the domain to tick-increment boundaries, iterating until the increment is stable. Returns a NEW scale (poetry immutability).
Returns the value of attribute range.
Float-exact ticks across the domain.
class < Poetry::Charts::Geometry::Scale::Band · lib/poetry/charts/geometry/scale/band.rb
A point scale: a band with padding_inner pinned to 1 - every category is a zero-width position, padding: is the outer padding.
Poetry::Charts::Geometry::Scale::Point
.new(domain: %w[a b c], range: [0, 300]).positions
Returns (Point) — a new instance of Point
class · lib/poetry/charts/geometry/stack.rb
The stack layout (declaration order): rows x series keys -> per-series [base, top] pairs. Missing values behave as JS +undefined = NaN (the offsets carry the exact NaN fallbacks). :expand normalizes each row to sum 1; :diverging routes negatives below the axis.
Poetry::Charts::Geometry::Stack.new(keys: %w[a b]).series(rows)
| Constant | Description |
|---|---|
| OFFSETS | The stack baseline modes. |
Returns (Stack) — a new instance of Stack
The stacked series for the rows, offsets applied.
data
(Enumerable)
— the rows
class < Struct · lib/poetry/charts/geometry/stack.rb
One stacked series: its key, order index, and [base, top] points.
The [base, top] pair at one row index.
Returns the value of attribute index
Returns (Object) — the current value of index
Sets the attribute index
value
(Object)
— the value to set the attribute index to.
Returns (Object) — the newly set value
Returns the value of attribute key
Returns (Object) — the current value of key
Sets the attribute key
value
(Object)
— the value to set the attribute key to.
Returns (Object) — the newly set value
The number of rows stacked.
Returns the value of attribute points
Returns (Object) — the current value of points
Sets the attribute points
value
(Object)
— the value to set the attribute points to.
Returns (Object) — the newly set value
module · lib/poetry/charts/geometry/ticks.rb
Float-exact tick generation: ticks / tick_increment / tick_step. The 1-2-5-10 step selection runs against square-root thresholds, and the inverted-increment encoding (negative inc = divisor) keeps tick values float-exact - a sub-1 step divides by an integer instead of multiplying by a fraction.
Poetry::Charts::Geometry::Ticks.ticks(0, 10, 5) # => [0, 2, 4, 6, 8, 10]
| Constant | Description |
|---|---|
| E10 | The step-10 selection threshold: sqrt(50). |
| E2 | The step-2 selection threshold: sqrt(2). |
| E5 | The step-5 selection threshold: sqrt(10). |
The raw increment for the run - negative encodes a divisor.
The absolute step size for the run (the increment decoded, signed by direction).
About count evenly stepped, float-exact values covering [start, stop].
class < Data · lib/poetry/charts.rb
The grid capture shape - which rule directions render (radar carries its own, with polygon/circle rings).
Returns the value of attribute horizontal
Returns (Object) — the current value of horizontal
Returns the value of attribute vertical
Returns (Object) — the current value of vertical
module · app/components/poetry/charts/legend_content/component.rb
The chart legend chrome.
module · app/components/poetry/charts/line_chart/component.rb
The line chart family.
module · app/components/poetry/charts/live.rb
Live mode: a chart that opts in with live: true embeds a {spec, frame} payload the client renderer recomputes geometry from when data changes too often to round-trip to the server. spec is the FROZEN spec v1 built by the same Poetry::Charts::Spec the adapter seam uses (the spec stays closed); frame is a PRIVATE engine envelope carrying the geometry-affecting knobs the spec deliberately omits. Everything else about the chart stays server-rendered.
Live charts cannot carry Ruby lambdas to the browser: tick formatters and label slots raise a teaching error - format data host-side (pre-formatted category strings) instead.
| Constant | Description |
|---|---|
| BRUSH_GAP | Pixels between the plot's bottom margin and the brush strip. |
| CONTROLLER | The live controller's full-string identifier. |
| WINDOW_CONTROLLER | The window controller's full-string identifier. |
module · app/components/poetry/charts/live.rb
The class-level macro families call to opt into live mode.
Declares the live-mode surface: the live: and zoom: options plus the with_brush slot.
module · app/components/poetry/charts/motion.rb
Shared animation surface for the chart families: chart-level options (animate / animation_duration / animation_easing / animation_begin, defaults set per family), emitted as data-animate plus --poetry-motion-* custom properties on the SVG. The animations themselves are CSS (the motion stylesheet) and the motion controller - the server computes all geometry; the client only interpolates between server-computed states.
| Constant | Description |
|---|---|
| CONTROLLER | The motion controller's full-string identifier. |
| EASINGS | The allowed animation_easing keywords. |
Hook: families append extra custom properties (radar ships the polar center so CSS can scale from it).
module · app/components/poetry/charts/motion.rb
The class-level macro families call to declare their animation options.
Declares the animation options with the family's defaults (bar 400ms, pie delayed 400ms, scatter 400ms linear, everything else 1500ms ease with no delay).
module · app/components/poetry/charts/pie_chart/component.rb
The pie chart family.
module · lib/poetry/charts/polar.rb
The polar geometry: pie-sector accumulation, tangent-circle corner rounding, and wedge/ring paths. Angles are degrees COUNTERCLOCKWISE from 3 o'clock, negated into SVG's y-down plane by polar_to_cartesian; pies start at angle 0 and sweep to 360.
Poetry::Charts::Polar.pie_sectors([3, 1], padding_angle: 2)
| Constant | Description |
|---|---|
| RADIAN | Degrees-to-radians factor. |
The largest radius fitting the plot: half the shorter side.
"80%" of the max radius, or a plain number.
The pie accumulation: values -> per-slice angles. Zero values collapse (and skip padding); paddings live BETWEEN non-zero slices (full circles pad after the last slice too, closing the ring).
The [x, y] point at (radius, angle) from the center, in SVG's y-down plane.
The wedge/ring path. The delta clamps at 359.999 so a full circle's endpoints never coincide.
The ring segment with all four corners rounded by tangent circles. Falls back to the plain path when the sweep is too small to fit the corners.
-1, 0, or 1 by the value's sign.
The corner circle tangent to an arc (at radius) and a radial edge (at angle) - the rounded-corner primitive for the radial bar's corner_radius.
module · app/components/poetry/charts/polar_family.rb
The polar family chassis (pie/radar/radial): the shared margin + plot/center geometry, and the per-sector pointer hit - polar marks are hit by pointerover on the marked sector/wedge, not bisect, so the svg gains the enter action after TooltipWiring's pointer/ keyboard set (include order is emission order - include this after TooltipWiring).
class GaugeChart::Component < Poetry::Core::Component
include Poetry::Charts::ChartFamily
include Poetry::Charts::TooltipWiring
include Poetry::Charts::PolarFamily
include Poetry::Charts::PolarFamily::SingleSeriesTooltip
end
| Constant | Description |
|---|---|
| MARGIN | The default polar margin - a slim, even inset on all sides. |
module · app/components/poetry/charts/polar_family.rb
The single-series polar tooltip (pie/radial): the FIRST series drives the chrome, and per-index names/colors retint the one row. Families supply polar_items (the sector geometry), polar_anchor (where the tooltip anchors on one item), and polar_value_rows (the rows the values read from). Radar keeps TooltipWiring's multi-series chrome and its own payload, so it includes PolarFamily alone.
module · app/components/poetry/charts/radar_chart/component.rb
The radar chart family.
module · app/components/poetry/charts/radial_bar_chart/component.rb
The radial bar chart family.
module · app/components/poetry/charts/reference_marks.rb
Reference marks - annotation lines, areas, and dots - for every cartesian family. Values speak the chart's own axes - categories on the category axis (the band/point center), numbers on the value axis (scatter overrides both to numeric) - and render as a single group painted ABOVE the series so annotations stay readable over the marks. Labels are strings (the live rule: no lambdas).
Hosts provide ref_x_pixel/ref_y_pixel (the concern's defaults speak cartesian), plot edges via cartesian, css(:reference_line/:reference_area/:tick), and fnum. Vertical layouts only (a horizontal bar raises - a declared limit).
module · app/components/poetry/charts/scatter_chart/component.rb
The scatter chart family.
class · lib/poetry/charts/theme_style.rb
Turns a chart's Config into the scoped per-series custom properties, one block per theme -
[data-chart=chart-revenue] { --color-desktop: var(--chart-1); } .dark [data-chart=chart-revenue] { --color-desktop: oklch(...); }
so series markup (SVG fills, tooltip indicators, legend swatches) can reference var(--color-<key>) and follow theme flips with ZERO re-render. Emission is safe by construction: Config validated every key and color at wrap time.
Poetry::Charts::ThemeStyle.new(id: "chart-revenue", config: config).css
| Constant | Description |
|---|---|
| THEMES | Theme name -> selector prefix (the .dark class convention). |
The stylesheet text, or nil when no entry carries a color - a colorless config renders no <style> element at all.
id
(String)
— the container's data-chart identifier
config
(Config, Hash)
— the chart config the colors come from
Returns (ThemeStyle) — a new instance of ThemeStyle
module · app/components/poetry/charts/tooltip_content/component.rb
The chart tooltip chrome.
module · app/components/poetry/charts/tooltip_layer/component.rb
The chart families' internal hover-tooltip mount.
module · app/components/poetry/charts/tooltip_wiring.rb
Shared tooltip wiring for the chart families: the frame wrapper carries the controller, the SVG carries targets/actions plus the accessibilityLayer floor (focusable, role=application, arrows walk categories), and the hidden chrome pre-renders per-series rows the controller text-swaps - zero chart math in the browser.
| Constant | Description |
|---|---|
| CONTROLLER | Full-string controller identifier - the bare :tooltip shorthand would be ambiguous with the core tooltip controller. |
| LEGEND_OPTIONS | The with_legend options forwarded to the LegendContent child. |
The wiring is declared, not hand-built: the frame carries the controller + sync value, the SVG carries the target and the pointer/keyboard actions (the accessibilityLayer floor), and the coordinates <script> is the data target the controller reads.