@macrostrat/column-views 3.9.0 → 3.11.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/column-views.css +10 -0
  3. package/dist/correlation-chart/main.cjs +11 -3
  4. package/dist/correlation-chart/main.cjs.map +1 -1
  5. package/dist/correlation-chart/main.d.ts +1 -1
  6. package/dist/correlation-chart/main.js +11 -3
  7. package/dist/correlation-chart/main.js.map +1 -1
  8. package/dist/index.cjs +18 -42
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.js +4 -34
  12. package/dist/index.js.map +1 -1
  13. package/dist/unit-details/age-window-tag.cjs +68 -0
  14. package/dist/unit-details/age-window-tag.cjs.map +1 -0
  15. package/dist/unit-details/age-window-tag.d.ts +26 -0
  16. package/dist/unit-details/age-window-tag.js +66 -0
  17. package/dist/unit-details/age-window-tag.js.map +1 -0
  18. package/dist/unit-details/age-window-tag.module.sass.cjs +12 -0
  19. package/dist/unit-details/age-window-tag.module.sass.cjs.map +1 -0
  20. package/dist/unit-details/age-window-tag.module.sass.js +11 -0
  21. package/dist/unit-details/age-window-tag.module.sass.js.map +1 -0
  22. package/dist/unit-details/index.d.ts +1 -0
  23. package/dist/units/boxes.cjs +3 -3
  24. package/dist/units/boxes.cjs.map +1 -1
  25. package/dist/units/boxes.js +3 -3
  26. package/dist/units/boxes.js.map +1 -1
  27. package/dist/units/index.cjs +7 -11
  28. package/dist/units/index.cjs.map +1 -1
  29. package/dist/units/index.d.ts +0 -1
  30. package/dist/units/index.js +4 -9
  31. package/dist/units/index.js.map +1 -1
  32. package/package.json +6 -27
  33. package/src/correlation-chart/main.ts +14 -2
  34. package/src/index.ts +3 -1
  35. package/src/unit-details/age-window-tag.module.sass +10 -0
  36. package/src/unit-details/age-window-tag.ts +102 -0
  37. package/src/unit-details/index.ts +1 -0
  38. package/src/units/boxes.ts +7 -4
  39. package/src/units/index.ts +9 -26
  40. package/dist/maps/column-correlation/core.cjs +0 -288
  41. package/dist/maps/column-correlation/core.cjs.map +0 -1
  42. package/dist/maps/column-correlation/core.d.ts +0 -9
  43. package/dist/maps/column-correlation/core.js +0 -286
  44. package/dist/maps/column-correlation/core.js.map +0 -1
  45. package/dist/maps/column-correlation/index.d.ts +0 -2
  46. package/dist/maps/column-correlation/state.cjs +0 -193
  47. package/dist/maps/column-correlation/state.cjs.map +0 -1
  48. package/dist/maps/column-correlation/state.d.ts +0 -62
  49. package/dist/maps/column-correlation/state.js +0 -190
  50. package/dist/maps/column-correlation/state.js.map +0 -1
  51. package/dist/maps/column-navigation/index.d.ts +0 -2
  52. package/dist/maps/column-navigation/mapbox/index.cjs +0 -215
  53. package/dist/maps/column-navigation/mapbox/index.cjs.map +0 -1
  54. package/dist/maps/column-navigation/mapbox/index.d.ts +0 -38
  55. package/dist/maps/column-navigation/mapbox/index.js +0 -213
  56. package/dist/maps/column-navigation/mapbox/index.js.map +0 -1
  57. package/dist/maps/column-navigation/mapbox/keyboard-navigation.cjs +0 -105
  58. package/dist/maps/column-navigation/mapbox/keyboard-navigation.cjs.map +0 -1
  59. package/dist/maps/column-navigation/mapbox/keyboard-navigation.d.ts +0 -8
  60. package/dist/maps/column-navigation/mapbox/keyboard-navigation.js +0 -105
  61. package/dist/maps/column-navigation/mapbox/keyboard-navigation.js.map +0 -1
  62. package/dist/maps/column-navigation/mapbox/state.cjs +0 -73
  63. package/dist/maps/column-navigation/mapbox/state.cjs.map +0 -1
  64. package/dist/maps/column-navigation/mapbox/state.d.ts +0 -23
  65. package/dist/maps/column-navigation/mapbox/state.js +0 -71
  66. package/dist/maps/column-navigation/mapbox/state.js.map +0 -1
  67. package/dist/maps/column-navigation/svg/animated.d.ts +0 -1
  68. package/dist/maps/column-navigation/svg/data-fetcher.cjs +0 -25
  69. package/dist/maps/column-navigation/svg/data-fetcher.cjs.map +0 -1
  70. package/dist/maps/column-navigation/svg/data-fetcher.d.ts +0 -6
  71. package/dist/maps/column-navigation/svg/data-fetcher.js +0 -25
  72. package/dist/maps/column-navigation/svg/data-fetcher.js.map +0 -1
  73. package/dist/maps/column-navigation/svg/index.cjs +0 -94
  74. package/dist/maps/column-navigation/svg/index.cjs.map +0 -1
  75. package/dist/maps/column-navigation/svg/index.d.ts +0 -9
  76. package/dist/maps/column-navigation/svg/index.js +0 -93
  77. package/dist/maps/column-navigation/svg/index.js.map +0 -1
  78. package/dist/maps/column-navigation/svg/layers.cjs +0 -177
  79. package/dist/maps/column-navigation/svg/layers.cjs.map +0 -1
  80. package/dist/maps/column-navigation/svg/layers.d.ts +0 -54
  81. package/dist/maps/column-navigation/svg/layers.js +0 -174
  82. package/dist/maps/column-navigation/svg/layers.js.map +0 -1
  83. package/dist/maps/column-navigation/utils/keyboard-navigation.cjs +0 -50
  84. package/dist/maps/column-navigation/utils/keyboard-navigation.cjs.map +0 -1
  85. package/dist/maps/column-navigation/utils/keyboard-navigation.d.ts +0 -10
  86. package/dist/maps/column-navigation/utils/keyboard-navigation.js +0 -50
  87. package/dist/maps/column-navigation/utils/keyboard-navigation.js.map +0 -1
  88. package/dist/maps/index.d.ts +0 -4
  89. package/dist/maps/inset-map/index.cjs +0 -71
  90. package/dist/maps/inset-map/index.cjs.map +0 -1
  91. package/dist/maps/inset-map/index.d.ts +0 -16
  92. package/dist/maps/inset-map/index.js +0 -69
  93. package/dist/maps/inset-map/index.js.map +0 -1
  94. package/dist/maps/layers/columns.cjs +0 -165
  95. package/dist/maps/layers/columns.cjs.map +0 -1
  96. package/dist/maps/layers/columns.d.ts +0 -28
  97. package/dist/maps/layers/columns.js +0 -165
  98. package/dist/maps/layers/columns.js.map +0 -1
  99. package/dist/maps/layers/index.d.ts +0 -2
  100. package/dist/maps/layers/units.cjs +0 -207
  101. package/dist/maps/layers/units.cjs.map +0 -1
  102. package/dist/maps/layers/units.d.ts +0 -14
  103. package/dist/maps/layers/units.js +0 -204
  104. package/dist/maps/layers/units.js.map +0 -1
  105. package/dist/units/colors.cjs +0 -115
  106. package/dist/units/colors.cjs.map +0 -1
  107. package/dist/units/colors.d.ts +0 -20
  108. package/dist/units/colors.js +0 -113
  109. package/dist/units/colors.js.map +0 -1
  110. package/dist/units/resolvers.cjs +0 -125
  111. package/dist/units/resolvers.cjs.map +0 -1
  112. package/dist/units/resolvers.d.ts +0 -8
  113. package/dist/units/resolvers.js +0 -125
  114. package/dist/units/resolvers.js.map +0 -1
  115. package/src/maps/column-correlation/column-correlation-map.stories.ts +0 -108
  116. package/src/maps/column-correlation/core.ts +0 -342
  117. package/src/maps/column-correlation/index.ts +0 -2
  118. package/src/maps/column-correlation/state.ts +0 -315
  119. package/src/maps/column-navigation/index.ts +0 -2
  120. package/src/maps/column-navigation/mapbox/column-navigation-map.stories.ts +0 -278
  121. package/src/maps/column-navigation/mapbox/index.ts +0 -276
  122. package/src/maps/column-navigation/mapbox/keyboard-navigation.ts +0 -133
  123. package/src/maps/column-navigation/mapbox/state.ts +0 -119
  124. package/src/maps/column-navigation/svg/animated.ts +0 -78
  125. package/src/maps/column-navigation/svg/column-navigation-svg-map.stories.ts +0 -46
  126. package/src/maps/column-navigation/svg/data-fetcher.ts +0 -34
  127. package/src/maps/column-navigation/svg/index.ts +0 -96
  128. package/src/maps/column-navigation/svg/layers.ts +0 -233
  129. package/src/maps/column-navigation/utils/keyboard-navigation.ts +0 -58
  130. package/src/maps/index.ts +0 -4
  131. package/src/maps/inset-map/index.ts +0 -87
  132. package/src/maps/inset-map/inset-map.stories.ts +0 -20
  133. package/src/maps/layers/columns.ts +0 -216
  134. package/src/maps/layers/index.ts +0 -2
  135. package/src/maps/layers/units.ts +0 -286
  136. package/src/maps/unit-map.stories.ts +0 -79
  137. package/src/units/colors.ts +0 -183
  138. package/src/units/resolvers.ts +0 -141
