Magnifying glass effect react-simple-maps

56 Views Asked by At

I'm trying to make a magnifying glass effect but I am not sure why it's offseted whenever I hover with the mouse here is the code.

im rendering a big map and a map positioned absoloutly

as you can see it checks for the mouse position and uses the container width and height with d3 projection to dertermaine where the center should be for the minimap

import { useRef, useState } from 'react';
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';

import clsx from 'clsx';
// import { ResponsiveChoropleth } from '@nivo/geo';
import { geoTimes } from 'd3-geo-projection';
import { geoPath } from 'd3-geo';

import { useFetchWaitingListCountries } from '_queries/analyticsQueries/analyticsQueries';
import { colors } from '_utils/constants/colors';
import worldCountries from '_utils/constants/world_countries.json';

export const EnableCountriesModal = () => {
  const [center, setCenter] = useState<[number, number]>([0.5, 0.5]);
  const [magnifierCenter, setMagnifierCenter] = useState<[number, number]>([0.5, 0.5]);
  const [magnifierVisible, setMagnifierVisible] = useState(false);
  const previousMagnifierVisible = useRef<boolean>(magnifierVisible);
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
  const [zoom, setZoom] = useState(1);
  const [magnifierZoom, setMagnifierZoom] = useState(zoom * 2);
  const containerRef = useRef<HTMLDivElement>(null);
  const magnifierContainerRef = useRef<HTMLDivElement>(null);

  const handleGeographyMouseOver = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    event.persist();
    if (!previousMagnifierVisible.current) {
      return;
    }
    const {
      width = 0,
      height = 0,
      left = 0,
      top = 0,
    } = containerRef?.current?.getBoundingClientRect() || {};

    const [x, y] = [
      Number(event?.nativeEvent?.clientX || 0) - (left || 0),
      Number(event?.nativeEvent?.clientY || 0) - (top || 0),
    ];

    if (x < 0 || x > width || y < 0 || y > height) {
      setMagnifierVisible(false);
      return;
    }

    setMagnifierVisible(previousMagnifierVisible.current);

    setMousePosition({ x, y });

    const projection = geoTimes()
      .translate([width / 2, height / 2])
      .scale(160);

    const [lng, lat] = projection.invert([x, y]);

    if (!lng || !lat) {
      setMagnifierVisible(false);
    }

    setMagnifierCenter([lng, lat]);
  };

  const onMove = (event: { zoom: number; coordinates: [number, number] }) => {
    setZoom(event.zoom);
    setMagnifierZoom(event.zoom * 2);
    if (event.coordinates[0] <= 0 || event.coordinates[1] <= 0) {
      return;
    }
    setCenter(event.coordinates);
  };

  return (
    <WaitingLayout className='flex flex-col md:!py-[30px] relative gap-20'>
      <div
        className='h-[520px] flex-shrink basis-0 relatvie'
        ref={containerRef}
        onMouseMove={handleGeographyMouseOver}>
        <ComposableMap
          projection='geoMercator'
          width={980}
          height={520}
          style={{
            width: '100%',
            height: 'auto',
          }}>
          <ZoomableGroup zoom={zoom} center={center} onMoveEnd={onMove}>
            <Geographies geography={worldCountries.features}>
              {({ geographies }: { geographies: any[] }) => {
                return geographies.map((geo) => {
                  const country = waitingListCountries.find((c) => c.countryCode === geo.id);
                  return (
                    <Geography
                      key={geo.rsmKey}
                      geography={geo}
                      fill={country ? getCountryColor(country) : colors.neutral.white[1]}
                      stroke='#7F7F7F'
                      strokeWidth={0.5}
                    />
                  );
                });
              }}
            </Geographies>
          </ZoomableGroup>
        </ComposableMap>
        {magnifierVisible && (
          <div
            className={clsx(
              'flex flex-col h-[260px] w-[260px] border-primary-2-light absolute rounded-full border-solid border-4 overflow-hidden bg-neutral-black-5',
            )}
            style={{
              left: mousePosition.x,
              top: mousePosition.y,
              transform: 'translate(-50%, -50%)',
            }}
            ref={magnifierContainerRef}>
            <ComposableMap
              projection='geoMercator'
              width={260}
              height={260}
              style={{
                width: '100%',
                height: 'auto',
              }}>
              <ZoomableGroup
                zoom={magnifierZoom}
                center={magnifierCenter}
                onMoveEnd={(event) => {
                  setMagnifierZoom(event.zoom);
                }}
                minZoom={zoom}
                maxZoom={zoom * 4}>
                <Geographies geography={worldCountries.features}>
                  {({ geographies }: { geographies: any[] }) => {
                    return geographies.map((geo) => {
                      return (
                        <Geography
                          key={geo.rsmKey}
                          geography={geo}
                          stroke='#7F7F7F'
                          strokeWidth={0.5}
                        />
                      );
                    });
                  }}
                </Geographies>
              </ZoomableGroup>
            </ComposableMap>
          </div>
        )}
      </div>
      <div className='flex gap-4'>
        <button
          className='flex items-center justify-center bg-primary-2-light rounded-full w-[50px] h-[50px] top-[50%] right-[50px]'
          disabled={zoom >= 4}
          onClick={() => {
            const newZoom = zoom + 0.5;
            setZoom(newZoom);
            setMagnifierZoom(newZoom * 2);
          }}>
          <span className='text-neutral-white-1 font-semibold text-[24px]'>+</span>
        </button>
        <span className='text-neutral-white-1 font-semibold text-[24px] top-[50%] right-[80px]'>
          {zoom * 100}%
        </span>
        <button
          className='flex items-center justify-center bg-primary-2-light rounded-full w-[50px] h-[50px] top-[50%] right-[120px]'
          disabled={zoom <= 1}
          onClick={() => {
            const newZoom = zoom - 0.5;
            setZoom(newZoom);
            setMagnifierZoom(newZoom * 2);
          }}>
          <span className='text-neutral-white-1 font-semibold text-[24px]'>-</span>
        </button>

        <button
          className='flex items-center justify-center bg-primary-2-light rounded-full w-[50px] h-[50px] top-[50%] left-[50px]'
          onClick={() => {
            previousMagnifierVisible.current = !previousMagnifierVisible.current;
          }}>
          <span className='text-neutral-white-1 font-semibold text-[24px]'>M</span>
        </button>
      </div>
      <CountriesLegend />
    </WaitingLayout>
  );
};

I tried to use magnifierContainerRef to figure out why is there an offset but it didn't work

0

There are 0 best solutions below