skills /observable-plot-react
Next.js Referenced

observable-plot-react

Create production-ready data visualizations using Observable Plot in React/Next.js applications. Build interactive charts, maps, and statistical graphics from Parquet data with TypeScript support. Export to SVG/JSON for deployment.

Observable Plot for React/Next.js

Create high-quality, interactive data visualizations using Observable Plot in React and Next.js applications with full TypeScript support.

When to Use This Skill

Use this skill when you need to:

  • Build data visualizations in React/Next.js applications
  • Create interactive charts and maps from Parquet files
  • Generate publication-quality statistical graphics
  • Export visualizations as SVG/JSON for deployment
  • Implement complex multi-layered compositions
  • Create geographic visualizations (choropleth maps, bubble maps)
  • Build time series, scatter plots, heatmaps, and density plots

Key indicators:

  • User mentions "Observable Plot", "Plot.js", or "data visualization in React"
  • Request involves Parquet data processing
  • Need for both Node.js generation and React rendering
  • Creating static exports (SVG/JSON) for Next.js/Vercel deployment
  • TypeScript-based visualization components required

Prerequisites

Core dependencies:

hljs bash
# Observable Plot and D3 npm install @observablehq/plot d3 # Data reading npm install apache-arrow parquet-wasm # OR npm install duckdb # For server-side rendering npm install jsdom # Geographic data npm install topojson-client d3-geo

UI components (if using shadcn/ui):

hljs bash
npx shadcn@latest add card button

System requirements:

  • Node.js 18+
  • React 18+
  • Next.js 13+ (for App Router components)
  • TypeScript 5+

Bundled Resources

References (references/)

Load this comprehensive guide when implementing Observable Plot visualizations:

  • plot-guide.md - Complete Observable Plot guide covering:
    • Installation and setup
    • Reading Parquet files with Apache Arrow and DuckDB
    • Core concepts and architecture
    • Chart patterns (scatter, line, bar, area, heatmap, density)
    • Geographic visualizations (choropleth, bubble maps)
    • Data transformations and aggregations
    • Exporting to SVG/JSON
    • React integration patterns
    • Performance optimization
    • Troubleshooting common issues

Assets (assets/)

Production-ready React/TypeScript components organized by type:

Maps (assets/maps/):

  • BubbleMap.tsx - US county bubble map with population sizing
  • ChoroplethMap.tsx - Choropleth map with color scales
  • EuropeMap.tsx - European geographic visualization
  • geo.state_map.v1.tsx - US state-level map component
  • ZipMap.tsx - ZIP code-level mapping

Charts (assets/charts/):

  • LineChart.tsx - Time series line charts with confidence intervals
  • TimeTrendDemo.tsx - Trend visualization with multiple series
  • timeseries.grouped.v1.tsx - Grouped time series comparisons
  • PlotBar.tsx - Standard bar charts
  • SplitBar.tsx - Split/stacked bar charts
  • stat.splitbar.v1.tsx - Statistical split bar variant

Specialized (assets/specialized/):

  • CorrelationHeatmap.tsx - Correlation matrix heatmap
  • matrix.heatmap.v1.tsx - General matrix heatmap
  • DensityPlot.tsx - 2D density visualization
  • DotPlot.tsx - Dot plot for distributions
  • HealthScatterplot.tsx - Scatterplot with health data patterns

Core Workflow

Step 1: Choose Visualization Type

Geographic Maps:

  • Choropleth maps for regional data
  • Bubble maps for point data with size encoding
  • State/county level visualizations
  • See assets/maps/

Time Series:

  • Line charts with confidence intervals
  • Multi-series comparisons
  • Trend analysis
  • See assets/charts/LineChart.tsx, TimeTrendDemo.tsx

Statistical Graphics:

  • Correlation heatmaps
  • Density plots
  • Dot plots for distributions
  • Scatterplots with regression
  • See assets/specialized/

Categorical Data:

  • Bar charts (horizontal/vertical)
  • Split/stacked bars
  • Grouped comparisons
  • See assets/charts/PlotBar.tsx, SplitBar.tsx

Step 2: Data Preparation

From Parquet files:

hljs typescript
import * as arrow from 'apache-arrow'; import { readParquet } from 'parquet-wasm/node'; import * as fs from 'fs'; // Read Parquet file const parquetBuffer = fs.readFileSync('data.parquet'); const arrowTable = readParquet(parquetBuffer); // Convert to JavaScript array const data = arrowTable.toArray().map(row => row.toJSON());

Using DuckDB:

