Bubble Chart

Extend scatter plots with a third variable encoded by marker size for richer comparisons.

npm install @plocks/charts

Basics

Scatter-style plot where point radius encodes a third quantitative dimension.

Loading demo…

import { BubbleChart } from '@plocks/charts'; import { companies } from './data'; export function Demo() { return ( <BubbleChart title="Revenue vs Growth" subtitle="Bubble size shows valuation (in millions)" h={360} data={companies} dataKey={{ x: 'revenue', y: 'growth', z: 'valuation', label: 'company', id: 'company', }} xAxis={{ title: 'Annual revenue (USD millions)', labelFormatter: (value) => `${Math.round(value)}m`, }} yAxis={{ title: 'YoY growth %', labelFormatter: (value) => `${Math.round(value)}%`, }} valueFormatter={(value) => `$${Math.round(value)}m`} grid={{ show: true }} withTooltip range={[64, 1152]} /> ); }

Cities Talent Footprint

Loading demo…

import { BubbleChart } from '@plocks/charts'; import { Region, cities, regionPalette } from './data'; const formatFootprint = (value: number) => `${value.toFixed(0)}k sq ft`; export function Demo() { return ( <BubbleChart title="Global Talent Hubs" subtitle="Talent depth vs cost of living — bubble size represents active office footprint" h={440} data={cities} dataKey={{ x: 'costOfLivingIndex', y: 'talentDepth', z: 'officeFootprintKsqft', label: 'city', color: 'region', id: 'city', }} colorScale={(value) => (value && regionPalette[value as Region]) || regionPalette.Americas} grid={{ show: true }} xAxis={{ title: 'Cost of living index (base 100 = SF)', labelFormatter: (value) => value.toFixed(0), }} yAxis={{ title: 'Tech talent depth (0–100 readiness)', labelFormatter: (value) => value.toFixed(0), }} valueFormatter={(value) => formatFootprint(value)} tooltip={{ formatter: ({ record, value }) => [ `Office footprint: ${formatFootprint(value)}`, `Remote ready: ${record.remoteReady}% • Avg tenure: ${record.averageTenure.toFixed(1)} yrs`, `Anchor university: ${record.anchorUniversity}`, ].join('\n'), }} range={[81, 1764]} legend={{ show: true, position: 'right', align: 'start' }} /> ); }

Customer Account Health

Loading demo…

import { BubbleChart } from '@plocks/charts'; import { accounts } from './data'; const formatArr = (value: number) => `$${value.toFixed(2)}M ARR`; export function Demo() { return ( <BubbleChart title="Customer Account Health vs Expansion" subtitle="Bubble size reflects current ARR; use upper-right quadrant to spot ready-to-expand logos" h={440} data={accounts} dataKey={{ x: 'healthScore', y: 'expansionPotential', z: 'arr', label: 'account', id: 'account', }} xAxis={{ title: 'Account health score', labelFormatter: (value) => `${Math.round(value)}`, }} yAxis={{ title: 'Expansion potential score', labelFormatter: (value) => `${Math.round(value)}`, }} grid={{ show: true }} valueFormatter={(value) => formatArr(value)} tooltip={{ formatter: ({ record, value }) => [ formatArr(value), `Segment: ${record.segment} • CSM: ${record.csOwner}`, `Last touch: ${record.lastTouch}`, ].join('\n'), }} range={[96, 1728]} /> ); }

Engineering Epic Risk

Loading demo…

import { BubbleChart } from '@plocks/charts'; import { Squad, epics, squadPalette } from './data'; const formatMultiplier = (value: number) => `${value.toFixed(1)}× risk`; export function Demo() { return ( <BubbleChart title="Epic Risk Landscape" subtitle="Story points vs defect density — bubble area communicates composite risk multiplier" h={420} data={epics} dataKey={{ x: 'storyPoints', y: 'defectDensity', z: 'riskMultiplier', label: 'epic', color: 'squad', id: 'epic', }} colorScale={(value) => (value ? squadPalette[value as Squad] : squadPalette['Platform Reliability'])} grid={{ show: true }} xAxis={{ title: 'Estimated effort (story points)', labelFormatter: (value) => `${Math.round(value)}`, }} yAxis={{ title: 'Defect density (per 1000 lines)', labelFormatter: (value) => value.toFixed(1), }} valueFormatter={(value) => formatMultiplier(value)} tooltip={{ formatter: ({ record, value }) => [ formatMultiplier(value), `Critical paths: ${record.criticalPaths}`, `Squad: ${record.squad} • Phase: ${record.phase}`, ].join('\n'), }} range={[64, 1296]} legend={{ show: true, position: 'right', align: 'start' }} /> ); }

Product Strategy Portfolio

Loading demo…

import { BubbleChart } from '@plocks/charts'; import { initiatives } from './data'; const formatMillions = (value: number) => `$${value.toFixed(1)}M`; export function Demo() { return ( <BubbleChart title="Product Initiative Portfolio" subtitle="Strategic value vs execution effort — bubble scales with projected revenue" h={440} data={initiatives} dataKey={{ x: 'executionEffort', y: 'strategicValue', z: 'projectedRevenue', label: 'initiative', id: 'initiative', }} grid={{ show: true }} xAxis={{ title: 'Execution effort (1=low, 10=high)', labelFormatter: (value) => value.toFixed(1), }} yAxis={{ title: 'Strategic value (1=low, 10=high)', labelFormatter: (value) => value.toFixed(1), }} valueFormatter={(value) => formatMillions(value)} tooltip={{ formatter: ({ record, value }) => [ `Projected revenue: ${formatMillions(value)}`, `Confidence: ${record.confidence}%`, `Owner: ${record.owner} • Horizon: ${record.horizon}`, ].join('\n'), }} range={[72, 1440]} /> ); }

Vendor Contracts Scoring

Loading demo…

import { BubbleChart } from '@plocks/charts'; import { Category, categoryPalette, contracts } from './data'; const formatSpend = (value: number) => `$${value.toFixed(1)}M`; export function Demo() { return ( <BubbleChart title="Vendor Contract Health" subtitle="Compliance vs renewal probability — bubble area encodes annual spend" h={420} data={contracts} dataKey={{ x: 'complianceScore', y: 'renewalProbability', z: 'annualSpendMillions', label: 'vendor', color: 'category', id: 'vendor', }} colorScale={(value) => (value && categoryPalette[value as Category]) || categoryPalette.Cloud} grid={{ show: true }} xAxis={{ title: 'Compliance readiness score', labelFormatter: (value) => `${Math.round(value)}`, }} yAxis={{ title: 'Renewal probability %', labelFormatter: (value) => `${Math.round(value)}%`, }} valueFormatter={(value) => formatSpend(value)} tooltip={{ formatter: ({ record, value }) => [ `Annual spend: ${formatSpend(value)}`, `Owner: ${record.owner} • Term ends: ${record.termEnds}`, `Risk: ${record.riskLevel}`, ].join('\n'), }} range={[72, 1620]} legend={{ show: true, position: 'right', align: 'start' }} /> ); }
Scan to open this page on your phoneplocks.dev/charts/BubbleChart

Related charts