0

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 output of default branch set to "senato"

This is the overlapping output when clicked on the other branch This is the overlapping output when clicked on the other branch

shakeel70
  • 61
  • 9

1 Answers1

0

I solved it by debugging. Basically I was having duplicating IDs for maps being rendered that made two of the maps to render because of the duplicating IDs, I had to give them separate IDs for them to not overlap and render as a separate map individually. Hope this answer helps anyone having the same issue in the future. Thanks.

shakeel70
  • 61
  • 9