@macrostrat/column-views 3.2.1 → 3.3.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 (96) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/age-axis.cjs +2 -2
  3. package/dist/age-axis.cjs.map +1 -1
  4. package/dist/age-axis.js +1 -1
  5. package/dist/column-views.css +130 -125
  6. package/dist/index.cjs +30 -19
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +16 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/maps/column-correlation/core.cjs +10 -30
  11. package/dist/maps/column-correlation/core.cjs.map +1 -1
  12. package/dist/maps/column-correlation/core.d.ts +1 -1
  13. package/dist/maps/column-correlation/core.js +10 -30
  14. package/dist/maps/column-correlation/core.js.map +1 -1
  15. package/dist/maps/column-navigation/mapbox/index.cjs +156 -118
  16. package/dist/maps/column-navigation/mapbox/index.cjs.map +1 -1
  17. package/dist/maps/column-navigation/mapbox/index.d.ts +23 -2
  18. package/dist/maps/column-navigation/mapbox/index.js +158 -120
  19. package/dist/maps/column-navigation/mapbox/index.js.map +1 -1
  20. package/dist/maps/column-navigation/mapbox/keyboard-navigation.cjs +7 -1
  21. package/dist/maps/column-navigation/mapbox/keyboard-navigation.cjs.map +1 -1
  22. package/dist/maps/column-navigation/mapbox/keyboard-navigation.d.ts +3 -26
  23. package/dist/maps/column-navigation/mapbox/keyboard-navigation.js +9 -3
  24. package/dist/maps/column-navigation/mapbox/keyboard-navigation.js.map +1 -1
  25. package/dist/maps/index.d.ts +2 -2
  26. package/dist/maps/{_shared/inset-map.cjs → inset-map/index.cjs} +1 -1
  27. package/dist/maps/inset-map/index.cjs.map +1 -0
  28. package/dist/maps/{_shared/inset-map.js → inset-map/index.js} +1 -1
  29. package/dist/maps/inset-map/index.js.map +1 -0
  30. package/dist/maps/layers/columns.cjs +161 -0
  31. package/dist/maps/layers/columns.cjs.map +1 -0
  32. package/dist/maps/layers/columns.d.ts +28 -0
  33. package/dist/maps/layers/columns.js +161 -0
  34. package/dist/maps/layers/columns.js.map +1 -0
  35. package/dist/maps/layers/index.d.ts +2 -0
  36. package/dist/maps/{units-overlay/index.cjs → layers/units.cjs} +41 -88
  37. package/dist/maps/layers/units.cjs.map +1 -0
  38. package/dist/maps/{units-overlay/index.d.ts → layers/units.d.ts} +1 -1
  39. package/dist/maps/{units-overlay/index.js → layers/units.js} +34 -82
  40. package/dist/maps/layers/units.js.map +1 -0
  41. package/dist/section.cjs +2 -2
  42. package/dist/section.cjs.map +1 -1
  43. package/dist/section.js +1 -1
  44. package/dist/unit-details/age-range.cjs +196 -0
  45. package/dist/unit-details/age-range.cjs.map +1 -0
  46. package/dist/unit-details/age-range.d.ts +70 -0
  47. package/dist/unit-details/age-range.js +194 -0
  48. package/dist/unit-details/age-range.js.map +1 -0
  49. package/dist/unit-details/age-range.module.sass.cjs +16 -0
  50. package/dist/unit-details/age-range.module.sass.cjs.map +1 -0
  51. package/dist/unit-details/age-range.module.sass.js +14 -0
  52. package/dist/unit-details/age-range.module.sass.js.map +1 -0
  53. package/dist/unit-details/index.d.ts +1 -0
  54. package/dist/unit-details/panel.cjs +8 -173
  55. package/dist/unit-details/panel.cjs.map +1 -1
  56. package/dist/unit-details/panel.js +3 -168
  57. package/dist/unit-details/panel.js.map +1 -1
  58. package/dist/unit-details/panel.module.sass.cjs +20 -22
  59. package/dist/unit-details/panel.module.sass.cjs.map +1 -1
  60. package/dist/unit-details/panel.module.sass.js +20 -22
  61. package/dist/unit-details/panel.module.sass.js.map +1 -1
  62. package/dist/unit-details/utils.cjs +31 -0
  63. package/dist/unit-details/utils.cjs.map +1 -0
  64. package/dist/unit-details/utils.d.ts +3 -0
  65. package/dist/unit-details/utils.js +31 -0
  66. package/dist/unit-details/utils.js.map +1 -0
  67. package/package.json +4 -4
  68. package/src/maps/column-correlation/core.ts +9 -29
  69. package/src/maps/column-navigation/mapbox/index.ts +191 -138
  70. package/src/maps/column-navigation/mapbox/keyboard-navigation.ts +17 -3
  71. package/src/maps/index.ts +2 -2
  72. package/src/maps/layers/columns.ts +210 -0
  73. package/src/maps/layers/index.ts +2 -0
  74. package/src/maps/{units-overlay/index.ts → layers/units.ts} +34 -84
  75. package/src/maps/unit-map.stories.ts +27 -12
  76. package/src/unit-details/age-range.module.sass +15 -0
  77. package/src/unit-details/age-range.ts +269 -0
  78. package/src/unit-details/index.ts +1 -0
  79. package/src/unit-details/panel.module.sass +0 -9
  80. package/src/unit-details/panel.ts +12 -226
  81. package/src/unit-details/utils.ts +33 -0
  82. package/dist/maps/_shared/index.d.ts +0 -2
  83. package/dist/maps/_shared/inset-map.cjs.map +0 -1
  84. package/dist/maps/_shared/inset-map.js.map +0 -1
  85. package/dist/maps/_shared/styles.cjs +0 -73
  86. package/dist/maps/_shared/styles.cjs.map +0 -1
  87. package/dist/maps/_shared/styles.d.ts +0 -2
  88. package/dist/maps/_shared/styles.js +0 -73
  89. package/dist/maps/_shared/styles.js.map +0 -1
  90. package/dist/maps/units-overlay/index.cjs.map +0 -1
  91. package/dist/maps/units-overlay/index.js.map +0 -1
  92. package/src/maps/_shared/index.ts +0 -2
  93. package/src/maps/_shared/styles.ts +0 -76
  94. /package/dist/maps/{_shared/inset-map.d.ts → inset-map/index.d.ts} +0 -0
  95. /package/src/maps/{_shared/inset-map.ts → inset-map/index.ts} +0 -0
  96. /package/src/maps/{_shared → inset-map}/inset-map.stories.ts +0 -0
