Make map Sites into a React component

This commit is contained in:
Franc Camps-Febrer
2018-12-19 09:55:57 +01:00
parent 8765b118ce
commit 47a01801af
5 changed files with 199 additions and 101 deletions

View File

@@ -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}
/>
<Viewport
<Map
mapId="map"
methods={{
onSelect: this.handleSelect,
onSelectNarrative: this.handleSelectNarrative,
getCategoryColor: category => this.getCategoryColor(category)
}}
/>
{/*<Viewport
methods={{
onSelect: this.handleSelect,
onSelectNarrative: this.handleSelectNarrative,
getCategoryColor: category => this.getCategoryColor(category)
}}
/>*/}
<Timeline
methods={{
onSelect: this.handleSelect,

View File

@@ -1,8 +1,11 @@
import React from 'react';
import hash from 'object-hash';
import MapLogic from '../js/map/map.js'
import { connect } from 'react-redux'
import * as selectors from '../selectors'
import MapLogic from '../js/map/map.js'
import MapSites from './MapSites.jsx';
import MapDefsMarkers from './MapDefsMarkers.jsx';
class Map extends React.Component {
@@ -12,10 +15,53 @@ class Map extends React.Component {
this.state = {
isInitialized: false,
map: null
map: null,
mapTransformX: 0,
mapTransformY: 0
}
}
componentDidMount(){
if (this.state.map === null) {
this.initializeMap();
}
}
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;
this.svg = pane.append('svg')
.attr('class', 'leaflet-svg')
.attr('width', width)
.attr('height', height);
this.g = this.svg.append('g');
this.state.map.on('zoomstart', () => {
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 (
<MapSites
sites={this.props.domain.sites}
map={this.state.map}
mapTransformX={this.state.mapTransformX}
mapTransformY={this.state.mapTransformY}
isEnabled={this.props.app.views.sites}
/>
);
}
}
return '';
}
render() {
const classes = this.props.app.narrative ? 'map-wrapper narrative-mode' : 'map-wrapper';
return (
<div id={this.props.mapId} />
<div className={classes}>
<div id={this.props.mapId} />
{this.renderSites()}
</div>
);
}
}
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)

View File

@@ -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 (<div
className="leaflet-tooltip site-label leaflet-zoom-animated leaflet-tooltip-top"
style={{ opacity: 1, transform: `translate3d(calc(${x}px - 50%), ${y - 25}px, 0px)`}}>
{site.site}
</div>
);
}
render () {
if (!this.props.sites || !this.props.sites.length) return <div />;
return (
<div className="sites-layer">
{this.props.sites.map(site => { return this.renderSite(site); })}
</div>
)
}
}
export default MapSites;

View File

@@ -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();
}

View File

@@ -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;
}
}
}
}