Linked Visualizations
Cross-framework state sharing using nanostores. The map (Svelte) and scatterplot (Vue) share state seamlessly through a common store.
Live Demo
SvelteClick a region to filter the scatterplot
Region Map
Click a region to filter demographics
VueDemographics update based on map selection
Demographics Scatterplot
Showing: All Regions (0 data points)
Implementation Guide
Step 1: Create a Shared Store
Define your shared state using nanostores atoms and computed values:
// stores/linkedVizStore.ts
import { atom, computed } from 'nanostores';
export const selectedRegionId = atom<string | null>(null);
export const filteredData = computed(selectedRegionId, (id) => {
if (!id) return ALL_DATA;
return ALL_DATA.filter(d => d.regionId === id);
});
export function selectRegion(id: string | null) {
selectedRegionId.set(id);
}Step 2: Subscribe in Svelte
Use the store directly in Svelte components:
<script lang="ts">
import { onMount } from 'svelte';
import { selectedRegionId, selectRegion } from '../stores/linkedVizStore';
let currentSelection: string | null = null;
onMount(() => {
const unsubscribe = selectedRegionId.subscribe((value) => {
currentSelection = value;
});
return unsubscribe;
});
</script>
<button on:click={() => selectRegion('northeast')}>
Northeast
</button>Step 3: Subscribe in Vue
Use the store with Vue's reactivity system:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { filteredData } from '../stores/linkedVizStore';
const data = ref([]);
let unsubscribe: (() => void) | null = null;
onMounted(() => {
unsubscribe = filteredData.subscribe((value) => {
data.value = value;
});
});
onUnmounted(() => {
unsubscribe?.();
});
</script>
<template>
<div v-for="item in data" :key="item.id">
{{ item.name }}
</div>
</template>Architecture
┌─────────────────────────────────────────────────┐
│ Astro Page │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ Svelte Map │ │ Vue Scatterplot │ │
│ │ │ │ │ │
│ │ onClick ────────┼──┼─► Updates display │ │
│ │ │ │ │ │
│ └────────┬─────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────────────────┐ │
│ │ Nanostore (TypeScript) │ │
│ │ │ │
│ │ selectedRegionId: atom<string | null> │ │
│ │ filteredData: computed(...) │ │
│ │ │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘Key Benefits
- Framework agnostic: Same store works with React, Vue, Svelte, and vanilla JS
- Tiny bundle: nanostores is ~1KB gzipped
- TypeScript support: Full type safety across frameworks
- Computed values: Derived state updates automatically
- No context needed: Works without React Context or Vue provide/inject
Dependencies
npm install nanostores @nanostores/vue
npm install @astrojs/svelte @astrojs/vue svelte vueKey Files
src/stores/linkedVizStore.ts- Shared statesrc/components/examples/RegionMap.svelte- Svelte mapsrc/components/examples/DemographicsScatter.vue- Vue chart
Use Cases
Dashboard Filters
Global filters that update multiple charts from different frameworks simultaneously.
Brushing & Linking
Select data in one visualization to highlight corresponding points in others.
Micro-frontends
Share state between independently deployed components built with different frameworks.