@@ -1,21 +1,17 @@
1
- import { useMapStyleOperator } from "@macrostrat/mapbox-react";
1
+ import { useMapRef, useMapStyleOperator } from "@macrostrat/mapbox-react";
2
2
  import h from "@macrostrat/hyper";
3
- import { FeatureCollection } from "geojson";
4
- import { ReactNode, useMemo, useRef } from "react";
5
- import { buildColumnsStyle, InsetMapProps } from "../../_shared";
6
- import { setGeoJSON } from "@macrostrat/mapbox-utils";
3
+ import { ReactNode, useCallback, useEffect, useMemo, useRef } from "react";
7
4
 
8
5
  import {
9
6
  ColumnNavigationProvider,
10
7
  NavigationProviderProps,
11
8
  useColumnNavigationStore,
12
9
  } from "./state";
13
- import { InsetMap } from "../../_shared";
10
+ import { InsetMap, type InsetMapProps } from "../../inset-map";
14
11
  import { geoCentroid } from "d3-geo";
15
- import {
16
- ColumnKeyboardNavigation,
17
- buildKeyboardNavigationStyle,
18
- } from "./keyboard-navigation";
12
+ import { BaseColumnsLayer, SelectedColumnOverlay } from "../../layers";
13
+ import { ColumnKeyboardNavigation } from "./keyboard-navigation";
14
+ import type { ColumnFeature } from "../../layers";
19
15
 
