@macrostrat/column-views 3.3.4 → 3.4.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 (91) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/age-axis.d.ts +3 -3
  3. package/dist/column.cjs +2 -1
  4. package/dist/column.cjs.map +1 -1
  5. package/dist/column.d.ts +3 -3
  6. package/dist/column.js +2 -1
  7. package/dist/column.js.map +1 -1
  8. package/dist/correlation-chart/main.d.ts +1 -1
  9. package/dist/correlation-chart/prepare-data.d.ts +1 -1
  10. package/dist/data-provider/unit-selection.cjs +7 -3
  11. package/dist/data-provider/unit-selection.cjs.map +1 -1
  12. package/dist/data-provider/unit-selection.d.ts +3 -2
  13. package/dist/data-provider/unit-selection.js +7 -3
  14. package/dist/data-provider/unit-selection.js.map +1 -1
  15. package/dist/facets/carbon-isotopes/data-area.d.ts +1 -1
  16. package/dist/facets/carbon-isotopes/data-provider.d.ts +1 -1
  17. package/dist/facets/measurements/base.d.ts +2 -2
  18. package/dist/index.cjs +13 -8
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +7 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/maps/column-correlation/state.d.ts +1 -1
  23. package/dist/maps/column-navigation/mapbox/state.d.ts +1 -1
  24. package/dist/maps/column-navigation/svg/data-fetcher.d.ts +1 -1
  25. package/dist/maps/column-navigation/svg/layers.d.ts +3 -3
  26. package/dist/maps/inset-map/index.d.ts +1 -1
  27. package/dist/maps/layers/columns.d.ts +1 -1
  28. package/dist/maps/layers/units.d.ts +1 -1
  29. package/dist/prepare-units/composite-scale.d.ts +1 -1
  30. package/dist/prepare-units/dynamic-scales.d.ts +1 -1
  31. package/dist/prepare-units/helpers.cjs +83 -49
  32. package/dist/prepare-units/helpers.cjs.map +1 -1
  33. package/dist/prepare-units/helpers.d.ts +21 -4
  34. package/dist/prepare-units/helpers.js +84 -50
  35. package/dist/prepare-units/helpers.js.map +1 -1
  36. package/dist/prepare-units/index.cjs.map +1 -1
  37. package/dist/prepare-units/index.d.ts +1 -1
  38. package/dist/prepare-units/index.js.map +1 -1
  39. package/dist/prepare-units/types.d.ts +2 -2
  40. package/dist/prepare-units/utils.d.ts +2 -2
  41. package/dist/section.cjs +1 -2
  42. package/dist/section.cjs.map +1 -1
  43. package/dist/section.d.ts +1 -1
  44. package/dist/section.js +1 -2
  45. package/dist/section.js.map +1 -1
  46. package/dist/unit-details/age-range.cjs.map +1 -1
  47. package/dist/unit-details/age-range.d.ts +2 -1
  48. package/dist/unit-details/age-range.js.map +1 -1
  49. package/dist/unit-details/identifiers.cjs +56 -0
  50. package/dist/unit-details/identifiers.cjs.map +1 -0
  51. package/dist/unit-details/identifiers.d.ts +31 -0
  52. package/dist/unit-details/identifiers.js +54 -0
  53. package/dist/unit-details/identifiers.js.map +1 -0
  54. package/dist/unit-details/index.d.ts +2 -0
  55. package/dist/unit-details/panel.cjs +73 -147
  56. package/dist/unit-details/panel.cjs.map +1 -1
  57. package/dist/unit-details/panel.d.ts +67 -0
  58. package/dist/unit-details/panel.js +19 -92
  59. package/dist/unit-details/panel.js.map +1 -1
  60. package/dist/unit-details/popover.cjs +1 -2
  61. package/dist/unit-details/popover.cjs.map +1 -1
  62. package/dist/unit-details/popover.js +2 -3
  63. package/dist/unit-details/popover.js.map +1 -1
  64. package/dist/unit-details/strat-names.cjs +44 -0
  65. package/dist/unit-details/strat-names.cjs.map +1 -0
  66. package/dist/unit-details/strat-names.d.ts +14 -0
  67. package/dist/unit-details/strat-names.js +42 -0
  68. package/dist/unit-details/strat-names.js.map +1 -0
  69. package/dist/units/boxes.d.ts +2 -2
  70. package/dist/units/colors.d.ts +1 -1
  71. package/dist/units/composite.d.ts +4 -4
  72. package/dist/units/index.cjs +20 -6
  73. package/dist/units/index.cjs.map +1 -1
  74. package/dist/units/index.d.ts +12 -21
  75. package/dist/units/index.js +20 -6
  76. package/dist/units/index.js.map +1 -1
  77. package/dist/units/names.d.ts +3 -3
  78. package/dist/units/resolvers.d.ts +2 -2
  79. package/package.json +19 -19
  80. package/src/column.ts +1 -1
  81. package/src/data-provider/unit-selection.ts +8 -4
  82. package/src/prepare-units/helpers.ts +121 -72
  83. package/src/prepare-units/index.ts +1 -1
  84. package/src/section.ts +0 -1
  85. package/src/unit-details/age-range.ts +1 -0
  86. package/src/unit-details/identifiers.ts +67 -0
  87. package/src/unit-details/index.ts +2 -0
  88. package/src/unit-details/panel.ts +27 -131
  89. package/src/unit-details/popover.ts +2 -7
  90. package/src/unit-details/strat-names.ts +57 -0
  91. package/src/units/index.ts +31 -8
