ScatterChart
Two quantities against each other as points, optionally grouped by colour and sized by a third value for a bubble chart. Keep colour groups to three — any two points can end up adjacent, which is a harder test than a bar chart's neighbours.
<UiScatterChart :data="..." :xKey="..." :yKey="..." :ariaLabel="..." />Needs the charts module
This component ships in @jgastager/bundy-ui/charts, a separate module so projects that never draw a chart don't carry the paint classes these need. Nothing extra to install — just add the module entry. See Charts.
Data
requireddata is one object per point, and unlike the series-based charts the rows stay in exactly the shape you have them — nothing needs reshaping into series first. The props below name which fields to read, so the same array can drive a plain scatter, a grouped one and a bubble chart without being touched.
Rows stay in their own shape here, unlike the series-based charts — x-key/y-key name the fields, and series-key/size-key are optional.
X key
requiredxKey names the field holding each point's horizontal value. It has to be numeric — a scatter's whole job is placing two quantities against each other, so a categorical x belongs on a bar chart instead. Grid rules stay on by default here, because reading a point means recovering two coordinates rather than one.
Y key
requiredyKey names the numeric field for the vertical position. Which of the two quantities goes here is a real choice: convention puts the thing you think is caused on the y axis, so a reader scanning left to right sees the effect follow the cause.
Series key
seriesKey groups the points by a field, one palette slot each. Keep this to three groups: a bar chart only ever puts neighbouring colors side by side, but a scatter can place any two points together, and past three the palette can no longer guarantee two arbitrary groups are distinguishable. Beyond that, facet into small multiples instead.
- Search
- Social
Keep series-key to three groups. Colours only have to survive against their neighbours in a bar or line chart, but a scatter can place any two points side by side; past three, the palette can't guarantee two arbitrary groups are distinguishable. Facet into small multiples instead of adding a fourth.
Size key
sizeKey turns it into a bubble chart. The value maps to bubble area , not radius — mapping it to radius makes a doubled value look four times as large. fill-opacity sits below 1 by default so overlapping bubbles still show what's underneath.
- Search
- Social
size-key maps to bubble *area*, not radius — mapping a value to radius directly makes a doubled value look four times as large.
ARIA label
requiredariaLabel is required rather than optional: a cloud of points is completely opaque to a screen reader, so this is the only description of the chart that exists. Name the relationship the scatter shows — that's the finding — rather than the geometry.
ARIA description
ariaDescription carries the detail the label shouldn't — the spread, an outlier, where the relationship breaks down. It's read after the label, so write the two as a headline and a paragraph rather than repeating one inside the other.
Color
color is the paint for the dots when there's no seriesKey to group them: without a grouping field every point means the same thing, so they all take one colour, and this is how that colour stops being primary. It's ignored entirely once seriesKey is set, where colors takes over.
Colors are the library's own semantic ones — primary, secondary, success, warning, error, info, pending, neutral — and any shade of them, primary-400 through primary-950, exactly as the bg-* utilities spell them. Set color per series (or per item) to pin one; leave it unset to take the next slot of the default sequence. neutral and secondary are never handed out automatically, so an unlabelled series never lands on them.
Colors
colors pins a paint per group, matched positionally to the order the groups first appear in data — so reordering your rows reorders the palette. Reach for it when a group has an identity the reader already ties to a colour; groups past the end of the list fall back to the default sequence.
- Search
- Social
Radius
radius is the dot size in pixels for a plain scatter, four by default. Shrink it when the cloud is dense enough that dots start merging, and grow it when there are only a handful of points and each one is worth pointing at. It's ignored once sizeKey turns the chart into a bubble chart.
Radius range
radiusRange sets the smallest and largest bubble in pixels; everything between is interpolated on area. Widen it when the values span orders of magnitude, narrow it when the points are dense enough that big bubbles start swallowing their neighbours.
Fill opacity
fillOpacity sits below 1 by default because overlap is the normal state of a scatter — it's how you see through a crowded cloud instead of losing whatever is underneath. It's also what hovering fades *relative to*, so the highlight keeps working at any setting.
fill-opacity sits below 1 by default because overlap is the normal state of a scatter. Solid dots hide how many points share a spot.
X label
xLabel titles the horizontal axis. It matters more on a scatter than on most charts: the ticks are bare numbers, and without a title nothing on the plot says what quantity they measure.
Y label
yLabel titles the vertical axis. Set both titles on a scatter as a rule — the pair is what turns two anonymous number lines into a stated relationship.
X format
xFormat formats the horizontal ticks and the x value in the tooltip together, so a unit written once shows up in both. Both axes are numeric here, which is why each gets its own formatter rather than sharing one.
Y format
yFormat does the same for the vertical axis. A scatter's tooltip names both fields it read, so formatted units carry straight into it — worth setting even when the axis ticks are short enough to read bare.
Grid
grid draws rules on both axes and is on by default here, unlike some of the other charts — reading a point means recovering two coordinates, and without rules the eye has to travel to both axes unaided. Drop it only on a thumbnail where nobody is reading values off the plot.
Tooltip
tooltip is the panel that follows the pointer, on by default, and the only place a point's exact pair of values appears. Turn it off for a decorative distribution thumbnail where the shape is the whole reading.
Hovering dims every dot except the nearest one, to 0.7 of whatever fill-opacity is set to. It's a factor rather than a fixed value because these dots don't rest at 1 — a flat target equal to fill-opacity would make hovering do nothing, and one above it would make the unfocused dots brighter than the focused one. The hit test is nearest-point within 40px in both axes rather than a bounding box, so a dot stays reachable however small size-key made it.
The hover fade is a CSS transition over the library's own 250ms duration, so it costs nothing and honours prefers-reduced-motion. There's no animate prop and nothing tweens on a data change — the marks are plain SVG elements Vue patches directly.
Legend
legend defaults to on whenever seriesKey is set and off when it isn't, which is almost always right: without grouping there are no identities to name. Pass it explicitly to drop the legend from a grouped chart whose colours are already explained by the surrounding copy.
- Search
- Social
Legend position
legendPosition moves the legend above the plot instead of below it. Top works when the group identities are what a reader needs before the dots mean anything.
- Search
- Social
Legend align
legendAlign places the legend horizontally — start by default, so it lines up with the plot's left edge and the axis title beneath it. Centre it when the chart is the only thing in its card and the symmetry reads better.
- Search
- Social
- Search
- Social
Legend orientation
legendOrientation flows the entries in a row or a column. Vertical costs height but reads as a list, which is the better shape beside a narrow chart or when the group names are long enough to wrap awkwardly in a row.
- Search
- Social
Guides
guides draws the axes and reserves the margins they need. Turning it off drops all of that — ticks, labels, grid and gutters — leaving the points to fill the box, which is what turns a scatter into a distribution thumbnail inside a card.
guides="false" drops the axes, ticks, labels and grid *and* the margins they reserve, so the marks fill the whole box. That plus a small height is the inline-sparkline shape — there's no separate component for it.
Height
height is the plot's height in pixels — width always comes from the container. It's ignored once aspectRatio is set, and it's held through the loading and empty states so a dashboard doesn't reflow as its panels resolve.
Aspect ratio
aspectRatio sizes the chart from its container's width instead of a fixed height. It's worth more on a scatter than on most charts: the shape of a cloud depends on the ratio of the two axes, so a chart that squashes on narrow screens can make a relationship look steeper than it is.
Loading
loading replaces the plot with a skeleton of exactly the same height, so the page doesn't jump when the data lands. An empty data array gets the empty state instead — two separate conditions, worth driving separately.
API Reference
Generated from the component's source — props, slots and emits as the component actually declares them.
Props
datarequiredRecord<string, unknown>[]stringariaLabelrequiredstringChartColorName'primary'ChartColorName[]Types
interface ScatterPoint {
key: string;
x: number;
y: number;
size: number | null;
group: string;
color: ChartColorName;
}interface ChartPlot {
x: number;
y: number;
width: number;
height: number;
/** Right edge, since marks need it more often than the width. */
right: number;
/** Bottom edge, which is also the x axis' baseline. */
bottom: number;
}type ChartColorName = ChartColorFamily | `${ChartColorFamily}-${ChartColorShade}`;