Files
stats/apps/start/src/components/realtime/map/map.helpers.tsx
Carl-Gerhard Lindesvärd 81a7e5d62e feat: dashboard v2, esm, upgrades (#211)
* esm

* wip

* wip

* wip

* wip

* wip

* wip

* subscription notice

* wip

* wip

* wip

* fix envs

* fix: update docker build

* fix

* esm/types

* delete dashboard :D

* add patches to dockerfiles

* update packages + catalogs + ts

* wip

* remove native libs

* ts

* improvements

* fix redirects and fetching session

* try fix favicon

* fixes

* fix

* order and resize reportds within a dashboard

* improvements

* wip

* added userjot to dashboard

* fix

* add op

* wip

* different cache key

* improve date picker

* fix table

* event details loading

* redo onboarding completely

* fix login

* fix

* fix

* extend session, billing and improve bars

* fix

* reduce price on 10M
2025-10-16 12:27:44 +02:00

94 lines
2.8 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react';
import { useZoomPan } from 'react-simple-maps';
import type { Coordinate } from './coordinates';
export const GEO_MAP_URL =
'https://unpkg.com/world-atlas@2.0.2/countries-50m.json';
export function useAnimatedState(initialValue: number) {
const [value, setValue] = useState(initialValue);
const [target, setTarget] = useState(initialValue);
const ref = useRef<number>(0);
const animate = useCallback(() => {
ref.current = requestAnimationFrame(() => {
setValue((prevValue) => {
const diff = target - prevValue;
if (Math.abs(diff) < 0.01) {
return target; // Stop animating when close enough
}
// Adjust this factor (e.g., 0.02) to control the speed of the animation
return prevValue + diff * 0.05;
});
animate(); // Loop the animation frame
});
}, [target]);
useEffect(() => {
animate(); // Start the animation
return () => cancelAnimationFrame(ref.current!); // Cleanup the animation on unmount
}, [animate]);
useEffect(() => {
setTarget(initialValue);
}, [initialValue]);
return [value, setTarget] as const;
}
export const getBoundingBox = (coordinates: Coordinate[]) => {
const longitudes = coordinates.map((coord) => coord.long);
const latitudes = coordinates.map((coord) => coord.lat);
const minLat = Math.min(...latitudes);
const maxLat = Math.max(...latitudes);
const minLong = Math.min(...longitudes);
const maxLong = Math.max(...longitudes);
return { minLat, maxLat, minLong, maxLong };
};
export const determineZoom = (
bbox: ReturnType<typeof getBoundingBox>,
aspectRatio = 1.0,
): number => {
const latDiff = bbox.maxLat - bbox.minLat;
const longDiff = bbox.maxLong - bbox.minLong;
// Normalize longitudinal span based on latitude to correct for increasing distortion
// towards the poles in a Mercator projection.
const avgLat = (bbox.maxLat + bbox.minLat) / 2;
const longDiffAdjusted = longDiff * Math.cos((avgLat * Math.PI) / 180);
// Adjust calculations depending on the aspect ratio.
const maxDiff =
aspectRatio > 1
? Math.max(latDiff / aspectRatio, longDiffAdjusted) // Wider than tall
: Math.max(latDiff, longDiffAdjusted * aspectRatio); // Taller than wide
// Adjust zoom level scaling factor based on application or testing.
const zoom = Math.max(1, Math.min(20, 200 / maxDiff));
return zoom;
};
export function CustomZoomableGroup({
zoom,
center,
children,
}: {
zoom: number;
center: [number, number];
children: React.ReactNode;
}) {
const { mapRef, transformString } = useZoomPan({
center: center,
zoom,
filterZoomEvent: () => false,
});
return (
<g ref={mapRef}>
<g transform={transformString}>{children}</g>
</g>
);
}