Raw data is boring. A spreadsheet of ONS broadband penetration figures or Ofcom spectrum usage statistics is about as gripping as a council planning notice. But bind those same numbers to a living, breathing SVG illustration and suddenly you have something people actually want to look at. That is the promise of reactive SVG data visualisation, and D3.js is the library that makes it properly possible without reaching for a bloated charting framework that hides all the interesting bits from you.
This article is a hands-on walkthrough. We are going to fetch real UK open data, parse it, and use D3 to bind it to SVG elements so the visuals respond dynamically to the figures. No toy datasets. No made-up numbers. Real data from sources the UK government and regulators actually publish.

Why SVG and D3 Instead of Canvas or a Chart Library?
Canvas is fast for pixel-heavy rendering. Chart libraries like Chart.js are quick to deploy. But neither gives you the fine-grained control that reactive SVG data visualisation demands when you want illustrations, not just bar charts. SVG is part of the DOM, which means every shape, path, and text node is queryable, styleable, and animatable with CSS. D3 exploits this completely.
The data join pattern at D3’s core (enter, update, exit) is genuinely elegant once it clicks. You tell D3 what data you have, what DOM elements should represent it, and what to do when data changes. The library handles the rest. It is declarative in the right places and imperative where you need control. I have used it on projects ranging from tiny inline sparklines to full-screen choropleth maps, and I keep coming back because nothing else gives you the same ceiling.
Getting Your UK Open Data: ONS and Ofcom
The Office for National Statistics publishes machine-readable datasets through its ONS API, which returns JSON in a fairly navigable structure. Ofcom releases its Connected Nations datasets as downloadable CSVs covering broadband coverage by local authority district. Both are genuinely free, regularly updated, and ideal for a reactive SVG project.
For this walkthrough we are using Ofcom’s Connected Nations data: specifically, the percentage of premises with access to gigabit-capable broadband by UK nation and region. It is a flat CSV, which means we can use D3’s built-in d3.csv() loader and avoid writing a custom parser.
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const DATA_URL = "https://your-hosted-copy/ofcom-connected-nations.csv";
d3.csv(DATA_URL, d => ({
region: d["Region"],
gigabit_pct: +d["Gigabit capable (%)"],
premises: +d["Total premises"]
})).then(data => {
render(data);
});
Note the + coercion on the numeric fields. D3’s CSV parser returns strings by default. Skip that coercion and your scales will treat “84” as text, which produces spectacularly wrong output. Ask me how I know.
Building the SVG Container and Scales
The margin convention in D3 is worth following even when it feels ceremonial. It keeps your axes and labels inside the viewport without wrestling with overflow: visible hacks later.
const margin = { top: 40, right: 30, bottom: 60, left: 120 };
const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const svg = d3.select("#chart")
.append("svg")
.attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
Using viewBox instead of hard-coded pixel dimensions is non-negotiable if you want the illustration to be responsive. The SVG scales itself to the container, and your D3 coordinates stay consistent regardless of screen size. This is how reactive SVG data visualisation actually becomes reactive to viewport changes as well as data changes.

Now the scales. For a horizontal bar chart of regional broadband coverage:
const x = d3.scaleLinear()
.domain([0, 100])
.range([0, width]);
const y = d3.scaleBand()
.domain(data.map(d => d.region))
.range([0, height])
.padding(0.3);
scaleBand handles the spacing maths for categorical axes so you do not have to. The padding value of 0.3 adds 30% whitespace between bands, which visually separates the bars without making them hair-thin.
The D3 Data Join: Where the Reactive Magic Happens
This is the part that confuses people initially and then becomes the thing they explain excitedly to colleagues. The data join binds an array of data objects to a selection of DOM elements. Elements that do not yet exist go into the enter selection. Elements whose data has disappeared go into the exit selection. Everything in between is the update selection.
const bars = svg.selectAll("rect.bar")
.data(data, d => d.region); // key function keeps transitions smooth
// Enter: create new bars
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("y", d => y(d.region))
.attr("height", y.bandwidth())
.attr("x", 0)
.attr("width", 0) // start at zero for transition
.attr("fill", "#4F46E5")
.merge(bars) // merge with update selection
.transition()
.duration(800)
.ease(d3.easeCubicOut)
.attr("width", d => x(d.gigabit_pct));
// Exit: remove stale bars
bars.exit()
.transition()
.duration(400)
.attr("width", 0)
.remove();
The key function in .data(data, d => d.region) is critical for smooth transitions. Without it, D3 matches data to elements by index. With it, elements are bound by region name, so if you filter or reorder the dataset the bars animate to their new positions rather than snapping jarringly.
Making It Genuinely Reactive: Filtering and UI Controls
The real payoff of reactive SVG data visualisation comes when you wire up UI controls that change the underlying data and re-call the render function. Add a simple dropdown that filters by UK nation:
const nations = ["All", "England", "Scotland", "Wales", "Northern Ireland"];
const select = d3.select("#controls")
.append("select")
.on("change", function() {
const selected = this.value;
const filtered = selected === "All"
? fullData
: fullData.filter(d => d.nation === selected);
render(filtered);
});
select.selectAll("option")
.data(nations)
.enter()
.append("option")
.text(d => d);
Each time the user changes the dropdown, render(filtered) runs with a new data array. D3 works out which bars need to be added, which need updating, and which need removing. You do not manually manipulate the DOM. That is the whole point.
Going Beyond Bar Charts: Binding Data to Illustrated SVG Paths
Bar charts are fine, but the really interesting territory is binding data to custom SVG illustrations, like a stylised map of the UK’s twelve regions, or an illustrated diagram of network infrastructure where path stroke-width encodes bandwidth figures.
The principle is identical. Load an SVG file (exported from Figma or Illustrator), inline it in your HTML, then use D3 to select specific paths by their id or data-region attributes and drive their visual properties from data. Colour fills from a sequential scale, opacity driven by coverage percentage, stroke animations that pulse for regions below a threshold: all of it is just attribute binding.
const colour = d3.scaleSequential()
.domain([0, 100])
.interpolator(d3.interpolateBlues);
data.forEach(d => {
d3.select(`#region-${d.region.replace(/\s+/g, "-").toLowerCase()}`)
.transition()
.duration(600)
.attr("fill", colour(d.gigabit_pct));
});
This approach turns a static SVG illustration into a living data artefact. Your designer exports a clean regional map from Figma. Your D3 code breathes data into it. The two disciplines talk to each other through attribute naming conventions agreed upfront. It is the kind of collaboration between design and dev that produces genuinely impressive output.
Performance and Accessibility Notes
A few things worth pinning to your monitor. First, ARIA labels. SVG is not inherently accessible. Add role="img" and aria-label to your SVG container, and use <title> and <desc> elements inside individual groups for screen readers. The Web Accessibility Initiative has clear guidance on SVG accessibility patterns.
Second, transitions look great but they are not free. If you are rendering several hundred elements, consider using d3.transition() with a shared timer rather than individual transitions, and cap your dataset size where the visual encoding stops being legible anyway. A choropleth with 400 regions is just noise.
Third, host your open data files yourself or proxy them. Fetching directly from government data portals in production is fragile; file structures change, URLs break, and there is no SLA. Pull the data into your own infrastructure on a schedule and serve it from there.
The Bottom Line on D3 and Open Data
Reactive SVG data visualisation using UK open datasets is one of those projects that teaches you an enormous amount in a short time. You learn D3’s join model. You learn how SVG coordinate systems actually work. You learn that ONS data is more useful than most developers realise. And you produce something genuinely worth showing in a portfolio, because it combines real public information with craft-level visual thinking. That combination is rare, and it shows.
The Ofcom and ONS datasets are updated regularly, which means a project you build this month stays relevant as new figures land. Wire it up to a scheduled data fetch, add a timestamp to your visualisation, and you have something that essentially maintains itself.
Frequently Asked Questions
What is reactive SVG data visualisation and how does it differ from a static chart?
Reactive SVG data visualisation means your SVG elements update dynamically when the underlying data changes, using a library like D3.js to bind data to DOM attributes in real time. A static chart is a fixed image or pre-rendered output; a reactive one responds to user input, data filters, or live data feeds without a page reload.
Where can I download free UK open datasets to use with D3.js?
The Office for National Statistics (ons.gov.uk) and Ofcom both publish machine-readable open datasets covering topics from broadband coverage to regional demographics. The ONS also provides a developer API that returns JSON, and Ofcom’s Connected Nations data is available as downloadable CSV files updated annually.
Do I need to know D3.js well to build reactive SVG illustrations, or can beginners start here?
D3’s data join pattern has a learning curve, so some JavaScript confidence is recommended before diving in. That said, the enter/update/exit model is well-documented and once it clicks, building reactive SVG visualisations becomes much more intuitive. Starting with a simple bar chart bound to a CSV is the fastest path to understanding it properly.
Can I import an SVG illustration from Figma and bind data to it using D3?
Yes, and this is one of the most powerful workflows available. Export your SVG from Figma, inline it in your HTML, and give key paths meaningful IDs or data attributes. D3 can then select those paths and drive their fill, opacity, stroke, or transform properties directly from your dataset.
How do I make D3 SVG visualisations accessible for screen readers?
Add role=”img” and an aria-label to the SVG container, and include a

Leave a Reply