├── .npmignore ├── .babelrc ├── assign.js ├── .gitignore ├── package.json ├── LICENSE ├── README.md ├── choropleth.jsx └── choropleth.js /.npmignore: -------------------------------------------------------------------------------- 1 | choropleth.jsx 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /assign.js: -------------------------------------------------------------------------------- 1 | module.exports = (typeof Object.assign === 'function') ? 2 | Object.assign : 3 | (function (target) { 4 | 'use strict' 5 | if (!target) { 6 | throw new TypeError('Cannot convert undefined or null to object') 7 | } 8 | 9 | for (var i = 1, len = arguments.length; i < len; i++) { 10 | var source = arguments[i] 11 | if (source) { 12 | for (var key in source) { 13 | if (source.hasOwnProperty(key)) { 14 | target[key] = source[key] 15 | } 16 | } 17 | } 18 | } 19 | return target 20 | }) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-leaflet-choropleth", 3 | "version": "2.0.1", 4 | "description": "React Component for displaying GeoJson data in Choropleth form", 5 | "main": "choropleth.js", 6 | "scripts": { 7 | "start": "babel choropleth.jsx > choropleth.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "chroma-js": "1.1.1" 14 | }, 15 | "peerDependencies": { 16 | "leaflet": "^1.0.2", 17 | "react-leaflet": "^1.0.1 || ^2.0.0" 18 | }, 19 | "devDependencies": { 20 | "babel-cli": "^6.9.0", 21 | "babel-preset-es2015": "^6.6.0", 22 | "babel-preset-react": "^6.5.0", 23 | "babel-preset-stage-0": "^6.5.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, LiveBy 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-leaflet-choropleth 2 | This component extends geoJson from react-leaflet and will use chroma to set the color, using a property or function value. 3 | This is highly based on [leaflet-choropleth](https://github.com/timwis/leaflet-choropleth) by timwis. 4 | ## Installation 5 | 6 | `npm install react-leaflet-choropleth` 7 | 8 | ## Options 9 | 10 | ### Required 11 | 12 | `data`: geojson data as a FeatureCollection our as an array. 13 | 14 | `style`: Styling to be applied to each geojson object. Can be a function or an object. 15 | 16 | `valueProperty`: The way a value is obtained from each geojson object. Can be a function like `function(feature){ return feature.properties.value }` or it will use the propert to look up on the value in the `properties` object on the geojson shape. 17 | 18 | `scale`: An array of the top and bottom colors to use. 19 | 20 | `steps`: A number to determine how many different colors to pick from. 21 | 22 | `mode`: The mode to use from chroma.js. This can be `'e'` from equadistant, `'q'` for quantile or `'k'` for k-means. 23 | 24 | ### Optional 25 | 26 | `visible`:A way to determine whether to use the fillColor provided in `styles`, or to use the fillColor of the choropleth. Useful for overwriting the choropleth color. 27 | 28 | `ref`: A way to get the leaflet geojson Object. `.leafletElement` will be a FeatureGroup containing the leaflet layers created. 29 | 30 | `identity`: A function used to set the `key` prop for each geojson feature 31 | 32 | All other props passed to `Choropleth` will also be passed to each geojson layer. 33 | 34 | ## Example 35 | ```js 36 | import Choropleth from 'react-leaflet-choropleth' 37 | import { Map } from 'react-leaflet' 38 | 39 | const style = { 40 | fillColor: '#F28F3B', 41 | weight: 2, 42 | opacity: 1, 43 | color: 'white', 44 | dashArray: '3', 45 | fillOpacity: 0.5 46 | } 47 | 48 | const map = (geojson) => ( 49 | 50 | feature.properties.value} 53 | visible={(feature) => feature.id !== active.id} 54 | scale={['#b3cde0', '#011f4b']} 55 | steps={7} 56 | mode='e' 57 | style={style} 58 | onEachFeature={(feature, layer) => layer.bindPopup(feature.properties.label)} 59 | ref={(el) => this.choropleth = el.leafletElement} 60 | /> 61 | 62 | ) 63 | ``` 64 | -------------------------------------------------------------------------------- /choropleth.jsx: -------------------------------------------------------------------------------- 1 | import { GeoJSON, FeatureGroup } from 'react-leaflet' 2 | import chroma from 'chroma-js' 3 | import React, { Component, cloneElement, Children } from 'react' 4 | import assign from './assign' 5 | 6 | export default class Choropleth extends Component { 7 | 8 | isFunction (prop) { 9 | return typeof prop === 'function' 10 | } 11 | 12 | getColors() { 13 | const { data, valueProperty, mode, steps, scale, colors: cl } = this.props 14 | const colors = {} 15 | const features = Array.isArray(data) ? data : data.features 16 | 17 | const values = features.map(item => this.isFunction(valueProperty) 18 | ? valueProperty(item) 19 | : item.properties[valueProperty]) 20 | 21 | colors.limits = chroma.limits(values, mode, steps - 1) 22 | colors.colors = cl || chroma.scale(scale).colors(steps) 23 | return colors 24 | } 25 | 26 | getStyle ({ limits, colors }, feature) { 27 | const { valueProperty, visible = (() => true), style: userStyle } = this.props 28 | 29 | if( !(( this.isFunction(visible) && visible(feature) ) || feature.properties[visible]) ) return userStyle 30 | 31 | const featureValue = this.isFunction(valueProperty) 32 | ? valueProperty(feature) 33 | : feature.properties[valueProperty] 34 | 35 | const idx = (!isNaN(featureValue)) 36 | ? limits.findIndex(lim => featureValue <= lim) 37 | : -1 38 | 39 | if(colors[idx]){ 40 | const style = { 41 | fillColor: colors[idx] 42 | } 43 | 44 | switch (typeof userStyle) { 45 | case 'function': 46 | return assign(userStyle(feature), style) 47 | case 'object': 48 | return assign({}, userStyle, style) 49 | default: 50 | return style 51 | } 52 | 53 | } else { 54 | return userStyle 55 | } 56 | 57 | } 58 | 59 | cloneChildrenWithFeature(props, feature){ 60 | const newProps = assign({}, props, { feature }) 61 | return Children.map(props.children, child => { 62 | return child ? cloneElement(child, newProps) : null 63 | }) 64 | } 65 | 66 | render(){ 67 | const features = Array.isArray(this.props.data) ? this.props.data : this.props.data.features 68 | const chroms = this.getColors() 69 | const { layerContainer, identity, ...options } = this.props //remove 70 | return ( 71 | layer ? this.leafletElement = layer.leafletElement : null } > 72 | {features.map( (feature, idx) => 73 | () 81 | ) } 82 | 83 | ) 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /choropleth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 10 | 11 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 12 | 13 | var _reactLeaflet = require('react-leaflet'); 14 | 15 | var _chromaJs = require('chroma-js'); 16 | 17 | var _chromaJs2 = _interopRequireDefault(_chromaJs); 18 | 19 | var _react = require('react'); 20 | 21 | var _react2 = _interopRequireDefault(_react); 22 | 23 | var _assign = require('./assign'); 24 | 25 | var _assign2 = _interopRequireDefault(_assign); 26 | 27 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 28 | 29 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 30 | 31 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 32 | 33 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 34 | 35 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 36 | 37 | var Choropleth = function (_Component) { 38 | _inherits(Choropleth, _Component); 39 | 40 | function Choropleth() { 41 | _classCallCheck(this, Choropleth); 42 | 43 | return _possibleConstructorReturn(this, (Choropleth.__proto__ || Object.getPrototypeOf(Choropleth)).apply(this, arguments)); 44 | } 45 | 46 | _createClass(Choropleth, [{ 47 | key: 'isFunction', 48 | value: function isFunction(prop) { 49 | return typeof prop === 'function'; 50 | } 51 | }, { 52 | key: 'getColors', 53 | value: function getColors() { 54 | var _this2 = this; 55 | 56 | var _props = this.props, 57 | data = _props.data, 58 | valueProperty = _props.valueProperty, 59 | mode = _props.mode, 60 | steps = _props.steps, 61 | scale = _props.scale, 62 | cl = _props.colors; 63 | 64 | var colors = {}; 65 | var features = Array.isArray(data) ? data : data.features; 66 | 67 | var values = features.map(function (item) { 68 | return _this2.isFunction(valueProperty) ? valueProperty(item) : item.properties[valueProperty]; 69 | }); 70 | 71 | colors.limits = _chromaJs2.default.limits(values, mode, steps - 1); 72 | colors.colors = cl || _chromaJs2.default.scale(scale).colors(steps); 73 | return colors; 74 | } 75 | }, { 76 | key: 'getStyle', 77 | value: function getStyle(_ref, feature) { 78 | var limits = _ref.limits, 79 | colors = _ref.colors; 80 | var _props2 = this.props, 81 | valueProperty = _props2.valueProperty, 82 | _props2$visible = _props2.visible, 83 | visible = _props2$visible === undefined ? function () { 84 | return true; 85 | } : _props2$visible, 86 | userStyle = _props2.style; 87 | 88 | 89 | if (!(this.isFunction(visible) && visible(feature) || feature.properties[visible])) return userStyle; 90 | 91 | var featureValue = this.isFunction(valueProperty) ? valueProperty(feature) : feature.properties[valueProperty]; 92 | 93 | var idx = !isNaN(featureValue) ? limits.findIndex(function (lim) { 94 | return featureValue <= lim; 95 | }) : -1; 96 | 97 | if (colors[idx]) { 98 | var style = { 99 | fillColor: colors[idx] 100 | }; 101 | 102 | switch (typeof userStyle === 'undefined' ? 'undefined' : _typeof(userStyle)) { 103 | case 'function': 104 | return (0, _assign2.default)(userStyle(feature), style); 105 | case 'object': 106 | return (0, _assign2.default)({}, userStyle, style); 107 | default: 108 | return style; 109 | } 110 | } else { 111 | return userStyle; 112 | } 113 | } 114 | }, { 115 | key: 'cloneChildrenWithFeature', 116 | value: function cloneChildrenWithFeature(props, feature) { 117 | var newProps = (0, _assign2.default)({}, props, { feature: feature }); 118 | return _react.Children.map(props.children, function (child) { 119 | return child ? (0, _react.cloneElement)(child, newProps) : null; 120 | }); 121 | } 122 | }, { 123 | key: 'render', 124 | value: function render() { 125 | var _this3 = this; 126 | 127 | var features = Array.isArray(this.props.data) ? this.props.data : this.props.data.features; 128 | var chroms = this.getColors(); 129 | 130 | var _props3 = this.props, 131 | layerContainer = _props3.layerContainer, 132 | identity = _props3.identity, 133 | options = _objectWithoutProperties(_props3, ['layerContainer', 'identity']); //remove 134 | 135 | 136 | return _react2.default.createElement( 137 | _reactLeaflet.FeatureGroup, 138 | { map: this.props.map, layerContainer: layerContainer, ref: function ref(layer) { 139 | return layer ? _this3.leafletElement = layer.leafletElement : null; 140 | } }, 141 | features.map(function (feature, idx) { 142 | return _react2.default.createElement(_reactLeaflet.GeoJSON, _extends({ 143 | key: identity ? identity(feature) : idx 144 | }, options, { 145 | style: _this3.getStyle(chroms, feature) 146 | }, _this3.getStyle(chroms, feature), { 147 | data: feature, 148 | children: _this3.props.children ? _this3.cloneChildrenWithFeature(_this3.props, feature) : _this3.props.children 149 | })); 150 | }) 151 | ); 152 | } 153 | }]); 154 | 155 | return Choropleth; 156 | }(_react.Component); 157 | 158 | exports.default = Choropleth; 159 | 160 | --------------------------------------------------------------------------------