/

Scatter Chart

Plot individual data points on two axes. Reveal correlations, clusters, and outliers in your data.

ABCDEFGH10203040

Quick Start

import { ScatterChart } from "@chartts/react"
 
const data = [
  { height: 170, weight: 68, name: "Alice" },
  { height: 175, weight: 74, name: "Bob" },
  { height: 160, weight: 55, name: "Carol" },
  { height: 182, weight: 82, name: "Dan" },
  { height: 168, weight: 62, name: "Eve" },
  { height: 190, weight: 90, name: "Frank" },
  { height: 155, weight: 50, name: "Grace" },
  { height: 178, weight: 78, name: "Hank" },
]
 
export function HeightWeightScatter() {
  return (
    <ScatterChart
      data={data}
      x="height"
      y="weight"
      className="h-80 w-full"
    />
  )
}

That renders a fully interactive scatter plot with axes, tooltips, responsive scaling, and hover effects. Each data point appears as a circle positioned by its x and y values.

When to Use Scatter Charts

Scatter charts plot individual observations on two numeric axes. Each point is one record in your dataset.

Use a scatter chart when:

  • Exploring the relationship between two numeric variables (height vs. weight, price vs. demand)
  • Looking for correlations, clusters, or outliers
  • Your dataset has many individual observations (tens to thousands of points)
  • You want to encode a third variable through point size or color

Don't use a scatter chart when:

  • One axis is categorical (use a bar chart or strip plot)
  • You want to show change over time with a connected path (use a line chart)
  • You have fewer than 5 data points (a table is clearer)
  • You need to show proportions of a whole (use a pie chart)

Props Reference

PropTypeDefaultDescription
dataT[]requiredArray of data objects
xkeyof TrequiredKey for x-axis values (numeric)
ykeyof TrequiredKey for y-axis values (numeric)
sizekeyof T | number6Point radius. A key maps values to radius; a number sets a fixed size
colorkeyof T | stringtheme defaultPoint fill. A key maps values to a color scale; a string sets a fixed color
shape'circle' | 'square' | 'triangle' | 'diamond''circle'Shape of each data point
classNamestring-Tailwind classes on the root SVG
pointClassNamestring-Tailwind classes on each point
axisClassNamestring-Tailwind classes on axis elements
tooltipClassNamestring-Tailwind classes on tooltip
responsivebooleantrueAuto-resize to container width
animatebooleantrueEnable point entry animation

Size Encoding

Map a numeric field to point radius to add a third dimension of information. This creates a bubble chart effect where larger values produce larger points.

const data = [
  { city: "Tokyo", population: 37400, gdp: 1920, area: 2194 },
  { city: "Delhi", population: 30290, gdp: 370, area: 1484 },
  { city: "Shanghai", population: 27058, gdp: 690, area: 6341 },
  { city: "São Paulo", population: 22043, gdp: 580, area: 1521 },
]
 
<ScatterChart
  data={data}
  x="gdp"
  y="population"
  size="area"
  className="h-96"
/>

The radius scales proportionally. The smallest value in your data gets the minimum point size, the largest gets the maximum. Override the range with sizeRange:

<ScatterChart
  data={data}
  x="gdp"
  y="population"
  size="area"
  sizeRange={[4, 40]}
/>

For a fixed size on all points, pass a number:

<ScatterChart data={data} x="gdp" y="population" size={8} />

Color Encoding

Map a field to color to distinguish categories or show a continuous gradient across a third variable.

Categorical color

When the mapped field contains strings, each unique value gets a distinct color from the palette:

<ScatterChart
  data={studentData}
  x="mathScore"
  y="scienceScore"
  color="grade"
/>

A legend appears automatically showing which color maps to which category.

Continuous color

When the mapped field contains numbers, values are mapped to a sequential color gradient:

<ScatterChart
  data={sensorReadings}
  x="temperature"
  y="humidity"
  color="pressure"
/>

Override the gradient with colorRange:

<ScatterChart
  data={sensorReadings}
  x="temperature"
  y="humidity"
  color="pressure"
  colorRange={["#06b6d4", "#ef4444"]}
