@macrostrat/column-views 3.4.2 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/column-views.css +7 -7
  3. package/dist/column.cjs +21 -21
  4. package/dist/column.cjs.map +1 -1
  5. package/dist/column.d.ts +3 -1
  6. package/dist/column.js +4 -4
  7. package/dist/column.js.map +1 -1
  8. package/dist/data-provider/store.cjs +26 -3
  9. package/dist/data-provider/store.cjs.map +1 -1
  10. package/dist/data-provider/store.d.ts +52 -0
  11. package/dist/data-provider/store.js +27 -4
  12. package/dist/data-provider/store.js.map +1 -1
  13. package/dist/data-provider/unit-selection.cjs +10 -4
  14. package/dist/data-provider/unit-selection.cjs.map +1 -1
  15. package/dist/data-provider/unit-selection.d.ts +3 -0
  16. package/dist/data-provider/unit-selection.js +10 -4
  17. package/dist/data-provider/unit-selection.js.map +1 -1
  18. package/dist/index.cjs +1 -0
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +2 -1
  21. package/dist/prepare-units/helpers.cjs +58 -36
  22. package/dist/prepare-units/helpers.cjs.map +1 -1
  23. package/dist/prepare-units/helpers.d.ts +13 -5
  24. package/dist/prepare-units/helpers.js +59 -37
  25. package/dist/prepare-units/helpers.js.map +1 -1
  26. package/dist/prepare-units/index.cjs +1 -0
  27. package/dist/prepare-units/index.cjs.map +1 -1
  28. package/dist/prepare-units/index.d.ts +2 -2
  29. package/dist/prepare-units/index.js +1 -0
  30. package/dist/prepare-units/index.js.map +1 -1
  31. package/dist/unit-details/panel.cjs +1 -1
  32. package/dist/unit-details/panel.cjs.map +1 -1
  33. package/dist/unit-details/panel.js +1 -1
  34. package/dist/unit-details/panel.js.map +1 -1
  35. package/dist/units/boxes.module.sass.cjs +3 -3
  36. package/dist/units/boxes.module.sass.js +3 -3
  37. package/dist/units/index.cjs +5 -5
  38. package/dist/units/index.cjs.map +1 -1
  39. package/dist/units/index.d.ts +2 -2
  40. package/dist/units/index.js +5 -5
  41. package/dist/units/index.js.map +1 -1
  42. package/package.json +4 -4
  43. package/src/column.ts +6 -4
  44. package/src/correlation-chart/stories/namibia-correlation.stories.ts +151 -0
  45. package/src/data-provider/store.ts +48 -4
  46. package/src/data-provider/unit-selection.ts +10 -5
  47. package/src/prepare-units/helpers.ts +98 -50
  48. package/src/prepare-units/index.ts +4 -1
  49. package/src/unit-details/panel.ts +1 -1
  50. package/src/units/boxes.module.sass +2 -2
  51. package/src/units/index.ts +7 -7
@@ -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,
@@ -7,8 +7,10 @@ const stratigraphyUtils = require("@macrostrat/stratigraphy-utils");
7
7
  const dt = 1e-3;
8
8
  class UnitGroup {
9
9
  units;
10
- constructor(units) {
10
+ axisType;
11
+ constructor(units, axisType = columnComponents.ColumnAxisType.AGE) {
11
12
  this.units = units;
13
+ this.axisType = axisType;
12
14
  }
13
15
  get t_age() {
14
16
  return Math.min(...this.units.map((d) => d.t_age));
@@ -20,25 +22,28 @@ class UnitGroup {
20
22
  return assignColumnsToUnitsWithinOverlappingGroup(this);
21
23
  }
22
24
  }
23
- function groupUnitsByMutualOverlap(units, tolerance = dt) {
24
- const sections = groupUnitsIntoSectionsByOverlap(
25
- units,
26
- columnComponents.ColumnAxisType.AGE,
27
- tolerance
28
- );
25
+ function groupUnitsByMutualOverlap(units, tolerance = dt, axisType = columnComponents.ColumnAxisType.AGE) {
26
+ const sections = groupUnitsIntoSectionsByOverlap(units, axisType, tolerance);
27
+ console.log("Grouped sections", sections);
29
28
  return sections.map((d) => {
30
- return new UnitGroup(d.units);
29
+ return new UnitGroup(d.units, axisType);
31
30
  });
32
31
  }
33
32
  const sortByAge = utils.createUnitSorter(columnComponents.ColumnAxisType.AGE);
34
33
  function assignColumnsToUnitsWithinOverlappingGroup(group2) {
35
- const sortedUnits = [...group2.units].sort((a, b) => b.b_age - a.b_age);
34
+ const sortedUnits = [...group2.units].sort(utils.createUnitSorter(group2.axisType));
36
35
  const columns = [];
37
36
  for (const unit of sortedUnits) {
37
+ const colHint = unit.layoutHints?.column;
38
+ if (colHint != null) {
39
+ columns[colHint] ??= [];
40
+ columns[colHint].push(unit);
41
+ continue;
42
+ }
38
43
  let didAddToExistingColumn = false;
39
44
  for (const column of columns) {
40
45
  const lastUnit = column[column.length - 1];
41
- if (!utils.unitsOverlap(unit, lastUnit, columnComponents.ColumnAxisType.AGE, dt)) {
46
+ if (!utils.unitsOverlap(unit, lastUnit, group2.axisType, dt)) {
42
47
  column.push(unit);
43
48
  didAddToExistingColumn = true;
44
49
  break;
@@ -48,13 +53,15 @@ function assignColumnsToUnitsWithinOverlappingGroup(group2) {
48
53
  columns.push([unit]);
49
54
  }
50
55
  }
56
+ console.log(columns);
51
57
  const units = columns.flatMap(
52
58
  (column, columnIndex) => column.map((unit) => {
53
59
  return {
54
60
  ...unit,
55
- overlap: {
61
+ layout: {
56
62
  column: columnIndex,
57
- nColumns: columns.length,
63
+ nColumns: 1,
64
+ totalColumns: columns.length,
58
65
  unitGroup: group2
59
66
  }
60
67
  };
@@ -68,7 +75,7 @@ function preprocessUnits(section, axisType = columnComponents.ColumnAxisType.AGE
68
75
  if (axisType != columnComponents.ColumnAxisType.AGE) {
69
76
  tolerance = 0.01;
70
77
  }
71
- const unitGroups = groupUnitsByMutualOverlap(units, tolerance);
78
+ const unitGroups = groupUnitsByMutualOverlap(units, tolerance, axisType);
72
79
  const divisions = [];
73
80
  for (const group2 of unitGroups) {
74
81
  divisions.push(...group2.getUnits());
@@ -111,33 +118,48 @@ function groupUnitsIntoSectionsBySectionID(units, axisType = columnComponents.Co
111
118
  return groups1;
112
119
  }
113
120
  function groupUnitsIntoSectionsByOverlap(units, axisType = columnComponents.ColumnAxisType.AGE, tolerance = dt) {
114
- const sectionList = [];
115
- for (const unit of units) {
116
- const heightRange = utils.getUnitHeightRange(unit, axisType);
117
- let section = sectionList.find(
118
- (s) => stratigraphyUtils.compareAgeRanges(heightRange, s.heightRange, tolerance) !== stratigraphyUtils.AgeRangeRelationship.Disjoint
119
- );
120
- if (section == null) {
121
- sectionList.push({
122
- heightRange,
123
- units: [unit]
124
- });
125
- } else {
126
- section.units.push(unit);
127
- if (axisType == columnComponents.ColumnAxisType.DEPTH || axisType == columnComponents.ColumnAxisType.AGE) {
128
- section.heightRange = [
129
- Math.max(section.heightRange[0], heightRange[0]),
130
- Math.min(section.heightRange[1], heightRange[1])
131
- ];
121
+ const initialSections = units.map((unit) => {
122
+ return {
123
+ units: [unit],
124
+ heightRange: utils.getUnitHeightRange(unit, axisType)
125
+ };
126
+ });
127
+ const isOverlapping = (a, b) => {
128
+ const res = stratigraphyUtils.compareAgeRanges(a.heightRange, b.heightRange, tolerance);
129
+ return res !== stratigraphyUtils.AgeRangeRelationship.Disjoint;
130
+ };
131
+ const mergeSections = (a, b) => {
132
+ return {
133
+ units: [...a.units, ...b.units],
134
+ heightRange: stratigraphyUtils.mergeAgeRanges(
135
+ [a.heightRange, b.heightRange],
136
+ stratigraphyUtils.MergeMode.Outer
137
+ )
138
+ };
139
+ };
140
+ let finalSectionList = initialSections;
141
+ while (true) {
142
+ let merged = false;
143
+ const nextSections = [];
144
+ for (const section of finalSectionList) {
145
+ if (nextSections.length === 0) {
146
+ nextSections.push(section);
132
147
  } else {
133
- section.heightRange = [
134
- Math.min(section.heightRange[0], heightRange[0]),
135
- Math.max(section.heightRange[1], heightRange[1])
136
- ];
148
+ const lastSection = nextSections[nextSections.length - 1];
149
+ if (isOverlapping(section, lastSection)) {
150
+ nextSections[nextSections.length - 1] = mergeSections(lastSection, section);
151
+ merged = true;
152
+ } else {
153
+ nextSections.push(section);
154
+ }
137
155
  }
138
156
  }
157
+ finalSectionList = nextSections;
158
+ if (!merged) {
159
+ break;
160
+ }
139
161
  }
140
- return sectionList.map((section, i) => {
162
+ return finalSectionList.map((section, i) => {
141
163
  const [b_age, t_age] = getSectionAgeRange(section.units);
142
164
  const [b_pos, t_pos] = getSectionPosRange(section.units, axisType);
143
165
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.cjs","sources":["../../src/prepare-units/helpers.ts"],"sourcesContent":["import type { BaseUnit, UnitLong } from \"@macrostrat/api-types\";\nimport { group } from \"d3-array\";\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\nimport {\n createUnitSorter,\n ensureArray,\n ensureRealFloat,\n getUnitHeightRange,\n unitsOverlap,\n} from \"./utils\";\nimport {\n AgeRangeRelationship,\n compareAgeRanges,\n} from \"@macrostrat/stratigraphy-utils\";\nimport type { ExtUnit, SectionInfo, StratigraphicPackage } from \"./types\";\n\nconst dt = 0.001;\n\nexport type UnitWithDefinedOverlap<T extends BaseUnit> = T & {\n overlap: UnitOverlapInfo<T>;\n};\n\ntype UnitOverlapInfo<T extends BaseUnit> = {\n column: number;\n nColumns: number;\n unitGroup: UnitGroup<T>;\n};\n\nclass UnitGroup<T extends BaseUnit> implements StratigraphicPackage {\n units: T[];\n\n constructor(units: T[]) {\n this.units = units;\n }\n\n get t_age(): number {\n return Math.min(...this.units.map((d) => d.t_age));\n }\n\n get b_age(): number {\n return Math.max(...this.units.map((d) => d.b_age));\n }\n\n getUnits(): UnitWithDefinedOverlap<T>[] {\n return assignColumnsToUnitsWithinOverlappingGroup(this);\n }\n}\n\nfunction groupUnitsByMutualOverlap<T extends BaseUnit>(\n units: T[],\n tolerance: number = dt,\n): UnitGroup<T>[] {\n /** Group units within a section that have mutual overlap. */\n const sections = groupUnitsIntoSectionsByOverlap(\n units,\n ColumnAxisType.AGE,\n tolerance,\n );\n return sections.map((d) => {\n return new UnitGroup(d.units);\n });\n}\n\nconst sortByAge = createUnitSorter(ColumnAxisType.AGE);\n\nfunction assignColumnsToUnitsWithinOverlappingGroup<T extends BaseUnit>(\n group: UnitGroup<T>,\n): UnitWithDefinedOverlap<T>[] {\n /** A relatively naïve function to assign columns to units within a mutually overlapping group.\n * This gets the number of columns that would be needed to accommodate the units in the group.\n * It then assigns columns to units in the group based on the number of columns needed, with\n * lower units further to the left.\n */\n\n const sortedUnits = [...group.units].sort((a, b) => b.b_age - a.b_age);\n\n const columns: T[][] = [];\n\n for (const unit of sortedUnits) {\n // Find a column that is not already occupied by an overlapping unit\n let didAddToExistingColumn = false;\n for (const column of columns) {\n const lastUnit = column[column.length - 1];\n if (!unitsOverlap(unit, lastUnit, ColumnAxisType.AGE, dt)) {\n column.push(unit);\n didAddToExistingColumn = true;\n break;\n }\n }\n if (!didAddToExistingColumn) {\n columns.push([unit]);\n }\n }\n\n const units: UnitWithDefinedOverlap<T>[] = columns.flatMap(\n (column, columnIndex) =>\n column.map((unit) => {\n return {\n ...unit,\n overlap: {\n column: columnIndex,\n nColumns: columns.length,\n unitGroup: group,\n },\n };\n }),\n );\n\n return units.toSorted(sortByAge);\n}\n\nexport function preprocessUnits<T extends UnitLong = UnitLong>(\n section: SectionInfo<T>,\n axisType: ColumnAxisType = ColumnAxisType.AGE,\n): ExtUnit[] {\n /** Preprocess units to add overlapping units and columns. */\n let units = section.units;\n\n let tolerance = 0.001; // 1 kyr tolerance for age columns\n if (axisType != ColumnAxisType.AGE) {\n tolerance = 0.01; // 1cm tolerance for height/depth columns\n }\n\n const unitGroups = groupUnitsByMutualOverlap(units, tolerance);\n\n const divisions: any[] = [];\n // Serialize all groups' units to a single array\n for (const group of unitGroups) {\n divisions.push(...group.getUnits());\n }\n\n return divisions.map((d) =>\n enhanceUnitWithClippingPosition(section, d, axisType),\n );\n}\n\nexport function enhanceUnitWithClippingPosition(section, d, axisType) {\n const [b_pos, t_pos] = getUnitHeightRange(d, axisType);\n if (axisType == ColumnAxisType.AGE) {\n if (b_pos > section.b_age) {\n d.b_clip_pos = section.b_age;\n }\n if (t_pos < section.t_age) {\n d.t_clip_pos = section.t_age;\n }\n }\n if (axisType == ColumnAxisType.DEPTH) {\n if (b_pos > section.b_pos) {\n d.b_clip_pos = section.b_pos;\n }\n if (t_pos < section.t_pos) {\n d.t_clip_pos = section.t_pos;\n }\n }\n return d;\n}\n\nexport function groupUnitsIntoSectionsBySectionID<T extends UnitLong>(\n units: T[],\n axisType: ColumnAxisType = ColumnAxisType.AGE,\n): SectionInfo<T>[] {\n /** Group units into sections by section_id.\n * This works for large-scale Macrostrat columns, where units are grouped by section_id.\n * */\n let groups = Array.from(group(units, (d) => d.section_id));\n const unitComparator = createUnitSorter(axisType);\n\n const groups1 = groups.map(([section_id, sectionUnits]) => {\n const [b_age, t_age] = getSectionAgeRange(sectionUnits);\n const [b_pos, t_pos] = getSectionPosRange(sectionUnits, axisType);\n\n // sort units by position\n sectionUnits.sort(unitComparator);\n return { section_id, t_age, b_age, b_pos, t_pos, units: sectionUnits };\n });\n // Sort sections by increasing top age, then increasing bottom age.\n // Sections have no relative ordinal position other than age...\n const compareSections = createUnitSorter(axisType) as (\n a: StratigraphicPackage,\n b: StratigraphicPackage,\n ) => number;\n groups1.sort(compareSections);\n return groups1;\n}\n\ninterface WorkingSection {\n units: UnitLong[];\n // Position or age\n heightRange?: [number, number];\n}\n\nexport function groupUnitsIntoSectionsByOverlap<T extends BaseUnit>(\n units: T[],\n axisType: ColumnAxisType = ColumnAxisType.AGE,\n tolerance: number = dt,\n): SectionInfo<T>[] {\n /** Group units into sections by overlap.\n * This creates \"synthetic\" sections that correspond to packages bound by scale gaps.\n * This is most useful in the height and depth domains, where gaps (e.g., missing core) are\n * common.\n * */\n // Start with each unit as its own \"section\", and progressively merge...\n const sectionList: WorkingSection[] = [];\n for (const unit of units) {\n // Check if the unit overlaps with any existing section\n const heightRange = getUnitHeightRange(unit, axisType);\n let section: WorkingSection | undefined = sectionList.find(\n (s) =>\n compareAgeRanges(heightRange, s.heightRange, tolerance) !==\n AgeRangeRelationship.Disjoint,\n );\n if (section == null) {\n // No overlap, create a new section\n sectionList.push({\n heightRange,\n units: [unit],\n });\n } else {\n // Overlap, merge the unit into the section\n section.units.push(unit);\n // Update the height range\n if (axisType == ColumnAxisType.DEPTH || axisType == ColumnAxisType.AGE) {\n section.heightRange = [\n Math.max(section.heightRange[0], heightRange[0]),\n Math.min(section.heightRange[1], heightRange[1]),\n ];\n } else {\n section.heightRange = [\n Math.min(section.heightRange[0], heightRange[0]),\n Math.max(section.heightRange[1], heightRange[1]),\n ];\n }\n }\n }\n // We should have a section for each unit, now we can convert to SectionInfo\n // Ages have to be really actually ages, not heights\n\n return sectionList.map((section, i) => {\n const [b_age, t_age] = getSectionAgeRange(section.units);\n const [b_pos, t_pos] = getSectionPosRange(section.units, axisType);\n return {\n // Negative section IDs are used to indicate that these are synthetic sections\n section_id: -i,\n t_age,\n b_age,\n t_pos,\n b_pos,\n units: section.units as T[],\n };\n });\n}\n\nexport function getSectionPosRange(\n units: BaseUnit[],\n axisType: ColumnAxisType,\n): [number, number] {\n /** Get the overall position range of a set of units. */\n const t_positions = units.map((d) => {\n switch (axisType) {\n case ColumnAxisType.AGE:\n return d.t_age;\n case ColumnAxisType.DEPTH:\n case ColumnAxisType.HEIGHT:\n case ColumnAxisType.ORDINAL:\n return d.t_pos;\n default:\n throw new Error(`Unknown axis type: ${axisType}`);\n }\n });\n const b_positions = units.map((d) => {\n switch (axisType) {\n case ColumnAxisType.AGE:\n return d.b_age;\n case ColumnAxisType.DEPTH:\n case ColumnAxisType.HEIGHT:\n case ColumnAxisType.ORDINAL:\n return d.b_pos;\n default:\n throw new Error(`Unknown axis type: ${axisType}`);\n }\n });\n if (axisType == ColumnAxisType.AGE || axisType == ColumnAxisType.DEPTH) {\n return [Math.max(...b_positions), Math.min(...t_positions)];\n } else {\n return [Math.min(...b_positions), Math.max(...t_positions)];\n }\n}\n\nexport function getSectionAgeRange(units: BaseUnit[]): [number, number] {\n /** Get the overall age range of a set of units. */\n return getSectionPosRange(units, ColumnAxisType.AGE);\n}\n\nexport function mergeOverlappingSections<T extends UnitLong>(\n sections: SectionInfo<T>[],\n): SectionInfo<T>[] {\n /** Columns can have sections that overlap in time. Here, we merge overlapping\n * sections into a single section to correctly render gap-bound packages.\n */\n const [firstSection, ...rest] = sections;\n const newSections = [firstSection];\n for (const section of rest) {\n const lastSection = newSections[newSections.length - 1];\n if (\n lastSection.b_age < section.t_age ||\n lastSection.t_age > section.b_age\n ) {\n // No overlap, add the section as normal\n newSections.push(section);\n continue;\n }\n\n // Overlap, merge the sections\n lastSection.section_id = [\n ...ensureArray(lastSection.section_id),\n ...ensureArray(section.section_id),\n ];\n lastSection.units.push(...section.units);\n lastSection.b_age = Math.max(lastSection.b_age, section.b_age);\n lastSection.t_age = Math.min(lastSection.t_age, section.t_age);\n }\n return newSections;\n}\n\nexport function preprocessSectionUnit(unit: UnitLong): UnitLong {\n /** Preprocess a single unit for a \"section\" column type.\n * This mostly handles vagaries of eODP-style columns.\n * */\n\n let b_pos = unit.b_pos;\n let t_pos = unit.t_pos;\n\n let unit_name = unit.unit_name;\n\n // eODP columns sometimes have overlapping core sections, which are encoded in the name field\n // Match eODP section names\n const match = unit.unit_name.match(/^(\\d+(\\.\\d+)?)-(\\d+(\\.\\d+)?): (.+)/);\n\n if (match) {\n // These values should already be set if we've used the show_positions flag\n t_pos ??= ensureRealFloat(match[1]);\n b_pos ??= ensureRealFloat(match[3]);\n unit_name = match[5];\n }\n\n return {\n ...unit,\n b_pos: ensureRealFloat(b_pos),\n t_pos: ensureRealFloat(t_pos),\n unit_name,\n };\n}\n"],"names":["ColumnAxisType","createUnitSorter","group","unitsOverlap","getUnitHeightRange","compareAgeRanges","AgeRangeRelationship","ensureArray","ensureRealFloat"],"mappings":"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"}
1
+ {"version":3,"file":"helpers.cjs","sources":["../../src/prepare-units/helpers.ts"],"sourcesContent":["import type { BaseUnit, UnitLong } from \"@macrostrat/api-types\";\nimport { group } from \"d3-array\";\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\nimport {\n createUnitSorter,\n ensureArray,\n ensureRealFloat,\n getUnitHeightRange,\n unitsOverlap,\n} from \"./utils\";\nimport {\n AgeRangeRelationship,\n compareAgeRanges,\n mergeAgeRanges,\n MergeMode,\n} from \"@macrostrat/stratigraphy-utils\";\nimport type { ExtUnit, SectionInfo, StratigraphicPackage } from \"./types\";\n\nconst dt = 0.001;\n\nexport type UnitWithLayoutParameters<T extends BaseUnit> = T & {\n layout: UnitLayoutData<T>;\n};\n\nexport type UnitWithLayoutHints<T extends BaseUnit> = T & {\n layoutHints?: UnitLayoutHints;\n};\n\ninterface UnitLayoutInfo {\n column: number;\n nColumns: number;\n}\n\ntype UnitLayoutHints = Partial<UnitLayoutInfo>;\n\ntype UnitLayoutData<T extends BaseUnit> = UnitLayoutInfo & {\n totalColumns: number;\n unitGroup: UnitGroup<T>;\n};\n\nclass UnitGroup<T extends BaseUnit> implements StratigraphicPackage {\n units: T[];\n axisType: ColumnAxisType;\n\n constructor(units: T[], axisType: ColumnAxisType = ColumnAxisType.AGE) {\n this.units = units;\n this.axisType = axisType;\n }\n\n get t_age(): number {\n return Math.min(...this.units.map((d) => d.t_age));\n }\n\n get b_age(): number {\n return Math.max(...this.units.map((d) => d.b_age));\n }\n\n getUnits(): UnitWithLayoutParameters<T>[] {\n return assignColumnsToUnitsWithinOverlappingGroup(this);\n }\n}\n\nfunction groupUnitsByMutualOverlap<T extends BaseUnit>(\n units: T[],\n tolerance: number = dt,\n axisType: ColumnAxisType = ColumnAxisType.AGE,\n): UnitGroup<T>[] {\n /** Group units within a section that have mutual overlap. */\n\n const sections = groupUnitsIntoSectionsByOverlap(units, axisType, tolerance);\n\n console.log(\"Grouped sections\", sections);\n\n return sections.map((d) => {\n return new UnitGroup(d.units, axisType);\n });\n}\n\nconst sortByAge = createUnitSorter(ColumnAxisType.AGE);\n\nfunction assignColumnsToUnitsWithinOverlappingGroup<T extends BaseUnit>(\n group: UnitGroup<UnitWithLayoutHints<T>>,\n): UnitWithLayoutParameters<T>[] {\n /** A relatively naïve function to assign columns to units within a mutually overlapping group.\n * This gets the number of columns that would be needed to accommodate the units in the group.\n * It then assigns columns to units in the group based on the number of columns needed, with\n * lower units further to the left.\n */\n\n const sortedUnits = [...group.units].sort(createUnitSorter(group.axisType));\n\n const columns: T[][] = [];\n\n let nColumns = 1;\n\n for (const unit of sortedUnits) {\n let startIndex = 0;\n let endIndex = 0;\n // Find a column that is not already occupied by an overlapping unit\n const colHint = unit.layoutHints?.column;\n if (colHint != null) {\n columns[colHint] ??= [];\n columns[colHint].push(unit);\n continue;\n }\n let didAddToExistingColumn = false;\n\n for (const column of columns) {\n const lastUnit = column[column.length - 1];\n if (!unitsOverlap(unit, lastUnit, group.axisType, dt)) {\n column.push(unit);\n didAddToExistingColumn = true;\n break;\n }\n }\n if (!didAddToExistingColumn) {\n columns.push([unit]);\n }\n }\n\n console.log(columns);\n\n const units: UnitWithLayoutParameters<T>[] = columns.flatMap(\n (column, columnIndex) =>\n column.map((unit) => {\n return {\n ...unit,\n layout: {\n column: columnIndex,\n nColumns: 1,\n totalColumns: columns.length,\n unitGroup: group,\n },\n };\n }),\n );\n\n return units.toSorted(sortByAge);\n}\n\nexport function preprocessUnits<T extends UnitLong = UnitLong>(\n section: SectionInfo<T>,\n axisType: ColumnAxisType = ColumnAxisType.AGE,\n): ExtUnit[] {\n /** Preprocess units to add overlapping units and columns. */\n let units = section.units;\n\n let tolerance = 0.001; // 1 kyr tolerance for age columns\n if (axisType != ColumnAxisType.AGE) {\n tolerance = 0.01; // 1cm tolerance for height/depth columns\n }\n\n const unitGroups = groupUnitsByMutualOverlap(units, tolerance, axisType);\n\n const divisions: any[] = [];\n // Serialize all groups' units to a single array\n for (const group of unitGroups) {\n divisions.push(...group.getUnits());\n }\n\n return divisions.map((d) =>\n enhanceUnitWithClippingPosition(section, d, axisType),\n );\n}\n\nexport function enhanceUnitWithClippingPosition(section, d, axisType) {\n const [b_pos, t_pos] = getUnitHeightRange(d, axisType);\n if (axisType == ColumnAxisType.AGE) {\n if (b_pos > section.b_age) {\n d.b_clip_pos = section.b_age;\n }\n if (t_pos < section.t_age) {\n d.t_clip_pos = section.t_age;\n }\n }\n if (axisType == ColumnAxisType.DEPTH) {\n if (b_pos > section.b_pos) {\n d.b_clip_pos = section.b_pos;\n }\n if (t_pos < section.t_pos) {\n d.t_clip_pos = section.t_pos;\n }\n }\n return d;\n}\n\nexport function groupUnitsIntoSectionsBySectionID<T extends UnitLong>(\n units: T[],\n axisType: ColumnAxisType = ColumnAxisType.AGE,\n): SectionInfo<T>[] {\n /** Group units into sections by section_id.\n * This works for large-scale Macrostrat columns, where units are grouped by section_id.\n * */\n let groups = Array.from(group(units, (d) => d.section_id));\n const unitComparator = createUnitSorter(axisType);\n\n const groups1 = groups.map(([section_id, sectionUnits]) => {\n const [b_age, t_age] = getSectionAgeRange(sectionUnits);\n const [b_pos, t_pos] = getSectionPosRange(sectionUnits, axisType);\n\n // sort units by position\n sectionUnits.sort(unitComparator);\n return { section_id, t_age, b_age, b_pos, t_pos, units: sectionUnits };\n });\n // Sort sections by increasing top age, then increasing bottom age.\n // Sections have no relative ordinal position other than age...\n const compareSections = createUnitSorter(axisType) as (\n a: StratigraphicPackage,\n b: StratigraphicPackage,\n ) => number;\n groups1.sort(compareSections);\n return groups1;\n}\n\ninterface WorkingSection {\n units: UnitLong[];\n // Position or age\n heightRange: [number, number];\n}\n\nexport function groupUnitsIntoSectionsByOverlap<T extends BaseUnit>(\n units: T[],\n axisType: ColumnAxisType = ColumnAxisType.AGE,\n tolerance: number = dt,\n): SectionInfo<T>[] {\n /** Group units into sections by overlap.\n * This creates \"synthetic\" sections that correspond to packages bound by scale gaps.\n * This is most useful in the height and depth domains, where gaps (e.g., missing core) are\n * common.\n * */\n // Start with each unit as its own \"section\", and progressively merge...\n const sectionList: WorkingSection[] = [];\n\n const initialSections: WorkingSection[] = units.map((unit) => {\n return {\n units: [unit],\n heightRange: getUnitHeightRange(unit, axisType),\n };\n });\n\n const isOverlapping = (a: WorkingSection, b: WorkingSection) => {\n const res = compareAgeRanges(a.heightRange, b.heightRange, tolerance);\n return res !== AgeRangeRelationship.Disjoint;\n };\n\n const mergeSections = (a: WorkingSection, b: WorkingSection) => {\n return {\n units: [...a.units, ...b.units],\n heightRange: mergeAgeRanges(\n [a.heightRange, b.heightRange],\n MergeMode.Outer,\n ),\n };\n };\n\n // Merge overlapping sections progressively until no more merges are possible\n let finalSectionList = initialSections;\n\n while (true) {\n let merged = false;\n const nextSections: WorkingSection[] = [];\n\n for (const section of finalSectionList) {\n if (nextSections.length === 0) {\n nextSections.push(section);\n } else {\n const lastSection = nextSections[nextSections.length - 1];\n if (isOverlapping(section, lastSection)) {\n // Merge with the last section\n nextSections[nextSections.length - 1] = mergeSections(lastSection, section);\n merged = true;\n } else {\n nextSections.push(section);\n }\n }\n }\n\n finalSectionList = nextSections;\n\n // Stop when no more merges occurred in this pass\n if (!merged) {\n break;\n }\n }\n\n return finalSectionList.map((section, i) => {\n const [b_age, t_age] = getSectionAgeRange(section.units);\n const [b_pos, t_pos] = getSectionPosRange(section.units, axisType);\n return {\n // Negative section IDs are used to indicate that these are synthetic sections\n section_id: -i,\n t_age,\n b_age,\n t_pos,\n b_pos,\n units: section.units as T[],\n };\n });\n}\n\nexport function getSectionPosRange(\n units: BaseUnit[],\n axisType: ColumnAxisType,\n): [number, number] {\n /** Get the overall position range of a set of units. */\n const t_positions = units.map((d) => {\n switch (axisType) {\n case ColumnAxisType.AGE:\n return d.t_age;\n case ColumnAxisType.DEPTH:\n case ColumnAxisType.HEIGHT:\n case ColumnAxisType.ORDINAL:\n return d.t_pos;\n default:\n throw new Error(`Unknown axis type: ${axisType}`);\n }\n });\n const b_positions = units.map((d) => {\n switch (axisType) {\n case ColumnAxisType.AGE:\n return d.b_age;\n case ColumnAxisType.DEPTH:\n case ColumnAxisType.HEIGHT:\n case ColumnAxisType.ORDINAL:\n return d.b_pos;\n default:\n throw new Error(`Unknown axis type: ${axisType}`);\n }\n });\n if (axisType == ColumnAxisType.AGE || axisType == ColumnAxisType.DEPTH) {\n return [Math.max(...b_positions), Math.min(...t_positions)];\n } else {\n return [Math.min(...b_positions), Math.max(...t_positions)];\n }\n}\n\nexport function getSectionAgeRange(units: BaseUnit[]): [number, number] {\n /** Get the overall age range of a set of units. */\n return getSectionPosRange(units, ColumnAxisType.AGE);\n}\n\nexport function mergeOverlappingSections<T extends UnitLong>(\n sections: SectionInfo<T>[],\n): SectionInfo<T>[] {\n /** Columns can have sections that overlap in time. Here, we merge overlapping\n * sections into a single section to correctly render gap-bound packages.\n */\n const [firstSection, ...rest] = sections;\n const newSections = [firstSection];\n for (const section of rest) {\n const lastSection = newSections[newSections.length - 1];\n if (\n lastSection.b_age < section.t_age ||\n lastSection.t_age > section.b_age\n ) {\n // No overlap, add the section as normal\n newSections.push(section);\n continue;\n }\n\n // Overlap, merge the sections\n lastSection.section_id = [\n ...ensureArray(lastSection.section_id),\n ...ensureArray(section.section_id),\n ];\n lastSection.units.push(...section.units);\n lastSection.b_age = Math.max(lastSection.b_age, section.b_age);\n lastSection.t_age = Math.min(lastSection.t_age, section.t_age);\n }\n return newSections;\n}\n\nexport function preprocessSectionUnit(unit: UnitLong): UnitLong {\n /** Preprocess a single unit for a \"section\" column type.\n * This mostly handles vagaries of eODP-style columns.\n * */\n\n let b_pos = unit.b_pos;\n let t_pos = unit.t_pos;\n\n let unit_name = unit.unit_name;\n\n // eODP columns sometimes have overlapping core sections, which are encoded in the name field\n // Match eODP section names\n const match = unit.unit_name.match(/^(\\d+(\\.\\d+)?)-(\\d+(\\.\\d+)?): (.+)/);\n\n if (match) {\n // These values should already be set if we've used the show_positions flag\n t_pos ??= ensureRealFloat(match[1]);\n b_pos ??= ensureRealFloat(match[3]);\n unit_name = match[5];\n }\n\n return {\n ...unit,\n b_pos: ensureRealFloat(b_pos),\n t_pos: ensureRealFloat(t_pos),\n unit_name,\n };\n}\n"],"names":["ColumnAxisType","createUnitSorter","group","unitsOverlap","getUnitHeightRange","compareAgeRanges","AgeRangeRelationship","mergeAgeRanges","MergeMode","ensureArray","ensureRealFloat"],"mappings":"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"}
@@ -1,20 +1,28 @@
1
1
  import { BaseUnit, UnitLong } from '@macrostrat/api-types';
2
2
  import { ColumnAxisType } from '@macrostrat/column-components';
3
3
  import { ExtUnit, SectionInfo, StratigraphicPackage } from './types';
4
- export type UnitWithDefinedOverlap<T extends BaseUnit> = T & {
5
- overlap: UnitOverlapInfo<T>;
4
+ export type UnitWithLayoutParameters<T extends BaseUnit> = T & {
5
+ layout: UnitLayoutData<T>;
6
6
  };
7
- type UnitOverlapInfo<T extends BaseUnit> = {
7
+ export type UnitWithLayoutHints<T extends BaseUnit> = T & {
8
+ layoutHints?: UnitLayoutHints;
9
+ };
10
+ interface UnitLayoutInfo {
8
11
  column: number;
9
12
  nColumns: number;
13
+ }
14
+ type UnitLayoutHints = Partial<UnitLayoutInfo>;
15
+ type UnitLayoutData<T extends BaseUnit> = UnitLayoutInfo & {
16
+ totalColumns: number;
10
17
  unitGroup: UnitGroup<T>;
11
18
  };
12
19
  declare class UnitGroup<T extends BaseUnit> implements StratigraphicPackage {
13
20
  units: T[];
14
- constructor(units: T[]);
21
+ axisType: ColumnAxisType;
22
+ constructor(units: T[], axisType?: ColumnAxisType);
15
23
  get t_age(): number;
16
24
  get b_age(): number;
17
- getUnits(): UnitWithDefinedOverlap<T>[];
25
+ getUnits(): UnitWithLayoutParameters<T>[];
18
26
  }
19
27
  export declare function preprocessUnits<T extends UnitLong = UnitLong>(section: SectionInfo<T>, axisType?: ColumnAxisType): ExtUnit[];
20
28
  export declare function enhanceUnitWithClippingPosition(section: any, d: any, axisType: any): any;