@macrostrat/column-views 3.6.1 → 3.9.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 +144 -0
- package/dist/animated-age-window.cjs +51 -0
- package/dist/animated-age-window.cjs.map +1 -0
- package/dist/animated-age-window.d.ts +51 -0
- package/dist/animated-age-window.js +51 -0
- package/dist/animated-age-window.js.map +1 -0
- package/dist/column-views.css +118 -89
- package/dist/column.cjs +12 -3
- package/dist/column.cjs.map +1 -1
- package/dist/column.d.ts +11 -0
- package/dist/column.js +12 -3
- package/dist/column.js.map +1 -1
- package/dist/column.module.sass.cjs +15 -15
- package/dist/column.module.sass.js +15 -15
- package/dist/correlation-chart/main.cjs +156 -42
- package/dist/correlation-chart/main.cjs.map +1 -1
- package/dist/correlation-chart/main.d.ts +40 -2
- package/dist/correlation-chart/main.js +138 -23
- package/dist/correlation-chart/main.js.map +1 -1
- package/dist/correlation-chart/main.module.sass.cjs +23 -20
- package/dist/correlation-chart/main.module.sass.cjs.map +1 -1
- package/dist/correlation-chart/main.module.sass.js +23 -20
- package/dist/correlation-chart/main.module.sass.js.map +1 -1
- package/dist/correlation-chart/prepare-data.cjs +0 -1
- package/dist/correlation-chart/prepare-data.cjs.map +1 -1
- package/dist/correlation-chart/prepare-data.js +0 -1
- package/dist/correlation-chart/prepare-data.js.map +1 -1
- package/dist/data-provider/store.cjs +17 -4
- package/dist/data-provider/store.cjs.map +1 -1
- package/dist/data-provider/store.d.ts +6 -1
- package/dist/data-provider/store.js +17 -4
- package/dist/data-provider/store.js.map +1 -1
- package/dist/data-provider/unit-selection.cjs +4 -1
- package/dist/data-provider/unit-selection.cjs.map +1 -1
- package/dist/data-provider/unit-selection.d.ts +1 -1
- package/dist/data-provider/unit-selection.js +4 -1
- package/dist/data-provider/unit-selection.js.map +1 -1
- package/dist/index.cjs +16 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/maps/column-correlation/core.cjs +127 -6
- package/dist/maps/column-correlation/core.cjs.map +1 -1
- package/dist/maps/column-correlation/core.js +128 -7
- package/dist/maps/column-correlation/core.js.map +1 -1
- package/dist/maps/column-correlation/state.cjs +63 -2
- package/dist/maps/column-correlation/state.cjs.map +1 -1
- package/dist/maps/column-correlation/state.d.ts +31 -1
- package/dist/maps/column-correlation/state.js +63 -2
- package/dist/maps/column-correlation/state.js.map +1 -1
- package/dist/maps/layers/columns.cjs +5 -1
- package/dist/maps/layers/columns.cjs.map +1 -1
- package/dist/maps/layers/columns.js +5 -1
- package/dist/maps/layers/columns.js.map +1 -1
- package/dist/prepare-units/composite-scale.cjs +94 -8
- package/dist/prepare-units/composite-scale.cjs.map +1 -1
- package/dist/prepare-units/composite-scale.d.ts +39 -0
- package/dist/prepare-units/composite-scale.js +96 -10
- package/dist/prepare-units/composite-scale.js.map +1 -1
- package/dist/prepare-units/helpers.cjs +0 -1
- package/dist/prepare-units/helpers.cjs.map +1 -1
- package/dist/prepare-units/helpers.js +0 -1
- package/dist/prepare-units/helpers.js.map +1 -1
- package/dist/prepare-units/index.cjs +33 -12
- package/dist/prepare-units/index.cjs.map +1 -1
- package/dist/prepare-units/index.js +34 -13
- package/dist/prepare-units/index.js.map +1 -1
- package/dist/prepare-units/types.cjs.map +1 -1
- package/dist/prepare-units/types.d.ts +13 -0
- package/dist/prepare-units/types.js.map +1 -1
- package/dist/section.cjs +10 -2
- package/dist/section.cjs.map +1 -1
- package/dist/section.d.ts +5 -0
- package/dist/section.js +10 -2
- package/dist/section.js.map +1 -1
- package/dist/unit-details/age-range.cjs +2 -1
- package/dist/unit-details/age-range.cjs.map +1 -1
- package/dist/unit-details/age-range.js +2 -1
- package/dist/unit-details/age-range.js.map +1 -1
- package/dist/unit-details/index.d.ts +1 -1
- package/dist/unit-details/panel.cjs +2 -3
- package/dist/unit-details/panel.cjs.map +1 -1
- package/dist/unit-details/panel.js +1 -2
- package/dist/unit-details/panel.js.map +1 -1
- package/dist/unit-details/panel.module.sass.cjs +18 -22
- package/dist/unit-details/panel.module.sass.cjs.map +1 -1
- package/dist/unit-details/panel.module.sass.js +18 -22
- package/dist/unit-details/panel.module.sass.js.map +1 -1
- package/dist/unit-details/strat-names.cjs +1 -2
- package/dist/unit-details/strat-names.cjs.map +1 -1
- package/dist/unit-details/strat-names.js +1 -2
- package/dist/unit-details/strat-names.js.map +1 -1
- package/dist/units/boxes.cjs +20 -36
- package/dist/units/boxes.cjs.map +1 -1
- package/dist/units/boxes.js +22 -38
- package/dist/units/boxes.js.map +1 -1
- package/dist/units/composite.cjs +12 -12
- package/dist/units/composite.cjs.map +1 -1
- package/dist/units/composite.js +12 -12
- package/dist/units/composite.js.map +1 -1
- package/package.json +9 -9
- package/src/animated-age-window.ts +120 -0
- package/src/column.module.sass +6 -0
- package/src/column.ts +24 -0
- package/src/correlation-chart/main.module.sass +46 -2
- package/src/correlation-chart/main.ts +210 -17
- package/src/correlation-chart/prepare-data.ts +0 -1
- package/src/data-provider/store.ts +21 -2
- package/src/data-provider/unit-selection.ts +13 -2
- package/src/index.ts +1 -0
- package/src/maps/column-correlation/column-correlation-map.stories.ts +12 -0
- package/src/maps/column-correlation/core.ts +155 -9
- package/src/maps/column-correlation/state.ts +110 -2
- package/src/maps/layers/columns.ts +7 -1
- package/src/prepare-units/composite-scale.ts +210 -10
- package/src/prepare-units/helpers.ts +0 -2
- package/src/prepare-units/index.ts +68 -14
- package/src/prepare-units/types.ts +13 -0
- package/src/section.ts +15 -1
- package/src/unit-details/age-range.ts +3 -1
- package/src/unit-details/index.ts +7 -1
- package/src/unit-details/panel.module.sass +0 -8
- package/src/unit-details/panel.ts +6 -6
- package/src/unit-details/strat-names.ts +1 -1
- package/src/units/boxes.ts +33 -53
- package/src/units/composite.ts +21 -12
- package/dist/correlation-chart/stories/utils.d.ts +0 -6
- package/dist/unit-details/identifiers.cjs +0 -56
- package/dist/unit-details/identifiers.cjs.map +0 -1
- package/dist/unit-details/identifiers.d.ts +0 -31
- package/dist/unit-details/identifiers.js +0 -54
- package/dist/unit-details/identifiers.js.map +0 -1
- package/src/correlation-chart/stories/correlation-chart.stories.ts +0 -228
- package/src/correlation-chart/stories/namibia-correlation.stories.ts +0 -151
- package/src/correlation-chart/stories/stories.module.sass +0 -18
- package/src/correlation-chart/stories/utils.ts +0 -24
- package/src/unit-details/identifiers.ts +0 -67
|
@@ -118,6 +118,11 @@ export interface MacrostratColumnDataContext<T extends BaseUnit> {
|
|
|
118
118
|
totalHeight?: number;
|
|
119
119
|
axisType?: ColumnAxisType;
|
|
120
120
|
allowUnitSelection?: boolean;
|
|
121
|
+
/** True while the age window is animating; propagated to each section's
|
|
122
|
+
* `ColumnProvider` so per-frame label/pattern work can be skipped. */
|
|
123
|
+
isTransitioning?: boolean;
|
|
124
|
+
/** Hide unit labels while transitioning (perf escape hatch; default false). */
|
|
125
|
+
hideLabelsWhileTransitioning?: boolean;
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
export interface ColumnDataProviderProps<T extends BaseUnit>
|
|
@@ -138,6 +143,8 @@ export function MacrostratColumnDataProvider<T extends BaseUnit>({
|
|
|
138
143
|
allowUnitSelection,
|
|
139
144
|
onUnitSelected,
|
|
140
145
|
selectedUnit,
|
|
146
|
+
isTransitioning,
|
|
147
|
+
hideLabelsWhileTransitioning,
|
|
141
148
|
ref,
|
|
142
149
|
}: ColumnDataProviderProps<T>) {
|
|
143
150
|
/** Internal provider for Macrostrat column data.
|
|
@@ -158,8 +165,17 @@ export function MacrostratColumnDataProvider<T extends BaseUnit>({
|
|
|
158
165
|
sections,
|
|
159
166
|
totalHeight,
|
|
160
167
|
axisType,
|
|
168
|
+
isTransitioning,
|
|
169
|
+
hideLabelsWhileTransitioning,
|
|
161
170
|
};
|
|
162
|
-
}, [
|
|
171
|
+
}, [
|
|
172
|
+
units,
|
|
173
|
+
sections,
|
|
174
|
+
totalHeight,
|
|
175
|
+
axisType,
|
|
176
|
+
isTransitioning,
|
|
177
|
+
hideLabelsWhileTransitioning,
|
|
178
|
+
]);
|
|
163
179
|
|
|
164
180
|
return h(
|
|
165
181
|
MacrostratColumnStateProvider,
|
|
@@ -242,7 +258,8 @@ export function MacrostratColumnProvider(props) {
|
|
|
242
258
|
* generic concepts to Macrostrat-specific ones.
|
|
243
259
|
*/
|
|
244
260
|
|
|
245
|
-
const { axisType } =
|
|
261
|
+
const { axisType, isTransitioning, hideLabelsWhileTransitioning } =
|
|
262
|
+
useMacrostratColumnData();
|
|
246
263
|
const { units, domain, pixelScale, scale, children } = props;
|
|
247
264
|
return h(
|
|
248
265
|
ColumnProvider,
|
|
@@ -252,6 +269,8 @@ export function MacrostratColumnProvider(props) {
|
|
|
252
269
|
range: domain,
|
|
253
270
|
pixelsPerMeter: pixelScale,
|
|
254
271
|
scale,
|
|
272
|
+
isTransitioning,
|
|
273
|
+
hideLabelsWhileTransitioning,
|
|
255
274
|
},
|
|
256
275
|
children,
|
|
257
276
|
);
|
|
@@ -156,12 +156,23 @@ if (props.onClickedColumn) {
|
|
|
156
156
|
|
|
157
157
|
export function useUnitSelectionTarget(
|
|
158
158
|
unit: IUnit,
|
|
159
|
-
): [React.RefObject<HTMLElement>, boolean, (evt: Event) => void] {
|
|
159
|
+
): [React.RefObject<HTMLElement>, boolean, (evt: Event) => void, boolean] {
|
|
160
160
|
const ref = useRef<HTMLElement>(null);
|
|
161
161
|
const [selectedUnit, selectUnit] = useUnitSelection();
|
|
162
162
|
const selected = selectedUnit?.unit_id == unit.unit_id;
|
|
163
163
|
const selectedUnitElement = scope.useAtomValue(selectedUnitElementAtom);
|
|
164
164
|
|
|
165
|
+
// "Linked" units share a stratigraphic name with the selected unit (e.g.
|
|
166
|
+
// the same formation correlated across columns), but are not the selection
|
|
167
|
+
// itself. They receive a subtler highlight.
|
|
168
|
+
const selectedStratName = (selectedUnit as any)?.strat_name_id;
|
|
169
|
+
const unitStratName = (unit as any)?.strat_name_id;
|
|
170
|
+
const linked =
|
|
171
|
+
!selected &&
|
|
172
|
+
selectedStratName != null &&
|
|
173
|
+
unitStratName != null &&
|
|
174
|
+
unitStratName === selectedStratName;
|
|
175
|
+
|
|
165
176
|
const onClick = useCallback(
|
|
166
177
|
(evt: Event) => {
|
|
167
178
|
selectUnit?.(unit, ref.current);
|
|
@@ -185,7 +196,7 @@ export function useUnitSelectionTarget(
|
|
|
185
196
|
});
|
|
186
197
|
}, [selectedUnitElement]);
|
|
187
198
|
|
|
188
|
-
return [ref, selected, onClick];
|
|
199
|
+
return [ref, selected, onClick, linked];
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
export function UnitKeyboardNavigation({
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ function ColumnCorrelationMapExt(
|
|
|
15
15
|
) {
|
|
16
16
|
const {
|
|
17
17
|
focusedLine,
|
|
18
|
+
manualColumns,
|
|
18
19
|
columns,
|
|
19
20
|
onSelectColumns,
|
|
20
21
|
projectID,
|
|
@@ -26,6 +27,7 @@ function ColumnCorrelationMapExt(
|
|
|
26
27
|
ColumnCorrelationProvider,
|
|
27
28
|
{
|
|
28
29
|
focusedLine,
|
|
30
|
+
manualColumns,
|
|
29
31
|
columns,
|
|
30
32
|
onSelectColumns,
|
|
31
33
|
projectID,
|
|
@@ -63,6 +65,16 @@ export const withPreloadedCrossSection = {
|
|
|
63
65
|
},
|
|
64
66
|
};
|
|
65
67
|
|
|
68
|
+
// Manual selection: click columns to add/remove them (no line of section).
|
|
69
|
+
// Hovering highlights a column; the dashed line shows the selection order.
|
|
70
|
+
export const manualColumnSelection = {
|
|
71
|
+
args: {
|
|
72
|
+
style: { width: "800px", height: "600px" },
|
|
73
|
+
padding: 100,
|
|
74
|
+
manualColumns: [432, 490],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
66
78
|
export const nonStandardProject = {
|
|
67
79
|
args: {
|
|
68
80
|
style: { width: "800px", height: "600px" },
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { LngLatBounds, Style } from "mapbox-gl";
|
|
8
8
|
import h from "@macrostrat/hyper";
|
|
9
9
|
import { Feature, FeatureCollection } from "geojson";
|
|
10
|
-
import { ReactNode, useMemo } from "react";
|
|
10
|
+
import { ReactNode, useMemo, useRef } from "react";
|
|
11
11
|
import { setGeoJSON, buildGeoJSONSource } from "@macrostrat/mapbox-utils";
|
|
12
12
|
|
|
13
13
|
import { useCorrelationMapStore } from "./state";
|
|
@@ -36,12 +36,48 @@ export function ColumnCorrelationMap(props: CorrelationMapProps) {
|
|
|
36
36
|
h(ColumnsLayer, { color: columnColor }),
|
|
37
37
|
h(SelectedColumnsLayer),
|
|
38
38
|
h(MapClickHandler),
|
|
39
|
+
h(ColumnInteractionHandler),
|
|
40
|
+
h(HoveredColumnHighlight),
|
|
41
|
+
h(ColumnZoomer, { padding }),
|
|
39
42
|
h(SectionLine, { padding }),
|
|
40
43
|
children,
|
|
41
44
|
],
|
|
42
45
|
);
|
|
43
46
|
}
|
|
44
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
|
+
|
|
45
81
|
function MapClickHandler() {
|
|
46
82
|
const onClickMap = useCorrelationMapStore((state) => state.onClickMap);
|
|
47
83
|
|
|
@@ -55,6 +91,102 @@ function MapClickHandler() {
|
|
|
55
91
|
return null;
|
|
56
92
|
}
|
|
57
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
|
+
|
|
58
190
|
function SelectedColumnsLayer() {
|
|
59
191
|
useOverlayStyle(() => selectedColumnsStyle, []);
|
|
60
192
|
|
|
@@ -148,11 +280,17 @@ const lineOfSectionStyle: Style = {
|
|
|
148
280
|
function SectionLine({ padding }: { padding: number }) {
|
|
149
281
|
useOverlayStyle(() => lineOfSectionStyle, []);
|
|
150
282
|
const focusedLine = useCorrelationMapStore((state) => state.focusedLine);
|
|
283
|
+
const focusedColumns = useCorrelationMapStore(
|
|
284
|
+
(state) => state.focusedColumns,
|
|
285
|
+
);
|
|
151
286
|
|
|
152
|
-
// Setup focused line
|
|
287
|
+
// Setup focused line (only drawn in line-of-section mode)
|
|
153
288
|
useMapStyleOperator(
|
|
154
289
|
(map) => {
|
|
155
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);
|
|
156
294
|
return;
|
|
157
295
|
}
|
|
158
296
|
const data: FeatureCollection = {
|
|
@@ -180,17 +318,25 @@ function SectionLine({ padding }: { padding: number }) {
|
|
|
180
318
|
);
|
|
181
319
|
|
|
182
320
|
const bounds = useMemo(() => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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]);
|
|
189
330
|
}
|
|
190
331
|
return bounds;
|
|
191
|
-
}, [focusedLine]);
|
|
332
|
+
}, [focusedLine, focusedColumns]);
|
|
192
333
|
|
|
193
334
|
useMapEaseTo({ bounds, padding, trackResize: true });
|
|
194
335
|
|
|
195
336
|
return null;
|
|
196
337
|
}
|
|
338
|
+
|
|
339
|
+
const emptyFeatureCollection: FeatureCollection = {
|
|
340
|
+
type: "FeatureCollection",
|
|
341
|
+
features: [],
|
|
342
|
+
};
|
|
@@ -19,13 +19,34 @@ import { useMacrostratColumns } from "@macrostrat/data-provider";
|
|
|
19
19
|
import { buffer } from "@turf/buffer";
|
|
20
20
|
import { booleanPointInPolygon } from "@turf/boolean-point-in-polygon";
|
|
21
21
|
|
|
22
|
+
export type CorrelationSelectionMode = "line" | "manual";
|
|
23
|
+
|
|
22
24
|
export interface CorrelationMapInput {
|
|
23
25
|
columns: ColumnGeoJSONRecord[];
|
|
24
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;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
export interface CorrelationMapStore extends CorrelationMapInput {
|
|
28
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;
|
|
29
50
|
projectID?: number;
|
|
30
51
|
}
|
|
31
52
|
|
|
@@ -45,13 +66,19 @@ const CorrelationStoreContext =
|
|
|
45
66
|
|
|
46
67
|
type ComputedStore = {
|
|
47
68
|
focusedColumns: FocusedColumnGeoJSONRecord[];
|
|
69
|
+
selectionMode: CorrelationSelectionMode;
|
|
48
70
|
};
|
|
49
71
|
|
|
50
72
|
/** A computed store that will automatically update when the state changes */
|
|
51
73
|
const computed = createComputed((state: CorrelationMapStore): ComputedStore => {
|
|
74
|
+
const manual = state.manualColumns != null;
|
|
52
75
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
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),
|
|
55
82
|
};
|
|
56
83
|
}) as any;
|
|
57
84
|
|
|
@@ -61,6 +88,7 @@ export function ColumnCorrelationProvider({
|
|
|
61
88
|
projectID,
|
|
62
89
|
inProcess,
|
|
63
90
|
focusedLine,
|
|
91
|
+
manualColumns = null,
|
|
64
92
|
onSelectColumns,
|
|
65
93
|
}: CorrelationProviderProps) {
|
|
66
94
|
const [store] = useState(() => {
|
|
@@ -68,10 +96,16 @@ export function ColumnCorrelationProvider({
|
|
|
68
96
|
computed((set, get): CorrelationMapStore => {
|
|
69
97
|
return {
|
|
70
98
|
focusedLine,
|
|
99
|
+
manualColumns,
|
|
100
|
+
hoveredColumn: null,
|
|
101
|
+
zoomColumn: null,
|
|
102
|
+
zoomNonce: 0,
|
|
71
103
|
projectID,
|
|
72
104
|
columns: null,
|
|
73
105
|
onClickMap(event: mapboxgl.MapMouseEvent, point: Point) {
|
|
74
106
|
const state = get();
|
|
107
|
+
// In manual-selection mode the map click is handled per-column
|
|
108
|
+
if (state.manualColumns != null) return;
|
|
75
109
|
// Check if shift key is pressed
|
|
76
110
|
const shiftKeyPressed = event.originalEvent.shiftKey;
|
|
77
111
|
let existingCoords = state.focusedLine?.coordinates ?? [];
|
|
@@ -87,6 +121,36 @@ export function ColumnCorrelationProvider({
|
|
|
87
121
|
},
|
|
88
122
|
});
|
|
89
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
|
+
},
|
|
90
154
|
};
|
|
91
155
|
}),
|
|
92
156
|
);
|
|
@@ -122,6 +186,24 @@ export function useCorrelationMapStore(
|
|
|
122
186
|
return useStore(storeApi, selector);
|
|
123
187
|
}
|
|
124
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
|
+
|
|
125
207
|
function buildCorrelationColumns(
|
|
126
208
|
columns: ColumnGeoJSONRecord[],
|
|
127
209
|
line: LineString,
|
|
@@ -135,6 +217,32 @@ function buildCorrelationColumns(
|
|
|
135
217
|
);
|
|
136
218
|
}
|
|
137
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
|
+
|
|
138
246
|
function computeIntersectingColumns(
|
|
139
247
|
columns: ColumnGeoJSONRecord[],
|
|
140
248
|
line: LineString,
|
|
@@ -75,7 +75,13 @@ export function buildColumnsStyle(
|
|
|
75
75
|
opts.selectedColor ?? getCSSVariable("--selection-color", "purple");
|
|
76
76
|
|
|
77
77
|
const lineColor = opts.lineColor ?? columnBaseColor;
|
|
78
|
-
|
|
78
|
+
// Highlight on either direct map hover or an externally-driven highlight
|
|
79
|
+
// (e.g. hovering the corresponding column header elsewhere in the UI).
|
|
80
|
+
const isHovered = [
|
|
81
|
+
"any",
|
|
82
|
+
["boolean", ["feature-state", "hover"], false],
|
|
83
|
+
["boolean", ["feature-state", "highlighted"], false],
|
|
84
|
+
];
|
|
79
85
|
// If color is in the feature state or geojson properties, use that as second choice
|
|
80
86
|
if (typeof columnColor === "string") {
|
|
81
87
|
columnColor = [
|