20
16
  export interface ColumnNavigationMapProps extends InsetMapProps {
21
17
  padding?: number;
@@ -27,14 +23,18 @@ export interface ColumnNavigationMapProps extends InsetMapProps {
27
23
  onHoverColumn?: (colID: number | null) => void;
28
24
  showTriangulation?: boolean;
29
25
  columnColor?: string;
30
- triangulationColor?: string;
31
- keyboardNavigation?: boolean;
32
26
  }
33
27
 
34
28
  export function ColumnNavigationMap(
35
29
  props: ColumnNavigationMapProps & NavigationProviderProps,
36
30
  ) {
37
31
  const {
32
+ padding = 50,
33
+ children,
34
+ columnColor,
35
+ showTriangulation,
36
+ triangulationColor,
37
+ keyboardNavigation = true,
38
38
  columns,
39
39
  columnIDs,
40
40
  onSelectColumn,
@@ -42,182 +42,235 @@ export function ColumnNavigationMap(
42
42
  selectedColumn,
43
43
  hoveredColumn,
44
44
  projectID,
45
- children,
46
45
  inProcess,
47
46
  ...rest
48
47
  } = props;
49
48
 
50
- return h(
51
- ColumnNavigationProvider,
52
- {
53
- columns,
54
- columnIDs,
55
- onSelectColumn,
56
- onHoverColumn,
57
- selectedColumn,
58
- hoveredColumn,
59
- projectID,
60
- inProcess,
61
- },
62
- h(_ColumnNavigationMap, props),
63
- );
64
- }
65
-
66
- function _ColumnNavigationMap(props: ColumnNavigationMapProps) {
67
- const {
68
- padding = 50,
69
- children,
70
- columnColor,
71
- showTriangulation,
72
- triangulationColor,
73
- keyboardNavigation = true,
74
- ...rest
75
- } = props;
76
-
77
- const overlayStyles = useMemo(() => {
78
- let styles: any[] = [buildColumnsStyle(columnColor)];
79
- if (showTriangulation) {
80
- styles.push(buildKeyboardNavigationStyle(triangulationColor));
81
- }
82
- return styles;
83
- }, [columnColor, showTriangulation, triangulationColor]);
84
-
85
49
  return h(
86
50
  InsetMap,
87
51
  {
88
52
  ...rest,
89
53
  boxZoom: false,
90
54
  dragRotate: false,
91
- overlayStyles,
92
55
  },
93
56
  [
94
- h(ColumnsLayer),
57
+ h(ColumnsNavigationLayer, {
58
+ color: columnColor,
59
+ columns,
60
+ columnIDs,
61
+ onSelectColumn,
62
+ onHoverColumn,
63
+ selectedColumn,
64
+ hoveredColumn,
65
+ projectID,
66
+ inProcess,
67
+ keyboardNavigation,
68
+ showTriangulation,
69
+ triangulationColor,
70
+ }),
95
71
  children,
96
- h.if(keyboardNavigation)(ColumnKeyboardNavigation, { showTriangulation }),
97
72
  ],
98
73
  );
99
74
  }
100
75
 
101
- function ColumnsLayer({ enabled = true }) {
76
+ interface ColumnLayerProps {
77
+ enabled?: boolean;
78
+ color?: string;
79
+ }
80
+
81
+ interface ColumnKeyboardNavigationProps {
82
+ triangulationColor?: string;
83
+ keyboardNavigation?: boolean;
84
+ showTriangulation?: boolean;
85
+ }
86
+
87
+ export function ColumnsNavigationLayer(
88
+ props: NavigationProviderProps &
89
+ ColumnLayerProps &
90
+ ColumnKeyboardNavigationProps,
91
+ ) {
92
+ const {
93
+ enabled,
94
+ color,
95
+ keyboardNavigation,
96
+ showTriangulation,
97
+ triangulationColor,
98
+ ...providerProps
99
+ } = props;
100
+
101
+ return h(ColumnNavigationProvider, providerProps, [
102
+ h(_ColumnsNavigationLayer, { enabled, color }),
103
+ h.if(keyboardNavigation)(ColumnKeyboardNavigation, {
104
+ showTriangulation,
105
+ triangulationColor,
106
+ }),
107
+ ]);
108
+ }
109
+
110
+ const columnLayers = ["columns-points", "columns-fill"];
111
+
112
+ function _ColumnsNavigationLayer({ enabled = true, color }) {
102
113
  const columns = useColumnNavigationStore((state) => state.columns);
103
- const selectedColumn = useColumnNavigationStore(
114
+ const selectedColumnID = useColumnNavigationStore(
104
115
  (state) => state.selectedColumn,
105
116
  );
106
- const hoveredColumn = useColumnNavigationStore(
107
- (state) => state.hoveredColumn,
108
- );
109
117
  const selectColumn = useColumnNavigationStore((state) => state.selectColumn);
110
118
  const setHoveredColumn = useColumnNavigationStore(
111
119
  (state) => state.setHoveredColumn,
112
120
  );
113
121
 
122
+ const onHover = useCallback(
123
+ (event) => setHoveredColumn(event.features?.[0]?.id),
124
+ [setHoveredColumn],
125
+ );
126
+
127
+ /** Convert features into column IDs to pass to the selection manager */
128
+ const onSelectColumn = useCallback(
129
+ (column) => selectColumn(column.id),
130
+ [selectColumn],
131
+ );
132
+
133
+ const selectedColumnFeature = useMemo(() => {
134
+ if (columns == null || selectedColumnID == null) return null;
135
+ return columns.find((d) => d.id == selectedColumnID);
136
+ }, [selectedColumnID, columns]);
137
+
138
+ return h([
139
+ h(BaseColumnsLayer, { enabled, color, columns }),
140
+ h(ColumnSelectionManager, {
141
+ selectedColumn: selectedColumnFeature,
142
+ onSelectColumn,
143
+ }),
144
+ h(ColumnHoverInteraction, { onHover }),
145
+ ]);
146
+ }
147
+
148
+ export function ColumnSelectionManager({
149
+ selectedColumn,
150
+ onSelectColumn,
151
+ centerOnSelect = true,
152
+ }: {
153
+ selectedColumn: ColumnFeature;
154
+ onSelectColumn: (column: ColumnFeature) => void;
155
+ centerOnSelect?: boolean;
156
+ }) {
157
+ const onClick = useCallback(
158
+ (event) => {
159
+ if (event.features.length == 0) return;
160
+ const selectedColumn = event.features[0];
161
+ onSelectColumn(selectedColumn);
162
+ },
163
+ [onSelectColumn],
164
+ );
165
+
166
+ return h([
167
+ h.if(centerOnSelect)(SelectedColumnCenterer, {
168
+ selectedColumn,
169
+ }),
170
+ h(SelectedColumnOverlay, { selectedColumn }),
171
+ h(ClickInteraction, {
172
+ onClick,
173
+ layers: columnLayers,
174
+ }),
175
+ ]);
176
+ }
177
+
178
+ export function ColumnHoverInteraction({ onHover }) {
179
+ return h(HoverInteraction, { onHover, layers: columnLayers });
180
+ }
181
+
182
+ function ClickInteraction({ onClick, layers }) {
114
183
  useMapStyleOperator(
115
184
  (map) => {
116
- if (columns == null) {
117
- return;
118
- }
119
- const data: FeatureCollection = {
120
- type: "FeatureCollection",
121
- features: columns,
185
+ // Setup hover styles on map
186
+ map.on("click", layers, onClick);
187
+ return () => {
188
+ map.off("click", layers, onClick);
122
189
  };
190
+ },
191
+ [layers, onClick],
192
+ );
193
+ return null;
194
+ }
123
195
 
124
- setGeoJSON(map, "columns", data);
196
+ function HoverInteraction({ onHover, layers }) {
197
+ const hoveredFeaturesRef = useRef([]);
198
+ const updateFeatureState = useCallback((map, features) => {
199
+ // Reset hover state for previously hovered features
200
+ for (const f of hoveredFeaturesRef.current) {
201
+ map.setFeatureState(
202
+ { source: f.source, id: f.id, sourceLayer: f.sourceLayer },
203
+ { hover: false },
204
+ );
205
+ }
206
+ // Set hover state for new features
207
+ for (const f of features) {
208
+ map.setFeatureState(
209
+ { source: f.source, id: f.id, sourceLayer: f.sourceLayer },
210
+ { hover: true },
211
+ );
212
+ }
213
+ hoveredFeaturesRef.current = features;
214
+ }, []);
125
215
 
216
+ useMapStyleOperator(
217
+ (map) => {
126
218
  const mouseMove = (event) => {
127
219
  if (event.features.length == 0) return;
128
- const hoveredFeature = event.features[0];
129
- setHoveredColumn(hoveredFeature.id);
220
+ onHover?.(event);
221
+ updateFeatureState(map, event.features);
130
222
  map.getCanvas().style.cursor = "pointer";
131
223
  };
132
- const mouseLeave = () => {
133
- setHoveredColumn(null);
224
+ const mouseLeave = (event) => {
225
+ onHover?.(event);
226
+ updateFeatureState(map, event.features ?? []);
134
227
  map.getCanvas().style.cursor = "";
135
228
  };
136
229
 
137
- const clickHandler = (event) => {
138
- if (event.features.length == 0) return;
139
- const selectedColumn = event.features[0];
140
- const id = selectedColumn.id;
141
- selectColumn(id);
142
- };
143
-
144
230
  // Setup hover styles on map
145
- const layers = ["columns-points", "columns-fill"];
146
231
  map.on("mousemove", layers, mouseMove);
147
232
  map.on("mouseleave", layers, mouseLeave);
148
- map.on("click", layers, clickHandler);
149
233
 
150
234
  return () => {
151
235
  map.off("mouseenter", layers, mouseMove);
152
236
  map.off("mouseleave", layers, mouseLeave);
153
- map.off("click", layers, clickHandler);
154
237
  };
155
238
  },
156
- [columns, enabled],
239
+ [layers, onHover],
157
240
  );
241
+ return null;
242
+ }
158
243
 
159
- const hoveredColumnRef = useRef(null);
160
- useMapStyleOperator(
161
- (map) => {
162
- const prevHoveredColumn = hoveredColumnRef.current;
163
- if (hoveredColumn == prevHoveredColumn) return;
164
- if (prevHoveredColumn != null) {
165
- // Deselect previous column
166
- map.setFeatureState(
167
- { source: "columns", id: prevHoveredColumn },
168
- { hover: false },
169
- );
170
- }
171
- hoveredColumnRef.current = hoveredColumn;
172
- // Select the current column
173
- map.setFeatureState(
174
- { source: "columns", id: hoveredColumn },
175
- { hover: true },
176
- );
177
- },
178
- [hoveredColumn, columns],
179
- );
244
+ function SelectedColumnCenterer({
245
+ selectedColumn,
246
+ easeDuration = 500,
247
+ }: {
248
+ selectedColumn: ColumnFeature;
249
+ easeDuration?: number;
250
+ }) {
251
+ const columnCenter = useMemo(() => {
252
+ const columnGeometry = selectedColumn?.geometry;
253
+ if (columnGeometry == null) return null;
254
+ return geoCentroid(columnGeometry);
255
+ }, [selectedColumn]);
180
256
 
181
- /** Set feature state for selected columns */
182
- const selectedColumnRef = useRef(null);
257
+ const mapRef = useMapRef();
258
+ const map = mapRef.current;
183
259
  const initialRenderRef = useRef(true);
184
- useMapStyleOperator(
185
- (map) => {
186
- if (columns == null) return;
187
- const prevSelectedColumn = selectedColumnRef.current;
188
- if (selectedColumn == prevSelectedColumn) return;
189
- if (prevSelectedColumn != null) {
190
- // Deselect previous column
191
- map.setFeatureState(
192
- { source: "columns", id: prevSelectedColumn },
193
- { selected: false },
194
- );
195
- }
196
-
197
- selectedColumnRef.current = selectedColumn;
198
-
199
- // Select the current column
200
- map.setFeatureState(
201
- { source: "columns", id: selectedColumn },
202
- { selected: true },
203
- );
204
-
205
- // Center the selected column
206
- const columnGeometry = columns.find(
207
- (d) => d.id == selectedColumn,
208
- )?.geometry;
209
- if (columnGeometry == null) return;
210
- const center = geoCentroid(columnGeometry);
211
-
212
- const isInitialRender = initialRenderRef.current;
213
-
214
- map.easeTo({ center }, { duration: isInitialRender ? 0 : 500 });
215
- if (isInitialRender) {
216
- initialRenderRef.current = false;
217
- }
218
- },
219
- [selectedColumn, columns],
220
- );
260
+ // Center the map on the selected column when it changes
261
+ useEffect(() => {
262
+ if (map == null) return;
263
+ const isInitialRender = initialRenderRef.current;
264
+ if (isInitialRender) initialRenderRef.current = false;
265
+ // If this is the first render of the map, jump to the location. Otherwise, ease to it.
266
+ if (columnCenter == null) return;
267
+ if (isInitialRender) {
268
+ map.setCenter(columnCenter);
269
+ initialRenderRef.current = false;
270
+ } else {
271
+ map.easeTo({ center: columnCenter }, { duration: easeDuration });
272
+ }
273
+ }, [mapRef.current, columnCenter]);
221
274
 
222
275
  return null;
223
276
  }
@@ -1,19 +1,25 @@
1
1
  import { useCallback, useEffect, useMemo } from "react";
2
2
  import { buildGeoJSONSource, setGeoJSON } from "@macrostrat/mapbox-utils";
3
- import { useMapRef, useMapStyleOperator } from "@macrostrat/mapbox-react";
3
+ import {
4
+ useMapRef,
5
+ useMapStyleOperator,
6
+ useOverlayStyle,
7
+ } from "@macrostrat/mapbox-react";
4
8
  import { useColumnNavigationStore } from "./state";
5
9
  import {
6
10
  buildTriangulation,
7
11
  buildKeyMapping,
8
12
  } from "../utils/keyboard-navigation";
13
+ import { StyleSpecification } from "mapbox-gl";
9
14
 
10
15
  interface KeyboardNavProps {
11
16
  showTriangulation: boolean;
17
+ triangulationColor?: string;
12
18
  }
13
19
 
14
20
  export function ColumnKeyboardNavigation(props: KeyboardNavProps) {
15
21
  /** Keyboard navigation of columns using a Delaunay triangulation */
16
- const { showTriangulation = false } = props;
22
+ const { showTriangulation = false, triangulationColor } = props;
17
23
  const columns = useColumnNavigationStore((state) => state.columns);
18
24
  const col_id = useColumnNavigationStore((state) => state.selectedColumn);
19
25
  const selectColumn = useColumnNavigationStore((state) => state.selectColumn);
@@ -26,6 +32,14 @@ export function ColumnKeyboardNavigation(props: KeyboardNavProps) {
26
32
  const mapRef = useMapRef();
27
33
  const map = mapRef.current;
28
34
 
35
+ // Set up overlay style for triangulation
36
+ useOverlayStyle(() => {
37
+ if (!showTriangulation) return null;
38
+ return buildKeyboardNavigationStyle(
39
+ triangulationColor,
40
+ ) as Partial<StyleSpecification>;
41
+ }, [triangulationColor, showTriangulation]);
42
+
29
43
  const keyMapping = useMemo(() => {
30
44
  if (columns == null || voronoi == null || map == null) return null;
31
45
 
@@ -115,5 +129,5 @@ export function buildKeyboardNavigationStyle(color: string = "purple") {
115
129
  },
116
130
  },
117
131
  ],
118
- };
132
+ } as Partial<StyleSpecification>;
119
133
  }
package/src/maps/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from "./column-navigation";
2
2
  export * from "./column-correlation";
3
- export * from "./_shared";
4
- export * from "./units-overlay";
3
+ export * from "./inset-map";
4
+ export * from "./layers";
@@ -0,0 +1,210 @@
1
+ import { useMapStyleOperator, useOverlayStyle } from "@macrostrat/mapbox-react";
2
+ import { setGeoJSON } from "@macrostrat/mapbox-utils";
3
+ import GeoJSON, { FeatureCollection } from "geojson";
4
+ import { GeoJSONFeature } from "mapbox-gl";
5
+ import { getCSSVariable } from "@macrostrat/color-utils";
6
+ import { buildGeoJSONSource } from "@macrostrat/mapbox-utils";
7
+ import { StyleFragment } from "@macrostrat/mapbox-react";
8
+
9
+ export interface ColumnsStyleOptions {
10
+ color?: string | any[];
11
+ selectedColor?: string | any[];
12
+ opacity?: number | any[];
13
+ circleRadius?: number | any[];
14
+ lineOpacity?: number | any[];
15
+ lineWidth?: number | any[];
16
+ lineColor?: string | any[];
17
+ }
18
+
19
+ interface ColumnLayerOptions extends ColumnsStyleOptions {
20
+ enabled: boolean;
21
+ columns: FeatureCollection | GeoJSONFeature[];
22
+ }
23
+
24
+ export function BaseColumnsLayer({
25
+ enabled = true,
26
+ columns,
27
+ ...rest
28
+ }: ColumnLayerOptions) {
29
+ useOverlayStyle(() => (enabled ? buildColumnsStyle(rest) : null), [enabled]);
30
+
31
+ useMapStyleOperator(
32
+ (map) => {
33
+ if (columns == null) {
34
+ return;
35
+ }
36
+
37
+ setGeoJSON(map, "columns", promoteToFeatureCollection(columns));
38
+ },
39
+ [columns, enabled],
40
+ );
41
+ return null;
42
+ }
43
+
44
+ function promoteToFeatureCollection(
45
+ data: FeatureCollection | GeoJSONFeature | GeoJSONFeature[] | null,
46
+ ): FeatureCollection | null {
47
+ if (data == null) return null;
48
+ if ("type" in data && data.type == "Feature") {
49
+ return {
50
+ type: "FeatureCollection",
51
+ features: [data],
52
+ };
53
+ }
54
+ if (
55
+ "type" in data &&
56
+ data.type == "FeatureCollection" &&
57
+ "features" in data
58
+ ) {
59
+ return data;
60
+ }
61
+ return {
62
+ type: "FeatureCollection",
63
+ features: data,
64
+ };
65
+ }
66
+
67
+ export function buildColumnsStyle(
68
+ opts: ColumnsStyleOptions = {},
69
+ ): StyleFragment {
70
+ const { color } = opts;
71
+ const columnBaseColor =
72
+ color ?? getCSSVariable("--text-subtle-color", "black");
73
+ let columnColor: any = columnBaseColor;
74
+ const columnSelectedColor =
75
+ opts.selectedColor ?? getCSSVariable("--selection-color", "purple");
76
+
77
+ const lineColor = opts.lineColor ?? columnBaseColor;
78
+ const isHovered = ["boolean", ["feature-state", "hover"], false];
79
+ // If color is in the feature state or geojson properties, use that as second choice
80
+ if (typeof columnColor === "string") {
81
+ columnColor = [
82
+ "coalesce",
83
+ ["feature-state", "color"],
84
+ ["get", "color"],
85
+ columnColor,
86
+ ];
87
+ }
88
+
89
+ const fillOpacity = opts.opacity ?? 0.1;
90
+
91
+ const lineOpacity = opts.lineOpacity ?? 0.5;
92
+ const lineWidth = opts.lineWidth ?? 2;
93
+
94
+ const circleRadius = opts.circleRadius ?? 4;
95
+
96
+ return {
97
+ sources: {
98
+ columns: buildGeoJSONSource(),
99
+ },
100
+ version: 8,
101
+ layers: [
102
+ {
103
+ id: "columns-fill",
104
+ type: "fill",
105
+ source: "columns",
106
+ paint: {
107
+ "fill-color": columnColor,
108
+ "fill-opacity": fillOpacity,
109
+ },
110
+ },
111
+ {
112
+ id: "columns-points",
113
+ type: "circle",
114
+ source: "columns",
115
+ paint: {
116
+ "circle-radius": circleRadius,
117
+ "circle-color": columnColor,
118
+ "circle-opacity": fillOpacity,
119
+ },
120
+ filter: ["==", "$type", "Point"],
121
+ },
122
+ {
123
+ id: "columns-line",
124
+ type: "line",
125
+ source: "columns",
126
+ paint: {
127
+ "line-color": ["case", isHovered, columnSelectedColor, lineColor],
128
+ "line-width": lineWidth,
129
+ "line-opacity": ["case", isHovered, 1.0, lineOpacity],
130
+ },
131
+ },
132
+ ],
133
+ };
134
+ }
135
+
136
+ export type ColumnFeature = GeoJSON.Feature & { id: number };
137
+
138
+ const selectedColumnSourceID = "selectedColumn";
139
+
140
+ export function SelectedColumnOverlay({
141
+ selectedColumn,
142
+ ...styleOpts
143
+ }: { selectedColumn: ColumnFeature | null } & SelectedColumnStyleOpts) {
144
+ useOverlayStyle(() => buildSelectedColumnStyle(styleOpts), []);
145
+
146
+ useMapStyleOperator(
147
+ (map) => {
148
+ setGeoJSON(
149
+ map,
150
+ selectedColumnSourceID,
151
+ promoteToFeatureCollection(selectedColumn as GeoJSONFeature | null),
152
+ );
153
+ },
154
+ [selectedColumn],
155
+ );
156
+
157
+ return null;
158
+ }
159
+
160
+ interface SelectedColumnStyleOpts {
161
+ color?: string | any[];
162
+ }
163
+
164
+ function buildSelectedColumnStyle(
165
+ opts: SelectedColumnStyleOpts = {},
166
+ ): StyleFragment {
167
+ const columnSelectedColor: any =
168
+ opts.color ?? getCSSVariable("--selection-color", "purple");
169
+
170
+ return {
171
+ sources: {
172
+ selectedColumn: buildGeoJSONSource(),
173
+ },
174
+ version: 8,
175
+ layers: [
176
+ {
177
+ id: "selected-column-highlight",
178
+ type: "fill",
179
+ source: selectedColumnSourceID,
180
+ paint: {
181
+ "fill-color": columnSelectedColor,
182
+ "fill-opacity": 0.5,
183
+ },
184
+ filter: ["==", "$type", "Polygon"],
185
+ },
186
+ {
187
+ id: "selected-column-edge",
188
+ type: "line",
189
+ source: selectedColumnSourceID,
190
+ paint: {
191
+ "line-color": columnSelectedColor,
192
+ "line-width": 2,
193
+ "line-opacity": 1.0,
194
+ },
195
+ filter: ["==", "$type", "Polygon"],
196
+ },
197
+ {
198
+ id: "selected-column-point",
199
+ type: "circle",
200
+ source: selectedColumnSourceID,
201
+ paint: {
202
+ "circle-radius": 6,
203
+ "circle-color": columnSelectedColor,
204
+ "circle-opacity": 1.0,
205
+ },
206
+ filter: ["==", "$type", "Point"],
207
+ },
208
+ ],
209
+ };
210
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./columns";
2
+ export * from "./units";