From Mermaid to WebGPU: Four Diagram Mistakes in a Year of Blogging
I’ve been blogging for over a year now, and I’ve drawn dozens of diagrams. Architecture diagrams, sequence diagrams, data comparison bar charts, TCP congestion window evolution curves.
Then one day on my commute home, it hit me: I’ve been using Mermaid almost daily, but I have no idea what technology powers it.
You write a ```mermaid code block, save the file, and there it is—a fully rendered diagram. But how does text become SVG? What even is SVG? Why do I use Chart.js for bar charts and ECharts for stacked line charts? What if I need a real-time dashboard, not just a static diagram?
I couldn’t answer any of these. So I spent a few weeks tracing the frontend graphics stack from Mermaid’s dagre layout engine down to WebGL shaders and WebGPU compute pipelines.
This isn’t a conclusion—it’s a wreck journal. Four mistakes, each one taught me a layer of understanding I didn’t have before.
Mistake #1: Two Days Wasted Fighting Mermaid’s Auto-Layout
I was writing a piece on DeepFlow’s eBPF architecture. The diagram was moderately complex: data flows from the Agent, splits into Flow and Packet paths, reaches the Server for correlation analysis, then lands in storage and display. About a dozen modules, bidirectional arrows, grouping.
Mermaid had it written in two minutes:
flowchart TD
A["Agent collects"] --> B{"Protocol type"}
B -->|"Flow"| C["Flow Processing"]
B -->|"Packet"| D["Packet Processing"]
C --> E["Server Correlation"]
D --> E
E --> F["ClickHouse"]
E --> G["Prometheus"]The diagram was functional, but the layout was awkward—the Flow and Packet paths should have been parallel, but Mermaid auto-laid them out one above the other with crossing lines.
Then I did something incredibly stupid: I spent two days trying to fix the layout through Mermaid configuration flags.
Invisible edges with ~~~ to reorder. Swapping between graph and flowchart hoping for a different layout algorithm. Even prepending number prefixes to node names to force ordering. The result? There was always another crossing.
Then I found a comment in a D2 discussion thread: “You’re trying to do manual layout with an auto-layout tool. That’s not a tool problem.”
I had missed a fundamental distinction:
- Mermaid is “text → auto-layout → SVG.” You give up layout control for speed.
- Draw.io / Excalidraw is “manual → SVG.” Pixel-perfect but entirely manual.
- D2 / Structurizr is “text → rule-based layout → SVG.” You specify layout constraints (hierarchical, left-to-right), tighter control than Mermaid but still faster than drawing.
What I should have done was either switch to D2 for rule-based control, or just manually draw it in Draw.io. Instead, I picked the worst path—trying to do manual-layout work with an auto-layout tool.
The rule I use now:
| Your need | Tool | Control | Speed |
|---|---|---|---|
| Quick sketch, share fast | Excalidraw | Full manual | Fast |
| Regular blog diagram | Mermaid | Auto (no tuning) | Instant |
| Complex with structure rules | D2 / Structurizr | Rule-based | Medium |
| Pixel-perfect | Draw.io / Figma | Full manual | Slow |
Mermaid is still your default for blog diagrams—```mermaid in Markdown is uniquely convenient. But when you find yourself thinking “the layout doesn’t look right,” ask: am I refusing to accept auto-layout randomness, or has this diagram genuinely exceeded Mermaid’s capabilities?
The former you need to accept. The latter means switch tools.
Mistake #2: Chart.js “Lightweight” Trap Almost Cost Me
Writing a comparison of Chinese LLM pricing, I needed a bar chart: model names on the X axis (GLM-5, Kimi K2.5, MiniMax M2.7), API unit prices on the Y. Six data points, tooltips on hover.
Chart.js was the natural choice. But after I finished, I wasn’t happy—default styling felt plain, color scheme looked amateur, tooltip cursor behavior was off.
So I started wondering: “Should I switch to ECharts? ECharts charts look nicer, the animations are richer.”
I was one CDN script tag away from swapping Chart.js (68KB gzip) for ECharts (360KB gzip)—a 5× size increase. For six bars and a tooltip style issue.
What I actually did was write a Chart.js plugin—about a dozen lines registering a custom plugin that changed the tooltip background color, border radius, and label format. Same amount of code as an ECharts option config, but 68KB stayed 68KB.
This looks like an “over-engineering” story on the surface. But it revealed a deeper problem: how do you actually judge “enough”?
My old logic was feature-checklist: “ECharts has 50 chart types, Chart.js has 8. ECharts is stronger.” But this ignores two critical variables:
- The features you actually need. Not “what ECharts can do,” but “can Chart.js cover what I need this time?” For a single bar chart, the answer is 100% yes.
- The cost difference between “almost enough” and “way more.” Chart.js is almost enough? Add a plugin, 2KB extra. Switch to ECharts? 292KB extra.
That cost is uncompromising for a single chart. But it gets amortized in data-intensive scenarios (dashboards, real-time monitoring)—ECharts’ data sampling, progressive rendering, and 50+ chart types suddenly become worth it.
So “enough” isn’t static. It depends on your frequency and scale. Making one chart? Chart.js. Making a hundred charts? ECharts. The dividing line is clearer than most people think.
The real mistake isn’t “I almost picked ECharts”—it’s solving a one-chart problem with a hundred-chart mindset.
Mistake #3: Defaulting to Canvas for Animation
Writing the TCP congestion control series, I wanted to show how Reno, CUBIC, and BBR congestion windows evolve over time. Conceptually: three curves progressively drawing from left to right, like an ECG monitor.
First instinct: “Animation? Canvas, obviously.”
I spent two days building a Canvas version. Clear the canvas each frame, draw three curves, drive with requestAnimationFrame. It worked, but:
- Resizing the window caused a flash (didn’t handle
devicePixelRatiochanges properly) - I was literally just making three lines gradually longer—not something that needs full-frame redraws
Then I thought: Can SVG + CSS do this?
Yes. With less code:
| |
SVG’s stroke-dasharray + stroke-dashoffset combined with CSS animation is a native “progressively draw a line” effect. And CSS animations run on the compositor thread, independent of the JS main thread.
This led to the most counterintuitive thing I learned all year:
Everyone assumes Canvas is always faster than SVG. But SVG is faster for single-element partial updates.
A simple benchmark (20,000 nodes, changing just one):
- SVG: redraws only that one DOM node—12ms
- Canvas: clears all 20,000 elements and redraws everything—87ms
That’s roughly 7× difference. Because Canvas is a bitmap—it has no “element” concept. What’s drawn is pixels. To update anything, you clear the entire canvas and redraw everything.
The rule I use now:
Element count doesn’t decide SVG vs Canvas. Update pattern does.
| Update pattern | Best fit |
|---|---|
| Occasional single-element changes (hover highlight, click) | SVG (DOM tree, element-level operations) |
| Full-frame redraw every frame (games, video streaming) | Canvas (bitmap, no DOM overhead) |
| Medium-frequency refresh (hundreds of points/sec) | Canvas with dirty rectangle optimization |
| Massive independent elements (100K+ scatter points) | WebGL |
My TCP congestion curves were “occasional single-element changes”—three lines gradually changing length. Canvas was the wrong tool, even though “animation” sounded like a Canvas job.
Mistake #4: A Week Learning Three.js for a Blog That Didn’t Need It
This wasn’t a diagram mistake. It was a learning-direction mistake.
I saw some data visualization dashboards with 3D globes, flying arc lines, rotating bar towers. My first thought: “Can I do this on my blog?”
So I started learning Three.js. Scenes, cameras, renderers, geometries, materials, lights… After a week, I had a spinning 3D scatter plot. It looked genuinely cool.
Then I asked myself: Does a reader of my blog need to spin a 3D data visualization in their browser?
The answer was no. They’re here for TCP internals, eBPF data collection, LLM cost comparisons. A spinning 3D chart doesn’t serve any of that. It might even confuse them—“why is this page loading slow, and what’s that spinning globe for?”
The wrong analogy is perfect: Three.js for a blogger is like buying a CNC milling machine to cut a birthday cake. It will cut the cake, beautifully—but a knife does it faster and quieter.
I looked into WebGPU too. By 2026, browser support is decent (Chrome 113+, Firefox 141+, Safari 26+). It goes beyond WebGL with native Compute Shaders, enabling AI inference in the browser.
Genuinely cool. My blog still doesn’t need it.
This taught me something important: knowing what not to learn is as valuable as knowing what to learn. The boundary of your tool selection isn’t “how cool is this technology”—it’s “what level does my problem live at?” 90% of a blogger’s problems live at the Mermaid + Chart.js level. Less than 1% ever reaches WebGL.
My rule now: learn down to the Three.js/WebGL level not to use it, but to know which layer I’m standing on—so if I ever genuinely need it, I know where to look.
Not Mistakes, But Worth Knowing
The four mistakes above are the wrecks in chronological order. After mapping the whole stack, a few cognitive findings emerged that don’t fit as “mistakes” but are useful for tool thinking.
The D3.js Misconception
People often say “Chart.js isn’t powerful enough, so I should learn D3.” This is the deepest misunderstanding about D3. D3 isn’t “stronger Chart.js”—it’s a completely different category. Chart.js and ECharts are chart factories (feed data, get chart). D3 is a parts toolbox—you assemble the chart yourself, not order it from a factory.
You don’t pick D3 because “Chart.js doesn’t have feature X.” You pick D3 because what you’re making can’t be expressed by any existing chart type. If you’re thinking “I just need a bar chart,” D3 is 10× the work for no benefit.
SVG Does More Than You Think
Most people think SVG is for “drawing vector icons.” But the SVG standard is far richer—filters (feGaussianBlur for blur), gradients (linearGradient / radialGradient), masks, clip paths, and native animation (<animate> / <animateTransform>). The vast majority of “blog-level” graphic effects can be done in SVG without any third-party library.
CSS Animations Cover More Than You’d Expect
95% of blog animation needs are covered by CSS. transition for state changes, @keyframes for continuous animation, CSS Scroll-Driven Animations (Chrome 115+) for scroll-triggered effects—no JavaScript required. If you’re within this envelope, introducing GSAP or any JS animation library is overhead you don’t need.
What I Actually Use Now
Not a checklist. Usage scenarios:
- Post diagrams → Mermaid. It’s in Markdown, version-controlled, sufficient.
- Data charts → Chart.js. Only consider ECharts when I genuinely have “multiple chart types overlapping” or “10K+ data points.”
- Animation → CSS animation / transition first. Only introduce GSAP for multi-element precise timeline orchestration.
- Complex compositions → Draw.io. Two or three times a year, but each time it’s “Mermaid can’t do this.”
- Three.js / WebGL / WebGPU → Don’t use. Don’t delete the bookmarks. Know where they live, in case.
Does this count as “selection advice”? It doesn’t look like one. It feels more like honesty about what you actually need. The biggest thing I learned this year isn’t that Mermaid outputs SVG, or where Chart.js breaks down—it’s learning to ask the question:
“Does this problem really need this tool?”
Most of the time, the answer is no.