Real-Time Mermaid Diagrams: Rendering Dynamic Data & Live Updates
Static diagrams are snapshots; live diagrams are windows into your system right now. Whether you're visualizing server topology, rendering an active workflow, or showing real-time traffic flow, dynamic Mermaid rendering turns diagrams into living documentation that stays in sync with your data.
Why Real-Time Diagrams Matter
- Operational visibility — engineers see system state without hunting through logs or dashboards
- Live collaboration — teams see the same diagram as data changes, no refresh needed
- Regulatory compliance — audit trails and flow diagrams stay current automatically
- Teaching & demos — show a simulation running as a diagram that evolves frame-by-frame
- Incident response — topology and state diagrams update during outages so responders see what's alive
Architecture: Polling vs. Streaming
Pattern 1: Polling (Simpler, Client-Driven)
Fetch new state at intervals; if it changed, re-render the diagram.
// Pseudo-code: React component with polling
import { useState, useEffect } from 'react';
import mermaid from 'mermaid';
export function LiveWorkflow({ workflowId }) {
const [diagramCode, setDiagramCode] = useState('');
useEffect(() => {
const poll = setInterval(async () => {
const res = await fetch(`/api/workflows/${workflowId}`);
const { status, steps, activeStepId } = await res.json();
// Generate Mermaid code from live data
const code = generateFlowchart(steps, activeStepId);
setDiagramCode(code);
}, 2000); // Poll every 2 seconds
return () => clearInterval(poll);
}, [workflowId]);
useEffect(() => {
if (diagramCode) {
mermaid.contentLoaded();
}
}, [diagramCode]);
return <div className="mermaid">{diagramCode}</div>;
}
function generateFlowchart(steps, activeStepId) {
let code = 'flowchart TD\n';
steps.forEach((step) => {
const style = step.id === activeStepId ? 'fill:#ffeb3b' : '';
const status = step.status === 'done' ? '✓' : '';
code += ` ${step.id}["${status} ${step.name}"]`;
if (style) code += `\n style ${step.id} ${style}`;
code += '\n';
});
// Add edges...
return code;
}
Pros: Simple, no server changes needed, stateless
Cons: Latency (up to 2s lag), wasted polls when data is static, scalability risk at high frequency
Use for: dashboards with 5–30 second tolerance, meeting demos, status boards
Pattern 2: WebSocket Streaming (Real-Time, Server-Driven)
Server pushes changes; client updates diagram immediately.
// React with WebSocket
export function LiveTopology({ clusterId }) {
const [diagramCode, setDiagramCode] = useState('');
useEffect(() => {
const ws = new WebSocket(
`wss://api.example.com/clusters/${clusterId}/stream`
);
ws.onmessage = (event) => {
const delta = JSON.parse(event.data); // { type: 'node_up', node: {...} }
// Merge delta into current state
updateState(delta);
// Re-render diagram
const code = generateTopology(currentState);
setDiagramCode(code);
};
return () => ws.close();
}, [clusterId]);
return <div className="mermaid">{diagramCode}</div>;
}
Pros: <100ms latency, server-driven (authoritative), scales to many clients
Cons: More server complexity, connection management, auth/TLS overhead
Use for: incident response, collaborative editing, production monitoring
Example: Live Workflow Execution
Here's a real workflow with dynamic state:
// Step data from your database
const workflowState = {
steps: [
{ id: 'fetch', name: 'Fetch Data', status: 'done', duration: 245 },
{ id: 'validate', name: 'Validate', status: 'running', duration: 0 },
{ id: 'transform', name: 'Transform', status: 'pending', duration: 0 },
{ id: 'save', name: 'Save', status: 'pending', duration: 0 },
],
activeStepId: 'validate',
errors: [],
};
function generateWorkflowDiagram(state) {
let code = 'flowchart TD\n';
state.steps.forEach((step) => {
const icon =
step.status === 'done' ? '✓' :
step.status === 'running' ? '⏱️' :
'⏳';
const label = `${icon} ${step.name} (${step.duration}ms)`;
code += ` ${step.id}["${label}"]\n`;
// Color by status
const color =
step.status === 'done' ? 'fill:#c8e6c9' :
step.status === 'running' ? 'fill:#fff9c4' :
'fill:#eceff1';
code += ` style ${step.id} ${color}\n`;
});
// Connect steps
state.steps.slice(0, -1).forEach((step, i) => {
code += ` ${step.id} --> ${state.steps[i + 1].id}\n`;
});
return code;
}
Render output:
flowchart TD
fetch["✓ Fetch Data (245ms)"]
validate["⏱️ Validate (0ms)"]
transform["⏳ Transform (0ms)"]
save["⏳ Save (0ms)"]
fetch --> validate --> transform --> save
style fetch fill:#c8e6c9
style validate fill:#fff9c4
style transform fill:#eceff1
style save fill:#eceff1
As the workflow progresses, activeStepId changes and the diagram updates in real-time.
Real-World Patterns
Pattern A: Infrastructure Topology (Monitoring)
// Live Kubernetes cluster state
async function fetchClusterTopology(clusterId) {
const res = await fetch(`/api/clusters/${clusterId}/nodes`);
const nodes = await res.json();
let code = 'flowchart LR\n';
code += ` Master["🎛️ Control Plane"]\n`;
nodes.forEach((node) => {
const status = node.ready ? '🟢' : '🔴';
const label = `${status} ${node.name}<br/>(CPU: ${node.cpu}%, MEM: ${node.mem}%)`;
code += ` ${node.id}["${label}"]\n`;
if (node.ready) {
code += ` style ${node.id} fill:#c8e6c9\n`;
} else {
code += ` style ${node.id} fill:#ffcdd2\n`;
}
code += ` Master --> ${node.id}\n`;
});
return code;
}
Use for: Kubernetes dashboards, server racks, mesh topology
Pattern B: Pipeline Progress (CI/CD)
// GitHub Actions / GitLab CI progress
function generatePipelineDiagram(pipeline) {
let code = 'flowchart TD\n';
const stages = ['build', 'test', 'deploy'];
stages.forEach((stage, i) => {
const jobs = pipeline.jobs.filter((j) => j.stage === stage);
code += ` subgraph ${stage}["${stage.toUpperCase()}"]\n`;
jobs.forEach((job) => {
const status = job.status === 'success' ? '✓' : job.status === 'running' ? '⏳' : '✗';
code += ` ${job.id}["${status} ${job.name}"]\n`;
});
code += ` end\n`;
if (i < stages.length - 1) {
code += ` ${stage} --> ${stages[i + 1]}\n`;
}
});
return code;
}
Pattern C: Collaborative Diagram Editing (Live Sync)
// Shared editing session with WebSocket
export function CollaborativeDiagram({ diagramId }) {
const [code, setCode] = useState('');
const [collaborators, setCollaborators] = useState([]);
useEffect(() => {
const ws = new WebSocket(`wss://api.example.com/diagrams/${diagramId}/collab`);
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'code_update') {
setCode(msg.code); // Someone else edited; update locally
} else if (msg.type === 'user_joined') {
setCollaborators([...collaborators, msg.user]);
}
};
return () => ws.close();
}, [diagramId, collaborators]);
const handleChange = (newCode) => {
setCode(newCode);
ws.send(JSON.stringify({ type: 'code_update', code: newCode }));
};
return (
<>
<div className="mermaid">{code}</div>
<p>Editing: {collaborators.map((u) => u.name).join(', ')}</p>
</>
);
}
Performance Tips
1. Diff Before Re-Render
Don't regenerate and re-render on every poll. Only update if the Mermaid code actually changed:
const [prevCode, setPrevCode] = useState('');
const newCode = generateDiagram(state);
if (newCode !== prevCode) {
setDiagramCode(newCode);
setPrevCode(newCode);
mermaid.contentLoaded();
}
2. Throttle Updates
Poll less frequently for non-critical dashboards:
const throttle = (fn, delay) => {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= delay) {
fn(...args);
last = now;
}
};
};
const handleUpdate = throttle(() => {
// Re-render diagram
}, 1000); // Max once per second
3. Batch Updates
If multiple data sources change, collect them before re-rendering:
const updates = [];
ws.onmessage = (event) => {
updates.push(JSON.parse(event.data));
};
setInterval(() => {
if (updates.length > 0) {
// Apply all updates at once
const newState = applyDeltas(state, updates);
setDiagramCode(generateDiagram(newState));
updates.length = 0; // Clear batch
}
}, 500); // Flush every 500ms
4. Limit Diagram Size
Keep live diagrams under 50 nodes; break complex topologies into views:
- Cluster view → region-level nodes
- Drill-down → click a region to see individual servers
Integration with MermaidCreator
Build and test live diagrams in MermaidCreator's code editor:
- Paste example state as JSON in comments
- Write the generation function
- Test it with a few state snapshots
- Export the code for integration
For collaborative editing on your team, upgrade to a workspace and use MermaidCreator's built-in Realtime presence + comments to see teammates editing live.
FAQ
How often should I poll? Start with 5 seconds; tune down to 2–3 seconds for dashboards, 500ms for incident response, 100ms only for real-time gaming/sim.
Will WebSocket updates work in my setup? Requires HTTPS + WSS (encrypted WebSocket). Most modern hosting (Vercel, Heroku, AWS) supports it. Polling works anywhere.
Can I animate transitions between states? Yes—use CSS transitions on the SVG elements Mermaid generates. Mermaid doesn't animate natively, but the SVG DOM does.
How do I handle errors or missed updates?
For polling: on fetch error, show a stale-data banner and retry exponentially.
For WebSocket: on disconnect, fall back to polling until reconnected.
Real-time diagrams are the next frontier of ops tooling. Start with polling on a dashboard, then graduate to WebSocket when latency matters. Try a live workflow or cluster topology on MermaidCreator today.
Related posts
Mermaid Notes & Annotations: Document Diagram Intent & Context
Add clarity to diagrams with notes, comments, and annotations—best practices for labeling decisions, explaining asynchronous flows, and preventing ambiguity.
Mermaid Subgraph Composition: Modular Complex Diagrams
Master nested subgraphs to organize large flowcharts and systems into reusable, readable modules—patterns for composition, hierarchy, and clarity.