Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/column-views/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format
is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.2.2] - 2025-12-04

- Fix a bug with unit deselection
- Fix missed updates in state management code
- Add a 'minimal' option to `unconformityLabels`
- Reduce precision of gap age labels
- Improvements to stories

## [2.2.1] - 2025-11-29

- Start unifying state management components
Expand Down
2 changes: 1 addition & 1 deletion packages/column-views/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@macrostrat/column-views",
"version": "2.2.1",
"version": "2.2.2",
"description": "Data views for Macrostrat stratigraphic columns",
"type": "module",
"source": "src/index.ts",
Expand Down
12 changes: 7 additions & 5 deletions packages/column-views/src/column.module.sass
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,11 @@ body:global(.dark-mode) .column-container
--unit-font-weight: 400

.unconformity-inner
//border-left: 1.5px dotted var(--secondary-color)
position: absolute
top: 5px
bottom: 5px
left: -3px
right: -2px
top: 3px
bottom: 3px
left: 0
right: 0
display: flex
justify-content: center
align-items: center
Expand All @@ -155,6 +154,9 @@ body:global(.dark-mode) .column-container
font-weight: 400
color: var(--secondary-color)

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


.column-title-row
Expand Down
31 changes: 25 additions & 6 deletions packages/column-views/src/column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,16 @@ interface BaseColumnProps extends SectionSharedProps {
// Timescale properties
showTimescale?: boolean;
timescaleLevels?: number | [number, number];
unconformityLabels?: boolean | UnconformityLabelPlacement;
onMouseOver?: (
unit: UnitLong | null,
height: number | null,
evt: MouseEvent,
) => void;
}

export type UnconformityLabelPlacement = "minimal" | "prominent" | "none";

