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