Skip to content

Scatter Chart

The Scatter Chart plots each datum at its own x and y, so the reading is the relationship between two continuous measures — correlation, clustering, outliers — rather than a trend along an ordered axis. Add sizeBy and it becomes a bubble chart, encoding a third measure as marker area between minRadius and maxRadius. Each series binds its own xBy, yBy, marker and yAxis, and dual-axis crosshair tracking, a legend, grid lines and axis titles are built in. Dense scatters take panning and zooming via navigator, and points animate on entry, update and exit. The target can be a Canvas, an SVG context or a terminal.

NOTE

For the full API, see the Charts API Reference.

Example

TIP

This chart has the navigator enabled. Scroll to zoom toward the cursor and click-and-hold to pan (⌘/Ctrl-drag works too). Use Reset View to return to the default framing.

Usage

ts
import {
    createScatterChart,
} from '@ripl/charts';

const chart = createScatterChart('#container', {
    data,
    key: 'id',
    series: [
        {
            id: 'sales',
            label: 'Sales',
            xBy: 'sales',
            yBy: 'profit',
        },
    ],
});

Data Format

Each item needs a unique key and numeric fields for x/y position (and optionally size):

ts
const data = [
    {
        id: 'a',
        sales: 42,
        profit: 78,
        volume: 15,
    },
    {
        id: 'b',
        sales: 68,
        profit: 35,
        volume: 30,
    },
    {
        id: 'c',
        sales: 91,
        profit: 52,
        volume: 8,
    },
];

Each series maps xBy and yBy to numeric fields, and optionally sizeBy for bubble sizing.

Variants

Bubble chart

Add sizeBy, minRadius, and maxRadius to enable bubble sizing:

ts
createScatterChart('#container', {
    data,
    key: 'id',
    series: [
        {
            id: 'sales',
            label: 'Sales',
            xBy: 'sales',
            yBy: 'profit',
            sizeBy: 'volume',
            minRadius: 5,
            maxRadius: 15,
        },
    ],
});

Multi-series

Plot multiple series on the same axes for comparison:

ts
createScatterChart('#container', {
    data,
    key: 'id',
    series: [
        {
            id: 'sales',
            label: 'Sales',
            xBy: 'sales',
            yBy: 'profit',
        },
        {
            id: 'marketing',
            label: 'Marketing',
            xBy: 'marketing',
            yBy: 'engagement',
        },
    ],
});

Multiple y-axes

Supply an array of axis.y entries to plot series with different y units on their own independently-scaled axes. Bind each series to an axis with its yAxis option, naming the axis's id; position: 'right' axes sit on the right and same-side axes stack outward in array order:

ts
createScatterChart('#container', {
    data,
    key: 'id',
    series: [
        {
            id: 'sales',
            label: 'Sales',
            xBy: 'spend',
            yBy: 'revenue',
            yAxis: 'revenue',
        },
        {
            id: 'efficiency',
            label: 'Efficiency',
            xBy: 'spend',
            yBy: 'roas',
            yAxis: 'roas',
        },
    ],
    axis: {
        y: [
            {
                id: 'revenue',
                title: 'Revenue ($)',
            },
            {
                id: 'roas',
                position: 'right',
                title: 'ROAS (×)',
            },
        ],
    },
});

Pan & zoom (navigator)

Set navigator: true to make the plot explorable: wheel-zoom toward the cursor and click-and-hold to pan, with the axis domains rescaling as the view changes (no data rebuild). Pass an object to tune which interactions are active:

ts
const chart = createScatterChart('#container', {
    data,
    key: 'id',
    series: [/* ... */],
    navigator: {
        zoom: true,
        pan: true,
        brush: true,
    },
});

// The controller is available for imperative framing and brush-and-link:
chart.navigator?.fitBounds({ x0: 0, y0: 0, x1: 200, y1: 200 });
chart.navigator?.on('brushend', ({ data: extent }) => console.log(extent));
chart.navigator?.reset();

Options

A full configuration for this chart. The options every chart shares — padding, title, animation, theme and the rest — behave the same everywhere and are documented on Shared Options.

ts
createScatterChart('#container', {
    data,
    key: 'id',
    labels: true,
    format: 'number',
    series: [
        {
            id: 'sales',
            label: 'Sales',
            color: '#7cacf8',
            xBy: 'spend',
            yBy: 'revenue',
            // Scales each bubble between `minRadius` and `maxRadius`.
            sizeBy: 'volume',
            minRadius: 4,
            maxRadius: 20,
            marker: 'circle',
            yAxis: 'revenue',
        },
        {
            id: 'reach',
            label: 'Reach',
            color: '#6dd5b1',
            xBy: 'spend',
            yBy: 'impressions',
            yAxis: 'impressions',
        },
    ],
    axis: {
        y: [
            {
                id: 'revenue',
                title: 'Revenue ($)',
            },
            {
                id: 'impressions',
                position: 'right',
                title: 'Impressions',
            },
        ],
    },
});

Events

Subscribe with chart.on(...). A handler receives an Event object, not the payload directly — the payload is on event.data, and carries the interacted datum plus its { x, y } anchor in chart pixels. event.target and event.stopPropagation() are also available.

ts
// Emitted when a bubble is clicked.
chart.on('markerclick', event => console.log(event.data)); // event.data: ScatterChartMarkerEvent
// Emitted when the pointer enters a bubble.
chart.on('markerenter', event => console.log(event.data)); // event.data: ScatterChartMarkerEvent
// Emitted when the pointer leaves a bubble.
chart.on('markerleave', event => console.log(event.data)); // event.data: ScatterChartMarkerEvent