GeoJSON MultiPolygon Inversion Works for All Federal Districts of Russia Except Siberia in D3.js Map

24 views Asked by At

I'm working on a map visualization using D3.js and GeoJSON data for Russian federal districts. The goal is to display each district as a filled polygon fill to highlight certain areas.

I followed the advice from this Stack Overflow answer to invert the polygons, applying this transformation globally to each district:

[
  [0, 90], [180, 90], [180, -90], [0, -90], [-180, -90], [-180, 0], [-180, 90], [0, 90]
]

This approach worked well for all districts except showing a weird line for the Siberian Federal District on the top left

I tried to play with the transformation array (flipping, changing order, scaling) but haven't succeed to fix it.

Would appreciate any help!

Component code:

function Map({
  statistics,
  style = {},
  colorsForScale = ["#F4F3EE", "#969AFF", "#000"]
}) {
  useEffect(() => {}, [statistics]);

  // Map
  const [regionDescription, setRegionDescription] = useState("");
  const [regionValue, setRegionValue] = useState("");

  const { language } = useLanguage();
  const [translations, setTranslations] = useState([]);

  useEffect(() => {
    getTranslations()
      .then((data) => {
        setTranslations(data);
      })
      .catch((err) => {})
      .finally(() => {});
  }, []);

  const projection = d3geo
    .geoConicConformal()
    .scale(300)
    .center([54, 44])
    .rotate([-110, 0]);

  console.log('projection',projection)  
  const path = d3geo.geoPath().projection(projection);
  console.log('path',path)
  const values = statistics.map((item) => item.value);
  const min = Math.min(0);
  const max = Math.max(100);

  const getScale = () => {
    return [min, (min + max) / 2, max];
  };

  var colorScale = d3.scaleLinear(getScale(), colorsForScale);

  const mapElements = useMemo(() => {
    if (statistics.length > 0) {
      console.log('geoData.features',geoData.features)
      return geoData.features.map((d, index) => {

        const pathD = path(d);
        if (!pathD) return null; // Skip if path is not defined

        const relevantStatistics = statistics.filter(
          (item) => item.name === d.properties.name
        )[0];
        const color = relevantStatistics
          ? colorScale(relevantStatistics?.value)
          : "lightgrey";
        return (
          <path
            key={"map-element-" + d.properties.name}
            name={d.properties.name}
            d={path(d)}
            fill= {color}
            stroke="#0e1724"
            strokeWidth="0.5"
            strokeOpacity="0.5"
            opacity="0.9"
            onMouseEnter={(e) => {
              d3.select(e.target).attr("opacity", 1);
            
              if (language === "ru") {
                setRegionDescription(relevantStatistics.name);
              } else {
                const translation = translations.find(
                  (t) => t.name_ru === relevantStatistics.name
                );
                setRegionDescription(
                  translation ? translation.name_en : relevantStatistics.name
                );
              }
              setRegionValue(Math.round(relevantStatistics.value));
            }}
            onMouseOut={(e) => {
              d3.select(e.target).attr("opacity", 0.9);
              setRegionDescription("");
              setRegionValue("");
            }}
          />
        );
      });
    } else {
      return (
        <>
          <p>No map data.</p>
        </>
      );
    }
  }, [geoData, statistics]);

  // Legend
  const mapTooltip = useRef(null);

  useEffect(() => {
    if (!mapTooltip.current) return;
  }, [mapTooltip]);

  const setTooltipPosition = (x, y) => {
    if (!mapTooltip.current) return;
    let newX = x - mapTooltip.current.offsetWidth / 2;
    newX = Math.max(newX, 0);
    mapTooltip.current.style.transform = `translate(${newX}px, ${y + 12}px)`;
  };

  if (statistics) {
    return (
      <div
        onPointerMove={(ev) => {
          setTooltipPosition(ev.clientX, ev.clientY);
        }}
        style={{ position: "relative", display: "inline-block", ...style }}
      >
        <svg className="map">
          <g className="map">{mapElements}</g>
        </svg>

        <div
          className={`map-tooltip ${!regionDescription && "hidden"}`}
          ref={mapTooltip}
        >
          {/* <div className="tip"></div> */}
          {regionDescription && (
            <>
              <h3>{regionDescription}</h3>
              <h1>{regionValue}%</h1>
            </>
          )}
        </div>
      </div>
    );
  }
  return <></>;
}

export default Map;

0

There are 0 answers