@@ -16,95 +16,143 @@ import type { ExtUnit, SectionInfo, StratigraphicPackage } from "./types";
16
16
 
17
17
  const dt = 0.001;
18
18
 
19
- export function preprocessUnits<T extends UnitLong = UnitLong>(
20
- section: SectionInfo<T>,
21
- axisType: ColumnAxisType = ColumnAxisType.AGE,
22
- ): ExtUnit[] {
23
- /** Preprocess units to add overlapping units and columns. */
24
- let units = section.units;
19
+ export type UnitWithDefinedOverlap<T extends BaseUnit> = T & {
20
+ overlap: UnitOverlapInfo<T>;
21
+ };
25
22
 
26
- let divisions = units.map((...args) => extendDivision(...args, axisType));
27
- for (let d of divisions) {
28
- const overlappingUnits = divisions.filter((u) =>
29
- d.overlappingUnits.includes(u.unit_id),
30
- );
23
+ type UnitOverlapInfo<T extends BaseUnit> = {
24
+ column: number;
25
+ nColumns: number;
26
+ unitGroup: UnitGroup<T>;
27
+ };
31
28
 
32
- // Overlapping columns
33
- const columns = overlappingUnits.map((d) => d.column);
29
+ class UnitGroup<T extends BaseUnit> implements StratigraphicPackage {
30
+ units: T[];
34
31
 
35
- if (columns.includes(d.column)) {
36
- let col = 0;
37
- // Go through columns to find a better index
38
- while (columns.includes(col)) {
39
- col++;
40
- }
41
- d.column ??= col;
42
- }
32
+ constructor(units: T[]) {
33
+ this.units = units;
34
+ }
43
35
 
44
- // If unit overlaps the edges of a section, set the clip positions
45
- const [b_pos, t_pos] = getUnitHeightRange(d, axisType);
46
- if (axisType == ColumnAxisType.AGE) {
47
- if (b_pos > section.b_age) {
48
- d.b_clip_pos = section.b_age;
49
- }
50
- if (t_pos < section.t_age) {
51
- d.t_clip_pos = section.t_age;
36
+ get t_age(): number {
37
+ return Math.min(...this.units.map((d) => d.t_age));
38
+ }
39
+
40
+ get b_age(): number {
41
+ return Math.max(...this.units.map((d) => d.b_age));
42
+ }
43
+
44
+ getUnits(): UnitWithDefinedOverlap<T>[] {
45
+ return assignColumnsToUnitsWithinOverlappingGroup(this);
46
+ }
47
+ }
48
+
49
+ function groupUnitsByMutualOverlap<T extends BaseUnit>(
50
+ units: T[],
51
+ tolerance: number = dt,
52
+ ): UnitGroup<T>[] {
53
+ /** Group units within a section that have mutual overlap. */
54
+ const sections = groupUnitsIntoSectionsByOverlap(
55
+ units,
56
+ ColumnAxisType.AGE,
57
+ tolerance,
58
+ );
59
+ return sections.map((d) => {
60
+ return new UnitGroup(d.units);
61
+ });
62
+ }
63
+
64
+ const sortByAge = createUnitSorter(ColumnAxisType.AGE);
65
+
66
+ function assignColumnsToUnitsWithinOverlappingGroup<T extends BaseUnit>(
67
+ group: UnitGroup<T>,
68
+ ): UnitWithDefinedOverlap<T>[] {
69
+ /** A relatively naïve function to assign columns to units within a mutually overlapping group.
70
+ * This gets the number of columns that would be needed to accommodate the units in the group.
71
+ * It then assigns columns to units in the group based on the number of columns needed, with
72
+ * lower units further to the left.
73
+ */
74
+
75
+ const sortedUnits = [...group.units].sort((a, b) => b.b_age - a.b_age);
76
+
77
+ const columns: T[][] = [];
78
+
79
+ for (const unit of sortedUnits) {
80
+ // Find a column that is not already occupied by an overlapping unit
81
+ let didAddToExistingColumn = false;
82
+ for (const column of columns) {
83
+ const lastUnit = column[column.length - 1];
84
+ if (!unitsOverlap(unit, lastUnit, ColumnAxisType.AGE, dt)) {
85
+ column.push(unit);
86
+ didAddToExistingColumn = true;
87
+ break;
52
88
  }
53
89
  }
54
- if (axisType == ColumnAxisType.DEPTH) {
55
- if (b_pos > section.b_pos) {
56
- d.b_clip_pos = section.b_pos;
57
- }
58
- if (t_pos < section.t_pos) {
59
- d.t_clip_pos = section.t_pos;
60
- }
90
+ if (!didAddToExistingColumn) {
91
+ columns.push([unit]);
61
92
  }
62
- // if (axisType == ColumnAxisType.HEIGHT) {
63
- // if (b_pos < section.b_pos) {
64
- // d.b_clip_pos = section.b_pos;
65
- // }
66
- // if (t_pos > section.t_pos) {
67
- // d.t_clip_pos = section.t_pos;
68
- // }
69
- // }
70
93
  }
71
94
 
72
- return divisions;
95
+ const units: UnitWithDefinedOverlap<T>[] = columns.flatMap(
96
+ (column, columnIndex) =>
97
+ column.map((unit) => {
98
+ return {
99
+ ...unit,
100
+ overlap: {
101
+ column: columnIndex,
102
+ nColumns: columns.length,
103
+ unitGroup: group,
104
+ },
105
+ };
106
+ }),
107
+ );
108
+
109
+ return units.toSorted(sortByAge);
73
110
  }
74
111
 
75
- function extendDivision(
76
- unit: UnitLong,
77
- i: number,
78
- divisions: UnitLong[],
112
+ export function preprocessUnits<T extends UnitLong = UnitLong>(
113
+ section: SectionInfo<T>,
79
114
  axisType: ColumnAxisType = ColumnAxisType.AGE,
80
- ): ExtUnit {
81
- // TODO: make this configurable
115
+ ): ExtUnit[] {
116
+ /** Preprocess units to add overlapping units and columns. */
117
+ let units = section.units;
118
+
82
119
  let tolerance = 0.001; // 1 kyr tolerance for age columns
83
120
  if (axisType != ColumnAxisType.AGE) {
84
121
  tolerance = 0.01; // 1cm tolerance for height/depth columns
85
122
  }
86
123
 
87
- const overlappingUnits = divisions.filter(
88
- (d) =>
89
- d.unit_id != unit.unit_id && unitsOverlap(unit, d, axisType, tolerance),
90
- );
91
- const u_pos = getUnitHeightRange(unit, axisType);
92
- const bottomOverlap = overlappingUnits.some((d) => {
93
- const d_pos = getUnitHeightRange(d, axisType);
94
- // Check if the unit is below the current unit
95
- return d_pos[0] < u_pos[0] + dt;
96
- });
97
- let column = 0;
98
- if (overlappingUnits.length == 1) {
99
- column = 1;
124
+ const unitGroups = groupUnitsByMutualOverlap(units, tolerance);
125
+
126
+ const divisions: any[] = [];
127
+ // Serialize all groups' units to a single array
128
+ for (const group of unitGroups) {
129
+ divisions.push(...group.getUnits());
100
130
  }
101
131
 
102
- return {
103
- ...unit,
104
- bottomOverlap,
105
- column: unit.column ?? column,
106
- overlappingUnits: overlappingUnits.map((d) => d.unit_id),
107
- };
132
+ return divisions.map((d) =>
133
+ enhanceUnitWithClippingPosition(section, d, axisType),
134
+ );
135
+ }
136
+
137
+ export function enhanceUnitWithClippingPosition(section, d, axisType) {
138
+ const [b_pos, t_pos] = getUnitHeightRange(d, axisType);
139
+ if (axisType == ColumnAxisType.AGE) {
140
+ if (b_pos > section.b_age) {
141
+ d.b_clip_pos = section.b_age;
142
+ }
143
+ if (t_pos < section.t_age) {
144
+ d.t_clip_pos = section.t_age;
145
+ }
146
+ }
147
+ if (axisType == ColumnAxisType.DEPTH) {
148
+ if (b_pos > section.b_pos) {
149
+ d.b_clip_pos = section.b_pos;
150
+ }
151
+ if (t_pos < section.t_pos) {
152
+ d.t_clip_pos = section.t_pos;
153
+ }
154
+ }
155
+ return d;
108
156
  }
109
157
 
110
158
  export function groupUnitsIntoSectionsBySectionID<T extends UnitLong>(
@@ -141,9 +189,10 @@ interface WorkingSection {
141
189
  heightRange?: [number, number];
142
190
  }
143
191
 
144
- export function groupUnitsIntoSectionsByOverlap<T extends UnitLong>(
192
+ export function groupUnitsIntoSectionsByOverlap<T extends BaseUnit>(
145
193
  units: T[],
146
194
  axisType: ColumnAxisType = ColumnAxisType.AGE,
195
+ tolerance: number = dt,
147
196
  ): SectionInfo<T>[] {
148
197
  /** Group units into sections by overlap.
149
198
  * This creates "synthetic" sections that correspond to packages bound by scale gaps.
@@ -157,7 +206,7 @@ export function groupUnitsIntoSectionsByOverlap<T extends UnitLong>(
157
206
  const heightRange = getUnitHeightRange(unit, axisType);
158
207
  let section: WorkingSection | undefined = sectionList.find(
159
208
  (s) =>
160
- compareAgeRanges(heightRange, s.heightRange) !==
209
+ compareAgeRanges(heightRange, s.heightRange, tolerance) !==
161
210
  AgeRangeRelationship.Disjoint,
162
211
  );
163
212
  if (section == null) {
@@ -59,7 +59,7 @@ export function prepareColumnUnits(
59
59
  scale,
60
60
  } = options;
61
61
 
62
- let _totalHeight = null;
62
+ let _totalHeight: number | null = null;
63
63
 
64
64
  if (scale != null) {
65
65
  // Set t_age and b_age based on scale domain if not already set
package/src/section.ts CHANGED
@@ -164,7 +164,6 @@ function SectionUnits(props: SectionProps) {
164
164
  nColumns: Math.min(
165
165
  maxInternalColumns ?? Math.floor(width / 10),
166
166
  unitComponentProps?.nColumns ?? Infinity,
167
- Math.max(...units.map((d) => d.column)) + 1,
168
167
  ),
169
168
  //axisType,
170
169
  };
@@ -114,6 +114,7 @@ export interface IntervalProportionsProps extends Omit<
114
114
  unit: UnitIntervalConstraints;
115
115
  showProportions?: boolean;
116
116
  calculateProportionsFromAges?: boolean;
117
+ onClickItem?: any;
117
118
  }
118
119
 
119
120
  export function IntervalProportions({
@@ -0,0 +1,67 @@
1
+ import { isClickable, ItemInteractionProps } from "@macrostrat/data-components";
2
+ import h from "./panel.module.sass";
3
+ import { ReactNode } from "react";
4
+ import classNames from "classnames";
5
+
6
+ export function ClickableText({
7
+ className,
8
+ ...rest
9
+ }: {
10
+ className?: string;
11
+ children: ReactNode;
12
+ } & ItemInteractionProps) {
13
+ /** An optionally clickable text element */
14
+ const clickable = isClickable(rest);
15
+ const tag = clickable ? "a" : "span";
16
+ return h(tag, { className: classNames(className, { clickable }), ...rest });
17
+ }
18
+
19
+ export function Identifier({
20
+ id,
21
+ className,
22
+ ...rest
23
+ }: {
24
+ id: number | string;
25
+ className?: string;
26
+ } & ItemInteractionProps) {
27
+ /** An item that displays a numeric identifier, optionally clickable */
28
+ return h(
29
+ ClickableText,
30
+ {
31
+ className: classNames("identifier", className),
32
+ ...rest,
33
+ },
34
+ id,
35
+ );
36
+ }
37
+
38
+ export type UnitInfo = {
39
+ unitID: number;
40
+ colID?: number;
41
+ name?: string;
42
+ };
43
+
44
+ export function UnitIdentifier({
45
+ unitID,
46
+ colID,
47
+ name,
48
+ ...interactionProps
49
+ }: UnitInfo & ItemInteractionProps) {
50
+ if (name != null) {
51
+ return h(
52
+ ClickableText,
53
+ {
54
+ className: "unit-name",
55
+ ...interactionProps,
56
+ },
57
+ name,
58
+ );
59
+ }
60
+
61
+ return h(Identifier, {
62
+ className: "unit-id",
63
+ key: unitID,
64
+ id: unitID,
65
+ ...interactionProps,
66
+ });
67
+ }
@@ -2,3 +2,5 @@ export * from "./panel";
2
2
  export * from "./popover";
3
3
  export * from "./modal-panel";
4
4
  export * from "./age-range";
5
+ export * from "./strat-names";
6
+ export * from "./identifiers";
@@ -1,13 +1,9 @@
1
- import hyper from "@macrostrat/hyper";
2
- import styles from "./panel.module.sass";
3
1
  import { JSONView } from "@macrostrat/ui-components";
4
2
  import { Button, ButtonGroup } from "@blueprintjs/core";
5
3
  import { ReactNode, useMemo, useState } from "react";
6
4
  import {
7
5
  DataField,
8
6
  EnvironmentsList,
9
- isClickable,
10
- ItemInteractionProps,
11
7
  ItemList,
12
8
  LithologyList,
13
9
  LithologyTagFeature,
@@ -16,7 +12,6 @@ import {
16
12
  MacrostratItemIdentifier,
17
13
  Parenthetical,
18
14
  useInteractionManager,
19
- useInteractionProps,
20
15
  Value,
21
16
  } from "@macrostrat/data-components";
22
17
  import { useColumnUnitsMap } from "../data-provider";
@@ -24,10 +19,10 @@ import {
24
19
  useMacrostratColumnInfo,
25
20
  useMacrostratData,
26
21
  useMacrostratDefs,
27
- useStratNames,
28
22
  } from "@macrostrat/data-provider";
29
23
  import type {
30
24
  Environment,
25
+ EnvironmentIdentifier,
31
26
  Interval,
32
27
  Lithology,
33
28
  UnitLong,
@@ -37,8 +32,10 @@ import { defaultNameFunction } from "../units/names";
37
32
  import classNames from "classnames";
38
33
  import { AgeField, Duration, IntervalProportions } from "./age-range";
39
34
  import { formatRange, formatSignificance } from "./utils.ts";
40
-
41
- const h = hyper.styled(styles);
35
+ import h from "./panel.module.sass";
36
+ import { StratNameField } from "./strat-names.ts";
37
+ import { Identifier, UnitIdentifier, UnitInfo } from "./identifiers.ts";
38
+ import { LithologyIdentifier } from "@macrostrat/api-types/src/lithologies";
42
39
 
43
40
  export interface UnitDetailsPanelProps {
44
41
  unit: any;
@@ -51,6 +48,7 @@ export interface UnitDetailsPanelProps {
51
48
  onSelectUnit?: (unitID: number) => void;
52
49
  onClickItem?: MacrostratItemClickHandler;
53
50
  interactionManager?: MacrostratInteractionManager;
51
+ showJSON?: boolean;
54
52
  }
55
53
 
56
54
  export function UnitDetailsPanel({
@@ -68,11 +66,12 @@ export function UnitDetailsPanel({
68
66
  hiddenActions = null,
69
67
  onSelectUnit,
70
68
  onClickItem,
69
+ showJSON,
71
70
  }: UnitDetailsPanelProps) {
72
- const [showJSON, setShowJSON] = useState(false);
71
+ const [_showJSON, setShowJSON] = useState(false);
73
72
 
74
- let content = null;
75
- if (showJSON) {
73
+ let content: ReactNode = null;
74
+ if (showJSON ?? _showJSON) {
76
75
  content = h(JSONView, { data: unit, showRoot: false });
77
76
  } else {
78
77
  content = h(UnitDetailsContent, {
@@ -120,7 +119,13 @@ export function UnitDetailsPanel({
120
119
  if ("unit_id" in item && !("col_id" in item)) {
121
120
  // We are selecting a unit within the column
122
121
  return (event: MouseEvent) => {
123
- onSelectUnit(item.unit_id);
122
+ if (onSelectUnit != null) {
123
+ onSelectUnit(item.unit_id);
124
+ } else if (onClickItem != null) {
125
+ console.warn(
126
+ "Unit selection: onClickItem is defined but onSelectUnit is not; ignoring click",
127
+ );
128
+ }
124
129
  // Don't allow event to propagate further (e.g., to open a link)
125
130
  event.preventDefault();
126
131
  };
@@ -173,7 +178,7 @@ export function LegendPanelHeader({
173
178
  minimal: true,
174
179
  small: true,
175
180
  onClick() {
176
- onClose();
181
+ onClose?.();
177
182
  },
178
183
  }),
179
184
  ]);
@@ -223,7 +228,7 @@ function UnitDetailsContent({
223
228
  const environments = enhanceEnvironments(unit.environ, envMap);
224
229
  const lithologies = enhanceLithologies(unit.lith ?? [], lithMap);
225
230
 
226
- let outcropField = null;
231
+ let outcropField: ReactNode = null;
227
232
  if (features.has(UnitDetailsFeature.OutcropType)) {
228
233
  // Determine outcrop type
229
234
  let outcrop = unit.outcrop;
@@ -236,7 +241,7 @@ function UnitDetailsContent({
236
241
  });
237
242
  }
238
243
 
239
- let thicknessOrHeightRange = null;
244
+ let thicknessOrHeightRange: ReactNode = null;
240
245
  const [thickness, thicknessUnit] = getThickness(unit);
241
246
  // Proxy for actual heights in t_pos and b_pos
242
247
  if (
@@ -281,7 +286,7 @@ function UnitDetailsContent({
281
286
  thicknessOrHeightRange,
282
287
  h.if(lithologies != null)(LithologyList, {
283
288
  label: "Lithology",
284
- lithologies,
289
+ lithologies: lithologies ?? [],
285
290
  features: lithologyFeatures,
286
291
  }),
287
292
  h(AgeField, { unit }, [
@@ -363,53 +368,7 @@ export function ReferencesField({ refs, className = null, ...rest }) {
363
368
  );
364
369
  }
365
370
 
366
- function useStratNameData(strat_name_id: number) {
367
- const stratNames = useStratNames([strat_name_id]);
368
- return stratNames?.[0];
369
- }
370
-
371
- function StratNameField(
372
- props: {
373
- strat_name_id: number;
374
- className?: string;
375
- } & ItemInteractionProps,
376
- ) {
377
- /** Handling for stratigraphic name field */
378
- const { strat_name_id, className, ...rest } = props;
379
- const data = useStratNameData(strat_name_id);
380
-
381
- const baseInteractionProps = useInteractionProps({ strat_name_id });
382
-
383
- const coreProps = {
384
- ...baseInteractionProps,
385
- ...rest,
386
- };
387
-
388
- let inner: any = h(Identifier, { id: strat_name_id });
389
- const name = data?.strat_name_long;
390
- if (name != null) {
391
- inner = h("span.strat-name", name);
392
- }
393
-
394
- const clickable = isClickable(coreProps);
395
-
396
- return h(
397
- DataField,
398
- {
399
- label: "Stratigraphic name",
400
- },
401
- h(
402
- clickable ? "a" : "span",
403
- {
404
- className: classNames({ clickable }, className),
405
- ...coreProps,
406
- },
407
- inner,
408
- ),
409
- );
410
- }
411
-
412
- function getThickness(unit): [string, string] {
371
+ function getThickness(unit): [string, string | null] {
413
372
  let minThickness = unit.min_thick ?? 0;
414
373
  let maxThickness = unit.max_thick ?? unit.min_thick ?? 0;
415
374
  let _unit = "m";
@@ -480,8 +439,8 @@ function Citation({ data, tag = "p" }) {
480
439
  }
481
440
 
482
441
  function enhanceEnvironments(
483
- environments: Partial<Environment>[] | null,
484
- envMap: Map<number, Environment>,
442
+ environments?: EnvironmentIdentifier[] | null,
443
+ envMap?: Map<number, Environment> | null,
485
444
  ) {
486
445
  return environments?.map((env) => {
487
446
  return {
@@ -492,8 +451,8 @@ function enhanceEnvironments(
492
451
  }
493
452
 
494
453
  function enhanceLithologies(
495
- lithologies: Partial<UnitLong["lith"]>,
496
- lithMap: Map<number, any>,
454
+ lithologies?: LithologyIdentifier[] | null,
455
+ lithMap?: Map<number, any> | null,
497
456
  ) {
498
457
  return lithologies?.map((lith) => {
499
458
  return {
@@ -503,44 +462,6 @@ function enhanceLithologies(
503
462
  });
504
463
  }
505
464
 
506
- export function ClickableText({
507
- className,
508
- ...rest
509
- }: {
510
- className?: string;
511
- children: ReactNode;
512
- } & ItemInteractionProps) {
513
- /** An optionally clickable text element */
514
- const clickable = isClickable(rest);
515
- const tag = clickable ? "a" : "span";
516
- return h(tag, { className: classNames(className, { clickable }), ...rest });
517
- }
518
-
519
- export function Identifier({
520
- id,
521
- className,
522
- ...rest
523
- }: {
524
- id: number | string;
525
- className?: string;
526
- } & ItemInteractionProps) {
527
- /** An item that displays a numeric identifier, optionally clickable */
528
- return h(
529
- ClickableText,
530
- {
531
- className: classNames("identifier", className),
532
- ...rest,
533
- },
534
- id,
535
- );
536
- }
537
-
538
- type UnitInfo = {
539
- unitID: number;
540
- colID?: number;
541
- name?: string;
542
- };
543
-
544
465
  function UnitIDList({ units, showNames = false }) {
545
466
  const unitsMap = useColumnUnitsMap();
546
467
 
@@ -551,7 +472,7 @@ function UnitIDList({ units, showNames = false }) {
551
472
  if (showNames) {
552
473
  return u1.map((unitID) => {
553
474
  const unitData = unitsMap?.get(unitID);
554
- let name: string = undefined;
475
+ let name: string | null = null;
555
476
  if (unitData != null) {
556
477
  name = defaultNameFunction(unitData);
557
478
  }
@@ -586,28 +507,3 @@ function UnitIDList({ units, showNames = false }) {
586
507
  }),
587
508
  );
588
509
  }
589
-
590
- function UnitIdentifier({
591
- unitID,
592
- colID,
593
- name,
594
- ...interactionProps
595
- }: UnitInfo & ItemInteractionProps) {
596
- if (name != null) {
597
- return h(
598
- ClickableText,
599
- {
600
- className: "unit-name",
601
- ...interactionProps,
602
- },
603
- name,
604
- );
605
- }
606
-
607
- return h(Identifier, {
608
- className: "unit-id",
609
- key: unitID,
610
- id: unitID,
611
- ...interactionProps,
612
- });
613
- }
@@ -1,11 +1,7 @@
1
1
  import hyper from "@macrostrat/hyper";
2
2
  import { Popover } from "@blueprintjs/core";
3
3
  import styles from "./popover.module.sass";
4
- import {
5
- useAtomOverlayPosition,
6
- useSelectedUnit,
7
- useUnitSelectionDispatch,
8
- } from "../data-provider";
4
+ import { useAtomOverlayPosition, useUnitSelection } from "../data-provider";
9
5
  import {
10
6
  UnitDetailsFeature,
11
7
  UnitDetailsPanel,
@@ -66,8 +62,7 @@ function InteractionBarrier({ children }) {
66
62
  export function UnitSelectionPopover(
67
63
  props: Omit<UnitDetailsPanelProps, "onSelectUnit" | "unit">,
68
64
  ) {
69
- const unit = useSelectedUnit();
70
- const selectUnit = useUnitSelectionDispatch();
65
+ const [unit, selectUnit] = useUnitSelection();
71
66
  const position = useAtomOverlayPosition();
72
67
  if (unit == null) {
73
68
  return null;
@@ -0,0 +1,57 @@
1
+ import {
2
+ DataField,
3
+ isClickable,
4
+ ItemInteractionProps,
5
+ useInteractionProps,
6
+ } from "@macrostrat/data-components";
7
+ import h from "./panel.module.sass";
8
+ import classNames from "classnames";
9
+ import { useStratNames } from "@macrostrat/data-provider";
10
+ import { Identifier } from "./identifiers.ts";
11
+
12
+ function useStratNameData(strat_name_id: number) {
13
+ const stratNames = useStratNames([strat_name_id]);
14
+ return stratNames?.[0];
15
+ }
16
+
17
+ export function StratNameField(
18
+ props: {
19
+ strat_name_id: number;
20
+ className?: string;
21
+ } & ItemInteractionProps,
22
+ ) {
23
+ /** Handling for stratigraphic name field */
24
+ const { strat_name_id, className, ...rest } = props;
25
+
26
+ const data = useStratNameData(strat_name_id);
27
+
28
+ const baseInteractionProps = useInteractionProps({ strat_name_id });
29
+
30
+ const coreProps = {
31
+ ...baseInteractionProps,
32
+ ...rest,
33
+ };
34
+
35
+ let inner: any = h(Identifier, { id: strat_name_id });
36
+ const name = data?.strat_name_long;
37
+ if (name != null) {
38
+ inner = h("span.strat-name", name);
39
+ }
40
+
41
+ const clickable = isClickable(coreProps);
42
+
43
+ return h(
44
+ DataField,
45
+ {
46
+ label: "Stratigraphic name",
47
+ },
48
+ h(
49
+ clickable ? "a" : "span",
50
+ {
51
+ className: classNames({ clickable }, className),
52
+ ...coreProps,
53
+ },
54
+ inner,
55
+ ),
56
+ );
57
+ }