Visualization Creator

by fmadore

skill

|

Skill Details

Repository Files

1 file in this skill directory


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)',
};

Related Skills

Attack Tree Construction

Build comprehensive attack trees to visualize threat paths. Use when mapping attack scenarios, identifying defense gaps, or communicating security risks to stakeholders.

skill

Grafana Dashboards

Create and manage production Grafana dashboards for real-time visualization of system and application metrics. Use when building monitoring dashboards, visualizing metrics, or creating operational observability interfaces.

skill

Matplotlib

Foundational plotting library. Create line plots, scatter, bar, histograms, heatmaps, 3D, subplots, export PNG/PDF/SVG, for scientific visualization and publication figures.

skill

Scientific Visualization

Create publication figures with matplotlib/seaborn/plotly. Multi-panel layouts, error bars, significance markers, colorblind-safe, export PDF/EPS/TIFF, for journal-ready scientific plots.

skill

Seaborn

Statistical visualization. Scatter, box, violin, heatmaps, pair plots, regression, correlation matrices, KDE, faceted plots, for exploratory analysis and publication figures.

skill

Shap

Model interpretability and explainability using SHAP (SHapley Additive exPlanations). Use this skill when explaining machine learning model predictions, computing feature importance, generating SHAP plots (waterfall, beeswarm, bar, scatter, force, heatmap), debugging models, analyzing model bias or fairness, comparing models, or implementing explainable AI. Works with tree-based models (XGBoost, LightGBM, Random Forest), deep learning (TensorFlow, PyTorch), linear models, and any black-box model

skill

Pydeseq2

Differential gene expression analysis (Python DESeq2). Identify DE genes from bulk RNA-seq counts, Wald tests, FDR correction, volcano/MA plots, for RNA-seq analysis.

skill

Query Writing

For writing and executing SQL queries - from simple single-table queries to complex multi-table JOINs and aggregations

skill

Pydeseq2

Differential gene expression analysis (Python DESeq2). Identify DE genes from bulk RNA-seq counts, Wald tests, FDR correction, volcano/MA plots, for RNA-seq analysis.

skill

Scientific Visualization

Meta-skill for publication-ready figures. Use when creating journal submission figures requiring multi-panel layouts, significance annotations, error bars, colorblind-safe palettes, and specific journal formatting (Nature, Science, Cell). Orchestrates matplotlib/seaborn/plotly with publication styles. For quick exploration use seaborn or plotly directly.

skill

Skill Information

Category:Skill
Last Updated:1/12/2026