@kahwee/sf-map-svg 5.1.3 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -5
- package/dist/data/README.md +1 -1
- package/dist/src/cluster-renderer.d.ts +1 -1
- package/dist/src/district-layer.d.ts +4 -4
- package/dist/src/district-layer.js +1 -1
- package/dist/src/district-style.d.ts +3 -3
- package/dist/src/explorer-data.d.ts +1 -1
- package/dist/src/guide-map.d.ts +0 -7
- package/dist/src/guide-map.js +0 -33
- package/dist/src/guide-static.d.ts +4 -4
- package/dist/src/guide-static.js +14 -11
- package/dist/src/guide.d.ts +2 -2
- package/dist/src/guide.js +1 -1
- package/dist/src/{configuration-controller.d.ts → interactive/configuration-controller.d.ts} +7 -7
- package/dist/src/{configuration-controller.js → interactive/configuration-controller.js} +2 -2
- package/dist/src/{district-controller.d.ts → interactive/district-controller.d.ts} +12 -12
- package/dist/src/{district-controller.js → interactive/district-controller.js} +9 -9
- package/dist/src/{explorer-sidebar.d.ts → interactive/explorer-sidebar.d.ts} +6 -6
- package/dist/src/{explorer-sidebar.js → interactive/explorer-sidebar.js} +4 -4
- package/dist/src/{label-coordinator.d.ts → interactive/label-coordinator.d.ts} +7 -7
- package/dist/src/{label-coordinator.js → interactive/label-coordinator.js} +2 -2
- package/dist/src/{label-renderer.d.ts → interactive/label-renderer.d.ts} +2 -2
- package/dist/src/{label-renderer.js → interactive/label-renderer.js} +2 -2
- package/dist/src/{explorer-controls.d.ts → interactive/map-controls.d.ts} +4 -4
- package/dist/src/{explorer-controls.js → interactive/map-controls.js} +4 -4
- package/dist/src/{explorer-core.d.ts → interactive/map-engine.d.ts} +7 -7
- package/dist/src/{explorer-core.js → interactive/map-engine.js} +40 -40
- package/dist/src/{explorer-shell.d.ts → interactive/map-shell.d.ts} +2 -2
- package/dist/src/{explorer-shell.js → interactive/map-shell.js} +2 -2
- package/dist/src/{marker-controller.d.ts → interactive/marker-controller.d.ts} +8 -8
- package/dist/src/{marker-controller.js → interactive/marker-controller.js} +3 -3
- package/dist/src/{marker-layer.d.ts → interactive/marker-renderer.d.ts} +3 -3
- package/dist/src/{marker-layer.js → interactive/marker-renderer.js} +2 -2
- package/dist/src/map.js +4 -4
- package/dist/src/marker-catalog.d.ts +2 -2
- package/dist/src/palettes.d.ts +2 -2
- package/dist/src/{layers.d.ts → rendering/svg-layers.d.ts} +7 -7
- package/dist/src/{layers.js → rendering/svg-layers.js} +1 -1
- package/dist/src/{map-core.d.ts → rendering/svg-map-renderer.d.ts} +7 -7
- package/dist/src/{map-core.js → rendering/svg-map-renderer.js} +6 -6
- package/dist/src/static-animation.d.ts +2 -2
- package/dist/src/static-labels.d.ts +3 -3
- package/dist/src/static-labels.js +2 -2
- package/dist/src/static.d.ts +6 -6
- package/dist/src/static.js +4 -9
- package/dist/src/transit.js +1 -1
- package/dist/src/types.d.ts +13 -11
- package/docs/API.md +7 -11
- package/docs/EXAMPLES.md +2 -7
- package/docs/api-audit.md +3 -3
- package/docs/consumer-integration.md +1 -1
- package/docs/developer-guide.md +1 -6
- package/docs/migration-v6.md +57 -0
- package/llms-full.txt +132 -82
- package/llms.txt +2 -1
- package/package.json +2 -1
- /package/dist/src/{svg.d.ts → rendering/svg-primitives.d.ts} +0 -0
- /package/dist/src/{svg.js → rendering/svg-primitives.js} +0 -0
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { copyAppearance } from '
|
|
2
|
-
import { createCamera, validateCameraOptions } from '
|
|
3
|
-
import { createClusterLayer } from '
|
|
4
|
-
import { clusterPoints } from '
|
|
1
|
+
import { copyAppearance } from '../appearance.js';
|
|
2
|
+
import { createCamera, validateCameraOptions } from '../camera.js';
|
|
3
|
+
import { createClusterLayer } from '../cluster-layer.js';
|
|
4
|
+
import { clusterPoints } from '../clusters.js';
|
|
5
|
+
import { prepareDistrictStyles } from '../district-style.js';
|
|
6
|
+
import { element, svgElement } from '../dom.js';
|
|
7
|
+
import { fitBounds, projectedBounds } from '../explorer-layout.js';
|
|
8
|
+
import { controlKeys, layerKeys, normalizeFeatures, validateSwitchPatch } from '../features.js';
|
|
9
|
+
import { createFrameScheduler } from '../frame-scheduler.js';
|
|
10
|
+
import { createLayerTransitions } from '../layer-transition.js';
|
|
11
|
+
import { createMarkerCatalog } from '../marker-catalog.js';
|
|
12
|
+
import { createMarkerChooser } from '../marker-chooser.js';
|
|
13
|
+
import { createMarkerNavigation } from '../marker-navigation.js';
|
|
14
|
+
import { createNeighborhoodLayer, createNeighborhoodLookup, formatSourceLabel, } from '../neighborhood-layer.js';
|
|
15
|
+
import { createOverlayLayer } from '../overlay-layer.js';
|
|
16
|
+
import { createSFMapWithData, districtColors, getLayerPathsWithData, resolveMapColors, } from '../rendering/svg-map-renderer.js';
|
|
17
|
+
import { validateExplorerOptions } from '../validation.js';
|
|
18
|
+
import { fitViewport, validateViewport } from '../viewport.js';
|
|
5
19
|
import { createDistrictController } from './district-controller.js';
|
|
6
|
-
import { prepareDistrictStyles } from './district-style.js';
|
|
7
|
-
import { element, svgElement } from './dom.js';
|
|
8
|
-
import { createExplorerControls } from './explorer-controls.js';
|
|
9
|
-
import { fitBounds, projectedBounds } from './explorer-layout.js';
|
|
10
|
-
import { controlKeys, layerKeys, normalizeFeatures, validateSwitchPatch } from './features.js';
|
|
11
|
-
import { createFrameScheduler } from './frame-scheduler.js';
|
|
12
20
|
import { createLabelCoordinator } from './label-coordinator.js';
|
|
13
|
-
import {
|
|
14
|
-
import { createSFMapWithData, districtColors, getLayerPathsWithData, resolveMapColors, } from './map-core.js';
|
|
15
|
-
import { createMarkerCatalog } from './marker-catalog.js';
|
|
16
|
-
import { createMarkerChooser } from './marker-chooser.js';
|
|
21
|
+
import { createMapControls } from './map-controls.js';
|
|
17
22
|
import { createMarkerController } from './marker-controller.js';
|
|
18
|
-
import {
|
|
19
|
-
import { createMarkerNavigation } from './marker-navigation.js';
|
|
20
|
-
import { createNeighborhoodLayer, createNeighborhoodLookup, formatSourceLabel, } from './neighborhood-layer.js';
|
|
21
|
-
import { createOverlayLayer } from './overlay-layer.js';
|
|
22
|
-
import { validateExplorerOptions } from './validation.js';
|
|
23
|
-
import { fitViewport, validateViewport } from './viewport.js';
|
|
23
|
+
import { createMarkerRenderer } from './marker-renderer.js';
|
|
24
24
|
/** Create an offline, browser-only neighborhood explorer. Call destroy() before disposal. */
|
|
25
|
-
export function
|
|
25
|
+
export function createMapEngine(options = {}, data, ui) {
|
|
26
26
|
validateExplorerOptions(options);
|
|
27
27
|
let { source = 'realtor', mode = 'neighborhoods', labels = true, neighborhood, year = 2022, theme = 'transit', colors = {}, labelStyle = {}, areaStyle = {}, districtStyle: initialDistrictStyle, motion = false, markerEntrance = false, selectedMarkerRing, clustering = false, legend: legendOptions = {}, attribution: attributionMode = 'full', northArrow = false, scaleBar = false, layerTransitions = false, districtMorph: districtMorphOption = false, interface: chrome = 'explorer', layers = {}, selectableNeighborhoods = true, labelSize = {}, fitPadding = 24, markers: initialMarkers = [], markerRadius = 6, markerHitSize = 44, markerColor = colors.marker ?? '#245b61', selectedMarkerColor = colors.selected ?? '#f04f32', onMarkerActivate, overlays: initialOverlays = [], onOverlayActivate, style: styleOptions = {}, strings = {}, controls = {}, } = options;
|
|
28
28
|
if (!['neighborhoods', 'districts', 'basemap'].includes(mode))
|
|
@@ -188,7 +188,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
188
188
|
reducedMotion,
|
|
189
189
|
enabled,
|
|
190
190
|
fitGeometry,
|
|
191
|
-
|
|
191
|
+
syncLayerComposition,
|
|
192
192
|
scheduleLabels,
|
|
193
193
|
get year() {
|
|
194
194
|
return year;
|
|
@@ -229,7 +229,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
229
229
|
cancel: (id) => cancelAnimationFrame(id),
|
|
230
230
|
render: drawLabels,
|
|
231
231
|
});
|
|
232
|
-
const markerRenderer =
|
|
232
|
+
const markerRenderer = createMarkerRenderer();
|
|
233
233
|
const cancelEntrances = markerRenderer.cancelEntrances;
|
|
234
234
|
const camera = createCamera({
|
|
235
235
|
read: getViewport,
|
|
@@ -271,13 +271,13 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
271
271
|
refresh: {
|
|
272
272
|
selection() {
|
|
273
273
|
scheduleLabels();
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
updateNeighborhoodResults();
|
|
275
|
+
updateSelectionDetail();
|
|
276
276
|
},
|
|
277
277
|
source() {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
updateNeighborhoodResults();
|
|
279
|
+
updateSelectionDetail();
|
|
280
|
+
syncLayerComposition();
|
|
281
281
|
},
|
|
282
282
|
},
|
|
283
283
|
moveView,
|
|
@@ -466,7 +466,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
466
466
|
isDestroyed: () => destroyed,
|
|
467
467
|
});
|
|
468
468
|
const setOverlays = overlays.set;
|
|
469
|
-
const mapControls =
|
|
469
|
+
const mapControls = createMapControls(shell, {
|
|
470
470
|
svg,
|
|
471
471
|
signal: controller.signal,
|
|
472
472
|
strings,
|
|
@@ -483,7 +483,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
483
483
|
setSource,
|
|
484
484
|
selectNeighborhood,
|
|
485
485
|
selectMarker,
|
|
486
|
-
|
|
486
|
+
updateNeighborhoodResults,
|
|
487
487
|
search(query) {
|
|
488
488
|
const exact = getNeighborhood(query, neighborhoods.source);
|
|
489
489
|
const matches = searchNeighborhoods(query, neighborhoods.source);
|
|
@@ -604,16 +604,16 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
604
604
|
labelCoordinator.draw();
|
|
605
605
|
}
|
|
606
606
|
const sidebar = ui.createSidebar?.(shell, data, chrome, strings.emptyResults, searchNeighborhoods);
|
|
607
|
-
function
|
|
608
|
-
sidebar?.
|
|
607
|
+
function updateSelectionDetail() {
|
|
608
|
+
sidebar?.updateSelectionDetail({
|
|
609
609
|
mode,
|
|
610
610
|
year,
|
|
611
611
|
source: neighborhoods.source,
|
|
612
612
|
selected: neighborhoods.selected,
|
|
613
613
|
});
|
|
614
614
|
}
|
|
615
|
-
function
|
|
616
|
-
sidebar?.
|
|
615
|
+
function updateNeighborhoodResults() {
|
|
616
|
+
sidebar?.updateNeighborhoodResults({
|
|
617
617
|
mode,
|
|
618
618
|
year,
|
|
619
619
|
source: neighborhoods.source,
|
|
@@ -629,9 +629,9 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
629
629
|
validateSwitchPatch(patch, layerKeys);
|
|
630
630
|
const next = { ...layers, ...patch };
|
|
631
631
|
layers = next;
|
|
632
|
-
|
|
632
|
+
syncLayerComposition();
|
|
633
633
|
}
|
|
634
|
-
function
|
|
634
|
+
function syncLayerComposition() {
|
|
635
635
|
for (const [name, key] of [
|
|
636
636
|
['landmarks', 'landmarks'],
|
|
637
637
|
['highways', 'highways'],
|
|
@@ -715,7 +715,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
715
715
|
controls = next;
|
|
716
716
|
mapControls.update(controls);
|
|
717
717
|
markerLabel.hidden = !markerCatalog.items.length || controls.markerPicker === false;
|
|
718
|
-
|
|
718
|
+
syncLayerComposition();
|
|
719
719
|
}
|
|
720
720
|
function setMode(next, reset = true) {
|
|
721
721
|
if (destroyed)
|
|
@@ -734,8 +734,8 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
734
734
|
const districts = mode !== 'neighborhoods';
|
|
735
735
|
for (const node of [sourceLabel, searchLabel, count, results])
|
|
736
736
|
node.hidden = districts;
|
|
737
|
-
|
|
738
|
-
|
|
737
|
+
syncLayerComposition();
|
|
738
|
+
updateSelectionDetail();
|
|
739
739
|
status.textContent =
|
|
740
740
|
mode === 'basemap'
|
|
741
741
|
? 'San Francisco basemap.'
|
|
@@ -755,7 +755,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
|
|
|
755
755
|
labels = visible;
|
|
756
756
|
mapControls.updateLabels(labels);
|
|
757
757
|
root.dataset.labels = String(labels);
|
|
758
|
-
|
|
758
|
+
syncLayerComposition();
|
|
759
759
|
if (!labels) {
|
|
760
760
|
labelLayer.replaceChildren();
|
|
761
761
|
root.dataset.visibleLabels = '0';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { NeighborhoodSource } from '
|
|
2
|
-
import type { NeighborhoodExplorerOptions } from '
|
|
1
|
+
import type { NeighborhoodSource } from '../../data/types.js';
|
|
2
|
+
import type { NeighborhoodExplorerOptions } from '../types.js';
|
|
3
3
|
/** Construct shared map chrome; optional explorer content has its own factory below. */
|
|
4
4
|
export declare function createMapShell({ chrome, styleOptions, strings, legendOptions, colors, attributionMode, }: {
|
|
5
5
|
source: NeighborhoodSource;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { element } from '
|
|
2
|
-
import { sourceNames } from '
|
|
1
|
+
import { element } from '../dom.js';
|
|
2
|
+
import { sourceNames } from '../neighborhood-layer.js';
|
|
3
3
|
let explorerCount = 0;
|
|
4
4
|
/** Construct shared map chrome; optional explorer content has its own factory below. */
|
|
5
5
|
export function createMapShell({ chrome, styleOptions, strings, legendOptions, colors, attributionMode, }) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { Position } from '
|
|
2
|
-
import { type View } from '
|
|
3
|
-
import type { normalizeFeatures } from '
|
|
4
|
-
import type { createMarkerCatalog } from '
|
|
5
|
-
import type { createMarkerChooser } from '
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
1
|
+
import type { Position } from '../../data/types.js';
|
|
2
|
+
import { type View } from '../explorer-layout.js';
|
|
3
|
+
import type { normalizeFeatures } from '../features.js';
|
|
4
|
+
import type { createMarkerCatalog } from '../marker-catalog.js';
|
|
5
|
+
import type { createMarkerChooser } from '../marker-chooser.js';
|
|
6
|
+
import type { createMarkerNavigation } from '../marker-navigation.js';
|
|
7
|
+
import type { CameraOptions, MapMarker, NeighborhoodExplorerOptions } from '../types.js';
|
|
8
|
+
import type { MarkerItem, MarkerVisual } from './marker-renderer.js';
|
|
9
9
|
interface MarkerHost {
|
|
10
10
|
root: HTMLElement;
|
|
11
11
|
canvas: HTMLElement;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { validateCameraOptions } from '
|
|
2
|
-
import { fitBounds } from '
|
|
3
|
-
import { fitViewport } from '
|
|
1
|
+
import { validateCameraOptions } from '../camera.js';
|
|
2
|
+
import { fitBounds } from '../explorer-layout.js';
|
|
3
|
+
import { fitViewport } from '../viewport.js';
|
|
4
4
|
/** Own selection revisions, activation and focus recovery independently of marker rendering. */
|
|
5
5
|
export function createMarkerController(host) {
|
|
6
6
|
const { root, canvas, svg, markerLayer, markerSelect, markerLabel, markerCatalog, markerNavigation, strings, fitPadding, onMarkerActivate, invalidateClusters, drawClusters, materializeMarker, scheduleLabels, syncFeatureControls, moveView, } = host;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { normalizeFeatures } from '
|
|
2
|
-
import type { MapMarker } from '
|
|
1
|
+
import type { normalizeFeatures } from '../features.js';
|
|
2
|
+
import type { MapMarker } from '../types.js';
|
|
3
3
|
export interface MarkerVisual {
|
|
4
4
|
node: SVGGElement;
|
|
5
5
|
dot: SVGCircleElement;
|
|
@@ -15,7 +15,7 @@ export interface MarkerItem {
|
|
|
15
15
|
visual: MarkerVisual | undefined;
|
|
16
16
|
}
|
|
17
17
|
/** Own marker visuals and entrances; selection/events remain with the controller. */
|
|
18
|
-
export declare function
|
|
18
|
+
export declare function createMarkerRenderer(): {
|
|
19
19
|
cancelEntrances: () => void;
|
|
20
20
|
remove(item: MarkerItem): void;
|
|
21
21
|
update(item: MarkerItem, marker: MapMarker, point: [number, number], index: number): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { svgElement } from '
|
|
1
|
+
import { svgElement } from '../dom.js';
|
|
2
2
|
/** Own marker visuals and entrances; selection/events remain with the controller. */
|
|
3
|
-
export function
|
|
3
|
+
export function createMarkerRenderer() {
|
|
4
4
|
const records = new Set();
|
|
5
5
|
const animations = new Map();
|
|
6
6
|
function cancelEntrances() {
|
package/dist/src/map.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { copyAppearance } from './appearance.js';
|
|
2
2
|
import { validateCameraOptions } from './camera.js';
|
|
3
3
|
import { configurationPatch, expandMapOptions, prepareConfiguration } from './configuration.js';
|
|
4
|
-
import { createConfigurationController } from './configuration-controller.js';
|
|
5
|
-
import { createNeighborhoodExplorerCore } from './explorer-core.js';
|
|
6
|
-
import { createMapShell } from './explorer-shell.js';
|
|
7
4
|
import { layerKeys } from './features.js';
|
|
5
|
+
import { createConfigurationController } from './interactive/configuration-controller.js';
|
|
6
|
+
import { createMapEngine } from './interactive/map-engine.js';
|
|
7
|
+
import { createMapShell } from './interactive/map-shell.js';
|
|
8
8
|
import { assertOptions } from './validation.js';
|
|
9
9
|
/** No geography is imported. Supply a preset or your own immutable data. */
|
|
10
10
|
export function createMap(data, options = {}) {
|
|
@@ -15,7 +15,7 @@ export function createMap(data, options = {}) {
|
|
|
15
15
|
controls: options.controls,
|
|
16
16
|
appearance: options.appearance,
|
|
17
17
|
});
|
|
18
|
-
const element =
|
|
18
|
+
const element = createMapEngine({
|
|
19
19
|
mode: 'basemap',
|
|
20
20
|
...expanded,
|
|
21
21
|
interface: 'map',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { createMarkerRenderer, MarkerItem } from './interactive/marker-renderer.js';
|
|
2
2
|
import type { MapMarker } from './types.js';
|
|
3
3
|
/** Own ordered records and picker reconciliation; prepare before committing DOM changes. */
|
|
4
|
-
export declare function createMarkerCatalog(renderer: ReturnType<typeof
|
|
4
|
+
export declare function createMarkerCatalog(renderer: ReturnType<typeof createMarkerRenderer>, picker: HTMLSelectElement, project: (point: import('../data/types.js').Position) => [number, number]): {
|
|
5
5
|
readonly items: MarkerItem[];
|
|
6
6
|
prepare(markers: readonly MapMarker[], selected: string | null): {
|
|
7
7
|
selection: string | null;
|
package/dist/src/palettes.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SvgRenderOptions } from './types.js';
|
|
2
2
|
/** Shared tokens for static SVGs, interactive maps, and consumer-owned place cards. */
|
|
3
3
|
export interface MapPalette {
|
|
4
|
-
readonly colors: NonNullable<
|
|
4
|
+
readonly colors: NonNullable<SvgRenderOptions['colors']>;
|
|
5
5
|
readonly haloColor: string;
|
|
6
6
|
readonly markerColor: string;
|
|
7
7
|
readonly selectedMarkerColor: string;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { Geometry } from '
|
|
2
|
-
import type { DistrictRow } from '
|
|
3
|
-
import type { Project } from '
|
|
4
|
-
import type { BartStation, KeyRoad, Landmark } from '
|
|
5
|
-
import type { MapMarker,
|
|
1
|
+
import type { Geometry } from '../../data/types.js';
|
|
2
|
+
import type { DistrictRow } from '../data.js';
|
|
3
|
+
import type { Project } from '../geometry.js';
|
|
4
|
+
import type { BartStation, KeyRoad, Landmark } from '../overlays.js';
|
|
5
|
+
import type { MapMarker, SvgRenderOptions } from '../types.js';
|
|
6
6
|
interface LayerContext {
|
|
7
7
|
labels: boolean;
|
|
8
8
|
/** Animated static maps index items for staggering and measure lines for drawing. */
|
|
9
9
|
animated?: boolean;
|
|
10
|
-
theme: NonNullable<
|
|
10
|
+
theme: NonNullable<SvgRenderOptions['theme']>;
|
|
11
11
|
idPrefix: string;
|
|
12
|
-
colors: Required<NonNullable<
|
|
12
|
+
colors: Required<NonNullable<SvgRenderOptions['colors']>>;
|
|
13
13
|
project: Project;
|
|
14
14
|
path: (geometry: Geometry | null | undefined) => string;
|
|
15
15
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { escapeXml, number, overlayLabel, stroke } from './svg.js';
|
|
1
|
+
import { escapeXml, number, overlayLabel, stroke } from './svg-primitives.js';
|
|
2
2
|
/** Custom JavaScript/JSON rows must not introduce markup or CSS through numeric IDs. */
|
|
3
3
|
function districtId(id) {
|
|
4
4
|
if (!Number.isFinite(id))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Geometry, Position } from '
|
|
2
|
-
import type { DistrictYear,
|
|
3
|
-
export type { DistrictYear, MapMarker, MapOverlay,
|
|
4
|
-
import { type Project } from '
|
|
1
|
+
import type { Geometry, Position } from '../../data/types.js';
|
|
2
|
+
import type { DistrictYear, SvgRenderOptions } from '../types.js';
|
|
3
|
+
export type { DistrictYear, MapMarker, MapOverlay, SvgRenderOptions } from '../types.js';
|
|
4
|
+
import { type Project } from '../geometry.js';
|
|
5
5
|
export interface SFMapData {
|
|
6
6
|
coast: Geometry;
|
|
7
7
|
districts?: Readonly<Partial<Record<DistrictYear, readonly DistrictRowData[]>>>;
|
|
@@ -50,7 +50,7 @@ export interface BartStationData {
|
|
|
50
50
|
export declare const districtYears: readonly DistrictYear[];
|
|
51
51
|
export declare const districtColors: readonly string[];
|
|
52
52
|
/** Canonical geometry in the same fitted coordinate space used by renderMap. */
|
|
53
|
-
export declare function getLayerPathsWithData(options:
|
|
53
|
+
export declare function getLayerPathsWithData(options: SvgRenderOptions, data: SFMapData, complete?: boolean): {
|
|
54
54
|
year: DistrictYear;
|
|
55
55
|
viewBox: [number, number, number, number];
|
|
56
56
|
project: (coordinates: Position) => [number, number];
|
|
@@ -82,7 +82,7 @@ export declare function getLayerPathsWithData(options: SFMapOptions, data: SFMap
|
|
|
82
82
|
point: [number, number];
|
|
83
83
|
}[];
|
|
84
84
|
};
|
|
85
|
-
export declare function resolveMapColors(theme:
|
|
85
|
+
export declare function resolveMapColors(theme: SvgRenderOptions['theme'], colors?: SvgRenderOptions['colors']): {
|
|
86
86
|
water: string;
|
|
87
87
|
land: string;
|
|
88
88
|
district: string;
|
|
@@ -97,7 +97,7 @@ export declare function resolveMapColors(theme: SFMapOptions['theme'], colors?:
|
|
|
97
97
|
selected: string;
|
|
98
98
|
};
|
|
99
99
|
/** Make an offline SVG and the matching longitude/latitude projection. */
|
|
100
|
-
export declare function createSFMapWithData(options:
|
|
100
|
+
export declare function createSFMapWithData(options: SvgRenderOptions, data: SFMapData, renderLabels?: (project: Project) => string): {
|
|
101
101
|
svg: string;
|
|
102
102
|
project: (coordinates: Position) => [number, number];
|
|
103
103
|
viewBox: [number, number, number, number];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { validateMarkers, validateOverlays } from '
|
|
2
|
-
import { prepareDistrictStyles } from '
|
|
3
|
-
import { geometryPath, positions, rawProject } from '
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { escapeXml, stroke } from './svg.js';
|
|
1
|
+
import { validateMarkers, validateOverlays } from '../validation.js';
|
|
2
|
+
import { prepareDistrictStyles } from '../district-style.js';
|
|
3
|
+
import { geometryPath, positions, rawProject } from '../geometry.js';
|
|
4
|
+
import { normalizeStaticAnimation, staticAnimationStyle } from '../static-animation.js';
|
|
5
|
+
import * as layers from './svg-layers.js';
|
|
6
|
+
import { escapeXml, stroke } from './svg-primitives.js';
|
|
7
7
|
export const districtYears = Object.freeze([2002, 2012, 2022]);
|
|
8
8
|
export const districtColors = Object.freeze([
|
|
9
9
|
'#c8dce5',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SvgRenderOptions } from './types.js';
|
|
2
2
|
export interface StaticAnimation {
|
|
3
3
|
duration: number;
|
|
4
4
|
delay: number;
|
|
5
5
|
}
|
|
6
6
|
/** Validate `animation` and return timings, or null when animation is off. */
|
|
7
|
-
export declare function normalizeStaticAnimation(value:
|
|
7
|
+
export declare function normalizeStaticAnimation(value: SvgRenderOptions['animation']): StaticAnimation | null;
|
|
8
8
|
/**
|
|
9
9
|
* Scoped, self-contained CSS for an animated static map. It runs when the SVG is
|
|
10
10
|
* inserted into a page or loaded as an image, and stays still under reduced motion.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Project } from './geometry.js';
|
|
2
|
-
import type { SFMapData } from './map-
|
|
3
|
-
import type {
|
|
2
|
+
import type { SFMapData } from './rendering/svg-map-renderer.js';
|
|
3
|
+
import type { SvgRenderOptions } from './types.js';
|
|
4
4
|
/** Conservative portable metrics; the browser refines these with its actual font. */
|
|
5
5
|
export declare function estimateLabelWidth(text: string, fontSize: number): number;
|
|
6
6
|
/** Interactive overview rules, expressed entirely in screen pixels before serialization. */
|
|
7
|
-
export declare function staticLabels(data: SFMapData, options:
|
|
7
|
+
export declare function staticLabels(data: SFMapData, options: SvgRenderOptions, project: Project): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { interiorAnchor, layoutLabels, projectedBounds } from './explorer-layout.js';
|
|
2
2
|
import { labelCandidates, styleLabels } from './label-candidates.js';
|
|
3
3
|
import { spreadLabels } from './label-spread.js';
|
|
4
|
-
import { escapeXml, number } from './svg.js';
|
|
4
|
+
import { escapeXml, number } from './rendering/svg-primitives.js';
|
|
5
5
|
/** Conservative portable metrics; the browser refines these with its actual font. */
|
|
6
6
|
export function estimateLabelWidth(text, fontSize) {
|
|
7
7
|
return Array.from(text).reduce((width, char) => width +
|
|
@@ -97,7 +97,7 @@ export function staticLabels(data, options, project) {
|
|
|
97
97
|
: []),
|
|
98
98
|
...markers.map((item) => {
|
|
99
99
|
const [x, y] = point(item.position);
|
|
100
|
-
// Also cover the
|
|
100
|
+
// Also cover the static SVG marker when it is larger than its interactive counterpart.
|
|
101
101
|
const radius = Math.max(item.radius ?? 6, (item.radius ?? (item.selected ? 8 : 5)) / unit) + 3;
|
|
102
102
|
return [x - radius, y - radius, x + radius, y + radius];
|
|
103
103
|
}),
|
package/dist/src/static.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { InteractiveSFMapData } from './explorer-data.js';
|
|
2
|
-
import { type SFMapData } from './map-
|
|
3
|
-
import type {
|
|
2
|
+
import { type SFMapData } from './rendering/svg-map-renderer.js';
|
|
3
|
+
import type { StaticMapOptions } from './types.js';
|
|
4
4
|
/** Either compact static geography or the same source-aware geography used by createMap. */
|
|
5
5
|
export type StaticMapInput = SFMapData | InteractiveSFMapData;
|
|
6
|
-
export type { DistrictRowData, SFMapData as StaticMapData } from './map-
|
|
7
|
-
export type { DistrictStyle, DistrictYear,
|
|
6
|
+
export type { DistrictRowData, SFMapData as StaticMapData } from './rendering/svg-map-renderer.js';
|
|
7
|
+
export type { DistrictStyle, DistrictYear, StaticMapAppearance, StaticMapLayers, StaticMapOptions, } from './types.js';
|
|
8
8
|
/** Server-safe SVG rendering with explicit data; returns SVG plus projection helpers. */
|
|
9
|
-
export declare function renderMap(data: StaticMapInput, options?:
|
|
9
|
+
export declare function renderMap(data: StaticMapInput, options?: StaticMapOptions): {
|
|
10
10
|
svg: string;
|
|
11
11
|
project: (coordinates: import("./geometry.js").Position) => [number, number];
|
|
12
12
|
viewBox: [number, number, number, number];
|
|
13
13
|
};
|
|
14
14
|
/** Project canonical layer geometry without constructing an SVG string. */
|
|
15
|
-
export declare function getLayerPaths(data: StaticMapInput, options?:
|
|
15
|
+
export declare function getLayerPaths(data: StaticMapInput, options?: StaticMapOptions): {
|
|
16
16
|
year: import("./types.js").DistrictYear;
|
|
17
17
|
viewBox: [number, number, number, number];
|
|
18
18
|
project: (coordinates: import("./geometry.js").Position) => [number, number];
|
package/dist/src/static.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { prepareAppearance } from './appearance.js';
|
|
2
2
|
import { validateSwitchPatch } from './features.js';
|
|
3
|
-
import { createSFMapWithData, getLayerPathsWithData } from './map-
|
|
3
|
+
import { createSFMapWithData, getLayerPathsWithData, } from './rendering/svg-map-renderer.js';
|
|
4
4
|
import { normalizeStaticAnimation } from './static-animation.js';
|
|
5
5
|
import { staticLabels } from './static-labels.js';
|
|
6
6
|
import { assertOptions, validateMarkers, validateOverlays } from './validation.js';
|
|
@@ -55,15 +55,11 @@ const staticLayerKeys = [
|
|
|
55
55
|
'keyRoads',
|
|
56
56
|
'roadLabels',
|
|
57
57
|
];
|
|
58
|
-
/**
|
|
58
|
+
/** Validate the public grouped contract before preparing internal renderer options. */
|
|
59
59
|
function expandStaticOptions(options) {
|
|
60
60
|
assertOptions(options, 'static map', [
|
|
61
|
-
...staticLayerKeys,
|
|
62
61
|
'layers',
|
|
63
62
|
'appearance',
|
|
64
|
-
'theme',
|
|
65
|
-
'colors',
|
|
66
|
-
'districtStyle',
|
|
67
63
|
'width',
|
|
68
64
|
'height',
|
|
69
65
|
'padding',
|
|
@@ -77,7 +73,7 @@ function expandStaticOptions(options) {
|
|
|
77
73
|
'idPrefix',
|
|
78
74
|
'animation',
|
|
79
75
|
]);
|
|
80
|
-
for (const key of [
|
|
76
|
+
for (const key of ['labels'])
|
|
81
77
|
if (options[key] !== undefined && typeof options[key] !== 'boolean')
|
|
82
78
|
throw new TypeError(`${key} must be boolean.`);
|
|
83
79
|
for (const key of ['title', 'idPrefix'])
|
|
@@ -117,11 +113,10 @@ function expandStaticOptions(options) {
|
|
|
117
113
|
if (options.appearance !== undefined) {
|
|
118
114
|
assertOptions(options.appearance, 'static appearance', ['theme', 'colors', 'districtStyle']);
|
|
119
115
|
}
|
|
120
|
-
const { layers, appearance,
|
|
116
|
+
const { layers, appearance, ...flat } = options;
|
|
121
117
|
const supplied = (group = {}) => Object.fromEntries(Object.entries(group).filter(([, value]) => value !== undefined));
|
|
122
118
|
return {
|
|
123
119
|
...flat,
|
|
124
|
-
...prepareAppearance({}, { theme, colors, districtStyle }),
|
|
125
120
|
...supplied(layers),
|
|
126
121
|
...supplied(appearance === undefined ? undefined : prepareAppearance({}, appearance)),
|
|
127
122
|
};
|
package/dist/src/transit.js
CHANGED
|
@@ -2,7 +2,7 @@ import stations from '../data/bart-stations.json' with { type: 'json' };
|
|
|
2
2
|
import coast from '../data/coast.json' with { type: 'json' };
|
|
3
3
|
import districts from '../data/districts-2022.json' with { type: 'json' };
|
|
4
4
|
import parks from '../data/landmarks.json' with { type: 'json' };
|
|
5
|
-
import { createSFMapWithData } from './map-
|
|
5
|
+
import { createSFMapWithData } from './rendering/svg-map-renderer.js';
|
|
6
6
|
const districtData = districts;
|
|
7
7
|
const parkData = parks;
|
|
8
8
|
const stationData = stations;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type DistrictYear = 2002 | 2012 | 2022;
|
|
|
11
11
|
export interface DistrictSelection {
|
|
12
12
|
id: number;
|
|
13
13
|
year: DistrictYear;
|
|
14
|
-
district: import('./map-
|
|
14
|
+
district: import('./rendering/svg-map-renderer.js').DistrictRowData;
|
|
15
15
|
}
|
|
16
16
|
export interface DistrictStyle {
|
|
17
17
|
fill?: string;
|
|
@@ -39,10 +39,7 @@ export interface MapOverlay {
|
|
|
39
39
|
visible?: boolean;
|
|
40
40
|
label?: string;
|
|
41
41
|
}
|
|
42
|
-
export interface
|
|
43
|
-
/** Grouped presentation; supplied keys take precedence over flat compatibility options. */
|
|
44
|
-
layers?: StaticMapLayers;
|
|
45
|
-
appearance?: StaticMapAppearance;
|
|
42
|
+
export interface SvgRenderOptions {
|
|
46
43
|
theme?: 'districts' | 'transit';
|
|
47
44
|
width?: number;
|
|
48
45
|
height?: number;
|
|
@@ -56,7 +53,7 @@ export interface SFMapOptions {
|
|
|
56
53
|
neighborhoodLabels?: boolean;
|
|
57
54
|
districtFills?: boolean;
|
|
58
55
|
/** Evaluated once per district at construction/style/year updates; call setDistrictStyle again when external data changes. */
|
|
59
|
-
districtStyle?: (district: import('./map-
|
|
56
|
+
districtStyle?: (district: import('./rendering/svg-map-renderer.js').DistrictRowData) => DistrictStyle;
|
|
60
57
|
districtLabels?: boolean;
|
|
61
58
|
/** Hide all visible text labels while retaining geographic symbols and accessible titles. */
|
|
62
59
|
labels?: boolean;
|
|
@@ -96,7 +93,12 @@ export interface SFMapOptions {
|
|
|
96
93
|
}
|
|
97
94
|
/** Presentation supported by both the static and interactive renderers. */
|
|
98
95
|
export type StaticMapLayers = InteractiveLayers;
|
|
99
|
-
export type StaticMapAppearance = Pick<
|
|
96
|
+
export type StaticMapAppearance = Pick<SvgRenderOptions, 'theme' | 'colors' | 'districtStyle'>;
|
|
97
|
+
/** Public static presentation uses the same grouped vocabulary as the map controller. */
|
|
98
|
+
export type StaticMapOptions = Omit<SvgRenderOptions, keyof StaticMapLayers | keyof StaticMapAppearance> & {
|
|
99
|
+
layers?: StaticMapLayers;
|
|
100
|
+
appearance?: StaticMapAppearance;
|
|
101
|
+
};
|
|
100
102
|
/** Serializable viewport in the interactive map's fixed 800 × 800 projected space. */
|
|
101
103
|
export type MapViewport = readonly [x: number, y: number, size: number];
|
|
102
104
|
export type ExplorerMode = 'neighborhoods' | 'districts' | 'basemap';
|
|
@@ -166,8 +168,8 @@ export interface NeighborhoodExplorerOptions extends MapFeatures {
|
|
|
166
168
|
source?: NeighborhoodSource;
|
|
167
169
|
neighborhood?: string;
|
|
168
170
|
year?: DistrictYear;
|
|
169
|
-
theme?:
|
|
170
|
-
colors?:
|
|
171
|
+
theme?: SvgRenderOptions['theme'];
|
|
172
|
+
colors?: SvgRenderOptions['colors'];
|
|
171
173
|
labelStyle?: {
|
|
172
174
|
fontFamily?: string;
|
|
173
175
|
fontWeight?: number;
|
|
@@ -179,7 +181,7 @@ export interface NeighborhoodExplorerOptions extends MapFeatures {
|
|
|
179
181
|
hoverFill?: string;
|
|
180
182
|
hoverStroke?: string;
|
|
181
183
|
};
|
|
182
|
-
districtStyle?:
|
|
184
|
+
districtStyle?: SvgRenderOptions['districtStyle'];
|
|
183
185
|
legend?: {
|
|
184
186
|
builtins?: boolean;
|
|
185
187
|
hidden?: readonly ('bart' | 'park' | 'highway' | 'road')[];
|
|
@@ -245,7 +247,7 @@ export interface NeighborhoodExplorerElement extends HTMLElement {
|
|
|
245
247
|
} & CameraOptions): boolean;
|
|
246
248
|
getSelectedDistrict(): DistrictSelection | null;
|
|
247
249
|
setDistrictYear(year: DistrictYear, options?: CameraOptions): void;
|
|
248
|
-
setDistrictStyle(style:
|
|
250
|
+
setDistrictStyle(style: SvgRenderOptions['districtStyle']): void;
|
|
249
251
|
setSource(source: NeighborhoodSource): void;
|
|
250
252
|
setMode(mode: ExplorerMode): void;
|
|
251
253
|
setLabels(visible: boolean): void;
|