Scatter chart
Updated
ScatterChart plots one mark per row: a solid dot, or with sizeKey a bubble, its tone adding up where marks overlap. Like every DitherCharts chart, it fills its container's width, animates in, moves a cursor with the arrow keys, and keeps a data table for screen readers.
Example
Scatter
Ad spend against revenue, a dot per campaign
| point | Ad spend | Revenue |
|---|---|---|
| Point 1 | 5 | 48 |
| Point 2 | 42 | 171 |
| Point 3 | 79 | 245 |
| Point 4 | 20 | 80 |
| Point 5 | 57 | 201 |
| Point 6 | 94 | 312 |
| Point 7 | 35 | 139 |
| Point 8 | 72 | 217 |
| Point 9 | 13 | 87 |
| Point 10 | 50 | 220 |
| Point 11 | 87 | 251 |
| Point 12 | 28 | 105 |
| Point 13 | 65 | 288 |
| Point 14 | 6 | 44 |
| Point 15 | 43 | 127 |
| Point 16 | 80 | 327 |
| Point 17 | 21 | 110 |
| Point 18 | 58 | 178 |
| Point 19 | 95 | 355 |
| Point 20 | 36 | 149 |
| Point 21 | 73 | 247 |
| Point 22 | 14 | 59 |
| Point 23 | 51 | 166 |
| Point 24 | 88 | 300 |
| Point 25 | 29 | 135 |
| Point 26 | 66 | 191 |
| Point 27 | 7 | 55 |
| Point 28 | 44 | 211 |
| Point 29 | 81 | 246 |
| Point 30 | 22 | 72 |
| Point 31 | 59 | 258 |
| Point 32 | 96 | 314 |
| Point 33 | 37 | 113 |
| Point 34 | 74 | 286 |
| Point 35 | 15 | 95 |
| Point 36 | 52 | 180 |
| Point 37 | 89 | 326 |
| Point 38 | 30 | 116 |
| Point 39 | 67 | 241 |
| Point 40 | 8 | 51 |
import { ScatterChart } from "@/components/dithercharts/scatter-chart"
import type { ChartConfig } from "@/components/dithercharts/chart-config"
const chartData = [
{ spend: 5, revenue: 48, segment: "enterprise" },
{ spend: 42, revenue: 171, segment: "smb" },
{ spend: 79, revenue: 245, segment: "smb" },
{ spend: 20, revenue: 80, segment: "enterprise" },
{ spend: 57, revenue: 201, segment: "smb" },
{ spend: 94, revenue: 312, segment: "smb" },
{ spend: 35, revenue: 139, segment: "enterprise" },
{ spend: 72, revenue: 217, segment: "smb" },
{ spend: 13, revenue: 87, segment: "smb" },
{ spend: 50, revenue: 220, segment: "enterprise" },
{ spend: 87, revenue: 251, segment: "smb" },
{ spend: 28, revenue: 105, segment: "smb" },
{ spend: 65, revenue: 288, segment: "enterprise" },
{ spend: 6, revenue: 44, segment: "smb" },
{ spend: 43, revenue: 127, segment: "smb" },
{ spend: 80, revenue: 327, segment: "enterprise" },
{ spend: 21, revenue: 110, segment: "smb" },
{ spend: 58, revenue: 178, segment: "smb" },
{ spend: 95, revenue: 355, segment: "enterprise" },
{ spend: 36, revenue: 149, segment: "smb" },
{ spend: 73, revenue: 247, segment: "smb" },
{ spend: 14, revenue: 59, segment: "enterprise" },
{ spend: 51, revenue: 166, segment: "smb" },
{ spend: 88, revenue: 300, segment: "smb" },
{ spend: 29, revenue: 135, segment: "enterprise" },
{ spend: 66, revenue: 191, segment: "smb" },
{ spend: 7, revenue: 55, segment: "smb" },
{ spend: 44, revenue: 211, segment: "enterprise" },
{ spend: 81, revenue: 246, segment: "smb" },
{ spend: 22, revenue: 72, segment: "smb" },
{ spend: 59, revenue: 258, segment: "enterprise" },
{ spend: 96, revenue: 314, segment: "smb" },
{ spend: 37, revenue: 113, segment: "smb" },
{ spend: 74, revenue: 286, segment: "enterprise" },
{ spend: 15, revenue: 95, segment: "smb" },
{ spend: 52, revenue: 180, segment: "smb" },
{ spend: 89, revenue: 326, segment: "enterprise" },
{ spend: 30, revenue: 116, segment: "smb" },
{ spend: 67, revenue: 241, segment: "smb" },
{ spend: 8, revenue: 51, segment: "enterprise" },
]
const chartConfig = {
spend: { label: "Ad spend" },
revenue: { label: "Revenue" },
smb: { label: "SMB" },
enterprise: { label: "Enterprise" },
} satisfies ChartConfig
export function ChartScatter() {
return (
<ScatterChart
data={chartData}
config={chartConfig}
xKey="spend"
yKey="revenue"
label="Ad spend vs revenue"
/>
)
}
Install
Charts install with DitherCharts Pro. See Installation to log in and import the stylesheet.
npx dithercharts add scatter-chart
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| datarequired | Type readonly ChartRow[] | Default – | One row per point, e.g. { name: "Search", spend: 20, revenue: 90, deals: 8, segment: "ent" }; a row without a numeric x and y (or, with seriesKey, without a series value) is skipped. |
| configrequired | Type ChartConfig | Default – | Labels for xKey, yKey and sizeKey (the tooltip, the table and the live region), and with seriesKey one entry per series value: its label and pattern ("stripes" draws ring dots, or striped bubbles), in legend order. A series value must not also be an axis key. |
| xKeyrequired | Type string | Default – | The row key that holds each point's x value, e.g. "spend". |
| yKeyrequired | Type string | Default – | The row key that holds each point's y value, e.g. "revenue". |
| sizeKey | Type string | Default none (dots) | The row key whose value sizes each point: a bubble chart, the bubble's area following the value. |
| seriesKey | Type string | Default none (one series) | The row key whose value names each point's series, e.g. "segment". |
| nameKey | Type string | Default – | The row key that names each point in the tooltip and the table, e.g. "name". |
| labelrequired | Type string | Default – | Accessible name, e.g. "Spend vs revenue". Also captions the table twin. |
| legend | Type boolean | Default false | A legend under the chart whose buttons show and hide series (only with seriesKey). |
| yAxis | Type boolean | Default true | Value labels beside the grid lines. |
| density | Type boolean | Default false | A faint halo round every dot, adding up to a darker cloud where points crowd (dots only). |
| tooltip | Type TooltipOptions | Default – | The tooltip's label, and the point (its row in data) it opens on at rest. |
| style | Type StyleId | Default "bayer4" | Dither style. |
| animation | Type "develop" | "none" | Default "develop" | Entrance animation; reduced motion always shows the final frame. |
| height | Type number | Default 300 | Height in CSS pixels; the width fills the container. |
| cell | Type number | Default 3 | Dither cell size in CSS pixels; a dot is 3 cells square. |
| className | Type string | Default – | Classes on the chart's outer figure. |
Styles
The dither styles style takes:
"bayer2""bayer4""bayer8""blue-noise""halftone""crosshatch""pixel""stipple""engraved""mezzotint""woodcut""floyd-steinberg""atkinson""riemersma"
Keyboard and accessibility
The chart is one Tab stop. Focusing it from the keyboard puts the cursor on the first point, or where the keys last left it; moving focus away clears it. The keys stop at the ends.
| Key | Action |
|---|---|
| Left arrow Right arrow | Previous / next point along x |
| Home End | First / last point along x |
| Up arrow Down arrow | Next point up / down |
| Escape | Dismiss the cursor |
Screen readers
- The chart's label names it, says how many points it has and lists these keys.
- Each key press is announced in a polite live region. Moving the pointer is silent.
- The
tablebutton under the chart shows its data as a table. Screen readers can reach the table even while it's hidden; clicking a row moves the cursor there and announces it. - With
legend, each entry is a toggle button. The last one showing can't be hidden, but its button stays focusable.
On a touch screen, a tap pins the cursor and a tap outside the chart dismisses it.

