import { geoRobinson } from 'd3-geo-projection' import { geoPath } from 'd3-geo' import type { GeoPath, GeoProjection } from 'd3-geo' import { feature } from 'topojson-client' import type { GeometryCollection, Topology } from 'topojson-specification' import get from 'lodash/get' import uniq from 'lodash/uniq' import { computed, toValue } from 'vue' import type { ComputedRef, MaybeRefOrGetter } from 'vue' /** * Reactive inputs driving {@link useSymbolMap}. Each input is a plain value, ref * or getter so the composable adapts to how the caller wires its state. The * composable holds no DOM state: the measured `width`/`height`, the parsed * topojson and the loaded marker data are passed in from the component. */ export interface UseSymbolMapOptions { /** * The parsed TopoJSON topology used to build the base map features. */ topojson: MaybeRefOrGetter /** * The marker rows to plot, each carrying at least `longitude`/`latitude`. */ loadedData: MaybeRefOrGetter[] | null> /** * Measured outer width of the map, in pixels. */ width: MaybeRefOrGetter /** * Measured outer height of the map, in pixels. */ height: MaybeRefOrGetter /** * Padding, in pixels, kept between the fitted geometry and the map edges. */ padding: MaybeRefOrGetter /** * Name of the TopoJSON objects collection to render as map features. */ topojsonObjects: MaybeRefOrGetter /** * Path in the marker rows to the category value used for grouping/coloring. */ categoryObjectsPath: MaybeRefOrGetter /** * When true, fit the projection to the marker bounds instead of the feature * bounds. */ fitToMarkers: MaybeRefOrGetter } /** * Reactive API returned by {@link useSymbolMap}. */ export interface UseSymbolMap { /** * The GeoJSON the projection is fitted to: the markers polygon when * `fitToMarkers` is set, otherwise the topojson features collection. */ geojson: ComputedRef /** * The topojson features collection rendered as the base map layer. */ featuresGeojson: ComputedRef /** * A single polygon spanning the markers' coordinates, used to fit the * projection to the markers' bounds. */ markersGeojson: ComputedRef /** * The `[longitude, latitude]` pairs of every loaded marker. */ coordinates: ComputedRef /** * The Robinson projection fitted to the map size and the geojson bounds, * inset by `padding` on every edge. */ mapProjection: ComputedRef /** * The geo path generator bound to {@link mapProjection}. */ featurePath: ComputedRef /** * The distinct category values across the loaded markers, stringified. */ categories: ComputedRef /** * Category name to index lookup, so per-marker class assignment stays O(1) * instead of running an `indexOf` (O(n)) for every marker. */ categoryIndexByName: ComputedRef> /** * Pure geometry helper that turns a projected point and the marker's measured * bounding box into the SVG `transform` that centers and scales the marker. */ markerTransformValue: ( point: [number, number], box: { width: number, height: number }, markerWidth: number ) => string } /** * Owns the symbol-map-specific reactive geometry: it derives the GeoJSON to * render (features or marker bounds), fits a Robinson projection to the map size * with edge padding, exposes the matching geo path generator, indexes the marker * categories, and centers/scales each marker around its projected coordinate. It * holds no DOM state — the measured size, parsed topojson and marker rows are * passed in, and rendering stays in the component. * * @remarks The projection is fitted with `fitExtent` (inset by `padding`), NOT * `fitSize`, so it differs from the shared `useMapProjection` and is kept here * to preserve the symbol map's exact framing behavior. * @param options - Reactive options (see {@link UseSymbolMapOptions}). * @returns The {@link UseSymbolMap} API of derived symbol-map geometry. * @example * // Internal building block of SymbolMap; not exported from the package root. * // Inside a `