Skip to content

Charts

Scatter Chart

A relationship between two numeric measures, with every point independently named and positioned.

When to use it: Use it to reveal correlation, clusters, and outliers across two measurements.

On this page

Example

Loading example…
Scatter Chart.tsxtsx
import {
  ChartDescription,
  ChartTable,
  ChartTitle,
  ChartTooltip,
  ScatterChart,
  ScatterChartPlot,
  ScatterChartPoint,
} from "@comp0/react";

const initiatives = [
  { label: "Search", x: 3, y: 8 },
  { label: "Checkout", x: 7, y: 9 },
  { label: "Navigation", x: 4, y: 6 },
  { label: "Reporting", x: 8, y: 4 },
  { label: "Onboarding", x: 5, y: 7 },
] as const;

const formatScore = (value: number | Date) => `${value}/10`;

export function Example() {
  return (
    <ScatterChart
      values={initiatives}
      xLabel="Effort"
      yLabel="Impact"
      formatX={formatScore}
      formatY={formatScore}
      className="w-full max-w-2xl rounded-lg has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-4 has-[:focus-visible]:outline-teal-600 dark:has-[:focus-visible]:outline-teal-400"
    >
      <ChartTitle className="text-base font-semibold text-zinc-950 dark:text-zinc-50">
        Initiative impact and effort
      </ChartTitle>
      <ScatterChartPlot
        aria-label="Scatter chart comparing initiative impact and effort"
        xMin={0}
        xMax={10}
        yMin={0}
        yMax={10}
        className="mx-auto mt-5 aspect-square w-full max-w-md overflow-visible"
      >
        {(point) => (
          <ScatterChartPoint key={point.index} point={point} className="group outline-none">
            <circle cx={point.x} cy={point.y} r="5" className="fill-transparent" />
            <circle
              cx={point.x}
              cy={point.y}
              r="2.4"
              className="fill-teal-600 stroke-transparent group-data-active:stroke-zinc-950 dark:fill-teal-400 dark:group-data-active:stroke-white"
              strokeWidth="2"
              vectorEffect="non-scaling-stroke"
            />
          </ScatterChartPoint>
        )}
      </ScatterChartPlot>
      <ChartDescription className="mt-5 text-sm text-zinc-600 dark:text-zinc-400">
        Search offers high impact for low effort; reporting is the least favorable tradeoff.
      </ChartDescription>
      <ChartTable className="mt-4 w-full border-collapse text-left text-sm [&_td]:border-t [&_td]:border-zinc-200 [&_td]:py-2 [&_th]:border-zinc-200 [&_th]:py-2 dark:[&_td]:border-zinc-800 dark:[&_th]:border-zinc-800">
        <caption className="sr-only">Initiative impact and effort values</caption>
        <thead>
          <tr>
            <th scope="col">Initiative</th>
            <th scope="col">Effort</th>
            <th scope="col">Impact</th>
          </tr>
        </thead>
        <tbody>
          {initiatives.map((initiative) => (
            <tr key={initiative.label}>
              <th scope="row">{initiative.label}</th>
              <td>{formatScore(initiative.x)}</td>
              <td>{formatScore(initiative.y)}</td>
            </tr>
          ))}
        </tbody>
      </ChartTable>
      <ChartTooltip className="pointer-events-none z-50 rounded-md bg-zinc-950 px-2 py-1 text-sm text-white shadow-lg dark:bg-zinc-50 dark:text-zinc-950" />
    </ScatterChart>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. ScatterChart

    Native figure sharing labelled coordinates, axis labels, and formatting with every part. Owns a DOM element.

  2. ChartTitle

    Native figcaption that visibly names the figure. Owns a DOM element.

  3. ScatterChartPlot / ScatterChartPoint

    Independently positioned SVG points against two visible numeric axes. Owns a DOM element.

  4. ChartDescription

    Visible prose summarizing the important relationship. Owns a DOM element.

  5. ChartTable

    Native table composed from the same labelled coordinates. Owns a DOM element.

  6. ChartTooltipOptional

    Optional floating point label shown on hover or focus. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Start ScatterChart with labelled x and y coordinates and visible axis labels.

  2. 2

    Add the supporting parts

    Render ScatterChartPoint inside ScatterChartPlot so arrow keys move spatially between points.

  3. 3

    Make the behavior clear

    Add ChartDescription, a native ChartTable, and an optional ChartTooltip for exact values.

    Exampletsx
    <ScatterChart values={initiatives} xLabel="Effort" yLabel="Impact">
      <ChartTitle>Initiative impact and effort</ChartTitle>
      <ScatterChartPlot aria-label="Scatter chart comparing impact and effort">
        {(point) => (
          <ScatterChartPoint point={point}>
            <circle cx={point.x} cy={point.y} />
          </ScatterChartPoint>
        )}
      </ScatterChartPlot>
      <ChartTable>
        <caption>Initiative values</caption>
      </ChartTable>
      <ChartTooltip />
    </ScatterChart>;

Keyboard

Enters the plot at its current point and leaves with one more Tab.
Moves to the nearest point in the requested visual direction.
Home
Moves to the first point.
End
Moves to the last point.
Esc
Dismisses an open ChartTooltip.

Forms and accessibility

Charts are descriptive content and do not create form values.

Accessibility checklist

  • Keep both numeric axis labels visible and use the same formatters in the table.
  • Wrap every custom mark in ScatterChartPoint so it receives a formatted accessible name.
  • Arrow keys move to the nearest point in the requested visual direction.
  • Do not use point color as the only series or category distinction.
  • Use ChartTooltip as an enhancement while retaining the native exact-value table.

API reference

Importtsx
import { ChartDescription, ChartTable, ChartTitle, ChartTooltip, ScatterChart, ScatterChartPlot, ScatterChartPoint } from "@comp0/react";

ScatterChart

DOM element

Native figure sharing labelled coordinates, axis labels, and formatting with every part.

PropTypeDescription
valuesreadonly ScatterChartValue[]Finite x and y coordinates with non-empty point labels.
xLabelstringVisible headings for both numeric axes.
yLabelstringVisible headings for both numeric axes.
formatX(value) => stringFormatters shared by ticks, point names, and table cells.
formatY(value) => stringFormatters shared by ticks, point names, and table cells.

ChartTitle

DOM element

Native figcaption that visibly names the figure.

ScatterChartPlot / ScatterChartPoint

DOM element

Independently positioned SVG points against two visible numeric axes.

PropTypeDescription
aria-labelstringConcise text alternative naming the relationship.
xMinnumberOptional finite bounds that contain every point.
xMaxnumberOptional finite bounds that contain every point.
yMinnumberOptional finite bounds that contain every point.
yMaxnumberOptional finite bounds that contain every point.
xTickCountnumberVisible tick counts; each defaults to five.
yTickCountnumberVisible tick counts; each defaults to five.
children(point: ScatterChartPointState) => ReactNodeCustom point renderer receiving its value and SVG position.
pointScatterChartPointStatePoint state passed from the plot to ScatterChartPoint.

ChartDescription

DOM element

Visible prose summarizing the important relationship.

ChartTable

DOM element

Native table composed from the same labelled coordinates.

ChartTooltip

OptionalDOM element

Optional floating point label shown on hover or focus.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

ScatterChartPlot / ScatterChartPoint

Style hookMeaning
[data-active]The point currently reached by pointer or keyboard.

ChartTooltip

Style hookMeaning
[data-open]A value tooltip is visible.

Keep exploring