Skip to content

Commit e448efb

Browse files
authored
Merge pull request #186 from UW-Macrostrat/column-updates
Column view updates
2 parents 421a018 + 64cc526 commit e448efb

File tree

13 files changed

+212
-126
lines changed

13 files changed

+212
-126
lines changed

packages/column-views/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ 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.2] - 2025-12-04
8+
9+
- Fix a bug with unit deselection
10+
- Fix missed updates in state management code
11+
- Add a 'minimal' option to `unconformityLabels`
12+
- Reduce precision of gap age labels
13+
- Improvements to stories
14+
715
## [2.2.1] - 2025-11-29
816

917
- Start unifying state management components

packages/column-views/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@macrostrat/column-views",
3-
"version": "2.2.1",
3+
"version": "2.2.2",
44
"description": "Data views for Macrostrat stratigraphic columns",
55
"type": "module",
66
"source": "src/index.ts",

packages/column-views/src/column.module.sass

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,11 @@ body:global(.dark-mode) .column-container
137137
--unit-font-weight: 400
138138

139139
.unconformity-inner
140-
//border-left: 1.5px dotted var(--secondary-color)
141140
position: absolute
142-
top: 5px
143-
bottom: 5px
144-
left: -3px
145-
right: -2px
141+
top: 3px
142+
bottom: 3px
143+
left: 0
144+
right: 0
146145
display: flex
147146
justify-content: center
148147
align-items: center
@@ -155,6 +154,9 @@ body:global(.dark-mode) .column-container
155154
font-weight: 400
156155
color: var(--secondary-color)
157156

157+
.timescale-column .unconformity-inner
158+
border-left: 1.5px dotted var(--secondary-color)
159+
left: -3px // align with age axis
158160

159161

160162
.column-title-row

packages/column-views/src/column.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,16 @@ interface BaseColumnProps extends SectionSharedProps {
6161
// Timescale properties
6262
showTimescale?: boolean;
6363
timescaleLevels?: number | [number, number];
64+
unconformityLabels?: boolean | UnconformityLabelPlacement;
6465
onMouseOver?: (
6566
unit: UnitLong | null,
6667
height: number | null,
6768
evt: MouseEvent,
6869
) => void;
6970
}
7071

