├── 5colors.png ├── README.md ├── bin └── fivecolormap ├── example.png ├── index.js ├── package.json └── test ├── data.json └── five-color-map.js /5colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpdennis/five-color-map/91b665c995c9ad8692d4f72b5955a80d42f8edbf/5colors.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # five-color-map 2 | 3 | Color a GeoJSON FeatureCollection so that bordering areas never have the same fill. Five colors [are all you'll need](https://en.wikipedia.org/wiki/Five_color_theorem). 4 | 5 | ## example 6 | 7 | ![USA Congressional Districts](./example.png) 8 | 9 | ## install 10 | 11 | install with [npm](https://www.npmjs.com/): 12 | 13 | ``` 14 | $ npm install --save five-color-map 15 | ``` 16 | 17 | ## command line 18 | 19 | install with [npm](https://www.npmjs.com/): 20 | ``` 21 | $ npm install -g five-color-map 22 | ``` 23 | then 24 | ``` 25 | $ fivecolormap data.geojson > colored-data.geojson 26 | ``` 27 | 28 | ## usage 29 | 30 | ``` javascript 31 | var fiveColorMap = require('five-color-map'); 32 | var coloredGeoJSON = fiveColorMap(geojson); 33 | ``` 34 | 35 | Each feature in `coloredGeoJSON.features` now has a property `fill` with one of five values: 36 | 37 | ![the five colors used by this package](./5colors.png) 38 | 39 | ## caveat 40 | 41 | At least one of your features must have less than 5 neighbors with adjacent sides. This won't work with a hexagonal grid. 42 | -------------------------------------------------------------------------------- /bin/fivecolormap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'), 4 | fiveColorMap = require('../'); 5 | 6 | var file = process.argv[2]; 7 | 8 | if (file) { 9 | var data = JSON.parse(fs.readFileSync(file)); 10 | var output = fiveColorMap(data); 11 | 12 | process.stdout.write(JSON.stringify(output)); 13 | } 14 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpdennis/five-color-map/91b665c995c9ad8692d4f72b5955a80d42f8edbf/example.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'), 2 | topojson = require('topojson'); 3 | 4 | module.exports = function(geojson) { 5 | 6 | var colors = ['#fbb4ae','#b3cde3','#ccebc5','#decbe4','#fed9a6']; 7 | 8 | geojson.features.map(function(d) { 9 | if (d.properties === undefined) { d.properties = {}; } 10 | return d; 11 | }); 12 | 13 | geojson.features.map(function(d,i) { 14 | if (d.properties.id === undefined) { d.properties.id = i + 1; } 15 | return d; 16 | }); 17 | 18 | var original = JSON.parse(JSON.stringify(geojson)); 19 | 20 | function propertyTransform(feature) { 21 | return feature.properties; 22 | } 23 | 24 | var topology = topojson.topology({ 'collection': geojson }, { 'property-transform': propertyTransform }); 25 | 26 | var neighbors = topojson.neighbors(topology.objects.collection.geometries), 27 | removed = []; 28 | 29 | var initialCheck = true; 30 | 31 | while (neighbors.length > 0) { 32 | 33 | var degrees = neighbors.map(function(d) { 34 | return d.length; 35 | }); 36 | 37 | if (initialCheck && loose >= d3.min(degrees)) { 38 | throw 'No region has less than 5 neighbors. Therefore, this version of the five color thereom cannot be applied.'; 39 | } 40 | 41 | var loose = degrees.indexOf(d3.min(degrees)); 42 | 43 | removed.push(topology.objects.collection.geometries.splice(loose, 1)[0]); 44 | 45 | neighbors = topojson.neighbors(topology.objects.collection.geometries); 46 | initialCheck = false; 47 | 48 | } 49 | 50 | var colorsRequired = []; 51 | while (removed.length > 0) { 52 | 53 | topology.objects.collection.geometries.push(removed.pop()); 54 | 55 | neighbors = topojson.neighbors(topology.objects.collection.geometries); 56 | 57 | var index = neighbors.length - 1; 58 | 59 | colorsRequired.push(neighbors[index].length); 60 | 61 | var contiguous = neighbors[index]; 62 | 63 | var taken = []; 64 | contiguous.map(function(i) { 65 | if (topology.objects.collection.geometries[i].properties.fill) { 66 | taken.push(topology.objects.collection.geometries[i].properties.fill); 67 | } 68 | }); 69 | var available = colors.filter(function(d) { 70 | return taken.indexOf(d) == -1; 71 | }) 72 | 73 | topology.objects.collection.geometries[index].properties.fill = available[Math.floor(Math.random() * available.length)]; // assign a random fill from those colors not used by any neighbors 74 | 75 | } 76 | 77 | var colored = topojson.feature(topology, topology.objects.collection); 78 | 79 | colored.features.map(function(d) { 80 | var id = d.properties.id, 81 | fill = d.properties.fill; 82 | //console.log(id, fill); 83 | original.features.map(function(p) { 84 | if (p.properties.id === id) { 85 | p.properties.fill = fill; 86 | } 87 | return p; 88 | }); 89 | }); 90 | 91 | return original; 92 | } 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "five-color-map", 3 | "version": "1.0.3", 4 | "description": "Color a GeoJSON FeatureCollection so that contiguous areas have different fills", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node ./test/five-color-map.js" 8 | }, 9 | "keywords": [ 10 | "geojson", 11 | "cartography", 12 | "map" 13 | ], 14 | "author": "Aaron Dennis (http://aarondennis.org)", 15 | "bin": { 16 | "fivecolormap": "./bin/fivecolormap" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/aaronpdennis/five-color-map.git" 21 | }, 22 | "license": "ISC", 23 | "dependencies": { 24 | "d3": "^3.5.16", 25 | "topojson": "^1.6.24" 26 | }, 27 | "devDependencies": { 28 | "geojsonhint": "^1.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/five-color-map.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | fiveColorMap = require('../'), 3 | geojsonhint = require('geojsonhint'); 4 | 5 | var geojson = JSON.parse(fs.readFileSync('./test/data.json')); 6 | 7 | var colorMap = fiveColorMap(geojson); 8 | 9 | console.log(geojsonhint.hint(colorMap)); 10 | --------------------------------------------------------------------------------