Scatter Plot
Two measured variables plotted against each other, with an optional least-squares trend line and Pearson’s r. Points can carry a third dimension as area and a fourth as colour.
src/components/ui/scatter-plot.tsxAbout
From the component's own source, where it was written.
Two measured variables plotted against each other.
**Different from Chart's scatter variant, which is why this exists.** That one takes number[] and spaces the points evenly along x — the x axis is an index, so it answers "how did this change in sequence". A scatter plot answers "are these two things related", and for that x must be a *measured value* with its own scale. Plotting spend against conversion on an index axis silently destroys the relationship you were looking for.
**The trend line and r are opt-in and honest.** trend fits an ordinary least-squares line and reports Pearson's r beside it. r measures *linear* association only: a perfect parabola scores near zero, and a single outlier can carry r from 0.1 to 0.8. It is shown to two decimal places and never as a claim about causation.
**Points can carry a third and fourth dimension** — size for magnitude and group for category. Area, not radius, is proportional to size, because scaling the radius makes a doubled value look four times bigger, which is the classic way a bubble chart lies.
Axes get real ticks from the data range rather than a fixed 0–100 grid, and both are drawn outside the stretched plot group so the labels are not distorted by the aspect fit.
Install
npx astralyx-ui add scatter-plot
Usage
import { ScatterPlot } from '@/components/ui/scatter-plot'
<ScatterPlot points={points} trend xLabel="Spend" yLabel="Conversion %" />Composer
Change the props and the source below follows.
<ScatterPlot
points={points}
trend={true}
grid={true}
xLabel="Spend per campaign"
yLabel="Conversion rate"
/>Examples
Spend against conversion, with a fit
<ScatterPlot points={campaigns} trend xLabel="Spend" yLabel="Conversion rate" />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
ScatterPlot
Extends Omit<ComponentProps<'figure'>, 'height'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| points | ScatterPoint[] | required | The plot is stretched to fill its box, which turns an SVG circle into an ellipse. Positioned elements stay round at any aspect ratio. |
| height | number | 240 | — |
| xLabel | string | — | — |
| yLabel | string | — | — |
| trend | boolean | false | Ordinary least squares, with Pearson’s r. r measures linear association only — a parabola scores near zero, and one outlier moves it a long way. |
| grid | boolean | true | — |
| radiusRange | [number, number] | [3, 14] | Radius range in pixels, when points carry size. |
| xDomain | [number, number] | — | Fixed bounds. Defaults to the data range with 5% padding so nothing sits on the axis. |
| yDomain | [number, number] | — | Fixed bounds. Defaults to the data range with 5% padding so nothing sits on the axis. |
| valueFormat | (value: number) => string | DEFAULT_FORMAT | — |
| legend | boolean | true | — |
| emptyLabel | string | 'No data.' | — |
| label | string | 'Scatter plot' | — |
Types
ScatterPoint
Two measured variables plotted against each other.
| Field | Type | Description |
|---|---|---|
| x | number | — |
| y | number | — |
| size? | number | Scaling the radius makes a doubled value look four times bigger. Area is proportional, so it reads honestly. |
| group? | string | Colours the point and builds the legend. |
| label? | string | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| vs Chart scatter | measured x | Chart’s scatter variant spaces number[] evenly along an index. Here x is a value, which is what makes the relationship visible at all. |