import { type GeoMapBubble } from "./geo-map.bubbles.js"; import type { GeoMapPoint } from "./geo-map.shared.js"; import type { FlagRow } from "../shared/chart-inspect.js"; /** One merge in the linkage, ascending by `distance` across the array. */ export interface GeoMapLink { a: number; b: number; distance: number; } /** One drawn bubble's worth of places. */ export interface GeoMapCluster { /** The count-weighted centroid of the members, in viewBox units. */ x: number; y: number; /** The members' counts, summed. This is what the bubble's AREA encodes. */ count: number; /** The members' indices into the original `points`, ascending. */ members: number[]; /** The largest member (ties to the lowest index): what the bubble is named after. */ lead: number; } /** * Merge two places while their drawn centres sit within this many viewBox units. * Derived, not picked: it is the width of the two smallest bubbles the map can * draw, rings included, just touching. Below it the discs overlap and the map is * lying about where the data is; above it they are legibly separate. */ export declare const CLUSTER_GAP: number; /** The all-singleton linkage, for a map with nothing to merge. */ export declare const NO_LINKS: readonly GeoMapLink[]; /** * The Euclidean minimum spanning tree over the projected points: exactly n-1 * links, ascending by length. Built ONCE per data set, because it does not depend * on the zoom at all; only the threshold it is cut at does. * * Edges are ordered by (distance, a, b), which is a TOTAL order, so the result is * deterministic and independent of the order the points arrived in. */ export declare function geoMapLinkage(points: readonly GeoMapPoint[]): GeoMapLink[]; /** * The merge distance at a zoom level: the gap halves every time the map doubles, * because a fixed on-screen gap covers half as much of the world each time. * * EXACTLY 0 at the deepest level, so full separation is guaranteed for any data at * all. Without that, two places closer together than the floor would share a bubble * even at maximum zoom, and the map would have a class of data it simply could not * pull apart. */ export declare function geoClusterThreshold(level: number): number; /** * Single-link clustering at `geoClusterThreshold(level)`, as the connected * components of the linkage once links at or beyond that threshold are dropped. * * Clusters come back ordered by their lowest member index, and `members` ascending, * so with no links at all `clusters[i].members` is `[i]` and the array order is the * input order: the degenerate case is exactly the un-clustered map. * * Centroids are COUNT-WEIGHTED and computed in PROJECTED space. Projected, because * averaging longitudes is wrong across the antimeridian and the Natural Earth * polynomial is non-linear, so the projection of the mean is not the mean of the * projections. Weighted, so a bubble sits on its data rather than in the empty * middle of it, falling back to the plain mean when every count is zero rather * than dividing by zero. */ export declare function geoMapClusters(points: readonly GeoMapPoint[], links: readonly GeoMapLink[], level: number): GeoMapCluster[]; /** * The normalizer every bubble is sized against: the largest AGGREGATE at level 0. * Zoom-invariant on purpose, so a bubble resizes only when it itself splits and * never because some unrelated cluster on the other side of the world split. */ export declare function geoMapPeak(points: readonly GeoMapPoint[], links: readonly GeoMapLink[]): number; /** * Size each cluster's bubble against the fixed peak. Because the radius is * proportional to the square root of the share, disc AREA is exactly linear in the * count, so a parent's ink equals the sum of its children's: a split redistributes * area rather than inventing it. The MIN_RADIUS floor is the one documented * departure, and it is the reason a tiny place stays on the map at all. */ export declare function geoMapClusterBubbles(clusters: readonly GeoMapCluster[], peak: number): GeoMapBubble[]; /** Which cluster holds a point, or null when the point is not on the map. */ export declare function geoClusterOfPoint(clusters: readonly GeoMapCluster[], index: number | null): number | null; /** * What a bubble is called. A single place is its own name, exactly as today; a * group is named after its largest member with the rest counted, because showing * one city's name for four cities' worth of data would be a lie. */ export declare function geoClusterLabel(cluster: GeoMapCluster, points: readonly GeoMapPoint[]): string; /** * The flag's rows. A single place keeps today's shape exactly: one unlabelled * value. A group lists its biggest members by name and then summarises whatever it * could not fit, so the flag never grows without bound. */ export declare function geoClusterRows(cluster: GeoMapCluster, points: readonly GeoMapPoint[], formatValue: (v: number) => string): FlagRow[]; /** * What a screen reader hears when the zoom changes. A pointer-free user cannot see * bubbles merge, so the grouping has to be said out loud, not just drawn. */ export declare function geoZoomAnnouncement(factor: number, clusters: number, places: number): string; //# sourceMappingURL=geo-map.cluster.d.ts.map