
visualization-creator
by fmadore
Interactive data visualization dashboard for the Islam West Africa Collection (IWAC) dataset, built with SvelteKit and featuring charts, treemaps, word clouds, and bilingual support.
SKILL.md
name: visualization-creator description: | Create new visualizations for the IWAC Dashboard. Use this skill when:
- Adding a new chart, graph, map, or data visualization
- Creating Python data generation scripts for new visualizations
- Building reusable visualization components with LayerChart, D3, Leaflet (maps), or Sigma.js (networks) This skill enforces the project's patterns: Svelte 5 runes, CSS variables, i18n, shadcn-svelte, and static data generation. model: opus
IWAC Visualization Creator
Overview
This skill guides you through creating new visualizations for the IWAC Dashboard following the established patterns and best practices.
Workflow
Step 1: Understand Data Requirements
First, invoke the iwac-dataset skill to understand:
- Which subset(s) you need (articles, publications, documents, audiovisual, index, references)
- Available fields and their types
- Common query patterns for filtering
Ask the user:
- What data should this visualization display?
- What filtering/interaction is needed?
- Should it be bilingual (always yes for user-facing text)?
Step 2: Check Existing Python Scripts
Look in scripts/ for existing generators that might already produce the needed data:
ls scripts/generate_*.py
Common existing scripts:
generate_overview_stats.py- Summary statisticsgenerate_index_entities.py- Entity datagenerate_treemap.py- Country/hierarchical datagenerate_timeline.py- Temporal datagenerate_categories.py- Category distributionsgenerate_wordcloud.py- Word frequenciesgenerate_world_map.py- Geographic datagenerate_cooccurrence.py- Term co-occurrence matricesgenerate_network.py- Network graph datagenerate_topics.py- Topic modeling results
Check static/data/ for existing JSON files that might already have what you need.
Step 3: Create/Update Python Generator
If new data generation is needed, create a script following this pattern:
"""
Generate [description] data for the IWAC Dashboard.
Output: static/data/[filename].json
"""
from datasets import load_dataset
import json
from pathlib import Path
def main():
# Load dataset using iwac-dataset skill patterns
ds = load_dataset("fmadore/islam-west-africa-collection", "articles")
df = ds['train'].to_pandas()
# Process data...
result = {
# Structure for frontend consumption
}
# Save to static/data/
output_dir = Path(__file__).parent.parent / "static" / "data"
output_dir.mkdir(parents=True, exist_ok=True)
with open(output_dir / "[filename].json", "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"Generated: {output_dir / '[filename].json'}")
if __name__ == "__main__":
main()
Key patterns:
- Output to
static/data/(and optionallybuild/data/) - Use UTF-8 encoding with
ensure_ascii=Falsefor French text - Pre-compute aggregations - no heavy processing in frontend
- Structure data for direct frontend consumption
Step 4: Check Existing Visualization Components
Before creating new components, check for reusable ones:
LayerChart components (src/lib/components/visualizations/charts/layerchart/):
Bar.svelte- Bar chartsPieChart.svelte- Pie/donut chartsTreemap.svelte- Hierarchical treemapsDuration.svelte- Duration/timeline barsTooltip.svelte- Reusable tooltip
D3 components (src/lib/components/visualizations/charts/d3/):
TimelineChart.svelte- Timeline visualizationsStackedBarChart.svelte- Stacked bar chartsCooccurrenceMatrix.svelte- Matrix visualizationsBarChartRace.svelte- Animated bar chart raceWordAssociations.svelte- Word association graphs
Map components (src/lib/components/visualizations/world-map/):
WorldMapVisualization.svelte- Main map container with controlsMap.svelte- Leaflet map wrapperChoroplethLayer.svelte- Choropleth coloring layer- Uses
mapDataStorefor state management (viewMode, filters, selected location)
Network components (src/lib/components/visualizations/network/):
NetworkGraph.svelte- Sigma.js graph rendererNetworkControls.svelte- Graph interaction controlsNetworkNodePanel.svelte- Node detail panel- Uses Graphology for graph data structure and ForceAtlas2 for layout
Other visualizations:
Wordcloud.svelte- D3 word clouds
Step 5: Create New Visualization Component
If a new component is needed, follow these patterns:
File Location
src/lib/components/visualizations/charts/layerchart/NewChart.svelte # LayerChart-based
src/lib/components/visualizations/charts/d3/NewChart.svelte # D3-based
src/lib/components/visualizations/NewVisualization.svelte # Standalone
Component Template (LayerChart preferred)
<script lang="ts">
import { t, languageStore } from '$lib/stores/translationStore.svelte.js';
import { Chart, Svg, Bar, Axis, Tooltip } from 'layerchart';
import { scaleBand, scaleLinear } from 'd3-scale';
interface DataItem {
label: string;
value: number;
}
let { data = [] }: { data: DataItem[] } = $props();
// Reactive to language changes
const chartTitle = $derived(t('chart.new_chart_title'));
// Scales
const xScale = $derived(scaleBand().domain(data.map(d => d.label)).padding(0.1));
const yScale = $derived(scaleLinear().domain([0, Math.max(...data.map(d => d.value))]));
</script>
<div class="h-[400px] w-full">
<Chart {data} {xScale} {yScale} padding={{ left: 40, bottom: 40, top: 20, right: 20 }}>
<Svg>
<Axis placement="left" />
<Axis placement="bottom" />
<Bar
x="label"
y="value"
class="fill-[var(--chart-1)]"
/>
</Svg>
<Tooltip.Root let:data>
<Tooltip.Header>{data.label}</Tooltip.Header>
<Tooltip.Item label={t('chart.value')} value={data.value} />
</Tooltip.Root>
</Chart>
</div>
Component Template (D3)
<script lang="ts">
import { t, languageStore } from '$lib/stores/translationStore.svelte.js';
import * as d3 from 'd3-selection';
import { scaleLinear, scaleBand } from 'd3-scale';
let { data = [] }: { data: unknown[] } = $props();
let container: HTMLDivElement;
// Re-render on language change
const lang = $derived(languageStore.current);
$effect(() => {
if (container && data.length > 0) {
// Access lang to create dependency
const _ = lang;
renderChart();
}
});
function renderChart() {
// D3 rendering logic using CSS variables
// Use: var(--chart-1), var(--foreground), var(--muted-foreground), etc.
}
</script>
<div bind:this={container} class="h-[400px] w-full"></div>
Component Template (Leaflet Map)
For geographic visualizations, use Leaflet with the existing map components:
<script lang="ts">
import { t } from '$lib/stores/translationStore.svelte.js';
import { mapDataStore } from '$lib/stores/mapDataStore.svelte.js';
import { Map, ChoroplethLayer } from '$lib/components/visualizations/world-map/index.js';
let { locations = [] }: { locations: GeoLocation[] } = $props();
interface GeoLocation {
lat: number;
lng: number;
country: string;
value: number;
}
</script>
<div class="h-[500px] w-full">
<Map
center={[12, 0]}
zoom={4}
locations={locations}
>
<ChoroplethLayer
data={locations}
valueField="value"
colorScale="blues"
/>
</Map>
</div>
Map data store (mapDataStore.svelte.ts):
viewMode: 'bubbles' | 'choropleth'selectedLocation: Currently selected locationfilters: sourceCountry, yearRangefilteredLocations: Derived filtered data
Component Template (Sigma.js Network)
For network/graph visualizations, use Sigma.js with Graphology:
<script lang="ts">
import { t } from '$lib/stores/translationStore.svelte.js';
import Graph from 'graphology';
import Sigma from 'sigma';
import forceAtlas2 from 'graphology-layout-forceatlas2';
let { nodes = [], edges = [] }: { nodes: NetworkNode[], edges: NetworkEdge[] } = $props();
let container: HTMLDivElement;
let sigma: Sigma | null = null;
interface NetworkNode {
id: string;
label: string;
size: number;
color?: string;
}
interface NetworkEdge {
source: string;
target: string;
weight: number;
}
$effect(() => {
if (container && nodes.length > 0) {
initGraph();
}
return () => {
sigma?.kill();
};
});
function initGraph() {
const graph = new Graph();
// Add nodes
nodes.forEach(node => {
graph.addNode(node.id, {
label: node.label,
size: node.size,
color: node.color || 'var(--chart-1)',
x: Math.random(),
y: Math.random()
});
});
// Add edges
edges.forEach(edge => {
graph.addEdge(edge.source, edge.target, { weight: edge.weight });
});
// Apply ForceAtlas2 layout
forceAtlas2.assign(graph, { iterations: 100 });
// Render with Sigma
sigma = new Sigma(graph, container, {
renderLabels: true,
labelColor: { color: 'var(--foreground)' }
});
}
</script>
<div bind:this={container} class="h-[600px] w-full"></div>
Network data format (from generate_network.py):
{
"nodes": [
{ "id": "node1", "label": "Entity Name", "size": 10, "type": "person" }
],
"edges": [
{ "source": "node1", "target": "node2", "weight": 5 }
]
}
Step 6: Styling Requirements
CRITICAL: Use CSS variables, never hardcode colors!
<!-- CORRECT -->
<div class="bg-background text-foreground border-border">
<Bar class="fill-[var(--chart-1)]" />
<text fill="var(--muted-foreground)">
<!-- WRONG -->
<div class="bg-white text-black border-gray-200">
<Bar class="fill-blue-500" />
<text fill="#666">
Available chart colors:
--chart-1through--chart-5- Primary chart palette--country-color-*- Country-specific colors (burkina-faso, benin, cote-divoire, niger, togo, nigeria)
Use shadcn-svelte for UI elements:
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { Skeleton } from '$lib/components/ui/skeleton/index.js';
Step 7: Internationalization
All user-facing text must use the translation function:
<script>
import { t, languageStore } from '$lib/stores/translationStore.svelte.js';
// For reactive updates when language changes
const title = $derived(t('chart.my_chart_title'));
</script>
<h2>{t('chart.title')}</h2>
<p>{t('chart.description', [someValue])}</p> <!-- with parameters -->
Add new translation keys to src/lib/stores/translationStore.svelte.ts:
// In the translations object, add to both 'en' and 'fr' sections:
'chart.new_key': 'English text',
'chart.new_key': 'French text',
Step 8: Create Route Page
If the visualization needs its own page:
src/routes/[page-name]/+page.ts:
import type { PageLoad } from './$types';
import { base } from '$app/paths';
export const prerender = true;
export const load: PageLoad = async ({ fetch }) => {
const response = await fetch(`${base}/data/[filename].json`);
if (!response.ok) {
throw new Error(`Failed to load data: ${response.status}`);
}
const data = await response.json();
return { data };
};
src/routes/[page-name]/+page.svelte:
<script lang="ts">
import { t } from '$lib/stores/translationStore.svelte.js';
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card/index.js';
import { NewVisualization } from '$lib/components/visualizations/index.js';
let { data: pageData } = $props();
const chartData = $derived(pageData.data);
</script>
<div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-4">{t('pages.new_page_title')}</h1>
<Card>
<CardHeader>
<CardTitle>{t('chart.new_chart_title')}</CardTitle>
</CardHeader>
<CardContent>
<NewVisualization data={chartData} />
</CardContent>
</Card>
</div>
Step 9: Update Barrel Exports
Add new components to the appropriate index.ts:
// src/lib/components/visualizations/charts/layerchart/index.ts
export { default as NewChart } from './NewChart.svelte';
Step 10: Add to Sidebar Navigation
Update src/lib/components/layout/AppSidebar.svelte to add navigation link.
Checklist
Before completing, verify:
- Data: Python script generates JSON to
static/data/ - Component: Uses Svelte 5 runes (
$props,$state,$derived,$effect) - Styling: Uses CSS variables only (no hardcoded colors)
- i18n: All text uses
t()function, keys added for EN and FR - UI: Uses shadcn-svelte components where applicable
- Imports: Uses barrel exports with
/index.jssuffix - Route: Page has
export const prerender = true; - Reactivity: Chart updates when language changes
- Types: TypeScript interfaces defined for data structures
Common Patterns
Loading State
{#if loading}
<Skeleton class="h-[400px] w-full" />
{:else if error}
<div class="text-destructive">{error}</div>
{:else}
<MyChart {data} />
{/if}
Responsive Container
<div class="h-[300px] sm:h-[400px] lg:h-[500px] w-full">
<Chart ... />
</div>
Country Colors
const countryColors: Record<string, string> = {
'Burkina Faso': 'var(--country-color-burkina-faso)',
'Benin': 'var(--country-color-benin)',
'Cote d\'Ivoire': 'var(--country-color-cote-divoire)',
'Niger': 'var(--country-color-niger)',
'Togo': 'var(--country-color-togo)',
'Nigeria': 'var(--country-color-nigeria)',
};
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon
