├── .gitignore ├── README.md ├── img └── screen.png ├── package.json ├── voronoi-projection └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | out/ 4 | node_modules 5 | npm-debug.log 6 | *~ 7 | .*~ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Voronoi projection 2 | 3 | ``` 4 | Usage: voronoi-projection [options] 5 | 6 | Generate a Voronoi map. 7 | 8 | Options: 9 | 10 | -V, --version output the version number 11 | -n, --n number of faces (default: 200) 12 | -w, --width width (default: 1920) 13 | --height height (default: null) 14 | --colors color scheme (default: none) 15 | --background background color ('' for transparent) (default: white) 16 | --angle angle (in degrees) (default: auto) 17 | --graticule show graticule 18 | --land strategy for land (auto, full, point, poly, none) (default: auto) 19 | --ocean favor ocean links 20 | --fibonacci fibonacci distribution 21 | --centroids large countries centroids distribution 22 | --noise perturbation (try: 1) (default: 0) 23 | -h, --help output usage information 24 | ``` 25 | 26 | Comments and timings will be printed to STDERR, and the resulting image sent to STDOUT. 27 | 28 | Typical usage could be: 29 | ``` 30 | ./voronoi-projection > voronoi-world.png 31 | ``` 32 | 33 | or: 34 | ``` 35 | ./voronoi-projection --colors schemeSet3 --centroids | imgcat 36 | ``` 37 | 38 | 39 | ![](img/screen.png) 40 | 41 | 42 | -------------------------------------------------------------------------------- /img/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fil/voronoi-projection/94f412f60beaa9863154b39642a04af369f3c292/img/screen.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voronoi-projection", 3 | "version": "0.0.1", 4 | "description": "Voronoi projection.", 5 | "keywords": ["d3", "geo", "polygon"], 6 | "homepage": "https://github.com/Fil/voronoi-projection", 7 | "license": "MIT", 8 | "author": { 9 | "name": "Philippe Rivière", 10 | "url": "https://visionscarto.net" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/Fil/voronoi-projection.git" 15 | }, 16 | "dependencies": { 17 | "canvas": "1.6.11", 18 | "commander": "2", 19 | "d3-array": "^0", 20 | "d3-delaunay": "^4", 21 | "d3-geo": "^1.10", 22 | "d3-geo-polygon": "^1", 23 | "d3-geo-voronoi": "^1.1", 24 | "d3-scale-chromatic": "^1", 25 | "topojson-client": "^3", 26 | "visionscarto-world-atlas": "^0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /voronoi-projection: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node --max-old-space-size=4096 2 | 3 | const fs = require("fs"), 4 | topojson = require("topojson-client"), 5 | Canvas = require("canvas"), 6 | d3 = Object.assign( 7 | {}, 8 | require("d3-array"), 9 | require("d3-geo"), 10 | require("d3-geo-polygon"), 11 | require("d3-geo-voronoi"), 12 | require("d3-scale-chromatic") 13 | ), 14 | radians = Math.PI / 180; 15 | 16 | const commander = require("commander"); 17 | 18 | commander 19 | .version(require("./package.json").version) 20 | .usage("[options]") 21 | .description("Generate a Voronoi map.") 22 | .option("-n, --n ", "number of faces", 200) 23 | .option("-w, --width ", "width", 2 * 960) 24 | .option("--height ", "height", null) 25 | .option("--colors ", "color scheme", "none") 26 | .option("--background ", "background color ('' for transparent)", "white") 27 | .option("--angle ", "angle (in degrees)", "auto") 28 | .option("--graticule", "show graticule", false) 29 | .option( 30 | "--land ", 31 | "strategy for land (auto, full, point, poly, none)", 32 | "auto" 33 | ) 34 | .option("--ocean", "favor ocean links", false) 35 | .option("--fibonacci", "fibonacci distribution", false) 36 | .option("--centroids", "large countries centroids distribution", false) 37 | .option("--noise ", "perturbation (try: 1)", 0) 38 | .parse(process.argv); 39 | 40 | var width = +commander.width, 41 | height = +commander.height || ((3 / 5) * width) | 0; 42 | 43 | var canvas = new Canvas(width, height), 44 | context = canvas.getContext("2d"); 45 | 46 | var world = require("visionscarto-world-atlas/world/110m.json"), 47 | land = topojson.feature(world, world.objects.land), 48 | countries = topojson.feature(world, world.objects.countries), 49 | graticule = d3.geoGraticule(); 50 | 51 | const n = +commander.n, 52 | ocean = !!commander.ocean, 53 | pointdistribution = commander.centroids 54 | ? centroids 55 | : commander.fibonacci 56 | ? fibonacci 57 | : randompoints; 58 | 59 | var time = Date.now(); 60 | 61 | randomseed(4); 62 | 63 | process.stderr.write("computing points… "); 64 | 65 | const points = pointdistribution(n); 66 | points.forEach((p, i) => { 67 | p[2] = i; 68 | }); 69 | 70 | if (commander.noise) { 71 | const perturb = +commander.noise / Math.sqrt(n); 72 | points.forEach((p, i) => { 73 | p[0] += ((Math.random() - 0.5) * perturb * 180) / Math.cos(p[1] * radians); 74 | p[1] = (p[1] + (Math.random() - 0.5) * perturb * 180) % 90; 75 | }); 76 | } 77 | 78 | console.warn(Date.now() - time); 79 | time = Date.now(); 80 | process.stderr.write("computing voronoi… "); 81 | 82 | const voro = d3.geoVoronoi()(points); 83 | 84 | const polygons = voro.polygons(); 85 | 86 | // this gains 99% on the computation of points.property.land 87 | // const contains = (f => d3.geoContains(land,f)); 88 | console.warn(Date.now() - time); 89 | time = Date.now(); 90 | process.stderr.write("computing distances… "); 91 | 92 | const contains = geoContainsCache(land), 93 | inland = points.map(d => +contains(d)), 94 | shore = points.map(dist2shore()); 95 | 96 | function cost2(a, b) { 97 | var mult = (1 + shore[a[2]]) ** 2 * (1 + shore[b[2]]) ** 2; 98 | if (ocean) mult = 1 / mult; 99 | return d3.geoDistance(a, b) / mult; 100 | } 101 | 102 | const kruskal = (function() { 103 | // https://github.com/mikolalysenko/union-find 104 | const UnionFind = (function() { 105 | "use strict"; 106 | "use restrict"; 107 | 108 | function UnionFind(count) { 109 | this.roots = new Array(count); 110 | this.ranks = new Array(count); 111 | 112 | for (var i = 0; i < count; ++i) { 113 | this.roots[i] = i; 114 | this.ranks[i] = 0; 115 | } 116 | } 117 | 118 | var proto = UnionFind.prototype; 119 | 120 | Object.defineProperty(proto, "length", { 121 | get: function() { 122 | return this.roots.length; 123 | } 124 | }); 125 | 126 | proto.makeSet = function() { 127 | var n = this.roots.length; 128 | this.roots.push(n); 129 | this.ranks.push(0); 130 | return n; 131 | }; 132 | 133 | proto.find = function(x) { 134 | var x0 = x; 135 | var roots = this.roots; 136 | while (roots[x] !== x) { 137 | x = roots[x]; 138 | } 139 | while (roots[x0] !== x) { 140 | var y = roots[x0]; 141 | roots[x0] = x; 142 | x0 = y; 143 | } 144 | return x; 145 | }; 146 | 147 | proto.link = function(x, y) { 148 | var xr = this.find(x), 149 | yr = this.find(y); 150 | if (xr === yr) { 151 | return; 152 | } 153 | var ranks = this.ranks, 154 | roots = this.roots, 155 | xd = ranks[xr], 156 | yd = ranks[yr]; 157 | if (xd < yd) { 158 | roots[xr] = yr; 159 | } else if (yd < xd) { 160 | roots[yr] = xr; 161 | } else { 162 | roots[yr] = xr; 163 | ++ranks[xr]; 164 | } 165 | }; 166 | 167 | return UnionFind; 168 | })(); 169 | 170 | function kruskal(graph, dist) { 171 | // 1 A := ø 172 | const A = []; 173 | // 2 for each vertex v of G : 174 | // 3 create set(v) 175 | let n = -Infinity; 176 | graph.forEach(l => { 177 | if (l.source.index > n) n = l.source.index; 178 | if (l.target.index > n) n = l.target.index; 179 | }); 180 | const uf = new UnionFind(n); 181 | // 4 sort the edges of G by increasing weight 182 | 183 | graph = graph.map(l => { 184 | l.w = dist ? dist(l.source, l.target) : l.length; 185 | return l; 186 | }); 187 | graph 188 | .sort((a, b) => d3.ascending(a.w, b.w)) 189 | // 5 for each edge (u, v) of G (sorted by weigth): 190 | .forEach(l => { 191 | // 6 if find(u) ≠ find(v) : 192 | if (uf.find(l.source.index) != uf.find(l.target.index)) { 193 | // 7 add (u, v) to the set A 194 | A.push(l); 195 | // 8 union(u, v) 196 | uf.link(l.source.index, l.target.index); 197 | } 198 | }); 199 | // 9 solution = A 200 | return A; 201 | } 202 | 203 | return kruskal; 204 | })(); 205 | 206 | console.warn(Date.now() - time); 207 | time = Date.now(); 208 | process.stderr.write("computing tree… "); 209 | const parents = (function() { 210 | var n = points.length; 211 | 212 | var links = voro.links().features.map(d => d.properties); //.filter(d => d.urquhart) 213 | 214 | var k = kruskal( 215 | links.map( 216 | d => ((d.source.index = d.source[2]), (d.target.index = d.target[2]), d) 217 | ), 218 | cost2 219 | ).map(l => ({ 220 | type: "LineString", 221 | coordinates: [l.source, l.target], 222 | properties: l 223 | })); 224 | 225 | // Build a tree of the faces 226 | var parents = [-1]; 227 | var search = n - 1; 228 | do { 229 | k.forEach(l => { 230 | var s = l.properties.source[2], 231 | t = l.properties.target[2]; 232 | if (parents[s] !== undefined && parents[t] === undefined) { 233 | parents[t] = s; 234 | search--; 235 | } else if (parents[t] !== undefined && parents[s] === undefined) { 236 | parents[s] = t; 237 | search--; 238 | } 239 | }); 240 | } while (search > 0); 241 | 242 | return parents; 243 | })(); 244 | 245 | const degrees = 180 / Math.PI; 246 | 247 | if (commander.background) { 248 | context.fillStyle = commander.background; 249 | context.fillRect(0, 0, width, height); 250 | } 251 | 252 | console.warn(Date.now() - time); 253 | time = Date.now(); 254 | process.stderr.write("computing projection… "); 255 | const projection = d3.geoPolyhedralVoronoi(parents, polygons, null, voro.find); 256 | 257 | var path = d3 258 | .geoPath() 259 | .projection(projection) 260 | .context(context); 261 | 262 | if (commander.angle === "auto") { 263 | console.warn(Date.now() - time); 264 | time = Date.now(); 265 | process.stderr.write("computing angle… "); 266 | commander.angle = angle(points, projection); 267 | process.stderr.write(`${commander.angle}° `); 268 | } 269 | 270 | console.warn(Date.now() - time); 271 | time = Date.now(); 272 | 273 | projection 274 | .angle(+commander.angle) 275 | .fitExtent([[3, 3], [width - 3, height - 3]], { type: "Sphere" }); 276 | 277 | process.stderr.write("projecting sphere… "); 278 | if (commander.colors !== "none") { 279 | const colors = get_colors(commander.colors); 280 | const preclip = projection.preclip(); 281 | projection.preclip(s => s); 282 | points.forEach((p, i) => { 283 | context.beginPath(); 284 | path(shrink(polygons.features[i].geometry)); 285 | context.fillStyle = colors[(i * 13) % colors.length]; 286 | context.fill(); 287 | }); 288 | projection.preclip(preclip); 289 | } else { 290 | context.beginPath(); 291 | path({ type: "Sphere" }); 292 | context.fillStyle = "#fefef6"; 293 | context.fill(); 294 | } 295 | 296 | if (commander.land === "auto") commander.land = n < 1000 ? "full" : "poly"; 297 | 298 | console.warn(Date.now() - time); 299 | time = Date.now(); 300 | process.stderr.write("projecting land… "); 301 | context.beginPath(); 302 | switch (commander.land) { 303 | case "full": 304 | path(land); 305 | break; 306 | case "point": 307 | path.pointRadius(Math.sqrt(300000 / n)); 308 | points.forEach(p => contains(p) && path({ type: "Point", coordinates: p })); 309 | break; 310 | 311 | case "poly": 312 | const preclip = projection.preclip(); 313 | projection.preclip(s => s); 314 | context.beginPath(); 315 | points.forEach( 316 | (p, i) => contains(p) && path(shrink(polygons.features[i].geometry)) 317 | ); 318 | projection.preclip(preclip); 319 | break; 320 | 321 | case "none": 322 | break; 323 | } 324 | context.fillStyle = "#000"; 325 | context.fill(); 326 | 327 | if (commander.graticule) { 328 | console.warn(Date.now() - time); 329 | time = Date.now(); 330 | process.stderr.write("projecting graticule… "); 331 | context.beginPath(); 332 | path(graticule()); 333 | context.strokeStyle = "rgba(119,119,119,0.5)"; 334 | context.stroke(); 335 | } 336 | 337 | console.warn(Date.now() - time); 338 | time = Date.now(); 339 | process.stderr.write("projecting sphere… "); 340 | context.beginPath(); 341 | path({ type: "Sphere" }); 342 | context.strokeStyle = "#000"; 343 | context.stroke(); 344 | 345 | console.warn(Date.now() - time); 346 | time = Date.now(); 347 | canvas.pngStream().pipe(process.stdout); 348 | 349 | //// 350 | 351 | // https://beta.observablehq.com/@fil/another-hex-map#geoContainsCache 352 | function geoContainsCache(land) { 353 | var w = 5000, 354 | h = 5000, 355 | projection = d3.geoAzimuthalEqualArea().fitSize([w, h], { type: "Sphere" }), 356 | canvas = new Canvas(w, h), 357 | context = canvas.getContext("2d"), 358 | path = d3.geoPath(projection, context); 359 | canvas.width = w; 360 | canvas.height = h; 361 | context.fillStyle = "white"; 362 | context.fillRect(0, 0, w, h); 363 | context.fillStyle = "black"; 364 | context.beginPath(), path(land), context.fill(); 365 | var c = context.getImageData(0, 0, w, h).data; 366 | canvas = context = path = null; // free up memory 367 | return function(point) { 368 | point = projection(point); 369 | return c[4 * (Math.floor(point[0]) + Math.floor(point[1]) * w)] < 128; 370 | }; 371 | } 372 | 373 | function dist2shore() { 374 | const vertices = geoVertices(land), 375 | delaunay = d3.geoDelaunay(vertices); 376 | return function(a) { 377 | return ( 378 | d3.geoDistance(a, vertices[delaunay.find(a[0], a[1])]) * 379 | (contains(a) ? 1 : -1) 380 | ); 381 | }; 382 | } 383 | 384 | // point distribution functions 385 | function randompoints(n) { 386 | return [[0, 0]].concat( 387 | d3 388 | .range(n - 1) 389 | .map(i => [ 390 | 360 * Math.random() - 180, 391 | 90 * (Math.random() - Math.random()) 392 | ]) 393 | ); 394 | } 395 | 396 | function fibonacci(n) { 397 | const phi = (1 + Math.sqrt(5)) / 2; 398 | return d3.range(n).map(i => { 399 | const x = i / phi, 400 | y = i / n; 401 | return [ 402 | ((x * 360) % 360) - 180, 403 | (Math.acos(2 * y - 1) / Math.PI) * 180 - 90 404 | ]; 405 | }); 406 | } 407 | 408 | function centroids(n) { 409 | return countries.features 410 | .slice() 411 | .sort((a, b) => d3.geoArea(b) - d3.geoArea(a)) 412 | .slice(0, n) 413 | .map(d3.geoCentroid); 414 | } 415 | 416 | function randomseed(k) { 417 | Math.seed = function(s) { 418 | return function() { 419 | s = Math.sin(s) * 10000; 420 | return s - Math.floor(s); 421 | }; 422 | }; 423 | Math.random = Math.seed(k); 424 | } 425 | 426 | function geoVertices(feature) { 427 | const vertices = []; 428 | const stream = { 429 | point: function(lambda, phi) { 430 | vertices.push([lambda, phi]); 431 | }, 432 | lineStart: function() {}, 433 | lineEnd: function() {}, 434 | polygonStart: function() {}, 435 | polygonEnd: function() {} 436 | }; 437 | d3.geoStream(feature, stream); 438 | return vertices; 439 | } 440 | 441 | function shrink(polygon) { 442 | const c = d3.geoCentroid(polygon); 443 | return { 444 | type: "Polygon", 445 | coordinates: [ 446 | polygon.coordinates[0].map(d => d3.geoInterpolate(d, c)(0.001)) 447 | ] 448 | }; 449 | } 450 | 451 | // find a good angle for the projection 452 | function angle(points, projection) { 453 | const A = convexHull(points.map(projection)); 454 | return d3.scan( 455 | d3.range(180).map(function(angle) { 456 | const c = Math.cos(angle * radians), 457 | s = -Math.sin(angle * radians), 458 | w = d3.extent(A.map(p => p[1] * c + p[0] * s)); 459 | return w[1] - w[0]; 460 | }) 461 | ); 462 | } 463 | 464 | /** 465 | * @param points An array of [X, Y] coordinates 466 | * https://beta.observablehq.com/@fil/planar-hull-andrews-monotone-chain-algorithm 467 | */ 468 | function convexHull(points) { 469 | function cross(a, b, o) { 470 | return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]); 471 | } 472 | 473 | points.sort(function(a, b) { 474 | return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]; 475 | }); 476 | var lower = []; 477 | for (var i = 0; i < points.length; i++) { 478 | while ( 479 | lower.length >= 2 && 480 | cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0 481 | ) { 482 | lower.pop(); 483 | } 484 | lower.push(points[i]); 485 | } 486 | var upper = []; 487 | for (var i = points.length - 1; i >= 0; i--) { 488 | while ( 489 | upper.length >= 2 && 490 | cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0 491 | ) { 492 | upper.pop(); 493 | } 494 | upper.push(points[i]); 495 | } 496 | 497 | lower.pop(); 498 | return lower.concat(upper); 499 | } 500 | 501 | function get_colors(colors) { 502 | if (colors in d3) return d3[colors]; 503 | // https://beta.observablehq.com/@fil/colormaps / roma 504 | if (colors === "roma") 505 | return [ 506 | "#7e1900", 507 | "#801c01", 508 | "#811f02", 509 | "#822203", 510 | "#832504", 511 | "#852705", 512 | "#862a06", 513 | "#872d06", 514 | "#882f07", 515 | "#8a3108", 516 | "#8b3409", 517 | "#8c360a", 518 | "#8d380b", 519 | "#8e3b0c", 520 | "#8f3d0c", 521 | "#903f0d", 522 | "#92410e", 523 | "#93440f", 524 | "#944610", 525 | "#954811", 526 | "#964a12", 527 | "#974c13", 528 | "#984e14", 529 | "#995015", 530 | "#9a5315", 531 | "#9b5516", 532 | "#9c5717", 533 | "#9d5918", 534 | "#9e5b19", 535 | "#9f5d1a", 536 | "#a05f1b", 537 | "#a1611c", 538 | "#a2631c", 539 | "#a3651d", 540 | "#a4671e", 541 | "#a5691f", 542 | "#a66b20", 543 | "#a76d21", 544 | "#a86f21", 545 | "#a97122", 546 | "#aa7323", 547 | "#ab7524", 548 | "#ac7725", 549 | "#ad7926", 550 | "#ae7c27", 551 | "#af7e27", 552 | "#b08028", 553 | "#b18229", 554 | "#b2842a", 555 | "#b3862b", 556 | "#b4882c", 557 | "#b58a2d", 558 | "#b68c2e", 559 | "#b78e2e", 560 | "#b8902f", 561 | "#b99230", 562 | "#ba9431", 563 | "#bb9632", 564 | "#bc9933", 565 | "#bd9b35", 566 | "#be9d36", 567 | "#bf9f37", 568 | "#c0a138", 569 | "#c1a339", 570 | "#c2a63b", 571 | "#c3a83c", 572 | "#c4aa3e", 573 | "#c6ac3f", 574 | "#c7af41", 575 | "#c8b143", 576 | "#c9b344", 577 | "#cab646", 578 | "#cbb848", 579 | "#cdba4a", 580 | "#cebc4d", 581 | "#cfbf4f", 582 | "#d0c151", 583 | "#d2c354", 584 | "#d3c556", 585 | "#d4c859", 586 | "#d5ca5c", 587 | "#d6cc5e", 588 | "#d8ce61", 589 | "#d9d064", 590 | "#dad267", 591 | "#dbd36a", 592 | "#dcd56c", 593 | "#ddd76f", 594 | "#ded872", 595 | "#dfda75", 596 | "#e0db78", 597 | "#e0dc7b", 598 | "#e1de7e", 599 | "#e2df80", 600 | "#e3e083", 601 | "#e3e186", 602 | "#e4e289", 603 | "#e4e38b", 604 | "#e5e48e", 605 | "#e5e590", 606 | "#e5e593", 607 | "#e6e695", 608 | "#e6e798", 609 | "#e6e79a", 610 | "#e6e89d", 611 | "#e6e89f", 612 | "#e6e9a1", 613 | "#e6e9a4", 614 | "#e6eaa6", 615 | "#e6eaa8", 616 | "#e6ebaa", 617 | "#e5ebad", 618 | "#e5ebaf", 619 | "#e4ecb1", 620 | "#e4ecb3", 621 | "#e3ecb5", 622 | "#e2ecb7", 623 | "#e1edb9", 624 | "#e0edba", 625 | "#dfedbc", 626 | "#deedbe", 627 | "#ddedc0", 628 | "#dbedc1", 629 | "#daeec3", 630 | "#d8eec4", 631 | "#d7eec6", 632 | "#d5eec7", 633 | "#d3eec9", 634 | "#d1edca", 635 | "#cfedcb", 636 | "#cdedcc", 637 | "#cbedcd", 638 | "#c9edcf", 639 | "#c6edd0", 640 | "#c4ecd1", 641 | "#c1ecd1", 642 | "#bfecd2", 643 | "#bcebd3", 644 | "#b9ebd4", 645 | "#b7ead5", 646 | "#b4ead5", 647 | "#b1e9d6", 648 | "#aee9d7", 649 | "#abe8d7", 650 | "#a8e7d8", 651 | "#a5e7d8", 652 | "#a2e6d9", 653 | "#9fe5d9", 654 | "#9ce4d9", 655 | "#99e3da", 656 | "#96e2da", 657 | "#93e1da", 658 | "#90e0da", 659 | "#8ddfda", 660 | "#8addda", 661 | "#87dcdb", 662 | "#84dbda", 663 | "#81d9da", 664 | "#7ed8da", 665 | "#7bd6da", 666 | "#78d5da", 667 | "#75d3da", 668 | "#73d1d9", 669 | "#70d0d9", 670 | "#6eced9", 671 | "#6bccd8", 672 | "#69cad8", 673 | "#67c9d7", 674 | "#65c7d7", 675 | "#63c5d6", 676 | "#61c3d6", 677 | "#5fc1d5", 678 | "#5ebfd4", 679 | "#5cbed4", 680 | "#5abcd3", 681 | "#59bad2", 682 | "#58b8d2", 683 | "#56b6d1", 684 | "#55b4d0", 685 | "#54b2d0", 686 | "#53b0cf", 687 | "#52afce", 688 | "#50adcd", 689 | "#4fabcd", 690 | "#4ea9cc", 691 | "#4da7cb", 692 | "#4da5ca", 693 | "#4ca4ca", 694 | "#4ba2c9", 695 | "#4aa0c8", 696 | "#499ec7", 697 | "#489cc7", 698 | "#479ac6", 699 | "#4799c5", 700 | "#4697c4", 701 | "#4595c3", 702 | "#4493c3", 703 | "#4492c2", 704 | "#4390c1", 705 | "#428ec0", 706 | "#418cc0", 707 | "#418abf", 708 | "#4089be", 709 | "#3f87bd", 710 | "#3e85bd", 711 | "#3e84bc", 712 | "#3d82bb", 713 | "#3c80bb", 714 | "#3c7eba", 715 | "#3b7db9", 716 | "#3a7bb8", 717 | "#3979b8", 718 | "#3978b7", 719 | "#3876b6", 720 | "#3774b5", 721 | "#3773b5", 722 | "#3671b4", 723 | "#356fb3", 724 | "#356eb3", 725 | "#346cb2", 726 | "#336ab1", 727 | "#3369b0", 728 | "#3267b0", 729 | "#3165af", 730 | "#3164ae", 731 | "#3062ae", 732 | "#2f61ad", 733 | "#2f5fac", 734 | "#2e5dac", 735 | "#2d5cab", 736 | "#2d5aaa", 737 | "#2c59a9", 738 | "#2b57a9", 739 | "#2b55a8", 740 | "#2a54a7", 741 | "#2952a7", 742 | "#2951a6", 743 | "#284fa5", 744 | "#274da5", 745 | "#274ca4", 746 | "#264aa3", 747 | "#2549a3", 748 | "#2547a2", 749 | "#2446a1", 750 | "#2344a1", 751 | "#2342a0", 752 | "#22419f", 753 | "#213f9f", 754 | "#203e9e", 755 | "#1f3c9d", 756 | "#1f3b9c", 757 | "#1e399c", 758 | "#1d379b", 759 | "#1c369a", 760 | "#1b349a", 761 | "#1a3399" 762 | ]; 763 | } 764 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | canvas@1.6.11: 6 | version "1.6.11" 7 | resolved "https://registry.yarnpkg.com/canvas/-/canvas-1.6.11.tgz#c2d8bcf283281f19ded14fa163a111804522330d" 8 | integrity sha512-ElVw5Uk8PReGpzXfDg6PDa+wntnZLGWWfdSHI0Pc8GyXiFbW13drSTzWU6C4E5QylHe+FnLqI7ngMRlp3eGZIQ== 9 | dependencies: 10 | nan "^2.10.0" 11 | 12 | commander@2: 13 | version "2.18.0" 14 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" 15 | 16 | d3-array@1, d3-array@^1.0: 17 | version "1.2.4" 18 | resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" 19 | 20 | d3-array@^0: 21 | version "0.8.1" 22 | resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-0.8.1.tgz#bc0a3f2217ec6156c8620e7698ba644fac4661c3" 23 | 24 | d3-color@1: 25 | version "1.2.3" 26 | resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.3.tgz#6c67bb2af6df3cc8d79efcc4d3a3e83e28c8048f" 27 | 28 | d3-delaunay@^4, d3-delaunay@^4.1.2: 29 | version "4.1.5" 30 | resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-4.1.5.tgz#4318c6d28b22988d88d11650eda440318afe419a" 31 | dependencies: 32 | delaunator "^2.0.0" 33 | 34 | d3-geo-polygon@^1: 35 | version "1.6.1" 36 | resolved "https://registry.yarnpkg.com/d3-geo-polygon/-/d3-geo-polygon-1.6.1.tgz#52945d222893d5bc2b2701827c064482958dd2d1" 37 | dependencies: 38 | d3-array "1" 39 | d3-geo "^1.11.1" 40 | d3-geo-projection "2" 41 | 42 | d3-geo-projection@2: 43 | version "2.4.1" 44 | resolved "https://registry.yarnpkg.com/d3-geo-projection/-/d3-geo-projection-2.4.1.tgz#9d0f3b296aeb4e6a9194a438b5cd04f70866092f" 45 | dependencies: 46 | commander "2" 47 | d3-array "1" 48 | d3-geo "^1.10.0" 49 | 50 | d3-geo-voronoi@^1.1: 51 | version "1.1.2" 52 | resolved "https://registry.yarnpkg.com/d3-geo-voronoi/-/d3-geo-voronoi-1.1.2.tgz#df56adfec59e5f06803d36391b5baf9290be585e" 53 | dependencies: 54 | d3-array "^1.0" 55 | d3-delaunay "^4.1.2" 56 | d3-geo "^1.0" 57 | 58 | d3-geo@^1.0, d3-geo@^1.10, d3-geo@^1.10.0, d3-geo@^1.11.1: 59 | version "1.11.1" 60 | resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.11.1.tgz#3f35e582c0d29296618b02a8ade0fdffb2c0e63c" 61 | dependencies: 62 | d3-array "1" 63 | 64 | d3-interpolate@1: 65 | version "1.3.2" 66 | resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.3.2.tgz#417d3ebdeb4bc4efcc8fd4361c55e4040211fd68" 67 | dependencies: 68 | d3-color "1" 69 | 70 | d3-scale-chromatic@^1: 71 | version "1.3.3" 72 | resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz#dad4366f0edcb288f490128979c3c793583ed3c0" 73 | dependencies: 74 | d3-color "1" 75 | d3-interpolate "1" 76 | 77 | delaunator@^2.0.0: 78 | version "2.0.5" 79 | resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-2.0.5.tgz#c2a9ba2cf3d5aaab8fa0aa3ae82426d3fc0aeaf5" 80 | 81 | nan@^2.10.0: 82 | version "2.14.0" 83 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" 84 | integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== 85 | 86 | topojson-client@^3: 87 | version "3.0.0" 88 | resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.0.0.tgz#1f99293a77ef42a448d032a81aa982b73f360d2f" 89 | dependencies: 90 | commander "2" 91 | 92 | visionscarto-world-atlas@^0: 93 | version "0.0.4" 94 | resolved "https://registry.yarnpkg.com/visionscarto-world-atlas/-/visionscarto-world-atlas-0.0.4.tgz#3bc2d741cc6fab128821b9886a9db767a89fca7b" 95 | --------------------------------------------------------------------------------