diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index a462491..984ecac 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -7,6 +7,7 @@ import * as selectors from '../selectors'; import LoadingOverlay from './presentational/LoadingOverlay'; import Viewport from './Viewport.jsx'; +import Map from './Map.jsx'; import Toolbar from './Toolbar.jsx'; import CardStack from './CardStack.jsx'; import NarrativeCard from './NarrativeCard.js'; @@ -85,13 +86,21 @@ class Dashboard extends React.Component { onSelectNarrative={this.handleSelectNarrative} actions={this.props.actions} /> - this.getCategoryColor(category) }} /> + {/* this.getCategoryColor(category) + }} + />*/} { + this.svg.classed('hide', true); + }); + this.state.map.on('zoomend', () => { + this.svg.classed('hide', false); + }); + + this.mapLogic = new MapLogic(this.state.map, this.svg, this.g, this.props.app, this.props.ui, this.props.methods) + this.mapLogic.update(this.props.domain, this.props.app) + + this.setState({ isInitialized: true }) + } + } + + componentWillReceiveProps(nextProps) { + if (hash(nextProps) !== hash(this.props)) { + this.mapLogic.update(nextProps.domain, nextProps.app) + } + } + + initializeMap() { /** * Creates a Leaflet map and a tilelayer for the map background @@ -23,13 +69,14 @@ class Map extends React.Component { * @param {array} center: [lat, long] coordinates the map will be centered on * @param {number} zoom: zoom level */ - const map = L.map(this.props.mapId) - .setView(this.props.app.mapAnchor, 14) - .setMinZoom(10) - .setMaxZoom(18) - .setMaxBounds([[180, -180], [-180, 180]]) + const map = + L.map(this.props.mapId) + .setView(this.props.app.mapAnchor, 14) + .setMinZoom(10) + .setMaxZoom(18) + .setMaxBounds([[180, -180], [-180, 180]]) - let s + let s; if (process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== 'your_token') { s = L.tileLayer( `http://a.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}` @@ -50,54 +97,112 @@ class Map extends React.Component { map.keyboard.disable(); + map.on("move", () => this.moveElements()); + this.setState({ map }); } - componentDidMount(){ - if (this.state.map === null) { - this.initializeMap(); + projectPoint(location) { + const latLng = new L.LatLng(location[0], location[1]); + return { + x: this.state.map.latLngToLayerPoint(latLng).x + this.state.mapTransformX, + y: this.state.map.latLngToLayerPoint(latLng).y + this.state.mapTransformY + }; + } + + getSVGBoundaries() { + const mapNode = d3.select('.leaflet-map-pane').node(); + if (mapNode === null) return { transformX: 0, transformY: 0 }; + + // We'll get the transform of the leaflet container, + // which will let us offset the SVG by the same quantity + const transform = window + .getComputedStyle(mapNode) + .getPropertyValue('transform'); + + // However getComputedStyle returns an awkward string of the format + // matrix(0, 0, 1, 0, 0.56523, 123123), hence this awkwardness + return { + transformX: +transform.split(',')[4], + transformY: +transform.split(',')[5].split(')')[0] } } - componentDidUpdate() { - if (!this.state.isInitialized) { - const pane = d3.select(this.state.map.getPanes().overlayPane); - const boundingClient = d3.select(`#${this.props.mapId}`).node().getBoundingClientRect(); - const width = boundingClient.width; - const height = boundingClient.height; + updateSVG() { + const boundingClient = d3.select(`#${this.props.mapId}`).node().getBoundingClientRect(); - let svg = pane.append('svg') - .attr('class', 'leaflet-svg') - .attr('width', width) - .attr('height', height); + let WIDTH = boundingClient.width; + let HEIGHT = boundingClient.height; - let g = svg.append('g'); + // Offset with leaflet map transform boundaries + const { transformX, transformY } = this.getSVGBoundaries(); + + this.setState({ + mapTransformX: transformX, + mapTransformY: transformY + }) - this.state.map.on('zoomstart', () => { - svg.classed('hide', true); - }); - this.state.map.on('zoomend', () => { - svg.classed('hide', false); - }); + /*this.svg.attr('width', WIDTH) + .attr('height', HEIGHT) + .attr('style', `left: ${-transformX}px; top: ${-transformY}px`); - this.mapLogic = new MapLogic(this.state.map, svg, g, this.props.app, this.props.ui, this.props.methods) - this.mapLogic.update(this.props.domain, this.props.app) - - this.setState({ isInitialized: true }) - } + this.g.selectAll('.location').attr('transform', (d) => { + const newPoint = projectPoint([+d.latitude, +d.longitude]); + return `translate(${newPoint.x},${newPoint.y})`; + });*/ } - componentWillReceiveProps(nextProps) { - if (hash(nextProps) !== hash(this.props)) { - this.mapLogic.update(nextProps.domain, nextProps.app) + moveElements() { + this.updateSVG(); + } + + renderSites() { + if (this.state.isInitialized) { + return ( + + ); } - } + return ''; + } render() { + const classes = this.props.app.narrative ? 'map-wrapper narrative-mode' : 'map-wrapper'; return ( -
+
+
+ {this.renderSites()} +
); } } -export default Map; +function mapStateToProps(state) { + return { + domain: { + locations: selectors.selectLocations(state), + narratives: selectors.selectNarratives(state), + categories: selectors.selectCategories(state), + sites: selectors.getSites(state) + }, + app: { + views: state.app.filters.views, + selected: state.app.selected, + highlighted: state.app.highlighted, + mapAnchor: state.app.mapAnchor, + narrative: state.app.narrative + }, + ui: { + dom: state.ui.dom, + narratives: state.ui.style.narratives + } + } +} + +export default connect(mapStateToProps)(Map) + diff --git a/src/components/MapSites.jsx b/src/components/MapSites.jsx new file mode 100644 index 0000000..8fdcd28 --- /dev/null +++ b/src/components/MapSites.jsx @@ -0,0 +1,35 @@ +import React from 'react'; + +class MapSites extends React.Component { + + projectPoint(location) { + const latLng = new L.LatLng(location[0], location[1]); + return { + x: this.props.map.latLngToLayerPoint(latLng).x + this.props.mapTransformX, + y: this.props.map.latLngToLayerPoint(latLng).y + this.props.mapTransformY + }; + } + + renderSite(site) { + const { x, y } = this.projectPoint([site.latitude, site.longitude]); + + return (
+ {site.site} +
+ ); + } + + render () { + if (!this.props.sites || !this.props.sites.length) return
; + return ( +
+ {this.props.sites.map(site => { return this.renderSite(site); })} +
+ ) + } + +} + +export default MapSites; \ No newline at end of file diff --git a/src/js/map/map.js b/src/js/map/map.js index ea61f55..7d37883 100644 --- a/src/js/map/map.js +++ b/src/js/map/map.js @@ -47,6 +47,7 @@ export default function(lMap, svg, g, newApp, ui, methods) { function getSVGBoundaries() { const mapNode = d3.select('.leaflet-map-pane').node(); + if (mapNode === null) return { transformX: 0, transformY: 0 }; // We'll get the transform of the leaflet container, // which will let us offset the SVG by the same quantity @@ -406,7 +407,7 @@ export default function(lMap, svg, g, newApp, ui, methods) { * Renders events on the map: takes data, and enters, updates and exits */ function renderDomain () { - renderSites(); + //renderSites(); renderNarratives(); renderEvents(); } diff --git a/src/scss/map.scss b/src/scss/map.scss index 4a761f0..0df2bb3 100644 --- a/src/scss/map.scss +++ b/src/scss/map.scss @@ -53,12 +53,13 @@ .site-label { background: rgba($black,0.6); color: #fff; - padding: 2px 7px; + padding: 5px; font-weight: 500; font-size: 11px; font-family: 'Lato', Helvetica, sans-serif; border: rgba($black,0.6); letter-spacing: 0.05em; + transition: transform 0.1s; &::before { border-top-color: rgba($black,0.6); @@ -66,6 +67,12 @@ } } +.sites-layer { + position: fixed; + top: 0px; + left: 110px; +} + /* * Leaflet mapping controls */ @@ -164,69 +171,10 @@ } } -.coevent-marker { - fill-opacity: 0.1; - stroke-dasharray: 8px 4px; - stroke-width: 2px; - opacity: 1; -} -.coevent-path { - stroke-dasharray: 8px 4px; - stroke-width: 2; -} - -.district-boundaries { - fill: $red; - fill-opacity: 0.3; - stroke-width: 2; - stroke: $red; -} - .path-polyline { stroke: $darkgrey; stroke-width: 2px; } -.route-polyline { - transition: 0.2s ease; - stroke: $darkgrey; - &:hover { - transition: 0.2s ease; - stroke: $black; - } -} -.district-popup { - button { - height: 80px; - line-height: 80px; - width: 200px; - padding: 0; - border: none; - background: none; - background-size: 100%; - color: $offwhite; - cursor: pointer; - outline: none; - font-family: 'Lato', Helvetica, sans-serif; - text-transform: uppercase; - - p { - font-size: $normal; - margin: 0; - transition: 0.2s ease; - letter-spacing: 0.1em; - &:first-child { - font-size: $xsmall; - } - } - - &:hover { - p:last-child { - transition: 0.2s ease; - letter-spacing: 0.15em; - } - } - } -}