Skip to main content
Technology & EngineeringData Visualization171 lines

Observable Plot

Expert guidance for building concise, exploratory data visualizations with Observable Plot

Quick Summary28 lines
You are an expert in creating data visualizations with Observable Plot.

## Key Points

- **Transforms**: Built-in data transforms like `Plot.binX`, `Plot.groupX`, `Plot.stackY`, `Plot.normalizeY`, and `Plot.windowY` for on-the-fly aggregation without preprocessing.
- **Compound Marks**: Use `Plot.boxY` for box plots, `Plot.hexbin` for hexagonal binning, and `Plot.contour` for density contours.
- **Interactions**: Use `Plot.tip` to add interactive tooltips and `Plot.crosshair` for crosshair annotations on hover.
- **Projections**: Use the `projection` option (e.g., `"albers-usa"`, `"mercator"`) with `Plot.geo` for geographic maps.
- **Marks as Annotations**: Use `Plot.ruleX`, `Plot.ruleY`, `Plot.text`, and `Plot.arrow` to annotate charts with reference lines and labels.
- Let Plot infer scales and axes from the data rather than manually configuring them; override only when needed.
- Use built-in transforms (`binX`, `groupX`, `stackY`) instead of pre-aggregating data, which keeps code concise and declarative.
- In React, always clean up the chart in the `useEffect` return function to prevent DOM node leaks.
- Forgetting that `Plot.plot()` returns a raw DOM element (SVG), not a React component; you must use a ref to append it to the DOM in React.
- Confusing mark orientation: `Plot.barY` draws vertical bars (y encodes the quantitative value), while `Plot.barX` draws horizontal bars. Mixing them up swaps the axes.

## Quick Example

```bash
npm install @observablehq/plot
```

```html
<script type="module">
  import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm";
</script>
```
skilldb get data-visualization-skills/Observable PlotFull skill: 171 lines
Paste into your CLAUDE.md or agent config

Observable Plot — Data Visualization

You are an expert in creating data visualizations with Observable Plot.

Overview

Observable Plot is a high-level, concise JavaScript library for exploratory data visualization created by the team behind D3. It follows a grammar-of-graphics approach where you declare marks (bars, dots, lines, areas, rules, text) and Plot handles scales, axes, and legends automatically. Use Observable Plot when you want to quickly explore and visualize data with minimal code, especially for statistical and analytical charts.

Setup & Configuration

Install via npm:

npm install @observablehq/plot

Or use the CDN:

<script type="module">
  import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm";
</script>

Basic usage (renders an SVG element):

import * as Plot from "@observablehq/plot";

const data = [
  { month: "Jan", sales: 100 },
  { month: "Feb", sales: 120 },
  { month: "Mar", sales: 90 },
];

const chart = Plot.plot({
  marks: [
    Plot.barY(data, { x: "month", y: "sales", fill: "steelblue" })
  ]
});

document.getElementById("chart").append(chart);

React Integration

import * as Plot from "@observablehq/plot";
import { useRef, useEffect } from "react";

function MyChart({ data }) {
  const containerRef = useRef();

  useEffect(() => {
    const chart = Plot.plot({
      marks: [
        Plot.dot(data, { x: "x", y: "y", fill: "category" })
      ]
    });
    containerRef.current.replaceChildren(chart);
    return () => chart.remove();
  }, [data]);

  return <div ref={containerRef} />;
}

Core Patterns

Dot / Scatter Plot

Plot.plot({
  marks: [
    Plot.dot(data, { x: "weight", y: "height", stroke: "species" }),
    Plot.frame()
  ],
  color: { legend: true }
});

Line Chart

Plot.plot({
  marks: [
    Plot.lineY(data, { x: "date", y: "price", stroke: "symbol" }),
    Plot.ruleY([0])
  ],
  y: { grid: true }
});

Histogram

Plot.plot({
  marks: [
    Plot.rectY(data, Plot.binX({ y: "count" }, { x: "age", fill: "steelblue" })),
    Plot.ruleY([0])
  ]
});

Faceted Charts

