@macrostrat/column-views 1.0.3 → 2.0.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.
- package/CHANGELOG.md +21 -0
- package/dist/esm/{data-area.daff8da0.js → column-views.062d164a.js} +1 -1
- package/dist/esm/{data-area.daff8da0.js.map → column-views.062d164a.js.map} +1 -1
- package/dist/esm/{data-provider.1506eb64.js → column-views.07521ce6.js} +1 -1
- package/dist/esm/column-views.07521ce6.js.map +1 -0
- package/dist/esm/column-views.134b2e2a.js +9 -0
- package/dist/esm/column-views.134b2e2a.js.map +1 -0
- package/dist/esm/{selection-popover.module.0a701258.css → column-views.16cc7a58.css} +11 -8
- package/dist/esm/column-views.16cc7a58.css.map +1 -0
- package/dist/esm/column-views.1c589cc4.js +141 -0
- package/dist/esm/column-views.1c589cc4.js.map +1 -0
- package/dist/esm/column-views.1ece326b.js +420 -0
- package/dist/esm/column-views.1ece326b.js.map +1 -0
- package/dist/esm/column-views.1edc7300.js +40 -0
- package/dist/esm/column-views.1edc7300.js.map +1 -0
- package/dist/esm/column-views.240ef158.css +159 -0
- package/dist/esm/column-views.240ef158.css.map +1 -0
- package/dist/esm/column-views.25471fe0.css +111 -0
- package/dist/esm/column-views.25471fe0.css.map +1 -0
- package/dist/esm/column-views.28ee5f43.js +288 -0
- package/dist/esm/column-views.28ee5f43.js.map +1 -0
- package/dist/esm/column-views.30646469.js +118 -0
- package/dist/esm/column-views.30646469.js.map +1 -0
- package/dist/esm/column-views.338a7a83.css +134 -0
- package/dist/esm/column-views.338a7a83.css.map +1 -0
- package/dist/esm/column-views.33dbc778.js +95 -0
- package/dist/esm/column-views.33dbc778.js.map +1 -0
- package/dist/esm/column-views.343b5aa2.js +98 -0
- package/dist/esm/column-views.343b5aa2.js.map +1 -0
- package/dist/esm/column-views.34636ec7.js +104 -0
- package/dist/esm/column-views.34636ec7.js.map +1 -0
- package/dist/esm/column-views.3465c8ad.js +16 -0
- package/dist/esm/column-views.3465c8ad.js.map +1 -0
- package/dist/esm/column-views.39657bbf.js +55 -0
- package/dist/esm/column-views.39657bbf.js.map +1 -0
- package/dist/esm/column-views.3b972a9b.css +4 -0
- package/dist/esm/{composite.module.69a38fd1.css.map → column-views.3b972a9b.css.map} +1 -1
- package/dist/esm/column-views.3bec70d6.js +167 -0
- package/dist/esm/column-views.3bec70d6.js.map +1 -0
- package/dist/esm/column-views.3de4dc43.js +66 -0
- package/dist/esm/column-views.3de4dc43.js.map +1 -0
- package/dist/esm/{column.db058031.js → column-views.3f4db8cb.js} +8 -8
- package/dist/esm/column-views.3f4db8cb.js.map +1 -0
- package/dist/esm/column-views.40c1f361.js +206 -0
- package/dist/esm/column-views.40c1f361.js.map +1 -0
- package/dist/esm/{composite.module.0638e7ee.js → column-views.40cd59b5.js} +1 -1
- package/dist/esm/{composite.module.0638e7ee.js.map → column-views.40cd59b5.js.map} +1 -1
- package/dist/esm/column-views.4637f4ce.js +46 -0
- package/dist/esm/column-views.4637f4ce.js.map +1 -0
- package/dist/esm/column-views.4c4c5bac.js +163 -0
- package/dist/esm/column-views.4c4c5bac.js.map +1 -0
- package/dist/esm/column-views.4d374a35.js +19 -0
- package/dist/esm/column-views.4d374a35.js.map +1 -0
- package/dist/esm/column-views.57c4ddf3.js +125 -0
- package/dist/esm/column-views.57c4ddf3.js.map +1 -0
- package/dist/esm/column-views.58b82432.js +58 -0
- package/dist/esm/column-views.58b82432.js.map +1 -0
- package/dist/esm/column-views.5b2635fd.js +13 -0
- package/dist/esm/column-views.5b2635fd.js.map +1 -0
- package/dist/esm/column-views.5c02fee2.js +258 -0
- package/dist/esm/column-views.5c02fee2.js.map +1 -0
- package/dist/esm/column-views.5cc09c1d.js +152 -0
- package/dist/esm/column-views.5cc09c1d.js.map +1 -0
- package/dist/esm/{names.f6a4e9a4.js → column-views.650bd9f5.js} +5 -5
- package/dist/esm/column-views.650bd9f5.js.map +1 -0
- package/dist/esm/column-views.68409161.js +89 -0
- package/dist/esm/column-views.68409161.js.map +1 -0
- package/dist/esm/column-views.6a954628.js +70 -0
- package/dist/esm/column-views.6a954628.js.map +1 -0
- package/dist/esm/column-views.6c670c13.css +26 -0
- package/dist/esm/column-views.6c670c13.css.map +1 -0
- package/dist/esm/column-views.6d6b73b4.js +55 -0
- package/dist/esm/column-views.6d6b73b4.js.map +1 -0
- package/dist/esm/column-views.86c4b36f.js +222 -0
- package/dist/esm/column-views.86c4b36f.js.map +1 -0
- package/dist/esm/column-views.88de2cee.js +9 -0
- package/dist/esm/column-views.88de2cee.js.map +1 -0
- package/dist/esm/{resolvers.b5c82b78.js → column-views.8dd877af.js} +16 -3
- package/dist/esm/column-views.8dd877af.js.map +1 -0
- package/dist/esm/column-views.8e51206e.js +143 -0
- package/dist/esm/column-views.8e51206e.js.map +1 -0
- package/dist/esm/{column.module.7f47d25a.js → column-views.94b5cca1.js} +1 -1
- package/dist/esm/{column.module.7f47d25a.js.map → column-views.94b5cca1.js.map} +1 -1
- package/dist/esm/column-views.9804efda.js +75 -0
- package/dist/esm/column-views.9804efda.js.map +1 -0
- package/dist/esm/column-views.9bdd477a.js +57 -0
- package/dist/esm/column-views.9bdd477a.js.map +1 -0
- package/dist/esm/column-views.9ced149e.js +98 -0
- package/dist/esm/column-views.9ced149e.js.map +1 -0
- package/dist/esm/{column.module.cbb072df.css → column-views.a7be0a4a.css} +1 -1
- package/dist/esm/{column.module.cbb072df.css.map → column-views.a7be0a4a.css.map} +1 -1
- package/dist/esm/column-views.a98214b2.js +9 -0
- package/dist/esm/column-views.a98214b2.js.map +1 -0
- package/dist/esm/column-views.aa9fcdba.js +221 -0
- package/dist/esm/column-views.aa9fcdba.js.map +1 -0
- package/dist/esm/{isotopes-column.7cb5cc45.js → column-views.ac9fb5e1.js} +3 -3
- package/dist/esm/{isotopes-column.7cb5cc45.js.map → column-views.ac9fb5e1.js.map} +1 -1
- package/dist/esm/column-views.b2e5f1d9.js +11 -0
- package/dist/esm/column-views.b2e5f1d9.js.map +1 -0
- package/dist/esm/column-views.b775c0a3.js +112 -0
- package/dist/esm/column-views.b775c0a3.js.map +1 -0
- package/dist/esm/column-views.beb54e95.js +39 -0
- package/dist/esm/column-views.beb54e95.js.map +1 -0
- package/dist/esm/{core.4db8e452.js → column-views.c58aad3f.js} +15 -16
- package/dist/esm/column-views.c58aad3f.js.map +1 -0
- package/dist/esm/{carbon-isotopes.3b502a9e.js → column-views.d1ad48eb.js} +4 -4
- package/dist/esm/{carbon-isotopes.3b502a9e.js.map → column-views.d1ad48eb.js.map} +1 -1
- package/dist/esm/column-views.dc42f5d0.js +48 -0
- package/dist/esm/column-views.dc42f5d0.js.map +1 -0
- package/dist/esm/column-views.dff52504.js +22 -0
- package/dist/esm/column-views.dff52504.js.map +1 -0
- package/dist/esm/column-views.e2b9c23c.css +33 -0
- package/dist/esm/column-views.e2b9c23c.css.map +1 -0
- package/dist/esm/column-views.e40b8bfb.css +14 -0
- package/dist/esm/column-views.e40b8bfb.css.map +1 -0
- package/dist/esm/column-views.e77b9d6b.js +170 -0
- package/dist/esm/column-views.e77b9d6b.js.map +1 -0
- package/dist/esm/column-views.ea237aa8.js +52 -0
- package/dist/esm/column-views.ea237aa8.js.map +1 -0
- package/dist/esm/column-views.edf77621.js +22 -0
- package/dist/esm/column-views.edf77621.js.map +1 -0
- package/dist/esm/column-views.f1efcc2c.js +9 -0
- package/dist/esm/column-views.f1efcc2c.js.map +1 -0
- package/dist/esm/column-views.f2e5a72f.js +66 -0
- package/dist/esm/column-views.f2e5a72f.js.map +1 -0
- package/dist/esm/column-views.f71ec4b4.js +127 -0
- package/dist/esm/column-views.f71ec4b4.js.map +1 -0
- package/dist/esm/column-views.f9dab157.js +3 -0
- package/dist/esm/column-views.f9dab157.js.map +1 -0
- package/dist/esm/column-views.fa6f3292.js +215 -0
- package/dist/esm/column-views.fa6f3292.js.map +1 -0
- package/dist/esm/column-views.fc2b4611.js +9 -0
- package/dist/esm/column-views.fc2b4611.js.map +1 -0
- package/dist/esm/index.d.ts +797 -130
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +13 -9
- package/dist/esm/index.js.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/package.json +29 -13
- package/src/age-axis.module.sass +27 -0
- package/src/age-axis.ts +82 -19
- package/src/age-model-overlay.module.sass +11 -0
- package/src/age-model-overlay.ts +73 -0
- package/src/carbon-isotopes/column.ts +4 -2
- package/src/carbon-isotopes/core.ts +9 -6
- package/src/carbon-isotopes/data-provider.ts +3 -3
- package/src/column.module.sass +58 -49
- package/src/column.ts +149 -135
- package/src/correlation-chart/hash-string.ts +57 -0
- package/src/correlation-chart/index.ts +2 -0
- package/src/correlation-chart/main.module.sass +122 -0
- package/src/correlation-chart/main.ts +305 -0
- package/src/correlation-chart/prepare-data.ts +217 -0
- package/src/correlation-chart/stories/correlation-chart.stories.ts +192 -0
- package/src/correlation-chart/stories/stories.module.sass +18 -0
- package/src/correlation-chart/unconformity-line.module.sass +8 -0
- package/src/correlation-chart/unconformity-line.ts +25 -0
- package/src/data-provider/base.ts +388 -0
- package/src/data-provider/column-nav.ts +53 -0
- package/src/data-provider/fetch.ts +231 -0
- package/src/data-provider/index.ts +4 -0
- package/src/data-provider/store.ts +72 -0
- package/src/index.ts +5 -3
- package/src/maps/_shared/index.ts +2 -0
- package/src/maps/_shared/inset-map.stories.ts +20 -0
- package/src/maps/_shared/inset-map.ts +87 -0
- package/src/maps/_shared/styles.ts +57 -0
- package/src/maps/column-correlation/column-correlation-map.stories.ts +96 -0
- package/src/maps/column-correlation/core.ts +225 -0
- package/src/maps/column-correlation/index.ts +2 -0
- package/src/maps/column-correlation/state.ts +192 -0
- package/src/maps/column-navigation/index.ts +2 -0
- package/src/maps/column-navigation/mapbox/column-navigation-map.stories.ts +102 -0
- package/src/maps/column-navigation/mapbox/index.ts +219 -0
- package/src/maps/column-navigation/mapbox/keyboard-navigation.ts +119 -0
- package/src/maps/column-navigation/mapbox/state.ts +109 -0
- package/src/maps/column-navigation/svg/column-navigation-svg-map.stories.ts +46 -0
- package/src/{map → maps/column-navigation/svg}/index.ts +18 -6
- package/src/{map → maps/column-navigation/svg}/layers.ts +28 -109
- package/src/maps/column-navigation/utils/keyboard-navigation.ts +58 -0
- package/src/maps/index.ts +3 -0
- package/src/prepare-units/composite-scale.ts +353 -0
- package/src/prepare-units/helpers.ts +226 -0
- package/src/prepare-units/index.ts +177 -0
- package/src/prepare-units/utils.ts +98 -0
- package/src/section.ts +264 -84
- package/src/unit-details/index.ts +2 -215
- package/src/unit-details/{index.module.sass → panel.module.sass} +39 -8
- package/src/unit-details/panel.ts +523 -0
- package/src/{selection-popover.module.sass → unit-details/popover.module.sass} +4 -2
- package/src/unit-details/popover.ts +82 -0
- package/src/units/boxes.module.sass +16 -0
- package/src/units/boxes.ts +243 -90
- package/src/units/colors.ts +64 -0
- package/src/units/composite.ts +157 -152
- package/src/units/index.ts +76 -9
- package/src/units/names.ts +8 -4
- package/src/units/resolvers.ts +12 -1
- package/src/units/selection.ts +115 -101
- package/src/units/types.ts +5 -16
- package/dist/esm/age-axis.64c509ee.js +0 -41
- package/dist/esm/age-axis.64c509ee.js.map +0 -1
- package/dist/esm/boxes.13968b76.js +0 -164
- package/dist/esm/boxes.13968b76.js.map +0 -1
- package/dist/esm/boxes.module.28873d08.css +0 -4
- package/dist/esm/boxes.module.28873d08.css.map +0 -1
- package/dist/esm/boxes.module.605bdcd6.js +0 -13
- package/dist/esm/boxes.module.605bdcd6.js.map +0 -1
- package/dist/esm/column.8dd26e1a.js +0 -149
- package/dist/esm/column.8dd26e1a.js.map +0 -1
- package/dist/esm/column.db058031.js.map +0 -1
- package/dist/esm/column.module.039d872c.css +0 -151
- package/dist/esm/column.module.039d872c.css.map +0 -1
- package/dist/esm/column.module.e632868a.js +0 -19
- package/dist/esm/column.module.e632868a.js.map +0 -1
- package/dist/esm/composite.608a4ca2.js +0 -153
- package/dist/esm/composite.608a4ca2.js.map +0 -1
- package/dist/esm/composite.module.69a38fd1.css +0 -4
- package/dist/esm/core.4db8e452.js.map +0 -1
- package/dist/esm/data-provider.1506eb64.js.map +0 -1
- package/dist/esm/helpers.8a03cfc7.js +0 -94
- package/dist/esm/helpers.8a03cfc7.js.map +0 -1
- package/dist/esm/index.module.0844afd8.css +0 -65
- package/dist/esm/index.module.0844afd8.css.map +0 -1
- package/dist/esm/index.module.24fe3a2e.js +0 -37
- package/dist/esm/index.module.24fe3a2e.js.map +0 -1
- package/dist/esm/layers.ad5fd25d.js +0 -231
- package/dist/esm/layers.ad5fd25d.js.map +0 -1
- package/dist/esm/map.7aa26f17.js +0 -82
- package/dist/esm/map.7aa26f17.js.map +0 -1
- package/dist/esm/names.f6a4e9a4.js.map +0 -1
- package/dist/esm/providers.cd1e652f.js +0 -25
- package/dist/esm/providers.cd1e652f.js.map +0 -1
- package/dist/esm/resolvers.b5c82b78.js.map +0 -1
- package/dist/esm/section.cc1b1100.js +0 -118
- package/dist/esm/section.cc1b1100.js.map +0 -1
- package/dist/esm/selection-popover.17d1bd55.js +0 -60
- package/dist/esm/selection-popover.17d1bd55.js.map +0 -1
- package/dist/esm/selection-popover.module.0a701258.css.map +0 -1
- package/dist/esm/selection-popover.module.e22f5183.js +0 -22
- package/dist/esm/selection-popover.module.e22f5183.js.map +0 -1
- package/dist/esm/selection.bbf26d0e.js +0 -117
- package/dist/esm/selection.bbf26d0e.js.map +0 -1
- package/dist/esm/store.25eaa168.js +0 -18
- package/dist/esm/store.25eaa168.js.map +0 -1
- package/dist/esm/types.f016e032.js +0 -18
- package/dist/esm/types.f016e032.js.map +0 -1
- package/dist/esm/unit-details.56ff8b70.js +0 -209
- package/dist/esm/unit-details.56ff8b70.js.map +0 -1
- package/dist/esm/units.361088d4.js +0 -42
- package/dist/esm/units.361088d4.js.map +0 -1
- package/src/helpers.ts +0 -133
- package/src/providers.ts +0 -34
- package/src/selection-popover.ts +0 -71
- package/src/store.ts +0 -12
- package/src/units/units.scss +0 -16
- /package/src/{map → maps/column-navigation/svg}/animated.ts +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { BaseUnit, UnitLong } from "@macrostrat/api-types";
|
|
2
|
+
import { group } from "d3-array";
|
|
3
|
+
import { ColumnAxisType } from "@macrostrat/column-components";
|
|
4
|
+
import {
|
|
5
|
+
unitsOverlap,
|
|
6
|
+
getUnitHeightRange,
|
|
7
|
+
createUnitSorter,
|
|
8
|
+
ensureArray,
|
|
9
|
+
ensureRealFloat,
|
|
10
|
+
} from "./utils";
|
|
11
|
+
|
|
12
|
+
const dt = 0.001;
|
|
13
|
+
|
|
14
|
+
export interface StratigraphicPackage {
|
|
15
|
+
/** A collection of stratigraphic information organized in time, corresponding
|
|
16
|
+
* to single or multiple columns. */
|
|
17
|
+
t_age: number;
|
|
18
|
+
b_age: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SectionInfo<T extends UnitLong = ExtUnit>
|
|
22
|
+
extends StratigraphicPackage {
|
|
23
|
+
/** A time-bounded part of a single stratigraphic column. */
|
|
24
|
+
section_id: number | number[];
|
|
25
|
+
units: T[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ExtUnit extends UnitLong {
|
|
29
|
+
bottomOverlap: boolean;
|
|
30
|
+
overlappingUnits: number[];
|
|
31
|
+
column?: number;
|
|
32
|
+
/* Positions (ages or heights) where the unit is clipped to its containing section.
|
|
33
|
+
* This is relevant if we are filtering by age/height/depth range.
|
|
34
|
+
*/
|
|
35
|
+
t_clip_pos?: number;
|
|
36
|
+
b_clip_pos?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function preprocessUnits<T extends UnitLong = UnitLong>(
|
|
40
|
+
section: SectionInfo<T>,
|
|
41
|
+
axisType: ColumnAxisType = ColumnAxisType.AGE
|
|
42
|
+
): ExtUnit[] {
|
|
43
|
+
/** Preprocess units to add overlapping units and columns. */
|
|
44
|
+
const units = section.units;
|
|
45
|
+
let divisions = units.map((...args) => extendDivision(...args, axisType));
|
|
46
|
+
for (let d of divisions) {
|
|
47
|
+
const overlappingUnits = divisions.filter((u) =>
|
|
48
|
+
d.overlappingUnits.includes(u.unit_id)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Overlapping columns
|
|
52
|
+
const columns = overlappingUnits.map((d) => d.column);
|
|
53
|
+
|
|
54
|
+
if (columns.includes(d.column)) {
|
|
55
|
+
let col = 0;
|
|
56
|
+
// Go through columns to find a better index
|
|
57
|
+
while (columns.includes(col)) {
|
|
58
|
+
col++;
|
|
59
|
+
}
|
|
60
|
+
d.column = col;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// If unit overlaps the edges of a section, set the clip positions
|
|
64
|
+
if (axisType == ColumnAxisType.AGE) {
|
|
65
|
+
const [b_pos, t_pos] = getUnitHeightRange(d, axisType);
|
|
66
|
+
if (b_pos > section.b_age) {
|
|
67
|
+
d.b_clip_pos = section.b_age;
|
|
68
|
+
}
|
|
69
|
+
if (t_pos < section.t_age) {
|
|
70
|
+
d.t_clip_pos = section.t_age;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (axisType != ColumnAxisType.AGE) {
|
|
76
|
+
return preprocessSectionUnits(divisions, axisType);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return divisions;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function extendDivision(
|
|
83
|
+
unit: UnitLong,
|
|
84
|
+
i: number,
|
|
85
|
+
divisions: UnitLong[],
|
|
86
|
+
axisType: ColumnAxisType = ColumnAxisType.AGE
|
|
87
|
+
): ExtUnit {
|
|
88
|
+
const overlappingUnits = divisions.filter(
|
|
89
|
+
(d) => d.unit_id != unit.unit_id && unitsOverlap(unit, d, axisType)
|
|
90
|
+
);
|
|
91
|
+
const u_pos = getUnitHeightRange(unit, axisType);
|
|
92
|
+
const bottomOverlap = overlappingUnits.some((d) => {
|
|
93
|
+
const d_pos = getUnitHeightRange(d, axisType);
|
|
94
|
+
// Check if the unit is below the current unit
|
|
95
|
+
return d_pos[0] < u_pos[0] + dt;
|
|
96
|
+
});
|
|
97
|
+
let column = 0;
|
|
98
|
+
if (overlappingUnits.length == 1) {
|
|
99
|
+
column = 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
...unit,
|
|
104
|
+
bottomOverlap,
|
|
105
|
+
column,
|
|
106
|
+
overlappingUnits: overlappingUnits.map((d) => d.unit_id),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function groupUnitsIntoSections<T extends UnitLong>(
|
|
111
|
+
units: T[],
|
|
112
|
+
axisType: ColumnAxisType = ColumnAxisType.AGE
|
|
113
|
+
): SectionInfo<T>[] {
|
|
114
|
+
let groups = Array.from(group(units, (d) => d.section_id));
|
|
115
|
+
const unitComparator = createUnitSorter(axisType);
|
|
116
|
+
|
|
117
|
+
const groups1 = groups.map(([section_id, sectionUnits]) => {
|
|
118
|
+
const [b_age, t_age] = getSectionAgeRange(sectionUnits);
|
|
119
|
+
// sort units by position
|
|
120
|
+
sectionUnits.sort(unitComparator);
|
|
121
|
+
return { section_id, t_age, b_age, units: sectionUnits };
|
|
122
|
+
});
|
|
123
|
+
// Sort sections by increasing top age, then increasing bottom age.
|
|
124
|
+
// Sections have no relative ordinal position other than age...
|
|
125
|
+
const compareSections = createUnitSorter(ColumnAxisType.AGE) as (
|
|
126
|
+
a: StratigraphicPackage,
|
|
127
|
+
b: StratigraphicPackage
|
|
128
|
+
) => number;
|
|
129
|
+
groups1.sort(compareSections);
|
|
130
|
+
return groups1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getSectionAgeRange(units: BaseUnit[]): [number, number] {
|
|
134
|
+
/** Get the overall age range of a set of units. */
|
|
135
|
+
const t_ages = units.map((d) => d.t_age);
|
|
136
|
+
const b_ages = units.map((d) => d.b_age);
|
|
137
|
+
const t_age = Math.min(...t_ages);
|
|
138
|
+
const b_age = Math.max(...b_ages);
|
|
139
|
+
return [b_age, t_age];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function mergeOverlappingSections<T extends UnitLong>(
|
|
143
|
+
sections: SectionInfo<T>[]
|
|
144
|
+
): SectionInfo<T>[] {
|
|
145
|
+
/** Columns can have sections that overlap in time. Here, we merge overlapping
|
|
146
|
+
* sections into a single section to correctly render gap-bound packages.
|
|
147
|
+
*/
|
|
148
|
+
const [firstSection, ...rest] = sections;
|
|
149
|
+
const newSections = [firstSection];
|
|
150
|
+
for (const section of rest) {
|
|
151
|
+
const lastSection = newSections[newSections.length - 1];
|
|
152
|
+
if (
|
|
153
|
+
lastSection.b_age < section.t_age ||
|
|
154
|
+
lastSection.t_age > section.b_age
|
|
155
|
+
) {
|
|
156
|
+
// No overlap, add the section as normal
|
|
157
|
+
newSections.push(section);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Overlap, merge the sections
|
|
162
|
+
lastSection.section_id = [
|
|
163
|
+
...ensureArray(lastSection.section_id),
|
|
164
|
+
...ensureArray(section.section_id),
|
|
165
|
+
];
|
|
166
|
+
lastSection.units.push(...section.units);
|
|
167
|
+
lastSection.b_age = Math.max(lastSection.b_age, section.b_age);
|
|
168
|
+
lastSection.t_age = Math.min(lastSection.t_age, section.t_age);
|
|
169
|
+
}
|
|
170
|
+
return newSections;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function preprocessSectionUnits(
|
|
174
|
+
units: UnitLong[],
|
|
175
|
+
axisType: ColumnAxisType = ColumnAxisType.DEPTH
|
|
176
|
+
): ExtUnit[] {
|
|
177
|
+
/** Preprocess units for a "section" column type, which is guaranteed to be simpler. */
|
|
178
|
+
// We have to assume the units are ordered...
|
|
179
|
+
let thickness = 0;
|
|
180
|
+
return units.map((unit, i) => {
|
|
181
|
+
let u1 = preprocessSectionUnit(unit, i, units, thickness, axisType);
|
|
182
|
+
thickness += Math.abs(u1.t_pos - u1.b_pos);
|
|
183
|
+
return u1;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function preprocessSectionUnit(
|
|
188
|
+
unit: UnitLong,
|
|
189
|
+
i: number,
|
|
190
|
+
units: UnitLong[],
|
|
191
|
+
accumulatedThickness: number = 0,
|
|
192
|
+
axisType: ColumnAxisType = ColumnAxisType.DEPTH
|
|
193
|
+
): ExtUnit {
|
|
194
|
+
/** Preprocess a single unit for a "section" column type.
|
|
195
|
+
* No provision for overlapping units.
|
|
196
|
+
* */
|
|
197
|
+
|
|
198
|
+
let b_pos = unit.b_pos;
|
|
199
|
+
let t_pos = unit.t_pos;
|
|
200
|
+
|
|
201
|
+
if (b_pos == t_pos && axisType == ColumnAxisType.ORDINAL) {
|
|
202
|
+
t_pos = t_pos - 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let unit_name = unit.unit_name;
|
|
206
|
+
|
|
207
|
+
// eODP columns sometimes have overlapping core sections, which are encoded in the name field
|
|
208
|
+
// Match eODP section names
|
|
209
|
+
const match = unit.unit_name.match(/^(\d+(\.\d+)?)-(\d+(\.\d+)?): (.+)/);
|
|
210
|
+
|
|
211
|
+
if (match) {
|
|
212
|
+
// These values should already be set if we've used the show_positions flag
|
|
213
|
+
t_pos ??= ensureRealFloat(match[1]);
|
|
214
|
+
b_pos ??= ensureRealFloat(match[3]);
|
|
215
|
+
unit_name = match[5];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
...unit,
|
|
220
|
+
b_pos: ensureRealFloat(b_pos),
|
|
221
|
+
t_pos: ensureRealFloat(t_pos),
|
|
222
|
+
unit_name,
|
|
223
|
+
bottomOverlap: false,
|
|
224
|
+
overlappingUnits: [],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSectionAgeRange,
|
|
3
|
+
groupUnitsIntoSections,
|
|
4
|
+
mergeOverlappingSections,
|
|
5
|
+
preprocessUnits,
|
|
6
|
+
} from "./helpers";
|
|
7
|
+
import { ColumnAxisType } from "@macrostrat/column-components";
|
|
8
|
+
import { useMemo } from "react";
|
|
9
|
+
import type { ExtUnit } from "./helpers";
|
|
10
|
+
import { UnitLong } from "@macrostrat/api-types";
|
|
11
|
+
import {
|
|
12
|
+
collapseUnconformitiesByPixelHeight,
|
|
13
|
+
ColumnScaleOptions,
|
|
14
|
+
CompositeColumnData,
|
|
15
|
+
computeSectionHeights,
|
|
16
|
+
finalizeSectionHeights,
|
|
17
|
+
PackageLayoutData,
|
|
18
|
+
} from "./composite-scale";
|
|
19
|
+
import type { SectionInfo } from "./helpers";
|
|
20
|
+
import { agesOverlap, unitsOverlap } from "./utils";
|
|
21
|
+
|
|
22
|
+
export * from "./utils";
|
|
23
|
+
export { preprocessUnits, groupUnitsIntoSections };
|
|
24
|
+
|
|
25
|
+
export interface PrepareColumnOptions extends ColumnScaleOptions {
|
|
26
|
+
axisType: ColumnAxisType;
|
|
27
|
+
t_age?: number;
|
|
28
|
+
b_age?: number;
|
|
29
|
+
mergeSections?: MergeSectionsMode;
|
|
30
|
+
collapseSmallUnconformities?: boolean | number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export enum MergeSectionsMode {
|
|
34
|
+
ALL = "all",
|
|
35
|
+
OVERLAPPING = "overlapping",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PreparedColumnData extends CompositeColumnData {
|
|
39
|
+
sections: PackageLayoutData[];
|
|
40
|
+
units: ExtUnit[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function usePreparedColumnUnits(
|
|
44
|
+
data: UnitLong[],
|
|
45
|
+
options: PrepareColumnOptions
|
|
46
|
+
): PreparedColumnData {
|
|
47
|
+
/** This function wraps and memoizes all preparation steps for converting
|
|
48
|
+
* an array of units from the /units route to a form ready for usage.
|
|
49
|
+
*/
|
|
50
|
+
return useMemo(() => {
|
|
51
|
+
return prepareColumnUnits(data, options);
|
|
52
|
+
}, [data, ...Object.values(options)]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function prepareColumnUnits(
|
|
56
|
+
units: UnitLong[],
|
|
57
|
+
options: PrepareColumnOptions
|
|
58
|
+
): PreparedColumnData {
|
|
59
|
+
/** Prepare units for rendering into Macrostrat columns */
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
t_age,
|
|
63
|
+
b_age,
|
|
64
|
+
mergeSections = MergeSectionsMode.OVERLAPPING,
|
|
65
|
+
axisType,
|
|
66
|
+
unconformityHeight,
|
|
67
|
+
collapseSmallUnconformities = false,
|
|
68
|
+
} = options;
|
|
69
|
+
|
|
70
|
+
/** Prototype filtering to age range */
|
|
71
|
+
let units1 = units.filter((d) => {
|
|
72
|
+
// Filter units by t_age and b_age, inclusive
|
|
73
|
+
return agesOverlap(d, { t_age, b_age });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let sections0: SectionInfo<UnitLong>[];
|
|
77
|
+
if (
|
|
78
|
+
mergeSections == MergeSectionsMode.ALL &&
|
|
79
|
+
axisType != ColumnAxisType.ORDINAL
|
|
80
|
+
) {
|
|
81
|
+
// For the "merge sections" mode, we need to create a single section
|
|
82
|
+
const [b_unit_age, t_unit_age] = getSectionAgeRange(units1);
|
|
83
|
+
sections0 = [
|
|
84
|
+
{
|
|
85
|
+
section_id: 0,
|
|
86
|
+
/**
|
|
87
|
+
* If ages limits are directly specified, use them to define the section bounds.
|
|
88
|
+
* */
|
|
89
|
+
t_age: t_age ?? t_unit_age,
|
|
90
|
+
b_age: b_age ?? b_unit_age,
|
|
91
|
+
units: units1,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
} else {
|
|
95
|
+
sections0 = groupUnitsIntoSections(units1, axisType);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Merging overlapping sections really only makes sense for age/height/depth
|
|
99
|
+
* columns. Ordinal columns are numbered by section so merging them
|
|
100
|
+
* results in collisions.
|
|
101
|
+
*/
|
|
102
|
+
let sections = sections0;
|
|
103
|
+
if (
|
|
104
|
+
mergeSections == MergeSectionsMode.OVERLAPPING &&
|
|
105
|
+
axisType != ColumnAxisType.ORDINAL
|
|
106
|
+
) {
|
|
107
|
+
sections = mergeOverlappingSections(sections);
|
|
108
|
+
}
|
|
109
|
+
// Filter out undefined sections just in case
|
|
110
|
+
sections = sections.filter((d) => d != null);
|
|
111
|
+
|
|
112
|
+
/* Now that we are done merging sections, we can ensure that our sections
|
|
113
|
+
* are correctly limited to the t_age and b_age applied to the overall column.
|
|
114
|
+
*/
|
|
115
|
+
sections = sections.map((section) => {
|
|
116
|
+
const { t_age, b_age } = section;
|
|
117
|
+
return {
|
|
118
|
+
...section,
|
|
119
|
+
t_age: Math.max(t_age, options.t_age ?? -Infinity),
|
|
120
|
+
b_age: Math.min(b_age, options.b_age ?? Infinity),
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
/* Compute pixel scales etc. for sections
|
|
125
|
+
* We need to do this now to determine which unconformities
|
|
126
|
+
* are small enough to collapse.
|
|
127
|
+
*/
|
|
128
|
+
let sectionsWithScales = computeSectionHeights(sections, options);
|
|
129
|
+
|
|
130
|
+
if (collapseSmallUnconformities ?? false) {
|
|
131
|
+
let threshold = unconformityHeight ?? 30;
|
|
132
|
+
if (typeof collapseSmallUnconformities == "number") {
|
|
133
|
+
threshold = collapseSmallUnconformities;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
sectionsWithScales = collapseUnconformitiesByPixelHeight(
|
|
137
|
+
sectionsWithScales,
|
|
138
|
+
threshold,
|
|
139
|
+
options
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Reconstitute the units so that they are sorted by section.
|
|
144
|
+
* This is mostly important so that unit keyboard navigation
|
|
145
|
+
* predictably selects adjacent units.
|
|
146
|
+
*/
|
|
147
|
+
const units2 = sectionsWithScales.reduce((acc, group) => {
|
|
148
|
+
const { units } = group;
|
|
149
|
+
for (const unit of units) {
|
|
150
|
+
acc.push(unit);
|
|
151
|
+
}
|
|
152
|
+
return acc;
|
|
153
|
+
}, []);
|
|
154
|
+
|
|
155
|
+
/** Prepare section scale information using groups */
|
|
156
|
+
const { totalHeight, sections: sections2 } = finalizeSectionHeights(
|
|
157
|
+
sectionsWithScales,
|
|
158
|
+
unconformityHeight
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
/** For each section, find units that are overlapping.
|
|
162
|
+
* We do this after merging sections so that we can
|
|
163
|
+
* handle cases where there are overlapping units across sections
|
|
164
|
+
* */
|
|
165
|
+
const sectionsOut = sections2.map((section) => {
|
|
166
|
+
return {
|
|
167
|
+
...section,
|
|
168
|
+
units: preprocessUnits(section, axisType),
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
units: units2,
|
|
174
|
+
totalHeight,
|
|
175
|
+
sections: sectionsOut,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { BaseUnit } from "@macrostrat/api-types";
|
|
2
|
+
import {
|
|
3
|
+
AgeRangeRelationship,
|
|
4
|
+
compareAgeRanges,
|
|
5
|
+
} from "@macrostrat/stratigraphy-utils";
|
|
6
|
+
import { ColumnAxisType } from "@macrostrat/column-components";
|
|
7
|
+
import { StratigraphicPackage } from "./helpers";
|
|
8
|
+
|
|
9
|
+
const dt = 0.001;
|
|
10
|
+
|
|
11
|
+
interface UnitsOverlap {
|
|
12
|
+
(
|
|
13
|
+
a: StratigraphicPackage,
|
|
14
|
+
b: StratigraphicPackage,
|
|
15
|
+
axisType?: ColumnAxisType.AGE,
|
|
16
|
+
tolerance?: number
|
|
17
|
+
): boolean;
|
|
18
|
+
(
|
|
19
|
+
a: BaseUnit,
|
|
20
|
+
b: BaseUnit,
|
|
21
|
+
axisType: ColumnAxisType,
|
|
22
|
+
tolerance?: number
|
|
23
|
+
): boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const unitsOverlap: UnitsOverlap = function (
|
|
27
|
+
a,
|
|
28
|
+
b,
|
|
29
|
+
axisType: ColumnAxisType = ColumnAxisType.AGE,
|
|
30
|
+
tolerance: number = 0.001
|
|
31
|
+
): boolean {
|
|
32
|
+
const rel = compareAgeRanges(
|
|
33
|
+
getUnitHeightRange(a, axisType),
|
|
34
|
+
getUnitHeightRange(b, axisType),
|
|
35
|
+
tolerance
|
|
36
|
+
);
|
|
37
|
+
return rel != AgeRangeRelationship.Disjoint;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** A more permissive overlap function in the age space */
|
|
41
|
+
export function agesOverlap(
|
|
42
|
+
a: StratigraphicPackage,
|
|
43
|
+
b: StratigraphicPackage,
|
|
44
|
+
tolerance: number = dt
|
|
45
|
+
): boolean {
|
|
46
|
+
return unitsOverlap(a, b, ColumnAxisType.AGE, tolerance);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface PossiblyClippedUnit extends BaseUnit {
|
|
50
|
+
// Internally created clipped positions
|
|
51
|
+
t_clip_pos?: number;
|
|
52
|
+
b_clip_pos?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getUnitHeightRange(
|
|
56
|
+
unit: PossiblyClippedUnit,
|
|
57
|
+
axisType: ColumnAxisType
|
|
58
|
+
): [number, number] {
|
|
59
|
+
switch (axisType) {
|
|
60
|
+
case ColumnAxisType.AGE:
|
|
61
|
+
return [unit.b_clip_pos ?? unit.b_age, unit.t_clip_pos ?? unit.t_age];
|
|
62
|
+
case ColumnAxisType.DEPTH:
|
|
63
|
+
case ColumnAxisType.ORDINAL:
|
|
64
|
+
case ColumnAxisType.HEIGHT:
|
|
65
|
+
return [unit.b_clip_pos ?? unit.b_pos, unit.t_clip_pos ?? unit.t_pos];
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`Unknown axis type: ${axisType}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const createUnitSorter = (axisType: ColumnAxisType) => {
|
|
72
|
+
return (a: BaseUnit, b: BaseUnit) => {
|
|
73
|
+
const a_pos = getUnitHeightRange(a, axisType);
|
|
74
|
+
const b_pos = getUnitHeightRange(b, axisType);
|
|
75
|
+
const d_top = a_pos[1] - b_pos[1];
|
|
76
|
+
if (d_top != 0) {
|
|
77
|
+
return d_top;
|
|
78
|
+
}
|
|
79
|
+
return a_pos[0] - b_pos[0];
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export function ensureArray<T>(x: T | T[]): T[] {
|
|
84
|
+
if (Array.isArray(x)) {
|
|
85
|
+
return x;
|
|
86
|
+
}
|
|
87
|
+
return [x];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function ensureRealFloat(x: number | string | null): number | null {
|
|
91
|
+
if (typeof x == "string") {
|
|
92
|
+
x = parseFloat(x);
|
|
93
|
+
}
|
|
94
|
+
if (isNaN(x)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return x;
|
|
98
|
+
}
|