Scatter Chart
A scatter chart for the relationship between two variables.
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 = [
{ height: 161, weight: 51 },
{ height: 167, weight: 59 },
{ height: 170, weight: 68 },
{ height: 174, weight: 66 },
{ height: 178, weight: 79 },
{ height: 183, weight: 84 },
{ height: 189, weight: 95 }
]
config = { sample: { label: "Sample", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_chart(:scatter, data: data, config: config, id: "scatter-default",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :height, name: "Height") %>
<% chart.with_y_axis(data_key: :weight, name: "Weight") %>
<% chart.with_scatter(key: :sample) %>
<% chart.with_tooltip %>
<% end %>
</div>
Bubbles
<%# with_z_axis sizes each point by a third dimension - the range is
marker AREA in px2 (recharts ZAxis semantics; r = sqrt(area/pi)). %>
<% data = [
{ height: 161, weight: 51, bmi: 19.7 },
{ height: 167, weight: 59, bmi: 21.2 },
{ height: 170, weight: 68, bmi: 23.5 },
{ height: 174, weight: 66, bmi: 21.8 },
{ height: 178, weight: 79, bmi: 24.9 },
{ height: 183, weight: 84, bmi: 25.1 },
{ height: 189, weight: 95, bmi: 26.6 }
]
config = { sample: { label: "Sample", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_chart(:scatter, data: data, config: config, id: "scatter-bubbles",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :height, name: "Height") %>
<% chart.with_y_axis(data_key: :weight, name: "Weight") %>
<% chart.with_z_axis(data_key: :bmi, range: [64, 400]) %>
<% chart.with_scatter(key: :sample) %>
<% chart.with_tooltip %>
<% end %>
</div>
Multiple
<%# Each with_scatter series can bring its own rows via data: - the
control group plots alongside the chart-level sample rows. %>
<% sample = [
{ height: 161, weight: 51 },
{ height: 167, weight: 59 },
{ height: 170, weight: 68 },
{ height: 174, weight: 66 },
{ height: 178, weight: 79 },
{ height: 183, weight: 84 },
{ height: 189, weight: 95 }
]
control = [
{ height: 158, weight: 62 },
{ height: 165, weight: 71 },
{ height: 172, weight: 60 },
{ height: 180, weight: 88 },
{ height: 186, weight: 77 }
]
config = { sample: { label: "Sample", color: "var(--chart-1)" },
control: { label: "Control", color: "var(--chart-2)" } } %>
<div class="w-full max-w-xl">
<%= poetry_chart(:scatter, data: sample, config: config, id: "scatter-multiple",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :height, name: "Height") %>
<% chart.with_y_axis(data_key: :weight, name: "Weight") %>
<% chart.with_scatter(key: :sample) %>
<% chart.with_scatter(key: :control, data: control) %>
<% chart.with_legend %>
<% chart.with_tooltip %>
<% end %>
</div>
Reference zones
<%# Reference marks annotate the plot area; error_key: names a row key
holding a symmetric offset (or [low, high]) drawn as a whisker. %>
<% data = [
{ height: 161, weight: 51, werr: 3 },
{ height: 167, weight: 59, werr: 4 },
{ height: 170, weight: 68, werr: 5 },
{ height: 174, weight: 66, werr: 4 },
{ height: 178, weight: 79, werr: 6 },
{ height: 183, weight: 84, werr: 5 },
{ height: 189, weight: 95, werr: 7 }
]
config = { sample: { label: "Sample", color: "var(--chart-1)" } } %>
<div class="w-full max-w-xl">
<%= poetry_chart(:scatter, data: data, config: config, id: "scatter-refs",
margin: { left: 12, right: 12 }) do |chart| %>
<% chart.with_grid %>
<% chart.with_x_axis(data_key: :height, name: "Height") %>
<% chart.with_y_axis(data_key: :weight, name: "Weight") %>
<% chart.with_scatter(key: :sample, error_key: :werr) %>
<% chart.with_reference_area(y1: 60, y2: 80, label: "target band") %>
<% chart.with_reference_line(x: 175) %>
<% chart.with_reference_dot(x: 172, y: 70, r: 10, label: "median") %>
<% chart.with_tooltip %>
<% end %>
</div>
API
Poetry::Charts::ScatterChart::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 | Default rows for series that don't bring their own data: - one hash per point. | |
| 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_grid | The gridlines: both directions by default. |
| with_legend | The legend row: align:, items:, and hide_icon:. |
| with_scatter | A point series colored by key:. data: gives it its own rows; error_key: adds error whiskers. |
| with_tooltip | The hover tooltip - per-point x/y(/z) rows under the series name. |
| with_x_axis | The numeric x axis: data_key: names the row key to plot; name: labels its tooltip row. |
| with_y_axis | The numeric y axis: data_key: names the row key to plot; name: labels its tooltip row. |
| with_z_axis | A third dimension sizing the markers: range: is marker AREA in px2 mapped linearly from the data_key: values. |
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-grid] | The gridline group (with_grid) - horizontal and/or vertical rules across the plot |
| [data-slot=chart-scatters] | The scatter-mark group - every series' points flattened with a global index |
| [data-slot=chart-scatter-point] | One data point (<circle>) - r carries the z-axis area sizing |
| [data-slot=chart-error-bars] | One series' error-whisker group (error_key:) - cap-stem-cap paths in the foreground color |
| [data-slot=chart-reference] | The reference-mark group (with_reference_line/_area/_dot), painted above the series |
| [data-slot=chart-x-axis] | The x-axis tick-label group (with_x_axis) |
| [data-slot=chart-y-axis] | The y-axis tick-label group (with_y_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-scatter-point | data-key | the series key | — |
| chart-scatter-point | data-index | the point's global index across every series | — |
| chart-scatter-point | data-active | runtime - the tooltip controller marks the hovered point | — |
| chart-error-bars | data-key | the series key | — |
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 |
| svg | poetry--charts--tooltip (if tooltip?) | enter on pointerover |
| coordinates | poetry--charts--tooltip (if tooltip?) | target data |
| tooltip_layer | poetry--charts--tooltip (if tooltip?) | target tooltip |