/>

Fixed color

Pass a CSS color string to apply a single color to all points:

<ScatterChart data={data} x="x" y="y" color="#10b981" />

Point Shapes

The shape prop controls the marker used for each point. Different shapes help distinguish groups, especially in print or for color-blind users.

// Circle (default)
<ScatterChart data={data} x="x" y="y" shape="circle" />
 
// Square
<ScatterChart data={data} x="x" y="y" shape="square" />
 
// Triangle
<ScatterChart data={data} x="x" y="y" shape="triangle" />
 
// Diamond
<ScatterChart data={data} x="x" y="y" shape="diamond" />

In multi-series mode, shapes can be assigned automatically per series so each group has a distinct shape and color. This provides a double encoding that remains readable in grayscale.


Trend and Regression Lines

Add a best-fit line to highlight the overall trend in your data:

<ScatterChart
  data={data}
  x="studyHours"
  y="examScore"
  trendline
/>

The default is a linear regression. Choose other methods:

// Linear (y = mx + b)
<ScatterChart data={data} x="x" y="y" trendline="linear" />
 
// Polynomial
<ScatterChart data={data} x="x" y="y" trendline="polynomial" />
 
// Logarithmic
<ScatterChart data={data} x="x" y="y" trendline="logarithmic" />

Style the trend line independently:

<ScatterChart
  data={data}
  x="x"
  y="y"
  trendline
  trendlineClassName="stroke-red-400 stroke-dashed stroke-1"
/>

Zoom and Pan

For datasets with many points, enable zoom and pan to let users explore dense regions:

<ScatterChart
  data={largeDataset}
  x="x"
  y="y"
  zoom
/>
  • Scroll to zoom in and out
  • Click and drag to pan across the chart
  • Double-click to reset the view

Constrain the zoom range:

<ScatterChart
  data={largeDataset}
  x="x"
  y="y"
  zoom
  zoomRange={[0.5, 10]}
/>

Multi-Series

Render multiple groups on the same chart. Each series gets a unique color and appears in the legend.

Using a group field

When your data has a categorical field that defines groups, pass it to group:

const data = [
  { x: 12, y: 45, species: "setosa" },
  { x: 18, y: 52, species: "versicolor" },
  { x: 22, y: 60, species: "virginica" },
  // ...more rows
]
 
<ScatterChart
  data={data}
  x="x"
  y="y"
  group="species"
/>

Custom series colors

<ScatterChart
  data={data}
  x="x"
  y="y"
  group="species"
  seriesClassName={{
    setosa: "fill-cyan-500",
    versicolor: "fill-emerald-500",
    virginica: "fill-amber-500",
  }}
/>

Styling with Tailwind

Every visual element exposes a className prop. Style scatter charts the same way you style the rest of your app.

<ScatterChart
  data={data}
  x="height"
  y="weight"
  className="rounded-xl bg-zinc-900/50 p-4"
  pointClassName="fill-cyan-400 hover:fill-cyan-300 transition-colors cursor-pointer"
  axisClassName="text-zinc-500 text-xs"
  tooltipClassName="bg-zinc-800 text-white rounded-lg shadow-xl px-3 py-2 text-sm"
/>

Dark mode with Tailwind variants:

<ScatterChart
  data={data}
  x="x"
  y="y"
  pointClassName="fill-blue-600 dark:fill-blue-400"
  axisClassName="text-gray-600 dark:text-gray-400"
/>

Conditional point styling based on value:

<ScatterChart
  data={data}
  x="x"
  y="y"
  pointClassName={(point) =>
    point.y > 100 ? "fill-red-500" : "fill-zinc-400"
  }
/>

Tooltips

Tooltips appear on hover by default. They show the x-value, y-value, and any size or color field values for the hovered point.

Disable tooltips:

<ScatterChart data={data} x="x" y="y" tooltip={false} />

Custom tooltip format:

