Skip to content

Commit 421a018

Browse files
authored
Merge pull request #184 from UW-Macrostrat/unit-details-state
Improve unit details state management
2 parents e85a508 + 21ccf36 commit 421a018

File tree

19 files changed

+257
-45
lines changed

19 files changed

+257
-45
lines changed

packages/column-views/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format
44
is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this
55
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [2.2.1] - 2025-11-29
8+
9+
- Start unifying state management components
10+
- Create a hoistable store for column state
11+
- Begin using `jotai` for some aspects of state management
12+
713
## [2.2.0] - 2025-11-28
814

915
- Update SGP and PBDB facets

packages/column-views/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@macrostrat/column-views",
3-
"version": "2.2.0",
3+
"version": "2.2.1",
44
"description": "Data views for Macrostrat stratigraphic columns",
55
"type": "module",
66
"source": "src/index.ts",
@@ -63,6 +63,8 @@
6363
"d3-path": "^3.1.0",
6464
"d3-scale": "^4.0.2",
6565
"d3-shape": "^3.2.0",
66+
"jotai": "^2.15.1",
67+
"jotai-scope": "^0.9.7",
6668
"mapbox-gl": "^2.15.0||^3.0.0",
6769
"react-spring": "^9.7.5",
6870
"topojson-client": "^3.1.0",

packages/column-views/src/column.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import {
1616
ComponentType,
1717
} from "react";
1818
import styles from "./column.module.sass";
19+
import { UnitComponent } from "./units";
1920
import {
2021
UnitSelectionProvider,
21-
UnitComponent,
2222
UnitKeyboardNavigation,
2323
useUnitSelectionDispatch,
24-
} from "./units";
24+
} from "./data-provider";
2525

