Composable Svelte Charts
by NeverSight
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.
Skill Details
name: composable-svelte-charts description: 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.
Composable 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
store: Store<ChartState, ChartAction>- Chart store (required)type: 'scatter' | 'line' | 'bar' | 'area' | 'histogram'- Chart type (default: 'scatter')width: number- Chart width (optional, responsive if omitted)height: number- Chart height (optional, defaults to 400px)x: string | ((d) => any)- X accessor (required)y: string | ((d) => any)- Y accessor (required)color: string | ((d) => any)- Color accessor (optional)size: number- Mark size (optional)xDomain: [number, number] | 'auto'- X domain (optional)yDomain: [number, number] | 'auto'- Y domain (optional)enableZoom: boolean- Enable zoom/pan (default: false)enableBrush: boolean- Enable brush selection (default: false)enableTooltip: boolean- Enable tooltips (default: true)enableAnimations: boolean- Enable transitions (default: true)onSelectionChange: (selected: any[]) => void- Selection callback (optional)
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: X-axis positiony: Y-axis positioncolor: Point color (optional)size: Point size (optional)
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
coloraccessor - 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:
role="img"- Marks as imagearia-label- Describes chart contentaria-describedby- Links to summary
<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
Tab: Focus chartArrow keys: Pan (when zoomed)+/-: Zoom in/out0: Reset zoomEscape: Clear selection
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
onSelectionChangecallback - Verify
enableBrush={true}orenableSelection={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)
Related Skills
Xlsx
Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas
Clickhouse Io
ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads.
Clickhouse Io
ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads.
Analyzing Financial Statements
This skill calculates key financial ratios and metrics from financial statement data for investment analysis
Data Storytelling
Transform data into compelling narratives using visualization, context, and persuasive structure. Use when presenting analytics to stakeholders, creating data reports, or building executive presentations.
Team Composition Analysis
This skill should be used when the user asks to "plan team structure", "determine hiring needs", "design org chart", "calculate compensation", "plan equity allocation", or requests organizational design and headcount planning for a startup.
Startup Financial Modeling
This skill should be used when the user asks to "create financial projections", "build a financial model", "forecast revenue", "calculate burn rate", "estimate runway", "model cash flow", or requests 3-5 year financial planning for a startup.
Kpi Dashboard Design
Design effective KPI dashboards with metrics selection, visualization best practices, and real-time monitoring patterns. Use when building business dashboards, selecting metrics, or designing data visualization layouts.
Dbt Transformation Patterns
Master dbt (data build tool) for analytics engineering with model organization, testing, documentation, and incremental strategies. Use when building data transformations, creating data models, or implementing analytics engineering best practices.
Startup Metrics Framework
This skill should be used when the user asks about "key startup metrics", "SaaS metrics", "CAC and LTV", "unit economics", "burn multiple", "rule of 40", "marketplace metrics", or requests guidance on tracking and optimizing business performance metrics.
