umami/src/components/metrics/WorldMap.tsx

89 lines
3.1 KiB
TypeScript
Raw Normal View History

import { useState, useMemo } from 'react';
2020-09-20 08:33:39 +00:00
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
2020-08-01 10:34:56 +00:00
import classNames from 'classnames';
2022-01-14 08:39:27 +00:00
import { colord } from 'colord';
2023-04-21 15:00:42 +00:00
import HoverTooltip from 'components/common/HoverTooltip';
import { ISO_COUNTRIES, MAP_FILE } from 'lib/constants';
import useTheme from 'components/hooks/useTheme';
import useCountryNames from 'components/hooks/useCountryNames';
import useLocale from 'components/hooks/useLocale';
2023-09-02 08:58:25 +00:00
import useMessages from 'components/hooks/useMessages';
2023-04-14 05:28:29 +00:00
import { formatLongNumber } from 'lib/format';
import { percentFilter } from 'lib/filters';
2023-04-21 15:00:42 +00:00
import styles from './WorldMap.module.css';
2020-08-01 10:34:56 +00:00
2023-12-03 11:07:03 +00:00
export function WorldMap({ data = [], className }: { data?: any[]; className?: string }) {
2023-06-16 03:15:31 +00:00
const [tooltip, setTooltipPopup] = useState();
const { theme, colors } = useTheme();
const { locale } = useLocale();
2023-09-02 08:58:25 +00:00
const { formatMessage, labels } = useMessages();
2020-09-30 23:27:27 +00:00
const countryNames = useCountryNames(locale);
2023-09-02 08:58:25 +00:00
const visitorsLabel = formatMessage(labels.visitors).toLocaleLowerCase(locale);
2023-04-14 06:05:54 +00:00
const metrics = useMemo(() => (data ? percentFilter(data) : []), [data]);
2020-08-01 10:34:56 +00:00
2023-12-03 11:07:03 +00:00
function getFillColor(code: string) {
2020-09-20 08:33:39 +00:00
if (code === 'AQ') return;
2023-04-14 05:28:29 +00:00
const country = metrics?.find(({ x }) => x === code);
2020-08-01 10:34:56 +00:00
2020-09-20 08:33:39 +00:00
if (!country) {
return colors.map.fillColor;
2020-09-20 08:33:39 +00:00
}
return colord(colors.map.baseColor)
2022-01-14 08:39:27 +00:00
[theme === 'light' ? 'lighten' : 'darken'](0.4 * (1.0 - country.z / 100))
.toHex();
2020-08-01 10:34:56 +00:00
}
2020-09-20 08:33:39 +00:00
function getOpacity(code) {
return code === 'AQ' ? 0 : 1;
2020-08-01 10:34:56 +00:00
}
2020-09-30 23:27:27 +00:00
function handleHover(code) {
2020-09-20 08:33:39 +00:00
if (code === 'AQ') return;
2023-04-14 05:28:29 +00:00
const country = metrics?.find(({ x }) => x === code);
2023-12-03 11:07:03 +00:00
setTooltipPopup(
`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} ${visitorsLabel}` as any,
);
2020-08-01 10:34:56 +00:00
}
return (
2020-09-20 08:33:39 +00:00
<div
className={classNames(styles.container, className)}
data-tip=""
data-for="world-map-tooltip"
>
<ComposableMap projection="geoMercator">
2020-08-01 10:34:56 +00:00
<ZoomableGroup zoom={0.8} minZoom={0.7} center={[0, 40]}>
2023-09-29 12:29:22 +00:00
<Geographies geography={`${process.env.basePath}${MAP_FILE}`}>
2020-08-01 10:34:56 +00:00
{({ geographies }) => {
return geographies.map(geo => {
2021-03-27 02:47:28 +00:00
const code = ISO_COUNTRIES[geo.id];
2020-08-01 10:34:56 +00:00
return (
<Geography
key={geo.rsmKey}
geography={geo}
fill={getFillColor(code)}
stroke={colors.map.strokeColor}
2020-09-20 08:33:39 +00:00
opacity={getOpacity(code)}
2020-08-01 10:34:56 +00:00
style={{
default: { outline: 'none' },
hover: { outline: 'none', fill: colors.map.hoverColor },
2020-08-01 10:34:56 +00:00
pressed: { outline: 'none' },
}}
2020-09-30 23:27:27 +00:00
onMouseOver={() => handleHover(code)}
2023-06-16 03:15:31 +00:00
onMouseOut={() => setTooltipPopup(null)}
2020-08-01 10:34:56 +00:00
/>
);
});
}}
</Geographies>
</ZoomableGroup>
</ComposableMap>
{tooltip && <HoverTooltip>{tooltip}</HoverTooltip>}
2020-08-01 10:34:56 +00:00
</div>
);
}
export default WorldMap;