Scatter Plot
FreeMulti-series scatter plot with data-driven axes, hexagonal density binning above 2,000 points, a canvas paint path above 1,200, hover tooltip and legend. Pure SVG.
import { SvScatterChartComponent } from '@styloviz/charts';Demo & usage
Basic usage
Usage example — open the gallery for the full interactive demo.
An interactive demo for this component lives in the gallery.
Open live demoMore examples
Labelled axes
<sv-scatter-chart [series]="series" xLabel="Price" yLabel="Rating" [showGrid]="true" [dotSize]="6" />
Point interactions
<sv-scatter-chart [series]="series" (pointSelect)="onSelect($event)" (pointHover)="onHover($event)" />
Density bins for a large cloud
<sv-scatter-chart [series]="series" renderMode="bins" [binRadius]="9" (binSelect)="openRows($event.points)" />
Tune the switch-over points
<sv-scatter-chart [series]="series" [binThreshold]="5000" [canvasThreshold]="2000" />
Cap what is drawn (the chart discloses the stride)
<sv-scatter-chart [series]="series" [maxPoints]="5000" />
When to use
- Revealing correlation or distribution between two numeric variables.
- You have many data points and want to spot clusters or outliers.
- For trends over time, use the Line Chart.
Async states
- loading
- empty
- error
- retry
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| loading | boolean | false | True while the series is being fetched. |
| error | SvDataError | string | null | null | Load failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`. |
| emptyMessage | string | 'No data to plot' | Copy for the no-data state. |
| filtered | boolean | false | True when the emptiness is caused by a filter rather than an empty set. |
| series | readonly ScatterSeries[] | [] | Point series: each with a name, color and (x, y) points. |
| width | number | 480 | Chart width in pixels. |
| height | number | 300 | Chart height in pixels. |
| dotSize | number | 6 | Point radius in pixels. |
| showGrid | boolean | true | Show background grid lines. |
| xLabel | string | 'X Axis' | X-axis label. |
| yLabel | string | 'Y Axis' | Y-axis label. |
| title | string | '' | Chart title. |
| ariaLabel | string | '' | Accessible name override for the chart. Falls back to a generated summary. |
| customClass | string | '' | Extra CSS classes applied to the root wrapper. |
| renderMode | ScatterRenderMode | 'auto' | What the plot draws. |
| binThreshold | number | 2000 | Point count at which `renderMode: 'auto'` switches to density bins. |
| canvasThreshold | number | 1200 | Point count above which the `points` path paints to a canvas instead of emitting one SVG node per datum. |
| binRadius | number | 9 | Circumradius of one density hexagon, in pixels. |
| maxPoints | number | 0 | Hard cap on how many points are drawn. `0` means no cap. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| retry | void | The host should re-run the failed load. |
| pointHover | ScatterPointEvent | Emitted when a point is hovered or keyboard-focused. |
| pointSelect | ScatterPointEvent | Emitted when a point is clicked or activated via keyboard (Enter/Space). |
| binHover | ScatterBinEvent | Emitted when a density bin is hovered or keyboard-focused. |
| binSelect | ScatterBinEvent | Emitted when a density bin is clicked or activated via keyboard. |
<sv-scatter-chart-pro>
Also in this family — SvScatterChartProComponent from @styloviz-pro/charts.
Async states
- loading
- empty
- error
- retry
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| loading | boolean | false | True while the series is being fetched. |
| error | SvDataError | string | null | null | Load failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`. |
| emptyMessage | string | 'No data to plot' | Copy for the no-data state. |
| filtered | boolean | false | True when the emptiness is caused by a filter rather than an empty set. |
| series | readonly ScatterSeries[] | [] | Point series: each with a name, color and (x, y) points. |
| width | number | 480 | Chart width in pixels. |
| height | number | 300 | Chart height in pixels. |
| dotSize | number | 6 | Point radius in pixels. |
| showGrid | boolean | true | Show background grid lines. |
| xLabel | string | 'X Axis' | X-axis label. |
| yLabel | string | 'Y Axis' | Y-axis label. |
| title | string | '' | Chart title. |
| ariaLabel | string | '' | Accessible name override for the chart. Falls back to a generated summary. |
| customClass | string | '' | Extra CSS classes applied to the root wrapper. |
| renderMode | ScatterRenderMode | 'auto' | What the plot draws. |
| binThreshold | number | 2000 | Point count at which `renderMode: 'auto'` switches to density bins. |
| canvasThreshold | number | 1200 | Point count above which the `points` path paints to a canvas instead of emitting one SVG node per datum. |
| binRadius | number | 9 | Circumradius of one density hexagon, in pixels. |
| maxPoints | number | 0 | Hard cap on how many points are drawn. `0` means no cap. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| retry | void | The host should re-run the failed load. |
| pointHover | ScatterPointEvent | Emitted when a point is hovered or keyboard-focused. |
| pointSelect | ScatterPointEvent | Emitted when a point is clicked or activated via keyboard (Enter/Space). |
| binHover | ScatterBinEvent | Emitted when a density bin is hovered or keyboard-focused. |
| binSelect | ScatterBinEvent | Emitted when a density bin is clicked or activated via keyboard. |
Wire it to your API
This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — drive it from whatever you already use.
Template
Bound to the names this component actually exposes.
<sv-scatter-chart [series]="data.items()" [loading]="data.loading()" [error]="data.error()" [filtered]="data.filtered()" (retry)="data.reload()" />
Host class
Drive the three async inputs from your own fetch.
// This component reports its state but does not emit a query, so drive // it from your own fetch and bind the three async inputs. readonly items = signal<Row[]>([]); readonly loading = signal(false); readonly error = signal<SvDataError | null>(null); async load(): Promise<void> { this.loading.set(true); this.error.set(null); try { this.items.set(await this.api.fetchRows()); } catch (e) { this.error.set(toSvDataError(e)); } finally { this.loading.set(false); } }
- This component does not emit a query, so it renders what you give it. Fetch on your own trigger and bind `series`, `loading` and `error`.
- Set `filtered` when a search or filter is active. It is the difference between "you have no orders" and "no orders match this filter" — telling a customer with 4,000 orders the first one reads as a bug.
- Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.
Full walkthrough: Connecting to a backend.
Customization & theming
- Multi-series points with custom colors.
- Axes are rounded out of the data itself (1/2/2.5/5/10 × a power of ten), so the ticks read as numbers a person would have picked and the unit is chosen per axis — `120k` down the side and `2024` along the bottom.
- `renderMode`, `binThreshold` and `binRadius` control the density picture; `canvasThreshold` controls when the points are painted as one image instead of one node each.
- Hover tooltip with point details, or with the bin count and its per-series breakdown.
Best practices
- Do: Label both axes with units so points are interpretable.
- Do: Use color/shape to separate series when overlaying groups.
- Do: Handle `binSelect` so a reader can open the rows behind a dense bin — the count alone is a dead end.
- Tip: Leave `renderMode` on `auto`: it draws points while they are still individually readable and switches to density bins past `binThreshold`.
- Tip: Reduce `dotSize` or add opacity when points overlap heavily.
- Don't: Do not pre-truncate a large feed with `slice()`. Feeds arrive sorted, so the first N rows are the first N of the range, not a sample of the cloud — pass everything and let `maxPoints` take an even stride and disclose it.
Accessibility
- The SVG is `role="group"` with a generated summary (override via `ariaLabel`); axis labels are real text.
- One tab stop, not one per datum: the arrow keys, Home and End walk the marks in reading order and Enter/Space emits `pointSelect` or `binSelect`. A cloud of 40,000 points is one Tab away from the rest of the page.
- Once the plot is binned or painted to a canvas, the bins are the focus targets and each announces its position, its count and which series contributed.
- `sv-data-summary-table` says in rows whatever the picture is currently drawing — points below the threshold, bins above it, with each bin's count and per-series split.
- Anything the picture cannot show is stated under it in words: dropped coordinates, undrawable sizes, coincident points, the sampling stride, and an outlier holding an axis open.
Feedback on Scatter Plot
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.