<ScatterChart
  data={data}
  x="height"
  y="weight"
  tooltipFormat={(point) =>
    `${point.name}: ${point.height}cm, ${point.weight}kg`
  }
/>

Accessibility

Scatter charts include full accessibility support by default:

  • Screen readers: Each data point is announced with its x-value and y-value. The overall distribution is summarized (e.g., "Scatter chart with 150 points, x ranges from 10 to 95, y ranges from 20 to 80").
  • Keyboard navigation: Tab to focus the chart, then use arrow keys to move between data points. The tooltip follows the focused point.
  • ARIA roles: The chart has role="img" with a descriptive aria-label. Individual points have role="listitem".
  • Shape encoding: Using distinct shapes alongside color ensures the chart remains readable for color-blind users.

Real-World Examples

Student performance analysis

const students = [
  { studyHours: 2, examScore: 55, subject: "Math" },
  { studyHours: 5, examScore: 72, subject: "Math" },
  { studyHours: 8, examScore: 88, subject: "Math" },
  { studyHours: 3, examScore: 61, subject: "Science" },
  { studyHours: 6, examScore: 79, subject: "Science" },
  { studyHours: 9, examScore: 92, subject: "Science" },
  { studyHours: 1, examScore: 42, subject: "English" },
  { studyHours: 4, examScore: 68, subject: "English" },
  { studyHours: 7, examScore: 85, subject: "English" },
]
 
<ScatterChart
  data={students}
  x="studyHours"
  y="examScore"
  group="subject"
  trendline
  seriesClassName={{
    Math: "fill-cyan-500",
    Science: "fill-emerald-500",
    English: "fill-amber-500",
  }}
  trendlineClassName="stroke-zinc-400 stroke-dashed"
  className="h-96"
/>

Real estate pricing

const properties = [
  { sqft: 850, price: 320000, bedrooms: 1 },
  { sqft: 1200, price: 450000, bedrooms: 2 },
  { sqft: 1600, price: 580000, bedrooms: 3 },
  { sqft: 2100, price: 720000, bedrooms: 4 },
  { sqft: 2800, price: 950000, bedrooms: 5 },
  { sqft: 1400, price: 410000, bedrooms: 2 },
  { sqft: 1900, price: 670000, bedrooms: 3 },
  { sqft: 3200, price: 1100000, bedrooms: 5 },
]
 
<ScatterChart
  data={properties}
  x="sqft"
  y="price"
  size="bedrooms"
  sizeRange={[6, 28]}
  color="bedrooms"
  colorRange={["#06b6d4", "#7c3aed"]}
  tooltipFormat={(p) =>
    `${p.sqft} sqft, $${(p.price / 1000).toFixed(0)}k, ${p.bedrooms}BR`
  }
  trendline
  className="h-96"
/>

Server performance monitoring

const servers = [
  { responseTime: 45, throughput: 1200, errorRate: 0.2, name: "us-east-1" },
  { responseTime: 62, throughput: 980, errorRate: 0.8, name: "us-west-2" },
  { responseTime: 38, throughput: 1400, errorRate: 0.1, name: "eu-west-1" },
  { responseTime: 120, throughput: 450, errorRate: 3.2, name: "ap-south-1" },
  { responseTime: 55, throughput: 1100, errorRate: 0.5, name: "eu-central-1" },
  { responseTime: 200, throughput: 200, errorRate: 8.1, name: "sa-east-1" },
]
 
<ScatterChart
  data={servers}
  x="responseTime"
  y="throughput"
  size="errorRate"
  sizeRange={[6, 32]}
  pointClassName={(point) =>
    point.errorRate > 3 ? "fill-red-500" : "fill-emerald-500"
  }
  tooltipFormat={(s) =>
    `${s.name}: ${s.responseTime}ms, ${s.throughput} req/s, ${s.errorRate}% errors`
  }
  zoom
  className="h-96 rounded-xl bg-zinc-950 p-4"
  axisClassName="text-zinc-500 text-xs"
  tooltipClassName="bg-zinc-800 text-white rounded-lg shadow-xl px-3 py-2 text-sm"
/>

Other Charts