hljs typescript
import Database from 'duckdb'; const db = new Database(':memory:'); const conn = db.connect(); conn.all(` SELECT * FROM read_parquet('data.parquet') WHERE category = 'A' ORDER BY date `, (err, data) => { // Use data with Observable Plot });

Step 3: Build React Component

Basic pattern:

hljs typescript
'use client'; import React, { useRef, useEffect } from 'react'; import * as Plot from "@observablehq/plot"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; interface ChartProps { data: any[]; title?: string; } const MyChart: React.FC<ChartProps> = ({ data, title }) => { const chartRef = useRef<HTMLDivElement>(null); useEffect(() => { if (!data.length || !chartRef.current) return; const plot = Plot.plot({ width: 800, height: 400, marginLeft: 60, x: { label: "X-axis →" }, y: { label: "Y-axis ↑", grid: true }, marks: [ Plot.dot(data, { x: "x", y: "y", fill: "category" }) ] }); chartRef.current.innerHTML = ''; chartRef.current.appendChild(plot); return () => plot.remove(); }, [data]); return ( <Card> <CardHeader> <CardTitle>{title}</CardTitle> </CardHeader> <CardContent> <div ref={chartRef} /> </CardContent> </Card> ); }; export default MyChart;

Step 4: Export for Deployment

Export to SVG:

hljs typescript
import * as Plot from '@observablehq/plot'; import { JSDOM } from 'jsdom'; import * as fs from 'fs'; // Setup virtual DOM const dom = new JSDOM('<!DOCTYPE html><body></body>'); global.document = dom.window.document; // Create plot const plot = Plot.plot({ marks: [Plot.dot(data, { x: "x", y: "y" })] }); // Extract SVG const svg = plot.querySelector('svg'); fs.writeFileSync('chart.svg', svg.outerHTML);

Export to JSON:

hljs typescript
// Export plot specification for later rendering const plotSpec = { marks: [{ type: 'dot', data, x: 'x', y: 'y' }], options: { width: 800, height: 400 } }; fs.writeFileSync('chart.json', JSON.stringify(plotSpec));

Common Patterns

Pattern 1: Time Series with Confidence Intervals

hljs typescript
Plot.plot({ marks: [ // Confidence interval area Plot.areaY(data, { x: "year", y1: "ci_lower", y2: "ci_upper", fill: "category", fillOpacity: 0.2 }), // Main line Plot.lineY(data, { x: "year", y: "value", stroke: "category", strokeWidth: 2 }), // Reference line Plot.ruleY([50], { stroke: "#ccc", strokeDasharray: "4" }) ] })

Pattern 2: Choropleth Map

hljs typescript
import * as topojson from "topojson-client"; // Load topology const us = await fetch('us-counties.json').then(r => r.json()); const counties = topojson.feature(us, us.objects.counties); Plot.plot({ projection: "albers-usa", color: { type: "quantile", scheme: "blues", label: "Population", legend: true }, marks: [ Plot.geo(counties, { fill: d => countyData.get(d.id)?.population, stroke: "white", strokeWidth: 0.5, title: d => `${d.properties.name}: ${countyData.get(d.id)?.population}` }) ] })

Pattern 3: Correlation Heatmap

hljs typescript
Plot.plot({ marginLeft: 100, marginBottom: 100, color: { type: "diverging", scheme: "rdbu", domain: [-1, 1], legend: true }, marks: [ Plot.cell(correlationData, { x: "var1", y: "var2", fill: "correlation", inset: 0.5 }), Plot.text(correlationData, { x: "var1", y: "var2", text: d => d.correlation.toFixed(2), fill: d => Math.abs(d.correlation) > 0.5 ? "white" : "black" }) ] })

Pattern 4: Density Plot

hljs typescript
Plot.plot({ marks: [ Plot.density(data, { x: "variable1", y: "variable2", bandwidth: 20, fill: "density", fillOpacity: 0.6 }), Plot.dot(data, { x: "variable1", y: "variable2", r: 2, fill: "#000", fillOpacity: 0.1 }) ] })

TypeScript Integration

Define Interfaces

hljs typescript
interface DataPoint { date: Date; value: number; category: string; ci_lower?: number; ci_upper?: number; } interface ChartProps { data: DataPoint[]; title?: string; subtitle?: string; width?: number; height?: number; colorScheme?: string; }

Type-Safe Plot Configuration

hljs typescript
import * as Plot from "@observablehq/plot"; type PlotOptions = Parameters<typeof Plot.plot>[0]; const createPlot = (data: DataPoint[], options?: Partial<PlotOptions>) => { return Plot.plot({ width: 800, height: 400, ...options, marks: [ Plot.lineY(data, { x: "date", y: "value", stroke: "category" }) ] }); };

Performance Optimization

1. Memoize Expensive Computations

hljs typescript
import { useMemo } from 'react'; const processedData = useMemo(() => { return data .filter(d => d.value > 0) .map(d => ({ ...d, date: new Date(d.date) })); }, [data]);

2. Lazy Loading for Large Datasets

hljs typescript
const [displayData, setDisplayData] = useState([]); useEffect(() => { // Load data in chunks const chunk = data.slice(0, 1000); setDisplayData(chunk); }, [data]);

3. Disable Animations for Performance

hljs typescript
Plot.plot({ marks: [ Plot.dot(data, { x: "x", y: "y", // No built-in animations in Observable Plot // Performance is inherently good }) ] })

Next.js Integration

Server Component (Generate SVG)

hljs typescript
// app/chart/page.tsx import { readParquet } from '@/lib/data'; import * as Plot from '@observablehq/plot'; import { JSDOM } from 'jsdom'; export default async function ChartPage() { const data = await readParquet('data.parquet'); const dom = new JSDOM('<!DOCTYPE html><body></body>'); global.document = dom.window.document; const plot = Plot.plot({ marks: [Plot.dot(data, { x: "x", y: "y" })] }); const svg = plot.querySelector('svg')?.outerHTML; return <div dangerouslySetInnerHTML={{ __html: svg }} />; }

Client Component (Interactive)

hljs typescript
// components/InteractiveChart.tsx 'use client'; import { useState } from 'react'; import * as Plot from '@observablehq/plot'; export default function InteractiveChart({ data }) { const [filter, setFilter] = useState('all'); const filteredData = data.filter(d => filter === 'all' || d.category === filter ); // Render plot with filteredData // ... }

Troubleshooting

Issue 1: "document is not defined" (Server-Side)

Solution: Use JSDOM for server-side rendering

hljs typescript
import { JSDOM } from 'jsdom'; const dom = new JSDOM('<!DOCTYPE html><body></body>'); global.document = dom.window.document;

Issue 2: React Hydration Mismatch

Solution: Use 'use client' directive

hljs typescript
'use client'; import React from 'react'; // Component code...

Issue 3: TopoJSON Not Loading

Solution: Ensure proper data fetching

hljs typescript
const topology = await fetch('/data/us-counties.json') .then(r => { if (!r.ok) throw new Error('Failed to load topology'); return r.json(); });

Issue 4: SVG Not Displaying

Solution: Check container ref and cleanup

hljs typescript
useEffect(() => { if (!chartRef.current) return; chartRef.current.innerHTML = ''; // Clear previous chartRef.current.appendChild(plot); return () => plot.remove(); // Cleanup }, [data]);

Best Practices

1. Always Use TypeScript Interfaces

Define clear data structures for type safety and autocomplete.

2. Implement Proper Error Handling

hljs typescript
try { const data = await fetchData(); renderPlot(data); } catch (error) { console.error('Failed to load data:', error); setError(error.message); }

3. Use Responsive Dimensions

hljs typescript
const [dimensions, setDimensions] = useState({ width: 800, height: 400 }); useEffect(() => { const updateDimensions = () => { if (containerRef.current) { setDimensions({ width: containerRef.current.offsetWidth, height: 400 }); } }; window.addEventListener('resize', updateDimensions); return () => window.removeEventListener('resize', updateDimensions); }, []);

4. Optimize Data Before Rendering

Filter and aggregate data before passing to Plot.

5. Use Proper Cleanup

Always remove plots in useEffect cleanup to prevent memory leaks.

Example Project Structure

my-app/ ├── app/ │ ├── charts/ │ │ └── page.tsx # Chart pages │ └── api/ │ └── data/ │ └── route.ts # API routes for data ├── components/ │ └── charts/ │ ├── LineChart.tsx │ ├── ChoroplethMap.tsx │ └── ... ├── lib/ │ ├── data.ts # Data fetching utilities │ └── plot-utils.ts # Plot helper functions └── public/ └── data/ ├── us-counties.json └── *.parquet

Resources

See Also

Comprehensive Guide:

  • references/plot-guide.md - Complete Observable Plot guide with all patterns

Component Templates by Type:

  • assets/maps/ - Geographic visualizations
  • assets/charts/ - Standard chart types
  • assets/specialized/ - Statistical graphics

Related Categories