Plot.plot({
  facet: { data, x: "region" },
  marks: [
    Plot.barY(data, { x: "product", y: "sales", fill: "product" })
  ],
  marginLeft: 60
});

Grouping and Aggregation

Plot.plot({
  marks: [
    Plot.barY(data, Plot.groupX({ y: "sum" }, { x: "category", y: "revenue", fill: "category" }))
  ]
});

Advanced Features

  • Transforms: Built-in data transforms like Plot.binX, Plot.groupX, Plot.stackY, Plot.normalizeY, and Plot.windowY for on-the-fly aggregation without preprocessing.
  • Compound Marks: Use Plot.boxY for box plots, Plot.hexbin for hexagonal binning, and Plot.contour for density contours.
  • Interactions: Use Plot.tip to add interactive tooltips and Plot.crosshair for crosshair annotations on hover.
  • Projections: Use the projection option (e.g., "albers-usa", "mercator") with Plot.geo for geographic maps.
  • Marks as Annotations: Use Plot.ruleX, Plot.ruleY, Plot.text, and Plot.arrow to annotate charts with reference lines and labels.

Best Practices

  • Let Plot infer scales and axes from the data rather than manually configuring them; override only when needed.
  • Use built-in transforms (binX, groupX, stackY) instead of pre-aggregating data, which keeps code concise and declarative.
  • In React, always clean up the chart in the useEffect return function to prevent DOM node leaks.

Core Philosophy

Observable Plot is designed for the analyst, not the dashboard developer. Its grammar-of-graphics approach -- declare marks, let the library infer scales and axes -- optimizes for the speed of going from a question about data to a visual answer. You should be able to see a distribution, compare groups, or spot a trend in one or two lines of code. If you are spending more time configuring Plot than thinking about data, you are likely overriding defaults that were already correct.

The built-in transforms (binX, groupX, stackY, normalizeY, windowY) are a core part of the API, not a convenience add-on. They allow you to aggregate, bin, and reshape data inside the plot specification rather than in a separate preprocessing step. This keeps the visualization self-contained: the Plot call describes both the data transformation and the visual encoding in one declaration. Prefer transforms over pre-aggregating data; it makes the code shorter and keeps the raw data available for other marks in the same plot.

Plot returns a raw DOM element (an SVG node), not a React component or a framework-specific widget. In vanilla JavaScript, you append it to the page. In React, you use a ref and useEffect. In both cases, you are responsible for cleanup when the data or component changes. This design keeps Plot framework-agnostic but requires you to understand the DOM lifecycle in your specific context.

Anti-Patterns

  • Pre-aggregating data that transforms could handle: Computing group sums, bins, or cumulative values in JavaScript before passing data to Plot, when Plot.groupX, Plot.binX, or Plot.windowY would do it declaratively. This duplicates logic and makes the plot harder to modify.

  • Over-configuring scales and axes: Manually setting scale domains, axis tick counts, and label formats when Plot's automatic inference would produce the same or better result. Override defaults only when the inferred behavior is wrong for your data.

  • Treating Plot as a React component: Rendering Plot.plot() directly in JSX or expecting it to respond to React state changes. Plot returns a DOM node; in React you must use a ref, call Plot.plot() inside useEffect, and clean up the previous node on re-render.

  • Confusing mark orientation: Using Plot.barY when horizontal bars are intended (which should be Plot.barX), or vice versa. The Y/X suffix indicates which axis encodes the quantitative value, not the direction the bars face.

  • Building dashboards with Plot: Attempting to use Observable Plot for production dashboards with real-time updates, complex interactivity, and coordinated views. Plot is optimized for exploratory, static-ish analysis charts. For interactive dashboards, use a library designed for that purpose.

Common Pitfalls

  • Forgetting that Plot.plot() returns a raw DOM element (SVG), not a React component; you must use a ref to append it to the DOM in React.
  • Confusing mark orientation: Plot.barY draws vertical bars (y encodes the quantitative value), while Plot.barX draws horizontal bars. Mixing them up swaps the axes.

Install this skill directly: skilldb add data-visualization-skills

Get CLI access →