Scatter Chart
Plot points by two continuous variables to detect relationships, clusters, or outliers.
Source
LLM docs
Package
npm install @plocks/chartsExamples
6
Properties
66
Playground
Basics
Plots data points in two-dimensional space for correlation analysis.
Loading demo…
import { ScatterChart } from '@plocks/charts';
import { SERIES } from './data';
export function Demo() {
return (
<ScatterChart
title="Spend vs. qualified leads"
subtitle="Campaign cohort"
h={340}
data={SERIES.flatMap((serie) => serie.data)}
series={SERIES}
showTrendline="per-series"
enableCrosshair
enablePanZoom
zoomMode="both"
multiTooltip
liveTooltip
xAxis={{
show: true,
title: 'Spend (USD thousands)',
labelFormatter: (value) => `$${value}`,
}}
yAxis={{
show: true,
title: 'Qualified leads',
}}
grid={{ show: true }}
legend={{ show: true, position: 'bottom' }}
tooltip={{
show: true,
formatter: (point) => `${point.x}k spend → ${point.y} leads`,
}}
/>
);
}Api Error Rate Volume
Loading demo…
import { ScatterChart } from '@plocks/charts';
import { QUADRANTS, SERIES } from './data';
const describeQuadrant = (x: number, y: number) => {
const labels = QUADRANTS.labels;
if (!labels) return null;
const isRight = x >= QUADRANTS.x;
const isTop = y >= QUADRANTS.y;
if (isRight && isTop) return labels.topRight ?? null;
if (!isRight && isTop) return labels.topLeft ?? null;
if (isRight && !isTop) return labels.bottomRight ?? null;
return labels.bottomLeft ?? null;
};
export function Demo() {
return (
<ScatterChart
title="API error rate vs. request volume"
subtitle="Each point represents a service, sized by throughput"
h={360}
data={SERIES.flatMap((serie) => serie.data)}
series={SERIES}
quadrants={QUADRANTS}
pointOpacity={0.9}
enableCrosshair
multiTooltip
liveTooltip
grid={{ show: true }}
legend={{ show: true, position: 'bottom' }}
xScaleType="log"
xAxis={{
show: true,
title: 'Requests per minute (thousands)',
labelFormatter: (value: number) => `${Math.round(value)}k`,
}}
yAxis={{
show: true,
title: 'Error rate (%)',
labelFormatter: (value: number) => `${value.toFixed(1)}%`,
}}
tooltip={{
show: true,
backgroundColor: '#12263A',
textColor: '#F4F6FB',
formatter: (point) => {
const quadrantNote = describeQuadrant(point.x, point.y);
const lines = [
point.label ?? 'Service',
`Volume ${Math.round(point.x)}k rpm · Errors ${point.y.toFixed(1)}%`,
];
if (quadrantNote) {
lines.push(quadrantNote);
}
return lines.join('\n');
},
}}
/>
);
}Campaign Spend Revenue
Loading demo…
import { ScatterChart } from '@plocks/charts';
import { QUADRANTS, SERIES } from './data';
const resolveAction = (x: number, y: number) => {
const labels = QUADRANTS.labels;
if (!labels) return null;
const right = x >= QUADRANTS.x;
const top = y >= QUADRANTS.y;
if (!right && top) return labels.topLeft ?? null;
if (right && top) return labels.topRight ?? null;
if (!right && !top) return labels.bottomLeft ?? null;
return labels.bottomRight ?? null;
};
export function Demo() {
return (
<ScatterChart
title="Campaign spend vs. attributed revenue"
subtitle="Ad set performance, each marker sized by budget grouping"
h={360}
data={SERIES.flatMap((serie) => serie.data)}
series={SERIES}
quadrants={QUADRANTS}
pointOpacity={0.86}
showTrendline="per-series"
enableCrosshair
multiTooltip
liveTooltip
grid={{ show: true }}
legend={{ show: true, position: 'bottom' }}
xAxis={{
show: true,
title: 'Spend (USD thousands)',
labelFormatter: (value: number) => `$${value}k`,
}}
yAxis={{
show: true,
title: 'Attributed revenue (USD thousands)',
labelFormatter: (value: number) => `$${value}k`,
}}
tooltip={{
show: true,
backgroundColor: '#0B1220',
textColor: '#F1F5F9',
formatter: (point) => {
const action = resolveAction(point.x, point.y);
const lines = [
point.label ?? 'Ad set',
`Spend $${point.x}k · Revenue $${point.y}k`,
];
if (action) {
lines.push(action);
}
return lines.join('\n');
},
}}
/>
);
}Customer Ltv Vs Cac
Loading demo…
import { ScatterChart } from '@plocks/charts';
import { QUADRANTS, SERIES } from './data';
const resolveQuadrantLabel = (x: number, y: number) => {
const horizontal = x >= QUADRANTS.x ? 'Right' : 'Left';
const vertical = y >= QUADRANTS.y ? 'Top' : 'Bottom';
const labels = QUADRANTS.labels;
if (!labels) return null;
if (horizontal === 'Left' && vertical === 'Top') return labels.topLeft ?? null;
if (horizontal === 'Right' && vertical === 'Top') return labels.topRight ?? null;
if (horizontal === 'Left' && vertical === 'Bottom') return labels.bottomLeft ?? null;
if (horizontal === 'Right' && vertical === 'Bottom') return labels.bottomRight ?? null;
return null;
};
export function Demo() {
return (
<ScatterChart
title="Customer LTV vs. Acquisition Cost"
subtitle="Segment performance across recent cohorts"
h={360}
data={SERIES.flatMap((serie) => serie.data)}
series={SERIES}
quadrants={QUADRANTS}
pointOpacity={0.9}
showTrendline="per-series"
enableCrosshair
multiTooltip
liveTooltip
grid={{ show: true }}
legend={{ show: true, position: 'bottom' }}
xAxis={{
show: true,
title: 'Acquisition cost (USD thousands)',
labelFormatter: (value: number) => `$${value}k`,
}}
yAxis={{
show: true,
title: 'Lifetime value (USD thousands)',
labelFormatter: (value: number) => `$${value}k`,
}}
tooltip={{
show: true,
backgroundColor: '#101218',
textColor: '#F8FAFC',
formatter: (point) => {
const quadrantLabel = resolveQuadrantLabel(point.x, point.y);
const lines = [
point.label ?? 'Segment',
`CAC $${point.x}k · LTV $${point.y}k`,
];
if (quadrantLabel) {
lines.push(quadrantLabel);
}
return lines.join('\n');
},
}}
/>
);
}Feature Usage Vs Satisfaction
Loading demo…
import { ScatterChart } from '@plocks/charts';
import { SERIES } from './data';
export function Demo() {
return (
<ScatterChart
title="Feature usage vs. satisfaction"
subtitle="Weekly feature interactions mapped to CSAT by cohort"
h={360}
data={SERIES.flatMap((serie) => serie.data)}
series={SERIES}
pointOpacity={0.85}
showTrendline="per-series"
enableCrosshair
multiTooltip
liveTooltip
grid={{ show: true }}
legend={{ show: true, position: 'bottom', align: 'center' }}
xAxis={{
show: true,
title: 'Weekly feature uses',
labelFormatter: (value: number) => `${value}x`,
}}
yAxis={{
show: true,
title: 'Customer satisfaction (1-10)',
labelFormatter: (value: number) => value.toFixed(1),
}}
tooltip={{
show: true,
formatter: (point) =>
`${point.label ?? 'Cohort'}\nUsage ${point.x.toFixed(1)}x | CSAT ${point.y.toFixed(1)}`,
}}
/>
);
}Performance Vs Tenure
Loading demo…
import { ScatterChart } from '@plocks/charts';
import { SERIES } from './data';
export function Demo() {
return (
<ScatterChart
title="Performance rating vs. tenure"
subtitle="Team-by-team view with marker size scaled to total compensation (USD thousands)"
h={360}
data={SERIES.flatMap((serie) => serie.data)}
series={SERIES}
pointOpacity={0.88}
showTrendline="per-series"
enableCrosshair
multiTooltip
liveTooltip
grid={{ show: true }}
legend={{ show: true, position: 'bottom' }}
xAxis={{
show: true,
title: 'Tenure (years)',
labelFormatter: (value: number) => `${value.toFixed(1)} yrs`,
}}
yAxis={{
show: true,
title: 'Performance rating (1-5)',
labelFormatter: (value: number) => value.toFixed(1),
}}
tooltip={{
show: true,
formatter: (point) => {
const compensation = point.data?.compensation;
const compensationText = typeof compensation === 'number' ? `$${compensation}k` : 'n/a';
return `${point.label ?? 'Team member'}\nRating ${point.y.toFixed(1)} | Tenure ${point.x.toFixed(1)} yrs\nComp ${compensationText}`;
},
}}
/>
);
}Related charts
Further reading
React Native design for iOS, Android, and Web.
Quick Links
Resources