Example of how to create dynamic map distance scales.
<!DOCTYPE html>
<head>
<title>Map distance scales</title>
<meta charset="utf-8">
<style>
body {
padding: 0;
margin: 0;
font-family: helvetica, arial, sans-serif;
}
.parishes {
fill: white;
stroke: #777;
stroke-opacity: 0.5;
stroke-width: 0.5px;
opacity: 0.8;
}
.parish-border {
fill: none;
stroke: #353535;
stroke-opacity: 0.4;
stroke-width: 0.5px;
opacity: 0.8;
}
.state-border {
fill: none;
stroke: #585858;
}
.distance-scale {
font-size: 11px;
line-height: 11px;
position: absolute;
font-weight: 500;
text-transform: uppercase;
color: #000;
}
.distance-scale-line {
stroke: #000;
stroke-width: 1;
stroke-opacity: 1;
opacity: 1;
fill: #000;
}
</style>
</head>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/queue.v1.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var projection1 = d3.geo.albers()
.center([0, 31.2])
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole)
.parallels([29, 33])
.translate([width * 0.1, height / 2])
.scale(1000);
var projection2 = d3.geo.albers()
.center([0, 31.2])
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole)
.parallels([29, 33])
.translate([width * 0.25, height / 2])
.scale(2000);
var projection3 = d3.geo.albers()
.center([0, 31.2])
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole)
.parallels([29, 33])
.translate([width * 0.5, height / 2])
.scale(3000);
var projection4 = d3.geo.albers()
.center([0, 31.2])
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole)
.parallels([29, 33])
.translate([width * 0.8, height / 2])
.scale(4000);
var map_path1 = d3.geo.path().pointRadius(2).projection(projection1);
var map_path2 = d3.geo.path().pointRadius(2).projection(projection2);
var map_path3 = d3.geo.path().pointRadius(2).projection(projection3);
var map_path4 = d3.geo.path().pointRadius(2).projection(projection4);
queue()
.defer(d3.json, "parishes.json")
.await(ready);
function pixelLength(this_topojson, this_projection, miles) {
// topojson = topojson.feature(districts, districts.objects['afghanistan-districts'])
// Calculates the window pixel length for a given map distance.
// Not sure if math is okay, given arcs, projection distortion, etc.
var actual_map_bounds = d3.geo.bounds(this_topojson);
var radians = d3.geo.distance(actual_map_bounds[0], actual_map_bounds[1]);
var earth_radius = 3959; // miles
var arc_length = earth_radius * radians; // s = r * theta
var projected_map_bounds = [
this_projection(actual_map_bounds[0]),
this_projection(actual_map_bounds[1])
];
var projected_map_width = projected_map_bounds[1][0] - projected_map_bounds[0][0];
var projected_map_height = projected_map_bounds[0][1] - projected_map_bounds[1][1];
var projected_map_hypotenuse = Math.sqrt(
(Math.pow(projected_map_width, 2)) + (Math.pow(projected_map_height, 2))
);
var pixels_per_mile = projected_map_hypotenuse / arc_length;
var pixel_distance = pixels_per_mile * miles;
return pixel_distance;
}
function ready(error, data) {
if (error) throw error;
// First multiple
var map1 = svg.append("g")
.attr("class", "parishes")
.attr("id", "map1")
.attr("transform", "translate(0, -90)");
map1.selectAll("path")
.data(topojson.feature(data, data.objects.parishes).features)
.enter().append("path")
.attr("d", map_path1);
map1.append("path")
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a !== b; }))
.attr("class", "parish-border")
.attr("d", map_path1);
map1.append("path")
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a === b; }))
.attr("class", "state-border")
.attr("d", map_path1);
// Second multiple
var map2 = svg.append("g")
.attr("class", "parishes")
.attr("id", "map2")
.attr("transform", "translate(0, -60)");
map2.selectAll("path")
.data(topojson.feature(data, data.objects.parishes).features)
.enter().append("path")
.attr("d", map_path2);
map2.append("path")
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a !== b; }))
.attr("class", "parish-border")
.attr("d", map_path2);
map2.append("path")
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a === b; }))
.attr("class", "state-border")
.attr("d", map_path2);
// Third multiple
var map3 = svg.append("g")
.attr("class", "parishes")
.attr("id", "map3")
.attr("transform", "translate(0, -30)");
map3.selectAll("path")
.data(topojson.feature(data, data.objects.parishes).features)
.enter().append("path")
.attr("d", map_path3);
map3.append("path")
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a !== b; }))
.attr("class", "parish-border")
.attr("d", map_path3);
map3.append("path")
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a === b; }))
.attr("class", "state-border")
.attr("d", map_path3);
// Fourth multiple
var map4 = svg.append("g")
.attr("class", "parishes")
.attr("id", "map4");
map4.selectAll("path")
.data(topojson.feature(data, data.objects.parishes).features)
.enter().append("path")
.attr("d", map_path4);
map4.append("path")
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a !== b; }))
.attr("class", "parish-border")
.attr("d", map_path4);
map4.append("path")
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a === b; }))
.attr("class", "state-border")
.attr("d", map_path4);
// Distance scale
// Line path generator
var line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("basis");
// Scale1
var pixels_per_hundred_1 = pixelLength(topojson.feature(data, data.objects['parishes']), projection1, 100);
var distance_scale1 = svg.selectAll("#distance-scale1")
.data([pixels_per_hundred_1])
.enter().append("g")
.attr("class", "distance-scale")
.attr("id", "distance-scale1")
.attr("transform", "translate(-35, 0)")
.attr("width", function(d) { return d; });
distance_scale1.append('text')
.attr("x", width * 0.1)
.attr("y", height * 0.1)
.attr("text-anchor", "start")
.text("100 miles");
distance_scale1.append('path')
.attr("class", "distance-scale-line")
.attr("d", function(d, i) {
var lineData = [
{"x": width * 0.1, "y": height * 0.1 + 10},
{"x": width * 0.1 + d, "y": height * 0.1 + 10}
];
return line(lineData);
});
// Scale2
var pixels_per_hundred_2 = pixelLength(topojson.feature(data, data.objects['parishes']), projection2, 100);
var distance_scale2 = svg.selectAll("#distance-scale2")
.data([pixels_per_hundred_2])
.enter().append("g")
.attr("class", "distance-scale")
.attr("id", "distance-scale2")
.attr("transform", "translate(-70, 0)")
.attr("width", function(d) { return d; });
distance_scale2.append('text')
.attr("x", width * 0.25)
.attr("y", height * 0.1)
.attr("text-anchor", "start")
.text("100 miles");
distance_scale2.append('path')
.attr("class", "distance-scale-line")
.attr("d", function(d, i) {
var lineData = [
{"x": width * 0.25, "y": height * 0.1 + 10},
{"x": width * 0.25 + d, "y": height * 0.1 + 10}
];
return line(lineData);
});
// Scale3
var pixels_per_hundred_3 = pixelLength(topojson.feature(data, data.objects['parishes']), projection3, 100);
var distance_scale3 = svg.selectAll("#distance-scale3")
.data([pixels_per_hundred_3])
.enter().append("g")
.attr("class", "distance-scale")
.attr("id", "distance-scale3")
.attr("transform", "translate(-110, 0)")
.attr("width", function(d) { return d; });
distance_scale3.append('text')
.attr("x", width * 0.5)
.attr("y", height * 0.1)
.attr("text-anchor", "start")
.text("100 miles");
distance_scale3.append('path')
.attr("class", "distance-scale-line")
.attr("d", function(d, i) {
var lineData = [
{"x": width * 0.5, "y": height * 0.1 + 10},
{"x": width * 0.5 + d, "y": height * 0.1 + 10}
];
return line(lineData);
});
// Scale4
var pixels_per_hundred_4 = pixelLength(topojson.feature(data, data.objects['parishes']), projection4, 100);
var distance_scale4 = svg.selectAll("#distance-scale4")
.data([pixels_per_hundred_4])
.enter().append("g")
.attr("class", "distance-scale")
.attr("id", "distance-scale4")
.attr("transform", "translate(-140, 0)")
.attr("width", function(d) { return d; });
distance_scale4.append('text')
.attr("x", width * 0.8)
.attr("y", height * 0.1)
.attr("text-anchor", "start")
.text("100 miles");
distance_scale4.append('path')
.attr("class", "distance-scale-line")
.attr("d", function(d, i) {
var lineData = [
{"x": width * 0.8, "y": height * 0.1 + 10},
{"x": width * 0.8 + d, "y": height * 0.1 + 10}
];
return line(lineData);
});
}
// Allows iframe on bl.ocks.org.
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>