|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Texas terrain</title> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
body { |
|
padding: 0; |
|
margin: 0; |
|
font-family: helvetica, arial, sans-serif; |
|
} |
|
|
|
.bold { |
|
font-weight: bold; |
|
} |
|
|
|
.raster { |
|
fill: none; |
|
opacity: 1; |
|
} |
|
|
|
.neutral-county { |
|
fill: #FFF; |
|
opacity: 0.4; |
|
} |
|
.highlighted-county { |
|
fill: #C00; |
|
opacity: 0.6; |
|
} |
|
|
|
.neutral-county, |
|
.highlighted-county { |
|
stroke: #6E6E6E; |
|
stroke-opacity: 0.6; |
|
stroke-width: 0.7px; |
|
} |
|
|
|
.counties { |
|
fill: none; |
|
stroke: #6E6E6E; |
|
stroke-opacity: 0.4; |
|
stroke-width: 0.5px; |
|
} |
|
|
|
.state-border { |
|
fill: none; |
|
stroke: #6E6E6E; |
|
stroke-opacity: 0.7; |
|
stroke-width: 1px; |
|
} |
|
|
|
.city-marker { |
|
fill: none; |
|
opacity: 0.6; |
|
stroke-width: 2px; |
|
stroke: #000; |
|
} |
|
.capital-marker { |
|
fill: goldenrod; |
|
opacity: 1; |
|
stroke-width: 3px; |
|
stroke: #000; |
|
stroke-opacity: 0.6; |
|
} |
|
|
|
.text-note { |
|
font-size: 15px; |
|
font-weight: 500; |
|
color: #000; |
|
opacity: 0.6; |
|
line-height: 18px; |
|
margin: 0; |
|
text-shadow: 1px 1px 0 white, |
|
1px -1px 0 white, |
|
-1px 1px 0 white, |
|
-1px -1px 0 white; |
|
} |
|
|
|
.city-label { |
|
text-anchor: middle; |
|
margin: 0; |
|
font-size: 15px; |
|
line-height: 14px; |
|
font-weight: 500; |
|
text-align: right; |
|
opacity: 0.6; |
|
color: #000; |
|
|
|
text-shadow: 1px 1px 0 white, |
|
1px -1px 0 white, |
|
-1px 1px 0 white, |
|
-1px -1px 0 white; |
|
} |
|
|
|
.legend { |
|
font-size: 15px; |
|
line-height: 24px; |
|
font-weight: 500; |
|
color: #333; |
|
} |
|
|
|
.label-line { |
|
stroke: #000; |
|
stroke-width: 1.5px; |
|
stroke-opacity: 1; |
|
opacity: 0.8; |
|
fill: none; |
|
} |
|
|
|
.state-label { |
|
font-weight: 500; |
|
text-transform: uppercase; |
|
text-anchor: middle; |
|
opacity: 0.3; |
|
color: #000; |
|
font-size: 24px; |
|
line-height: 28px; |
|
letter-spacing: 0.6em; |
|
} |
|
|
|
.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; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
</style> |
|
</head> |
|
<body> |
|
<svg id="map"></svg> |
|
|
|
<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 keeper_cities = [ |
|
'Abilene', |
|
'Amarillo', |
|
'Austin', |
|
'Beaumont', |
|
'Brownsville', |
|
'Corpus Christi', |
|
'Dallas', |
|
'El Paso', |
|
'Freeport', |
|
'Ft. Worth', |
|
'Galveston', |
|
'Houston', |
|
'Killeen', |
|
'Laredo', |
|
'Lubbock', |
|
'Odessa', |
|
'San Antonio', |
|
'Tyler', |
|
'Victoria', |
|
'Waco', |
|
'Wichita Falls' |
|
]; |
|
|
|
var keeper_counties = [ |
|
'Lubbock' |
|
]; |
|
|
|
// Make sure at least one dimension is smaller than raster image (705 x 670). |
|
var map_width = 700, |
|
map_height = 700; |
|
|
|
var svg = d3.selectAll("#map") |
|
.attr("width", map_width) |
|
.attr("height", map_height); |
|
|
|
// Create a unit projection |
|
var map_projection = d3.geo.mercator() |
|
.scale(1) |
|
.translate([0, 0]); |
|
|
|
var map_path = d3.geo.path() |
|
.projection(map_projection); |
|
|
|
queue() |
|
.defer(d3.json, "texas-counties.json") |
|
.defer(d3.json, "texas-cities.json") |
|
.await(ready); |
|
|
|
function ready(error, counties, cities) { |
|
if (error) throw error; |
|
|
|
// Scale and center the map to fit into the given dimensions. |
|
var b = map_path.bounds(topojson.feature(counties, counties.objects['texas-counties'])); |
|
|
|
// Pixels per map-path-degree, for both directions. |
|
// 0.95 is for padding. 1.0 would fill entire bounding box. |
|
var s = 0.95 / Math.max((b[1][0] - b[0][0]) / map_width, (b[1][1] - b[0][1]) / map_height); |
|
var t = [(map_width - s * (b[1][0] + b[0][0])) / 2.0, (map_height - s * (b[1][1] + b[0][1])) / 2.0]; |
|
|
|
// Scale and center vector |
|
map_projection |
|
.scale(s) |
|
.translate(t); |
|
|
|
// Scale and position shaded relief raster image. Assumes already cropped. |
|
var raster_width = (b[1][0] - b[0][0]) * s; |
|
var raster_height = (b[1][1] - b[0][1]) * s; |
|
|
|
var rtranslate_x = (map_width - raster_width) / 2.0; |
|
var rtranslate_y = (map_height - raster_height) / 2.0; |
|
|
|
// Shaded relief |
|
svg.append("image") |
|
.attr('id', 'Raster') |
|
.attr("clip-path", "url(#texas_clip)") |
|
.attr("xlink:href", "texas-raster.png") |
|
.attr("class", "raster") |
|
.attr("width", raster_width) |
|
.attr("height", raster_height) |
|
.attr("transform", "translate(" + rtranslate_x + ", " + rtranslate_y + ")"); |
|
|
|
// Draw counties |
|
svg.append("g") |
|
.attr('id', 'Counties') |
|
.selectAll("path") |
|
.data(topojson.feature(counties, counties.objects['texas-counties']).features) |
|
.enter().append("path") |
|
.attr("class", function(d) { |
|
if (keeper_counties.indexOf(d.properties.NAME) > -1) { |
|
return 'highlighted-county'; |
|
} else { |
|
return "neutral-county"; |
|
} |
|
}) |
|
.attr("d", map_path); |
|
|
|
// Draw state border |
|
svg.append("g") |
|
.attr('id', 'StateBorder') |
|
.datum(topojson.mesh(counties, counties.objects['texas-counties'], function(a, b) { return a === b; })) |
|
.append("path") |
|
.attr("class", "state-border") |
|
.attr("id", "texas_border") // For shaded relief |
|
.attr("d", map_path); |
|
|
|
// Draw city markers |
|
svg.append('g') |
|
.attr('id', 'CityMarkers') |
|
.selectAll("circle") |
|
.data(cities.features) |
|
.enter().append("circle") |
|
.attr('r', '4px') |
|
.attr("class", function(d) { |
|
if (d.properties.NAME === 'Austin') { |
|
return 'capital-marker'; |
|
} else { |
|
return "city-marker"; |
|
} |
|
}) |
|
.attr("transform", function(d) { return "translate(" + map_projection(d.geometry.coordinates) + ")"; }) |
|
.filter(function(d) { |
|
return keeper_cities.indexOf(d.properties.NAME) === -1; |
|
}).remove(); |
|
|
|
// Write city label text |
|
svg.append('g').attr('id', 'CityLabels').selectAll('.city-label') |
|
.data(cities.features) |
|
.enter().append('text') |
|
.attr("class", "city-label") |
|
.each(function(d) { |
|
d3.select(this) |
|
.attr("transform", function(d) { |
|
var t = "translate(" + map_projection(d.geometry.coordinates) + ")"; |
|
if (d.properties.NAME === 'Dallas') { |
|
t += "translate(0, -20)"; |
|
} else if (d.properties.NAME === 'Beaumont') { |
|
t += "translate(-10, -20)"; |
|
} else if (d.properties.NAME === 'Ft. Worth' || d.properties.NAME === 'Killeen') { |
|
t += "translate(-10, 0)"; |
|
} |
|
return t; |
|
}) |
|
.attr("dx", "5") |
|
.attr("dy", "15") |
|
.style("text-anchor", function(d) { |
|
if (d.properties.NAME === 'Ft. Worth' || d.properties.NAME === 'Killeen' || d.properties.NAME === 'Beaumont') { |
|
return "end"; |
|
} else { |
|
return "start"; |
|
} |
|
}) |
|
.text(function(d) { return d.properties.NAME; }); |
|
}) |
|
.filter(function(d) { |
|
return keeper_cities.indexOf(d.properties.NAME) === -1; |
|
}).remove(); |
|
|
|
// State label |
|
svg.append("text") |
|
.attr("class", "state-label") |
|
.attr("x", map_width * 0.55) |
|
.attr("y", map_height * 0.5) |
|
.text('Texas'); |
|
|
|
// Line path generator |
|
var line = d3.svg.line() |
|
.x(function(d) { return d.x; }) |
|
.y(function(d) { return d.y; }) |
|
.interpolate("basis"); |
|
|
|
// Draw curve between county and text |
|
svg.selectAll(".label-line") |
|
.data(topojson.feature(counties, counties.objects['texas-counties']).features) |
|
.enter().append('path') |
|
.attr("class", "label-line") |
|
.attr("d", function(d) { |
|
var centroid = map_path.centroid(d); |
|
|
|
if (d.properties.NAME === 'Lubbock') { |
|
var lineData = [ |
|
{"x": centroid[0] - 50, "y": centroid[1] - 59}, |
|
{"x": centroid[0] - 2, "y": centroid[1] - 7} |
|
]; |
|
} else { |
|
return; |
|
} |
|
var x_diff = lineData[1].x - lineData[0].x; |
|
var y_diff = lineData[1].y - lineData[0].y; |
|
return line([ |
|
{"x": lineData[0].x, "y": lineData[0].y}, |
|
{"x": lineData[0].x + x_diff * 0.5, "y": lineData[0].y}, |
|
{"x": lineData[1].x, "y": lineData[0].y + y_diff * 0.5}, |
|
{"x": lineData[1].x, "y": lineData[1].y} |
|
]); |
|
}) |
|
.filter(function(d) { |
|
return d.properties.NAME !== 'Lubbock'; |
|
}).remove(); |
|
|
|
// Draw line between city and text |
|
svg.selectAll(".label-line") |
|
.data(cities.features) |
|
.enter().append('path') |
|
.attr("class", "label-line") |
|
.attr("d", function(d) { |
|
var centroid = map_projection(d.geometry.coordinates); |
|
|
|
if (d.properties.NAME === 'Austin') { |
|
var lineData = [ |
|
{"x": centroid[0] + 3, "y": centroid[1] - 3}, |
|
{"x": centroid[0] + 30, "y": centroid[1] - 30} |
|
]; |
|
} else { |
|
return; |
|
} |
|
return line(lineData); |
|
}) |
|
.filter(function(d) { |
|
return d.properties.NAME !== 'Austin'; |
|
}).remove(); |
|
|
|
// Write counties text note |
|
svg.selectAll('.text-note') |
|
.data(topojson.feature(counties, counties.objects['texas-counties']).features) |
|
.enter().append('text') |
|
.attr("class", "text-note") |
|
.each(function(d) { |
|
if (d.properties.NAME === 'Lubbock') { |
|
d3.select(this) |
|
.attr("transform", function(d) { return "translate(" + map_path.centroid(d) + ")"; }) |
|
.append("tspan") |
|
.attr("dx", "-55") |
|
.attr("dy", "-55") |
|
.style("text-anchor", "end") |
|
.text("This is "); |
|
d3.select(this) |
|
.append("tspan") |
|
.attr("class", "bold") |
|
.text("Lubbock County."); |
|
} else { |
|
return; |
|
} |
|
}) |
|
.filter(function(d) { |
|
return d.properties.NAME !== 'Lubbock'; |
|
}).remove(); |
|
|
|
// Write text for cities |
|
svg.selectAll('.text-note') |
|
.data(cities.features) |
|
.enter().append('text') |
|
.attr("class", "text-note") |
|
.each(function(d) { |
|
if (d.properties.NAME === 'Austin') { |
|
d3.select(this) |
|
.attr("transform", function(d) { return "translate(" + map_path.centroid(d) + ")"; }) |
|
.append("tspan") |
|
.attr("dx", "32") |
|
.attr("dy", "-32") |
|
.style("text-anchor", "start") |
|
.text("This is the "); |
|
d3.select(this) |
|
.append("tspan") |
|
.attr("class", "bold") |
|
.text("capital."); |
|
} else { |
|
return; |
|
} |
|
}) |
|
.filter(function(d) { |
|
return d.properties.NAME !== 'Austin'; |
|
}).remove(); |
|
|
|
// Distance scale |
|
function pixelLength(topojson, miles) { |
|
// Calculates the window pixel length for a given map distance. |
|
|
|
var actual_map_bounds = d3.geo.bounds(topojson); |
|
|
|
var radians = d3.geo.distance(actual_map_bounds[0], actual_map_bounds[1]); |
|
var earth_radius = 3959; // miles |
|
var arc_length = radians * earth_radius; // s = r * theta |
|
|
|
var projected_map_bounds = [ |
|
map_projection(actual_map_bounds[0]), |
|
map_projection(actual_map_bounds[1]) |
|
]; |
|
|
|
var projected_width = projected_map_bounds[1][0] - projected_map_bounds[0][0]; |
|
var projected_height = projected_map_bounds[0][1] - projected_map_bounds[1][1]; |
|
var projected_map_hypotenuse = Math.sqrt( |
|
(Math.pow(projected_width, 2)) + (Math.pow(projected_height, 2)) |
|
); |
|
|
|
var pixels_per_mile = projected_map_hypotenuse / arc_length; |
|
var pixel_distance = pixels_per_mile * miles; |
|
|
|
return pixel_distance; |
|
} |
|
|
|
var pixels_for_hundred_miles = pixelLength(topojson.feature(counties, counties.objects['texas-counties']), 100); |
|
|
|
var distance_scale = svg.selectAll("#distance-scale") |
|
.data([pixels_for_hundred_miles]) |
|
.enter().append("g") |
|
.attr("class", "distance-scale") |
|
.attr("width", function(d) { return d; }); |
|
|
|
distance_scale.append('text') |
|
.attr("x", function(d, i) { return map_width * 0.15; }) |
|
.attr("y", function(d, i) { return (map_height * 0.85) + (i * 20); }) |
|
.text("100 miles"); |
|
|
|
distance_scale.append('path') |
|
.attr("class", "distance-scale-line") |
|
.attr("d", function(d, i) { |
|
var lineData = [ |
|
{"x": map_width * 0.15, "y": (map_height * 0.85) + (i * 20) + 3}, |
|
{"x": map_width * 0.15 + d, "y": (map_height * 0.85) + (i * 20) + 3} |
|
]; |
|
|
|
return line(lineData); |
|
}); |
|
|
|
} |
|
|
|
</script> |
|
</body> |
|
</html> |