Claude-skill-registry composable-svelte-charts
Data visualization and chart components for Composable Svelte. Use when creating charts, graphs, or data visualizations. Covers chart types (scatter, line, bar, area, histogram), data binding, state-driven updates, interactive features (zoom, brush, tooltips), and responsive design from @composable-svelte/charts package built with Observable Plot and D3.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/composable-svelte-charts" ~/.claude/skills/majiayu000-claude-skill-registry-composable-svelte-charts && rm -rf "$T"
skills/data/composable-svelte-charts/SKILL.mdComposable Svelte Charts
Interactive data visualization components built with Observable Plot and D3.
PACKAGE OVERVIEW
Package:
@composable-svelte/charts
Purpose: State-driven interactive charts and data visualizations.
Technology Stack:
- Observable Plot: Declarative visualization grammar from Observable
- D3: Low-level utilities for scales, shapes, and interactions
- Motion One: Smooth transitions and animations
Chart Types:
- Scatter plots
- Line charts
- Bar charts
- Area charts
- Histograms
Interactive Features:
- Zoom & pan
- Brush selection
- Tooltips (automatic)
- Range selection
- Responsive sizing
State Management: All charts use pure reducers with type-safe actions following Composable Architecture patterns.
QUICK START
import { createStore } from '@composable-svelte/core'; import { Chart, chartReducer, createInitialChartState } from '@composable-svelte/charts'; // Sample data const data = [ { x: 1, y: 10, category: 'A' }, { x: 2, y: 25, category: 'B' }, { x: 3, y: 15, category: 'A' }, { x: 4, y: 30, category: 'B' } ]; // Create chart store const chartStore = createStore({ initialState: createInitialChartState({ data }), reducer: chartReducer, dependencies: {} }); // Render scatter plot <Chart store={chartStore} type="scatter" x="x" y="y" color="category" width={800} height={400} enableZoom={true} enableTooltip={true} />
CHART COMPONENT
Purpose: High-level wrapper for creating charts with Observable Plot.
Props
- Chart store (required)store: Store<ChartState, ChartAction>
- Chart type (default: 'scatter')type: 'scatter' | 'line' | 'bar' | 'area' | 'histogram'
- Chart width (optional, responsive if omitted)width: number
- Chart height (optional, defaults to 400px)height: number
- X accessor (required)x: string | ((d) => any)
- Y accessor (required)y: string | ((d) => any)
- Color accessor (optional)color: string | ((d) => any)
- Mark size (optional)size: number
- X domain (optional)xDomain: [number, number] | 'auto'
- Y domain (optional)yDomain: [number, number] | 'auto'
- Enable zoom/pan (default: false)enableZoom: boolean
- Enable brush selection (default: false)enableBrush: boolean
- Enable tooltips (default: true)enableTooltip: boolean
- Enable transitions (default: true)enableAnimations: boolean
- Selection callback (optional)onSelectionChange: (selected: any[]) => void
Usage
<Chart store={chartStore} type="scatter" x="date" y="value" color={(d) => d.category} size={4} xDomain="auto" yDomain={[0, 100]} enableZoom={true} enableTooltip={true} onSelectionChange={(selected) => console.log('Selected:', selected)} />
CHART TYPES
Scatter Plot
Purpose: Display individual data points in 2D space.
Best for: Correlations, distributions, outliers.
<Chart store={chartStore} type="scatter" x="temperature" y="sales" color="region" size={5} enableZoom={true} />
Accessories:
: X-axis positionx
: Y-axis positiony
: Point color (optional)color
: Point size (optional)size
Line Chart
Purpose: Show trends over time or continuous data.
Best for: Time series, trends, comparisons.
<Chart store={chartStore} type="line" x="date" y="price" color="ticker" enableZoom={true} />
Notes:
- Data should be sorted by X for proper rendering
- Multiple series via
accessorcolor - Supports missing data (gaps in line)
Bar Chart
Purpose: Compare categorical data with rectangular bars.
Best for: Category comparisons, rankings, distributions.
<Chart store={chartStore} type="bar" x="category" y="count" color="segment" enableTooltip={true} />
Variants:
- Vertical bars (default)
- Grouped bars (via
)color - Stacked bars (via config)
Area Chart
Purpose: Line chart with filled area below.
Best for: Cumulative data, part-to-whole relationships.
<Chart store={chartStore} type="area" x="date" y="value" color="category" enableZoom={true} />
Notes:
- Multiple series stack by default
- Baseline at Y=0 unless configured
Histogram
Purpose: Distribution of numerical data into bins.
Best for: Data distributions, frequency analysis.
<Chart store={chartStore} type="histogram" x="value" enableTooltip={true} />
Notes:
- Automatically bins data
- Y-axis shows frequency count
- Customize bins via state actions
STATE MANAGEMENT
ChartState Interface
interface ChartState<T = unknown> { // Data data: T[]; // Original data filteredData: T[]; // After filters applied // Visualization config spec: PlotSpec; // Observable Plot spec dimensions: { width: number; height: number; }; // Selection selection: { type: 'none' | 'point' | 'range' | 'brush'; selectedData: T[]; selectedIndices: number[]; brushExtent?: [[number, number], [number, number]]; range?: [number, number]; }; // Zoom/pan transform: { x: number; y: number; k: number; // scale factor }; targetTransform?: ZoomTransform; // For animated zoom // Animation isAnimating: boolean; transitionDuration: number; }
ChartAction Types
type ChartAction<T = unknown> = // Data | { type: 'setData'; data: T[] } | { type: 'filterData'; predicate: (d: T) => boolean } | { type: 'clearFilters' } // Selection | { type: 'selectPoint'; data: T; index: number } | { type: 'selectRange'; range: [number, number] } | { type: 'brushStart'; position: [number, number] } | { type: 'brushMove'; extent: [[number, number], [number, number]] } | { type: 'brushEnd' } | { type: 'clearSelection' } // Zoom/pan | { type: 'zoom'; transform: ZoomTransform } | { type: 'zoomAnimated'; targetTransform: ZoomTransform } | { type: 'zoomProgress'; transform: ZoomTransform } | { type: 'zoomComplete' } | { type: 'resetZoom' } // Dimensions | { type: 'resize'; dimensions: { width: number; height: number } } // Config | { type: 'updateSpec'; spec: Partial<PlotSpec> };
Creating Initial State
import { createInitialChartState } from '@composable-svelte/charts'; const initialState = createInitialChartState({ data: myData, dimensions: { width: 800, height: 400 }, transitionDuration: 300 });
INTERACTIVE FEATURES
Zoom & Pan
Enable:
enableZoom={true}
Controls:
- Mouse wheel: Zoom in/out
- Click + drag: Pan
- Double-click: Reset zoom
Programmatic zoom:
// Zoom in chartStore.dispatch({ type: 'zoom', transform: { x: 0, y: 0, k: 2 } // 2x zoom }); // Reset zoom chartStore.dispatch({ type: 'resetZoom' }); // Animated zoom chartStore.dispatch({ type: 'zoomAnimated', targetTransform: { x: 100, y: 50, k: 1.5 } });
Brush Selection
Enable:
enableBrush={true}
Controls:
- Click + drag: Create brush
- Drag corners: Resize brush
- Drag center: Move brush
- Click outside: Clear brush
Access selected data:
const selected = $chartStore.selection.selectedData; console.log('Selected points:', selected);
Callback:
<Chart store={chartStore} enableBrush={true} onSelectionChange={(selected) => { console.log('Selected:', selected); // Do something with selected data }} />
Tooltips
Enable:
enableTooltip={true} (default)
Behavior:
- Hover over data points to show tooltip
- Automatically displays data values
- Tooltip content customizable via Observable Plot
Custom tooltips:
// Via Plot spec const spec = { marks: [ Plot.dot(data, { x: 'x', y: 'y', title: (d) => `${d.name}: ${d.value}` // Custom tooltip }) ] };
Point Selection
Enable: Click on points when
enableBrush={false}
// Listen for point selection $effect(() => { if ($chartStore.selection.type === 'point') { const selected = $chartStore.selection.selectedData[0]; console.log('Selected point:', selected); } }); // Programmatic selection chartStore.dispatch({ type: 'selectPoint', data: myDataPoint, index: 5 }); // Clear selection chartStore.dispatch({ type: 'clearSelection' });
DATA BINDING
Static Data
const data = [ { x: 1, y: 10 }, { x: 2, y: 20 }, { x: 3, y: 15 } ]; const chartStore = createStore({ initialState: createInitialChartState({ data }), reducer: chartReducer, dependencies: {} });
Dynamic Data Updates
// Update data chartStore.dispatch({ type: 'setData', data: newData }); // Filter data chartStore.dispatch({ type: 'filterData', predicate: (d) => d.value > 10 }); // Clear filters chartStore.dispatch({ type: 'clearFilters' });
Real-time Data
// Append new point const currentData = $chartStore.data; chartStore.dispatch({ type: 'setData', data: [...currentData, newPoint] }); // Update via Effect Effect.run(async (dispatch) => { const newData = await fetchLatestData(); dispatch({ type: 'setData', data: newData }); });
RESPONSIVE DESIGN
Auto-sizing
Omit
width and height for responsive sizing:
<Chart store={chartStore} type="scatter" x="x" y="y" />
Chart will:
- Use container width (100%)
- Default height (400px)
- Resize on window resize
Fixed Dimensions
<Chart store={chartStore} type="scatter" x="x" y="y" width={800} height={600} />
Container-based Sizing
<div class="chart-container"> <Chart store={chartStore} ... /> </div> <style> .chart-container { width: 100%; height: 500px; } </style>
Responsive Breakpoints
let chartWidth = $state(800); $effect(() => { const updateWidth = () => { chartWidth = window.innerWidth < 768 ? 400 : 800; }; window.addEventListener('resize', updateWidth); updateWidth(); return () => window.removeEventListener('resize', updateWidth); }); <Chart store={chartStore} width={chartWidth} ... />
ACCESSIBILITY
ARIA Labels
Chart component includes:
- Marks as imagerole="img"
- Describes chart contentaria-label
- Links to summaryaria-describedby
<Chart store={chartStore} type="scatter" x="x" y="y" aria-label="Scatter plot showing relationship between X and Y" />
Screen Reader Summary
Auto-generated summary includes:
- Chart type
- Number of data points
- Selection status
- Filter status
Example output:
"Scatter plot showing 42 data points, 5 selected"
Keyboard Navigation
: Focus chartTab
: Pan (when zoomed)Arrow keys
: Zoom in/out+/-
: Reset zoom0
: Clear selectionEscape
COMPLETE EXAMPLES
Basic Scatter Plot
<script lang="ts"> import { createStore } from '@composable-svelte/core'; import { Chart, chartReducer, createInitialChartState } from '@composable-svelte/charts'; const data = [ { x: 10, y: 20, category: 'A' }, { x: 15, y: 35, category: 'B' }, { x: 20, y: 25, category: 'A' }, { x: 25, y: 45, category: 'B' } ]; const chartStore = createStore({ initialState: createInitialChartState({ data }), reducer: chartReducer, dependencies: {} }); </script> <Chart store={chartStore} type="scatter" x="x" y="y" color="category" size={6} width={800} height={400} enableZoom={true} enableTooltip={true} />
Time Series Line Chart
<script lang="ts"> import { createStore } from '@composable-svelte/core'; import { Chart, chartReducer, createInitialChartState } from '@composable-svelte/charts'; interface DataPoint { date: Date; value: number; series: string; } const data: DataPoint[] = [ { date: new Date('2024-01-01'), value: 100, series: 'A' }, { date: new Date('2024-01-02'), value: 120, series: 'A' }, { date: new Date('2024-01-03'), value: 115, series: 'A' }, { date: new Date('2024-01-01'), value: 80, series: 'B' }, { date: new Date('2024-01-02'), value: 95, series: 'B' }, { date: new Date('2024-01-03'), value: 105, series: 'B' } ]; const chartStore = createStore({ initialState: createInitialChartState({ data }), reducer: chartReducer, dependencies: {} }); </script> <Chart store={chartStore} type="line" x="date" y="value" color="series" width={1000} height={400} enableZoom={true} enableTooltip={true} />
Interactive Bar Chart
<script lang="ts"> import { createStore } from '@composable-svelte/core'; import { Chart, chartReducer, createInitialChartState } from '@composable-svelte/charts'; const data = [ { category: 'Q1', revenue: 45000, expenses: 32000 }, { category: 'Q2', revenue: 52000, expenses: 38000 }, { category: 'Q3', revenue: 48000, expenses: 35000 }, { category: 'Q4', revenue: 61000, expenses: 42000 } ]; const chartStore = createStore({ initialState: createInitialChartState({ data }), reducer: chartReducer, dependencies: {} }); let selectedCategory = $state<string | null>(null); function handleSelection(selected: any[]) { selectedCategory = selected[0]?.category || null; } </script> <div> <Chart store={chartStore} type="bar" x="category" y="revenue" enableBrush={true} enableTooltip={true} onSelectionChange={handleSelection} /> {#if selectedCategory} <p>Selected: {selectedCategory}</p> {/if} </div>
Real-time Data Visualization
<script lang="ts"> import { createStore, Effect } from '@composable-svelte/core'; import { Chart, chartReducer, createInitialChartState } from '@composable-svelte/charts'; import { onMount } from 'svelte'; let data = $state<Array<{ time: number; value: number }>>([]); const chartStore = createStore({ initialState: createInitialChartState({ data }), reducer: chartReducer, dependencies: {} }); // Simulate real-time data stream let intervalId: number; onMount(() => { let time = 0; intervalId = setInterval(() => { const newPoint = { time: time++, value: Math.random() * 100 }; data = [...data.slice(-50), newPoint]; // Keep last 50 points chartStore.dispatch({ type: 'setData', data }); }, 100); return () => clearInterval(intervalId); }); </script> <Chart store={chartStore} type="line" x="time" y="value" yDomain={[0, 100]} enableAnimations={true} />
COMMON PATTERNS
Multiple Charts with Shared Selection
<script lang="ts"> const data = [...]; // Shared data const chartStore1 = createStore({...}); const chartStore2 = createStore({...}); let selectedData = $state<any[]>([]); function syncSelection(selected: any[]) { selectedData = selected; // Update both charts const indices = selected.map(d => data.indexOf(d)); chartStore1.dispatch({ type: 'selectRange', range: [indices[0], indices[indices.length - 1]] }); chartStore2.dispatch({ type: 'selectRange', range: [indices[0], indices[indices.length - 1]] }); } </script> <Chart store={chartStore1} ... onSelectionChange={syncSelection} /> <Chart store={chartStore2} ... onSelectionChange={syncSelection} />
Linked Zoom
<script lang="ts"> const masterStore = createStore({...}); const detailStore = createStore({...}); $effect(() => { const transform = $masterStore.transform; detailStore.dispatch({ type: 'zoom', transform }); }); </script> <Chart store={masterStore} enableZoom={true} /> <Chart store={detailStore} /> <!-- Zooms with master -->
Dynamic Filtering
<script lang="ts"> let minValue = $state(0); let maxValue = $state(100); $effect(() => { chartStore.dispatch({ type: 'filterData', predicate: (d) => d.value >= minValue && d.value <= maxValue }); }); </script> <input type="range" bind:value={minValue} min="0" max="100" /> <input type="range" bind:value={maxValue} min="0" max="100" /> <Chart store={chartStore} ... />
PERFORMANCE CONSIDERATIONS
Large Datasets
Problem: Rendering 10,000+ points can be slow.
Solutions:
- Data aggregation: Bin/group data before rendering
- Sampling: Show subset of data (e.g., every 10th point)
- Level-of-detail: Show more detail when zoomed in
- WebGL rendering: Use Plot's WebGL marks (future)
// Example: Downsample data const downsample = (data: any[], factor: number) => data.filter((_, i) => i % factor === 0); const displayData = data.length > 1000 ? downsample(data, Math.ceil(data.length / 1000)) : data; chartStore.dispatch({ type: 'setData', data: displayData });
Frequent Updates
Problem: Real-time data updates cause re-renders.
Solutions:
- Batch updates: Update every N milliseconds, not every data point
- Sliding window: Keep fixed number of points (e.g., last 100)
- Throttle: Limit update frequency
// Throttle updates let pendingData: any[] = []; let updateTimer: number | null = null; function queueUpdate(newData: any[]) { pendingData = newData; if (updateTimer === null) { updateTimer = setTimeout(() => { chartStore.dispatch({ type: 'setData', data: pendingData }); updateTimer = null; }, 100); // Update max once per 100ms } }
Animation Performance
Disable animations for large datasets or frequent updates:
<Chart store={chartStore} enableAnimations={false} ... />
TESTING
Basic Chart Testing
import { TestStore } from '@composable-svelte/core'; import { chartReducer, createInitialChartState } from '@composable-svelte/charts'; const store = new TestStore({ initialState: createInitialChartState({ data: [] }), reducer: chartReducer, dependencies: {} }); // Test data update await store.send({ type: 'setData', data: [{ x: 1, y: 10 }] }, (state) => { expect(state.data.length).toBe(1); expect(state.filteredData.length).toBe(1); }); // Test filtering await store.send({ type: 'filterData', predicate: (d) => d.y > 5 }, (state) => { expect(state.filteredData.length).toBe(1); });
Selection Testing
await store.send({ type: 'selectPoint', data: { x: 1, y: 10 }, index: 0 }, (state) => { expect(state.selection.type).toBe('point'); expect(state.selection.selectedData.length).toBe(1); expect(state.selection.selectedIndices).toEqual([0]); }); await store.send({ type: 'clearSelection' }, (state) => { expect(state.selection.type).toBe('none'); expect(state.selection.selectedData.length).toBe(0); });
TROUBLESHOOTING
Chart not rendering:
- Check Observable Plot installed:
npm install @observablehq/plot - Verify data is non-empty array
- Ensure x/y accessors match data properties
Tooltips not showing:
- Verify
enableTooltip={true} - Check Observable Plot version (0.6+ required)
- Ensure data points have valid values (not null/undefined)
Zoom not working:
- Verify
enableZoom={true} - Check chart has fixed dimensions (not 100% width/height)
- Ensure D3-zoom is installed
Poor performance:
- Reduce data points (aggregate, sample, or downsample)
- Disable animations for large datasets
- Use simpler mark types (dots vs complex shapes)
Selection not updating:
- Check
callbackonSelectionChange - Verify
orenableBrush={true}enableSelection={true} - Ensure store is reactive (
)$chartStore.selection
CROSS-REFERENCES
Related Skills:
- composable-svelte-core: Store, reducer, Effect system
- composable-svelte-components: UI components (Button, Slider, etc.)
- composable-svelte-testing: TestStore for testing chart reducers
When to Use Each Package:
- charts: 2D data visualization, interactive charts
- graphics: 3D scenes, WebGPU/WebGL (see composable-svelte-graphics)
- maps: Geospatial data (see composable-svelte-maps)
- code: Code editors, media players (see composable-svelte-code)