Visualization Cookbook¶
Recipes for building and integrating the seven D3 visualizations in the FCC React frontend. Each recipe follows the same structure: data shape, props interface, integration example, and accessibility notes.
Table of Contents¶
- The D3 Rosetta Pattern
- ForceGraph Recipe
- SankeyWorkflow Recipe
- VoronoiPersonaMap Recipe
- ChordDiagram Recipe
- HyperEdgeGraph Recipe
- ProvenanceFlow Recipe
- HeatmapMatrix Recipe
- Responsive Sizing Pattern
- Dark Mode Support
- Accessibility
The D3 Rosetta Pattern¶
Every FCC visualization follows the same architectural pattern:
- React owns the container. The component renders an
<svg ref={svgRef}>element and manages the component lifecycle. - D3 renders inside
useRef. AuseEffecthook clears the SVG and rebuilds the visualization whenever props change. - Shared utilities. All visualizations import from
d3-utils.tsfor consistent color scales, tooltips, and responsive SVG setup.
import { useRef, useEffect } from 'react'
import * as d3 from 'd3'
import { createColorScale, createTooltip, responsiveSvg } from './d3-utils'
interface MyVizProps {
data: MyDataType[]
width?: number
height?: number
}
export default function MyVisualization({ data, width = 800, height = 600 }: MyVizProps) {
const svgRef = useRef<SVGSVGElement>(null)
useEffect(() => {
if (!svgRef.current || data.length === 0) return
const svg = d3.select(svgRef.current)
svg.selectAll('*').remove() // Clear previous render
const container = svgRef.current.parentElement
const tip = container ? createTooltip(container) : null
// ... D3 rendering logic ...
return () => { tip?.hide() }
}, [data, width, height])
return (
<svg
ref={svgRef}
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
role="img"
aria-label="My Visualization"
/>
)
}
Shared Utilities in d3-utils.ts¶
| Utility | Purpose |
|---|---|
FCC_COLORS |
Record mapping phases and categories to hex colors |
createColorScale(categories) |
D3 ordinal scale from category names to FCC colors |
createTooltip(container) |
Tooltip with show, hide, update methods |
responsiveSvg(container, w, h) |
Creates a responsive SVG with viewBox |
getFccColor(key, fallback?) |
Look up a single FCC color by phase or category |
lightenColor(hex, amount?) |
Compute a lighter variant for fills |
ForceGraph Recipe¶
Purpose: Render a force-directed network of persona relationships.
Data Shape¶
import { ForceNode, ForceLink } from '../visualizations/ForceGraph'
// Nodes: one per persona
const nodes: ForceNode[] = [
{ id: 'RC', name: 'Research Catalyst', category: 'core', phase: 'find' },
{ id: 'SA', name: 'Solution Architect', category: 'core', phase: 'create' },
{ id: 'QR', name: 'Quality Reviewer', category: 'core', phase: 'critique' },
]
// Links: cross-reference relationships
const links: ForceLink[] = [
{ source: 'RC', target: 'SA', type: 'handoff', strength: 'primary' },
{ source: 'SA', target: 'QR', type: 'handoff', strength: 'primary' },
{ source: 'QR', target: 'RC', type: 'feedback', strength: 'secondary' },
]
Props Interface¶
interface ForceGraphProps {
nodes: ForceNode[]
links: ForceLink[]
width?: number // default: 800
height?: number // default: 600
}
Integration Example¶
import ForceGraph from '../visualizations/ForceGraph'
function PersonaNetwork() {
const { personas } = usePersonaRegistry()
const nodes = personas.map(p => ({
id: p.id,
name: p.name,
category: p.category,
phase: p.phase,
}))
// Build links from cross-reference data
const links = buildCrossReferenceLinks(personas)
return <ForceGraph nodes={nodes} links={links} width={1000} height={700} />
}
Key Configuration¶
- Collision radius: Prevents node overlap via
d3.forceCollide() - Category color mapping: Uses
createColorScalewith FCC_COLORS fallback - Link strength:
primary= 0.7,secondary= 0.4,tertiary= 0.2 - Link distance: 100px default
Accessibility Notes¶
- Add
role="img"andaria-labelto the SVG element - Tooltip content is surfaced on hover and focus
- Nodes are keyboard-focusable when the SVG receives focus
SankeyWorkflow Recipe¶
Purpose: Render workflow transitions as a Sankey diagram with FCC phase columns.
Data Shape¶
import { SankeyNode, SankeyLink } from '../visualizations/SankeyWorkflow'
const nodes: SankeyNode[] = [
{ id: 'research', name: 'Research', phase: 'Find' },
{ id: 'design', name: 'Design', phase: 'Create' },
{ id: 'review', name: 'Review', phase: 'Critique' },
{ id: 'build', name: 'Build', phase: 'Build' },
{ id: 'deploy', name: 'Deploy', phase: 'Ops' },
]
const links: SankeyLink[] = [
{ source: 'research', target: 'design', value: 3 },
{ source: 'design', target: 'review', value: 3 },
{ source: 'review', target: 'build', value: 2 },
{ source: 'review', target: 'research', value: 1 }, // feedback loop
{ source: 'build', target: 'deploy', value: 2 },
]
Props Interface¶
interface SankeyWorkflowProps {
nodes: SankeyNode[]
links: SankeyLink[]
width?: number // default: 800
height?: number // default: 600
}
Integration Example¶
import SankeyWorkflow from '../visualizations/SankeyWorkflow'
function WorkflowView({ graphName }: { graphName: string }) {
const { nodes, links } = useWorkflowGraph(graphName)
return <SankeyWorkflow nodes={nodes} links={links} />
}
Phase Column Order¶
The layout function uses this predefined order:
Nodes without a recognized phase default to the "Find" column.
FCC_COLORS for Phases¶
| Phase | Color | Hex |
|---|---|---|
| Find | Blue | #1E88E5 |
| Create | Green | #43A047 |
| Critique | Red | #E53935 |
| Build | Orange | #FB8C00 |
| Ops | Purple | #8E24AA |
Accessibility Notes¶
- Curved link paths include
aria-labelattributes describing the transition - Phase column headers act as group labels
- Tooltip shows node name and flow value
VoronoiPersonaMap Recipe¶
Purpose: Render Voronoi tessellation of personas clustered by category.
Data Shape¶
import { VoronoiPersona } from '../visualizations/VoronoiPersonaMap'
const personas: VoronoiPersona[] = [
{ id: 'RC', name: 'Research Catalyst', category: 'core' },
{ id: 'SIO', name: 'Systems Integration Officer', category: 'integration' },
{ id: 'EGO', name: 'Ethics & Governance Officer', category: 'governance' },
]
Props Interface¶
interface VoronoiPersonaMapProps {
personas: VoronoiPersona[]
width?: number // default: 800
height?: number // default: 600
}
Integration Example¶
import VoronoiPersonaMap from '../visualizations/VoronoiPersonaMap'
function TerritoryView() {
const { personas } = usePersonaRegistry()
const voronoiData = personas.map(p => ({
id: p.id,
name: p.name,
category: p.category,
}))
return <VoronoiPersonaMap personas={voronoiData} />
}
Layout Algorithm¶
- Categories are distributed angularly around the center (equal spacing).
- A force simulation nudges personas toward their category centroid with
forceXandforceY(strength 0.3). forceCollidewith radius 12 prevents overlap.- 120 simulation ticks run synchronously for deterministic output.
d3.Delaunaycomputes Voronoi cells from final positions.
Accessibility Notes¶
- Voronoi cells have
role="listitem"for screen readers - Hover tooltips show persona ID, name, and category
- Category legend provides group context
ChordDiagram Recipe¶
Purpose: Render cross-reference interactions between persona categories.
Data Shape¶
import { ChordCategory, ChordInteraction } from '../visualizations/ChordDiagram'
const categories: ChordCategory[] = [
{ name: 'core', count: 5 },
{ name: 'integration', count: 8 },
{ name: 'governance', count: 4 },
{ name: 'stakeholder', count: 5 },
]
const interactions: ChordInteraction[] = [
{ source: 'core', target: 'integration', value: 12 },
{ source: 'core', target: 'governance', value: 8 },
{ source: 'integration', target: 'governance', value: 6 },
{ source: 'stakeholder', target: 'core', value: 10 },
]
Props Interface¶
interface ChordDiagramProps {
categories: ChordCategory[]
interactions: ChordInteraction[]
width?: number // default: 600
height?: number // default: 600
}
Integration Example¶
import ChordDiagram from '../visualizations/ChordDiagram'
function CrossReferenceView() {
const { categories, interactions } = useCrossReferenceMatrix()
return <ChordDiagram categories={categories} interactions={interactions} />
}
Matrix Construction¶
The component builds an NxN adjacency matrix from the interactions array.
Interactions are symmetric: source->target value is added to both
matrix[si][ti] and matrix[ti][si] (unless source equals target).
Accessibility Notes¶
- Arc labels use
<text>elements readable by screen readers - Ribbons have
aria-labeldescribing the source, target, and value d3.chord().padAngle(0.04)ensures arcs have visible gaps for clarity
HyperEdgeGraph Recipe¶
Purpose: Render champion-to-persona orchestration as hyperedges (convex hulls).
Data Shape¶
import { ChampionNode } from '../visualizations/HyperEdgeGraph'
const nodes: ChampionNode[] = [
{ id: 'CHF', name: 'Champion of Find', category: 'champion', isChampion: true,
orchestrates: ['RC', 'KS', 'DBO'] },
{ id: 'RC', name: 'Research Catalyst', category: 'core', isChampion: false },
{ id: 'KS', name: 'Knowledge Synthesizer', category: 'core', isChampion: false },
{ id: 'DBO', name: 'Data Bridge Officer', category: 'integration', isChampion: false },
]
Props Interface¶
interface HyperEdgeGraphProps {
nodes: ChampionNode[]
width?: number // default: 800
height?: number // default: 600
}
Integration Example¶
import HyperEdgeGraph from '../visualizations/HyperEdgeGraph'
function ChampionView() {
const nodes = buildChampionNodes(personas, crossReferences)
return <HyperEdgeGraph nodes={nodes} />
}
Hyperedge Rendering¶
For each champion node with orchestrates entries:
- Identify the champion and its orchestrated persona positions.
- Compute the convex hull of all positions in the group.
- Render the hull as a semi-transparent filled polygon.
- Champion nodes are rendered at 1.5x the base node radius.
Accessibility Notes¶
- Champion nodes have a distinct visual marker (larger radius, bold label)
- Hyperedge polygons have
aria-labellisting the champion and group members - Tooltip on hull hover shows the full orchestration group
ProvenanceFlow Recipe¶
Purpose: Render project phase timeline as a horizontal DAG flow.
Data Shape¶
import { ProvenancePhase } from '../visualizations/ProvenanceFlow'
const phases: ProvenancePhase[] = [
{ id: 'p1', name: 'Phase 8', phase: 'find',
startDate: '2025-06-01', endDate: '2025-08-15',
milestones: ['Registry v1', 'Cross-ref matrix'] },
{ id: 'p2', name: 'Phase 9', phase: 'create',
startDate: '2025-08-15', endDate: '2025-11-01',
milestones: ['Event bus', 'Collaboration engine'] },
{ id: 'p3', name: 'Phase 10', phase: 'create',
startDate: '2025-11-01', endDate: '2026-01-15' },
]
Props Interface¶
interface ProvenanceFlowProps {
phases: ProvenancePhase[]
width?: number // default: 900
height?: number // default: 300
}
Integration Example¶
import ProvenanceFlow from '../visualizations/ProvenanceFlow'
function ProjectTimeline() {
const phases = useProjectPhases()
return <ProvenanceFlow phases={phases} width={1100} height={350} />
}
Date Parsing¶
Dates are parsed with d3.timeParse('%Y-%m-%d'). If parsing fails, the
component falls back to new Date(dateString). The x-axis extent is computed
from all start and end dates.
Accessibility Notes¶
- Phase bars have
aria-labelwith name and date range - Milestone markers include text labels
- Time axis uses readable date formatting
HeatmapMatrix Recipe¶
Purpose: Render a matrix heatmap of dimension scores across personas.
Data Shape¶
import { HeatmapCell } from '../visualizations/HeatmapMatrix'
const labels = ['RC', 'SA', 'QR', 'CE', 'KS']
const cells: HeatmapCell[] = [
{ source: 'RC', target: 'curiosity', value: 92 },
{ source: 'RC', target: 'leadership', value: 78 },
{ source: 'SA', target: 'curiosity', value: 85 },
{ source: 'SA', target: 'leadership', value: 88 },
// ... 0-100 scale
]
Props Interface¶
interface HeatmapMatrixProps {
labels: string[] // Row and column labels
cells: HeatmapCell[] // Cell values
width?: number // default: 600
height?: number // default: 600
colorRange?: [string, string] // default: ['#f5f5f5', '#1565C0']
}
Integration Example¶
import HeatmapMatrix from '../visualizations/HeatmapMatrix'
function DimensionHeatmap() {
const { labels, cells } = useDimensionScores()
return (
<HeatmapMatrix
labels={labels}
cells={cells}
colorRange={['#f5f5f5', '#1565C0']}
/>
)
}
Color Scale¶
The default color range interpolates from light gray (#f5f5f5) to deep blue
(#1565C0) using d3.interpolateRgb. The scale domain is [0, maxValue]
where maxValue is the highest cell value in the dataset.
Cell Sizing¶
Cell size is auto-computed as:
This ensures cells are square and fit within the available space.
Accessibility Notes¶
- Each cell has
aria-labelin the format "PersonaID / Dimension: Score" - Column labels are rotated 45 degrees for readability
- Color scale legend shows the value range
Responsive Sizing Pattern¶
All visualizations use the same responsive pattern via d3-utils.ts:
import { responsiveSvg } from './d3-utils'
// In useEffect:
const svg = responsiveSvg(container, width, height)
// Sets: viewBox="0 0 {width} {height}"
// preserveAspectRatio="xMidYMid meet"
// max-width: 100%
// height: auto
To make a visualization fill its parent container, pass the container's
clientWidth and clientHeight as width and height props.
Dark Mode Support¶
Dark mode is toggled via the ThemeToggle component. Visualizations adapt
through:
- CSS custom properties -- Background and text colors are theme-aware.
- D3 color utilities -- Use
lightenColorfromd3-utils.tsfor fill colors that remain visible in both modes. - Tooltip styling -- The
createTooltipfunction uses a dark semi-transparent background (rgba(0, 0, 0, 0.85)) with white text, which works in both light and dark themes.
Accessibility¶
All visualizations follow these accessibility practices:
ARIA Labels¶
Every SVG root element includes role="img" and a descriptive aria-label.
Interactive elements (nodes, cells, arcs) include aria-label attributes
describing their content.
Keyboard Navigation¶
Focusable SVG elements can be navigated with Tab. The tooltip show and
hide functions are triggered on both mouseenter/mouseleave and
focus/blur events.
Color Contrast¶
The FCC color palette is designed for WCAG AA contrast against both light and
dark backgrounds. Use getFccColor rather than hardcoding hex values.
Screen Reader Support¶
- Tooltip content is mirrored in
aria-labelattributes - Group elements use
role="list"androle="listitem"where applicable - Data tables can be provided as a hidden
<table>alternative for complex visualizations
See Also¶
- Web Frontend Guided Demo -- full frontend walkthrough
- Distiller Bridge Demo -- NanoCube data for visualizations
frontend/src/visualizations/d3-utils.ts-- shared utility source