2626
import {
2727
Identifier,
@@ -95,7 +95,6 @@ export function Column(props: ColumnProps) {
9595
selectedUnit,
9696
children,
9797
units: rawUnits,
98-
axisType = ColumnAxisType.AGE,
9998
t_age,
10099
b_age,
101100
t_pos,
@@ -109,6 +108,7 @@ export function Column(props: ColumnProps) {
109108
allowUnitSelection,
110109
hybridScale,
111110
scale,
111+
axisType,
112112
...rest
113113
} = props;
114114
const ref = useRef<HTMLElement>();
@@ -125,8 +125,20 @@ export function Column(props: ColumnProps) {
125125
_minPixelScale = pixelScale;
126126
}
127127

128+
// Handle special cases for hybrid scales (WIP, we need to regularize this)
129+
let _axisType = axisType ?? ColumnAxisType.AGE;
130+
let ageAxisComponent = CompositeAgeAxis;
131+
if (
132+
hybridScale?.type === HybridScaleType.ApproximateHeight &&
133+
_axisType != ColumnAxisType.AGE
134+
) {
135+
// Use approximate height axis for non-age columns if a non-age axis type is requested
136+
ageAxisComponent = ApproximateHeightAxis;
137+
_axisType = ColumnAxisType.AGE;
138+
}
139+
128140
const { sections, units, totalHeight } = usePreparedColumnUnits(rawUnits, {
129-
axisType,
141+
axisType: _axisType,
130142
t_age,
131143
b_age,
132144
t_pos,
@@ -154,15 +166,6 @@ export function Column(props: ColumnProps) {
154166
);
155167
}
156168

157-
let ageAxisComponent = CompositeAgeAxis;
158-
if (
159-
hybridScale?.type === HybridScaleType.ApproximateHeight &&
160-
axisType != ColumnAxisType.AGE
161-
) {
162-
// Use approximate height axis for non-age columns if a non-age axis type is requested
163-
ageAxisComponent = ApproximateHeightAxis;
164-
}
165-
166169
let main: any = h(
167170
ColumnInner,
168171
{ columnRef: ref, ageAxisComponent, ...rest },
@@ -196,7 +199,7 @@ export function Column(props: ColumnProps) {
196199

197200
return h(
198201
MacrostratColumnDataProvider,
199-
{ units, sections, totalHeight, axisType },
202+
{ units, sections, totalHeight, axisType: _axisType },
200203
main,
201204
);
202205
}

packages/column-views/src/correlation-chart/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
UnitSelectionProvider,
44
UnitKeyboardNavigation,
55
useUnitSelectionStore,
6-
} from "../units";
6+
} from "../data-provider";
77
import { UnitSelectionPopover } from "../unit-details";
88
import hyper from "@macrostrat/hyper";
99
import styles from "./main.module.sass";

packages/column-views/src/data-provider/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from "./fetch";
22
export * from "./base";
33
export * from "./column-nav";
44
export * from "./store";
5+
export * from "./unit-selection";

packages/column-views/src/data-provider/store.ts

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,61 @@ import {
66
} from "../prepare-units/composite-scale";
77
import { ColumnAxisType } from "@macrostrat/column-components";
88
import type { ExtUnit, PackageLayoutData } from "../prepare-units";
9+
// An isolated jotai store for Macrostrat column usage
10+
// TODO: there might be a better way to do this using the MacrostratDataProvider or similar
11+
import { createIsolation } from "jotai-scope";
12+
import { atom, type WritableAtom } from "jotai";
13+
14+
const { Provider, useAtom, useAtomValue, useStore } = createIsolation();
15+
16+
type ProviderProps = {
17+
children: ReactNode;
18+
initialValues?: Iterable<[WritableAtom<any, any, any>, any]>;
19+
};
20+
21+
const columnUnitsAtom = atom<ExtUnit[]>();
22+
23+
const columnUnitsMapAtom = atom<Map<number, ExtUnit> | null>((get) => {
24+
const units = get(columnUnitsAtom);
25+
if (!units) return null;
26+
const unitMap = new Map<number, ExtUnit>();
27+
units.forEach((unit) => {
28+
unitMap.set(unit.unit_id, unit);
29+
});
30+
return unitMap;
31+
});
32+
33+
function ScopedProvider({ children, ...rest }: ProviderProps) {
34+
// Always use the same store instance in this tree
35+
let val = null;
36+
try {
37+
val = useStore();
38+
} catch {
39+
// No store found, create a new one
40+
val = null;
41+
}
42+
return h(Provider, { store: val, ...rest }, children);
43+
}
44+
45+
export function MacrostratColumnStateProvider({
46+
children,
47+
units,
48+
}: {
49+
children: ReactNode;
50+
units: ExtUnit[];
51+
}) {
52+
/** Top-level provider for Macrostrat column data.
53+
* It is either provided by the Column component itself, or
54+
* can be hoisted higher in the tree to provide a common data context
55+
*/
56+
return h(
57+
ScopedProvider,
58+
{
59+
initialValues: [[columnUnitsAtom, units]],
60+
},
61+
children,
62+
);
63+
}
964

1065
export interface MacrostratColumnDataContext {
1166
units: ExtUnit[];
@@ -24,7 +79,7 @@ export function MacrostratColumnDataProvider({
2479
totalHeight,
2580
axisType,
2681
}: MacrostratColumnDataContext & { children: ReactNode }) {
27-
/** Provider for Macrostrat column data.
82+
/** Internal provider for Macrostrat column data.
2883
* As a general rule, we want to provide data and column-axis
2984
* height calculations through the context, since these need to
3085
* be accessed by any component that lays out information on the
@@ -45,7 +100,11 @@ export function MacrostratColumnDataProvider({
45100
};
46101
}, [units, sections, totalHeight, axisType]);
47102

48-
return h(MacrostratColumnDataContext.Provider, { value }, children);
103+
return h(
104+
MacrostratColumnStateProvider,
105+
{ units },
106+
h(MacrostratColumnDataContext.Provider, { value }, children),
107+
);
49108
}
50109

51110
export function useMacrostratColumnData() {
@@ -59,19 +118,15 @@ export function useMacrostratColumnData() {
59118
}
60119

61120
export function useMacrostratUnits() {
62-
return useMacrostratColumnData().units;
121+
return useAtomValue(columnUnitsAtom);
63122
}
64123

65124
export function useColumnUnitsMap(): Map<number, ExtUnit> | null {
66-
const ctx = useContext(MacrostratColumnDataContext);
67-
return useMemo(() => {
68-
if (ctx == null) return null;
69-
const unitMap = new Map<number, ExtUnit>();
70-
ctx.units.forEach((unit) => {
71-
unitMap.set(unit.unit_id, unit);
72-
});
73-
return unitMap;
74-
}, [ctx?.units]);
125+
try {
126+
return useAtomValue(columnUnitsMapAtom);
127+
} catch {
128+
return null;
129+
}
75130
}
76131

77132
export function useCompositeScale(): CompositeColumnScale {

packages/column-views/src/units/selection.ts renamed to packages/column-views/src/data-provider/unit-selection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
useCallback,
1313
} from "react";
1414
import { createStore, StoreApi, useStore } from "zustand";
15-
import type { RectBounds, IUnit } from "./types";
15+
import type { RectBounds, IUnit } from "../units/types";
1616

1717
type UnitSelectDispatch = (
1818
unit: number | BaseUnit | null,

packages/column-views/src/maps/column-correlation/state.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { lineIntersect } from "@turf/line-intersect";
66
import distance from "@turf/distance";
77
import { nearestPointOnLine } from "@turf/nearest-point-on-line";
88
import { centroid } from "@turf/centroid";
9-
import mapboxgl from "mapbox-gl";
109
import {
1110
createContext,
1211
useState,

packages/column-views/src/unit-details/panel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ function UnitIDList({ units, onSelectUnit, showNames = false }) {
593593
const u1 = units.filter((d) => d != 0);
594594
if (showNames) {
595595
return u1.map((unitID) => {
596-
const unitData = unitsMap.get(unitID);
596+
const unitData = unitsMap?.get(unitID);
597597
let name: string = undefined;
598598
if (unitData != null) {
599599
name = defaultNameFunction(unitData);

packages/column-views/src/unit-details/popover.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import hyper from "@macrostrat/hyper";
22
import { Popover } from "@blueprintjs/core";
33
import styles from "./popover.module.sass";
4-
import { useSelectedUnit, useUnitSelectionStore } from "../units";
4+
import { useSelectedUnit, useUnitSelectionStore } from "../data-provider";
55
import { UnitDetailsPanel } from "./panel";
66

77
const h = hyper.styled(styles);

0 commit comments

Comments
 (0)