@@ -1,342 +0,0 @@
1
- import {
2
- useMapClickHandler,
3
- useMapEaseTo,
4
- useMapStyleOperator,
5
- useOverlayStyle,
6
- } from "@macrostrat/mapbox-react";
7
- import { LngLatBounds, Style } from "mapbox-gl";
8
- import h from "@macrostrat/hyper";
9
- import { Feature, FeatureCollection } from "geojson";
10
- import { ReactNode, useMemo, useRef } from "react";
11
- import { setGeoJSON, buildGeoJSONSource } from "@macrostrat/mapbox-utils";
12
-
13
- import { useCorrelationMapStore } from "./state";
14
- import { InsetMap, type InsetMapProps } from "../inset-map";
15
- import { BaseColumnsLayer } from "../layers";
16
- import { buildCrossSectionLayers } from "@macrostrat/map-styles";
17
-
18
- export interface CorrelationMapProps extends InsetMapProps {
19
- padding?: number;
20
- children?: ReactNode;
21
- accessToken?: string;
22
- columnColor?: string;
23
- }
24
-
25
- export function ColumnCorrelationMap(props: CorrelationMapProps) {
26
- const { padding = 50, children, columnColor, projectID, ...rest } = props;
27
-
28
- return h(
29
- InsetMap,
30
- {
31
- ...rest,
32
- boxZoom: false,
33
- dragRotate: false,
34
- },
35
- [
36
- h(ColumnsLayer, { color: columnColor }),
37
- h(SelectedColumnsLayer),
38
- h(MapClickHandler),
39
- h(ColumnInteractionHandler),
40
- h(HoveredColumnHighlight),
41
- h(ColumnZoomer, { padding }),
42
- h(SectionLine, { padding }),
43
- children,
44
- ],
45
- );
46
- }
47
-
48
- function ColumnZoomer({ padding }: { padding: number }) {
49
- /** Frame a single column when `zoomColumn` is set (e.g. header click). */
50
- const zoomColumn = useCorrelationMapStore((state) => state.zoomColumn);
51
- const zoomNonce = useCorrelationMapStore((state) => state.zoomNonce);
52
- const columns = useCorrelationMapStore((state) => state.columns);
53
-
54
- const bounds = useMemo(() => {
55
- if (zoomColumn == null || columns == null) return null;
56
- const col = columns.find((c) => c.properties?.col_id === zoomColumn);
57
- if (col?.geometry == null) return null;
58
- const b = new LngLatBounds();
59
- forEachCoordinate(col.geometry, (coord) => b.extend(coord as [number, number]));
60
- return b.isEmpty() ? null : b;
61
- // zoomNonce forces a fresh bounds object so repeat clicks re-frame
62
- }, [zoomColumn, zoomNonce, columns]);
63
-
64
- useMapEaseTo({ bounds, padding: padding * 2 });
65
- return null;
66
- }
67
-
68
- function forEachCoordinate(geometry: any, fn: (coord: number[]) => void) {
69
- const walk = (coords: any) => {
70
- if (typeof coords[0] === "number") {
71
- fn(coords);
72
- } else {
73
- for (const c of coords) walk(c);
74
- }
75
- };
76
- if (geometry?.coordinates != null) walk(geometry.coordinates);
77
- }
78
-
79
- const columnLayers = ["columns-fill", "columns-points"];
80
-
81
- function MapClickHandler() {
82
- const onClickMap = useCorrelationMapStore((state) => state.onClickMap);
83
-
84
- useMapClickHandler(
85
- (e) => {
86
- onClickMap(e, { type: "Point", coordinates: e.lngLat.toArray() });
87
- },
88
- [onClickMap],
89
- );
90
-
91
- return null;
92
- }
93
-
94
- function ColumnInteractionHandler() {
95
- /** Per-column click (manual selection) and hover highlighting. */
96
- const toggleColumn = useCorrelationMapStore((state) => state.toggleColumn);
97
- const setHoveredColumn = useCorrelationMapStore(
98
- (state) => state.setHoveredColumn,
99
- );
100
- const isManual = useCorrelationMapStore(
101
- (state) => state.selectionMode === "manual",
102
- );
103
- const hoveredRef = useRef<number | string | null>(null);
104
-
105
- // Click a column to toggle it in/out of the selection (manual mode only)
106
- useMapStyleOperator(
107
- (map) => {
108
- if (!isManual) return;
109
- const onClick = (e) => {
110
- const colID = e.features?.[0]?.properties?.col_id;
111
- if (colID != null) toggleColumn(colID);
112
- };
113
- map.on("click", columnLayers, onClick);
114
- return () => map.off("click", columnLayers, onClick);
115
- },
116
- [isManual, toggleColumn],
117
- );
118
-
119
- // Hover highlighting (via the columns layer's `hover` feature-state)
120
- useMapStyleOperator(
121
- (map) => {
122
- const clearHover = () => {
123
- if (hoveredRef.current != null) {
124
- map.setFeatureState(
125
- { source: "columns", id: hoveredRef.current },
126
- { hover: false },
127
- );
128
- hoveredRef.current = null;
129
- }
130
- };
131
- const onMove = (e) => {
132
- const f = e.features?.[0];
133
- if (f == null) return;
134
- if (hoveredRef.current !== f.id) {
135
- clearHover();
136
- map.setFeatureState({ source: "columns", id: f.id }, { hover: true });
137
- hoveredRef.current = f.id;
138
- }
139
- setHoveredColumn(f.properties?.col_id ?? null);
140
- map.getCanvas().style.cursor = "pointer";
141
- };
142
- const onLeave = () => {
143
- clearHover();
144
- setHoveredColumn(null);
145
- map.getCanvas().style.cursor = "";
146
- };
147
- map.on("mousemove", columnLayers, onMove);
148
- map.on("mouseleave", columnLayers, onLeave);
149
- return () => {
150
- map.off("mousemove", columnLayers, onMove);
151
- map.off("mouseleave", columnLayers, onLeave);
152
- clearHover();
153
- };
154
- },
155
- [setHoveredColumn],
156
- );
157
-
158
- return null;
159
- }
160
-
161
- function HoveredColumnHighlight() {
162
- /** Reflects the store's `hoveredColumn` on the map (e.g. when a column is
163
- * hovered elsewhere in the UI) using the `highlighted` feature-state. */
164
- const hoveredColumn = useCorrelationMapStore((state) => state.hoveredColumn);
165
- const columns = useCorrelationMapStore((state) => state.columns);
166
- const appliedRef = useRef<number | string | null>(null);
167
-
168
- useMapStyleOperator(
169
- (map) => {
170
- if (appliedRef.current != null) {
171
- map.setFeatureState(
172
- { source: "columns", id: appliedRef.current },
173
- { highlighted: false },
174
- );
175
- appliedRef.current = null;
176
- }
177
- if (hoveredColumn == null || columns == null) return;
178
- const col = columns.find((c) => c.properties?.col_id === hoveredColumn);
179
- const id = (col as any)?.id ?? col?.properties?.col_id;
180
- if (id == null) return;
181
- map.setFeatureState({ source: "columns", id }, { highlighted: true });
182
- appliedRef.current = id;
183
- },
184
- [hoveredColumn, columns],
185
- );
186
-
187
- return null;
188
- }
189
-
190
- function SelectedColumnsLayer() {
191
- useOverlayStyle(() => selectedColumnsStyle, []);
192
-
193
- const focusedColumns = useCorrelationMapStore(
194
- (state) => state.focusedColumns,
195
- );
196
-
197
- useMapStyleOperator(
198
- (map) => {
199
- let features = focusedColumns;
200
-
201
- const data: FeatureCollection = {
202
- type: "FeatureCollection",
203
- features,
204
- };
205
-
206
- const columnCentroidLine: Feature = {
207
- type: "Feature",
208
- geometry: {
209
- type: "LineString",
210
- coordinates: features.map(
211
- (col) => col.properties.centroid.geometry.coordinates,
212
- ),
213
- },
214
- properties: {},
215
- };
216
-
217
- setGeoJSON(map, "selected-columns", data);
218
- setGeoJSON(map, "selected-column-centroids", {
219
- type: "FeatureCollection",
220
- features: [columnCentroidLine],
221
- });
222
- },
223
- [focusedColumns],
224
- );
225
- return null;
226
- }
227
-
228
- function ColumnsLayer({ enabled = true, color }) {
229
- const columns = useCorrelationMapStore((state) => state.columns);
230
- return h(BaseColumnsLayer, { enabled, color, columns });
231
- }
232
-
233
- const selectedColumnsStyle: Style = {
234
- version: 8,
235
- sources: {
236
- "selected-columns": buildGeoJSONSource(),
237
- "selected-column-centroids": buildGeoJSONSource(),
238
- },
239
- layers: [
240
- {
241
- id: "selected-columns-fill",
242
- type: "fill",
243
- source: "selected-columns",
244
- paint: {
245
- "fill-color": "rgba(255, 0, 0, 0.1)",
246
- },
247
- },
248
- {
249
- id: "selected-column-centroids-line",
250
- type: "line",
251
- source: "selected-column-centroids",
252
- paint: {
253
- "line-color": "rgba(255, 0, 0, 0.8)",
254
- "line-width": 2,
255
- "line-dasharray": [2, 2],
256
- },
257
- },
258
- {
259
- id: "selected-column-centroids-points",
260
- type: "circle",
261
- source: "selected-column-centroids",
262
- paint: {
263
- "circle-radius": 4,
264
- "circle-color": "rgba(255, 0, 0, 0.8)",
265
- },
266
- },
267
- ],
268
- };
269
-
270
- const lineOfSectionStyle: Style = {
271
- version: 8,
272
- sources: {
273
- elevationMarker: buildGeoJSONSource(),
274
- crossSectionLine: buildGeoJSONSource(),
275
- crossSectionEndpoints: buildGeoJSONSource(),
276
- },
277
- layers: buildCrossSectionLayers(),
278
- };
279
-
280
- function SectionLine({ padding }: { padding: number }) {
281
- useOverlayStyle(() => lineOfSectionStyle, []);
282
- const focusedLine = useCorrelationMapStore((state) => state.focusedLine);
283
- const focusedColumns = useCorrelationMapStore(
284
- (state) => state.focusedColumns,
285
- );
286
-
287
- // Setup focused line (only drawn in line-of-section mode)
288
- useMapStyleOperator(
289
- (map) => {
290
- if (focusedLine == null) {
291
- // Clear any previously-drawn line (e.g. after switching to manual mode)
292
- setGeoJSON(map, "crossSectionLine", emptyFeatureCollection);
293
- setGeoJSON(map, "crossSectionEndpoints", emptyFeatureCollection);
294
- return;
295
- }
296
- const data: FeatureCollection = {
297
- type: "FeatureCollection",
298
- features: [
299
- {
300
- type: "Feature",
301
- geometry: focusedLine,
302
- properties: { id: "focusedLine" },
303
- },
304
- ],
305
- };
306
-
307
- setGeoJSON(map, "crossSectionLine", data);
308
- setGeoJSON(map, "crossSectionEndpoints", {
309
- type: "FeatureCollection",
310
- features: focusedLine.coordinates.map((coord) => ({
311
- type: "Feature",
312
- geometry: { type: "Point", coordinates: coord },
313
- properties: {},
314
- })),
315
- });
316
- },
317
- [focusedLine],
318
- );
319
-
320
- const bounds = useMemo(() => {
321
- // In line mode, frame the line; in manual mode, frame the selected columns
322
- const coords: number[][] =
323
- focusedLine != null && focusedLine.coordinates.length >= 2
324
- ? focusedLine.coordinates
325
- : focusedColumns.map((c) => c.properties.centroid.geometry.coordinates);
326
- if (coords.length < 2) return null;
327
- const bounds = new LngLatBounds();
328
- for (const coord of coords) {
329
- bounds.extend(coord as [number, number]);
330
- }
331
- return bounds;
332
- }, [focusedLine, focusedColumns]);
333
-
334
- useMapEaseTo({ bounds, padding, trackResize: true });
335
-
336
- return null;
337
- }
338
-
339
- const emptyFeatureCollection: FeatureCollection = {
340
- type: "FeatureCollection",
341
- features: [],
342
- };
@@ -1,2 +0,0 @@
1
- export * from "./core";
2
- export * from "./state";
@@ -1,315 +0,0 @@
1
- import { LineString, Point } from "geojson";
2
- import { create, StoreApi, useStore } from "zustand";
3
- import type { ColumnGeoJSONRecord } from "@macrostrat/api-types";
4
- // Turf intersection
5
- import { lineIntersect } from "@turf/line-intersect";
6
- import distance from "@turf/distance";
7
- import { nearestPointOnLine } from "@turf/nearest-point-on-line";
8
- import { centroid } from "@turf/centroid";
9
- import {
10
- createContext,
11
- useState,
12
- useContext,
13
- ReactNode,
14
- useEffect,
15
- } from "react";
16
- import h from "@macrostrat/hyper";
17
- import { createComputed } from "zustand-computed";
18
- import { useMacrostratColumns } from "@macrostrat/data-provider";
19
- import { buffer } from "@turf/buffer";
20
- import { booleanPointInPolygon } from "@turf/boolean-point-in-polygon";
21
-
22
- export type CorrelationSelectionMode = "line" | "manual";
23
-
24
- export interface CorrelationMapInput {
25
- columns: ColumnGeoJSONRecord[];
26
- focusedLine: LineString | null;
27
- /** When set, columns are selected manually (by clicking) in this order,
28
- * rather than derived from a line of section. */
29
- manualColumns?: number[] | null;
30
- }
31
-
32
- export interface CorrelationMapStore extends CorrelationMapInput {
33
- onClickMap: (event: mapboxgl.MapMouseEvent, point: Point) => void;
34
- /** Toggle a column in/out of the manual selection (used in "manual" mode) */
35
- toggleColumn: (colID: number) => void;
36
- /** Remove a column from the selection. In "line" mode this converts the
37
- * current selection to a manual one (a line can't represent an arbitrary
38
- * subset), so removing a column switches modes. */
39
- removeColumn: (colID: number) => void;
40
- /** Replace the manual column selection with an explicit ordered list (used
41
- * e.g. for drag-and-drop reordering). Switches to manual mode. */
42
- setManualColumns: (colIDs: number[]) => void;
43
- setHoveredColumn: (colID: number | null) => void;
44
- hoveredColumn: number | null;
45
- /** Request the map to frame a particular column (e.g. on header click). */
46
- zoomToColumn: (colID: number | null) => void;
47
- zoomColumn: number | null;
48
- /** Incremented on each zoom request so repeat clicks re-frame the column. */
49
- zoomNonce: number;
50
- projectID?: number;
51
- }
52
-
53
- export interface CorrelationProviderProps extends CorrelationMapInput {
54
- projectID?: number;
55
- inProcess?: boolean;
56
- columns: ColumnGeoJSONRecord[] | null;
57
- children: ReactNode;
58
- onSelectColumns?: (
59
- columns: FocusedColumnGeoJSONRecord[],
60
- line: LineString | null,
61
- ) => void;
62
- }
63
-
64
- const CorrelationStoreContext =
65
- createContext<StoreApi<CorrelationMapStore> | null>(null);
66
-
67
- type ComputedStore = {
68
- focusedColumns: FocusedColumnGeoJSONRecord[];
69
- selectionMode: CorrelationSelectionMode;
70
- };
71
-
72
- /** A computed store that will automatically update when the state changes */
73
- const computed = createComputed((state: CorrelationMapStore): ComputedStore => {
74
- const manual = state.manualColumns != null;
75
- return {
76
- selectionMode: manual ? "manual" : "line",
77
- // Focused columns are derived either from an explicit manual selection or
78
- // from the columns intersecting the line of section.
79
- focusedColumns: manual
80
- ? buildManualColumns(state.columns, state.manualColumns)
81
- : buildCorrelationColumns(state.columns, state.focusedLine),
82
- };
83
- }) as any;
84
-
85
- export function ColumnCorrelationProvider({
86
- children,
87
- columns,
88
- projectID,
89
- inProcess,
90
- focusedLine,
91
- manualColumns = null,
92
- onSelectColumns,
93
- }: CorrelationProviderProps) {
94
- const [store] = useState(() => {
95
- return create<CorrelationMapStore & ComputedStore>(
96
- computed((set, get): CorrelationMapStore => {
97
- return {
98
- focusedLine,
99
- manualColumns,
100
- hoveredColumn: null,
101
- zoomColumn: null,
102
- zoomNonce: 0,
103
- projectID,
104
- columns: null,
105
- onClickMap(event: mapboxgl.MapMouseEvent, point: Point) {
106
- const state = get();
107
- // In manual-selection mode the map click is handled per-column
108
- if (state.manualColumns != null) return;
109
- // Check if shift key is pressed
110
- const shiftKeyPressed = event.originalEvent.shiftKey;
111
- let existingCoords = state.focusedLine?.coordinates ?? [];
112
-
113
- if (existingCoords.length >= 2 && !shiftKeyPressed) {
114
- // Reset the line to zero length
115
- existingCoords = [];
116
- }
117
- set({
118
- focusedLine: {
119
- type: "LineString",
120
- coordinates: [...existingCoords, point.coordinates],
121
- },
122
- });
123
- },
124
- toggleColumn(colID: number) {
125
- const state = get();
126
- const current =
127
- state.manualColumns ??
128
- state.focusedColumns.map((d) => d.properties.col_id);
129
- const next = current.includes(colID)
130
- ? current.filter((d) => d !== colID)
131
- : [...current, colID];
132
- set({ manualColumns: next, focusedLine: null });
133
- },
134
- removeColumn(colID: number) {
135
- const state = get();
136
- // Seed from the current selection (line-derived or manual)
137
- const current =
138
- state.manualColumns ??
139
- state.focusedColumns.map((d) => d.properties.col_id);
140
- set({
141
- manualColumns: current.filter((d) => d !== colID),
142
- focusedLine: null,
143
- });
144
- },
145
- setManualColumns(colIDs: number[]) {
146
- set({ manualColumns: colIDs, focusedLine: null });
147
- },
148
- setHoveredColumn(colID: number | null) {
149
- set({ hoveredColumn: colID });
150
- },
151
- zoomToColumn(colID: number | null) {
152
- set({ zoomColumn: colID, zoomNonce: get().zoomNonce + 1 });
153
- },
154
- };
155
- }),
156
- );
157
- });
158
-
159
- // Set up the store
160
- /** TODO: move the fetching of all columns to within the map */
161
- const _columns = useMacrostratColumns(projectID, inProcess);
162
- useEffect(() => {
163
- if (_columns != null) {
164
- store.setState({ columns: _columns });
165
- }
166
- }, [_columns]);
167
-
168
- // Kind of an awkward way to do this but we need to allow the selector to run
169
- const focusedColumns = useStore(store, (state) => state.focusedColumns);
170
- const _focusedLine = useStore(store, (state) => state.focusedLine);
171
-
172
- useEffect(() => {
173
- onSelectColumns?.(focusedColumns, _focusedLine);
174
- }, [focusedColumns, _focusedLine]);
175
-
176
- return h(CorrelationStoreContext.Provider, { value: store }, children);
177
- }
178
-
179
- export function useCorrelationMapStore(
180
- selector: (state: CorrelationMapStore & ComputedStore) => any,
181
- ) {
182
- const storeApi = useContext(CorrelationStoreContext);
183
- if (storeApi == null) {
184
- throw new Error("Missing CorrelationMapProvider");
185
- }
186
- return useStore(storeApi, selector);
187
- }
188
-
189
- export interface ColumnMapLink {
190
- onColumnMouseOver: (colID: number | null) => void;
191
- onColumnClick: (colID: number) => void;
192
- }
193
-
194
- /** Wire a correlation chart's column-level events to the correlation map:
195
- * hovering a column highlights it on the map, and clicking a column header
196
- * frames it. Spread the result into `CorrelationChart`. Must be used within a
197
- * `ColumnCorrelationProvider`. */
198
- export function useColumnMapLink(): ColumnMapLink {
199
- const setHoveredColumn = useCorrelationMapStore((s) => s.setHoveredColumn);
200
- const zoomToColumn = useCorrelationMapStore((s) => s.zoomToColumn);
201
- return {
202
- onColumnMouseOver: setHoveredColumn,
203
- onColumnClick: zoomToColumn,
204
- };
205
- }
206
-
207
- function buildCorrelationColumns(
208
- columns: ColumnGeoJSONRecord[],
209
- line: LineString,
210
- ): FocusedColumnGeoJSONRecord[] {
211
- if (columns == null || line == null || line.coordinates.length < 2) {
212
- return [];
213
- }
214
- return orderColumnsByDistance(
215
- computeIntersectingColumns(columns, line),
216
- line,
217
- );
218
- }
219
-
220
- function buildManualColumns(
221
- columns: ColumnGeoJSONRecord[],
222
- ids: number[],
223
- ): FocusedColumnGeoJSONRecord[] {
224
- /** Build focused columns from an explicit, ordered list of column IDs. The
225
- * ordering line simply connects them in the order they were selected. */
226
- if (columns == null || ids == null) return [];
227
- const byID = new Map(columns.map((c) => [c.properties.col_id, c]));
228
- const result: FocusedColumnGeoJSONRecord[] = [];
229
- ids.forEach((id, i) => {
230
- const col = byID.get(id);
231
- if (col == null) return;
232
- const c = centroid(col.geometry);
233
- result.push({
234
- ...col,
235
- properties: {
236
- ...col.properties,
237
- centroid: c,
238
- nearestPointOnLine: c,
239
- distanceAlongLine: i,
240
- },
241
- });
242
- });
243
- return result;
244
- }
245
-
246
- function computeIntersectingColumns(
247
- columns: ColumnGeoJSONRecord[],
248
- line: LineString,
249
- ): ColumnGeoJSONRecord[] {
250
- if (columns == null || line == null) {
251
- return [];
252
- }
253
-
254
- /** eODP-focused process. Find buffers around line and then find columns intersecting that buffer */
255
- const bufferedLine = buffer(line, 1, { units: "degrees" });
256
-
257
- const nearbyPoints = columns.filter((col) => {
258
- if (col.geometry?.type != "Point") {
259
- return false;
260
- }
261
- return booleanPointInPolygon(col.geometry, bufferedLine);
262
- });
263
-
264
- const intersectingPolygons = columns.filter((col) => {
265
- const poly = col.geometry;
266
-
267
- // Some in-process datasets seem to have null geometries
268
- if (poly == null) return false;
269
- if (poly.type != "Polygon" && poly.type != "MultiPolygon") return false;
270
-
271
- const intersection = lineIntersect(line, poly);
272
- return intersection.features.length > 0;
273
- });
274
-
275
- return [...intersectingPolygons, ...nearbyPoints];
276
- }
277
-
278
- interface FocusedColumnGeoJSONRecord extends ColumnGeoJSONRecord {
279
- properties: {
280
- centroid: Point;
281
- nearestPointOnLine: Point;
282
- distanceAlongLine: number;
283
- } & ColumnGeoJSONRecord["properties"];
284
- }
285
-
286
- function orderColumnsByDistance(
287
- columns: ColumnGeoJSONRecord[],
288
- line: LineString,
289
- ): FocusedColumnGeoJSONRecord[] {
290
- const centroids = columns.map((col) => centroid(col.geometry));
291
- const projectedPoints = centroids.map((point) =>
292
- nearestPointOnLine(line, point),
293
- );
294
- const distances = projectedPoints.map((point) =>
295
- distance(point.geometry.coordinates, line.coordinates[0]),
296
- );
297
-
298
- let newColumns = columns.map((col, i) => {
299
- return {
300
- ...col,
301
- properties: {
302
- ...col.properties,
303
- centroid: centroids[i],
304
- nearestPointOnLine: projectedPoints[i],
305
- distanceAlongLine: distances[i],
306
- },
307
- };
308
- });
309
-
310
- return sorted(newColumns, (d) => d.properties.distanceAlongLine);
311
- }
312
-
313
- function sorted(data, accessor: (d) => number) {
314
- return data.sort((a, b) => accessor(a) - accessor(b));
315
- }
@@ -1,2 +0,0 @@
1
- export * from "./mapbox";
2
- export * from "./svg";