Scatter Chart

Plot points by two continuous variables to detect relationships, clusters, or outliers.

npm install @plocks/charts

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}`; }, }} /> ); }
Scan to open this page on your phoneplocks.dev/charts/ScatterChart

Related charts