react-simple-maps
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -13,12 +13,4 @@ "use strict"; | ||
var _defaultStyles = require("./defaultStyles"); | ||
var _topojsonClient = require("topojson-client"); | ||
var _defaultStyles2 = _interopRequireDefault(_defaultStyles); | ||
var _utils = require("./utils"); | ||
var _Geography = require("./Geography"); | ||
var _Geography2 = _interopRequireDefault(_Geography); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -32,45 +24,86 @@ | ||
var Geographies = function (_React$Component) { | ||
_inherits(Geographies, _React$Component); | ||
var Geographies = function (_Component) { | ||
_inherits(Geographies, _Component); | ||
function Geographies() { | ||
function Geographies(props) { | ||
_classCallCheck(this, Geographies); | ||
return _possibleConstructorReturn(this, (Geographies.__proto__ || Object.getPrototypeOf(Geographies)).apply(this, arguments)); | ||
var _this = _possibleConstructorReturn(this, (Geographies.__proto__ || Object.getPrototypeOf(Geographies)).call(this, props)); | ||
_this.state = { | ||
geographyPaths: props.geographyPaths | ||
}; | ||
_this.fetchGeographies = _this.fetchGeographies.bind(_this); | ||
return _this; | ||
} | ||
_createClass(Geographies, [{ | ||
key: "fetchGeographies", | ||
value: function fetchGeographies(geographyUrl) { | ||
var _this2 = this; | ||
var _props = this.props, | ||
width = _props.width, | ||
height = _props.height; | ||
if (!geographyUrl) return; | ||
var request = new XMLHttpRequest(); | ||
request.open("GET", geographyUrl, true); | ||
request.onload = function () { | ||
if (request.status >= 200 && request.status < 400) { | ||
var geographyPaths = JSON.parse(request.responseText); | ||
_this2.setState({ | ||
geographyPaths: (0, _topojsonClient.feature)(geographyPaths, geographyPaths.objects[Object.keys(geographyPaths.objects)[0]]).features | ||
}, function () { | ||
if (!_this2.props.onGeographiesLoaded) return; | ||
_this2.props.onGeographyPathsLoaded(String(request.status)); | ||
}); | ||
} else { | ||
if (!_this2.props.onGeographiesLoaded) return; | ||
_this2.props.onGeographyPathsLoaded(String(request.status)); | ||
} | ||
}; | ||
request.onerror = function () { | ||
console.log("There was a connection error..."); | ||
}; | ||
request.send(); | ||
} | ||
}, { | ||
key: "componentWillReceiveProps", | ||
value: function componentWillReceiveProps(nextProps) { | ||
; | ||
if (!nextProps.geographyUrl) return; | ||
if (nextProps.geographyUrl !== this.props.geographyUrl) { | ||
this.fetchGeographies(nextProps.geographyUrl); | ||
} | ||
} | ||
}, { | ||
key: "shouldComponentUpdate", | ||
value: function shouldComponentUpdate(nextProps) { | ||
var geoPathsChanged = nextProps.geographyPaths.length !== this.props.geographyPaths.length; | ||
var includesChanged = nextProps.include.length !== this.props.include.length; | ||
var excludesChanged = nextProps.exclude.length !== this.props.exclude.length; | ||
value: function shouldComponentUpdate(nextProps, nextState) { | ||
var geoPathsChanged = nextState.geographyPaths.length !== this.state.geographyPaths.length; | ||
var choroplethChanged = JSON.stringify(nextProps.choropleth) !== JSON.stringify(this.props.choropleth); | ||
return !this.props.freezeGeographyPaths || geoPathsChanged || includesChanged || excludesChanged || choroplethChanged; | ||
return geoPathsChanged || choroplethChanged || nextProps.disableOptimization; | ||
} | ||
}, { | ||
key: "componentDidMount", | ||
value: function componentDidMount() { | ||
this.fetchGeographies(this.props.geographyUrl); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var _this2 = this; | ||
var _props2 = this.props, | ||
projection = _props2.projection, | ||
style = _props2.style, | ||
children = _props2.children; | ||
var styles = this.props.styles.geography || _defaultStyles2.default.geography; | ||
return _react2.default.createElement( | ||
"g", | ||
{ className: "rsm-geographies" }, | ||
this.props.geographyPaths.map(function (geography, i) { | ||
var included = _this2.props.include.indexOf(geography.id) !== -1; | ||
var notExcluded = _this2.props.exclude.indexOf(geography.id) === -1; | ||
var shouldInclude = _this2.props.include.length > 0 ? included : notExcluded; | ||
return shouldInclude ? _react2.default.createElement(_Geography2.default, { | ||
zoom: _this2.props.zoom, | ||
key: geography.id ? geography.id + "-" + i : i, | ||
geography: geography, | ||
projection: _this2.props.projection, | ||
choroplethValue: _this2.props.choropleth[geography.id], | ||
styles: styles, | ||
events: _this2.props.events | ||
}) : null; | ||
}) | ||
{ className: "rsm-geographies", style: style }, | ||
children(this.state.geographyPaths, projection) | ||
); | ||
@@ -81,25 +114,10 @@ } | ||
return Geographies; | ||
}(_react2.default.Component); | ||
}(_react.Component); | ||
Geographies.propTypes = { | ||
geographyPaths: _react.PropTypes.array, | ||
projection: _react.PropTypes.func.isRequired, | ||
freezeGeographyPaths: _react.PropTypes.bool, | ||
exclude: _react.PropTypes.array, | ||
include: _react.PropTypes.array, | ||
styles: _react.PropTypes.object, | ||
choropleth: _react.PropTypes.object, | ||
events: _react.PropTypes.object | ||
}; | ||
Geographies.defaultProps = { | ||
geographyPaths: [], | ||
freezeGeographyPaths: true, | ||
exclude: [], | ||
include: [], | ||
styles: _defaultStyles2.default, | ||
choropleth: {}, | ||
events: {} | ||
componentIdentifier: "Geographies", | ||
disableOptimization: false, | ||
geographyPaths: [] | ||
}; | ||
exports.default = Geographies; |
@@ -15,6 +15,2 @@ "use strict"; | ||
var _defaultStyles = require("./defaultStyles"); | ||
var _defaultStyles2 = _interopRequireDefault(_defaultStyles); | ||
var _utils = require("./utils"); | ||
@@ -30,4 +26,4 @@ | ||
var Geography = function (_React$Component) { | ||
_inherits(Geography, _React$Component); | ||
var Geography = function (_Component) { | ||
_inherits(Geography, _Component); | ||
@@ -40,9 +36,14 @@ function Geography() { | ||
_this.state = { | ||
hovered: false | ||
hover: false, | ||
pressed: false | ||
}; | ||
_this.handleMouseEnter = _this.handleMouseEnter.bind(_this); | ||
_this.handleMouseMove = _this.handleMouseMove.bind(_this); | ||
_this.handleMouseLeave = _this.handleMouseLeave.bind(_this); | ||
_this.handleMouseMove = _this.handleMouseMove.bind(_this); | ||
_this.handleClick = _this.handleClick.bind(_this); | ||
_this.handleMouseDown = _this.handleMouseDown.bind(_this); | ||
_this.handleMouseUp = _this.handleMouseUp.bind(_this); | ||
_this.handleMouseClick = _this.handleMouseClick.bind(_this); | ||
_this.handleFocus = _this.handleFocus.bind(_this); | ||
_this.handleBlur = _this.handleBlur.bind(_this); | ||
return _this; | ||
@@ -52,71 +53,142 @@ } | ||
_createClass(Geography, [{ | ||
key: "handleMouseClick", | ||
value: function handleMouseClick(evt) { | ||
evt.persist(); | ||
var _props = this.props, | ||
onClick = _props.onClick, | ||
geography = _props.geography; | ||
return onClick && onClick(geography, evt); | ||
} | ||
}, { | ||
key: "handleMouseEnter", | ||
value: function handleMouseEnter(geography, evt) { | ||
evt.preventDefault(); | ||
this.setState({ hovered: true }); | ||
if (this.props.events.onMouseEnter) { | ||
this.props.events.onMouseEnter(geography, evt); | ||
} | ||
value: function handleMouseEnter(evt) { | ||
evt.persist(); | ||
var _props2 = this.props, | ||
onMouseEnter = _props2.onMouseEnter, | ||
geography = _props2.geography; | ||
this.setState({ | ||
hover: true | ||
}, function () { | ||
return onMouseEnter && onMouseEnter(geography, evt); | ||
}); | ||
} | ||
}, { | ||
key: "handleMouseMove", | ||
value: function handleMouseMove(evt) { | ||
evt.persist(); | ||
if (this.state.pressed) return; | ||
var _props3 = this.props, | ||
onMouseMove = _props3.onMouseMove, | ||
geography = _props3.geography; | ||
if (!this.state.hover) { | ||
this.setState({ | ||
hover: true | ||
}, function () { | ||
return onMouseMove && onMouseMove(geography, evt); | ||
}); | ||
} else if (onMouseMove) onMouseMove(geography, evt);else return; | ||
} | ||
}, { | ||
key: "handleMouseLeave", | ||
value: function handleMouseLeave(geography, evt) { | ||
evt.preventDefault(); | ||
this.setState({ hovered: false }); | ||
if (this.props.events.onMouseLeave) { | ||
this.props.events.onMouseLeave(geography, evt); | ||
} | ||
value: function handleMouseLeave(evt) { | ||
evt.persist(); | ||
var _props4 = this.props, | ||
onMouseLeave = _props4.onMouseLeave, | ||
geography = _props4.geography; | ||
this.setState({ | ||
hover: false | ||
}, function () { | ||
return onMouseLeave && onMouseLeave(geography, evt); | ||
}); | ||
} | ||
}, { | ||
key: "handleMouseMove", | ||
value: function handleMouseMove(geography, evt) { | ||
evt.preventDefault(); | ||
if (this.props.events.onMouseMove) { | ||
this.props.events.onMouseMove(geography, evt); | ||
} | ||
key: "handleMouseDown", | ||
value: function handleMouseDown(evt) { | ||
evt.persist(); | ||
var _props5 = this.props, | ||
onMouseDown = _props5.onMouseDown, | ||
geography = _props5.geography; | ||
this.setState({ | ||
pressed: true | ||
}, function () { | ||
return onMouseDown && onMouseDown(geography, evt); | ||
}); | ||
} | ||
}, { | ||
key: "handleClick", | ||
value: function handleClick(geography, evt) { | ||
evt.preventDefault(); | ||
if (this.props.events.onClick) { | ||
this.props.events.onClick(geography, evt); | ||
} | ||
key: "handleMouseUp", | ||
value: function handleMouseUp(evt) { | ||
evt.persist(); | ||
var _props6 = this.props, | ||
onMouseUp = _props6.onMouseUp, | ||
geography = _props6.geography; | ||
this.setState({ | ||
pressed: false | ||
}, function () { | ||
return onMouseUp && onMouseUp(geography, evt); | ||
}); | ||
} | ||
}, { | ||
key: "shouldComponentUpdate", | ||
value: function shouldComponentUpdate(nextProps, nextState) { | ||
var changedHoverState = this.state.hovered !== nextState.hovered; | ||
var changedChoroplethValue = this.props.choroplethValue !== nextProps.choroplethValue; | ||
var changedGeography = this.props.geography !== nextProps.geography; | ||
return changedGeography || changedChoroplethValue || changedHoverState; | ||
key: "handleFocus", | ||
value: function handleFocus(evt) { | ||
evt.persist(); | ||
var _props7 = this.props, | ||
onFocus = _props7.onFocus, | ||
geography = _props7.geography; | ||
this.setState({ | ||
hover: true | ||
}, function () { | ||
return onFocus && onFocus(geography, evt); | ||
}); | ||
} | ||
}, { | ||
key: "handleBlur", | ||
value: function handleBlur(evt) { | ||
evt.persist(); | ||
var _props8 = this.props, | ||
onBlur = _props8.onBlur, | ||
geography = _props8.geography; | ||
this.setState({ | ||
hover: false | ||
}, function () { | ||
return onBlur && onBlur(geography, evt); | ||
}); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var _this2 = this; | ||
var _props9 = this.props, | ||
geography = _props9.geography, | ||
projection = _props9.projection, | ||
round = _props9.round, | ||
precision = _props9.precision, | ||
tabable = _props9.tabable, | ||
style = _props9.style; | ||
var _state = this.state, | ||
hover = _state.hover, | ||
pressed = _state.pressed; | ||
var _props = this.props, | ||
geography = _props.geography, | ||
projection = _props.projection, | ||
styles = _props.styles, | ||
choroplethValue = _props.choroplethValue; | ||
var pathString = (0, _d3Geo.geoPath)().projection(projection())(geography); | ||
return _react2.default.createElement("path", { | ||
d: (0, _d3Geo.geoPath)().projection(projection())(geography), | ||
onMouseEnter: function onMouseEnter(evt) { | ||
return _this2.handleMouseEnter(geography, evt); | ||
}, | ||
onMouseLeave: function onMouseLeave(evt) { | ||
return _this2.handleMouseLeave(geography, evt); | ||
}, | ||
onMouseMove: function onMouseMove(evt) { | ||
return _this2.handleMouseMove(geography, evt); | ||
}, | ||
onClick: function onClick(evt) { | ||
return _this2.handleClick(geography, evt); | ||
}, | ||
style: styles(choroplethValue, geography)[this.state.hovered ? "hover" : "default"] || styles(choroplethValue, geography)["default"], | ||
className: "rsm-geography" | ||
d: round ? (0, _utils.roundPath)(pathString, precision) : pathString, | ||
className: "rsm-geography" + (pressed && " rsm-geography--pressed") + (hover && " rsm-geography--hover"), | ||
style: style[pressed || hover ? pressed ? "pressed" : "hover" : "default"], | ||
onClick: this.handleMouseClick, | ||
onMouseEnter: this.handleMouseEnter, | ||
onMouseMove: this.handleMouseMove, | ||
onMouseLeave: this.handleMouseLeave, | ||
onMouseDown: this.handleMouseDown, | ||
onMouseUp: this.handleMouseUp, | ||
onFocus: tabable && this.handleFocus, | ||
onBlur: tabable && this.handleBlur, | ||
tabIndex: tabable ? 0 : -1 | ||
}); | ||
@@ -127,16 +199,15 @@ } | ||
return Geography; | ||
}(_react2.default.Component); | ||
}(_react.Component); | ||
Geography.propTypes = { | ||
geography: _react.PropTypes.object.isRequired, | ||
projection: _react.PropTypes.func.isRequired, | ||
choroplethValue: _react.PropTypes.object, | ||
events: _react.PropTypes.object | ||
}; | ||
Geography.defaultProps = { | ||
styles: _defaultStyles2.default.geography, | ||
events: {} | ||
precision: 0.1, | ||
round: true, | ||
tabable: true, | ||
style: { | ||
default: {}, | ||
hover: {}, | ||
pressed: {} | ||
} | ||
}; | ||
exports.default = Geography; |
392
lib/index.js
@@ -7,365 +7,65 @@ "use strict"; | ||
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; }; }(); | ||
var _ComposableMap = require("./ComposableMap"); | ||
var _react = require("react"); | ||
Object.defineProperty(exports, "ComposableMap", { | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_ComposableMap).default; | ||
} | ||
}); | ||
var _react2 = _interopRequireDefault(_react); | ||
var _topojsonClient = require("topojson-client"); | ||
var _Loader = require("./Loader"); | ||
var _Loader2 = _interopRequireDefault(_Loader); | ||
var _MapControls = require("./MapControls"); | ||
var _MapControls2 = _interopRequireDefault(_MapControls); | ||
var _ZoomableGroup = require("./ZoomableGroup"); | ||
var _ZoomableGroup2 = _interopRequireDefault(_ZoomableGroup); | ||
Object.defineProperty(exports, "ZoomableGroup", { | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_ZoomableGroup).default; | ||
} | ||
}); | ||
var _Geographies = require("./Geographies"); | ||
var _Geographies2 = _interopRequireDefault(_Geographies); | ||
Object.defineProperty(exports, "Geographies", { | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_Geographies).default; | ||
} | ||
}); | ||
var _Markers = require("./Markers"); | ||
var _Geography = require("./Geography"); | ||
var _Markers2 = _interopRequireDefault(_Markers); | ||
Object.defineProperty(exports, "Geography", { | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_Geography).default; | ||
} | ||
}); | ||
var _projections = require("./projections"); | ||
var _Marker = require("./Marker"); | ||
var _projections2 = _interopRequireDefault(_projections); | ||
Object.defineProperty(exports, "Marker", { | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_Marker).default; | ||
} | ||
}); | ||
var _projectionConfig = require("./projectionConfig"); | ||
var _Markers = require("./Markers"); | ||
var _projectionConfig2 = _interopRequireDefault(_projectionConfig); | ||
var _defaultStyles = require("./defaultStyles"); | ||
var _defaultStyles2 = _interopRequireDefault(_defaultStyles); | ||
var _utils = require("./utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
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; } | ||
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; } | ||
var ReactSimpleMap = function (_React$Component) { | ||
_inherits(ReactSimpleMap, _React$Component); | ||
function ReactSimpleMap(props) { | ||
_classCallCheck(this, ReactSimpleMap); | ||
var _this = _possibleConstructorReturn(this, (ReactSimpleMap.__proto__ || Object.getPrototypeOf(ReactSimpleMap)).call(this, props)); | ||
_this.state = { | ||
geographyPaths: props.geographyPaths, | ||
zoom: props.zoom, | ||
mouseX: (0, _utils.calculateMousePosition)("x", _this.projection.bind(_this), props, props.zoom, 1), | ||
mouseY: (0, _utils.calculateMousePosition)("y", _this.projection.bind(_this), props, props.zoom, 1), | ||
mouseXStart: 0, | ||
mouseYStart: 0, | ||
isPressed: false, | ||
loadingError: null, | ||
resizeFactorX: 1, | ||
resizeFactorY: 1 | ||
}; | ||
_this.projection = _this.projection.bind(_this); | ||
_this.fetchGeographies = _this.fetchGeographies.bind(_this); | ||
_this.handleZoomReset = _this.handleZoomReset.bind(_this); | ||
_this.handleZoomIn = _this.handleZoomIn.bind(_this); | ||
_this.handleZoomOut = _this.handleZoomOut.bind(_this); | ||
_this.handleMouseDown = _this.handleMouseDown.bind(_this); | ||
_this.handleMouseMove = _this.handleMouseMove.bind(_this); | ||
_this.handleMouseUp = _this.handleMouseUp.bind(_this); | ||
return _this; | ||
Object.defineProperty(exports, "Markers", { | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_Markers).default; | ||
} | ||
}); | ||
_createClass(ReactSimpleMap, [{ | ||
key: "componentWillReceiveProps", | ||
value: function componentWillReceiveProps(nextProps) { | ||
if (nextProps.zoom !== this.state.zoom || nextProps.center !== this.props.center) { | ||
this.setState({ | ||
zoom: nextProps.zoom, | ||
mouseX: (0, _utils.calculateMousePosition)("x", this.projection, nextProps, nextProps.zoom, this.state.resizeFactorX), | ||
mouseY: (0, _utils.calculateMousePosition)("y", this.projection, nextProps, nextProps.zoom, this.state.resizeFactorY) | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "fetchGeographies", | ||
value: function fetchGeographies(geographyUrl) { | ||
var _this2 = this; | ||
var _Annotation = require("./Annotation"); | ||
if (!this.props.geographyUrl) return; | ||
var request = new XMLHttpRequest(); | ||
request.open("GET", geographyUrl, true); | ||
request.onload = function () { | ||
if (request.status >= 200 && request.status < 400) { | ||
var geographyPaths = JSON.parse(request.responseText); | ||
_this2.setState({ | ||
geographyPaths: (0, _topojsonClient.feature)(geographyPaths, geographyPaths.objects[Object.keys(geographyPaths.objects)[0]]).features | ||
}); | ||
} else { | ||
_this2.setState({ | ||
loadingError: String(request.status) | ||
}); | ||
} | ||
}; | ||
request.onerror = function () { | ||
console.log("There was a connection error..."); | ||
}; | ||
request.send(); | ||
} | ||
}, { | ||
key: "projection", | ||
value: function projection() { | ||
if (typeof this.props.projection !== "function") { | ||
return (0, _projections2.default)(this.props.width, this.props.height, this.props.projectionConfig, this.props.projection); | ||
} else { | ||
return this.props.projection(this.props.width, this.props.height, this.props.projectionConfig); | ||
} | ||
} | ||
}, { | ||
key: "handleZoomIn", | ||
value: function handleZoomIn() { | ||
if (this.state.zoom < this.props.maxZoom) { | ||
this.setState({ | ||
zoom: this.state.zoom * 2, | ||
mouseX: this.state.mouseX * 2, | ||
mouseY: this.state.mouseY * 2 | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "handleZoomOut", | ||
value: function handleZoomOut() { | ||
if (this.state.zoom > this.props.minZoom) { | ||
this.setState({ | ||
zoom: this.state.zoom / 2, | ||
mouseX: this.state.zoom === 2 ? (0, _utils.calculateMousePosition)("x", this.projection, this.props, this.props.minZoom, this.state.resizeFactorX) : this.state.mouseX / 2, | ||
mouseY: this.state.zoom === 2 ? (0, _utils.calculateMousePosition)("y", this.projection, this.props, this.props.minZoom, this.state.resizeFactorY) : this.state.mouseY / 2 | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "handleZoomReset", | ||
value: function handleZoomReset() { | ||
if (this.state.zoom > this.props.minZoom) { | ||
this.setState({ | ||
zoom: this.props.minZoom, | ||
mouseX: (0, _utils.calculateMousePosition)("x", this.projection, this.props, this.props.minZoom, this.state.resizeFactorX), | ||
mouseY: (0, _utils.calculateMousePosition)("y", this.projection, this.props, this.props.minZoom, this.state.resizeFactorY) | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "handleMouseDown", | ||
value: function handleMouseDown(_ref) { | ||
var pageX = _ref.pageX, | ||
pageY = _ref.pageY; | ||
this.setState({ | ||
isPressed: true, | ||
mouseXStart: pageX - this.state.mouseX, | ||
mouseYStart: pageY - this.state.mouseY | ||
}); | ||
} | ||
}, { | ||
key: "handleMouseMove", | ||
value: function handleMouseMove(_ref2) { | ||
var pageX = _ref2.pageX, | ||
pageY = _ref2.pageY; | ||
if (this.state.isPressed) { | ||
this.setState({ | ||
mouseX: pageX - this.state.mouseXStart, | ||
mouseY: pageY - this.state.mouseYStart | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "handleMouseUp", | ||
value: function handleMouseUp(_ref3) { | ||
var pageX = _ref3.pageX, | ||
pageY = _ref3.pageY; | ||
this.setState({ | ||
isPressed: false | ||
}); | ||
} | ||
}, { | ||
key: "componentDidMount", | ||
value: function componentDidMount() { | ||
var _this3 = this; | ||
this.fetchGeographies(this.props.geographyUrl); | ||
var actualWidth = this.wrapperNode.clientWidth; | ||
var actualHeight = this.wrapperNode.clientHeight; | ||
var resizeFactorX = (0, _utils.calculateResizeFactor)(actualWidth, this.props.width); | ||
var resizeFactorY = (0, _utils.calculateResizeFactor)(actualHeight, this.props.height); | ||
this.setState({ | ||
resizeFactorX: resizeFactorX, | ||
resizeFactorY: resizeFactorY, | ||
mouseX: (0, _utils.calculateMousePosition)("x", this.projection, this.props, this.state.zoom, resizeFactorX), | ||
mouseY: (0, _utils.calculateMousePosition)("y", this.projection, this.props, this.state.zoom, resizeFactorY) | ||
}); | ||
window.addEventListener("resize", function () { | ||
var actualWidth = _this3.wrapperNode.clientWidth; | ||
var actualHeight = _this3.wrapperNode.clientHeight; | ||
var resizeFactorX = (0, _utils.calculateResizeFactor)(actualWidth, _this3.props.width); | ||
var resizeFactorY = (0, _utils.calculateResizeFactor)(actualHeight, _this3.props.height); | ||
_this3.setState({ | ||
resizeFactorX: resizeFactorX, | ||
resizeFactorY: resizeFactorY, | ||
mouseX: (0, _utils.calculateMousePosition)("x", _this3.projection, _this3.props, _this3.state.zoom, resizeFactorX), | ||
mouseY: (0, _utils.calculateMousePosition)("y", _this3.projection, _this3.props, _this3.state.zoom, resizeFactorY) | ||
}); | ||
}); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var _this4 = this; | ||
var _props = this.props, | ||
width = _props.width, | ||
height = _props.height, | ||
styles = _props.styles, | ||
freezeGeographyPaths = _props.freezeGeographyPaths, | ||
markers = _props.markers, | ||
exclude = _props.exclude, | ||
include = _props.include, | ||
center = _props.center; | ||
var _state = this.state, | ||
geographyPaths = _state.geographyPaths, | ||
loadingError = _state.loadingError; | ||
return _react2.default.createElement( | ||
"div", | ||
{ style: styles.wrapper ? styles.wrapper() : _defaultStyles2.default.wrapper(), className: "rsm-wrapper" }, | ||
this.props.showControls ? _react2.default.createElement(_MapControls2.default, { | ||
handleZoomIn: this.handleZoomIn, | ||
handleZoomOut: this.handleZoomOut, | ||
handleZoomReset: this.handleZoomReset | ||
}) : null, | ||
geographyPaths.length === 0 ? _react2.default.createElement(_Loader2.default, { | ||
styles: styles.loader || _defaultStyles2.default.loader, | ||
loadingError: loadingError | ||
}) : null, | ||
_react2.default.createElement( | ||
"svg", | ||
{ width: width, | ||
height: height, | ||
viewBox: "0 0 " + width + " " + height, | ||
style: styles.svg ? styles.svg() : _defaultStyles2.default.svg(), | ||
className: "rsm-svg", | ||
ref: function ref(wrapperNode) { | ||
return _this4.wrapperNode = wrapperNode; | ||
} | ||
}, | ||
_react2.default.createElement( | ||
_ZoomableGroup2.default, | ||
{ | ||
zoom: this.state.zoom, | ||
mouseX: this.state.mouseX, | ||
mouseY: this.state.mouseY, | ||
width: width, | ||
height: height, | ||
isPressed: this.state.isPressed, | ||
handleMouseMove: this.handleMouseMove, | ||
handleMouseUp: this.handleMouseUp, | ||
handleMouseDown: this.handleMouseDown, | ||
styles: styles, | ||
center: [-center[0], -center[1]], | ||
projection: this.projection, | ||
resizeFactorX: this.state.resizeFactorX, | ||
resizeFactorY: this.state.resizeFactorY | ||
}, | ||
geographyPaths.length > 0 ? _react2.default.createElement(_Geographies2.default, { | ||
zoom: this.state.zoom, | ||
projection: this.projection, | ||
geographyPaths: geographyPaths, | ||
freezeGeographyPaths: freezeGeographyPaths, | ||
exclude: exclude, | ||
include: include, | ||
styles: styles, | ||
choropleth: this.props.choropleth, | ||
events: this.props.events.geography | ||
}) : null, | ||
geographyPaths.length > 0 ? _react2.default.createElement(_Markers2.default, { | ||
projection: this.projection, | ||
markers: markers, | ||
zoom: this.state.zoom, | ||
styles: styles, | ||
events: this.props.events.marker | ||
}) : null | ||
) | ||
) | ||
); | ||
} | ||
}]); | ||
return ReactSimpleMap; | ||
}(_react2.default.Component); | ||
ReactSimpleMap.propTypes = { | ||
width: _react.PropTypes.number, | ||
height: _react.PropTypes.number, | ||
geographyUrl: _react.PropTypes.string, | ||
geographyPaths: _react.PropTypes.array, | ||
projection: _react.PropTypes.oneOfType([_react.PropTypes.string, _react.PropTypes.func]), | ||
freezeGeographyPaths: _react.PropTypes.bool, | ||
styles: _react.PropTypes.object, | ||
markers: _react.PropTypes.array, | ||
exclude: _react.PropTypes.array, | ||
include: _react.PropTypes.array, | ||
zoom: _react.PropTypes.number, | ||
minZoom: _react.PropTypes.number, | ||
maxZoom: _react.PropTypes.number, | ||
center: _react.PropTypes.array, | ||
projectionConfig: _react.PropTypes.object, | ||
choropleth: _react.PropTypes.object, | ||
showControls: _react.PropTypes.bool | ||
}; | ||
ReactSimpleMap.defaultProps = { | ||
width: 800, | ||
height: 450, | ||
geographyUrl: null, | ||
geographyPaths: [], | ||
projection: "times", | ||
choropleth: {}, | ||
freezeGeographyPaths: true, | ||
styles: _defaultStyles2.default, | ||
markers: [], | ||
exclude: [], | ||
include: [], | ||
zoom: 1, | ||
minZoom: 1, | ||
maxZoom: 8, | ||
center: [0, 0], | ||
projectionConfig: _projectionConfig2.default, | ||
showControls: false, | ||
events: { | ||
geography: {}, | ||
marker: {} | ||
Object.defineProperty(exports, "Annotation", { | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_Annotation).default; | ||
} | ||
}; | ||
}); | ||
exports.default = ReactSimpleMap; | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
@@ -13,6 +13,2 @@ "use strict"; | ||
var _defaultStyles = require("./defaultStyles"); | ||
var _defaultStyles2 = _interopRequireDefault(_defaultStyles); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -35,3 +31,4 @@ | ||
_this.state = { | ||
hovered: false | ||
hover: false, | ||
pressed: false | ||
}; | ||
@@ -41,4 +38,8 @@ | ||
_this.handleMouseLeave = _this.handleMouseLeave.bind(_this); | ||
_this.handleMouseDown = _this.handleMouseDown.bind(_this); | ||
_this.handleMouseUp = _this.handleMouseUp.bind(_this); | ||
_this.handleMouseClick = _this.handleMouseClick.bind(_this); | ||
_this.handleMouseMove = _this.handleMouseMove.bind(_this); | ||
_this.handleClick = _this.handleClick.bind(_this); | ||
_this.handleFocus = _this.handleFocus.bind(_this); | ||
_this.handleBlur = _this.handleBlur.bind(_this); | ||
return _this; | ||
@@ -49,74 +50,144 @@ } | ||
key: "handleMouseEnter", | ||
value: function handleMouseEnter(marker, evt) { | ||
evt.preventDefault(); | ||
this.setState({ hovered: true }); | ||
if (this.props.events.onMouseEnter) { | ||
this.props.events.onMouseEnter(marker, evt); | ||
} | ||
value: function handleMouseEnter(evt) { | ||
evt.persist(); | ||
var _props = this.props, | ||
onMouseEnter = _props.onMouseEnter, | ||
marker = _props.marker; | ||
this.setState({ | ||
hover: true | ||
}, function () { | ||
return onMouseEnter && onMouseEnter(marker, evt); | ||
}); | ||
} | ||
}, { | ||
key: "handleMouseMove", | ||
value: function handleMouseMove(evt) { | ||
evt.persist(); | ||
if (this.state.pressed) return; | ||
var _props2 = this.props, | ||
onMouseMove = _props2.onMouseMove, | ||
marker = _props2.marker; | ||
if (!this.state.hover) { | ||
this.setState({ | ||
hover: true | ||
}, function () { | ||
return onMouseMove && onMouseMove(marker, evt); | ||
}); | ||
} else if (onMouseMove) onMouseMove(marker, evt);else return; | ||
} | ||
}, { | ||
key: "handleMouseLeave", | ||
value: function handleMouseLeave(marker, evt) { | ||
evt.preventDefault(); | ||
this.setState({ hovered: false }); | ||
if (this.props.events.onMouseLeave) { | ||
this.props.events.onMouseLeave(marker, evt); | ||
} | ||
value: function handleMouseLeave(evt) { | ||
evt.persist(); | ||
var _props3 = this.props, | ||
onMouseLeave = _props3.onMouseLeave, | ||
marker = _props3.marker; | ||
this.setState({ | ||
hover: false | ||
}, function () { | ||
return onMouseLeave && onMouseLeave(marker, evt); | ||
}); | ||
} | ||
}, { | ||
key: "handleMouseMove", | ||
value: function handleMouseMove(marker, evt) { | ||
evt.preventDefault(); | ||
if (this.props.events.onMouseMove) { | ||
this.props.events.onMouseMove(marker, evt); | ||
} | ||
key: "handleMouseDown", | ||
value: function handleMouseDown(evt) { | ||
evt.persist(); | ||
var _props4 = this.props, | ||
onMouseDown = _props4.onMouseDown, | ||
marker = _props4.marker; | ||
this.setState({ | ||
pressed: true | ||
}, function () { | ||
return onMouseDown && onMouseDown(marker, evt); | ||
}); | ||
} | ||
}, { | ||
key: "handleClick", | ||
value: function handleClick(marker, evt) { | ||
evt.preventDefault(); | ||
if (this.props.events.onClick) { | ||
this.props.events.onClick(marker, evt); | ||
} | ||
key: "handleMouseUp", | ||
value: function handleMouseUp(evt) { | ||
evt.persist(); | ||
var _props5 = this.props, | ||
onMouseUp = _props5.onMouseUp, | ||
marker = _props5.marker; | ||
this.setState({ | ||
pressed: false | ||
}, function () { | ||
return onMouseUp && onMouseUp(marker, evt); | ||
}); | ||
} | ||
}, { | ||
key: "shouldComponentUpdate", | ||
value: function shouldComponentUpdate(nextProps, nextState) { | ||
var hoverStateChanged = nextState.hovered !== this.state.hovered; | ||
var radiusChanged = nextProps.marker.radius !== this.props.marker.radius; | ||
var zoomChanged = nextProps.zoom !== this.props.zoom; | ||
return hoverStateChanged || radiusChanged || zoomChanged; | ||
key: "handleMouseClick", | ||
value: function handleMouseClick(evt) { | ||
if (!this.props.onClick) return; | ||
evt.persist(); | ||
var _props6 = this.props, | ||
onClick = _props6.onClick, | ||
marker = _props6.marker, | ||
projection = _props6.projection; | ||
return onClick && onClick(marker, projection()(marker.coordinates), evt); | ||
} | ||
}, { | ||
key: "handleFocus", | ||
value: function handleFocus(evt) { | ||
evt.persist(); | ||
var _props7 = this.props, | ||
onFocus = _props7.onFocus, | ||
marker = _props7.marker; | ||
this.setState({ | ||
hover: true | ||
}, function () { | ||
return onFocus && onFocus(marker, evt); | ||
}); | ||
} | ||
}, { | ||
key: "handleBlur", | ||
value: function handleBlur(evt) { | ||
evt.persist(); | ||
var _props8 = this.props, | ||
onBlur = _props8.onBlur, | ||
marker = _props8.marker; | ||
this.setState({ | ||
hover: false | ||
}, function () { | ||
return onBlur && onBlur(marker, evt); | ||
}); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var _this2 = this; | ||
var _props9 = this.props, | ||
projection = _props9.projection, | ||
marker = _props9.marker, | ||
style = _props9.style, | ||
tabable = _props9.tabable, | ||
children = _props9.children; | ||
var _state = this.state, | ||
pressed = _state.pressed, | ||
hover = _state.hover; | ||
var _props = this.props, | ||
marker = _props.marker, | ||
styles = _props.styles, | ||
zoom = _props.zoom, | ||
projection = _props.projection; | ||
return _react2.default.createElement("circle", { | ||
cx: projection()(marker.coordinates)[0], | ||
cy: projection()(marker.coordinates)[1], | ||
r: marker.radius, | ||
style: styles(marker, zoom)[this.state.hovered ? "hover" : "default"] || styles(marker, zoom)["default"], | ||
className: "rsm-marker", | ||
onMouseEnter: function onMouseEnter(evt) { | ||
return _this2.handleMouseEnter(marker, evt); | ||
return _react2.default.createElement( | ||
"g", | ||
{ className: "rsm-marker" + (pressed && " rsm-marker--pressed") + (hover && " rsm-marker--hover"), | ||
transform: "translate(\n " + projection()(marker.coordinates)[0] + "\n " + projection()(marker.coordinates)[1] + "\n )", | ||
style: style[pressed || hover ? pressed ? "pressed" : "hover" : "default"], | ||
onMouseEnter: this.handleMouseEnter, | ||
onMouseLeave: this.handleMouseLeave, | ||
onMouseDown: this.handleMouseDown, | ||
onMouseUp: this.handleMouseUp, | ||
onClick: this.handleMouseClick, | ||
onMouseMove: this.handleMouseMove, | ||
onFocus: this.handleFocus, | ||
onBlur: this.handleBlur, | ||
tabIndex: tabable ? 0 : -1 | ||
}, | ||
onMouseLeave: function onMouseLeave(evt) { | ||
return _this2.handleMouseLeave(marker, evt); | ||
}, | ||
onMouseMove: function onMouseMove(evt) { | ||
return _this2.handleMouseMove(marker, evt); | ||
}, | ||
onClick: function onClick(evt) { | ||
return _this2.handleClick(marker, evt); | ||
}, | ||
transform: "\n translate(\n " + projection()(marker.coordinates)[0] + "\n " + projection()(marker.coordinates)[1] + "\n )\n scale(\n " + 1 / zoom + "\n )\n translate(\n " + -projection()(marker.coordinates)[0] + "\n " + -projection()(marker.coordinates)[1] + "\n )\n " | ||
}); | ||
children | ||
); | ||
} | ||
@@ -128,17 +199,11 @@ }]); | ||
Marker.propTypes = { | ||
marker: _react.PropTypes.object, | ||
zoom: _react.PropTypes.number, | ||
events: _react.PropTypes.object, | ||
projection: _react.PropTypes.func, | ||
styles: _react.PropTypes.func | ||
}; | ||
Marker.defaultProps = { | ||
marker: {}, | ||
zoom: 1, | ||
events: {}, | ||
styles: _defaultStyles2.default.marker | ||
style: { | ||
default: {}, | ||
hover: {}, | ||
pressed: {} | ||
}, | ||
tabable: true | ||
}; | ||
exports.default = Marker; |
@@ -13,10 +13,2 @@ "use strict"; | ||
var _defaultStyles = require("./defaultStyles"); | ||
var _defaultStyles2 = _interopRequireDefault(_defaultStyles); | ||
var _Marker = require("./Marker"); | ||
var _Marker2 = _interopRequireDefault(_Marker); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -30,4 +22,4 @@ | ||
var Markers = function (_React$Component) { | ||
_inherits(Markers, _React$Component); | ||
var Markers = function (_Component) { | ||
_inherits(Markers, _Component); | ||
@@ -43,15 +35,17 @@ function Markers() { | ||
value: function render() { | ||
var _this2 = this; | ||
var _props = this.props, | ||
children = _props.children, | ||
projection = _props.projection, | ||
style = _props.style; | ||
return _react2.default.createElement( | ||
"g", | ||
{ className: "rsm-markers" }, | ||
this.props.markers.map(function (marker, i) { | ||
return _react2.default.createElement(_Marker2.default, { | ||
key: Math.abs(marker.coordinates[0]) + "-" + Math.abs(marker.coordinates[1]) + "-" + i, | ||
projection: _this2.props.projection, | ||
marker: marker, | ||
styles: _this2.props.styles.marker, | ||
events: _this2.props.events, | ||
zoom: _this2.props.zoom | ||
{ className: "rsm-markers", style: style }, | ||
!children.length ? _react2.default.cloneElement(children, { | ||
projection: projection | ||
}) : children.map(function (child, i) { | ||
return _react2.default.cloneElement(child, { | ||
key: child.key || "marker-" + i, | ||
projection: projection | ||
}); | ||
@@ -64,19 +58,8 @@ }) | ||
return Markers; | ||
}(_react2.default.Component); | ||
}(_react.Component); | ||
Markers.propTypes = { | ||
markers: _react.PropTypes.array, | ||
projection: _react.PropTypes.func.isRequired, | ||
zoom: _react.PropTypes.number, | ||
events: _react.PropTypes.object, | ||
styles: _react.PropTypes.object | ||
}; | ||
Markers.defaultProps = { | ||
markers: [], | ||
zoom: 1, | ||
events: {}, | ||
styles: _defaultStyles2.default | ||
componentIdentifier: "Markers" | ||
}; | ||
exports.default = Markers; |
@@ -10,2 +10,14 @@ "use strict"; | ||
exports.calculateMousePosition = calculateMousePosition; | ||
exports.isChildOfType = isChildOfType; | ||
exports.createNewChildren = createNewChildren; | ||
exports.roundPath = roundPath; | ||
exports.createConnectorPath = createConnectorPath; | ||
exports.createTextAnchor = createTextAnchor; | ||
var _react = require("react"); | ||
var _react2 = _interopRequireDefault(_react); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function replaceStrokeWidth(styles) { | ||
@@ -36,8 +48,54 @@ var newStyles = {}; | ||
function calculateMousePosition(direction, projection, props, zoom, resizeFactor) { | ||
var center = props.center, | ||
width = props.width, | ||
height = props.height; | ||
var center = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : props.center; | ||
var width = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : props.width; | ||
var height = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : props.height; | ||
var reference = { x: 0, y: 1 }; | ||
return (projection()([-center[0], -center[1]])[reference[direction]] - (reference[direction] === 0 ? width : height) / 2) * zoom * (1 / resizeFactor); | ||
var reverseRotation = projection().rotate().map(function (item) { | ||
return -item; | ||
}); | ||
return (projection().rotate(reverseRotation)([-center[0], -center[1]])[reference[direction]] - (reference[direction] === 0 ? width : height) / 2) * zoom * (1 / resizeFactor); | ||
} | ||
function isChildOfType(child, expectedType) { | ||
return child.props.componentIdentifier === expectedType; | ||
} | ||
function createNewChildren(children, props) { | ||
if (!children.length) { | ||
return isChildOfType(children, "Geographies") ? _react2.default.cloneElement(children, { | ||
geographyPaths: props.geographyPaths, | ||
projection: props.projection | ||
}) : isChildOfType(children, "Markers") || isChildOfType(children, "Annotation") ? _react2.default.cloneElement(children, { | ||
projection: props.projection, | ||
zoom: props.zoom | ||
}) : children; | ||
} else { | ||
return children.map(function (child, i) { | ||
return isChildOfType(child, "Geographies") ? _react2.default.cloneElement(child, { | ||
key: "zoomable-child-" + i, | ||
geographyPaths: props.geographyPaths, | ||
projection: props.projection | ||
}) : isChildOfType(child, "Markers") || isChildOfType(child, "Annotation") ? _react2.default.cloneElement(child, { | ||
key: "zoomable-child-" + i, | ||
projection: props.projection, | ||
zoom: props.zoom | ||
}) : child; | ||
}); | ||
} | ||
} | ||
function roundPath(path, precision) { | ||
var query = /[\d\.-][\d\.e-]*/g; | ||
return path.replace(query, function (n) { | ||
return Math.round(n * (1 / precision)) / (1 / precision); | ||
}); | ||
} | ||
function createConnectorPath(connectorType, endPoint) { | ||
return "M0,0 L" + endPoint[0] + "," + endPoint[1]; | ||
} | ||
function createTextAnchor(dx) { | ||
if (dx > 0) return "start";else if (dx < 0) return "end";else return "middle"; | ||
} |
@@ -13,6 +13,4 @@ "use strict"; | ||
var _defaultStyles = require("./defaultStyles"); | ||
var _utils = require("./utils"); | ||
var _defaultStyles2 = _interopRequireDefault(_defaultStyles); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -26,36 +24,181 @@ | ||
var ZoomableGroup = function (_React$Component) { | ||
_inherits(ZoomableGroup, _React$Component); | ||
var ZoomableGroup = function (_Component) { | ||
_inherits(ZoomableGroup, _Component); | ||
function ZoomableGroup() { | ||
function ZoomableGroup(props) { | ||
_classCallCheck(this, ZoomableGroup); | ||
return _possibleConstructorReturn(this, (ZoomableGroup.__proto__ || Object.getPrototypeOf(ZoomableGroup)).apply(this, arguments)); | ||
var _this = _possibleConstructorReturn(this, (ZoomableGroup.__proto__ || Object.getPrototypeOf(ZoomableGroup)).call(this, props)); | ||
_this.state = { | ||
mouseX: (0, _utils.calculateMousePosition)("x", props.projection, props, props.zoom, 1), | ||
mouseY: (0, _utils.calculateMousePosition)("y", props.projection, props, props.zoom, 1), | ||
mouseXStart: 0, | ||
mouseYStart: 0, | ||
isPressed: false, | ||
resizeFactorX: 1, | ||
resizeFactorY: 1 | ||
}; | ||
_this.handleMouseMove = _this.handleMouseMove.bind(_this); | ||
_this.handleMouseUp = _this.handleMouseUp.bind(_this); | ||
_this.handleMouseDown = _this.handleMouseDown.bind(_this); | ||
_this.handleResize = _this.handleResize.bind(_this); | ||
return _this; | ||
} | ||
_createClass(ZoomableGroup, [{ | ||
key: "handleMouseMove", | ||
value: function handleMouseMove(_ref) { | ||
var pageX = _ref.pageX, | ||
pageY = _ref.pageY; | ||
if (this.props.disablePanning) return; | ||
if (this.state.isPressed) { | ||
this.setState({ | ||
mouseX: pageX - this.state.mouseXStart, | ||
mouseY: pageY - this.state.mouseYStart | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "handleMouseUp", | ||
value: function handleMouseUp(_ref2) { | ||
var pageX = _ref2.pageX, | ||
pageY = _ref2.pageY; | ||
if (this.props.disablePanning) return; | ||
this.setState({ | ||
isPressed: false | ||
}); | ||
} | ||
}, { | ||
key: "handleMouseDown", | ||
value: function handleMouseDown(_ref3) { | ||
var pageX = _ref3.pageX, | ||
pageY = _ref3.pageY; | ||
if (this.props.disablePanning) return; | ||
this.setState({ | ||
isPressed: true, | ||
mouseXStart: pageX - this.state.mouseX, | ||
mouseYStart: pageY - this.state.mouseY | ||
}); | ||
} | ||
}, { | ||
key: "componentWillReceiveProps", | ||
value: function componentWillReceiveProps(nextProps) { | ||
var _state = this.state, | ||
mouseX = _state.mouseX, | ||
mouseY = _state.mouseY, | ||
resizeFactorX = _state.resizeFactorX, | ||
resizeFactorY = _state.resizeFactorY; | ||
var _props = this.props, | ||
projection = _props.projection, | ||
center = _props.center, | ||
zoom = _props.zoom; | ||
var zoomFactor = nextProps.zoom / zoom; | ||
var centerChanged = JSON.stringify(nextProps.center) !== JSON.stringify(center); | ||
this.setState({ | ||
zoom: nextProps.zoom, | ||
mouseX: centerChanged ? (0, _utils.calculateMousePosition)("x", projection, nextProps, nextProps.zoom, resizeFactorX) : mouseX * zoomFactor, | ||
mouseY: centerChanged ? (0, _utils.calculateMousePosition)("y", projection, nextProps, nextProps.zoom, resizeFactorY) : mouseY * zoomFactor | ||
}); | ||
} | ||
}, { | ||
key: "handleResize", | ||
value: function handleResize() { | ||
var _props2 = this.props, | ||
width = _props2.width, | ||
height = _props2.height, | ||
projection = _props2.projection, | ||
zoom = _props2.zoom; | ||
var resizeFactorX = (0, _utils.calculateResizeFactor)(this.zoomableGroupNode.parentElement.clientWidth, width); | ||
var resizeFactorY = (0, _utils.calculateResizeFactor)(this.zoomableGroupNode.parentElement.clientHeight, height); | ||
var xPercentageChange = 1 / resizeFactorX * this.state.resizeFactorX; | ||
var yPercentageChange = 1 / resizeFactorY * this.state.resizeFactorY; | ||
this.setState({ | ||
resizeFactorX: resizeFactorX, | ||
resizeFactorY: resizeFactorY, | ||
mouseX: this.state.mouseX * xPercentageChange, | ||
mouseY: this.state.mouseY * yPercentageChange | ||
}); | ||
} | ||
}, { | ||
key: "componentDidMount", | ||
value: function componentDidMount() { | ||
var _props3 = this.props, | ||
width = _props3.width, | ||
height = _props3.height, | ||
projection = _props3.projection, | ||
zoom = _props3.zoom; | ||
var resizeFactorX = (0, _utils.calculateResizeFactor)(this.zoomableGroupNode.parentElement.clientWidth, width); | ||
var resizeFactorY = (0, _utils.calculateResizeFactor)(this.zoomableGroupNode.parentElement.clientHeight, height); | ||
this.setState({ | ||
resizeFactorX: resizeFactorX, | ||
resizeFactorY: resizeFactorY, | ||
mouseX: (0, _utils.calculateMousePosition)("x", projection, this.props, zoom, resizeFactorX), | ||
mouseY: (0, _utils.calculateMousePosition)("y", projection, this.props, zoom, resizeFactorY) | ||
}); | ||
window.addEventListener("resize", this.handleResize); | ||
} | ||
}, { | ||
key: "componentWillUnmount", | ||
value: function componentWillUnmount() { | ||
window.removeEventListener("resize", this.handleResize); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var _props = this.props, | ||
zoom = _props.zoom, | ||
mouseX = _props.mouseX, | ||
mouseY = _props.mouseY, | ||
width = _props.width, | ||
height = _props.height, | ||
isPressed = _props.isPressed, | ||
center = _props.center; | ||
var _this2 = this; | ||
var _props4 = this.props, | ||
width = _props4.width, | ||
height = _props4.height, | ||
zoom = _props4.zoom, | ||
style = _props4.style, | ||
projection = _props4.projection, | ||
children = _props4.children; | ||
var _state2 = this.state, | ||
mouseX = _state2.mouseX, | ||
mouseY = _state2.mouseY, | ||
resizeFactorX = _state2.resizeFactorX, | ||
resizeFactorY = _state2.resizeFactorY; | ||
var backdropDimensions = [projection().scale() / 100 * width, projection().scale() / 100 * height]; | ||
return _react2.default.createElement( | ||
"g", | ||
{ | ||
transform: "\n translate(\n " + (width / 2 + this.props.resizeFactorX * mouseX) + "\n " + (height / 2 + this.props.resizeFactorY * mouseY) + "\n )\n scale(" + zoom + ")\n translate(" + -width / 2 + " " + -height / 2 + ")\n ", | ||
style: this.props.styles.geographies ? this.props.styles.geographies(zoom) : _defaultStyles2.default.geographies(zoom), | ||
onMouseMove: this.props.handleMouseMove, | ||
onMouseUp: this.props.handleMouseUp, | ||
onMouseDown: this.props.handleMouseDown, | ||
className: "rsm-zoomable-group" | ||
{ className: "rsm-zoomable-group", | ||
ref: function ref(zoomableGroupNode) { | ||
return _this2.zoomableGroupNode = zoomableGroupNode; | ||
}, | ||
transform: "\n translate(\n " + (width / 2 + resizeFactorX * mouseX) + "\n " + (height / 2 + resizeFactorY * mouseY) + "\n )\n scale(" + zoom + ")\n translate(" + -width / 2 + " " + -height / 2 + ")\n ", | ||
onMouseMove: this.handleMouseMove, | ||
onMouseUp: this.handleMouseUp, | ||
onMouseDown: this.handleMouseDown, | ||
style: style | ||
}, | ||
_react2.default.createElement("rect", { x: 0, y: 0, width: width, height: height, fill: "transparent" }), | ||
this.props.children | ||
_react2.default.createElement("rect", { | ||
x: width / 2, | ||
y: height / 2, | ||
width: width, | ||
height: height, | ||
fill: "transparent", | ||
transform: "translate(-" + width / 2 + ", -" + height / 2 + ")", | ||
style: { strokeWidth: 0 } | ||
}), | ||
(0, _utils.createNewChildren)(children, this.props) | ||
); | ||
@@ -66,18 +209,10 @@ } | ||
return ZoomableGroup; | ||
}(_react2.default.Component); | ||
}(_react.Component); | ||
ZoomableGroup.propTypes = { | ||
zoom: _react.PropTypes.number.isRequired, | ||
mouseX: _react.PropTypes.number.isRequired, | ||
mouseY: _react.PropTypes.number.isRequired, | ||
isPressed: _react.PropTypes.bool.isRequired, | ||
styles: _react.PropTypes.object, | ||
center: _react.PropTypes.array | ||
}; | ||
ZoomableGroup.defaultProps = { | ||
styles: _defaultStyles2.default, | ||
center: [0, 0] | ||
center: [0, 0], | ||
zoom: 1, | ||
disablePanning: false | ||
}; | ||
exports.default = ZoomableGroup; |
{ | ||
"name": "react-simple-maps", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "An svg map component built with and for React", | ||
@@ -10,3 +10,3 @@ "main": "lib/index.js", | ||
"prepublish": "npm run build", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"test": "mocha './tests/**/*.spec.js' --compilers js:babel-core/register" | ||
}, | ||
@@ -40,4 +40,7 @@ "babel": { | ||
"babel-preset-react": "6.23.0", | ||
"expect": "1.20.2", | ||
"mocha": "3.4.1", | ||
"react": "15.4.2", | ||
"react-dom": "15.4.2" | ||
"react-dom": "15.4.2", | ||
"react-test-utils": "0.0.1" | ||
}, | ||
@@ -44,0 +47,0 @@ "peerDependencies": { |
import React, { PropTypes } from "react" | ||
import defaultStyles from "./defaultStyles" | ||
import { replaceStrokeWidth } from "./utils" | ||
import Geography from "./Geography" | ||
import React, { Component } from "react" | ||
import { feature } from "topojson-client" | ||
class Geographies extends React.Component { | ||
shouldComponentUpdate(nextProps) { | ||
const geoPathsChanged = nextProps.geographyPaths.length !== this.props.geographyPaths.length | ||
const includesChanged = nextProps.include.length !== this.props.include.length | ||
const excludesChanged = nextProps.exclude.length !== this.props.exclude.length | ||
class Geographies extends Component { | ||
constructor(props) { | ||
super(props) | ||
this.state = { | ||
geographyPaths: props.geographyPaths, | ||
} | ||
this.fetchGeographies = this.fetchGeographies.bind(this) | ||
} | ||
fetchGeographies(geographyUrl) { | ||
const { width, height } = this.props | ||
if(!geographyUrl) return | ||
const request = new XMLHttpRequest() | ||
request.open("GET", geographyUrl, true) | ||
request.onload = () => { | ||
if (request.status >= 200 && request.status < 400) { | ||
const geographyPaths = JSON.parse(request.responseText) | ||
this.setState({ | ||
geographyPaths: feature(geographyPaths, geographyPaths.objects[Object.keys(geographyPaths.objects)[0]]).features, | ||
}, () => { | ||
if (!this.props.onGeographiesLoaded) return | ||
this.props.onGeographyPathsLoaded(String(request.status)) | ||
}) | ||
} else { | ||
if (!this.props.onGeographiesLoaded) return | ||
this.props.onGeographyPathsLoaded(String(request.status)) | ||
} | ||
} | ||
request.onerror = () => { | ||
console.log("There was a connection error...") | ||
} | ||
request.send() | ||
} | ||
componentWillReceiveProps(nextProps) {; | ||
if (!nextProps.geographyUrl) return | ||
if (nextProps.geographyUrl !== this.props.geographyUrl) { | ||
this.fetchGeographies(nextProps.geographyUrl) | ||
} | ||
} | ||
shouldComponentUpdate(nextProps, nextState) { | ||
const geoPathsChanged = nextState.geographyPaths.length !== this.state.geographyPaths.length | ||
const choroplethChanged = JSON.stringify(nextProps.choropleth) !== JSON.stringify(this.props.choropleth) | ||
return !this.props.freezeGeographyPaths || geoPathsChanged || includesChanged || excludesChanged || choroplethChanged | ||
return geoPathsChanged || choroplethChanged || nextProps.disableOptimization | ||
} | ||
componentDidMount() { | ||
this.fetchGeographies(this.props.geographyUrl) | ||
} | ||
render() { | ||
const styles = this.props.styles.geography || defaultStyles.geography | ||
const { | ||
projection, | ||
style, | ||
children, | ||
} = this.props | ||
return ( | ||
<g className="rsm-geographies"> | ||
{ | ||
this.props.geographyPaths.map((geography, i) => { | ||
const included = this.props.include.indexOf(geography.id) !== -1 | ||
const notExcluded = this.props.exclude.indexOf(geography.id) === -1 | ||
const shouldInclude = this.props.include.length > 0 ? included : notExcluded | ||
return shouldInclude ? ( | ||
<Geography | ||
zoom={ this.props.zoom } | ||
key={ geography.id ? `${geography.id}-${i}` : i } | ||
geography={ geography } | ||
projection={ this.props.projection } | ||
choroplethValue={ this.props.choropleth[geography.id] } | ||
styles={ styles } | ||
events={ this.props.events } | ||
/> | ||
) : null | ||
}) | ||
} | ||
<g className="rsm-geographies" style={ style }> | ||
{ children(this.state.geographyPaths, projection) } | ||
</g> | ||
@@ -45,23 +72,8 @@ ) | ||
Geographies.propTypes = { | ||
geographyPaths: PropTypes.array, | ||
projection: PropTypes.func.isRequired, | ||
freezeGeographyPaths: PropTypes.bool, | ||
exclude: PropTypes.array, | ||
include: PropTypes.array, | ||
styles: PropTypes.object, | ||
choropleth: PropTypes.object, | ||
events: PropTypes.object, | ||
} | ||
Geographies.defaultProps = { | ||
componentIdentifier: "Geographies", | ||
disableOptimization: false, | ||
geographyPaths: [], | ||
freezeGeographyPaths: true, | ||
exclude: [], | ||
include: [], | ||
styles: defaultStyles, | ||
choropleth: {}, | ||
events: {}, | ||
} | ||
export default Geographies |
import React, { PropTypes } from "react" | ||
import React, { Component } from "react" | ||
import { geoPath } from "d3-geo" | ||
import defaultStyles from "./defaultStyles" | ||
import { createChoroplethStyles } from "./utils" | ||
class Geography extends React.Component { | ||
import { roundPath } from "./utils" | ||
class Geography extends Component { | ||
constructor() { | ||
@@ -12,42 +12,74 @@ super() | ||
this.state = { | ||
hovered: false, | ||
hover: false, | ||
pressed: false, | ||
} | ||
this.handleMouseEnter = this.handleMouseEnter.bind(this) | ||
this.handleMouseMove = this.handleMouseMove.bind(this) | ||
this.handleMouseLeave = this.handleMouseLeave.bind(this) | ||
this.handleMouseMove = this.handleMouseMove.bind(this) | ||
this.handleClick = this.handleClick.bind(this) | ||
this.handleMouseDown = this.handleMouseDown.bind(this) | ||
this.handleMouseUp = this.handleMouseUp.bind(this) | ||
this.handleMouseClick = this.handleMouseClick.bind(this) | ||
this.handleFocus = this.handleFocus.bind(this) | ||
this.handleBlur = this.handleBlur.bind(this) | ||
} | ||
handleMouseEnter(geography, evt) { | ||
evt.preventDefault() | ||
this.setState({ hovered: true }) | ||
if(this.props.events.onMouseEnter) { | ||
this.props.events.onMouseEnter(geography, evt) | ||
} | ||
handleMouseClick(evt) { | ||
evt.persist() | ||
const { onClick, geography } = this.props | ||
return onClick && onClick(geography, evt) | ||
} | ||
handleMouseLeave(geography, evt) { | ||
evt.preventDefault() | ||
this.setState({ hovered: false }) | ||
if(this.props.events.onMouseLeave) { | ||
this.props.events.onMouseLeave(geography, evt) | ||
} | ||
handleMouseEnter(evt) { | ||
evt.persist() | ||
const { onMouseEnter, geography } = this.props | ||
this.setState({ | ||
hover: true, | ||
}, () => onMouseEnter && onMouseEnter(geography, evt)) | ||
} | ||
handleMouseMove(geography, evt) { | ||
evt.preventDefault() | ||
if(this.props.events.onMouseMove) { | ||
this.props.events.onMouseMove(geography, evt) | ||
handleMouseMove(evt) { | ||
evt.persist() | ||
if (this.state.pressed) return | ||
const { onMouseMove, geography } = this.props | ||
if (!this.state.hover) { | ||
this.setState({ | ||
hover: true, | ||
}, () => onMouseMove && onMouseMove(geography, evt)) | ||
} | ||
else if (onMouseMove) onMouseMove(geography, evt) | ||
else return | ||
} | ||
handleClick(geography, evt) { | ||
evt.preventDefault() | ||
if(this.props.events.onClick) { | ||
this.props.events.onClick(geography, evt) | ||
} | ||
handleMouseLeave(evt) { | ||
evt.persist() | ||
const { onMouseLeave, geography } = this.props | ||
this.setState({ | ||
hover: false, | ||
}, () => onMouseLeave && onMouseLeave(geography, evt)) | ||
} | ||
shouldComponentUpdate(nextProps, nextState) { | ||
const changedHoverState = this.state.hovered !== nextState.hovered | ||
const changedChoroplethValue = this.props.choroplethValue !== nextProps.choroplethValue | ||
const changedGeography = this.props.geography !== nextProps.geography | ||
return changedGeography || changedChoroplethValue || changedHoverState | ||
handleMouseDown(evt) { | ||
evt.persist() | ||
const { onMouseDown, geography } = this.props | ||
this.setState({ | ||
pressed: true, | ||
}, () => onMouseDown && onMouseDown(geography, evt)) | ||
} | ||
handleMouseUp(evt) { | ||
evt.persist() | ||
const { onMouseUp, geography } = this.props | ||
this.setState({ | ||
pressed: false, | ||
}, () => onMouseUp && onMouseUp(geography, evt)) | ||
} | ||
handleFocus(evt) { | ||
evt.persist() | ||
const { onFocus, geography } = this.props | ||
this.setState({ | ||
hover: true, | ||
}, () => onFocus && onFocus(geography, evt)) | ||
} | ||
handleBlur(evt) { | ||
evt.persist() | ||
const { onBlur, geography } = this.props | ||
this.setState({ | ||
hover: false, | ||
}, () => onBlur && onBlur(geography, evt)) | ||
} | ||
render() { | ||
@@ -58,15 +90,29 @@ | ||
projection, | ||
styles, | ||
choroplethValue, | ||
round, | ||
precision, | ||
tabable, | ||
style, | ||
} = this.props | ||
const { | ||
hover, | ||
pressed, | ||
} = this.state | ||
const pathString = geoPath().projection(projection())(geography) | ||
return ( | ||
<path | ||
d={ geoPath().projection(projection())(geography) } | ||
onMouseEnter={(evt) => this.handleMouseEnter(geography, evt) } | ||
onMouseLeave={(evt) => this.handleMouseLeave(geography, evt) } | ||
onMouseMove={(evt) => this.handleMouseMove(geography, evt) } | ||
onClick={(evt) => this.handleClick(geography, evt) } | ||
style={ styles(choroplethValue, geography)[ this.state.hovered ? "hover" : "default" ] || styles(choroplethValue, geography)["default"] } | ||
className="rsm-geography" | ||
d={ round ? roundPath(pathString, precision) : pathString } | ||
className={ `rsm-geography${ pressed && " rsm-geography--pressed" }${ hover && " rsm-geography--hover" }` } | ||
style={ style[pressed || hover ? (pressed ? "pressed" : "hover") : "default"] } | ||
onClick={ this.handleMouseClick } | ||
onMouseEnter={ this.handleMouseEnter } | ||
onMouseMove={ this.handleMouseMove } | ||
onMouseLeave={ this.handleMouseLeave } | ||
onMouseDown={ this.handleMouseDown } | ||
onMouseUp={ this.handleMouseUp } | ||
onFocus={ tabable && this.handleFocus } | ||
onBlur={ tabable && this.handleBlur } | ||
tabIndex={ tabable ? 0 : -1 } | ||
/> | ||
@@ -77,14 +123,13 @@ ) | ||
Geography.propTypes = { | ||
geography: PropTypes.object.isRequired, | ||
projection: PropTypes.func.isRequired, | ||
choroplethValue: PropTypes.object, | ||
events: PropTypes.object, | ||
} | ||
Geography.defaultProps = { | ||
styles: defaultStyles.geography, | ||
events: {}, | ||
precision: 0.1, | ||
round: true, | ||
tabable: true, | ||
style: { | ||
default: {}, | ||
hover: {}, | ||
pressed: {}, | ||
} | ||
} | ||
export default Geography |
311
src/index.js
import React, { PropTypes } from "react" | ||
import { feature } from "topojson-client" | ||
import Loader from "./Loader" | ||
import MapControls from "./MapControls" | ||
import ZoomableGroup from "./ZoomableGroup" | ||
import Geographies from "./Geographies" | ||
import Markers from "./Markers" | ||
import projections from "./projections" | ||
import defaultProjectionConfig from "./projectionConfig" | ||
import defaultStyles from "./defaultStyles" | ||
import { | ||
calculateResizeFactor, | ||
calculateMousePosition, | ||
} from "./utils" | ||
class ReactSimpleMap extends React.Component { | ||
constructor(props) { | ||
super(props) | ||
this.state = { | ||
geographyPaths: props.geographyPaths, | ||
zoom: props.zoom, | ||
mouseX: calculateMousePosition("x", this.projection.bind(this), props, props.zoom, 1), | ||
mouseY: calculateMousePosition("y", this.projection.bind(this), props, props.zoom, 1), | ||
mouseXStart: 0, | ||
mouseYStart: 0, | ||
isPressed: false, | ||
loadingError: null, | ||
resizeFactorX: 1, | ||
resizeFactorY: 1, | ||
} | ||
this.projection = this.projection.bind(this) | ||
this.fetchGeographies = this.fetchGeographies.bind(this) | ||
this.handleZoomReset = this.handleZoomReset.bind(this) | ||
this.handleZoomIn = this.handleZoomIn.bind(this) | ||
this.handleZoomOut = this.handleZoomOut.bind(this) | ||
this.handleMouseDown = this.handleMouseDown.bind(this) | ||
this.handleMouseMove = this.handleMouseMove.bind(this) | ||
this.handleMouseUp = this.handleMouseUp.bind(this) | ||
} | ||
componentWillReceiveProps(nextProps) { | ||
if(nextProps.zoom !== this.state.zoom || nextProps.center !== this.props.center) { | ||
this.setState({ | ||
zoom: nextProps.zoom, | ||
mouseX: calculateMousePosition("x", this.projection, nextProps, nextProps.zoom, this.state.resizeFactorX), | ||
mouseY: calculateMousePosition("y", this.projection, nextProps, nextProps.zoom, this.state.resizeFactorY), | ||
}) | ||
} | ||
} | ||
fetchGeographies(geographyUrl) { | ||
if(!this.props.geographyUrl) return | ||
const request = new XMLHttpRequest() | ||
request.open("GET", geographyUrl, true) | ||
request.onload = () => { | ||
if (request.status >= 200 && request.status < 400) { | ||
const geographyPaths = JSON.parse(request.responseText) | ||
this.setState({ | ||
geographyPaths: feature(geographyPaths, geographyPaths.objects[Object.keys(geographyPaths.objects)[0]]).features, | ||
}) | ||
} else { | ||
this.setState({ | ||
loadingError: String(request.status), | ||
}) | ||
} | ||
} | ||
request.onerror = () => { | ||
console.log("There was a connection error...") | ||
} | ||
request.send() | ||
} | ||
projection() { | ||
if(typeof this.props.projection !== "function") { | ||
return projections(this.props.width, this.props.height, this.props.projectionConfig, this.props.projection) | ||
} | ||
else { | ||
return this.props.projection(this.props.width, this.props.height, this.props.projectionConfig) | ||
} | ||
} | ||
handleZoomIn() { | ||
if(this.state.zoom < this.props.maxZoom) { | ||
this.setState({ | ||
zoom: this.state.zoom * 2, | ||
mouseX: this.state.mouseX * 2, | ||
mouseY: this.state.mouseY * 2, | ||
}) | ||
} | ||
} | ||
handleZoomOut() { | ||
if(this.state.zoom > this.props.minZoom) { | ||
this.setState({ | ||
zoom: this.state.zoom / 2, | ||
mouseX: this.state.zoom === 2 ? calculateMousePosition("x", this.projection, this.props, this.props.minZoom, this.state.resizeFactorX) : this.state.mouseX / 2, | ||
mouseY: this.state.zoom === 2 ? calculateMousePosition("y", this.projection, this.props, this.props.minZoom, this.state.resizeFactorY) : this.state.mouseY / 2, | ||
}) | ||
} | ||
} | ||
handleZoomReset() { | ||
if(this.state.zoom > this.props.minZoom) { | ||
this.setState({ | ||
zoom: this.props.minZoom, | ||
mouseX: calculateMousePosition("x", this.projection, this.props, this.props.minZoom, this.state.resizeFactorX), | ||
mouseY: calculateMousePosition("y", this.projection, this.props, this.props.minZoom, this.state.resizeFactorY), | ||
}) | ||
} | ||
} | ||
handleMouseDown({ pageX, pageY }) { | ||
this.setState({ | ||
isPressed: true, | ||
mouseXStart: pageX - this.state.mouseX, | ||
mouseYStart: pageY - this.state.mouseY, | ||
}) | ||
} | ||
handleMouseMove({ pageX, pageY }) { | ||
if(this.state.isPressed) { | ||
this.setState({ | ||
mouseX: pageX - this.state.mouseXStart, | ||
mouseY: pageY - this.state.mouseYStart, | ||
}) | ||
} | ||
} | ||
handleMouseUp({ pageX, pageY }) { | ||
this.setState({ | ||
isPressed: false, | ||
}) | ||
} | ||
componentDidMount() { | ||
this.fetchGeographies(this.props.geographyUrl) | ||
const actualWidth = this.wrapperNode.clientWidth | ||
const actualHeight = this.wrapperNode.clientHeight | ||
const resizeFactorX = calculateResizeFactor(actualWidth, this.props.width) | ||
const resizeFactorY = calculateResizeFactor(actualHeight, this.props.height) | ||
this.setState({ | ||
resizeFactorX: resizeFactorX, | ||
resizeFactorY: resizeFactorY, | ||
mouseX: calculateMousePosition("x", this.projection, this.props, this.state.zoom, resizeFactorX), | ||
mouseY: calculateMousePosition("y", this.projection, this.props, this.state.zoom, resizeFactorY), | ||
}) | ||
window.addEventListener("resize", () => { | ||
const actualWidth = this.wrapperNode.clientWidth | ||
const actualHeight = this.wrapperNode.clientHeight | ||
const resizeFactorX = calculateResizeFactor(actualWidth, this.props.width) | ||
const resizeFactorY = calculateResizeFactor(actualHeight, this.props.height) | ||
this.setState({ | ||
resizeFactorX: resizeFactorX, | ||
resizeFactorY: resizeFactorY, | ||
mouseX: calculateMousePosition("x", this.projection, this.props, this.state.zoom, resizeFactorX), | ||
mouseY: calculateMousePosition("y", this.projection, this.props, this.state.zoom, resizeFactorY), | ||
}) | ||
}) | ||
} | ||
render() { | ||
const { | ||
width, | ||
height, | ||
styles, | ||
freezeGeographyPaths, | ||
markers, | ||
exclude, | ||
include, | ||
center, | ||
} = this.props | ||
const { | ||
geographyPaths, | ||
loadingError, | ||
} = this.state | ||
return ( | ||
<div style={ styles.wrapper ? styles.wrapper() : defaultStyles.wrapper() } className="rsm-wrapper"> | ||
{ | ||
this.props.showControls ? ( | ||
<MapControls | ||
handleZoomIn={ this.handleZoomIn } | ||
handleZoomOut={ this.handleZoomOut } | ||
handleZoomReset={ this.handleZoomReset } | ||
/> | ||
) : null | ||
} | ||
{ | ||
geographyPaths.length === 0 ? ( | ||
<Loader | ||
styles={ styles.loader || defaultStyles.loader } | ||
loadingError={ loadingError } | ||
/> | ||
) : null | ||
} | ||
<svg width={ width } | ||
height={ height } | ||
viewBox={ `0 0 ${width} ${height}` } | ||
style={ styles.svg ? styles.svg() : defaultStyles.svg() } | ||
className="rsm-svg" | ||
ref={(wrapperNode) => this.wrapperNode = wrapperNode} | ||
> | ||
<ZoomableGroup | ||
zoom={ this.state.zoom } | ||
mouseX={ this.state.mouseX } | ||
mouseY={ this.state.mouseY } | ||
width={ width } | ||
height={ height } | ||
isPressed={ this.state.isPressed } | ||
handleMouseMove={ this.handleMouseMove } | ||
handleMouseUp={ this.handleMouseUp } | ||
handleMouseDown={ this.handleMouseDown } | ||
styles={ styles } | ||
center={ [-center[0],-center[1]] } | ||
projection={ this.projection } | ||
resizeFactorX={ this.state.resizeFactorX } | ||
resizeFactorY={ this.state.resizeFactorY } | ||
> | ||
{ | ||
geographyPaths.length > 0 ? ( | ||
<Geographies | ||
zoom={ this.state.zoom } | ||
projection={ this.projection } | ||
geographyPaths={ geographyPaths } | ||
freezeGeographyPaths={ freezeGeographyPaths } | ||
exclude={ exclude } | ||
include={ include } | ||
styles={ styles } | ||
choropleth={ this.props.choropleth } | ||
events={ this.props.events.geography } | ||
/> | ||
) : null | ||
} | ||
{ | ||
geographyPaths.length > 0 ? ( | ||
<Markers | ||
projection={ this.projection } | ||
markers={ markers } | ||
zoom={ this.state.zoom } | ||
styles={ styles } | ||
events={ this.props.events.marker } | ||
/> | ||
) : null | ||
} | ||
</ZoomableGroup> | ||
</svg> | ||
</div> | ||
) | ||
} | ||
} | ||
ReactSimpleMap.propTypes = { | ||
width: PropTypes.number, | ||
height: PropTypes.number, | ||
geographyUrl: PropTypes.string, | ||
geographyPaths: PropTypes.array, | ||
projection: PropTypes.oneOfType([ | ||
PropTypes.string, | ||
PropTypes.func, | ||
]), | ||
freezeGeographyPaths: PropTypes.bool, | ||
styles: PropTypes.object, | ||
markers: PropTypes.array, | ||
exclude: PropTypes.array, | ||
include: PropTypes.array, | ||
zoom: PropTypes.number, | ||
minZoom: PropTypes.number, | ||
maxZoom: PropTypes.number, | ||
center: PropTypes.array, | ||
projectionConfig: PropTypes.object, | ||
choropleth: PropTypes.object, | ||
showControls: PropTypes.bool, | ||
} | ||
ReactSimpleMap.defaultProps = { | ||
width: 800, | ||
height: 450, | ||
geographyUrl: null, | ||
geographyPaths: [], | ||
projection: "times", | ||
choropleth: {}, | ||
freezeGeographyPaths: true, | ||
styles: defaultStyles, | ||
markers: [], | ||
exclude: [], | ||
include: [], | ||
zoom: 1, | ||
minZoom: 1, | ||
maxZoom: 8, | ||
center: [0,0], | ||
projectionConfig: defaultProjectionConfig, | ||
showControls: false, | ||
events: { | ||
geography: {}, | ||
marker: {}, | ||
} | ||
} | ||
export default ReactSimpleMap | ||
export { default as ComposableMap } from "./ComposableMap" | ||
export { default as ZoomableGroup } from "./ZoomableGroup" | ||
export { default as Geographies } from "./Geographies" | ||
export { default as Geography } from "./Geography" | ||
export { default as Marker } from "./Marker" | ||
export { default as Markers } from "./Markers" | ||
export { default as Annotation } from "./Annotation" |
import React, { Component, PropTypes } from "react" | ||
import defaultStyles from "./defaultStyles" | ||
import React, { Component } from "react" | ||
@@ -10,3 +9,4 @@ class Marker extends Component { | ||
this.state = { | ||
hovered: false, | ||
hover: false, | ||
pressed: false, | ||
} | ||
@@ -16,71 +16,103 @@ | ||
this.handleMouseLeave = this.handleMouseLeave.bind(this) | ||
this.handleMouseDown = this.handleMouseDown.bind(this) | ||
this.handleMouseUp = this.handleMouseUp.bind(this) | ||
this.handleMouseClick = this.handleMouseClick.bind(this) | ||
this.handleMouseMove = this.handleMouseMove.bind(this) | ||
this.handleClick = this.handleClick.bind(this) | ||
this.handleFocus = this.handleFocus.bind(this) | ||
this.handleBlur = this.handleBlur.bind(this) | ||
} | ||
handleMouseEnter(marker, evt) { | ||
evt.preventDefault() | ||
this.setState({ hovered: true }) | ||
if(this.props.events.onMouseEnter) { | ||
this.props.events.onMouseEnter(marker, evt) | ||
} | ||
handleMouseEnter(evt) { | ||
evt.persist() | ||
const { onMouseEnter, marker } = this.props | ||
this.setState({ | ||
hover: true, | ||
}, () => onMouseEnter && onMouseEnter(marker, evt)) | ||
} | ||
handleMouseLeave(marker, evt) { | ||
evt.preventDefault() | ||
this.setState({ hovered: false }) | ||
if(this.props.events.onMouseLeave) { | ||
this.props.events.onMouseLeave(marker, evt) | ||
handleMouseMove(evt) { | ||
evt.persist() | ||
if (this.state.pressed) return | ||
const { onMouseMove, marker } = this.props | ||
if (!this.state.hover) { | ||
this.setState({ | ||
hover: true | ||
}, () => onMouseMove && onMouseMove(marker, evt)) | ||
} | ||
else if (onMouseMove) onMouseMove(marker, evt) | ||
else return | ||
} | ||
handleMouseMove(marker, evt) { | ||
evt.preventDefault() | ||
if(this.props.events.onMouseMove) { | ||
this.props.events.onMouseMove(marker, evt) | ||
} | ||
handleMouseLeave(evt) { | ||
evt.persist() | ||
const { onMouseLeave, marker } = this.props | ||
this.setState({ | ||
hover: false, | ||
}, () => onMouseLeave && onMouseLeave(marker, evt)) | ||
} | ||
handleClick(marker, evt) { | ||
evt.preventDefault() | ||
if(this.props.events.onClick) { | ||
this.props.events.onClick(marker, evt) | ||
} | ||
handleMouseDown(evt) { | ||
evt.persist() | ||
const { onMouseDown, marker } = this.props | ||
this.setState({ | ||
pressed: true, | ||
}, () => onMouseDown && onMouseDown(marker, evt)) | ||
} | ||
shouldComponentUpdate(nextProps, nextState) { | ||
const hoverStateChanged = nextState.hovered !== this.state.hovered | ||
const radiusChanged = nextProps.marker.radius !== this.props.marker.radius | ||
const zoomChanged = nextProps.zoom !== this.props.zoom | ||
return hoverStateChanged || radiusChanged || zoomChanged | ||
handleMouseUp(evt) { | ||
evt.persist() | ||
const { onMouseUp, marker } = this.props | ||
this.setState({ | ||
pressed: false, | ||
}, () => onMouseUp && onMouseUp(marker, evt)) | ||
} | ||
handleMouseClick(evt) { | ||
if (!this.props.onClick) return | ||
evt.persist() | ||
const { onClick, marker, projection } = this.props | ||
return onClick && onClick(marker, projection()(marker.coordinates), evt) | ||
} | ||
handleFocus(evt) { | ||
evt.persist() | ||
const { onFocus, marker } = this.props | ||
this.setState({ | ||
hover: true, | ||
}, () => onFocus && onFocus(marker, evt)) | ||
} | ||
handleBlur(evt) { | ||
evt.persist() | ||
const { onBlur, marker } = this.props | ||
this.setState({ | ||
hover: false, | ||
}, () => onBlur && onBlur(marker, evt)) | ||
} | ||
render() { | ||
const { | ||
projection, | ||
marker, | ||
styles, | ||
zoom, | ||
projection, | ||
style, | ||
tabable, | ||
children, | ||
} = this.props | ||
const { | ||
pressed, | ||
hover, | ||
} = this.state | ||
return ( | ||
<circle | ||
cx={ projection()(marker.coordinates)[0] } | ||
cy={ projection()(marker.coordinates)[1] } | ||
r={ marker.radius } | ||
style={ styles(marker, zoom)[ this.state.hovered ? "hover" : "default" ] || styles(marker, zoom)["default"] } | ||
className="rsm-marker" | ||
onMouseEnter={ (evt) => this.handleMouseEnter(marker, evt) } | ||
onMouseLeave={ (evt) => this.handleMouseLeave(marker, evt) } | ||
onMouseMove={ (evt) => this.handleMouseMove(marker, evt) } | ||
onClick={ (evt) => this.handleClick(marker, evt) } | ||
transform={` | ||
translate( | ||
${ projection()(marker.coordinates)[0] } | ||
${ projection()(marker.coordinates)[1] } | ||
) | ||
scale( | ||
${ 1/zoom } | ||
) | ||
translate( | ||
${ -projection()(marker.coordinates)[0] } | ||
${ -projection()(marker.coordinates)[1] } | ||
) | ||
`} | ||
/> | ||
<g className={ `rsm-marker${ pressed && " rsm-marker--pressed" }${ hover && " rsm-marker--hover" }` } | ||
transform={ `translate( | ||
${ projection()(marker.coordinates)[0] } | ||
${ projection()(marker.coordinates)[1] } | ||
)`} | ||
style={ style[pressed || hover ? (pressed ? "pressed" : "hover") : "default"] } | ||
onMouseEnter={ this.handleMouseEnter } | ||
onMouseLeave={ this.handleMouseLeave } | ||
onMouseDown={ this.handleMouseDown } | ||
onMouseUp={ this.handleMouseUp } | ||
onClick={ this.handleMouseClick } | ||
onMouseMove={ this.handleMouseMove } | ||
onFocus={ this.handleFocus } | ||
onBlur={ this.handleBlur } | ||
tabIndex={ tabable ? 0 : -1 } | ||
> | ||
{ children } | ||
</g> | ||
) | ||
@@ -90,17 +122,11 @@ } | ||
Marker.propTypes = { | ||
marker: PropTypes.object, | ||
zoom: PropTypes.number, | ||
events: PropTypes.object, | ||
projection: PropTypes.func, | ||
styles: PropTypes.func, | ||
} | ||
Marker.defaultProps = { | ||
marker: {}, | ||
zoom: 1, | ||
events: {}, | ||
styles: defaultStyles.marker, | ||
style: { | ||
default: {}, | ||
hover: {}, | ||
pressed: {}, | ||
}, | ||
tabable: true, | ||
} | ||
export default Marker |
import React, { PropTypes } from "react" | ||
import defaultStyles from "./defaultStyles" | ||
import Marker from "./Marker" | ||
import React, { Component } from "react" | ||
class Markers extends React.Component { | ||
class Markers extends Component { | ||
render() { | ||
const { | ||
children, | ||
projection, | ||
style, | ||
} = this.props | ||
return ( | ||
<g className="rsm-markers"> | ||
<g className="rsm-markers" style={ style }> | ||
{ | ||
this.props.markers.map((marker, i) => ( | ||
<Marker | ||
key={ `${Math.abs(marker.coordinates[0])}-${Math.abs(marker.coordinates[1])}-${i}` } | ||
projection={ this.props.projection } | ||
marker={ marker } | ||
styles={ this.props.styles.marker } | ||
events={ this.props.events } | ||
zoom={ this.props.zoom } | ||
/> | ||
)) | ||
!children.length ? | ||
React.cloneElement(children, { | ||
projection, | ||
}) : | ||
children.map((child, i) => ( | ||
React.cloneElement(child, { | ||
key: child.key || `marker-${i}`, | ||
projection, | ||
}) | ||
)) | ||
} | ||
@@ -27,17 +32,6 @@ </g> | ||
Markers.propTypes = { | ||
markers: PropTypes.array, | ||
projection: PropTypes.func.isRequired, | ||
zoom: PropTypes.number, | ||
events: PropTypes.object, | ||
styles: PropTypes.object, | ||
} | ||
Markers.defaultProps = { | ||
markers: [], | ||
zoom: 1, | ||
events: {}, | ||
styles: defaultStyles, | ||
componentIdentifier: "Markers", | ||
} | ||
export default Markers |
@@ -27,6 +27,6 @@ | ||
return projectionReference[projectionName]() | ||
.scale(scale) | ||
.translate([ xOffset + width / 2, yOffset + height / 2 ]) | ||
.rotate(rotation) | ||
.precision(precision) | ||
.scale(scale) | ||
.translate([ xOffset + width / 2, yOffset + height / 2 ]) | ||
.rotate(rotation) | ||
.precision(precision) | ||
} |
import React from "react" | ||
export function replaceStrokeWidth(styles) { | ||
@@ -29,6 +31,55 @@ let newStyles = {} | ||
export function calculateMousePosition(direction, projection, props, zoom, resizeFactor) { | ||
const { center, width, height } = props | ||
export function calculateMousePosition(direction, projection, props, zoom, resizeFactor, center = props.center, width = props.width, height = props.height) { | ||
const reference = { x: 0, y: 1 } | ||
return (projection()([-center[0],-center[1]])[reference[direction]] - (reference[direction] === 0 ? width : height) / 2) * zoom * (1/resizeFactor) | ||
const reverseRotation = projection().rotate().map(item => -item) | ||
return (projection().rotate(reverseRotation)([-center[0],-center[1]])[reference[direction]] - (reference[direction] === 0 ? width : height) / 2) * zoom * (1/resizeFactor) | ||
} | ||
export function isChildOfType(child, expectedType) { | ||
return child.props.componentIdentifier === expectedType | ||
} | ||
export function createNewChildren(children, props) { | ||
if (!children.length) { | ||
return isChildOfType(children, "Geographies") ? React.cloneElement(children, { | ||
geographyPaths: props.geographyPaths, | ||
projection: props.projection, | ||
}) : (isChildOfType(children, "Markers") || isChildOfType(children, "Annotation") ? React.cloneElement(children, { | ||
projection: props.projection, | ||
zoom: props.zoom, | ||
}) : children) | ||
} | ||
else { | ||
return children.map((child, i) => { | ||
return isChildOfType(child, "Geographies") ? | ||
React.cloneElement(child, { | ||
key: `zoomable-child-${i}`, | ||
geographyPaths: props.geographyPaths, | ||
projection: props.projection, | ||
}) : (isChildOfType(child, "Markers") || isChildOfType(child, "Annotation") ? | ||
React.cloneElement(child, { | ||
key: `zoomable-child-${i}`, | ||
projection: props.projection, | ||
zoom: props.zoom, | ||
}): child) | ||
}) | ||
} | ||
} | ||
export function roundPath(path, precision) { | ||
const query = /[\d\.-][\d\.e-]*/g | ||
return path.replace(query, n => Math.round(n * (1/precision)) / (1/precision)) | ||
} | ||
export function createConnectorPath(connectorType, endPoint) { | ||
return `M0,0 L${endPoint[0]},${endPoint[1]}` | ||
} | ||
export function createTextAnchor(dx) { | ||
if (dx > 0) | ||
return "start" | ||
else if (dx < 0 ) | ||
return "end" | ||
else | ||
return "middle" | ||
} |
import React, { PropTypes } from "react" | ||
import defaultStyles from "./defaultStyles" | ||
import React, { Component } from "react" | ||
class ZoomableGroup extends React.Component { | ||
import { | ||
calculateResizeFactor, | ||
calculateMousePosition, | ||
isChildOfType, | ||
createNewChildren, | ||
} from "./utils" | ||
class ZoomableGroup extends Component { | ||
constructor(props) { | ||
super(props) | ||
this.state = { | ||
mouseX: calculateMousePosition("x", props.projection, props, props.zoom, 1), | ||
mouseY: calculateMousePosition("y", props.projection, props, props.zoom, 1), | ||
mouseXStart: 0, | ||
mouseYStart: 0, | ||
isPressed: false, | ||
resizeFactorX: 1, | ||
resizeFactorY: 1, | ||
} | ||
this.handleMouseMove = this.handleMouseMove.bind(this) | ||
this.handleMouseUp = this.handleMouseUp.bind(this) | ||
this.handleMouseDown = this.handleMouseDown.bind(this) | ||
this.handleResize = this.handleResize.bind(this) | ||
} | ||
handleMouseMove({ pageX, pageY }) { | ||
if (this.props.disablePanning) return | ||
if(this.state.isPressed) { | ||
this.setState({ | ||
mouseX: pageX - this.state.mouseXStart, | ||
mouseY: pageY - this.state.mouseYStart, | ||
}) | ||
} | ||
} | ||
handleMouseUp({ pageX, pageY }) { | ||
if (this.props.disablePanning) return | ||
this.setState({ | ||
isPressed: false, | ||
}) | ||
} | ||
handleMouseDown({ pageX, pageY }) { | ||
if (this.props.disablePanning) return | ||
this.setState({ | ||
isPressed: true, | ||
mouseXStart: pageX - this.state.mouseX, | ||
mouseYStart: pageY - this.state.mouseY, | ||
}) | ||
} | ||
componentWillReceiveProps(nextProps) { | ||
const { mouseX, mouseY, resizeFactorX, resizeFactorY } = this.state | ||
const { projection, center, zoom } = this.props | ||
const zoomFactor = nextProps.zoom / zoom | ||
const centerChanged = JSON.stringify(nextProps.center) !== JSON.stringify(center) | ||
this.setState({ | ||
zoom: nextProps.zoom, | ||
mouseX: centerChanged ? calculateMousePosition("x", projection, nextProps, nextProps.zoom, resizeFactorX) : mouseX * zoomFactor, | ||
mouseY: centerChanged ? calculateMousePosition("y", projection, nextProps, nextProps.zoom, resizeFactorY) : mouseY * zoomFactor, | ||
}) | ||
} | ||
handleResize() { | ||
const { width, height, projection, zoom } = this.props | ||
const resizeFactorX = calculateResizeFactor(this.zoomableGroupNode.parentElement.clientWidth, width) | ||
const resizeFactorY = calculateResizeFactor(this.zoomableGroupNode.parentElement.clientHeight, height) | ||
const xPercentageChange = 1 / resizeFactorX * this.state.resizeFactorX | ||
const yPercentageChange = 1 / resizeFactorY * this.state.resizeFactorY | ||
this.setState({ | ||
resizeFactorX: resizeFactorX, | ||
resizeFactorY: resizeFactorY, | ||
mouseX: this.state.mouseX * xPercentageChange, | ||
mouseY: this.state.mouseY * yPercentageChange, | ||
}) | ||
} | ||
componentDidMount() { | ||
const { width, height, projection, zoom } = this.props | ||
const resizeFactorX = calculateResizeFactor(this.zoomableGroupNode.parentElement.clientWidth, width) | ||
const resizeFactorY = calculateResizeFactor(this.zoomableGroupNode.parentElement.clientHeight, height) | ||
this.setState({ | ||
resizeFactorX: resizeFactorX, | ||
resizeFactorY: resizeFactorY, | ||
mouseX: calculateMousePosition("x", projection, this.props, zoom, resizeFactorX), | ||
mouseY: calculateMousePosition("y", projection, this.props, zoom, resizeFactorY), | ||
}) | ||
window.addEventListener("resize", this.handleResize) | ||
} | ||
componentWillUnmount() { | ||
window.removeEventListener("resize", this.handleResize) | ||
} | ||
render() { | ||
const { | ||
zoom, | ||
mouseX, | ||
mouseY, | ||
width, | ||
height, | ||
isPressed, | ||
center, | ||
zoom, | ||
style, | ||
projection, | ||
children, | ||
} = this.props | ||
const { | ||
mouseX, | ||
mouseY, | ||
resizeFactorX, | ||
resizeFactorY, | ||
} = this.state | ||
const backdropDimensions = [ | ||
(projection().scale()) / 100 * width, | ||
(projection().scale()) / 100 * height, | ||
] | ||
return ( | ||
<g | ||
transform={` | ||
translate( | ||
${ (width / 2) + this.props.resizeFactorX * mouseX } | ||
${ (height / 2) + this.props.resizeFactorY * mouseY } | ||
) | ||
scale(${ zoom }) | ||
translate(${ -width / 2 } ${ -height / 2 }) | ||
`} | ||
style={ this.props.styles.geographies ? this.props.styles.geographies(zoom) : defaultStyles.geographies(zoom) } | ||
onMouseMove={ this.props.handleMouseMove } | ||
onMouseUp={ this.props.handleMouseUp } | ||
onMouseDown={ this.props.handleMouseDown } | ||
className="rsm-zoomable-group" | ||
<g className="rsm-zoomable-group" | ||
ref={ zoomableGroupNode => this.zoomableGroupNode = zoomableGroupNode } | ||
transform={` | ||
translate( | ||
${ width / 2 + resizeFactorX * mouseX } | ||
${ height / 2 + resizeFactorY * mouseY } | ||
) | ||
scale(${ zoom }) | ||
translate(${ -width / 2 } ${ -height / 2 }) | ||
`} | ||
onMouseMove={ this.handleMouseMove } | ||
onMouseUp={ this.handleMouseUp } | ||
onMouseDown={ this.handleMouseDown } | ||
style={ style } | ||
> | ||
<rect x={0} y={0} width={width} height={height} fill="transparent" /> | ||
{ this.props.children } | ||
<rect | ||
x={ width/2 } | ||
y={ height/2 } | ||
width={ width } | ||
height={ height } | ||
fill="transparent" | ||
transform={ `translate(-${ width / 2 }, -${ height / 2 })` } | ||
style={{ strokeWidth: 0 }} | ||
/> | ||
{ createNewChildren(children, this.props) } | ||
</g> | ||
@@ -41,16 +154,8 @@ ) | ||
ZoomableGroup.propTypes = { | ||
zoom: PropTypes.number.isRequired, | ||
mouseX: PropTypes.number.isRequired, | ||
mouseY: PropTypes.number.isRequired, | ||
isPressed: PropTypes.bool.isRequired, | ||
styles: PropTypes.object, | ||
center: PropTypes.array, | ||
} | ||
ZoomableGroup.defaultProps = { | ||
styles: defaultStyles, | ||
center: [0,0], | ||
center: [ 0, 0 ], | ||
zoom: 1, | ||
disablePanning: false, | ||
} | ||
export default ZoomableGroup |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
2178
1
393218
8
29
1