D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
armollica
Full window
Github gist
Isometric map
<html> <head> <link href="https://fonts.googleapis.com/css?family=Ubuntu+Mono" rel="stylesheet"> <style> html, body { font-family: 'Ubuntu Mono', monospace; } .county { fill: #ebeae0; stroke: #cdcbb1; } .road { fill: none; stroke: #b566ff; } .road.motorway { stroke-width: 1px; } .road.trunk, .road.motorway_link, .road.primary { stroke-width: 0.5px; } .water { fill: #e3e3ff; stroke: #d7d7ff; } .label line { stroke: #333; stroke-width: 1px; } .label text { text-shadow: -1px -1px 1px #fff, -1px 0px 1px #fff, -1px 1px 1px #fff, 0px -1px 1px #fff, 0px 1px 1px #fff, 1px -1px 1px #fff, 1px 0px 1px #fff, 1px 1px 1px #fff; } .label.extra-large { font-size: 18px; } .label.large { font-size: 15px; } .label.medium { font-size: 12px; } .label.small { font-size: 10px; } </style> </head> <body> <script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://unpkg.com/topojson@3"></script> <script> var width = 960, height = 960; var svg = d3.select('body').append('svg') .attr('width', width) .attr('height', height) .append('g') .attr('transform', 'translate(' + (width / 2) + ',' + (-100) + ')') var projection = d3.geoMercator(); var isoprojection = isometricProjection(); var path = isometricPath() .projection(projection) .isoprojection(isoprojection); d3.json('topo.json', function(error, topo) { if (error) throw error; var countyData = topojson.feature(topo, topo.objects.counties), roadData = topojson.feature(topo, topo.objects.roads), waterData = topojson.feature(topo, topo.objects.water); projection.fitExtent([[40, 40], [width - 40, height - 40]], countyData); var water = svg.append('g').attr('class', 'water') .selectAll('.water').data(waterData.features) .enter().append('path') .attr('class', 'water') .attr('d', path.height(-20)); var county = svg.append('g').attr('class', 'counties') .selectAll('.county').data(countyData.features) .enter().append('path') .attr('class', 'county') .attr('d', path.height(0)); var road = svg.append('g').attr('class', 'roads') .selectAll('.road').data(roadData.features) .enter().append('path') .attr('class', function(d) { return 'road ' + d.properties.fclass; }) .attr('d', path.height(20)); var label = svg.append('g').attr('class', 'labels') .selectAll('label').data(labelData()) .enter().append('g') .attr('class', function(d) { return 'label ' + d.className; }); label.append('line'); label.append('text') .style('text-anchor', 'middle') .attr('dy', '-0.33em') .text(function(d) { return d.label; }); label .each(function(d) { var p = projection(d.location), l0 = isoprojection([p[0], -p[1], 20]), l1 = isoprojection([p[0], -p[1], 20 + d.height]); d3.select(this).select('line') .attr('x1', l0[0]) .attr('y1', l0[1]) .attr('x2', l1[0]) .attr('y2', l1[1]); d3.select(this).select('text') .attr('x', l1[0]) .attr('y', l1[1]); }); function update() { water.attr('d', path.height(-20)); county.attr('d', path.height(0)); road.attr('d', path.height(20)); label .each(function(d) { var p = projection(d.location), l0 = isoprojection([p[0], -p[1], 20]), l1 = isoprojection([p[0], -p[1], 20 + d.height]); d3.select(this).select('line') .attr('x1', l0[0]) .attr('y1', l0[1]) .attr('x2', l1[0]) .attr('y2', l1[1]); d3.select(this).select('text') .attr('x', l1[0]) .attr('y', l1[1]); }); } d3.interval(function(elapsed) { var t = elapsed / 15000 - Math.PI / 4, pitch = ((Math.sin(t) + 1) / 2) * (Math.PI / 12) + (Math.PI / 6); yaw = ((Math.sin(t) + 1) / 2) * (Math.PI / 12) - (Math.PI / 4); isoprojection .pitch(pitch) .yaw(yaw); update(); }, 33); }); function isometricProjection() { var sin = Math.sin, cos = Math.cos, asin = Math.asin, tan = Math.atan, PI = Math.PI; var pitch = PI / 6, yaw = PI / 4, alpha = asin(tan(pitch)), beta = yaw; // See https://en.wikipedia.org/wiki/Isometric_projection // TODO: Figure out why ax, ay and az needed to be flipped around. function project(point) { var ax = point[1], ay = -point[2], az = point[0]; var x = cos(beta) * ax - sin(beta) * az, y = cos(alpha) * ay + sin(alpha) * (sin(beta) * ax + cos(beta) * az); return [x, y]; } project.pitch = function(x) { if (!arguments.length) return alpha; pitch = x; alpha = Math.asin(Math.tan(pitch)); return project; }; project.yaw = function(x) { if (!arguments.length) return beta; yaw = x; beta = yaw; return project; }; return project; } function isometricPath() { var projection, isoprojection, height = 0; function path(feature) { if (feature.geometry) { return { Polygon: polygon, MultiPolygon: multipolygon, LineString: linestring, }[feature.geometry.type](feature.geometry.coordinates); } else { return null; } } path.isoprojection = function(x) { if (!arguments.length) return isoprojection; isoprojection = x; return path; }; path.projection = function(x) { if (!arguments.length) return projection; projection = x; return path; }; path.height = function(x) { if (!arguments.length) return height; height = x; return path; }; function project(point) { var p = projection(point), d = [p[0], -p[1], height]; return isoprojection(d); } function multipolygon(coordinates) { return 'M' + coordinates.map(function(multipolygon) { var outerPolygon = multipolygon[0], innerPolygons = multipolygon.slice(1); var pathString = outerPolygon.map(function(point) { return project(point).join(','); }).join('L'); if (innerPolygons.length > 0) { pathString += innerPolygons.map(function(polygon) { return 'M' + polygon.map(function(point) { return project(point).join(','); }).join('L'); }); }; return pathString; }).join('M'); } function polygon(coordinates) { var outerPolygon = coordinates[0], innerPolygons = coordinates.slice(1); var pathString = 'M' + outerPolygon.map(function(point) { return project(point).join(','); }).join('L'); if (innerPolygons.length > 0) { pathString += innerPolygons.map(function(polygon) { return 'M' + polygon.map(function(point) { return project(point).join(','); }).join('L'); }); }; return pathString; } function linestring(coordinates) { return 'M' + coordinates.map(function(point) { return project(point).join(','); }); } return path; } function radiansToDegrees(radians) { return radians * 180 / Math.PI; } function degreesToRadians(degrees) { return degrees * Math.PI / 180; } function labelData() { return [ { label: 'Milwaukee', location: [-87.909665, 43.041331], height: 50, className: 'extra-large' }, { label: 'Wauwautosa', location: [-88.010879, 43.050316], height: 20, className: 'medium' }, { label: 'West Allis', location: [-88.007216, 43.016242], height: 20, className: 'medium' }, { label: 'Waukesha', location: [-88.233588, 43.013146], height: 40, className: 'large' }, { label: 'Brookfield', location: [-88.106788, 43.061748], height: 20, className: 'small' }, { label: 'Oak Creek', location: [-87.864665, 42.884469], height: 20, className: 'medium' }, { label: 'Cedarburg', location: [-87.989352, 43.296658], height: 20, className: 'small' }, { label: 'Menomonee Falls', location: [-88.106047, 43.184513], height: 20, className: 'small' }, { label: 'Muskego', location: [-88.141417, 42.904828], height: 20, className: 'small' } ]; } </script> </body> </html>
https://d3js.org/d3.v4.min.js
https://unpkg.com/topojson@3