An updated version of Visualizing London Tube map for display on the Data Science Institute's Data Observatory. Hover over areas to reveal the number of stations within each boundary
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson@3"></script>
<style>
body { margin:0; position:fixed; top:0; right:0; bottom:0; left:0; }
.outline-group path {
fill: none;
stroke: black;
opacity: 0.2;
}
.routes line {
stroke-width: 2px;
}
circle.interchange {
fill: white;
stroke: black;
}
circle.station {
fill: white;
stroke: grey;
opacity: 0.5;
}
.constituency {
stroke: black;
fill: white;
opacity: 0.05;
}
text {
font-family: sans-serif;
font-size: 10px;
}
</style>
</head>
<body>
<script>
var margin = {top: 30, right: 20, bottom: 30, left: 20};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var stationsById = {};
var routesById = {};
d3.queue()
.defer(d3.json, "london_topo_wpc.json")
.defer(d3.csv, "stations.csv")
.defer(d3.csv, "lines2.csv")
.defer(d3.csv, "routes.csv")
.await(ready);
function ready(error, london, stations, connections, routes) {
if (error) throw error;
var merged = topojson.merge(london, london.objects.wpc.geometries);
// create an object index by station id
stations.forEach(function(s) {
stationsById[s.id] = s;
s.conns = [];
s.display_name = (s.display_name == 'NULL') ? null : s.display_name;
s.rail = parseInt(s.rail,10);
s.totalLines = parseInt(s.total_lines, 10);
s.latitude = parseFloat(s.latitude);
s.longitude = parseFloat(s.longitude);
});
// link lines
connections.forEach(function(c) {
c.station1 = stationsById[c.station1];
c.station2 = stationsById[c.station2];
c.station1.conns.push(c);
c.station2.conns.push(c);
c.time = parseInt(c.time, 10);
});
routes.forEach(function(r) {
routesById[r.line] = r;
});
var projection = d3.geoAlbers()
.rotate([0, 0])
.fitSize([width, height], merged);
var path = d3.geoPath()
.projection(projection);
var outline = svg.append("g")
.attr("class", "outline-group")
.datum(merged)
.append("path")
.attr("d", path);
var constituencies = svg.append("g")
.attr("class", "constituencies")
.selectAll("path")
.data(topojson.feature(london, london.objects.wpc).features)
.enter().append("path")
.attr("class", "constituency")
.attr("d", path)
.on("mouseover", function(d) {
// fix this so it works for constituency's with multiple geographical regions
var region = d.geometry.coordinates[0];
var regionCentroid = projection(d3.polygonCentroid(region));
d3.select(this).style("opacity", 0.2);
var counter = 0;
d3.selectAll(".station")
.attr("r", function(d) {
if (d3.polygonContains(region, [d.longitude, d.latitude])) {
counter++;
return 5;
} else {
return 2;
}
})
// move this text out using d3-annotations
d3.select(this.parentNode).append("text")
.attr("x", regionCentroid[0])
.attr("y", regionCentroid[1])
.attr("text-anchor", "middle")
.text(counter)
})
.on("mouseout", function() {
d3.select(this).style("opacity", 0.05);
d3.selectAll(".station").attr("r", 2);
d3.select(this.parentNode).select("text")
.remove();
})
var tubeMap = svg.append("g")
.attr("class", "tube-map");
// need to clean this up a bit
var routes = tubeMap.append("g")
.attr("class", "routes")
.selectAll("line")
.data(connections)
.enter().append("line")
.attr("class", "route")
.attr("stroke", function(d) { return "#" + routesById[d.line].colour; })
.attr("stroke-linecap", 'round')
.attr("x1", function(d) {
return projection([d.station1.longitude, d.station1.latitude])[0];
})
.attr("y1", function(d) {
return projection([d.station1.longitude, d.station1.latitude])[1];
})
.attr("x2", function(d) {
return projection([d.station2.longitude, d.station2.latitude])[0];
})
.attr("y2", function(d) {
return projection([d.station2.longitude, d.station2.latitude])[1];
});
var interchanges = tubeMap.append("g")
.attr("class", "changes")
.selectAll("circle")
.data(stations.filter(function(d) { return d.totalLines - d.rail > 1; }))
.enter().append("circle")
.attr("class", "interchange")
.attr("cx", function(d) {
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function(d) {
return projection([d.longitude, d.latitude])[1];
})
.attr("r", 2);
var stations = tubeMap.append("g")
.attr("class", "stations")
.selectAll("circle")
.data(stations)
.enter().append("circle")
.attr("class", "station")
.attr("id", function(d) { return 'station'+d.id })
.attr("cx", function(d) {
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function(d) {
return projection([d.longitude, d.latitude])[1];
})
.attr("r", 2)
.attr("title", function(d) { return d.name });
}
</script>
</body>
https://d3js.org/d3.v4.min.js
https://unpkg.com/topojson@3