Skip to content

Instantly share code, notes, and snippets.

@dianaow
Last active May 18, 2019 09:04
Show Gist options
  • Save dianaow/8adda02db4776b9b60ced857c82ce170 to your computer and use it in GitHub Desktop.
Save dianaow/8adda02db4776b9b60ced857c82ce170 to your computer and use it in GitHub Desktop.
Hexagonal map
license: mit

Hexagonal map code by Nadieh Bremer.

I thought that the hexagonal map overlayed with the basemap gave a nice 3D effect.

I wanted to colour the hexagons based on density around each country's centroid, but i am having difficulty locating the specific hexagon closest to each country's centroid.

Built with blockbuilder.org

var hexmap = function hexmap() {
//________________________________________________
// GET/SET defaults
//________________________________________________
var width = 1200
var height = 600
var margin = { top: 20, right: 10, bottom: 20, left: 10 }
var hexbin = d3.hexbin().size([width, height]).radius(5);
var modal = d3.select('#vis')
var canvas = modal.append('canvas').attr('width', width).attr('height', height).attr('id', 'mapCanvas');
var context = canvas.node().getContext('2d');
var countries = [
{'country': 'Singapore', '1': 5, '0.9': 10, '0.8': 10, '0.7': 10, '0.6': 10, '0.5': 10, '0.4': 1, '0.3': 2, '0.2': 3, '0.1': 10},
{'country': 'Japan', '1': 5, '0.9': 10, '0.8': 10, '0.7': 10, '0.6': 10, '0.5': 10, '0.4': 1, '0.3': 2, '0.2': 3, '0.1': 10},
{'country': 'Russia', '1': 5, '0.9': 10, '0.8': 10, '0.7': 10, '0.6': 10, '0.5': 10, '0.4': 1, '0.3': 2, '0.2': 3, '0.1': 10}
]
var countries = ['Singapore', 'Japan', 'Russia']
var colorScale = d3.scaleOrdinal()
.domain(['0-0.3', '0.3-0.5', '0.5-0.7', '0.7-0.9', '1'])
.range(['#73D055FF', '#29AF7FFF', '#238A8DFF', '#33638DFF', '#440154FF'])
var projection = d3
.geoEquirectangular()
.center([0, 0]) // set centre to further North
.scale(width / 2 / Math.PI) // scale to fit group width
.translate([width / 2, height / 2]) // ensure centred in group
var path = d3.geoPath(projection, context);
var path1 = d3.geoPath(projection);
var svg = modal.append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "map")
countriesGroup = svg.append("g")
var global_sel_centroids = []
execute(function() {
// difficult to get centroid, hence laying another map behind to store array of centroid locations
// also, helps to sanity check hexagon location
execute(function() {
normal_map()
hex_map()
})
})
function normal_map() {
d3.json("https://raw.githubusercontent.com/andybarefoot/andybarefoot-www/master/maps/mapdata/custom50.json", function(error, json) {
// draw a path for each feature/country
countriesPaths = countriesGroup
.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path1)
.attr("id", function(d, i) {
return "country" + d.properties.name;
})
.attr("class", "country")
var centroids = []
json.features.map(d=> {
centroids.push([d.properties.name, path.centroid(d)[0], path.centroid(d)[1]])
})
sel_centroids = centroids.filter(function(d){ return countries.indexOf(d[0]) != -1 })
global_sel_centroids = sel_centroids
})
}
function hex_map() {
d3.json("world-110m.json", function (error, world) {
if (error) throw error;
//Create a land shape | Filter out Antarctica
var land = topojson.merge(world, world.objects.countries.geometries)
// Initialize the context’s path with the desired boundary (nothing is drawn to the screen)
context.beginPath()
path(land)
//Figure out the hexagon grid dimensions
var SQRT3 = 1.7320508075688772
var hex_radius = 3
const hex_width = SQRT3 * hex_radius
const hex_height = 2 * hex_radius
const map_columns = Math.ceil(width / hex_width)
const map_rows = Math.ceil((height - 0.5*hex_radius)/(1.5 * hex_radius))
//Loop over hexagon grid
let hex_points = []
for (let i = 0; i < map_rows; i++) {
for (let j = 0; j < map_columns; j++) {
let a
let b = (3 * i) * hex_radius / 2
if (i % 2 === 0) a = SQRT3 * j * hex_radius
else a = SQRT3 * (j - 0.5) * hex_radius
//Check if this point lies within the landmass, if yes, save it
if (context.isPointInPath(a, b)) hex_points.push({x: a, y: b})
}
}
//console.log(hex_points)
var svg = modal.append('svg')
.attr('width', width)
.attr('height', height)
.attr('id', 'hexagon_map')
const size_increase = 1 //dummy to not have very small white lines between the hexagons
var hexagon_outer_points = [[0, -1], [SQRT3 / 2, -0.5], [SQRT3 / 2, 0.5], [0, 1], [-SQRT3 / 2, 0.5], [-SQRT3 / 2, -0.5], [0, -1]]
//The "local" SVG path coordinates (happens when you use small case letters; m, l, z)
const hexagon_path = "m" + hexagon_outer_points
.map(p => [p[0] * hex_radius*size_increase, p[1] * hex_radius*size_increase].join(','))
.join('l') + "z"
var hexagons = svg.append('g')
.selectAll('path')
.data(hex_points)
.enter().append('path')
.attr('class', 'hexagons')
.attr("d", d => {
//Move the path to the center and then draw a "locally" based SVG path
return "M" + [d.x, d.y] + hexagon_path
})
.style('fill', function (d) {
var polygon = [[0+d.x, -1+d.y], [SQRT3 / 2+d.x, -0.5+d.y], [SQRT3 / 2+d.x, 0.5+d.y],
[0+d.x, 1+d.y], [-SQRT3 / 2+d.x, 0.5+d.y], [-SQRT3 / 2+d.x, -0.5+d.y], [0+d.x, -1+d.y]]
//console.log(global_sel_centroids)
//global_sel_centroids.map(function(c) {
//if (d3.polygonContains(polygon, [0+d.x, -1+d.y])) {
//return "red"
//} else {
return "black"
//}
//})
})
.attr("stroke", "white")
.attr("stroke-width", "1px")
//d3.selectAll('.hexagons')
//.each(function(d) {
//var a_hexagon_path = d3.select(this).attr("d")
//centroids.map(function(c) {
//if (d3.polygonContains(a_hexagon_path, [c[1],c[2]])) {
//d3.select(this).style('fill', 'red')
//}
//})
//})
})
}
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////// Helper functions ////////////////////////////
///////////////////////////////////////////////////////////////////////////
function execute(callback) {
setTimeout(function() {
callback();
},1000);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson@3"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<script src="hexmap1.js"></script>
<style>
#vis {
position: relative;
}
#map {
background-color: #F5F5F2;
position:absolute;
top:0;
left:0;
}
#hexagon_map {
position:absolute;
top:0;
left:0;
}
#map path {
fill: lightgrey;
stroke-width: 2px;
stroke: slategrey;
}
</style>
</head>
<body>
<div id="vis"></div>
<script>
hexmap();
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment