@macrostrat/column-views 3.4.1 → 3.5.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.
@@ -16,6 +16,7 @@ function useSelectedUnit() {
16
16
  }
17
17
  const allowUnitSelectionAtom = jotai.atom(true);
18
18
  const selectedUnitIDAtom = jotai.atom();
19
+ const selectedUnitElementAtom = jotai.atom();
19
20
  const overlayPositionAtom = jotai.atom();
20
21
  const columnRefAtom = jotai.atom({ current: null });
21
22
  function useColumnRef() {
@@ -64,6 +65,7 @@ const selectedUnitAtom = jotai.atom(
64
65
  height: targetRect.height
65
66
  };
66
67
  }
68
+ set(selectedUnitElementAtom, target);
67
69
  set(selectedUnitIDAtom, unitID);
68
70
  set(overlayPositionAtom, overlayPosition);
69
71
  return unit;
@@ -85,6 +87,7 @@ function useUnitSelectionTarget(unit) {
85
87
  const ref = React.useRef(null);
86
88
  const [selectedUnit, selectUnit] = useUnitSelection();
87
89
  const selected = selectedUnit?.unit_id == unit.unit_id;
90
+ const selectedUnitElement = store.scope.useAtomValue(selectedUnitElementAtom);
88
91
  const onClick = React.useCallback(
89
92
  (evt) => {
90
93
  selectUnit?.(unit, ref.current);
@@ -93,14 +96,16 @@ function useUnitSelectionTarget(unit) {
93
96
  [unit, selectUnit]
94
97
  );
95
98
  React.useEffect(() => {
96
- if (!selected || selectUnit == null) return;
97
- selectUnit(unit, ref.current);
98
- ref.current?.scrollIntoView({
99
+ if (!selected) return;
100
+ selectUnit?.(unit, ref.current);
101
+ }, [ref.current, selected, selectUnit]);
102
+ React.useEffect(() => {
103
+ selectedUnitElement?.scrollIntoView({
99
104
  behavior: "smooth",
100
105
  block: "center",
101
106
  inline: "nearest"
102
107
  });
103
- }, [selected, selectUnit]);
108
+ }, [selectedUnitElement]);
104
109
  return [ref, selected, onClick];
105
110
  }
106
111
  function UnitKeyboardNavigation({
@@ -219,6 +224,7 @@ function getMostOverlappingUnit(targetUnit, candidateUnits) {
219
224
  exports.UnitKeyboardNavigation = UnitKeyboardNavigation;
220
225
  exports.UnitSelectionCallbackManager = UnitSelectionCallbackManager;
221
226
  exports.allowUnitSelectionAtom = allowUnitSelectionAtom;
227
+ exports.selectedUnitElementAtom = selectedUnitElementAtom;
222
228
  exports.selectedUnitIDAtom = selectedUnitIDAtom;
223
229
  exports.useAtomOverlayPosition = useAtomOverlayPosition;
224
230
  exports.useColumnRef = useColumnRef;
@@ -1 +1 @@
1
- {"version":3,"file":"unit-selection.cjs","sources":["../../src/data-provider/unit-selection.ts"],"sourcesContent":["import type { BaseUnit, UnitLong } from \"@macrostrat/api-types\";\nimport { useKeyHandler } from \"@macrostrat/ui-components\";\nimport { useEffect, useRef, useCallback, useMemo } from \"react\";\nimport type { RectBounds, IUnit } from \"../units/types\";\nimport { atom } from \"jotai\";\nimport { scope, columnUnitsMapAtom } from \"./store\";\nimport {\n AgeRangeQuantifiedDifference,\n ageRangeQuantifiedDifference,\n AgeRangeRelationship,\n} from \"@macrostrat/stratigraphy-utils\";\nimport type { ColumnData } from \"@macrostrat/data-provider\";\n\ntype UnitSelectDispatch = (\n unit: number | BaseUnit | null,\n target: HTMLElement | null,\n) => void;\n\nexport function useUnitSelection() {\n return scope.use(selectedUnitAtom);\n}\n\nexport function useUnitSelectionDispatch(): UnitSelectDispatch {\n return scope.useSetAtom(selectedUnitAtom);\n}\n\nexport function useSelectedUnit() {\n return scope.useAtomValue(selectedUnitAtom);\n}\n\nexport interface ColumnClickData {\n unitID: number | null;\n unit: BaseUnit | null;\n target: HTMLElement | null;\n height: number;\n // Room for boundary IDs eventually\n}\n\nexport interface UnitSelectionCallbacks {\n // It's sort of unfortunate that we need to pass in the column ref here\n onClickedColumn?: (columnClickData: ColumnClickData, event: Event) => void;\n onUnitSelected?: <T extends BaseUnit>(\n unitID: number | null,\n unit: T | null,\n ) => void;\n}\n\nexport const allowUnitSelectionAtom = atom<boolean>(true);\n\nexport const selectedUnitIDAtom = atom<number | null>();\n\nconst overlayPositionAtom = atom<RectBounds | null>();\n\nconst columnRefAtom = atom<{ current: HTMLElement | null }>({ current: null });\n\nexport function useColumnRef() {\n return scope.useAtomValue(columnRefAtom);\n}\n\nconst selectedUnitAtom = atom(\n (get) => {\n if (!get(allowUnitSelectionAtom)) return null;\n\n const unitID = get(selectedUnitIDAtom);\n if (unitID == null) return null;\n const unitsMap = get(columnUnitsMapAtom);\n return unitsMap?.get(unitID) || null;\n },\n (\n get,\n set,\n selectedUnit: number | BaseUnit | null,\n target: HTMLElement | null = null,\n ): BaseUnit | null => {\n if (!get(allowUnitSelectionAtom)) {\n console.error(\"Unit selection is disabled.\");\n return null;\n }\n\n let unitID: number | null = null;\n let unit: BaseUnit | null = null;\n if (selectedUnit == null) {\n unitID = null;\n } else if (typeof selectedUnit === \"number\") {\n unitID = selectedUnit;\n } else if (\"unit_id\" in selectedUnit) {\n unitID = selectedUnit.unit_id;\n }\n\n if (unitID != null) {\n const unitsMap = get(columnUnitsMapAtom);\n // Verify that the unit exists in the current colum, else throw\n if (!unitsMap?.has(unitID)) {\n throw new Error(\n `Unit with ID ${unitID} not found in current column units.`,\n );\n }\n unit = unitsMap.get(unitID) ?? null;\n }\n\n let overlayPosition: RectBounds | null = null;\n\n const columnEl = get(columnRefAtom)?.current;\n\n if (unit != null && columnEl != null && target != null) {\n const rect = columnEl.getBoundingClientRect();\n const targetRect = target.getBoundingClientRect();\n overlayPosition = {\n x: targetRect.left - rect.left,\n y: targetRect.top - rect.top,\n width: targetRect.width,\n height: targetRect.height,\n };\n }\n\n set(selectedUnitIDAtom, unitID);\n set(overlayPositionAtom, overlayPosition);\n\n return unit;\n },\n);\n\nexport function useAtomOverlayPosition() {\n return scope.useAtomValue(overlayPositionAtom);\n}\n\nexport function UnitSelectionCallbackManager({\n onUnitSelected,\n}: UnitSelectionCallbacks) {\n const selectedUnit = scope.useAtomValue(selectedUnitAtom);\n useEffect(() => {\n onUnitSelected?.(selectedUnit?.unit_id ?? null, selectedUnit ?? null);\n }, [selectedUnit?.unit_id]);\n return null;\n}\n\n/** TODO: figure out if onClickedColumn is still needed\n /** This is not the natural place to get positions within the column,\n * but it will work for now.\nif (props.onClickedColumn) {\n // Infer height from top and bottom height of unit (because that's passed back with the call)\n //const py = event.y;\n //const [bottom, top] = getUnitHeightRange(unit, axisType);\n //const height = Math.abs(bottom - top);\n\n const columnClickData: ColumnClickData = {\n unitID: unit?.unit_id,\n unit,\n target,\n height: el?.getBoundingClientRect().height || 0,\n };\n props.onClickedColumn(columnClickData, event);\n}*/\n\nexport function useUnitSelectionTarget(\n unit: IUnit,\n): [React.RefObject<HTMLElement>, boolean, (evt: Event) => void] {\n const ref = useRef<HTMLElement>(null);\n const [selectedUnit, selectUnit] = useUnitSelection();\n const selected = selectedUnit?.unit_id == unit.unit_id;\n\n const onClick = useCallback(\n (evt: Event) => {\n selectUnit?.(unit, ref.current);\n evt.stopPropagation();\n },\n [unit, selectUnit],\n );\n\n useEffect(() => {\n if (!selected || selectUnit == null) return;\n // In case we haven't set the position of the unit (if we don't have a target), set the selected unit\n selectUnit(unit, ref.current);\n\n // Scroll the unit into view\n ref.current?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n inline: \"nearest\",\n });\n }, [selected, selectUnit]);\n\n return [ref, selected, onClick];\n}\n\nexport function UnitKeyboardNavigation({\n columnData,\n units,\n allowHorizontalNavigation,\n}: {\n columnData?: ColumnData[];\n units?: UnitLong[];\n allowHorizontalNavigation?: boolean;\n}) {\n if (units == null && columnData == null) {\n throw new Error(\"Either units or columnData must be provided.\");\n }\n\n let _columnData: ColumnData[] = useMemo(() => {\n if (columnData != null) return columnData;\n // Build column data from units\n const colMap = new Map<number, UnitLong[]>();\n for (const unit of units!) {\n if (!colMap.has(unit.col_id)) {\n colMap.set(unit.col_id, []);\n }\n colMap.get(unit.col_id)!.push(unit);\n }\n const cols: ColumnData[] = [];\n for (const [colID, units] of colMap) {\n cols.push({ columnID: colID, units });\n }\n // Sort columns by columnID\n cols.sort((a, b) => a.columnID - b.columnID);\n return cols;\n }, [columnData, units]);\n\n // Default to allowing horizontal navigation only if columnData is provided\n const _allowHorizontalNavigation =\n allowHorizontalNavigation ?? columnData != null;\n\n const selectedUnit = useSelectedUnit() as UnitLong | null;\n const selectUnit = useUnitSelectionDispatch();\n\n const keyMap: Record<number, Direction> = {\n 38: \"up\",\n 40: \"down\",\n };\n if (_allowHorizontalNavigation) {\n keyMap[37] = \"left\";\n keyMap[39] = \"right\";\n }\n\n useKeyHandler(\n (event) => {\n const direction = keyMap[event.keyCode];\n if (direction == null) return;\n const nextUnit = getBestUnit(_columnData, selectedUnit, direction);\n if (nextUnit == null) return;\n selectUnit(nextUnit, null);\n event.stopPropagation();\n },\n [_columnData, selectedUnit],\n );\n return null;\n}\n\ntype Direction = \"up\" | \"down\" | \"left\" | \"right\";\n\nfunction getBestUnit(\n columnData: ColumnData[],\n targetUnit: UnitLong,\n direction: Direction,\n): UnitLong | null {\n const thisColIndex = columnData.findIndex(\n (col) => col.columnID === targetUnit.col_id,\n );\n if (thisColIndex === -1) return null;\n\n // If up or down, stay in the same column\n if (direction === \"up\" || direction === \"down\") {\n const units = columnData[thisColIndex].units;\n const ix = units.findIndex((unit) => unit.unit_id === targetUnit.unit_id);\n if (ix === -1) return null;\n if (direction === \"up\") {\n return units[ix - 1] || null;\n } else {\n return units[ix + 1] || null;\n }\n }\n\n // If left or right, move to adjacent column\n let adjacentColIndex: number;\n if (direction === \"left\") {\n adjacentColIndex =\n (thisColIndex - 1 + columnData.length) % columnData.length;\n } else {\n adjacentColIndex = (thisColIndex + 1) % columnData.length;\n }\n\n const adjacentColUnits = columnData[adjacentColIndex].units;\n return getMostOverlappingUnit(targetUnit, adjacentColUnits);\n}\n\ntype UnitAgeRangeRelationship = AgeRangeQuantifiedDifference & {\n unit: UnitLong;\n};\n\nfunction getMostOverlappingUnit(\n targetUnit: UnitLong,\n candidateUnits: UnitLong[],\n): UnitLong | null {\n const targetAgeRange = [targetUnit.b_age, targetUnit.t_age];\n\n const overlaps: UnitAgeRangeRelationship[] = [];\n for (const candidate of candidateUnits) {\n const candidateAgeRange = [candidate.b_age, candidate.t_age];\n\n const rel = ageRangeQuantifiedDifference(targetAgeRange, candidateAgeRange);\n if (rel.type === AgeRangeRelationship.Identical) {\n return candidate;\n }\n overlaps.push({ ...rel, unit: candidate });\n }\n\n let bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Containing,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Contained,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.PartialOverlap,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Disjoint,\n );\n bestOverlaps.sort((a, b) => a.distance - b.distance);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n return null;\n}\n"],"names":["scope","atom","columnUnitsMapAtom","useEffect","useRef","useCallback","useMemo","units","useKeyHandler","ageRangeQuantifiedDifference","AgeRangeRelationship"],"mappings":"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"}
1
+ {"version":3,"file":"unit-selection.cjs","sources":["../../src/data-provider/unit-selection.ts"],"sourcesContent":["import type { BaseUnit, UnitLong } from \"@macrostrat/api-types\";\nimport { useKeyHandler } from \"@macrostrat/ui-components\";\nimport { useEffect, useRef, useCallback, useMemo } from \"react\";\nimport type { RectBounds, IUnit } from \"../units/types\";\nimport { atom } from \"jotai\";\nimport { scope, columnUnitsMapAtom } from \"./store\";\nimport {\n AgeRangeQuantifiedDifference,\n ageRangeQuantifiedDifference,\n AgeRangeRelationship,\n} from \"@macrostrat/stratigraphy-utils\";\nimport type { ColumnData } from \"@macrostrat/data-provider\";\n\ntype UnitSelectDispatch = (\n unit: number | BaseUnit | null,\n target: HTMLElement | null,\n) => void;\n\nexport function useUnitSelection() {\n return scope.use(selectedUnitAtom);\n}\n\nexport function useUnitSelectionDispatch(): UnitSelectDispatch {\n return scope.useSetAtom(selectedUnitAtom);\n}\n\nexport function useSelectedUnit() {\n return scope.useAtomValue(selectedUnitAtom);\n}\n\nexport interface ColumnClickData {\n unitID: number | null;\n unit: BaseUnit | null;\n target: HTMLElement | null;\n height: number;\n // Room for boundary IDs eventually\n}\n\nexport interface UnitSelectionCallbacks {\n // It's sort of unfortunate that we need to pass in the column ref here\n onClickedColumn?: (columnClickData: ColumnClickData, event: Event) => void;\n onUnitSelected?: <T extends BaseUnit>(\n unitID: number | null,\n unit: T | null,\n ) => void;\n}\n\nexport const allowUnitSelectionAtom = atom<boolean>(true);\n\nexport const selectedUnitIDAtom = atom<number | null>();\nexport const selectedUnitElementAtom = atom<HTMLElement | null>();\n\nconst overlayPositionAtom = atom<RectBounds | null>();\n\nconst columnRefAtom = atom<{ current: HTMLElement | null }>({ current: null });\n\nexport function useColumnRef() {\n return scope.useAtomValue(columnRefAtom);\n}\n\nconst selectedUnitAtom = atom(\n (get) => {\n if (!get(allowUnitSelectionAtom)) return null;\n\n const unitID = get(selectedUnitIDAtom);\n if (unitID == null) return null;\n const unitsMap = get(columnUnitsMapAtom);\n return unitsMap?.get(unitID) || null;\n },\n (\n get,\n set,\n selectedUnit: number | BaseUnit | null,\n target: HTMLElement | null = null,\n ): BaseUnit | null => {\n if (!get(allowUnitSelectionAtom)) {\n console.error(\"Unit selection is disabled.\");\n return null;\n }\n\n let unitID: number | null = null;\n let unit: BaseUnit | null = null;\n if (selectedUnit == null) {\n unitID = null;\n } else if (typeof selectedUnit === \"number\") {\n unitID = selectedUnit;\n } else if (\"unit_id\" in selectedUnit) {\n unitID = selectedUnit.unit_id;\n }\n\n if (unitID != null) {\n const unitsMap = get(columnUnitsMapAtom);\n // Verify that the unit exists in the current colum, else throw\n if (!unitsMap?.has(unitID)) {\n throw new Error(\n `Unit with ID ${unitID} not found in current column units.`,\n );\n }\n unit = unitsMap.get(unitID) ?? null;\n }\n\n let overlayPosition: RectBounds | null = null;\n\n const columnEl = get(columnRefAtom)?.current;\n\n if (unit != null && columnEl != null && target != null) {\n const rect = columnEl.getBoundingClientRect();\n const targetRect = target.getBoundingClientRect();\n overlayPosition = {\n x: targetRect.left - rect.left,\n y: targetRect.top - rect.top,\n width: targetRect.width,\n height: targetRect.height,\n };\n }\n\n set(selectedUnitElementAtom, target);\n set(selectedUnitIDAtom, unitID);\n set(overlayPositionAtom, overlayPosition);\n\n return unit;\n },\n);\n\nexport function useAtomOverlayPosition() {\n return scope.useAtomValue(overlayPositionAtom);\n}\n\nexport function UnitSelectionCallbackManager({\n onUnitSelected,\n}: UnitSelectionCallbacks) {\n const selectedUnit = scope.useAtomValue(selectedUnitAtom);\n useEffect(() => {\n onUnitSelected?.(selectedUnit?.unit_id ?? null, selectedUnit ?? null);\n }, [selectedUnit?.unit_id]);\n return null;\n}\n\n/** TODO: figure out if onClickedColumn is still needed\n /** This is not the natural place to get positions within the column,\n * but it will work for now.\nif (props.onClickedColumn) {\n // Infer height from top and bottom height of unit (because that's passed back with the call)\n //const py = event.y;\n //const [bottom, top] = getUnitHeightRange(unit, axisType);\n //const height = Math.abs(bottom - top);\n\n const columnClickData: ColumnClickData = {\n unitID: unit?.unit_id,\n unit,\n target,\n height: el?.getBoundingClientRect().height || 0,\n };\n props.onClickedColumn(columnClickData, event);\n}*/\n\nexport function useUnitSelectionTarget(\n unit: IUnit,\n): [React.RefObject<HTMLElement>, boolean, (evt: Event) => void] {\n const ref = useRef<HTMLElement>(null);\n const [selectedUnit, selectUnit] = useUnitSelection();\n const selected = selectedUnit?.unit_id == unit.unit_id;\n const selectedUnitElement = scope.useAtomValue(selectedUnitElementAtom);\n\n const onClick = useCallback(\n (evt: Event) => {\n selectUnit?.(unit, ref.current);\n evt.stopPropagation();\n },\n [unit, selectUnit],\n );\n\n useEffect(() => {\n // Sync selection with unit element...\n if (!selected) return;\n selectUnit?.(unit, ref.current);\n }, [ref.current, selected, selectUnit]);\n\n useEffect(() => {\n // Scroll the unit into view\n selectedUnitElement?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n inline: \"nearest\",\n });\n }, [selectedUnitElement]);\n\n return [ref, selected, onClick];\n}\n\nexport function UnitKeyboardNavigation({\n columnData,\n units,\n allowHorizontalNavigation,\n}: {\n columnData?: ColumnData[];\n units?: UnitLong[];\n allowHorizontalNavigation?: boolean;\n}) {\n if (units == null && columnData == null) {\n throw new Error(\"Either units or columnData must be provided.\");\n }\n\n let _columnData: ColumnData[] = useMemo(() => {\n if (columnData != null) return columnData;\n // Build column data from units\n const colMap = new Map<number, UnitLong[]>();\n for (const unit of units!) {\n if (!colMap.has(unit.col_id)) {\n colMap.set(unit.col_id, []);\n }\n colMap.get(unit.col_id)!.push(unit);\n }\n const cols: ColumnData[] = [];\n for (const [colID, units] of colMap) {\n cols.push({ columnID: colID, units });\n }\n // Sort columns by columnID\n cols.sort((a, b) => a.columnID - b.columnID);\n return cols;\n }, [columnData, units]);\n\n // Default to allowing horizontal navigation only if columnData is provided\n const _allowHorizontalNavigation =\n allowHorizontalNavigation ?? columnData != null;\n\n const selectedUnit = useSelectedUnit() as UnitLong | null;\n const selectUnit = useUnitSelectionDispatch();\n\n const keyMap: Record<number, Direction> = {\n 38: \"up\",\n 40: \"down\",\n };\n if (_allowHorizontalNavigation) {\n keyMap[37] = \"left\";\n keyMap[39] = \"right\";\n }\n\n useKeyHandler(\n (event) => {\n const direction = keyMap[event.keyCode];\n if (direction == null) return;\n const nextUnit = getBestUnit(_columnData, selectedUnit, direction);\n if (nextUnit == null) return;\n selectUnit(nextUnit, null);\n event.stopPropagation();\n },\n [_columnData, selectedUnit],\n );\n return null;\n}\n\ntype Direction = \"up\" | \"down\" | \"left\" | \"right\";\n\nfunction getBestUnit(\n columnData: ColumnData[],\n targetUnit: UnitLong,\n direction: Direction,\n): UnitLong | null {\n const thisColIndex = columnData.findIndex(\n (col) => col.columnID === targetUnit.col_id,\n );\n if (thisColIndex === -1) return null;\n\n // If up or down, stay in the same column\n if (direction === \"up\" || direction === \"down\") {\n const units = columnData[thisColIndex].units;\n const ix = units.findIndex((unit) => unit.unit_id === targetUnit.unit_id);\n if (ix === -1) return null;\n if (direction === \"up\") {\n return units[ix - 1] || null;\n } else {\n return units[ix + 1] || null;\n }\n }\n\n // If left or right, move to adjacent column\n let adjacentColIndex: number;\n if (direction === \"left\") {\n adjacentColIndex =\n (thisColIndex - 1 + columnData.length) % columnData.length;\n } else {\n adjacentColIndex = (thisColIndex + 1) % columnData.length;\n }\n\n const adjacentColUnits = columnData[adjacentColIndex].units;\n return getMostOverlappingUnit(targetUnit, adjacentColUnits);\n}\n\ntype UnitAgeRangeRelationship = AgeRangeQuantifiedDifference & {\n unit: UnitLong;\n};\n\nfunction getMostOverlappingUnit(\n targetUnit: UnitLong,\n candidateUnits: UnitLong[],\n): UnitLong | null {\n const targetAgeRange = [targetUnit.b_age, targetUnit.t_age];\n\n const overlaps: UnitAgeRangeRelationship[] = [];\n for (const candidate of candidateUnits) {\n const candidateAgeRange = [candidate.b_age, candidate.t_age];\n\n const rel = ageRangeQuantifiedDifference(targetAgeRange, candidateAgeRange);\n if (rel.type === AgeRangeRelationship.Identical) {\n return candidate;\n }\n overlaps.push({ ...rel, unit: candidate });\n }\n\n let bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Containing,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Contained,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.PartialOverlap,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Disjoint,\n );\n bestOverlaps.sort((a, b) => a.distance - b.distance);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n return null;\n}\n"],"names":["scope","atom","columnUnitsMapAtom","useEffect","useRef","useCallback","useMemo","units","useKeyHandler","ageRangeQuantifiedDifference","AgeRangeRelationship"],"mappings":"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"}
@@ -21,6 +21,9 @@ export declare const allowUnitSelectionAtom: import('jotai').PrimitiveAtom<boole
21
21
  export declare const selectedUnitIDAtom: import('jotai').PrimitiveAtom<number | null | undefined> & {
22
22
  init: number | null | undefined;
23
23
  };
24
+ export declare const selectedUnitElementAtom: import('jotai').PrimitiveAtom<HTMLElement | null | undefined> & {
25
+ init: HTMLElement | null | undefined;
26
+ };
24
27
  export declare function useColumnRef(): any;
25
28
  export declare function useAtomOverlayPosition(): any;
26
29
  export declare function UnitSelectionCallbackManager({ onUnitSelected, }: UnitSelectionCallbacks): null;
@@ -14,6 +14,7 @@ function useSelectedUnit() {
14
14
  }
15
15
  const allowUnitSelectionAtom = atom(true);
16
16
  const selectedUnitIDAtom = atom();
17
+ const selectedUnitElementAtom = atom();
17
18
  const overlayPositionAtom = atom();
18
19
  const columnRefAtom = atom({ current: null });
19
20
  function useColumnRef() {
@@ -62,6 +63,7 @@ const selectedUnitAtom = atom(
62
63
  height: targetRect.height
63
64
  };
64
65
  }
66
+ set(selectedUnitElementAtom, target);
65
67
  set(selectedUnitIDAtom, unitID);
66
68
  set(overlayPositionAtom, overlayPosition);
67
69
  return unit;
@@ -83,6 +85,7 @@ function useUnitSelectionTarget(unit) {
83
85
  const ref = useRef(null);
84
86
  const [selectedUnit, selectUnit] = useUnitSelection();
85
87
  const selected = selectedUnit?.unit_id == unit.unit_id;
88
+ const selectedUnitElement = scope.useAtomValue(selectedUnitElementAtom);
86
89
  const onClick = useCallback(
87
90
  (evt) => {
88
91
  selectUnit?.(unit, ref.current);
@@ -91,14 +94,16 @@ function useUnitSelectionTarget(unit) {
91
94
  [unit, selectUnit]
92
95
  );
93
96
  useEffect(() => {
94
- if (!selected || selectUnit == null) return;
95
- selectUnit(unit, ref.current);
96
- ref.current?.scrollIntoView({
97
+ if (!selected) return;
98
+ selectUnit?.(unit, ref.current);
99
+ }, [ref.current, selected, selectUnit]);
100
+ useEffect(() => {
101
+ selectedUnitElement?.scrollIntoView({
97
102
  behavior: "smooth",
98
103
  block: "center",
99
104
  inline: "nearest"
100
105
  });
101
- }, [selected, selectUnit]);
106
+ }, [selectedUnitElement]);
102
107
  return [ref, selected, onClick];
103
108
  }
104
109
  function UnitKeyboardNavigation({
@@ -218,6 +223,7 @@ export {
218
223
  UnitKeyboardNavigation,
219
224
  UnitSelectionCallbackManager,
220
225
  allowUnitSelectionAtom,
226
+ selectedUnitElementAtom,
221
227
  selectedUnitIDAtom,
222
228
  useAtomOverlayPosition,
223
229
  useColumnRef,
@@ -1 +1 @@
1
- {"version":3,"file":"unit-selection.js","sources":["../../src/data-provider/unit-selection.ts"],"sourcesContent":["import type { BaseUnit, UnitLong } from \"@macrostrat/api-types\";\nimport { useKeyHandler } from \"@macrostrat/ui-components\";\nimport { useEffect, useRef, useCallback, useMemo } from \"react\";\nimport type { RectBounds, IUnit } from \"../units/types\";\nimport { atom } from \"jotai\";\nimport { scope, columnUnitsMapAtom } from \"./store\";\nimport {\n AgeRangeQuantifiedDifference,\n ageRangeQuantifiedDifference,\n AgeRangeRelationship,\n} from \"@macrostrat/stratigraphy-utils\";\nimport type { ColumnData } from \"@macrostrat/data-provider\";\n\ntype UnitSelectDispatch = (\n unit: number | BaseUnit | null,\n target: HTMLElement | null,\n) => void;\n\nexport function useUnitSelection() {\n return scope.use(selectedUnitAtom);\n}\n\nexport function useUnitSelectionDispatch(): UnitSelectDispatch {\n return scope.useSetAtom(selectedUnitAtom);\n}\n\nexport function useSelectedUnit() {\n return scope.useAtomValue(selectedUnitAtom);\n}\n\nexport interface ColumnClickData {\n unitID: number | null;\n unit: BaseUnit | null;\n target: HTMLElement | null;\n height: number;\n // Room for boundary IDs eventually\n}\n\nexport interface UnitSelectionCallbacks {\n // It's sort of unfortunate that we need to pass in the column ref here\n onClickedColumn?: (columnClickData: ColumnClickData, event: Event) => void;\n onUnitSelected?: <T extends BaseUnit>(\n unitID: number | null,\n unit: T | null,\n ) => void;\n}\n\nexport const allowUnitSelectionAtom = atom<boolean>(true);\n\nexport const selectedUnitIDAtom = atom<number | null>();\n\nconst overlayPositionAtom = atom<RectBounds | null>();\n\nconst columnRefAtom = atom<{ current: HTMLElement | null }>({ current: null });\n\nexport function useColumnRef() {\n return scope.useAtomValue(columnRefAtom);\n}\n\nconst selectedUnitAtom = atom(\n (get) => {\n if (!get(allowUnitSelectionAtom)) return null;\n\n const unitID = get(selectedUnitIDAtom);\n if (unitID == null) return null;\n const unitsMap = get(columnUnitsMapAtom);\n return unitsMap?.get(unitID) || null;\n },\n (\n get,\n set,\n selectedUnit: number | BaseUnit | null,\n target: HTMLElement | null = null,\n ): BaseUnit | null => {\n if (!get(allowUnitSelectionAtom)) {\n console.error(\"Unit selection is disabled.\");\n return null;\n }\n\n let unitID: number | null = null;\n let unit: BaseUnit | null = null;\n if (selectedUnit == null) {\n unitID = null;\n } else if (typeof selectedUnit === \"number\") {\n unitID = selectedUnit;\n } else if (\"unit_id\" in selectedUnit) {\n unitID = selectedUnit.unit_id;\n }\n\n if (unitID != null) {\n const unitsMap = get(columnUnitsMapAtom);\n // Verify that the unit exists in the current colum, else throw\n if (!unitsMap?.has(unitID)) {\n throw new Error(\n `Unit with ID ${unitID} not found in current column units.`,\n );\n }\n unit = unitsMap.get(unitID) ?? null;\n }\n\n let overlayPosition: RectBounds | null = null;\n\n const columnEl = get(columnRefAtom)?.current;\n\n if (unit != null && columnEl != null && target != null) {\n const rect = columnEl.getBoundingClientRect();\n const targetRect = target.getBoundingClientRect();\n overlayPosition = {\n x: targetRect.left - rect.left,\n y: targetRect.top - rect.top,\n width: targetRect.width,\n height: targetRect.height,\n };\n }\n\n set(selectedUnitIDAtom, unitID);\n set(overlayPositionAtom, overlayPosition);\n\n return unit;\n },\n);\n\nexport function useAtomOverlayPosition() {\n return scope.useAtomValue(overlayPositionAtom);\n}\n\nexport function UnitSelectionCallbackManager({\n onUnitSelected,\n}: UnitSelectionCallbacks) {\n const selectedUnit = scope.useAtomValue(selectedUnitAtom);\n useEffect(() => {\n onUnitSelected?.(selectedUnit?.unit_id ?? null, selectedUnit ?? null);\n }, [selectedUnit?.unit_id]);\n return null;\n}\n\n/** TODO: figure out if onClickedColumn is still needed\n /** This is not the natural place to get positions within the column,\n * but it will work for now.\nif (props.onClickedColumn) {\n // Infer height from top and bottom height of unit (because that's passed back with the call)\n //const py = event.y;\n //const [bottom, top] = getUnitHeightRange(unit, axisType);\n //const height = Math.abs(bottom - top);\n\n const columnClickData: ColumnClickData = {\n unitID: unit?.unit_id,\n unit,\n target,\n height: el?.getBoundingClientRect().height || 0,\n };\n props.onClickedColumn(columnClickData, event);\n}*/\n\nexport function useUnitSelectionTarget(\n unit: IUnit,\n): [React.RefObject<HTMLElement>, boolean, (evt: Event) => void] {\n const ref = useRef<HTMLElement>(null);\n const [selectedUnit, selectUnit] = useUnitSelection();\n const selected = selectedUnit?.unit_id == unit.unit_id;\n\n const onClick = useCallback(\n (evt: Event) => {\n selectUnit?.(unit, ref.current);\n evt.stopPropagation();\n },\n [unit, selectUnit],\n );\n\n useEffect(() => {\n if (!selected || selectUnit == null) return;\n // In case we haven't set the position of the unit (if we don't have a target), set the selected unit\n selectUnit(unit, ref.current);\n\n // Scroll the unit into view\n ref.current?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n inline: \"nearest\",\n });\n }, [selected, selectUnit]);\n\n return [ref, selected, onClick];\n}\n\nexport function UnitKeyboardNavigation({\n columnData,\n units,\n allowHorizontalNavigation,\n}: {\n columnData?: ColumnData[];\n units?: UnitLong[];\n allowHorizontalNavigation?: boolean;\n}) {\n if (units == null && columnData == null) {\n throw new Error(\"Either units or columnData must be provided.\");\n }\n\n let _columnData: ColumnData[] = useMemo(() => {\n if (columnData != null) return columnData;\n // Build column data from units\n const colMap = new Map<number, UnitLong[]>();\n for (const unit of units!) {\n if (!colMap.has(unit.col_id)) {\n colMap.set(unit.col_id, []);\n }\n colMap.get(unit.col_id)!.push(unit);\n }\n const cols: ColumnData[] = [];\n for (const [colID, units] of colMap) {\n cols.push({ columnID: colID, units });\n }\n // Sort columns by columnID\n cols.sort((a, b) => a.columnID - b.columnID);\n return cols;\n }, [columnData, units]);\n\n // Default to allowing horizontal navigation only if columnData is provided\n const _allowHorizontalNavigation =\n allowHorizontalNavigation ?? columnData != null;\n\n const selectedUnit = useSelectedUnit() as UnitLong | null;\n const selectUnit = useUnitSelectionDispatch();\n\n const keyMap: Record<number, Direction> = {\n 38: \"up\",\n 40: \"down\",\n };\n if (_allowHorizontalNavigation) {\n keyMap[37] = \"left\";\n keyMap[39] = \"right\";\n }\n\n useKeyHandler(\n (event) => {\n const direction = keyMap[event.keyCode];\n if (direction == null) return;\n const nextUnit = getBestUnit(_columnData, selectedUnit, direction);\n if (nextUnit == null) return;\n selectUnit(nextUnit, null);\n event.stopPropagation();\n },\n [_columnData, selectedUnit],\n );\n return null;\n}\n\ntype Direction = \"up\" | \"down\" | \"left\" | \"right\";\n\nfunction getBestUnit(\n columnData: ColumnData[],\n targetUnit: UnitLong,\n direction: Direction,\n): UnitLong | null {\n const thisColIndex = columnData.findIndex(\n (col) => col.columnID === targetUnit.col_id,\n );\n if (thisColIndex === -1) return null;\n\n // If up or down, stay in the same column\n if (direction === \"up\" || direction === \"down\") {\n const units = columnData[thisColIndex].units;\n const ix = units.findIndex((unit) => unit.unit_id === targetUnit.unit_id);\n if (ix === -1) return null;\n if (direction === \"up\") {\n return units[ix - 1] || null;\n } else {\n return units[ix + 1] || null;\n }\n }\n\n // If left or right, move to adjacent column\n let adjacentColIndex: number;\n if (direction === \"left\") {\n adjacentColIndex =\n (thisColIndex - 1 + columnData.length) % columnData.length;\n } else {\n adjacentColIndex = (thisColIndex + 1) % columnData.length;\n }\n\n const adjacentColUnits = columnData[adjacentColIndex].units;\n return getMostOverlappingUnit(targetUnit, adjacentColUnits);\n}\n\ntype UnitAgeRangeRelationship = AgeRangeQuantifiedDifference & {\n unit: UnitLong;\n};\n\nfunction getMostOverlappingUnit(\n targetUnit: UnitLong,\n candidateUnits: UnitLong[],\n): UnitLong | null {\n const targetAgeRange = [targetUnit.b_age, targetUnit.t_age];\n\n const overlaps: UnitAgeRangeRelationship[] = [];\n for (const candidate of candidateUnits) {\n const candidateAgeRange = [candidate.b_age, candidate.t_age];\n\n const rel = ageRangeQuantifiedDifference(targetAgeRange, candidateAgeRange);\n if (rel.type === AgeRangeRelationship.Identical) {\n return candidate;\n }\n overlaps.push({ ...rel, unit: candidate });\n }\n\n let bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Containing,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Contained,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.PartialOverlap,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Disjoint,\n );\n bestOverlaps.sort((a, b) => a.distance - b.distance);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n return null;\n}\n"],"names":["units"],"mappings":"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"}
1
+ {"version":3,"file":"unit-selection.js","sources":["../../src/data-provider/unit-selection.ts"],"sourcesContent":["import type { BaseUnit, UnitLong } from \"@macrostrat/api-types\";\nimport { useKeyHandler } from \"@macrostrat/ui-components\";\nimport { useEffect, useRef, useCallback, useMemo } from \"react\";\nimport type { RectBounds, IUnit } from \"../units/types\";\nimport { atom } from \"jotai\";\nimport { scope, columnUnitsMapAtom } from \"./store\";\nimport {\n AgeRangeQuantifiedDifference,\n ageRangeQuantifiedDifference,\n AgeRangeRelationship,\n} from \"@macrostrat/stratigraphy-utils\";\nimport type { ColumnData } from \"@macrostrat/data-provider\";\n\ntype UnitSelectDispatch = (\n unit: number | BaseUnit | null,\n target: HTMLElement | null,\n) => void;\n\nexport function useUnitSelection() {\n return scope.use(selectedUnitAtom);\n}\n\nexport function useUnitSelectionDispatch(): UnitSelectDispatch {\n return scope.useSetAtom(selectedUnitAtom);\n}\n\nexport function useSelectedUnit() {\n return scope.useAtomValue(selectedUnitAtom);\n}\n\nexport interface ColumnClickData {\n unitID: number | null;\n unit: BaseUnit | null;\n target: HTMLElement | null;\n height: number;\n // Room for boundary IDs eventually\n}\n\nexport interface UnitSelectionCallbacks {\n // It's sort of unfortunate that we need to pass in the column ref here\n onClickedColumn?: (columnClickData: ColumnClickData, event: Event) => void;\n onUnitSelected?: <T extends BaseUnit>(\n unitID: number | null,\n unit: T | null,\n ) => void;\n}\n\nexport const allowUnitSelectionAtom = atom<boolean>(true);\n\nexport const selectedUnitIDAtom = atom<number | null>();\nexport const selectedUnitElementAtom = atom<HTMLElement | null>();\n\nconst overlayPositionAtom = atom<RectBounds | null>();\n\nconst columnRefAtom = atom<{ current: HTMLElement | null }>({ current: null });\n\nexport function useColumnRef() {\n return scope.useAtomValue(columnRefAtom);\n}\n\nconst selectedUnitAtom = atom(\n (get) => {\n if (!get(allowUnitSelectionAtom)) return null;\n\n const unitID = get(selectedUnitIDAtom);\n if (unitID == null) return null;\n const unitsMap = get(columnUnitsMapAtom);\n return unitsMap?.get(unitID) || null;\n },\n (\n get,\n set,\n selectedUnit: number | BaseUnit | null,\n target: HTMLElement | null = null,\n ): BaseUnit | null => {\n if (!get(allowUnitSelectionAtom)) {\n console.error(\"Unit selection is disabled.\");\n return null;\n }\n\n let unitID: number | null = null;\n let unit: BaseUnit | null = null;\n if (selectedUnit == null) {\n unitID = null;\n } else if (typeof selectedUnit === \"number\") {\n unitID = selectedUnit;\n } else if (\"unit_id\" in selectedUnit) {\n unitID = selectedUnit.unit_id;\n }\n\n if (unitID != null) {\n const unitsMap = get(columnUnitsMapAtom);\n // Verify that the unit exists in the current colum, else throw\n if (!unitsMap?.has(unitID)) {\n throw new Error(\n `Unit with ID ${unitID} not found in current column units.`,\n );\n }\n unit = unitsMap.get(unitID) ?? null;\n }\n\n let overlayPosition: RectBounds | null = null;\n\n const columnEl = get(columnRefAtom)?.current;\n\n if (unit != null && columnEl != null && target != null) {\n const rect = columnEl.getBoundingClientRect();\n const targetRect = target.getBoundingClientRect();\n overlayPosition = {\n x: targetRect.left - rect.left,\n y: targetRect.top - rect.top,\n width: targetRect.width,\n height: targetRect.height,\n };\n }\n\n set(selectedUnitElementAtom, target);\n set(selectedUnitIDAtom, unitID);\n set(overlayPositionAtom, overlayPosition);\n\n return unit;\n },\n);\n\nexport function useAtomOverlayPosition() {\n return scope.useAtomValue(overlayPositionAtom);\n}\n\nexport function UnitSelectionCallbackManager({\n onUnitSelected,\n}: UnitSelectionCallbacks) {\n const selectedUnit = scope.useAtomValue(selectedUnitAtom);\n useEffect(() => {\n onUnitSelected?.(selectedUnit?.unit_id ?? null, selectedUnit ?? null);\n }, [selectedUnit?.unit_id]);\n return null;\n}\n\n/** TODO: figure out if onClickedColumn is still needed\n /** This is not the natural place to get positions within the column,\n * but it will work for now.\nif (props.onClickedColumn) {\n // Infer height from top and bottom height of unit (because that's passed back with the call)\n //const py = event.y;\n //const [bottom, top] = getUnitHeightRange(unit, axisType);\n //const height = Math.abs(bottom - top);\n\n const columnClickData: ColumnClickData = {\n unitID: unit?.unit_id,\n unit,\n target,\n height: el?.getBoundingClientRect().height || 0,\n };\n props.onClickedColumn(columnClickData, event);\n}*/\n\nexport function useUnitSelectionTarget(\n unit: IUnit,\n): [React.RefObject<HTMLElement>, boolean, (evt: Event) => void] {\n const ref = useRef<HTMLElement>(null);\n const [selectedUnit, selectUnit] = useUnitSelection();\n const selected = selectedUnit?.unit_id == unit.unit_id;\n const selectedUnitElement = scope.useAtomValue(selectedUnitElementAtom);\n\n const onClick = useCallback(\n (evt: Event) => {\n selectUnit?.(unit, ref.current);\n evt.stopPropagation();\n },\n [unit, selectUnit],\n );\n\n useEffect(() => {\n // Sync selection with unit element...\n if (!selected) return;\n selectUnit?.(unit, ref.current);\n }, [ref.current, selected, selectUnit]);\n\n useEffect(() => {\n // Scroll the unit into view\n selectedUnitElement?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n inline: \"nearest\",\n });\n }, [selectedUnitElement]);\n\n return [ref, selected, onClick];\n}\n\nexport function UnitKeyboardNavigation({\n columnData,\n units,\n allowHorizontalNavigation,\n}: {\n columnData?: ColumnData[];\n units?: UnitLong[];\n allowHorizontalNavigation?: boolean;\n}) {\n if (units == null && columnData == null) {\n throw new Error(\"Either units or columnData must be provided.\");\n }\n\n let _columnData: ColumnData[] = useMemo(() => {\n if (columnData != null) return columnData;\n // Build column data from units\n const colMap = new Map<number, UnitLong[]>();\n for (const unit of units!) {\n if (!colMap.has(unit.col_id)) {\n colMap.set(unit.col_id, []);\n }\n colMap.get(unit.col_id)!.push(unit);\n }\n const cols: ColumnData[] = [];\n for (const [colID, units] of colMap) {\n cols.push({ columnID: colID, units });\n }\n // Sort columns by columnID\n cols.sort((a, b) => a.columnID - b.columnID);\n return cols;\n }, [columnData, units]);\n\n // Default to allowing horizontal navigation only if columnData is provided\n const _allowHorizontalNavigation =\n allowHorizontalNavigation ?? columnData != null;\n\n const selectedUnit = useSelectedUnit() as UnitLong | null;\n const selectUnit = useUnitSelectionDispatch();\n\n const keyMap: Record<number, Direction> = {\n 38: \"up\",\n 40: \"down\",\n };\n if (_allowHorizontalNavigation) {\n keyMap[37] = \"left\";\n keyMap[39] = \"right\";\n }\n\n useKeyHandler(\n (event) => {\n const direction = keyMap[event.keyCode];\n if (direction == null) return;\n const nextUnit = getBestUnit(_columnData, selectedUnit, direction);\n if (nextUnit == null) return;\n selectUnit(nextUnit, null);\n event.stopPropagation();\n },\n [_columnData, selectedUnit],\n );\n return null;\n}\n\ntype Direction = \"up\" | \"down\" | \"left\" | \"right\";\n\nfunction getBestUnit(\n columnData: ColumnData[],\n targetUnit: UnitLong,\n direction: Direction,\n): UnitLong | null {\n const thisColIndex = columnData.findIndex(\n (col) => col.columnID === targetUnit.col_id,\n );\n if (thisColIndex === -1) return null;\n\n // If up or down, stay in the same column\n if (direction === \"up\" || direction === \"down\") {\n const units = columnData[thisColIndex].units;\n const ix = units.findIndex((unit) => unit.unit_id === targetUnit.unit_id);\n if (ix === -1) return null;\n if (direction === \"up\") {\n return units[ix - 1] || null;\n } else {\n return units[ix + 1] || null;\n }\n }\n\n // If left or right, move to adjacent column\n let adjacentColIndex: number;\n if (direction === \"left\") {\n adjacentColIndex =\n (thisColIndex - 1 + columnData.length) % columnData.length;\n } else {\n adjacentColIndex = (thisColIndex + 1) % columnData.length;\n }\n\n const adjacentColUnits = columnData[adjacentColIndex].units;\n return getMostOverlappingUnit(targetUnit, adjacentColUnits);\n}\n\ntype UnitAgeRangeRelationship = AgeRangeQuantifiedDifference & {\n unit: UnitLong;\n};\n\nfunction getMostOverlappingUnit(\n targetUnit: UnitLong,\n candidateUnits: UnitLong[],\n): UnitLong | null {\n const targetAgeRange = [targetUnit.b_age, targetUnit.t_age];\n\n const overlaps: UnitAgeRangeRelationship[] = [];\n for (const candidate of candidateUnits) {\n const candidateAgeRange = [candidate.b_age, candidate.t_age];\n\n const rel = ageRangeQuantifiedDifference(targetAgeRange, candidateAgeRange);\n if (rel.type === AgeRangeRelationship.Identical) {\n return candidate;\n }\n overlaps.push({ ...rel, unit: candidate });\n }\n\n let bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Containing,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Contained,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.PartialOverlap,\n );\n bestOverlaps.sort((a, b) => b.overlap - a.overlap);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n\n bestOverlaps = overlaps.filter(\n (d) => d.type === AgeRangeRelationship.Disjoint,\n );\n bestOverlaps.sort((a, b) => a.distance - b.distance);\n if (bestOverlaps.length > 0) {\n return bestOverlaps[0].unit;\n }\n return null;\n}\n"],"names":["units"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -151,6 +151,7 @@ exports.UnitSelectionPopover = popover.UnitSelectionPopover;
151
151
  exports.UnitKeyboardNavigation = unitSelection.UnitKeyboardNavigation;
152
152
  exports.UnitSelectionCallbackManager = unitSelection.UnitSelectionCallbackManager;
153
153
  exports.allowUnitSelectionAtom = unitSelection.allowUnitSelectionAtom;
154
+ exports.selectedUnitElementAtom = unitSelection.selectedUnitElementAtom;
154
155
  exports.selectedUnitIDAtom = unitSelection.selectedUnitIDAtom;
155
156
  exports.useAtomOverlayPosition = unitSelection.useAtomOverlayPosition;
156
157
  exports.useColumnRef = unitSelection.useColumnRef;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import { SGPMeasurementsColumn } from "./facets/measurements/sgp.js";
32
32
  import { StratNameField } from "./unit-details/strat-names.js";
33
33
  import { UnitDetailsPanelWithNavigation } from "./unit-details/modal-panel.js";
34
34
  import { UnitDetailsPopover, UnitSelectionPopover } from "./unit-details/popover.js";
35
- import { UnitKeyboardNavigation, UnitSelectionCallbackManager, allowUnitSelectionAtom, selectedUnitIDAtom, useAtomOverlayPosition, useColumnRef, useSelectedUnit, useUnitSelection, useUnitSelectionDispatch, useUnitSelectionTarget } from "./data-provider/unit-selection.js";
35
+ import { UnitKeyboardNavigation, UnitSelectionCallbackManager, allowUnitSelectionAtom, selectedUnitElementAtom, selectedUnitIDAtom, useAtomOverlayPosition, useColumnRef, useSelectedUnit, useUnitSelection, useUnitSelectionDispatch, useUnitSelectionTarget } from "./data-provider/unit-selection.js";
36
36
  import { flattenLithologies, getMixedColorForData, getMixedUnitColor } from "./units/colors.js";
37
37
  import { parseLineFromString, stringifyLine } from "./correlation-chart/hash-string.js";
38
38
  import { preprocessUnits } from "./prepare-units/helpers.js";
@@ -142,6 +142,7 @@ export {
142
142
  prepareColumnUnits,
143
143
  preprocessUnits,
144
144
  scope,
145
+ selectedUnitElementAtom,
145
146
  selectedUnitIDAtom,
146
147
  standardizeMeasurementHeight,
147
148
  stringifyLine,
package/dist/section.cjs CHANGED
@@ -156,7 +156,7 @@ function CompositeTimescale(props) {
156
156
  function CompositeTimescaleCore(props) {
157
157
  const { levels = 3, packages, unconformityLabels = false } = props;
158
158
  const baseURL = dataProvider.useMacrostratBaseURL();
159
- const intervals = timescale.useMacrostratIntervals(baseURL);
159
+ const intervals = timescale.useMacrostratIntervals({ baseURL });
160
160
  let _levels;
161
161
  if (typeof levels === "number") {
162
162
  _levels = [2, Math.max(2 + Math.min(levels, 5) - 1, 1)];
@@ -1 +1 @@
1
- {"version":3,"file":"section.cjs","sources":["../src/section.ts"],"sourcesContent":["import {\n CompositeUnitsColumn,\n LabelTrackerProvider,\n SectionLabelsColumn,\n} from \"./units\";\nimport { ReactNode, FunctionComponent, useMemo } from \"react\";\nimport {\n Timescale,\n TimescaleOrientation,\n useMacrostratIntervals,\n} from \"@macrostrat/timescale\";\nimport { ColumnAxisType, SVG } from \"@macrostrat/column-components\";\nimport hyper from \"@macrostrat/hyper\";\nimport styles from \"./column.module.sass\";\nimport {\n useMacrostratColumnData,\n useMacrostratUnits,\n MacrostratColumnProvider,\n} from \"./data-provider\";\nimport { useMacrostratBaseURL } from \"@macrostrat/data-provider\";\nimport { Duration } from \"./unit-details\";\nimport { Value } from \"@macrostrat/data-components\";\nimport type { ExtUnit, PackageScaleLayoutData } from \"./prepare-units/types\";\n\nconst h = hyper.styled(styles);\n\nexport interface SectionSharedProps {\n unitComponent?: FunctionComponent<any>;\n unitComponentProps?: any;\n showLabels?: boolean;\n width?: number;\n columnWidth?: number;\n children?: ReactNode;\n showLabelColumn?: boolean;\n axisType?: ColumnAxisType;\n className?: string;\n clipUnits?: boolean;\n maxInternalColumns?: number;\n // Space between sections\n verticalSpacing?: number;\n // Whether to render unconformity labels\n unconformityLabels?: boolean;\n}\n\nexport interface SectionProps extends SectionSharedProps {\n units: ExtUnit[];\n scaleInfo: PackageScaleLayoutData;\n}\n\nexport function SectionsColumn(props: SectionSharedProps) {\n const {\n unconformityLabels = true,\n unitComponent,\n unitComponentProps,\n showLabels = true,\n width = 300,\n columnWidth = 150,\n showLabelColumn = true,\n clipUnits = true,\n maxInternalColumns,\n } = props;\n\n const units = useMacrostratUnits();\n\n // Get a unique key for the column\n const key = units[0]?.col_id;\n\n return h(LabelTrackerProvider, { units, key }, [\n h(SectionUnitsColumn, {\n width: columnWidth,\n unitComponent,\n unitComponentProps,\n showLabels,\n clipUnits,\n maxInternalColumns,\n unconformityLabels,\n }),\n h.if(showLabelColumn)(SectionLabelsColumn, {\n width: width - columnWidth,\n }),\n ]);\n}\n\nfunction SectionUnitsColumn(props: SectionSharedProps) {\n const {\n width,\n showLabels,\n unitComponent,\n unitComponentProps,\n clipUnits,\n maxInternalColumns,\n axisType,\n unconformityLabels = true,\n } = props;\n\n const { sections, totalHeight } = useMacrostratColumnData();\n\n const scaleData: PackageScaleLayoutData[] = sections.map((section) => {\n return section.scaleInfo;\n });\n\n const innerWidth = width - 2;\n\n return h(\"div.section-units-container\", { style: { width } }, [\n h(\n SVG,\n {\n className: \"sections\",\n height: totalHeight,\n innerWidth,\n paddingH: 1,\n },\n sections.map((group) => {\n const { units, scaleInfo, section_id } = group;\n\n const key = `section-${section_id}`;\n return h(\n SectionUnits,\n {\n units,\n scaleInfo,\n key,\n unitComponent,\n unitComponentProps,\n showLabels,\n width: innerWidth,\n clipUnits,\n maxInternalColumns,\n }, // This unconformity is with the section _above_\n );\n }),\n ),\n h.if(unconformityLabels)(UnconformityLabels, {\n width,\n sections: scaleData,\n verbose: false,\n }),\n ]);\n}\n\nfunction SectionUnits(props: SectionProps) {\n // Section with \"squishy\" timescale\n const {\n units,\n scaleInfo,\n unitComponent,\n showLabels = true,\n width = 300,\n unitComponentProps,\n className,\n clipUnits = true,\n maxInternalColumns,\n } = props;\n\n const { domain, pixelScale, pixelHeight, scale } = scaleInfo;\n\n /** Ensure that we can arrange units into the maximum number\n * of columns defined by unitComponentProps, but that we don't\n * use more than necessary.\n */\n const _unitComponentProps = useMemo(() => {\n return {\n ...unitComponentProps,\n nColumns: Math.min(\n maxInternalColumns ?? Math.floor(width / 10),\n unitComponentProps?.nColumns ?? Infinity,\n ),\n //axisType,\n };\n }, [units, unitComponentProps, maxInternalColumns, width]);\n\n const style = {\n \"--section-height\": `${pixelHeight}px`,\n \"--section-width\": `${width}px`,\n };\n\n return h(\n \"g.section\",\n { className, style, transform: `translate(0 ${scaleInfo.offset})` },\n h(\n MacrostratColumnProvider,\n {\n units,\n domain,\n pixelScale, // Actually pixels per myr,\n scale,\n },\n h(CompositeUnitsColumn, {\n width,\n showLabels,\n unitComponent,\n unitComponentProps: _unitComponentProps,\n clipToFrame: clipUnits,\n }),\n ),\n );\n}\n\ninterface CompositeTimescaleProps {\n levels?: [number, number] | number;\n unconformityLabels?: boolean;\n}\n\nexport function CompositeTimescale(props: CompositeTimescaleProps) {\n const { sections } = useMacrostratColumnData();\n const sectionScales = sections.map((section) => {\n return section.scaleInfo;\n });\n\n return h(CompositeTimescaleCore, {\n packages: sectionScales,\n ...props,\n });\n}\n\ntype CompositeTimescaleCoreProps = CompositeTimescaleProps & {\n packages: PackageScaleLayoutData[];\n};\n\nexport function CompositeTimescaleCore(props: CompositeTimescaleCoreProps) {\n const { levels = 3, packages, unconformityLabels = false } = props;\n\n // Use intervals from Macrostrat API\n const baseURL = useMacrostratBaseURL();\n const intervals = useMacrostratIntervals(baseURL);\n\n let _levels: [number, number];\n if (typeof levels === \"number\") {\n // If levels is a number, use the most common starting level\n _levels = [2, Math.max(2 + Math.min(levels, 5) - 1, 1)];\n } else {\n _levels = levels;\n }\n\n const nCols = _levels[1] - _levels[0] + 1;\n\n return h(\"div.timescale-column\", [\n h(\n \"div.timescales\",\n packages.map((group) => {\n const { pixelHeight, paddingTop, key, scale } = group;\n return h(\n \"div.timescale-container\",\n { style: { paddingTop, \"--timescale-level-count\": nCols }, key },\n [\n h(Timescale, {\n orientation: TimescaleOrientation.VERTICAL,\n length: pixelHeight,\n levels: _levels,\n absoluteAgeScale: true,\n showAgeAxis: false,\n scale,\n intervals,\n }),\n ],\n );\n }),\n ),\n h.if(unconformityLabels)(UnconformityLabels, {\n width: \"100%\",\n sections: packages,\n className: \"unconformity-labels\",\n axisType: ColumnAxisType.AGE,\n }),\n ]);\n}\n\nexport function UnconformityLabels(props: {\n width: string | number;\n sections: PackageScaleLayoutData[];\n axisType?: ColumnAxisType;\n className?: string;\n verbose?: boolean;\n}) {\n const { width, sections, className, axisType, verbose } = props;\n\n return h(\n \"div.unconformity-labels\",\n {\n style: {\n width,\n },\n className,\n },\n sections.map((scaleInfo, i) => {\n const lastGroup = sections[i - 1];\n const top = scaleInfo.offset - scaleInfo.paddingTop;\n const upperAge = lastGroup?.domain[0];\n const lowerAge = scaleInfo.domain[1];\n return h(Unconformity, {\n axisType,\n upperAge,\n lowerAge,\n style: {\n width,\n height: scaleInfo.paddingTop,\n top,\n },\n verbose,\n });\n }),\n );\n}\n\nfunction Unconformity({\n upperAge,\n lowerAge,\n style,\n axisType,\n verbose = false,\n}) {\n if (upperAge == null || lowerAge == null) {\n return null;\n }\n\n const ageGap = Math.abs(upperAge - lowerAge);\n\n let maximumFractionDigits = 0;\n\n let className: string = null;\n if (ageGap > 1000) {\n className = \"giga\";\n } else if (ageGap > 100) {\n className = \"mega\";\n } else if (ageGap > 10) {\n className = \"large\";\n } else if (ageGap < 1) {\n className = \"small\";\n maximumFractionDigits = 2;\n } else {\n maximumFractionDigits = 1;\n }\n\n let val: ReactNode;\n if (axisType === ColumnAxisType.DEPTH || axisType === ColumnAxisType.HEIGHT) {\n const _txt = ageGap.toLocaleString(\"en-US\", { maximumFractionDigits });\n val = h(Value, { value: _txt, unit: \"m\" });\n } else {\n val = h(Duration, { value: ageGap, maximumFractionDigits });\n }\n\n let prefix: ReactNode = null;\n if (verbose) {\n prefix = h([\" \", h(\"span.prefix\", \" gap\")]);\n }\n\n return h(\"div.unconformity\", { style, className }, [\n h(\"div.unconformity-inner\", h(\"div.unconformity-text\", [val, prefix])),\n ]);\n}\n"],"names":["hyper","styles","useMacrostratUnits","LabelTrackerProvider","SectionLabelsColumn","useMacrostratColumnData","SVG","useMemo","MacrostratColumnProvider","CompositeUnitsColumn","useMacrostratBaseURL","useMacrostratIntervals","Timescale","TimescaleOrientation","ColumnAxisType","Value","Duration"],"mappings":"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"}
1
+ {"version":3,"file":"section.cjs","sources":["../src/section.ts"],"sourcesContent":["import {\n CompositeUnitsColumn,\n LabelTrackerProvider,\n SectionLabelsColumn,\n} from \"./units\";\nimport { ReactNode, FunctionComponent, useMemo } from \"react\";\nimport {\n Timescale,\n TimescaleOrientation,\n useMacrostratIntervals,\n} from \"@macrostrat/timescale\";\nimport { ColumnAxisType, SVG } from \"@macrostrat/column-components\";\nimport hyper from \"@macrostrat/hyper\";\nimport styles from \"./column.module.sass\";\nimport {\n useMacrostratColumnData,\n useMacrostratUnits,\n MacrostratColumnProvider,\n} from \"./data-provider\";\nimport {\n useMacrostratBaseURL,\n useMacrostratData,\n} from \"@macrostrat/data-provider\";\nimport { Duration } from \"./unit-details\";\nimport { Value } from \"@macrostrat/data-components\";\nimport type { ExtUnit, PackageScaleLayoutData } from \"./prepare-units/types\";\n\nconst h = hyper.styled(styles);\n\nexport interface SectionSharedProps {\n unitComponent?: FunctionComponent<any>;\n unitComponentProps?: any;\n showLabels?: boolean;\n width?: number;\n columnWidth?: number;\n children?: ReactNode;\n showLabelColumn?: boolean;\n axisType?: ColumnAxisType;\n className?: string;\n clipUnits?: boolean;\n maxInternalColumns?: number;\n // Space between sections\n verticalSpacing?: number;\n // Whether to render unconformity labels\n unconformityLabels?: boolean;\n}\n\nexport interface SectionProps extends SectionSharedProps {\n units: ExtUnit[];\n scaleInfo: PackageScaleLayoutData;\n}\n\nexport function SectionsColumn(props: SectionSharedProps) {\n const {\n unconformityLabels = true,\n unitComponent,\n unitComponentProps,\n showLabels = true,\n width = 300,\n columnWidth = 150,\n showLabelColumn = true,\n clipUnits = true,\n maxInternalColumns,\n } = props;\n\n const units = useMacrostratUnits();\n\n // Get a unique key for the column\n const key = units[0]?.col_id;\n\n return h(LabelTrackerProvider, { units, key }, [\n h(SectionUnitsColumn, {\n width: columnWidth,\n unitComponent,\n unitComponentProps,\n showLabels,\n clipUnits,\n maxInternalColumns,\n unconformityLabels,\n }),\n h.if(showLabelColumn)(SectionLabelsColumn, {\n width: width - columnWidth,\n }),\n ]);\n}\n\nfunction SectionUnitsColumn(props: SectionSharedProps) {\n const {\n width,\n showLabels,\n unitComponent,\n unitComponentProps,\n clipUnits,\n maxInternalColumns,\n axisType,\n unconformityLabels = true,\n } = props;\n\n const { sections, totalHeight } = useMacrostratColumnData();\n\n const scaleData: PackageScaleLayoutData[] = sections.map((section) => {\n return section.scaleInfo;\n });\n\n const innerWidth = width - 2;\n\n return h(\"div.section-units-container\", { style: { width } }, [\n h(\n SVG,\n {\n className: \"sections\",\n height: totalHeight,\n innerWidth,\n paddingH: 1,\n },\n sections.map((group) => {\n const { units, scaleInfo, section_id } = group;\n\n const key = `section-${section_id}`;\n return h(\n SectionUnits,\n {\n units,\n scaleInfo,\n key,\n unitComponent,\n unitComponentProps,\n showLabels,\n width: innerWidth,\n clipUnits,\n maxInternalColumns,\n }, // This unconformity is with the section _above_\n );\n }),\n ),\n h.if(unconformityLabels)(UnconformityLabels, {\n width,\n sections: scaleData,\n verbose: false,\n }),\n ]);\n}\n\nfunction SectionUnits(props: SectionProps) {\n // Section with \"squishy\" timescale\n const {\n units,\n scaleInfo,\n unitComponent,\n showLabels = true,\n width = 300,\n unitComponentProps,\n className,\n clipUnits = true,\n maxInternalColumns,\n } = props;\n\n const { domain, pixelScale, pixelHeight, scale } = scaleInfo;\n\n /** Ensure that we can arrange units into the maximum number\n * of columns defined by unitComponentProps, but that we don't\n * use more than necessary.\n */\n const _unitComponentProps = useMemo(() => {\n return {\n ...unitComponentProps,\n nColumns: Math.min(\n maxInternalColumns ?? Math.floor(width / 10),\n unitComponentProps?.nColumns ?? Infinity,\n ),\n //axisType,\n };\n }, [units, unitComponentProps, maxInternalColumns, width]);\n\n const style = {\n \"--section-height\": `${pixelHeight}px`,\n \"--section-width\": `${width}px`,\n };\n\n return h(\n \"g.section\",\n { className, style, transform: `translate(0 ${scaleInfo.offset})` },\n h(\n MacrostratColumnProvider,\n {\n units,\n domain,\n pixelScale, // Actually pixels per myr,\n scale,\n },\n h(CompositeUnitsColumn, {\n width,\n showLabels,\n unitComponent,\n unitComponentProps: _unitComponentProps,\n clipToFrame: clipUnits,\n }),\n ),\n );\n}\n\ninterface CompositeTimescaleProps {\n levels?: [number, number] | number;\n unconformityLabels?: boolean;\n}\n\nexport function CompositeTimescale(props: CompositeTimescaleProps) {\n const { sections } = useMacrostratColumnData();\n const sectionScales = sections.map((section) => {\n return section.scaleInfo;\n });\n\n return h(CompositeTimescaleCore, {\n packages: sectionScales,\n ...props,\n });\n}\n\ntype CompositeTimescaleCoreProps = CompositeTimescaleProps & {\n packages: PackageScaleLayoutData[];\n};\n\nexport function CompositeTimescaleCore(props: CompositeTimescaleCoreProps) {\n const { levels = 3, packages, unconformityLabels = false } = props;\n\n // Use intervals from Macrostrat API\n const baseURL = useMacrostratBaseURL();\n const intervals = useMacrostratIntervals({ baseURL });\n\n let _levels: [number, number];\n if (typeof levels === \"number\") {\n // If levels is a number, use the most common starting level\n _levels = [2, Math.max(2 + Math.min(levels, 5) - 1, 1)];\n } else {\n _levels = levels;\n }\n\n const nCols = _levels[1] - _levels[0] + 1;\n\n return h(\"div.timescale-column\", [\n h(\n \"div.timescales\",\n packages.map((group) => {\n const { pixelHeight, paddingTop, key, scale } = group;\n return h(\n \"div.timescale-container\",\n { style: { paddingTop, \"--timescale-level-count\": nCols }, key },\n [\n h(Timescale, {\n orientation: TimescaleOrientation.VERTICAL,\n length: pixelHeight,\n levels: _levels,\n absoluteAgeScale: true,\n showAgeAxis: false,\n scale,\n intervals,\n }),\n ],\n );\n }),\n ),\n h.if(unconformityLabels)(UnconformityLabels, {\n width: \"100%\",\n sections: packages,\n className: \"unconformity-labels\",\n axisType: ColumnAxisType.AGE,\n }),\n ]);\n}\n\nexport function UnconformityLabels(props: {\n width: string | number;\n sections: PackageScaleLayoutData[];\n axisType?: ColumnAxisType;\n className?: string;\n verbose?: boolean;\n}) {\n const { width, sections, className, axisType, verbose } = props;\n\n return h(\n \"div.unconformity-labels\",\n {\n style: {\n width,\n },\n className,\n },\n sections.map((scaleInfo, i) => {\n const lastGroup = sections[i - 1];\n const top = scaleInfo.offset - scaleInfo.paddingTop;\n const upperAge = lastGroup?.domain[0];\n const lowerAge = scaleInfo.domain[1];\n return h(Unconformity, {\n axisType,\n upperAge,\n lowerAge,\n style: {\n width,\n height: scaleInfo.paddingTop,\n top,\n },\n verbose,\n });\n }),\n );\n}\n\nfunction Unconformity({\n upperAge,\n lowerAge,\n style,\n axisType,\n verbose = false,\n}) {\n if (upperAge == null || lowerAge == null) {\n return null;\n }\n\n const ageGap = Math.abs(upperAge - lowerAge);\n\n let maximumFractionDigits = 0;\n\n let className: string = null;\n if (ageGap > 1000) {\n className = \"giga\";\n } else if (ageGap > 100) {\n className = \"mega\";\n } else if (ageGap > 10) {\n className = \"large\";\n } else if (ageGap < 1) {\n className = \"small\";\n maximumFractionDigits = 2;\n } else {\n maximumFractionDigits = 1;\n }\n\n let val: ReactNode;\n if (axisType === ColumnAxisType.DEPTH || axisType === ColumnAxisType.HEIGHT) {\n const _txt = ageGap.toLocaleString(\"en-US\", { maximumFractionDigits });\n val = h(Value, { value: _txt, unit: \"m\" });\n } else {\n val = h(Duration, { value: ageGap, maximumFractionDigits });\n }\n\n let prefix: ReactNode = null;\n if (verbose) {\n prefix = h([\" \", h(\"span.prefix\", \" gap\")]);\n }\n\n return h(\"div.unconformity\", { style, className }, [\n h(\"div.unconformity-inner\", h(\"div.unconformity-text\", [val, prefix])),\n ]);\n}\n"],"names":["hyper","styles","useMacrostratUnits","LabelTrackerProvider","SectionLabelsColumn","useMacrostratColumnData","SVG","useMemo","MacrostratColumnProvider","CompositeUnitsColumn","useMacrostratBaseURL","useMacrostratIntervals","Timescale","TimescaleOrientation","ColumnAxisType","Value","Duration"],"mappings":"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"}
package/dist/section.js CHANGED
@@ -152,7 +152,7 @@ function CompositeTimescale(props) {
152
152
  function CompositeTimescaleCore(props) {
153
153
  const { levels = 3, packages, unconformityLabels = false } = props;
154
154
  const baseURL = useMacrostratBaseURL();
155
- const intervals = useMacrostratIntervals(baseURL);
155
+ const intervals = useMacrostratIntervals({ baseURL });
156
156
  let _levels;
157
157
  if (typeof levels === "number") {
158
158
  _levels = [2, Math.max(2 + Math.min(levels, 5) - 1, 1)];
@@ -1 +1 @@
1
- {"version":3,"file":"section.js","sources":["../src/section.ts"],"sourcesContent":["import {\n CompositeUnitsColumn,\n LabelTrackerProvider,\n SectionLabelsColumn,\n} from \"./units\";\nimport { ReactNode, FunctionComponent, useMemo } from \"react\";\nimport {\n Timescale,\n TimescaleOrientation,\n useMacrostratIntervals,\n} from \"@macrostrat/timescale\";\nimport { ColumnAxisType, SVG } from \"@macrostrat/column-components\";\nimport hyper from \"@macrostrat/hyper\";\nimport styles from \"./column.module.sass\";\nimport {\n useMacrostratColumnData,\n useMacrostratUnits,\n MacrostratColumnProvider,\n} from \"./data-provider\";\nimport { useMacrostratBaseURL } from \"@macrostrat/data-provider\";\nimport { Duration } from \"./unit-details\";\nimport { Value } from \"@macrostrat/data-components\";\nimport type { ExtUnit, PackageScaleLayoutData } from \"./prepare-units/types\";\n\nconst h = hyper.styled(styles);\n\nexport interface SectionSharedProps {\n unitComponent?: FunctionComponent<any>;\n unitComponentProps?: any;\n showLabels?: boolean;\n width?: number;\n columnWidth?: number;\n children?: ReactNode;\n showLabelColumn?: boolean;\n axisType?: ColumnAxisType;\n className?: string;\n clipUnits?: boolean;\n maxInternalColumns?: number;\n // Space between sections\n verticalSpacing?: number;\n // Whether to render unconformity labels\n unconformityLabels?: boolean;\n}\n\nexport interface SectionProps extends SectionSharedProps {\n units: ExtUnit[];\n scaleInfo: PackageScaleLayoutData;\n}\n\nexport function SectionsColumn(props: SectionSharedProps) {\n const {\n unconformityLabels = true,\n unitComponent,\n unitComponentProps,\n showLabels = true,\n width = 300,\n columnWidth = 150,\n showLabelColumn = true,\n clipUnits = true,\n maxInternalColumns,\n } = props;\n\n const units = useMacrostratUnits();\n\n // Get a unique key for the column\n const key = units[0]?.col_id;\n\n return h(LabelTrackerProvider, { units, key }, [\n h(SectionUnitsColumn, {\n width: columnWidth,\n unitComponent,\n unitComponentProps,\n showLabels,\n clipUnits,\n maxInternalColumns,\n unconformityLabels,\n }),\n h.if(showLabelColumn)(SectionLabelsColumn, {\n width: width - columnWidth,\n }),\n ]);\n}\n\nfunction SectionUnitsColumn(props: SectionSharedProps) {\n const {\n width,\n showLabels,\n unitComponent,\n unitComponentProps,\n clipUnits,\n maxInternalColumns,\n axisType,\n unconformityLabels = true,\n } = props;\n\n const { sections, totalHeight } = useMacrostratColumnData();\n\n const scaleData: PackageScaleLayoutData[] = sections.map((section) => {\n return section.scaleInfo;\n });\n\n const innerWidth = width - 2;\n\n return h(\"div.section-units-container\", { style: { width } }, [\n h(\n SVG,\n {\n className: \"sections\",\n height: totalHeight,\n innerWidth,\n paddingH: 1,\n },\n sections.map((group) => {\n const { units, scaleInfo, section_id } = group;\n\n const key = `section-${section_id}`;\n return h(\n SectionUnits,\n {\n units,\n scaleInfo,\n key,\n unitComponent,\n unitComponentProps,\n showLabels,\n width: innerWidth,\n clipUnits,\n maxInternalColumns,\n }, // This unconformity is with the section _above_\n );\n }),\n ),\n h.if(unconformityLabels)(UnconformityLabels, {\n width,\n sections: scaleData,\n verbose: false,\n }),\n ]);\n}\n\nfunction SectionUnits(props: SectionProps) {\n // Section with \"squishy\" timescale\n const {\n units,\n scaleInfo,\n unitComponent,\n showLabels = true,\n width = 300,\n unitComponentProps,\n className,\n clipUnits = true,\n maxInternalColumns,\n } = props;\n\n const { domain, pixelScale, pixelHeight, scale } = scaleInfo;\n\n /** Ensure that we can arrange units into the maximum number\n * of columns defined by unitComponentProps, but that we don't\n * use more than necessary.\n */\n const _unitComponentProps = useMemo(() => {\n return {\n ...unitComponentProps,\n nColumns: Math.min(\n maxInternalColumns ?? Math.floor(width / 10),\n unitComponentProps?.nColumns ?? Infinity,\n ),\n //axisType,\n };\n }, [units, unitComponentProps, maxInternalColumns, width]);\n\n const style = {\n \"--section-height\": `${pixelHeight}px`,\n \"--section-width\": `${width}px`,\n };\n\n return h(\n \"g.section\",\n { className, style, transform: `translate(0 ${scaleInfo.offset})` },\n h(\n MacrostratColumnProvider,\n {\n units,\n domain,\n pixelScale, // Actually pixels per myr,\n scale,\n },\n h(CompositeUnitsColumn, {\n width,\n showLabels,\n unitComponent,\n unitComponentProps: _unitComponentProps,\n clipToFrame: clipUnits,\n }),\n ),\n );\n}\n\ninterface CompositeTimescaleProps {\n levels?: [number, number] | number;\n unconformityLabels?: boolean;\n}\n\nexport function CompositeTimescale(props: CompositeTimescaleProps) {\n const { sections } = useMacrostratColumnData();\n const sectionScales = sections.map((section) => {\n return section.scaleInfo;\n });\n\n return h(CompositeTimescaleCore, {\n packages: sectionScales,\n ...props,\n });\n}\n\ntype CompositeTimescaleCoreProps = CompositeTimescaleProps & {\n packages: PackageScaleLayoutData[];\n};\n\nexport function CompositeTimescaleCore(props: CompositeTimescaleCoreProps) {\n const { levels = 3, packages, unconformityLabels = false } = props;\n\n // Use intervals from Macrostrat API\n const baseURL = useMacrostratBaseURL();\n const intervals = useMacrostratIntervals(baseURL);\n\n let _levels: [number, number];\n if (typeof levels === \"number\") {\n // If levels is a number, use the most common starting level\n _levels = [2, Math.max(2 + Math.min(levels, 5) - 1, 1)];\n } else {\n _levels = levels;\n }\n\n const nCols = _levels[1] - _levels[0] + 1;\n\n return h(\"div.timescale-column\", [\n h(\n \"div.timescales\",\n packages.map((group) => {\n const { pixelHeight, paddingTop, key, scale } = group;\n return h(\n \"div.timescale-container\",\n { style: { paddingTop, \"--timescale-level-count\": nCols }, key },\n [\n h(Timescale, {\n orientation: TimescaleOrientation.VERTICAL,\n length: pixelHeight,\n levels: _levels,\n absoluteAgeScale: true,\n showAgeAxis: false,\n scale,\n intervals,\n }),\n ],\n );\n }),\n ),\n h.if(unconformityLabels)(UnconformityLabels, {\n width: \"100%\",\n sections: packages,\n className: \"unconformity-labels\",\n axisType: ColumnAxisType.AGE,\n }),\n ]);\n}\n\nexport function UnconformityLabels(props: {\n width: string | number;\n sections: PackageScaleLayoutData[];\n axisType?: ColumnAxisType;\n className?: string;\n verbose?: boolean;\n}) {\n const { width, sections, className, axisType, verbose } = props;\n\n return h(\n \"div.unconformity-labels\",\n {\n style: {\n width,\n },\n className,\n },\n sections.map((scaleInfo, i) => {\n const lastGroup = sections[i - 1];\n const top = scaleInfo.offset - scaleInfo.paddingTop;\n const upperAge = lastGroup?.domain[0];\n const lowerAge = scaleInfo.domain[1];\n return h(Unconformity, {\n axisType,\n upperAge,\n lowerAge,\n style: {\n width,\n height: scaleInfo.paddingTop,\n top,\n },\n verbose,\n });\n }),\n );\n}\n\nfunction Unconformity({\n upperAge,\n lowerAge,\n style,\n axisType,\n verbose = false,\n}) {\n if (upperAge == null || lowerAge == null) {\n return null;\n }\n\n const ageGap = Math.abs(upperAge - lowerAge);\n\n let maximumFractionDigits = 0;\n\n let className: string = null;\n if (ageGap > 1000) {\n className = \"giga\";\n } else if (ageGap > 100) {\n className = \"mega\";\n } else if (ageGap > 10) {\n className = \"large\";\n } else if (ageGap < 1) {\n className = \"small\";\n maximumFractionDigits = 2;\n } else {\n maximumFractionDigits = 1;\n }\n\n let val: ReactNode;\n if (axisType === ColumnAxisType.DEPTH || axisType === ColumnAxisType.HEIGHT) {\n const _txt = ageGap.toLocaleString(\"en-US\", { maximumFractionDigits });\n val = h(Value, { value: _txt, unit: \"m\" });\n } else {\n val = h(Duration, { value: ageGap, maximumFractionDigits });\n }\n\n let prefix: ReactNode = null;\n if (verbose) {\n prefix = h([\" \", h(\"span.prefix\", \" gap\")]);\n }\n\n return h(\"div.unconformity\", { style, className }, [\n h(\"div.unconformity-inner\", h(\"div.unconformity-text\", [val, prefix])),\n ]);\n}\n"],"names":["hyper","styles"],"mappings":"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"}
1
+ {"version":3,"file":"section.js","sources":["../src/section.ts"],"sourcesContent":["import {\n CompositeUnitsColumn,\n LabelTrackerProvider,\n SectionLabelsColumn,\n} from \"./units\";\nimport { ReactNode, FunctionComponent, useMemo } from \"react\";\nimport {\n Timescale,\n TimescaleOrientation,\n useMacrostratIntervals,\n} from \"@macrostrat/timescale\";\nimport { ColumnAxisType, SVG } from \"@macrostrat/column-components\";\nimport hyper from \"@macrostrat/hyper\";\nimport styles from \"./column.module.sass\";\nimport {\n useMacrostratColumnData,\n useMacrostratUnits,\n MacrostratColumnProvider,\n} from \"./data-provider\";\nimport {\n useMacrostratBaseURL,\n useMacrostratData,\n} from \"@macrostrat/data-provider\";\nimport { Duration } from \"./unit-details\";\nimport { Value } from \"@macrostrat/data-components\";\nimport type { ExtUnit, PackageScaleLayoutData } from \"./prepare-units/types\";\n\nconst h = hyper.styled(styles);\n\nexport interface SectionSharedProps {\n unitComponent?: FunctionComponent<any>;\n unitComponentProps?: any;\n showLabels?: boolean;\n width?: number;\n columnWidth?: number;\n children?: ReactNode;\n showLabelColumn?: boolean;\n axisType?: ColumnAxisType;\n className?: string;\n clipUnits?: boolean;\n maxInternalColumns?: number;\n // Space between sections\n verticalSpacing?: number;\n // Whether to render unconformity labels\n unconformityLabels?: boolean;\n}\n\nexport interface SectionProps extends SectionSharedProps {\n units: ExtUnit[];\n scaleInfo: PackageScaleLayoutData;\n}\n\nexport function SectionsColumn(props: SectionSharedProps) {\n const {\n unconformityLabels = true,\n unitComponent,\n unitComponentProps,\n showLabels = true,\n width = 300,\n columnWidth = 150,\n showLabelColumn = true,\n clipUnits = true,\n maxInternalColumns,\n } = props;\n\n const units = useMacrostratUnits();\n\n // Get a unique key for the column\n const key = units[0]?.col_id;\n\n return h(LabelTrackerProvider, { units, key }, [\n h(SectionUnitsColumn, {\n width: columnWidth,\n unitComponent,\n unitComponentProps,\n showLabels,\n clipUnits,\n maxInternalColumns,\n unconformityLabels,\n }),\n h.if(showLabelColumn)(SectionLabelsColumn, {\n width: width - columnWidth,\n }),\n ]);\n}\n\nfunction SectionUnitsColumn(props: SectionSharedProps) {\n const {\n width,\n showLabels,\n unitComponent,\n unitComponentProps,\n clipUnits,\n maxInternalColumns,\n axisType,\n unconformityLabels = true,\n } = props;\n\n const { sections, totalHeight } = useMacrostratColumnData();\n\n const scaleData: PackageScaleLayoutData[] = sections.map((section) => {\n return section.scaleInfo;\n });\n\n const innerWidth = width - 2;\n\n return h(\"div.section-units-container\", { style: { width } }, [\n h(\n SVG,\n {\n className: \"sections\",\n height: totalHeight,\n innerWidth,\n paddingH: 1,\n },\n sections.map((group) => {\n const { units, scaleInfo, section_id } = group;\n\n const key = `section-${section_id}`;\n return h(\n SectionUnits,\n {\n units,\n scaleInfo,\n key,\n unitComponent,\n unitComponentProps,\n showLabels,\n width: innerWidth,\n clipUnits,\n maxInternalColumns,\n }, // This unconformity is with the section _above_\n );\n }),\n ),\n h.if(unconformityLabels)(UnconformityLabels, {\n width,\n sections: scaleData,\n verbose: false,\n }),\n ]);\n}\n\nfunction SectionUnits(props: SectionProps) {\n // Section with \"squishy\" timescale\n const {\n units,\n scaleInfo,\n unitComponent,\n showLabels = true,\n width = 300,\n unitComponentProps,\n className,\n clipUnits = true,\n maxInternalColumns,\n } = props;\n\n const { domain, pixelScale, pixelHeight, scale } = scaleInfo;\n\n /** Ensure that we can arrange units into the maximum number\n * of columns defined by unitComponentProps, but that we don't\n * use more than necessary.\n */\n const _unitComponentProps = useMemo(() => {\n return {\n ...unitComponentProps,\n nColumns: Math.min(\n maxInternalColumns ?? Math.floor(width / 10),\n unitComponentProps?.nColumns ?? Infinity,\n ),\n //axisType,\n };\n }, [units, unitComponentProps, maxInternalColumns, width]);\n\n const style = {\n \"--section-height\": `${pixelHeight}px`,\n \"--section-width\": `${width}px`,\n };\n\n return h(\n \"g.section\",\n { className, style, transform: `translate(0 ${scaleInfo.offset})` },\n h(\n MacrostratColumnProvider,\n {\n units,\n domain,\n pixelScale, // Actually pixels per myr,\n scale,\n },\n h(CompositeUnitsColumn, {\n width,\n showLabels,\n unitComponent,\n unitComponentProps: _unitComponentProps,\n clipToFrame: clipUnits,\n }),\n ),\n );\n}\n\ninterface CompositeTimescaleProps {\n levels?: [number, number] | number;\n unconformityLabels?: boolean;\n}\n\nexport function CompositeTimescale(props: CompositeTimescaleProps) {\n const { sections } = useMacrostratColumnData();\n const sectionScales = sections.map((section) => {\n return section.scaleInfo;\n });\n\n return h(CompositeTimescaleCore, {\n packages: sectionScales,\n ...props,\n });\n}\n\ntype CompositeTimescaleCoreProps = CompositeTimescaleProps & {\n packages: PackageScaleLayoutData[];\n};\n\nexport function CompositeTimescaleCore(props: CompositeTimescaleCoreProps) {\n const { levels = 3, packages, unconformityLabels = false } = props;\n\n // Use intervals from Macrostrat API\n const baseURL = useMacrostratBaseURL();\n const intervals = useMacrostratIntervals({ baseURL });\n\n let _levels: [number, number];\n if (typeof levels === \"number\") {\n // If levels is a number, use the most common starting level\n _levels = [2, Math.max(2 + Math.min(levels, 5) - 1, 1)];\n } else {\n _levels = levels;\n }\n\n const nCols = _levels[1] - _levels[0] + 1;\n\n return h(\"div.timescale-column\", [\n h(\n \"div.timescales\",\n packages.map((group) => {\n const { pixelHeight, paddingTop, key, scale } = group;\n return h(\n \"div.timescale-container\",\n { style: { paddingTop, \"--timescale-level-count\": nCols }, key },\n [\n h(Timescale, {\n orientation: TimescaleOrientation.VERTICAL,\n length: pixelHeight,\n levels: _levels,\n absoluteAgeScale: true,\n showAgeAxis: false,\n scale,\n intervals,\n }),\n ],\n );\n }),\n ),\n h.if(unconformityLabels)(UnconformityLabels, {\n width: \"100%\",\n sections: packages,\n className: \"unconformity-labels\",\n axisType: ColumnAxisType.AGE,\n }),\n ]);\n}\n\nexport function UnconformityLabels(props: {\n width: string | number;\n sections: PackageScaleLayoutData[];\n axisType?: ColumnAxisType;\n className?: string;\n verbose?: boolean;\n}) {\n const { width, sections, className, axisType, verbose } = props;\n\n return h(\n \"div.unconformity-labels\",\n {\n style: {\n width,\n },\n className,\n },\n sections.map((scaleInfo, i) => {\n const lastGroup = sections[i - 1];\n const top = scaleInfo.offset - scaleInfo.paddingTop;\n const upperAge = lastGroup?.domain[0];\n const lowerAge = scaleInfo.domain[1];\n return h(Unconformity, {\n axisType,\n upperAge,\n lowerAge,\n style: {\n width,\n height: scaleInfo.paddingTop,\n top,\n },\n verbose,\n });\n }),\n );\n}\n\nfunction Unconformity({\n upperAge,\n lowerAge,\n style,\n axisType,\n verbose = false,\n}) {\n if (upperAge == null || lowerAge == null) {\n return null;\n }\n\n const ageGap = Math.abs(upperAge - lowerAge);\n\n let maximumFractionDigits = 0;\n\n let className: string = null;\n if (ageGap > 1000) {\n className = \"giga\";\n } else if (ageGap > 100) {\n className = \"mega\";\n } else if (ageGap > 10) {\n className = \"large\";\n } else if (ageGap < 1) {\n className = \"small\";\n maximumFractionDigits = 2;\n } else {\n maximumFractionDigits = 1;\n }\n\n let val: ReactNode;\n if (axisType === ColumnAxisType.DEPTH || axisType === ColumnAxisType.HEIGHT) {\n const _txt = ageGap.toLocaleString(\"en-US\", { maximumFractionDigits });\n val = h(Value, { value: _txt, unit: \"m\" });\n } else {\n val = h(Duration, { value: ageGap, maximumFractionDigits });\n }\n\n let prefix: ReactNode = null;\n if (verbose) {\n prefix = h([\" \", h(\"span.prefix\", \" gap\")]);\n }\n\n return h(\"div.unconformity\", { style, className }, [\n h(\"div.unconformity-inner\", h(\"div.unconformity-text\", [val, prefix])),\n ]);\n}\n"],"names":["hyper","styles"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@macrostrat/column-views",
3
- "version": "3.4.1",
3
+ "version": "3.5.0",
4
4
  "description": "Data views for Macrostrat stratigraphic columns",
5
5
  "repository": {
6
6
  "type": "git",
@@ -49,16 +49,16 @@
49
49
  "@macrostrat/api-types": "^1.2.0",
50
50
  "@macrostrat/color-utils": "^1.2.2",
51
51
  "@macrostrat/column-components": "^2.0.8",
52
- "@macrostrat/data-components": "^1.1.11",
53
- "@macrostrat/data-provider": "^1.0.4",
52
+ "@macrostrat/data-components": "^1.1.12",
53
+ "@macrostrat/data-provider": "^1.1.0",
54
54
  "@macrostrat/hyper": "^3.1.1",
55
55
  "@macrostrat/map-interface": "^2.2.6",
56
- "@macrostrat/map-styles": "^2.2.3",
56
+ "@macrostrat/map-styles": "^2.2.4",
57
57
  "@macrostrat/mapbox-react": "^3.2.2",
58
58
  "@macrostrat/mapbox-utils": "^1.7.4",
59
59
  "@macrostrat/stratigraphy-utils": "^1.3.0",
60
60
  "@macrostrat/svg-map-components": "^2.0.3",
61
- "@macrostrat/timescale": "^3.1.3",
61
+ "@macrostrat/timescale": "^3.1.4",
62
62
  "@macrostrat/ui-components": "^5.0.10",
63
63
  "@turf/boolean-point-in-polygon": "^7.3.2",
64
64
  "@turf/buffer": "^7.3.2",
package/src/column.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { ColumnAxisType } from "@macrostrat/column-components";
2
- import { hyperStyled } from "@macrostrat/hyper";
3
2
  import {
4
3
  Box,
5
4
  extractPadding,
@@ -12,10 +11,12 @@ import {
12
11
  useCallback,
13
12
  CSSProperties,
14
13
  ComponentType,
14
+ RefObject,
15
15
  } from "react";
16
- import styles from "./column.module.sass";
16
+ import h from "./column.module.sass";
17
17
  import { UnitComponent } from "./units";
18
18
  import {
19
+ ColumnRef,
19
20
  UnitKeyboardNavigation,
20
21
  useColumnRef,
21
22
  useUnitSelectionDispatch,
@@ -48,8 +49,6 @@ import { NonIdealState } from "@blueprintjs/core";
48
49
  import { DataField } from "@macrostrat/data-components";
49
50
  import { ScaleContinuousNumeric } from "d3-scale";
50
51
 
51
- const h = hyperStyled(styles);
52
-
53
52
  interface BaseColumnProps extends Omit<
54
53
  SectionSharedProps,
55
54
  "unconformityLabels"
@@ -88,6 +87,7 @@ export interface ColumnProps
88
87
  // Unconformity height in pixels
89
88
  unconformityHeight?: number;
90
89
  scale?: ScaleContinuousNumeric<number, number>;
90
+ ref?: RefObject<ColumnRef>;
91
91
  }
92
92
 
93
93
  export function Column(props: ColumnProps) {
@@ -113,6 +113,7 @@ export function Column(props: ColumnProps) {
113
113
  hybridScale,
114
114
  scale,
115
115
  axisType,
116
+ ref,
116
117
  ...rest
117
118
  } = props;
118
119
 
@@ -178,6 +179,7 @@ export function Column(props: ColumnProps) {
178
179
  allowUnitSelection: showUnitPopover || allowUnitSelection,
179
180
  onUnitSelected,
180
181
  selectedUnit,
182
+ ref,
181
183
  },
182
184
  h(ColumnInner, { ageAxisComponent, ...rest }, [
183
185
  children,