This guide explains the main parts of the workshop visualisation.
It is designed to sit alongside the live demonstration. You do not need to understand every line of code immediately. The aim is to help you recognise what each part of the file is doing and where you can make small changes during the workshop.
The visualisation uses:
- HTML to structure the page;
- CSS to style the page;
- JavaScript to load data and build the visualisation;
- Leaflet to create an interactive map;
- D3.js to create a timeline chart.
- What you will build
- Before you start
- Project files
- The main file: index.html
- Part 1: HTML page structure
- Part 2: Loading external libraries
- Part 3: Page layout and styling
- Part 4: Creating the Leaflet map
- Part 5: Adding map tiles
- Part 6: Loading the data
- Part 7: Adding markers to the map
- Part 8: Preparing the timeline data
- Part 9: Creating the SVG chart area
- Part 10: Creating scales and axes
- Part 11: Drawing the line chart
- Part 12: Adding points to the chart
- Part 13: Interactivity
- Debugging tips
- Key terms
- Next step
In this workshop, you will work with a web-based visualisation that combines:
- a map showing places from the dataset;
- a timeline showing records by entry year;
- data loaded from a JSON file;
- basic interaction through map markers and popups.
The current starter file creates two main visual areas:
Map area D3 visualisation area
--------- ----------------------
Leaflet map SVG timeline chart
The map and chart are shown side by side in the browser.
Before working through this page, make sure you have completed:
You should have:
- your own fork or copy of the workshop repository;
- the project open in VS Code;
- Live Preview or Live Server installed;
index.htmlopening successfully in your browser.
Important: Open the project using Live Preview or Live Server.
Do not openindex.htmlby double-clicking it, because the project needs to load data from thedata/folder.
The project currently contains files similar to this:
uoe_visualization_workshop/
index.html
data/
women_doctors_geo.json
The exact structure may change as the workshop materials develop.
The most important file for this part of the workshop is:
index.html
This file contains:
- the page structure;
- links to D3.js and Leaflet;
- the map container;
- the chart container;
- the JavaScript code that loads the data and builds the visualisation.
Open this file in VS Code:
index.html
This is the main webpage. It contains three broad parts:
| Part | What it does |
|---|---|
<head> |
Loads metadata, styles, and external libraries |
<body> |
Contains the visible page structure |
<script> |
Contains the JavaScript that builds the map and chart |
HTML gives the page its structure.
The starter file begins with a standard HTML document structure:
<!doctype html>
<html lang="en">
<head>
...
</head>
<body>
...
</body>
</html>The two most important parts are:
| HTML section | Purpose |
|---|---|
<head> |
Information for the browser, such as page title, CSS, and script links |
<body> |
Content that appears on the page |
In this workshop, the visible page content is inside the <body>.
The page has:
<header>
<h1>Digital Humanities Visualization</h1>
<p>D3.js + Leaflet starter project</p>
</header>This creates the title area at the top of the page.
It also has:
<main>
<div id="map"></div>
<div id="viz">
<h2>D3 Visualization</h2>
<svg id="chart"></svg>
</div>
</main>This creates the two main visualisation areas:
| Element | Purpose |
|---|---|
<div id="map"></div> |
The container where Leaflet will place the map |
<svg id="chart"></svg> |
The drawing area where D3 will place the chart |
The visualisation uses two JavaScript libraries:
- Leaflet for the map;
- D3.js for loading and visualising data.
These are loaded in the <head> of the page.
<!-- Leaflet CSS -->
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
/>
<!-- D3 -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>The Leaflet CSS controls how the map looks.
The D3 script makes D3 functions available, such as:
d3.select()
d3.json()
d3.rollups()
d3.scaleLinear()
d3.axisBottom()
d3.line()The Leaflet script makes Leaflet functions available, such as:
L.map()
L.tileLayer()
L.marker()The visual layout is controlled by CSS inside the <style> block.
For example:
main {
display: grid;
grid-template-columns: 1fr 1fr;
height: calc(100vh - 70px);
}This creates a two-column layout:
- the map appears on the left;
- the D3 visualisation appears on the right.
The map area is styled with:
#map {
height: 100%;
width: 100%;
}Leaflet maps need a visible height. If the map container has no height, the map may not appear.
The SVG chart is styled with:
svg {
width: 100%;
height: 600px;
border: 1px solid #ddd;
}This makes the chart area visible and gives it a border.
The map is created with this line:
const map = L.map("map").setView([55.9533, -3.1883], 5);This does three things:
| Code | Meaning |
|---|---|
L.map("map") |
Creates a Leaflet map inside the element with id="map" |
[55.9533, -3.1883] |
Sets the starting centre point of the map |
5 |
Sets the starting zoom level |
The coordinates used here point to Edinburgh.
You can try changing the zoom level:
const map = L.map("map").setView([55.9533, -3.1883], 6);A larger number zooms in.
A smaller number zooms out.
The map needs background tiles. These are the map images that show land, roads, labels, and other base map details.
The starter file uses OpenStreetMap tiles:
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors",
}).addTo(map);This adds the tile layer to the map.
The attribution line credits the tile provider.
The data is loaded with D3:
d3.json("data/women_doctors_geo.json")
.then((data) => {
console.log(data);
// Code using the data goes here
})
.catch((error) => {
console.error("Error loading JSON:", error);
});This means:
| Code | Meaning |
|---|---|
d3.json(...) |
Load a JSON data file |
.then((data) => { ... }) |
Run this code after the data loads successfully |
console.log(data) |
Print the data in the browser console |
.catch((error) => { ... }) |
Show an error if the data does not load |
The data path is:
data/women_doctors_geo.json
This means the file should be inside the data/ folder.
After the data loads, the code loops through each record:
data.forEach((element) => {
if (
element.source_data?.birthplace?.lat &&
element.source_data?.birthplace?.lon
) {
L.marker([
element.source_data.birthplace.lat,
element.source_data.birthplace.lon,
])
.addTo(map)
.bindPopup("University of Edinburgh");
}
});This checks whether each record has:
- a birthplace latitude;
- a birthplace longitude.
If both exist, a marker is added to the map.
The popup currently says:
.bindPopup("University of Edinburgh");This is a basic placeholder.
During the workshop, this could be changed to show more useful information from the data.
For example:
.bindPopup("Birthplace record");or, if the relevant fields are available:
.bindPopup(`
<strong>Entry year:</strong> ${element.source_data?.entry_year ?? "Unknown"}
`);The timeline uses the entry_year field.
First, the code extracts the years:
const years = data
.map((d) => Number(d.source_data?.entry_year))
.filter((year) => Number.isFinite(year) && year > 0);This does three things:
| Code | Meaning |
|---|---|
.map(...) |
Go through each record and extract the entry year |
Number(...) |
Convert the year into a number |
.filter(...) |
Keep only valid years |
Then the code groups the years and counts how many records appear in each year:
const timelineData = d3
.rollups(
years,
(values) => values.length,
(year) => year,
)
.map(([year, count]) => ({ year, count }))
.sort((a, b) => a.year - b.year);The result is a new dataset that looks conceptually like this:
year count
---- -----
1870 2
1871 5
1872 4
This new grouped data is used to draw the timeline.
The D3 chart is drawn inside this SVG element:
<svg id="chart"></svg>The JavaScript selects that SVG using D3:
const svg = d3.select("#chart");The chart dimensions are set with:
const width = 900;
const height = 600;
const margin = { top: 30, right: 30, bottom: 60, left: 60 };The margins create space around the chart for labels and axes.
The SVG is made responsive with:
svg.attr("viewBox", `0 0 ${width} ${height}`);The viewBox helps the SVG scale to the available space.
D3 scales map data values to screen positions.
For the x-axis, the data values are years.
const x = d3
.scaleLinear()
.domain(d3.extent(timelineData, (d) => d.year))
.range([margin.left, width - margin.right]);This says:
| Part | Meaning |
|---|---|
scaleLinear() |
Create a linear scale |
domain(...) |
The data range, from earliest year to latest year |
range(...) |
The screen range, from left side to right side |
For the y-axis, the data values are counts.
const y = d3
.scaleLinear()
.domain([0, d3.max(timelineData, (d) => d.count)])
.range([height - margin.bottom, margin.top]);This maps record counts to vertical screen positions.
The x-axis is drawn with:
svg
.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickFormat(d3.format("d")));The y-axis is drawn with:
svg
.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y));The axis labels are added with SVG text:
svg
.append("text")
.attr("x", width / 2)
.attr("y", height - 15)
.attr("text-anchor", "middle")
.text("Entry Year");svg
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", 20)
.attr("text-anchor", "middle")
.text("Number of Records");The line generator is created with:
const line = d3
.line()
.x((d) => x(d.year))
.y((d) => y(d.count));This tells D3 how to convert each data point into a position on the chart.
The line is drawn as an SVG path:
svg
.append("path")
.datum(timelineData)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 2)
.attr("d", line);Important parts:
| Code | Meaning |
|---|---|
.append("path") |
Add a flexible SVG shape |
.datum(timelineData) |
Attach the timeline data |
fill: none |
Do not fill the inside of the path |
stroke: steelblue |
Set the line colour |
stroke-width: 2 |
Set the line thickness |
.attr("d", line) |
Use the D3 line generator to draw the path |
The starter file also adds circles to show each year/count point:
svg
.selectAll(".point")
.data(timelineData)
.join("circle")
.attr("class", "point")
.attr("cx", (d) => x(d.year))
.attr("cy", (d) => y(d.count))
.attr("r", 3)
.attr("fill", "steelblue");This creates one circle for each item in timelineData.
| Code | Meaning |
|---|---|
.selectAll(".point") |
Select all existing points |
.data(timelineData) |
Connect the data to the points |
.join("circle") |
Create circles for the data |
cx |
x-position of each circle |
cy |
y-position of each circle |
r |
radius of each circle |
fill |
circle colour |
The starter project currently includes simple map interactivity through popups:
.bindPopup("University of Edinburgh");When someone clicks a marker, the popup appears.
There is also a note in the code:
// add slider for the timelineThis means the timeline slider is not currently implemented
Possible interactive features include:
- clicking a map marker to show information;
- hovering over a chart point to see the year and count;
- changing marker size or colour;
- filtering records by year;
- linking the map and timeline.
The starter file loads D3 and Leaflet from online links.
If you are offline, these links may not load:
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>| Term | Meaning |
|---|---|
| HTML | The structure of the webpage |
| CSS | The styling of the webpage |
| JavaScript | The programming language used to load data and create interaction |
| D3.js | A JavaScript library for data-driven visualisation |
| Leaflet | A JavaScript library for interactive maps |
| JSON | A structured data format |
| GeoJSON | A data format for geographic information |
| SVG | A drawing area for shapes, lines, circles, and text |
| Scale | A function that maps data values to screen positions |
| Axis | A visual guide showing values on a chart |
| Marker | A point placed on a map |
| Popup | A small information box that appears when a marker is clicked |
| Console | A browser tool for checking messages and errors |
After building and editing the visualisation, continue to:
