xiaohongshu
xiaohongshu
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 @composab...
Full skill instructions
Interactive data visualization components built with Observable Plot and D3.
Package: @composable-svelte/charts
Purpose: State-driven interactive charts and data visualizations.
Technology Stack:
Chart Types:
Interactive Features:
State Management: All charts use pure reducers with type-safe actions following Composable Architecture patterns.
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}
/>
Purpose: High-level wrapper for creating charts with Observable Plot.
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)<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)}
/>
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)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:
color accessorPurpose: 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:
color)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:
Purpose: Distribution of numerical data into bins.
Best for: Data distributions, frequency analysis.
<Chart
store={chartStore}
type="histogram"
x="value"
enableTooltip={true}
/>
Notes:
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;
}
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> };
import { createInitialChartState } from '@composable-svelte/charts';
const initialState = createInitialChartState({
data: myData,
dimensions: { width: 800, height: 400 },
transitionDuration: 300
});
Enable: enableZoom={true}
Controls:
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 }
});
Enable: enableBrush={true}
Controls:
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
}}
/>
Enable: enableTooltip={true} (default)
Behavior:
Custom tooltips:
// Via Plot spec
const spec = {
marks: [
Plot.dot(data, {
x: 'x',
y: 'y',
title: (d) => `${d.name}: ${d.value}` // Custom tooltip
})
]
};
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' });
const data = [
{ x: 1, y: 10 },
{ x: 2, y: 20 },
{ x: 3, y: 15 }
];
const chartStore = createStore({
initialState: createInitialChartState({ data }),
reducer: chartReducer,
dependencies: {}
});
// 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' });
// 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 });
});
Omit width and height for responsive sizing:
<Chart
store={chartStore}
type="scatter"
x="x"
y="y"
/>
Chart will:
<Chart
store={chartStore}
type="scatter"
x="x"
y="y"
width={800}
height={600}
/>
<div class="chart-container">
<Chart store={chartStore} ... />
</div>
<style>
.chart-container {
width: 100%;
height: 500px;
}
</style>
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} ... />
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"
/>
Auto-generated summary includes:
Example output:
"Scatter plot showing 42 data points, 5 selected"
Tab: Focus chartArrow keys: Pan (when zoomed)+/-: Zoom in/out0: Reset zoomEscape: Clear selection<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}
/>
<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}
/>
<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>
<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}
/>
<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} />
<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 -->
<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} ... />
Problem: Rendering 10,000+ points can be slow.
Solutions:
// 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 });
Problem: Real-time data updates cause re-renders.
Solutions:
// 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
}
}
Disable animations for large datasets or frequent updates:
<Chart
store={chartStore}
enableAnimations={false}
...
/>
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);
});
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);
});
Chart not rendering:
npm install @observablehq/plotTooltips not showing:
enableTooltip={true}Zoom not working:
enableZoom={true}Poor performance:
Selection not updating:
onSelectionChange callbackenableBrush={true} or enableSelection={true}$chartStore.selection)Related Skills:
When to Use Each Package:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience