Search code examples
javascriptd3.jstopojsonworld-map

D3 world map, some countries centroid is off


Hi I am creating React app in which I am trying to make world map using D3. I want when user hovers on specific country to appear circle on centroid of that country path, but for some countries like "USA", "France", "Norway" centroid is a bit off because parts of those countries are separated from one another.

Here is my component:

import styles from './WorldMap.module.css'
import * as d3 from "d3";
import {useEffect, useRef, useState} from "react";
import * as topojson from "topojson-client";

const WorldMap = () => {

    const svgRef = useRef(null)
    const pathRefs = useRef([])
    let [mapJson, setMapJson] = useState(null)
    let [mapTopo, setMapTopo] = useState(null)
    let [tooltipCoordinates, setTooltipCoordinates] = useState([0,0])

    useEffect(() => {
        d3.json("https://unpkg.com/[email protected]/countries-50m.json").then(data => setMapTopo(data))
    }, [])

    useEffect(() => {
        if (!mapTopo) return;
        setMapJson(topojson.feature(mapTopo, mapTopo.objects.countries))
    }, [mapTopo])

    const projection = d3.geoMercator()
        .center([0, 60])

    const path = d3.geoPath(projection);

    const handleMouseOver = (feature) => (e) => {
        const coordinates = path.centroid(feature)
        setTooltipCoordinates(coordinates)
    }

    return (
            <svg className={styles.svg__map} ref={svgRef} viewBox={"0 0 900 400"}>
                <g>
                    {mapJson?.features?.map((feature, i) => <path onMouseOver={handleMouseOver(feature)}   className={styles.svg__country} ref={el => pathRefs.current[i] = el}  key={i}  d={d3.geoPath(projection)(feature)} />)}
                    <circle cx={tooltipCoordinates[0]} cy={tooltipCoordinates[1]} r={6} fill={"#2e2eca"} />
                </g>
            </svg>
    )
}

export default WorldMap;

I am fetching countries path data as topojson then converting it to json.

css file:


.svg__map{
    width: 100%;
    min-height: 65vh;
    background-color: #cbcccc;
}

.svg__country{
    stroke-width: .5;
    stroke: #949292;
}

This is screenshot of centeroid for norway: enter image description here

Is there some fix for this, or maybe some other geo Projection for this kind of stuff?


Solution

  • I found a solution, basically you need to fetch data from some service that contains lat/long values for each country and on that values apply the projection to the coordinates , without relying on calculating centroid for each country's shape.

    This is maybe better explained in this article https://yangdanny97.github.io/blog/2019/08/24/D3-Mapmaking-Tips