mirror of
https://github.com/tabler/tabler.git
synced 2026-08-29 13:21:29 +04:00
158 lines
4.3 KiB
Plaintext
158 lines
4.3 KiB
Plaintext
---
|
|
import mapsVector from '@data/maps-vector.json';
|
|
import CaptureScript from '../components/CaptureScript.astro';
|
|
|
|
interface MapData {
|
|
title?: string;
|
|
map: string;
|
|
color?: string;
|
|
filled?: boolean;
|
|
zoom?: boolean;
|
|
values?: Record<string, string>;
|
|
markers?: { name: string; coords: string }[];
|
|
lines?: { from: string; to: string }[];
|
|
}
|
|
|
|
interface Props {
|
|
mapId: string;
|
|
color?: string;
|
|
ratio?: string;
|
|
}
|
|
|
|
const { mapId, color: colorProp, ratio = '4x3' } = Astro.props;
|
|
|
|
const data = (mapsVector as Record<string, MapData>)[mapId];
|
|
const color = colorProp ?? data?.color ?? 'primary';
|
|
|
|
// scale1..scale10 — a 10-step color-mix ramp against var(--tblr-primary).
|
|
const scale = Object.fromEntries(
|
|
Array.from({ length: 10 }, (_, n) => [
|
|
`scale${n + 1}`,
|
|
`color-mix(in srgb, transparent, var(--tblr-primary) ${(n + 1) * 10}%)`,
|
|
]),
|
|
);
|
|
|
|
const markers = data?.markers?.map((marker) => ({
|
|
coords: marker.coords.split(',').map((n) => Number(n.trim())),
|
|
name: marker.name,
|
|
}));
|
|
|
|
const mapKey = `map-${mapId}`;
|
|
const mapSelector = `#map-${mapId}`;
|
|
const mapLabel = data?.title ?? 'Map';
|
|
|
|
// jsVectorMap renders its own zoom +/- buttons inside this container when
|
|
// zoom is enabled. `role="img"` flattens all descendants for assistive
|
|
// tech, which would swallow those buttons — so only apply it when there's
|
|
// no interactive chrome to hide.
|
|
const hasZoomControls = Boolean(data?.zoom);
|
|
|
|
const mapOptions = data
|
|
? {
|
|
backgroundColor: 'transparent',
|
|
regionStyle: {
|
|
initial: {
|
|
fill: 'var(--tblr-bg-surface-secondary)',
|
|
stroke: data.filled ? '#fff' : 'var(--tblr-border-color)',
|
|
strokeWidth: data.filled ? 1 : 2,
|
|
},
|
|
},
|
|
zoomOnScroll: Boolean(data.zoom),
|
|
zoomButtons: Boolean(data.zoom),
|
|
...(data.values
|
|
? { series: { regions: [{ attribute: 'fill', scale, values: data.values }] } }
|
|
: {}),
|
|
...(markers
|
|
? {
|
|
markers,
|
|
markerStyle: {
|
|
initial: {
|
|
r: 4,
|
|
stroke: '#fff',
|
|
opacity: 1,
|
|
strokeWidth: 3,
|
|
strokeOpacity: 0.5,
|
|
fill: `var(--tblr-${color})`,
|
|
},
|
|
hover: { fill: `var(--tblr-${color})`, stroke: `var(--tblr-${color})` },
|
|
},
|
|
markerLabelStyle: { initial: { fontSize: 10 } },
|
|
}
|
|
: {}),
|
|
...(data.lines
|
|
? {
|
|
lines: data.lines,
|
|
lineStyle: {
|
|
strokeDasharray: '4 4',
|
|
animation: true,
|
|
stroke: 'rgba(98, 105, 118, .75)',
|
|
strokeWidth: 0.5,
|
|
},
|
|
}
|
|
: {}),
|
|
}
|
|
: undefined;
|
|
---
|
|
|
|
{
|
|
data && (
|
|
<Fragment>
|
|
<div class={`ratio ratio-${ratio}`}>
|
|
<div>
|
|
<div
|
|
id={mapKey}
|
|
class="w-100 h-100"
|
|
role={hasZoomControls ? undefined : 'img'}
|
|
aria-label={mapLabel}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<CaptureScript>
|
|
<!-- BEGIN MAP VECTOR -->
|
|
<script is:inline define:vars={{ mapKey, mapSelector, mapName: data.map, mapOptions }}>
|
|
window.tabler_map_vector ??= {};
|
|
|
|
function createMapVector() {
|
|
const map = (window.tabler_map_vector[mapKey] = new jsVectorMap({
|
|
selector: mapSelector,
|
|
map: mapName,
|
|
...mapOptions,
|
|
// labels.markers.render is a function, not JSON-serializable — added here
|
|
// rather than baked into mapOptions.
|
|
...(mapOptions.markers ? { labels: { markers: { render: (marker) => marker.name } } } : {}),
|
|
}));
|
|
|
|
window.addEventListener('resize', () => {
|
|
map.updateSize();
|
|
});
|
|
}
|
|
|
|
function initMapVector() {
|
|
const container = document.querySelector(mapSelector);
|
|
|
|
// jsVectorMap computes its initial scale from the container's layout
|
|
// size. If the container is still 0x0 (e.g. layout hasn't settled
|
|
// yet), that scale ends up 0, and a later resize divides by it,
|
|
// producing a NaN transform. Wait for real dimensions first.
|
|
if (container.offsetWidth && container.offsetHeight) {
|
|
createMapVector();
|
|
return;
|
|
}
|
|
|
|
const observer = new ResizeObserver((entries) => {
|
|
if (entries[0].contentRect.width && entries[0].contentRect.height) {
|
|
observer.disconnect();
|
|
createMapVector();
|
|
}
|
|
});
|
|
observer.observe(container);
|
|
}
|
|
|
|
document.readyState !== 'loading' ? initMapVector() : document.addEventListener('DOMContentLoaded', initMapVector, { once: true });
|
|
</script>
|
|
<!-- END MAP VECTOR -->
|
|
</CaptureScript>
|
|
</Fragment>
|
|
)
|
|
}
|