← All posts

Data Visualization

Designing dashboards with D3 and practical UI

May 22, 202612 min read
D3.jsDashboardsUX
Designing dashboards with D3 and practical UI

D3 gives you total control, which is exactly why dashboards built with it so often become unreadable. The library will happily draw anything you ask for — radial stacked area charts, animated chord diagrams, whatever. The discipline has to come from the design, not the tool.

I spent a year building React and D3 dashboards for a security product, where the audience wasn't data scientists — it was operators making decisions under time pressure. That constraint taught me more about visualisation than any gallery of beautiful charts ever did. What follows is the working method that came out of it.

Every chart answers exactly one question

Before choosing a chart type I write down the single question the user is trying to answer: 'Is this trending the wrong way?' 'Which category is the outlier?' 'Are we above or below target?' If a visualisation can't be tied to a concrete decision someone makes, it's decoration — and decoration is the first thing to cut from an operational screen.

This sounds obvious and is violated constantly. The most common dashboard failure I see isn't bad charts; it's charts that exist because the data was available, not because anyone asked a question. Ten aimless charts are worth less than three pointed ones, because the aimless ones cost attention the pointed ones needed.

Context beats precision

A number is meaningless without a baseline. 47 alerts today — is that good? Compared to what? I lean on comparisons everywhere: versus last period, versus target, versus the rest of the cohort. The reader should know instantly whether a value is good or bad without doing arithmetic in their head.

In practice this means every big number gets a companion: a delta, a sparkline, a target line, or a coloured badge. And it means resisting false precision — '47.283%' communicates less than '47%', because the extra digits cost reading time and imply an accuracy the data usually doesn't have.

Keep the ink low and the signal high

Axes, gridlines, and tooltips exist to serve context, not to prove the chart is sophisticated. My defaults: muted gridlines at maybe 10% opacity, no chart borders, generous whitespace, direct labels instead of legends where possible, and colour reserved for the one series that matters. Everything else earns its ink or gets removed.

With D3 specifically, the temptation is transitions. A little motion when data updates helps the eye track change; five-hundred-millisecond eased entrances on every render just make the dashboard feel slow. I animate state changes, never initial paints, and I respect prefers-reduced-motion without exception.

D3 draws, React owns the DOM

The classic D3-in-React fight is over DOM ownership. My resolution: D3 does the math — scales, shapes, layouts, axes generators — and React does the rendering. Scales and path generators are pure functions, which makes them easy to test and memoise; the JSX maps their output to SVG elements the same way it maps any other data to markup.

The exception is genuinely heavy interaction — brushing, zooming, force simulations — where I'll give D3 a ref-fenced subtree and let it own that island completely. The rule is that ownership is never shared: any given node is React's or D3's, never both. Every hybrid I've debugged that broke mysteriously was breaking exactly on that line.

Design against the ugliest data you can find

Dashboards break in production because they were designed against tidy sample data. Real data has zeros, nulls, a single data point, ten thousand data points, negative values where you assumed positive, and category names forty characters long. I keep a set of hostile fixtures and run every chart against them before it ships.

The single-point case deserves special mention: a line chart with one point renders as nothing, an area chart as a sliver, and both look broken. Every chart needs an explicit answer for 'not enough data yet' — a designed empty state, not an accidental blank rectangle. Same for the loading state: skeleton shapes that match the chart's silhouette beat a spinner, because they don't shift the layout when data lands.

The dashboard is a product, not a report

A report gets read once; a dashboard gets lived in. That changes the design brief: consistency across screens matters more than any single screen's cleverness, familiar chart types beat novel ones, and performance is a feature — an operator who waits four seconds for a filter to apply stops using filters.

The dashboards that survived longest in production were the boring-looking ones: bars, lines, big numbers with deltas, one accent colour, instant interactions. Nobody ever complimented them in a demo. Everybody used them every day. In operational software, that's what winning looks like.

Thanks for reading

Keep exploring or start a conversation.