I have created two maps to show with React simple maps library. If I set a branch as default and click on the other branch to show its map, the one I set as default shows in the background, does not matter which one of the two branches I set as default, the default one overlaps with the other. And if I do not set a branch as default, the first branch in the array is set to default and shows in the background as overlapping map when the other branch is clicked. Here is my index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Interactive Map</title>
<meta name="description" content="" />
</head>
<body>
<div id="ep18InteractiveMap"></div>
<script>
window.ep18MapConfig = {
enableToggle: true,
branch: "senato",
//mapPath:'https://wp40.ilfattoquotidiano.it/wp-content/themes/ifq-2017/assets/js/async/politiche2018/interactivemap/mapdata',
mapPath: "/public/mapdata-2022",
logoPath:
"https://st.ilfattoquotidiano.it/wp-content/uploads/politiche-2018/loghi-ministero/",
path: "/public",
};
</script>
</body>
</html>
Its a react code built with classes using React Simple maps Library. This is my maps code:
import React, { Component } from "react";
import request from "axios";
import { geoMercator, geoPath, geoCentroid } from "d3-geo";
import { geoTimes } from "d3-geo-projection";
import { feature } from "topojson-client";
import { Motion, spring } from "react-motion";
import DistrictDetails from "./DistrictDetails";
import Navigator from "./Navigator";
import Spinner from "./Spinner";
import SvgMap from "./SvgMap";
import ResultsProvider from "./ResultsProvider";
import getShapeFromLocalMap from "../utils/getShapeFromLocalMap";
class Maps extends Component {
constructor(props) {
super(props);
this.changeType = this.changeType.bind(this);
// mapsToLoad order is important, from innermost to outermost!
this.mapsToLoad = ["circoscrizioni", "plurinominale", "uninominale"];
this.defaultCoords = [12.338021942602374, 41.84718933156246];
this.parentConfigPath = props.remotePath + "/static/json";
this.maxZoomLevel = 2;
this.initConfig = {
loading: true,
zoomLevel: 0,
zoomDetail: 1,
zoomCoords: this.defaultCoords,
mapsToRender: [],
navigator: {},
geo: null,
errorMsg: null,
};
this.state = this.initConfig;
}
componentDidMount() {
this.appInit(this.props.branch);
}
componentWillReceiveProps(newProps) {
// reset everything to initial state and re-init app
if (newProps.branch !== this.props.branch) {
this.setState(this.initConfig);
this.appInit(newProps.branch);
}
}
appInit(branch) {
let mapData = [];
// get all maps as promises, either via localstorage if present or via ajax
this.mapsToLoad.forEach((map, index) => {
const storageKey = `ep18-${map}-${branch}`;
const isMapInLocalstorage = localStorage.getItem(storageKey);
let mapPromise = isMapInLocalstorage
? this.getMapFromLocalStorage(storageKey)
: this.getMapViaAjax(branch, map, storageKey, index);
mapData.push(mapPromise);
});
Promise.all(mapData).then((mapDataArray) => {
// cant be sure about ordering of returning array
// so sort by order property to show map from innermost to outermost
// in our render function
const mapsToRender = mapDataArray.sort((a, b) => b.order - a.order);
// all maps retrieved and formatted, so save to state and let's go ...
this.setState({ mapsToRender });
this.hideSpinner();
this.notificateAmpIframe();
});
}
getMapFromLocalStorage(key) {
// retrieve formatted map data from localstorage as promise
return new Promise((resolve, reject) => {
const mapInLocalStorage = localStorage.getItem(key);
resolve(JSON.parse(mapInLocalStorage));
});
}
getMapViaAjax(branch, map, storageKey, index) {
// load config files with parent id for every shape (not for circoscrizioni. They dont have parents!)
let fileName;
if (map === "plurinominale") fileName = "pluri_to_circoscrizione";
if (map === "uninominale") fileName = "uni_to_pluri";
//let configPath = `${this.parentConfigPath}/${fileName}_${branch}.json`;
let configPath = `${this.props.mapPath}/${branch}/${fileName}_${branch}.json`;
// resolve promise with formatted map data and save in localstorage for repeated views
return new Promise((resolve, reject) => {
const getMap = () =>
request.get(
this.props.mapPath + "/" + branch + "/" + map + "_" + branch + ".json"
);
const getShapeParents = () =>
map !== "circoscrizioni" ? request.get(configPath) : { data: "" };
request.all([getMap(), getShapeParents()]).then(
request.spread((resp, parents) => {
const formattedData = this.formatMapData(
resp.data,
parents.data,
branch
);
const mapObj = {
order: index,
mapType: map,
mapData: formattedData,
};
const storageItem = JSON.stringify(mapObj);
localStorage.setItem(storageKey, storageItem);
resolve(mapObj);
})
);
});
}
formatMapData(rawData, parentsData, branch) {
// preprocess raw map data before saving
const mapType = Object.keys(rawData.objects)[0];
const features = feature(
rawData,
rawData.objects[Object.keys(rawData.objects)[0]]
).features;
const shapes = features.map((feat) => {
// get shape center coordinates
feat.shapeCenterCoords = geoCentroid(feat);
switch (mapType) {
case "circoscrizioni_camera":
// additional clean circ. camera xx_abc... --> abc...
feat.shapeId = feat.properties.CIRC_COD;
feat.label = feat.properties.CIRC_DEN;
feat.navigator = {
circoscrizione: feat.properties.CIRC_DEN,
plurinominale: null,
uninominale: null,
};
break;
case "circoscrizioni_senato":
feat.shapeId = feat.properties.COD_REG;
feat.label = feat.properties.DEN_REG;
feat.navigator = {
circoscrizione: feat.properties.DEN_REG,
plurinominale: null,
uninominale: null,
};
break;
case "plurinominale_camera":
feat.shapeId = "CP" + feat.properties.CP20_COD;
feat.label = feat.properties.CP20_DEN;
feat.navigator = {
circoscrizione: feat.properties.CP20_DEN.replace(
/\s{0,1}-\s{0,1}(\s{0,1}\w{1,2}\d{1,2})/g,
""
),
plurinominale: feat.properties.CP20_DEN,
uninominale: null,
};
break;
case "plurinominale_senato":
feat.shapeId = "SP" + feat.properties.SP20_COD;
feat.label = feat.properties.SP20_DEN;
feat.navigator = {
circoscrizione: feat.properties.SP20_DEN.replace(
/\s{0,1}-\s{0,1}(\s{0,1}\w{1,2}\d{1,2})/g,
""
),
plurinominale: feat.properties.SP20_DEN,
};
break;
case "uninominale_camera":
feat.shapeId = feat.properties.CU20_COD;
const pluriCamera = getShapeFromLocalMap(
"camera",
2,
parentsData[feat.shapeId]
);
feat.label = `${feat.properties.CU20_DEN}`;
feat.navigator = {
circoscrizione: feat.properties.CU20_DEN.replace(
/\s{0,1}-\s{0,1}(\s{0,1}\w{1,2}\d{1,2})/g,
""
),
plurinominale: pluriCamera ? pluriCamera.label : "", //feat.properties.CU20_DEN,
uninominale: `${feat.properties.CU20_DEN}`,
};
break;
case "uninominale_senato":
feat.shapeId = feat.properties.SU20_COD;
const pluriSenato = getShapeFromLocalMap(
"senato",
2,
parentsData[feat.shapeId]
);
feat.label = `${feat.properties.SU20_DEN}`;
feat.navigator = {
circoscrizione: feat.properties.SU20_DEN.replace(
/\s{0,1}-\s{0,1}(\s{0,1}\w{1,2}\d{1,2})/g,
""
),
plurinominale: pluriSenato ? pluriSenato.label : "", //feat.properties.SU20_DEN,
uninominale: `${feat.properties.SU20_DEN}`,
};
break;
}
// add parent reference to every shape
feat.parent = parentsData[feat.shapeId]
? parentsData[feat.shapeId]
: null;
return feat;
});
return shapes;
}
zoomIn(shape) {
const zoomLevel = this.state.zoomLevel >= 3 ? 3 : this.state.zoomLevel + 1;
if (zoomLevel > 0) {
this.handleZoom(
shape,
zoomLevel,
shape.shapeCenterCoords,
shape.navigator
);
}
}
changeCoords(shape, coords, navigator) {
this.handleZoom(shape, this.state.zoomLevel, coords, navigator);
}
navBack(level, parent) {
const circoscrizione =
level <= 0 ? null : this.state.navigator.circoscrizione;
const plurinominale =
level <= 1 ? null : this.state.navigator.plurinominale;
const uninominale = level <= 2 ? null : this.state.navigator.uninominale;
const navigator = { circoscrizione, plurinominale, uninominale };
const coords = level === 0 ? this.defaultCoords : this.state.zoomCoords;
const shape = parent
? getShapeFromLocalMap(this.props.branch, level, this.state.geo.parent)
: null;
this.handleZoom(shape, level, coords, navigator);
}
handleZoom(geo, zoomLevel, zoomCoords, navigator) {
let zoomDetail;
switch (zoomLevel) {
case 0:
zoomDetail = 1;
break;
case 3:
zoomDetail = 10;
break;
default:
zoomDetail = zoomLevel * 2;
break;
}
// split setState in 2 tranches to allow smooth zoom out animation
this.setState(
{
geo,
zoomLevel,
navigator,
},
function () {
// wrap in setTimeout 0 to avoid map misplacements
setTimeout(() => {
this.setState({
zoomCoords,
zoomDetail,
});
this.notificateAmpIframe();
}, 0);
}
);
}
changeType(evt) {
// toogle map second level on select
this.showSpinner();
const secondLevel = evt.target.value;
// load map if needed or get from cache
this.getMapData(this.props.branch, evt.target.value, (currentMap) => {
if (this.state.zoomLevel > 1) {
// we are already viewing map detail, so change currentMap too
this.setState({ currentMap, mapType: secondLevel, secondLevel });
} else {
// map has been cached, just save reference to second level map to show
this.setState({ secondLevel });
}
this.hideSpinner();
});
}
setTitle() {
const branch =
this.props.branch === "camera"
? "Camera dei Deputati"
: "Senato della Repubblica";
switch (this.state.mapType) {
case "uninominale":
var type = "Collegi Uninominali";
break;
case "plurinominale":
var type = "Collegi Plurinominali";
break;
default:
var type = "Circoscrizioni Elettorali";
break;
}
return `${branch} - ${type}`;
}
showSpinner() {
this.setState({ loading: true });
}
hideSpinner() {
this.setState({ loading: false });
}
notificateAmpIframe() {
// AMP IFRAME RESIZE (change iframe height on mobile)
console.warn("amp iframe resize ", document.body.scrollHeight);
window.parent.postMessage(
{
sentinel: "amp",
type: "embed-size",
height: document.body.scrollHeight,
},
"*"
);
}
render() {
const {
loading,
zoomLevel,
zoomDetail,
zoomCoords,
mapsToRender,
navigator,
geo,
} = this.state;
return (
<div className="mapWrapper">
<Navigator
zoomLevel={this.state.zoomLevel}
navBack={this.navBack.bind(this)}
navigator={this.state.navigator}
/>
<div className="ep18-svg-container">
<ResultsProvider
mapType={this.mapsToLoad[this.state.zoomLevel]}
remotePath={this.props.remotePath}
branch={this.props.branch}
mapPath={this.props.mapPath}
>
{(results) => (
<React.Fragment>
<div className="absoluteMap">
{mapsToRender.map((map) => {
if (zoomLevel <= map.order || map.order === 2) {
return (
<SvgMap
key={map.order}
order={map.order}
mapData={map.mapData}
mapType={map.mapType}
zoomLevel={zoomLevel}
zoomIn={this.zoomIn.bind(this)}
changeCoords={this.changeCoords.bind(this)}
zoomCoords={zoomCoords}
zoomDetail={zoomDetail}
maxZoomLevel={this.maxZoomLevel}
navigator={navigator}
branch={this.props.branch}
currentShape={geo ? geo.shapeId : null}
isNarrowWidth={this.props.isNarrowWidth}
results={results.data[this.props.branch][map.mapType]}
/>
);
}
})}
</div>
<div className="mapDetails">
<DistrictDetails
{...geo}
mapsToLoad={this.mapsToLoad}
zoomLevel={this.state.zoomLevel}
branch={this.props.branch}
results={results}
logoPath={this.props.logoPath}
visible={geo ? true : false}
/>
</div>
</React.Fragment>
)}
</ResultsProvider>
</div>
<Spinner enabled={loading} />
</div>
);
}
}
export default Maps;
And this is my App.js code:
import "../interactiveMap.css";
import React, { Component } from "react";
import PropTypes from "prop-types";
import request from "axios";
import Maps from "./Maps";
import { Ep2018ListConfig } from "../../public/ep2018_listconfig.js";
class App extends Component {
constructor(props) {
super(props);
this.branches = [
{ id: "camera", title: "Camera dei Deputati", tabLabel: "Camera" },
{ id: "senato", title: "Senato della Repubblica", tabLabel: "Senato" },
];
this.state = {
activeBranch: null,
title: "",
enableToggle: false,
width: window.innerWidth,
};
}
getChildContext() {
return {
lists: Ep2018ListConfig,
};
}
componentWillMount() {
window.addEventListener("resize", this.handleWindowSizeChange);
}
componentWillUnmount() {
window.removeEventListener("resize", this.handleWindowSizeChange);
}
componentDidMount() {
// setup app wrapper
const title =
this.props.branch === "senato"
? this.branches[1].title
: this.branches[0].title;
const enableToggle = this.props.enableToggle ? true : false;
this.setState({
activeBranch: this.props.branch || "camera",
title,
enableToggle,
});
}
toggleBranch(isTabActive) {
if (!isTabActive) {
this.setState(() => {
const isCamera = this.state.activeBranch === "senato";
return {
activeBranch: isCamera ? this.branches[0].id : this.branches[1].id,
title: isCamera ? this.branches[0].title : this.branches[1].title,
};
});
}
}
renderToggle() {
if (this.state.enableToggle) {
const { activeBranch, title } = this.state;
return (
<div className="toogleBranch">
{this.branches.map((branch) => {
const isTabActive = activeBranch === branch.id;
return (
<span
key={branch.id}
className={isTabActive ? "toggleTab active" : "toggleTab"}
onClick={() => this.toggleBranch(isTabActive)}
>
{branch.title}
</span>
);
})}
</div>
);
} else {
return <h2>{this.state.title}</h2>;
}
}
handleWindowSizeChange = () => {
this.setState({ width: window.innerWidth });
};
render() {
return (
<div className="ep2018-interactive-map ep18im">
{this.renderToggle()}
{this.state.activeBranch && (
<Maps
branch={this.state.activeBranch}
remotePath={this.props.path}
mapPath={this.props.mapPath}
logoPath={this.props.logoPath}
isNarrowWidth={this.state.width <= 700}
/>
)}
</div>
);
}
}
App.childContextTypes = {
lists: PropTypes.object,
};
export default App;
This is the output of default branch set to "senato"
This is the overlapping output when clicked on the other branch