import { SwarmPlot } from "semiotic"
SwarmPlot (also known as a beeswarm plot) visualizes distributions by plotting individual data points as non-overlapping circles within each category. It is ideal for showing the shape of a distribution while preserving every data point, making patterns, clusters, and outliers easy to spot.
Quick Start
The simplest swarm plot requires just data. Points are automatically spread to avoid overlap.
import { SwarmPlot } from "semiotic" const frameProps = { /* --- Data --- */ data: [ { category: "Control", value: 23 }, { category: "Control", value: 27 }, { category: "Treatment", value: 35 }, // ...more data points per category ], /* --- Process --- */ valueAccessor: "value", categoryAccessor: "category", /* --- Customize --- */ categoryLabel: "Group", valueLabel: "Response" } export default () => { return <SwarmPlot {...frameProps} /> }
Examples
Color by Subgroup
Use colorBy to color points by a data field, revealing subgroup distributions within each category.
import { SwarmPlot } from "semiotic" const frameProps = { /* --- Data --- */ data: [ { category: "Control", value: 23, trial: "A" }, { category: "Control", value: 27, trial: "A" }, // ...data with trial field for coloring ], /* --- Process --- */ valueAccessor: "value", categoryAccessor: "category", /* --- Customize --- */ colorBy: "trial", categoryLabel: "Group", valueLabel: "Response" } export default () => { return <SwarmPlot {...frameProps} /> }
Dynamic Point Sizing
Use sizeBy to encode an additional variable in the point radius, and sizeRange to control the min/max sizes.
import { SwarmPlot } from "semiotic" const frameProps = { /* --- Data --- */ data: [ { category: "Team A", value: 82, importance: 3 }, { category: "Team A", value: 91, importance: 8 }, // ...data with importance field for sizing ], /* --- Process --- */ valueAccessor: "value", categoryAccessor: "category", /* --- Customize --- */ sizeBy: "importance", sizeRange: [3, 10], categoryLabel: "Team", valueLabel: "Score" } export default () => { return <SwarmPlot {...frameProps} /> }
Horizontal Orientation
Set orientation to "horizontal" for a horizontal swarm layout.
import { SwarmPlot } from "semiotic" const frameProps = { /* --- Data --- */ data: experimentData, /* --- Layout --- */ orientation: "horizontal", /* --- Process --- */ valueAccessor: "value", categoryAccessor: "category", /* --- Customize --- */ categoryLabel: "Group", valueLabel: "Response" } export default () => { return <SwarmPlot {...frameProps} /> }
Brush + Zoom
Enable brush on an overview to select a value range, then pass it as frameProps.rExtent to a detail chart to zoom in.
Drag to select a value range
Detail view (brush above to zoom)
JSX
const [extent, setExtent] = useState(null) <SwarmPlot data={data} brush onBrush={setExtent} height={160} /> <SwarmPlot data={data} frameProps={extent ? { rExtent: extent.r } : undefined} height={250} />
Props
| Prop | Type | Required | Default | Description |
|---|
data | array | Yes | — | Array of data points with category and value fields. |
categoryAccessor | string | function | — | "category" | Field name or function to access category values. |
valueAccessor | string | function | — | "value" | Field name or function to access numeric values. |
orientation | "vertical" | "horizontal" | — | "vertical" | Chart orientation. |
categoryLabel | string | — | — | Label for the category axis. |
valueLabel | string | — | — | Label for the value axis. |
valueFormat | function | — | — | Format function for value axis tick labels. |
colorBy | string | function | — | — | Field name or function to determine point color. |
colorScheme | string | array | — | "category10" | Color scheme name or custom colors array. |
sizeBy | string | function | — | — | Field name or function to determine point size. |
sizeRange | [number, number] | — | [3, 8] | Min and max radius for points when using dynamic sizing. |
pointRadius | number | — | 4 | Default point radius when sizeBy is not specified. |
pointOpacity | number | — | 0.7 | Point opacity (0 to 1). |
categoryPadding | number | — | 20 | Padding between categories in pixels. |
enableHover | boolean | — | true | Enable hover annotations on individual points. |
showGrid | boolean | — | false | Show background grid lines. |
showLegend | boolean | — | true (when colorBy set) | Show a legend. Defaults to true when colorBy is specified. |
tooltip | object | function | — | — | Tooltip configuration or render function. |
width | number | — | 600 | Chart width in pixels. |
height | number | — | 400 | Chart height in pixels. |
margin | object | — | { top: 50, bottom: 60, left: 70, right: 40 } | Margin around the chart area. |
title | string | — | — | Chart title displayed at the top. |
brush | boolean | — | — | Enable value-axis brush selection. |
onBrush | function | — | — | Callback with { r: [min, max] } or null when brush clears. |
linkedBrush | string | object | — | — | LinkedCharts brush integration name. |
frameProps | object | — | — | Additional StreamOrdinalFrame props for advanced customization. Escape hatch to the full Frame API. |
Graduating to the Frame
When you need more control — custom point marks, mixed summary types, annotations — graduate to StreamOrdinalFrame directly. Every SwarmPlot is just a configured StreamOrdinalFrame under the hood.
Chart (simple)
JSX
import { SwarmPlot } from "semiotic" <SwarmPlot data={experimentData} categoryAccessor="category" valueAccessor="value" colorBy="trial" pointRadius={5} categoryLabel="Group" valueLabel="Response" />
Frame (full control)
JSX
import { StreamOrdinalFrame } from "semiotic" <StreamOrdinalFrame data={experimentData} oAccessor="category" rAccessor="value" type="swarm" style={d => ({ fill: colorScale(d.trial), fillOpacity: 0.7, r: 5 })} oPadding={20} axes={[ { orient: "left", label: "Response" }, { orient: "bottom", label: "Group" } ]} pieceHoverAnnotation={true} size={[600, 400]} margin={{ top: 50, bottom: 60, left: 70, right: 40 }} />
The frameProps prop on SwarmPlot lets you pass any StreamOrdinalFrame prop without fully graduating:
JSX
// Use frameProps as an escape hatch <SwarmPlot data={experimentData} categoryAccessor="category" valueAccessor="value" frameProps={{ summaryType: "violin", summaryStyle: { fill: "#eee", stroke: "#999" }, annotations: [ { type: "or", category: "Treatment", label: "Significant" } ] }} />
- BoxPlot — statistical summary with quartiles and whiskers
- BarChart — for aggregated category comparisons
- StreamOrdinalFrame — the underlying Frame with full control over every rendering detail
- Annotations — adding callouts, highlights, and notes to any visualization
- Tooltips — custom tooltip content and positioning