Back to list
fmadore

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.

0🍴 0📅 Jan 25, 2026

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 statistics
  • generate_index_entities.py - Entity data
  • generate_treemap.py - Country/hierarchical data
  • generate_timeline.py - Temporal data
  • generate_categories.py - Category distributions
  • generate_wordcloud.py - Word frequencies
  • generate_world_map.py - Geographic data
  • generate_cooccurrence.py - Term co-occurrence matrices
  • generate_network.py - Network graph data
  • generate_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 optionally build/data/)
  • Use UTF-8 encoding with ensure_ascii=False for 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 charts
  • PieChart.svelte - Pie/donut charts
  • Treemap.svelte - Hierarchical treemaps
  • Duration.svelte - Duration/timeline bars
  • Tooltip.svelte - Reusable tooltip

D3 components (src/lib/components/visualizations/charts/d3/):

  • TimelineChart.svelte - Timeline visualizations
  • StackedBarChart.svelte - Stacked bar charts
  • CooccurrenceMatrix.svelte - Matrix visualizations
  • BarChartRace.svelte - Animated bar chart race
  • WordAssociations.svelte - Word association graphs

Map components (src/lib/components/visualizations/world-map/):

  • WorldMapVisualization.svelte - Main map container with controls
  • Map.svelte - Leaflet map wrapper
  • ChoroplethLayer.svelte - Choropleth coloring layer
  • Uses mapDataStore for state management (viewMode, filters, selected location)

Network components (src/lib/components/visualizations/network/):

  • NetworkGraph.svelte - Sigma.js graph renderer
  • NetworkControls.svelte - Graph interaction controls
  • NetworkNodePanel.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 location
  • filters: sourceCountry, yearRange
  • filteredLocations: 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-1 through --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.js suffix
  • 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

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon