Relationship · Basic

Scatter plot

How two numbers relate, one dot per item, colored by series.

Open in playground
Use when
How two numeric values relate, such as cost and latency per request, with up to 4 series
Avoid when
x is time (a line works better), or there are more than 50,000 dots

Try it

36

Scatter plot

Cost and latency per request by model
  • Large
  • Medium
  • Small
Scatter plotScatter plot. 54 points. x from 1 to 14, y from 320 to 3,520. 3 series: Large 18 points, Medium 18 points, Small 18 points. Latency (ms) rises as Cost (¢) rises, R² 0.88. Reference line at y 2,000ms.Latency (ms)
Scatter plot data
Request 1 · Largex 7, y 2,400ms
Request 2 · Mediumx 6, y 1,500ms
Request 3 · Smallx 3, y 660ms
Request 4 · Largex 7, y 2,390ms
Request 5 · Mediumx 7, y 1,560ms
Request 6 · Smallx 1, y 410ms
Request 7 · Largex 5, y 1,810ms
Request 8 · Mediumx 6, y 1,500ms
Request 9 · Smallx 2, y 410ms
Request 10 · Largex 13, y 3,440ms
Request 11 · Mediumx 4, y 1,180ms
Request 12 · Smallx 3, y 590ms
Request 13 · Largex 9, y 3,520ms
Request 14 · Mediumx 6, y 1,710ms
Request 15 · Smallx 3, y 650ms
Request 16 · Largex 6, y 2,550ms
Request 17 · Mediumx 7, y 1,670ms
Request 18 · Smallx 1, y 510ms
Request 19 · Largex 5, y 1,910ms
Request 20 · Mediumx 6, y 1,580ms
Request 21 · Smallx 3, y 820ms
Request 22 · Largex 14, y 3,410ms
Request 23 · Mediumx 4, y 1,130ms
Request 24 · Smallx 2, y 590ms
Request 25 · Largex 10, y 3,050ms
Request 26 · Mediumx 4, y 1,430ms
Request 27 · Smallx 1, y 440ms
Request 28 · Largex 12, y 2,880ms
Request 29 · Mediumx 4, y 1,250ms
Request 30 · Smallx 3, y 690ms
Request 31 · Largex 14, y 3,020ms
Request 32 · Mediumx 6, y 1,460ms
Request 33 · Smallx 2, y 630ms
Request 34 · Largex 12, y 3,240ms
Request 35 · Mediumx 5, y 1,480ms
Request 36 · Smallx 2, y 660ms
Request 37 · Largex 9, y 2,540ms
Request 38 · Mediumx 3, y 1,050ms
Request 39 · Smallx 2, y 730ms
Request 40 · Largex 5, y 1,810ms
Request 41 · Mediumx 3, y 960ms
Request 42 · Smallx 2, y 430ms
Request 43 · Largex 9, y 3,020ms
Request 44 · Mediumx 6, y 1,790ms
Request 45 · Smallx 2, y 590ms
Request 46 · Largex 13, y 3,350ms
Request 47 · Mediumx 5, y 1,450ms
Request 48 · Smallx 2, y 570ms
Request 49 · Largex 6, y 2,490ms
Request 50 · Mediumx 3, y 1,020ms
Request 51 · Smallx 2, y 620ms
Request 52 · Largex 11, y 2,510ms
Request 53 · Mediumx 5, y 1,360ms
Request 54 · Smallx 1, y 320ms
Only the large model goes over 2 sBasic

Hover over the chart, or Tab in and use the arrow keys to move between items. The numbers are sample data.

Variants

Named looks for this chart. Each one is the example above with a few options or fields changed. The CLI (ztd-charts example scatter --variant <id>) and the MCP server know the same names.

Trend line

Cost and latency per request by model
  • Large
  • Medium
  • Small
Sample data

Quadrants

Cost and latency per request by model
  • Large
  • Medium
  • Small
Sample data

Jitter

Cost and latency per request by model
  • Large
  • Medium
  • Small
Sample data

Code

ScatterChart.tsx
import { ChartCard, ScatterChart } from "ztd-charts";

const data = [
  {"id":"Request 1","model":"Large","cost":7,"ms":2400},
  {"id":"Request 2","model":"Medium","cost":6,"ms":1500},
  {"id":"Request 3","model":"Small","cost":3,"ms":660},
  {"id":"Request 4","model":"Large","cost":7,"ms":2390},
  {"id":"Request 5","model":"Medium","cost":7,"ms":1560},
  {"id":"Request 6","model":"Small","cost":1,"ms":410},
  // … 48 more rows
];

export function Example() {
  return (
    <ChartCard number="36" title="Scatter plot" subtitle="Cost and latency per request by model" takeaway="Only the large model goes over 2 s" level="basic" replay>
      <ScatterChart
        data={data}
        x="cost"
        y="ms"
        series="model"
        label="id"
        highlight="Large"
        xLabel="Cost (¢)"
        yLabel="Latency (ms)"
        unit="ms"
        refLines={{"y":2000}}
        quadrants={["Slow and cheap","Slow and costly","Fast and cheap","Fast and costly"]}
      />
    </ChartCard>
  );
}

Props

<ScatterChart> takes these props. Rows marked Common work the same on every chart.

38 props

Prop
dataRequiredAn array of objects. Pass CSV, REST or database rows as they areobject[]
xRequiredx valuestring | (d, i) => number
yRequiredy valuestring | (d, i) => number
seriesSeries field (one color per series)string | (d, i) => string
labelNamestring | (d, i) => string
xLabelx axis titlestring
yLabely axis titlestring
zeroInclude 0 on the axesbooleanDefault false
maxLabelsMaximum number of dot labels. With a series channel the series names are labeled instead, and this defaults to 0numberDefault 6
canvasThresholdDot count that switches to canvasnumberDefault 1500
xFormatx value format"number" | "percent" | "compact" | …
yScaley axis scale. "log" spaces 1, 10, 100 and 1,000 evenly, so values spread over 1,000 times or more stay readable. Values at or below 0 are left out"linear" | "log"Default "linear"
hiddenSeries to hide. Colors stay as if every series were shown. With card.legend "toggle", viewers can show them againstring[]
fitTrend line over the visible dots (least squares). The tooltip shows its slope and R²"linear"
refLinesReference lines { x, y }. With both, they split the plot into four quadrantsobject
quadrantsQuadrant names, drawn when refLines has both x and y. In reading order: top left, top right, bottom left, bottom rightstring[]
jitter{ amount, seed }: spreads dots that share an x value sideways, by up to amount × the smallest gap between x values (default 0.4). The seed (default 1) keeps the picture the same on every renderobject
heightCommonHeight in px. Without it, each chart picks a default that fits its widthnumber
widthCommonFixed width in px. Without it, the chart fits its parentnumber
highlightCommonItem to highlight. An index, a name, an array, or "max" | "min" | "first" | "last" | "none"number | string | (number|string)[] | "max" …
formatCommonValue format. "number" | "integer" | "percent" (values are already percents: 42 → "42%") | "ratio" (0.42 → "42%") | "compact" | "currency" (KRW for Korean locales, otherwise USD; for another currency pass Intl options with currency) | "decimal" or Intl.NumberFormat options"number" | "percent" | "compact" | …
unitCommonUnit appended to values. For example "%" or "ms"string
localeCommonLocale for numbers, dates and the text a chart writes itself (tooltips, screen reader sentences). "ko" or "ko-*" switches to Korean. Falls back to the ThemeProvider localestringDefault "en-US"
animateCommonEntry and update animationbooleanDefault true
durationCommonAnimation length (ms)number
easingCommonMotion curve"spring" | "easeOut" | "easeInOut" | "linear"Default "spring"
staggerCommonDelay between items as they enter (ms)number
reducedMotionCommon"auto" follows the OS setting. true always turns motion offanyDefault "auto"
animateOnViewCommonPlay when the chart scrolls into viewbooleanDefault true
showLabelsCommonLabels drawn on the chart (values and names)booleanDefault true
tooltipCommonShow tooltips. { template: "{label} · {value} · owner {owner}" } writes them: {label}, {value}, {series} or any other column of the row. \n starts a new line, and a line with an empty value is left outtooltipDefault true
syncIdCommonCharts with the same syncId show the hovered x together (line, area, bar, candlestick). <ChartGrid sync> sets one for every chart inside itstring
ariaLabelCommonName for screen readersstring
descriptionCommonDescription for screen readers. Built from the data when omittedstring
onHoverCommonRuns when hover or keyboard focus moves to another item(e: ChartEvent | null) => void
onClickCommonRuns on click, Enter or Space(e: ChartEvent) => void
replayKeyCommonReplays the entry animation when it changesstring | number
themeCommonChanges theme tokens for this chart onlyPartial<ChartTheme>

Above 1,500 items it draws on a canvas. Axes and labels stay SVG.