Visualization Creator
by fmadore
|
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 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)',
};
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.
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.
Matplotlib
Foundational plotting library. Create line plots, scatter, bar, histograms, heatmaps, 3D, subplots, export PNG/PDF/SVG, for scientific visualization and publication figures.
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.
Seaborn
Statistical visualization. Scatter, box, violin, heatmaps, pair plots, regression, correlation matrices, KDE, faceted plots, for exploratory analysis and publication figures.
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
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.
Query Writing
For writing and executing SQL queries - from simple single-table queries to complex multi-table JOINs and aggregations
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.
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.