72+
export type UnconformityLabelPlacement = "minimal" | "prominent" | "none";
73+
7174
export interface ColumnProps
7275
extends Padding, BaseColumnProps, ColumnHeightScaleOptions {
7376
// Macrostrat units
@@ -222,7 +225,7 @@ function ColumnInner(props: ColumnInnerProps) {
222225

223226
const {
224227
unitComponent = UnitComponent,
225-
unconformityLabels = true,
228+
unconformityLabels = "minimal",
226229
showLabels = true,
227230
width: _width = 300,
228231
columnWidth: _columnWidth = 150,
@@ -240,6 +243,15 @@ function ColumnInner(props: ColumnInnerProps) {
240243

241244
const { axisType } = useMacrostratColumnData();
242245

246+
// Coalesce unconformity label setting to a boolean
247+
let _timescaleUnconformityLabels = false;
248+
let _sectionUnconformityLabels = false;
249+
if (unconformityLabels === true || unconformityLabels === "prominent") {
250+
_sectionUnconformityLabels = true;
251+
} else if (unconformityLabels === "minimal") {
252+
_timescaleUnconformityLabels = true;
253+
}
254+
243255
let width = _width;
244256
let columnWidth = _columnWidth;
245257
if (columnWidth > width) {
@@ -265,15 +277,18 @@ function ColumnInner(props: ColumnInnerProps) {
265277
},
266278
h("div.column", { ref: columnRef }, [
267279
h(ageAxisComponent),
268-
h.if(_showTimescale)(CompositeTimescale, { levels: timescaleLevels }),
280+
h.if(_showTimescale)(CompositeTimescale, {
281+
levels: timescaleLevels,
282+
unconformityLabels: _timescaleUnconformityLabels,
283+
}),
269284
h(SectionsColumn, {
270285
unitComponent,
271286
showLabels,
272287
width,
273288
columnWidth,
274289
showLabelColumn,
275290
clipUnits,
276-
unconformityLabels,
291+
unconformityLabels: _sectionUnconformityLabels,
277292
maxInternalColumns,
278293
}),
279294
children,
@@ -351,18 +366,22 @@ export function ColumnContainer(props: ColumnContainerProps) {
351366
});
352367
}
353368

354-
export function ColumnBasicInfo({ data, showColumnID = true }) {
369+
export function ColumnBasicInfo({
370+
data,
371+
showColumnID = true,
372+
showReferences = true,
373+
}) {
355374
if (data == null) return null;
356375
return h("div.column-info", [
357376
h("div.column-title-row", [
358377
h("h2", data.col_name),
359378
h.if(showColumnID)("h4", h(Identifier, { id: data.col_id })),
360379
]),
361380
h(DataField, { row: true, label: "Group", value: data.col_group }),
362-
h(ReferencesField, {
381+
h.if(showReferences)(ReferencesField, {
363382
refs: data.refs,
364383
inline: false,
365-
row: true,
384+
row: false,
366385
className: "column-refs",
367386
}),
368387
]);

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

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { createContext, ReactNode, useContext, useMemo } from "react";
1+
import {
2+
createContext,
3+
ReactNode,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useRef,
8+
} from "react";
29
import h from "@macrostrat/hyper";
310
import {
411
CompositeColumnScale,
@@ -9,9 +16,9 @@ import type { ExtUnit, PackageLayoutData } from "../prepare-units";
916
// An isolated jotai store for Macrostrat column usage
1017
// TODO: there might be a better way to do this using the MacrostratDataProvider or similar
1118
import { createIsolation } from "jotai-scope";
12-
import { atom, type WritableAtom } from "jotai";
19+
import { atom, PrimitiveAtom, type WritableAtom } from "jotai";
1320

14-
const { Provider, useAtom, useAtomValue, useStore } = createIsolation();
21+
const { Provider, useSetAtom, useAtomValue, useStore } = createIsolation();
1522

1623
type ProviderProps = {
1724
children: ReactNode;
@@ -53,12 +60,17 @@ export function MacrostratColumnStateProvider({
5360
* It is either provided by the Column component itself, or
5461
* can be hoisted higher in the tree to provide a common data context
5562
*/
63+
64+
const atomMap: [[PrimitiveAtom<ExtUnit[]>, ExtUnit[]]] = [
65+
[columnUnitsAtom, units],
66+
];
67+
5668
return h(
5769
ScopedProvider,
5870
{
59-
initialValues: [[columnUnitsAtom, units]],
71+
initialValues: atomMap,
6072
},
61-
children,
73+
[h(AtomUpdater, { atoms: atomMap }), children],
6274
);
6375
}
6476

@@ -136,3 +148,34 @@ export function useCompositeScale(): CompositeColumnScale {
136148
[ctx.sections],
137149
);
138150
}
151+
152+
function AtomUpdater({
153+
atoms,
154+
}: {
155+
atoms: [WritableAtom<any, any, any>, any][];
156+
}) {
157+
/**
158+
* A generic updater to sync Jotai atoms with state passed as props.
159+
* Useful for scoped providers where state needs to be synced outside
160+
* of the current context.
161+
*/
162+
/** TODO: this is an awkward way to keep atoms updated */
163+
// The scoped store
164+
const store = useStore();
165+
166+
// Warn on atoms changing length
167+
const prevLengthRef = useRef(atoms.length);
168+
useEffect(() => {
169+
if (prevLengthRef.current !== atoms.length) {
170+
console.error("Error: number of atoms in ScopedAtomUpdater has changed.");
171+
prevLengthRef.current = atoms.length;
172+
}
173+
}, [atoms.length]);
174+
175+
for (const [atom, value] of atoms) {
176+
useEffect(() => {
177+
store.set(atom, value);
178+
}, [store, value]);
179+
}
180+
return null;
181+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export function UnitSelectionProvider<T extends BaseUnit>(props: {
9393
event: PointerEvent = null,
9494
) => {
9595
if (input == null) {
96+
props.onUnitSelected?.(null, null);
9697
return set({
9798
selectedUnit: null,
9899
selectedUnitData: null,

packages/column-views/src/section.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function SectionsColumn(props: SectionSharedProps) {
6363
const units = useMacrostratUnits();
6464

6565
// Get a unique key for the column
66-
const key = units[0]?.unit_id;
66+
const key = units[0]?.col_id;
6767

6868
return h(LabelTrackerProvider, { units, key }, [
6969
h(SectionUnitsColumn, {
@@ -199,6 +199,7 @@ function SectionUnits(props: SectionProps) {
199199

200200
interface CompositeTimescaleProps {
201201
levels?: [number, number] | number;
202+
unconformityLabels?: boolean;
202203
}
203204

204205
export function CompositeTimescale(props: CompositeTimescaleProps) {
@@ -215,7 +216,6 @@ export function CompositeTimescale(props: CompositeTimescaleProps) {
215216

216217
type CompositeTimescaleCoreProps = CompositeTimescaleProps & {
217218
packages: PackageScaleLayoutData[];
218-
unconformityLabels?: boolean;
219219
};
220220

221221
export function CompositeTimescaleCore(props: CompositeTimescaleCoreProps) {
@@ -316,6 +316,8 @@ function Unconformity({
316316

317317
const ageGap = Math.abs(upperAge - lowerAge);
318318

319+
let maximumFractionDigits = 0;
320+
319321
let className: string = null;
320322
if (ageGap > 1000) {
321323
className = "giga";
@@ -325,14 +327,17 @@ function Unconformity({
325327
className = "large";
326328
} else if (ageGap < 1) {
327329
className = "small";
330+
maximumFractionDigits = 2;
331+
} else {
332+
maximumFractionDigits = 1;
328333
}
329334

330335
let val: ReactNode;
331336
if (axisType === ColumnAxisType.DEPTH || axisType === ColumnAxisType.HEIGHT) {
332-
const _txt = ageGap.toLocaleString("en-US", { maximumFractionDigits: 2 });
337+
const _txt = ageGap.toLocaleString("en-US", { maximumFractionDigits });
333338
val = h(Value, { value: _txt, unit: "m" });
334339
} else {
335-
val = h(Duration, { value: ageGap });
340+
val = h(Duration, { value: ageGap, maximumFractionDigits });
336341
}
337342

338343
let prefix: ReactNode = null;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,11 @@ export function ReferencesField({ refs, className = null, ...rest }) {
301301
if (refs == null || refs.length === 0) {
302302
return null;
303303
}
304+
304305
return h(
305306
DataField,
306307
{
307-
label: "Source",
308+
label: "References",
308309
className: classNames("refs-field", className),
309310
...rest,
310311
},
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
export const sharedColumnArgTypes = {
2+
columnID: {
3+
control: {
4+
type: "number",
5+
},
6+
},
7+
selectedUnit: {
8+
control: {
9+
type: "number",
10+
},
11+
},
12+
t_age: {
13+
control: {
14+
type: "number",
15+
},
16+
},
17+
b_age: {
18+
control: {
19+
type: "number",
20+
},
21+
},
22+
unconformityLabels: {
23+
options: ["minimal", "prominent", "none"],
24+
control: { type: "radio" },
25+
},
26+
axisType: {
27+
options: ["age", "ordinal", "depth"],
28+
control: { type: "radio" },
29+
},
30+
mergeSections: {
31+
options: ["all", "overlapping", null],
32+
control: { type: "radio" },
33+
},
34+
pixelScale: {
35+
control: {
36+
type: "number",
37+
},
38+
},
39+
collapseSmallUnconformities: {
40+
control: {
41+
type: "boolean",
42+
},
43+
},
44+
minSectionHeight: {
45+
control: {
46+
type: "number",
47+
},
48+
},
49+
targetUnitHeight: {
50+
control: {
51+
type: "number",
52+
},
53+
},
54+
showLabelColumn: {
55+
control: {
56+
type: "boolean",
57+
},
58+
},
59+
maxInternalColumns: {
60+
control: {
61+
type: "number",
62+
},
63+
},
64+
};

0 commit comments

Comments
 (0)