Scatter Plot
Correlation
Does more of X really move Y, or are you chasing a coincidence? Plot your points and the trend, the clusters, and the outliers all surface at a glance, with bubble size carrying a third variable for free. The Pearson correlation comes back in getContext(), so you can quote the number instead of squinting at the cloud.
The chart above is the same engine in every framework - only the integration code below differs.
When to reach for it
- Testing a hypothesis. Does spend move conversion? Does tenure move churn? The cloud, the trend and the outliers answer at a glance, and
getContext()hands you the Pearson r to quote in the write-up. - Finding segments before the average hides them. Clusters and outliers jump out of a scatter long before they surface in a summary table - the analyst's first look at any new dataset.
- If one axis is time, use a Line chart - a scatter treats time as just another number and loses the reading order your audience expects.
Heavy data on WebGPU Experimental
ScatterChart has an opt-in renderer="webgpu" that paints the point cloud as GPU-instanced circles while axes, labels and tooltips stay on the SVG layer. It is capability-gated: on a browser without WebGPU it downgrades to canvas automatically, and getContext().renderer reports whichever actually painted.
The demo below is a nod to particle physics: 50,000 simulated dimuon events over a falling continuum background. The sharp vertical bands are the J/ψ, ψ(2S) and Υ(1S/2S/3S) resonances, the same structure an LHC dimuon spectrum shows, and exactly the kind of point cloud a GPU renderer exists for.
Play through the years
The Gapminder move: tag each point with a date, turn on timeline, and watch the scatter drift year by year with the built-in play button and scrubber. Off by default - nothing changes until a chart opts in.
Press the play button under the chart: it steps through the years, one snapshot at a time. Drag the scrubber to jump to any year.
const ref = useRef<ScatterChartHandle>(null);
<ScatterChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / stepForward()<ScatterChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" /><div use:scatterChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>applyScatterChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });<michi-vz-scatter-chart id="c"></michi-vz-scatter-chart>
<script>
const el = document.getElementById("c");
el.timeline = { speedMs: 1000, loop: true };
// el.getTimeline() -> play() / pause() / seek(year)
</script>speedMssets the pace,loopwraps around,autoplay: truestarts on mount,showControl: falsehides the built-in bar.- Values glide between periods by default (
interpolate); tune the motion withtweenMsandeasing, or setinterpolate: falsefor hard cuts. Reduced motion always gets the hard cut. - The headless controller is always available:
chart.timeline()exposesplay() / pause() / toggle() / seek(period) / stepForward() / stepBack(), plusonStepandformatPeriodin the config for custom UI. - Pair it with
pointLabelsso every bubble stays named while it moves; afilterstill applies inside each period. - Points without a
datestay visible in every period.
Usage
import { ScatterChart } from "@michi-vz/react";
export default () => <ScatterChart {...props} />; // props = the chart options<script setup>
import { ScatterChart } from "@michi-vz/vue";
</script>
<template>
<ScatterChart :options="props" />
</template><script>
import { scatterChart } from "@michi-vz/svelte";
</script>
<div use:scatterChart={props}></div>// main.ts - register the elements once
import "@michi-vz/angular";
import { applyScatterChartProps } from "@michi-vz/angular";
// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-scatter-chart #c></michi-vz-scatter-chart>
applyScatterChartProps(this.c.nativeElement, props);<script type="module" src="https://cdn.jsdelivr.net/npm/@michi-vz/wc/dist/michi-vz-wc.bundle.js"></script>
<michi-vz-scatter-chart id="c"></michi-vz-scatter-chart>
<script>
Object.assign(document.getElementById("c"), props); // dataSet/series, title, …
</script>import { mountScatterChart } from "@michi-vz/core";
const chart = mountScatterChart(el, props);
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();API
Props are typed as ScatterChartProps in @michi-vz/core. Shared across all charts: width, height, margin, colors / colorsMapping, renderer ("svg", "canvas", or experimental "webgpu"), highlightItems, disabledItems, and the on* callbacks. onChartDataProcessed / getContext() return the renderer-agnostic ChartContext.