export interface ColumnProps
extends Padding, BaseColumnProps, ColumnHeightScaleOptions {
// Macrostrat units
Expand Down Expand Up @@ -222,7 +225,7 @@ function ColumnInner(props: ColumnInnerProps) {

const {
unitComponent = UnitComponent,
unconformityLabels = true,
unconformityLabels = "minimal",
showLabels = true,
width: _width = 300,
columnWidth: _columnWidth = 150,
Expand All @@ -240,6 +243,15 @@ function ColumnInner(props: ColumnInnerProps) {

const { axisType } = useMacrostratColumnData();

// Coalesce unconformity label setting to a boolean
let _timescaleUnconformityLabels = false;
let _sectionUnconformityLabels = false;
if (unconformityLabels === true || unconformityLabels === "prominent") {
_sectionUnconformityLabels = true;
} else if (unconformityLabels === "minimal") {
_timescaleUnconformityLabels = true;
}

let width = _width;
let columnWidth = _columnWidth;
if (columnWidth > width) {
Expand All @@ -265,15 +277,18 @@ function ColumnInner(props: ColumnInnerProps) {
},
h("div.column", { ref: columnRef }, [
h(ageAxisComponent),
h.if(_showTimescale)(CompositeTimescale, { levels: timescaleLevels }),
h.if(_showTimescale)(CompositeTimescale, {
levels: timescaleLevels,
unconformityLabels: _timescaleUnconformityLabels,
}),
h(SectionsColumn, {
unitComponent,
showLabels,
width,
columnWidth,
showLabelColumn,
clipUnits,
unconformityLabels,
unconformityLabels: _sectionUnconformityLabels,
maxInternalColumns,
}),
children,
Expand Down Expand Up @@ -351,18 +366,22 @@ export function ColumnContainer(props: ColumnContainerProps) {
});
}

export function ColumnBasicInfo({ data, showColumnID = true }) {
export function ColumnBasicInfo({
data,
showColumnID = true,
showReferences = true,
}) {
if (data == null) return null;
return h("div.column-info", [
h("div.column-title-row", [
h("h2", data.col_name),
h.if(showColumnID)("h4", h(Identifier, { id: data.col_id })),
]),
h(DataField, { row: true, label: "Group", value: data.col_group }),
h(ReferencesField, {
h.if(showReferences)(ReferencesField, {
refs: data.refs,
inline: false,
row: true,
row: false,
className: "column-refs",
}),
]);
Expand Down
53 changes: 48 additions & 5 deletions packages/column-views/src/data-provider/store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { createContext, ReactNode, useContext, useMemo } from "react";
import {
createContext,
ReactNode,
useContext,
useEffect,
useMemo,
useRef,
} from "react";
import h from "@macrostrat/hyper";
import {
CompositeColumnScale,
Expand All @@ -9,9 +16,9 @@ import type { ExtUnit, PackageLayoutData } from "../prepare-units";
// An isolated jotai store for Macrostrat column usage
// TODO: there might be a better way to do this using the MacrostratDataProvider or similar
import { createIsolation } from "jotai-scope";
import { atom, type WritableAtom } from "jotai";
import { atom, PrimitiveAtom, type WritableAtom } from "jotai";

const { Provider, useAtom, useAtomValue, useStore } = createIsolation();
const { Provider, useSetAtom, useAtomValue, useStore } = createIsolation();

type ProviderProps = {
children: ReactNode;
Expand Down Expand Up @@ -53,12 +60,17 @@ export function MacrostratColumnStateProvider({
* It is either provided by the Column component itself, or
* can be hoisted higher in the tree to provide a common data context
*/

const atomMap: [[PrimitiveAtom<ExtUnit[]>, ExtUnit[]]] = [
[columnUnitsAtom, units],
];

return h(
ScopedProvider,
{
initialValues: [[columnUnitsAtom, units]],
initialValues: atomMap,
},
children,
[h(AtomUpdater, { atoms: atomMap }), children],
);
}

Expand Down Expand Up @@ -136,3 +148,34 @@ export function useCompositeScale(): CompositeColumnScale {
[ctx.sections],
);
}

function AtomUpdater({
atoms,
}: {
atoms: [WritableAtom<any, any, any>, any][];
}) {
/**
* A generic updater to sync Jotai atoms with state passed as props.
* Useful for scoped providers where state needs to be synced outside
* of the current context.
*/
/** TODO: this is an awkward way to keep atoms updated */
// The scoped store
const store = useStore();

// Warn on atoms changing length
const prevLengthRef = useRef(atoms.length);
useEffect(() => {
if (prevLengthRef.current !== atoms.length) {
console.error("Error: number of atoms in ScopedAtomUpdater has changed.");
prevLengthRef.current = atoms.length;
}
}, [atoms.length]);

for (const [atom, value] of atoms) {
useEffect(() => {
store.set(atom, value);
}, [store, value]);
}
return null;
}
1 change: 1 addition & 0 deletions packages/column-views/src/data-provider/unit-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export function UnitSelectionProvider<T extends BaseUnit>(props: {
event: PointerEvent = null,
) => {
if (input == null) {
props.onUnitSelected?.(null, null);
return set({
selectedUnit: null,
selectedUnitData: null,
Expand Down
13 changes: 9 additions & 4 deletions packages/column-views/src/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function SectionsColumn(props: SectionSharedProps) {
const units = useMacrostratUnits();

// Get a unique key for the column
const key = units[0]?.unit_id;
const key = units[0]?.col_id;

return h(LabelTrackerProvider, { units, key }, [
h(SectionUnitsColumn, {
Expand Down Expand Up @@ -199,6 +199,7 @@ function SectionUnits(props: SectionProps) {

interface CompositeTimescaleProps {
levels?: [number, number] | number;
unconformityLabels?: boolean;
}

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

type CompositeTimescaleCoreProps = CompositeTimescaleProps & {
packages: PackageScaleLayoutData[];
unconformityLabels?: boolean;
};

export function CompositeTimescaleCore(props: CompositeTimescaleCoreProps) {
Expand Down Expand Up @@ -316,6 +316,8 @@ function Unconformity({

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

let maximumFractionDigits = 0;

let className: string = null;
if (ageGap > 1000) {
className = "giga";
Expand All @@ -325,14 +327,17 @@ function Unconformity({
className = "large";
} else if (ageGap < 1) {
className = "small";
maximumFractionDigits = 2;
} else {
maximumFractionDigits = 1;
}

let val: ReactNode;
if (axisType === ColumnAxisType.DEPTH || axisType === ColumnAxisType.HEIGHT) {
const _txt = ageGap.toLocaleString("en-US", { maximumFractionDigits: 2 });
const _txt = ageGap.toLocaleString("en-US", { maximumFractionDigits });
val = h(Value, { value: _txt, unit: "m" });
} else {
val = h(Duration, { value: ageGap });
val = h(Duration, { value: ageGap, maximumFractionDigits });
}

let prefix: ReactNode = null;
Expand Down
3 changes: 2 additions & 1 deletion packages/column-views/src/unit-details/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,11 @@ export function ReferencesField({ refs, className = null, ...rest }) {
if (refs == null || refs.length === 0) {
return null;
}

return h(
DataField,
{
label: "Source",
label: "References",
className: classNames("refs-field", className),
...rest,
},
Expand Down
64 changes: 64 additions & 0 deletions packages/column-views/stories/arg-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export const sharedColumnArgTypes = {
columnID: {
control: {
type: "number",
},
},
selectedUnit: {
control: {
type: "number",
},
},
t_age: {
control: {
type: "number",
},
},
b_age: {
control: {
type: "number",
},
},
unconformityLabels: {
options: ["minimal", "prominent", "none"],
control: { type: "radio" },
},
axisType: {
options: ["age", "ordinal", "depth"],
control: { type: "radio" },
},
mergeSections: {
options: ["all", "overlapping", null],
control: { type: "radio" },
},
pixelScale: {
control: {
type: "number",
},
},
collapseSmallUnconformities: {
control: {
type: "boolean",
},
},
minSectionHeight: {
control: {
type: "number",
},
},
targetUnitHeight: {
control: {
type: "number",
},
},
showLabelColumn: {
control: {
type: "boolean",
},
},
maxInternalColumns: {
control: {
type: "number",
},
},
};
Loading