From 90289f79251bab1bb58c84fe25bfb4bc3b67ec16 Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Mon, 27 Jun 2016 23:38:40 -0400 Subject: [PATCH 1/6] Upgrade devDependencies --- examples/index.jsx | 6 +++--- package.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/index.jsx b/examples/index.jsx index 9d68346..8c11309 100644 --- a/examples/index.jsx +++ b/examples/index.jsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import Datamap from '../src'; -const App = React.createClass({ +const App = React.createClass({ // eslint-disable-line react/prefer-es6-class displayName: 'App', @@ -15,7 +15,7 @@ const App = React.createClass({ }; }, - update() { + handleUpdate() { this.setState(Object.assign({}, { height: parseInt(this.refs.height.value, 10) || null, scope: this.refs.scope.value || null, @@ -30,7 +30,7 @@ const App = React.createClass({ - +
diff --git a/package.json b/package.json index 3fd54aa..c9a8356 100644 --- a/package.json +++ b/package.json @@ -35,12 +35,12 @@ "datamaps": "^0.4.2" }, "devDependencies": { - "@btmills/eslint-config-btmills": "^0.1.3", + "@btmills/eslint-config-btmills": "^1.0.0-beta.6", "babel-cli": "^6.4.5", "babel-plugin-transform-object-rest-spread": "^6.3.13", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", - "eslint": "^1.10.3", - "eslint-plugin-react": "^3.16.1" + "eslint": "^2.13.1", + "eslint-plugin-react": "^5.2.2" } } From 9e14ac7c5194e915b1dd103bda7558847231c55f Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Mon, 27 Jun 2016 23:39:09 -0400 Subject: [PATCH 2/6] Convert Datamap to ES6 class --- .babelrc | 5 ++++- .eslintrc.json | 2 ++ package.json | 2 ++ src/datamap.jsx | 33 ++++++++++++++++----------------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.babelrc b/.babelrc index 6fbf25a..c31ad38 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,7 @@ { "presets": ["es2015", "react"], - "plugins": ["transform-object-rest-spread"] + "plugins": [ + "transform-class-properties", + "transform-object-rest-spread" + ] } diff --git a/.eslintrc.json b/.eslintrc.json index 1257227..592f247 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,8 @@ "extends": "@btmills/eslint-config-btmills/react", + "parser": "babel-eslint", + "env": { "browser": true } diff --git a/package.json b/package.json index c9a8356..4c3eb56 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "devDependencies": { "@btmills/eslint-config-btmills": "^1.0.0-beta.6", "babel-cli": "^6.4.5", + "babel-eslint": "^6.1.0", + "babel-plugin-transform-class-properties": "^6.10.2", "babel-plugin-transform-object-rest-spread": "^6.3.13", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", diff --git a/src/datamap.jsx b/src/datamap.jsx index 506d6b9..cb1acc5 100644 --- a/src/datamap.jsx +++ b/src/datamap.jsx @@ -1,47 +1,46 @@ import React from 'react'; -import Datamap from 'datamaps'; +import Datamaps from 'datamaps'; -export default React.createClass({ +export default class Datamap extends React.Component { - displayName: 'Datamap', - - propTypes: { + static propTypes = { arc: React.PropTypes.array, arcOptions: React.PropTypes.object, bubbleOptions: React.PropTypes.object, bubbles: React.PropTypes.array, graticule: React.PropTypes.bool, labels: React.PropTypes.bool - }, + }; componentDidMount() { this.drawMap(); - }, + } componentWillReceiveProps() { this.clear(); - }, + } componentDidUpdate() { this.drawMap(); - }, + } componentWillUnmount() { this.clear(); - }, + } clear() { - const container = this.refs.container; + const { container } = this.refs; for (const child of Array.from(container.childNodes)) { container.removeChild(child); } - }, + } drawMap() { - const map = new Datamap(Object.assign({}, { ...this.props }, { + const map = new Datamaps({ + ...this.props, element: this.refs.container - })); + }); if (this.props.arc) { map.arc(this.props.arc, this.props.arcOptions); @@ -58,14 +57,14 @@ export default React.createClass({ if (this.props.labels) { map.labels(); } - }, + } render() { const style = { position: 'relative' }; - return
; + return
; } -}); +} From b40cbbbbb10e6e57a918f8bf55092b653305f513 Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Mon, 27 Jun 2016 23:42:08 -0400 Subject: [PATCH 3/6] Convert example to ES6 class --- examples/index.jsx | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/examples/index.jsx b/examples/index.jsx index 8c11309..da8a703 100644 --- a/examples/index.jsx +++ b/examples/index.jsx @@ -3,25 +3,21 @@ import ReactDOM from 'react-dom'; import Datamap from '../src'; -const App = React.createClass({ // eslint-disable-line react/prefer-es6-class +class App extends React.Component { - displayName: 'App', + state = { + height: 300, + scope: 'world', + width: 500 + }; - getInitialState() { - return { - height: 300, - scope: 'world', - width: 500 - }; - }, - - handleUpdate() { + handleUpdate = () => { this.setState(Object.assign({}, { height: parseInt(this.refs.height.value, 10) || null, scope: this.refs.scope.value || null, width: parseInt(this.refs.width.value, 10) || null }, window.example)); - }, + }; render() { return ( @@ -39,7 +35,7 @@ const App = React.createClass({ // eslint-disable-line react/prefer-es6-class ); } -}); +} ReactDOM.render( , From 85681ecd633513c8a59abe3f3dd043f323bcf551 Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Tue, 28 Jun 2016 01:35:14 -0400 Subject: [PATCH 4/6] Support updateChoropleth --- src/datamap.jsx | 60 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/src/datamap.jsx b/src/datamap.jsx index cb1acc5..09bd7a1 100644 --- a/src/datamap.jsx +++ b/src/datamap.jsx @@ -8,16 +8,25 @@ export default class Datamap extends React.Component { arcOptions: React.PropTypes.object, bubbleOptions: React.PropTypes.object, bubbles: React.PropTypes.array, + data: React.PropTypes.object, graticule: React.PropTypes.bool, - labels: React.PropTypes.bool + height: React.PropTypes.any, + labels: React.PropTypes.bool, + style: React.PropTypes.object, + width: React.PropTypes.any }; componentDidMount() { this.drawMap(); } - componentWillReceiveProps() { - this.clear(); + componentWillReceiveProps(newProps) { + if ( + this.props.height !== newProps.height + || this.props.width !== newProps.width + ) { + this.clear(); + } } componentDidUpdate() { @@ -34,34 +43,57 @@ export default class Datamap extends React.Component { for (const child of Array.from(container.childNodes)) { container.removeChild(child); } + + delete this.map; } drawMap() { - const map = new Datamaps({ - ...this.props, - element: this.refs.container - }); + const { + arc, + arcOptions, + bubbles, + bubbleOptions, + data, + graticule, + labels, + ...props + } = this.props; + + let map = this.map; + + if (!map) { + map = this.map = new Datamaps({ + ...props, + data, + element: this.refs.container + }); + } else { + map.updateChoropleth(data); + } - if (this.props.arc) { - map.arc(this.props.arc, this.props.arcOptions); + if (arc) { + map.arc(arc, arcOptions); } - if (this.props.bubbles) { - map.bubbles(this.props.bubbles, this.props.bubbleOptions); + if (bubbles) { + map.bubbles(bubbles, bubbleOptions); } - if (this.props.graticule) { + if (graticule) { map.graticule(); } - if (this.props.labels) { + if (labels) { map.labels(); } } render() { const style = { - position: 'relative' + height: '100%', + position: 'relative', + width: '100%', + ...this.props.style }; return
; From 98442788f9d8bd41632d6e253dbe6bc6c40f4daa Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Tue, 28 Jun 2016 01:36:03 -0400 Subject: [PATCH 5/6] Clone Datamaps examples in React --- examples/arcs-example.jsx | 86 +++++++ examples/basic-example.jsx | 16 ++ examples/bubbles-example.jsx | 63 ++++++ examples/choropleth-example.jsx | 62 +++++ examples/example.jsx | 25 +++ examples/index.jsx | 38 ++-- examples/projections-graticules-example.jsx | 71 ++++++ examples/screen.css | 67 ++---- examples/state-labels-example.jsx | 236 ++++++++++++++++++++ examples/zoom-example.jsx | 95 ++++++++ 10 files changed, 687 insertions(+), 72 deletions(-) create mode 100644 examples/arcs-example.jsx create mode 100644 examples/basic-example.jsx create mode 100644 examples/bubbles-example.jsx create mode 100644 examples/choropleth-example.jsx create mode 100644 examples/example.jsx create mode 100644 examples/projections-graticules-example.jsx create mode 100644 examples/state-labels-example.jsx create mode 100644 examples/zoom-example.jsx diff --git a/examples/arcs-example.jsx b/examples/arcs-example.jsx new file mode 100644 index 0000000..7eb80b1 --- /dev/null +++ b/examples/arcs-example.jsx @@ -0,0 +1,86 @@ +import React from 'react'; + +import Datamap from '../src'; +import Example from './example'; + +export default class ArcsExample extends React.Component { + + render() { + return ( + + + + ); + } + +} diff --git a/examples/basic-example.jsx b/examples/basic-example.jsx new file mode 100644 index 0000000..a428450 --- /dev/null +++ b/examples/basic-example.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import Datamap from '../src'; +import Example from './example'; + +export default class BasicExample extends React.Component { + + render() { + return ( + + + + ); + } + +} diff --git a/examples/bubbles-example.jsx b/examples/bubbles-example.jsx new file mode 100644 index 0000000..53265dc --- /dev/null +++ b/examples/bubbles-example.jsx @@ -0,0 +1,63 @@ +import React from 'react'; + +import Datamap from '../src'; +import Example from './example'; + +export default class BubblesExample extends React.Component { + + render() { + return ( + + + `
Yield: ${data.yeild}\nExploded on ${data.date} by the ${data.country}` + }} + /> + + ); + } + +} diff --git a/examples/choropleth-example.jsx b/examples/choropleth-example.jsx new file mode 100644 index 0000000..fcec303 --- /dev/null +++ b/examples/choropleth-example.jsx @@ -0,0 +1,62 @@ +/* global d3 */ + +import React from 'react'; + +import Datamap from '../src'; +import Example from './example'; + +const colors = d3.scale.category10(); + +export default class ChoroplethExample extends React.Component { + + state = { + data: { + USA: { fillKey: 'authorHasTraveledTo' }, + JPN: { fillKey: 'authorHasTraveledTo' }, + ITA: { fillKey: 'authorHasTraveledTo' }, + CRI: { fillKey: 'authorHasTraveledTo' }, + KOR: { fillKey: 'authorHasTraveledTo' }, + DEU: { fillKey: 'authorHasTraveledTo' } + } + }; + + componentDidMount() { + this.update(); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + update() { + this.interval = setInterval(() => { + this.setState({ + data: { + USA: colors(Math.random() * 10), + RUS: colors(Math.random() * 100), + AUS: { fillKey: 'authorHasTraveledTo' }, + BRA: colors(Math.random() * 50), + CAN: colors(Math.random() * 50), + ZAF: colors(Math.random() * 50), + IND: colors(Math.random() * 50) + } + }); + }, 2000); + } + + render() { + return ( + + + + ); + } + +} diff --git a/examples/example.jsx b/examples/example.jsx new file mode 100644 index 0000000..2d4e502 --- /dev/null +++ b/examples/example.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +export default class Example extends React.Component { + + static propTypes = { + children: React.PropTypes.element.isRequired, + label: React.PropTypes.string.isRequired, + mapStyle: React.PropTypes.object + }; + + render() { + return ( +
+

{this.props.label}

+
+ {React.Children.only(this.props.children)} +
+
+ ); + } + +} diff --git a/examples/index.jsx b/examples/index.jsx index da8a703..bfa8e7c 100644 --- a/examples/index.jsx +++ b/examples/index.jsx @@ -1,36 +1,26 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import Datamap from '../src'; +import ArcsExample from './arcs-example'; +import BasicExample from './basic-example'; +import BubblesExample from './bubbles-example'; +import ChoroplethExample from './choropleth-example'; +import ProjectionsGraticulesExample from './projections-graticules-example.jsx'; +import StateLabelsExample from './state-labels-example'; +import ZoomExample from './zoom-example'; class App extends React.Component { - state = { - height: 300, - scope: 'world', - width: 500 - }; - - handleUpdate = () => { - this.setState(Object.assign({}, { - height: parseInt(this.refs.height.value, 10) || null, - scope: this.refs.scope.value || null, - width: parseInt(this.refs.width.value, 10) || null - }, window.example)); - }; - render() { return (
-
- - - - -
-
- -
+ + + + + + +
); } diff --git a/examples/projections-graticules-example.jsx b/examples/projections-graticules-example.jsx new file mode 100644 index 0000000..6cf2f6c --- /dev/null +++ b/examples/projections-graticules-example.jsx @@ -0,0 +1,71 @@ +/* global d3 */ + +import React from 'react'; + +import Datamap from '../src'; +import Example from './example'; + +const colors = d3.scale.category10(); + +export default class ProjectionsGraticulesExample extends React.Component { + + render() { + return ( + + + + ); + } + +} diff --git a/examples/screen.css b/examples/screen.css index 88e4a1c..757ce48 100644 --- a/examples/screen.css +++ b/examples/screen.css @@ -1,7 +1,13 @@ -* { +html { box-sizing: border-box; } +*, +*:before, +*:after { + box-sizing: inherit; +} + html, body, #app { @@ -15,57 +21,22 @@ body { font: 16px sans-serif; } -.App { - display: flex; - height: 100%; -} - -.App-options { +.Example { + align-items: center; display: flex; flex-direction: column; - padding: 10px; - align-items: stretch; - background: #fafafa; - border-right: 1px solid #e8e8e8; - z-index: 1; } -.App-options label { - padding: 10px; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.App-options input { - width: 200px; - margin-left: 10px; - padding: 5px 10px; - font: inherit; - outline: 0; -} - -.App-options button { - margin: 10px; - padding: 10px; - background: #4183c4; - font: inherit; - color: white; - border: 0; - outline: 0; -} - -.App-options button:active { - background: #3673b0; -} - -.App-map { - flex: 1; - display: flex; - align-items: center; - justify-content: center; +.Example-map { + height: 500px; + width: 750px; } -.App-map > * { - border: 1px solid #eee; +.hoverinfo { + padding: 4px; + border-radius: 1px; + background-color: #fff; + box-shadow: 1px 1px 5px #ccc; + font-size: 12px; + border: 1px solid #ccc; } diff --git a/examples/state-labels-example.jsx b/examples/state-labels-example.jsx new file mode 100644 index 0000000..093bccf --- /dev/null +++ b/examples/state-labels-example.jsx @@ -0,0 +1,236 @@ +import React from 'react'; + +import Datamap from '../src'; +import Example from './example'; + +export default class StateLabelsExample extends React.Component { + + render() { + return ( + + + `
${geography.properties.name}\nElectoral Votes: ${data.electoralVotes}`, + highlightBorderWidth: 3 + }} + fills={{ + 'Republican': '#cc4731', + 'Democrat': '#306596', + 'Heavy Democrat': '#667faf', + 'Light Democrat': '#a9c0de', + 'Heavy Republican': '#ca5e5b', + 'Light Republican': '#eaa9a8', + 'defaultFill': '#eddc4e' + }} + data={{ + AZ: { + fillKey: 'Republican', + electoralVotes: 5 + }, + CO: { + fillKey: 'Light Democrat', + electoralVotes: 5 + }, + DE: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + FL: { + fillKey: 'UNDECIDED', + electoralVotes: 29 + }, + GA: { + fillKey: 'Republican', + electoralVotes: 32 + }, + HI: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + ID: { + fillKey: 'Republican', + electoralVotes: 32 + }, + IL: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + IN: { + fillKey: 'Republican', + electoralVotes: 11 + }, + IA: { + fillKey: 'Light Democrat', + electoralVotes: 11 + }, + KS: { + fillKey: 'Republican', + electoralVotes: 32 + }, + KY: { + fillKey: 'Republican', + electoralVotes: 32 + }, + LA: { + fillKey: 'Republican', + electoralVotes: 32 + }, + MD: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + ME: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + MA: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + MN: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + MI: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + MS: { + fillKey: 'Republican', + electoralVotes: 32 + }, + MO: { + fillKey: 'Republican', + electoralVotes: 13 + }, + MT: { + fillKey: 'Republican', + electoralVotes: 32 + }, + NC: { + fillKey: 'Light Republican', + electoralVotes: 32 + }, + NE: { + fillKey: 'Republican', + electoralVotes: 32 + }, + NV: { + fillKey: 'Heavy Democrat', + electoralVotes: 32 + }, + NH: { + fillKey: 'Light Democrat', + electoralVotes: 32 + }, + NJ: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + NY: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + ND: { + fillKey: 'Republican', + electoralVotes: 32 + }, + NM: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + OH: { + fillKey: 'UNDECIDED', + electoralVotes: 32 + }, + OK: { + fillKey: 'Republican', + electoralVotes: 32 + }, + OR: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + PA: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + RI: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + SC: { + fillKey: 'Republican', + electoralVotes: 32 + }, + SD: { + fillKey: 'Republican', + electoralVotes: 32 + }, + TN: { + fillKey: 'Republican', + electoralVotes: 32 + }, + TX: { + fillKey: 'Republican', + electoralVotes: 32 + }, + UT: { + fillKey: 'Republican', + electoralVotes: 32 + }, + WI: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + VA: { + fillKey: 'Light Democrat', + electoralVotes: 32 + }, + VT: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + WA: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + WV: { + fillKey: 'Republican', + electoralVotes: 32 + }, + WY: { + fillKey: 'Republican', + electoralVotes: 32 + }, + CA: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + CT: { + fillKey: 'Democrat', + electoralVotes: 32 + }, + AK: { + fillKey: 'Republican', + electoralVotes: 32 + }, + AR: { + fillKey: 'Republican', + electoralVotes: 32 + }, + AL: { + fillKey: 'Republican', + electoralVotes: 32 + } + }} + labels + /> + + ); + } + +} diff --git a/examples/zoom-example.jsx b/examples/zoom-example.jsx new file mode 100644 index 0000000..06e65d2 --- /dev/null +++ b/examples/zoom-example.jsx @@ -0,0 +1,95 @@ +/* global d3 */ + +import React from 'react'; + +import Datamap from '../src'; +import Example from './example'; + +const colors = d3.scale.category10(); + +export default class ZoomExample extends React.Component { + + setProjection(element) { + const projection = d3.geo.equirectangular() + .center([23, -3]) + .rotate([4.4, 0]) + .scale(400) + .translate([element.offsetWidth / 2, element.offsetHeight / 2]); + const path = d3.geo.path() + .projection(projection); + + return { path, projection }; + } + + render() { + return ( + + + `
Bubble for ${data.name}` + }} + /> + + ); + } + +} From 4b6acf3ea037bf8ed79e5c6a4e9994fd1ca98b92 Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Thu, 30 Jun 2016 23:19:06 -0400 Subject: [PATCH 6/6] Add updateChoroplethOptions prop --- examples/choropleth-example.jsx | 5 +++-- src/datamap.jsx | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/choropleth-example.jsx b/examples/choropleth-example.jsx index fcec303..2f270d6 100644 --- a/examples/choropleth-example.jsx +++ b/examples/choropleth-example.jsx @@ -48,12 +48,13 @@ export default class ChoroplethExample extends React.Component { return ( ); diff --git a/src/datamap.jsx b/src/datamap.jsx index 09bd7a1..a71b2f3 100644 --- a/src/datamap.jsx +++ b/src/datamap.jsx @@ -13,6 +13,7 @@ export default class Datamap extends React.Component { height: React.PropTypes.any, labels: React.PropTypes.bool, style: React.PropTypes.object, + updateChoroplethOptions: React.PropTypes.object, width: React.PropTypes.any }; @@ -56,6 +57,7 @@ export default class Datamap extends React.Component { data, graticule, labels, + updateChoroplethOptions, ...props } = this.props; @@ -68,7 +70,7 @@ export default class Datamap extends React.Component { element: this.refs.container }); } else { - map.updateChoropleth(data); + map.updateChoropleth(data, updateChoroplethOptions); } if (arc) {