@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.
Files changed (258) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/esm/{data-area.daff8da0.js → column-views.062d164a.js} +1 -1
  3. package/dist/esm/{data-area.daff8da0.js.map → column-views.062d164a.js.map} +1 -1
  4. package/dist/esm/{data-provider.1506eb64.js → column-views.07521ce6.js} +1 -1
  5. package/dist/esm/column-views.07521ce6.js.map +1 -0
  6. package/dist/esm/column-views.134b2e2a.js +9 -0
  7. package/dist/esm/column-views.134b2e2a.js.map +1 -0
  8. package/dist/esm/{selection-popover.module.0a701258.css → column-views.16cc7a58.css} +11 -8
  9. package/dist/esm/column-views.16cc7a58.css.map +1 -0
  10. package/dist/esm/column-views.1c589cc4.js +141 -0
  11. package/dist/esm/column-views.1c589cc4.js.map +1 -0
  12. package/dist/esm/column-views.1ece326b.js +420 -0
  13. package/dist/esm/column-views.1ece326b.js.map +1 -0
  14. package/dist/esm/column-views.1edc7300.js +40 -0
  15. package/dist/esm/column-views.1edc7300.js.map +1 -0
  16. package/dist/esm/column-views.240ef158.css +159 -0
  17. package/dist/esm/column-views.240ef158.css.map +1 -0
  18. package/dist/esm/column-views.25471fe0.css +111 -0
  19. package/dist/esm/column-views.25471fe0.css.map +1 -0
  20. package/dist/esm/column-views.28ee5f43.js +288 -0
  21. package/dist/esm/column-views.28ee5f43.js.map +1 -0
  22. package/dist/esm/column-views.30646469.js +118 -0
  23. package/dist/esm/column-views.30646469.js.map +1 -0
  24. package/dist/esm/column-views.338a7a83.css +134 -0
  25. package/dist/esm/column-views.338a7a83.css.map +1 -0
  26. package/dist/esm/column-views.33dbc778.js +95 -0
  27. package/dist/esm/column-views.33dbc778.js.map +1 -0
  28. package/dist/esm/column-views.343b5aa2.js +98 -0
  29. package/dist/esm/column-views.343b5aa2.js.map +1 -0
  30. package/dist/esm/column-views.34636ec7.js +104 -0
  31. package/dist/esm/column-views.34636ec7.js.map +1 -0
  32. package/dist/esm/column-views.3465c8ad.js +16 -0
  33. package/dist/esm/column-views.3465c8ad.js.map +1 -0
  34. package/dist/esm/column-views.39657bbf.js +55 -0
  35. package/dist/esm/column-views.39657bbf.js.map +1 -0
  36. package/dist/esm/column-views.3b972a9b.css +4 -0
  37. package/dist/esm/{composite.module.69a38fd1.css.map → column-views.3b972a9b.css.map} +1 -1
  38. package/dist/esm/column-views.3bec70d6.js +167 -0
  39. package/dist/esm/column-views.3bec70d6.js.map +1 -0
  40. package/dist/esm/column-views.3de4dc43.js +66 -0
  41. package/dist/esm/column-views.3de4dc43.js.map +1 -0
  42. package/dist/esm/{column.db058031.js → column-views.3f4db8cb.js} +8 -8
  43. package/dist/esm/column-views.3f4db8cb.js.map +1 -0
  44. package/dist/esm/column-views.40c1f361.js +206 -0
  45. package/dist/esm/column-views.40c1f361.js.map +1 -0
  46. package/dist/esm/{composite.module.0638e7ee.js → column-views.40cd59b5.js} +1 -1
  47. package/dist/esm/{composite.module.0638e7ee.js.map → column-views.40cd59b5.js.map} +1 -1
  48. package/dist/esm/column-views.4637f4ce.js +46 -0
  49. package/dist/esm/column-views.4637f4ce.js.map +1 -0
  50. package/dist/esm/column-views.4c4c5bac.js +163 -0
  51. package/dist/esm/column-views.4c4c5bac.js.map +1 -0
  52. package/dist/esm/column-views.4d374a35.js +19 -0
  53. package/dist/esm/column-views.4d374a35.js.map +1 -0
  54. package/dist/esm/column-views.57c4ddf3.js +125 -0
  55. package/dist/esm/column-views.57c4ddf3.js.map +1 -0
  56. package/dist/esm/column-views.58b82432.js +58 -0
  57. package/dist/esm/column-views.58b82432.js.map +1 -0
  58. package/dist/esm/column-views.5b2635fd.js +13 -0
  59. package/dist/esm/column-views.5b2635fd.js.map +1 -0
  60. package/dist/esm/column-views.5c02fee2.js +258 -0
  61. package/dist/esm/column-views.5c02fee2.js.map +1 -0
  62. package/dist/esm/column-views.5cc09c1d.js +152 -0
  63. package/dist/esm/column-views.5cc09c1d.js.map +1 -0
  64. package/dist/esm/{names.f6a4e9a4.js → column-views.650bd9f5.js} +5 -5
  65. package/dist/esm/column-views.650bd9f5.js.map +1 -0
  66. package/dist/esm/column-views.68409161.js +89 -0
  67. package/dist/esm/column-views.68409161.js.map +1 -0
  68. package/dist/esm/column-views.6a954628.js +70 -0
  69. package/dist/esm/column-views.6a954628.js.map +1 -0
  70. package/dist/esm/column-views.6c670c13.css +26 -0
  71. package/dist/esm/column-views.6c670c13.css.map +1 -0
  72. package/dist/esm/column-views.6d6b73b4.js +55 -0
  73. package/dist/esm/column-views.6d6b73b4.js.map +1 -0
  74. package/dist/esm/column-views.86c4b36f.js +222 -0
  75. package/dist/esm/column-views.86c4b36f.js.map +1 -0
  76. package/dist/esm/column-views.88de2cee.js +9 -0
  77. package/dist/esm/column-views.88de2cee.js.map +1 -0
  78. package/dist/esm/{resolvers.b5c82b78.js → column-views.8dd877af.js} +16 -3
  79. package/dist/esm/column-views.8dd877af.js.map +1 -0
  80. package/dist/esm/column-views.8e51206e.js +143 -0
  81. package/dist/esm/column-views.8e51206e.js.map +1 -0
  82. package/dist/esm/{column.module.7f47d25a.js → column-views.94b5cca1.js} +1 -1
  83. package/dist/esm/{column.module.7f47d25a.js.map → column-views.94b5cca1.js.map} +1 -1
  84. package/dist/esm/column-views.9804efda.js +75 -0
  85. package/dist/esm/column-views.9804efda.js.map +1 -0
  86. package/dist/esm/column-views.9bdd477a.js +57 -0
  87. package/dist/esm/column-views.9bdd477a.js.map +1 -0
  88. package/dist/esm/column-views.9ced149e.js +98 -0
  89. package/dist/esm/column-views.9ced149e.js.map +1 -0
  90. package/dist/esm/{column.module.cbb072df.css → column-views.a7be0a4a.css} +1 -1
  91. package/dist/esm/{column.module.cbb072df.css.map → column-views.a7be0a4a.css.map} +1 -1
  92. package/dist/esm/column-views.a98214b2.js +9 -0
  93. package/dist/esm/column-views.a98214b2.js.map +1 -0
  94. package/dist/esm/column-views.aa9fcdba.js +221 -0
  95. package/dist/esm/column-views.aa9fcdba.js.map +1 -0
  96. package/dist/esm/{isotopes-column.7cb5cc45.js → column-views.ac9fb5e1.js} +3 -3
  97. package/dist/esm/{isotopes-column.7cb5cc45.js.map → column-views.ac9fb5e1.js.map} +1 -1
  98. package/dist/esm/column-views.b2e5f1d9.js +11 -0
  99. package/dist/esm/column-views.b2e5f1d9.js.map +1 -0
  100. package/dist/esm/column-views.b775c0a3.js +112 -0
  101. package/dist/esm/column-views.b775c0a3.js.map +1 -0
  102. package/dist/esm/column-views.beb54e95.js +39 -0
  103. package/dist/esm/column-views.beb54e95.js.map +1 -0
  104. package/dist/esm/{core.4db8e452.js → column-views.c58aad3f.js} +15 -16
  105. package/dist/esm/column-views.c58aad3f.js.map +1 -0
  106. package/dist/esm/{carbon-isotopes.3b502a9e.js → column-views.d1ad48eb.js} +4 -4
  107. package/dist/esm/{carbon-isotopes.3b502a9e.js.map → column-views.d1ad48eb.js.map} +1 -1
  108. package/dist/esm/column-views.dc42f5d0.js +48 -0
  109. package/dist/esm/column-views.dc42f5d0.js.map +1 -0
  110. package/dist/esm/column-views.dff52504.js +22 -0
  111. package/dist/esm/column-views.dff52504.js.map +1 -0
  112. package/dist/esm/column-views.e2b9c23c.css +33 -0
  113. package/dist/esm/column-views.e2b9c23c.css.map +1 -0
  114. package/dist/esm/column-views.e40b8bfb.css +14 -0
  115. package/dist/esm/column-views.e40b8bfb.css.map +1 -0
  116. package/dist/esm/column-views.e77b9d6b.js +170 -0
  117. package/dist/esm/column-views.e77b9d6b.js.map +1 -0
  118. package/dist/esm/column-views.ea237aa8.js +52 -0
  119. package/dist/esm/column-views.ea237aa8.js.map +1 -0
  120. package/dist/esm/column-views.edf77621.js +22 -0
  121. package/dist/esm/column-views.edf77621.js.map +1 -0
  122. package/dist/esm/column-views.f1efcc2c.js +9 -0
  123. package/dist/esm/column-views.f1efcc2c.js.map +1 -0
  124. package/dist/esm/column-views.f2e5a72f.js +66 -0
  125. package/dist/esm/column-views.f2e5a72f.js.map +1 -0
  126. package/dist/esm/column-views.f71ec4b4.js +127 -0
  127. package/dist/esm/column-views.f71ec4b4.js.map +1 -0
  128. package/dist/esm/column-views.f9dab157.js +3 -0
  129. package/dist/esm/column-views.f9dab157.js.map +1 -0
  130. package/dist/esm/column-views.fa6f3292.js +215 -0
  131. package/dist/esm/column-views.fa6f3292.js.map +1 -0
  132. package/dist/esm/column-views.fc2b4611.js +9 -0
  133. package/dist/esm/column-views.fc2b4611.js.map +1 -0
  134. package/dist/esm/index.d.ts +797 -130
  135. package/dist/esm/index.d.ts.map +1 -1
  136. package/dist/esm/index.js +13 -9
  137. package/dist/esm/index.js.map +1 -1
  138. package/dist/node/index.js +1 -1
  139. package/dist/node/index.js.map +1 -1
  140. package/package.json +29 -13
  141. package/src/age-axis.module.sass +27 -0
  142. package/src/age-axis.ts +82 -19
  143. package/src/age-model-overlay.module.sass +11 -0
  144. package/src/age-model-overlay.ts +73 -0
  145. package/src/carbon-isotopes/column.ts +4 -2
  146. package/src/carbon-isotopes/core.ts +9 -6
  147. package/src/carbon-isotopes/data-provider.ts +3 -3
  148. package/src/column.module.sass +58 -49
  149. package/src/column.ts +149 -135
  150. package/src/correlation-chart/hash-string.ts +57 -0
  151. package/src/correlation-chart/index.ts +2 -0
  152. package/src/correlation-chart/main.module.sass +122 -0
  153. package/src/correlation-chart/main.ts +305 -0
  154. package/src/correlation-chart/prepare-data.ts +217 -0
  155. package/src/correlation-chart/stories/correlation-chart.stories.ts +192 -0
  156. package/src/correlation-chart/stories/stories.module.sass +18 -0
  157. package/src/correlation-chart/unconformity-line.module.sass +8 -0
  158. package/src/correlation-chart/unconformity-line.ts +25 -0
  159. package/src/data-provider/base.ts +388 -0
  160. package/src/data-provider/column-nav.ts +53 -0
  161. package/src/data-provider/fetch.ts +231 -0
  162. package/src/data-provider/index.ts +4 -0
  163. package/src/data-provider/store.ts +72 -0
  164. package/src/index.ts +5 -3
  165. package/src/maps/_shared/index.ts +2 -0
  166. package/src/maps/_shared/inset-map.stories.ts +20 -0
  167. package/src/maps/_shared/inset-map.ts +87 -0
  168. package/src/maps/_shared/styles.ts +57 -0
  169. package/src/maps/column-correlation/column-correlation-map.stories.ts +96 -0
  170. package/src/maps/column-correlation/core.ts +225 -0
  171. package/src/maps/column-correlation/index.ts +2 -0
  172. package/src/maps/column-correlation/state.ts +192 -0
  173. package/src/maps/column-navigation/index.ts +2 -0
  174. package/src/maps/column-navigation/mapbox/column-navigation-map.stories.ts +102 -0
  175. package/src/maps/column-navigation/mapbox/index.ts +219 -0
  176. package/src/maps/column-navigation/mapbox/keyboard-navigation.ts +119 -0
  177. package/src/maps/column-navigation/mapbox/state.ts +109 -0
  178. package/src/maps/column-navigation/svg/column-navigation-svg-map.stories.ts +46 -0
  179. package/src/{map → maps/column-navigation/svg}/index.ts +18 -6
  180. package/src/{map → maps/column-navigation/svg}/layers.ts +28 -109
  181. package/src/maps/column-navigation/utils/keyboard-navigation.ts +58 -0
  182. package/src/maps/index.ts +3 -0
  183. package/src/prepare-units/composite-scale.ts +353 -0
  184. package/src/prepare-units/helpers.ts +226 -0
  185. package/src/prepare-units/index.ts +177 -0
  186. package/src/prepare-units/utils.ts +98 -0
  187. package/src/section.ts +264 -84
  188. package/src/unit-details/index.ts +2 -215
  189. package/src/unit-details/{index.module.sass → panel.module.sass} +39 -8
  190. package/src/unit-details/panel.ts +523 -0
  191. package/src/{selection-popover.module.sass → unit-details/popover.module.sass} +4 -2
  192. package/src/unit-details/popover.ts +82 -0
  193. package/src/units/boxes.module.sass +16 -0
  194. package/src/units/boxes.ts +243 -90
  195. package/src/units/colors.ts +64 -0
  196. package/src/units/composite.ts +157 -152
  197. package/src/units/index.ts +76 -9
  198. package/src/units/names.ts +8 -4
  199. package/src/units/resolvers.ts +12 -1
  200. package/src/units/selection.ts +115 -101
  201. package/src/units/types.ts +5 -16
  202. package/dist/esm/age-axis.64c509ee.js +0 -41
  203. package/dist/esm/age-axis.64c509ee.js.map +0 -1
  204. package/dist/esm/boxes.13968b76.js +0 -164
  205. package/dist/esm/boxes.13968b76.js.map +0 -1
  206. package/dist/esm/boxes.module.28873d08.css +0 -4
  207. package/dist/esm/boxes.module.28873d08.css.map +0 -1
  208. package/dist/esm/boxes.module.605bdcd6.js +0 -13
  209. package/dist/esm/boxes.module.605bdcd6.js.map +0 -1
  210. package/dist/esm/column.8dd26e1a.js +0 -149
  211. package/dist/esm/column.8dd26e1a.js.map +0 -1
  212. package/dist/esm/column.db058031.js.map +0 -1
  213. package/dist/esm/column.module.039d872c.css +0 -151
  214. package/dist/esm/column.module.039d872c.css.map +0 -1
  215. package/dist/esm/column.module.e632868a.js +0 -19
  216. package/dist/esm/column.module.e632868a.js.map +0 -1
  217. package/dist/esm/composite.608a4ca2.js +0 -153
  218. package/dist/esm/composite.608a4ca2.js.map +0 -1
  219. package/dist/esm/composite.module.69a38fd1.css +0 -4
  220. package/dist/esm/core.4db8e452.js.map +0 -1
  221. package/dist/esm/data-provider.1506eb64.js.map +0 -1
  222. package/dist/esm/helpers.8a03cfc7.js +0 -94
  223. package/dist/esm/helpers.8a03cfc7.js.map +0 -1
  224. package/dist/esm/index.module.0844afd8.css +0 -65
  225. package/dist/esm/index.module.0844afd8.css.map +0 -1
  226. package/dist/esm/index.module.24fe3a2e.js +0 -37
  227. package/dist/esm/index.module.24fe3a2e.js.map +0 -1
  228. package/dist/esm/layers.ad5fd25d.js +0 -231
  229. package/dist/esm/layers.ad5fd25d.js.map +0 -1
  230. package/dist/esm/map.7aa26f17.js +0 -82
  231. package/dist/esm/map.7aa26f17.js.map +0 -1
  232. package/dist/esm/names.f6a4e9a4.js.map +0 -1
  233. package/dist/esm/providers.cd1e652f.js +0 -25
  234. package/dist/esm/providers.cd1e652f.js.map +0 -1
  235. package/dist/esm/resolvers.b5c82b78.js.map +0 -1
  236. package/dist/esm/section.cc1b1100.js +0 -118
  237. package/dist/esm/section.cc1b1100.js.map +0 -1
  238. package/dist/esm/selection-popover.17d1bd55.js +0 -60
  239. package/dist/esm/selection-popover.17d1bd55.js.map +0 -1
  240. package/dist/esm/selection-popover.module.0a701258.css.map +0 -1
  241. package/dist/esm/selection-popover.module.e22f5183.js +0 -22
  242. package/dist/esm/selection-popover.module.e22f5183.js.map +0 -1
  243. package/dist/esm/selection.bbf26d0e.js +0 -117
  244. package/dist/esm/selection.bbf26d0e.js.map +0 -1
  245. package/dist/esm/store.25eaa168.js +0 -18
  246. package/dist/esm/store.25eaa168.js.map +0 -1
  247. package/dist/esm/types.f016e032.js +0 -18
  248. package/dist/esm/types.f016e032.js.map +0 -1
  249. package/dist/esm/unit-details.56ff8b70.js +0 -209
  250. package/dist/esm/unit-details.56ff8b70.js.map +0 -1
  251. package/dist/esm/units.361088d4.js +0 -42
  252. package/dist/esm/units.361088d4.js.map +0 -1
  253. package/src/helpers.ts +0 -133
  254. package/src/providers.ts +0 -34
  255. package/src/selection-popover.ts +0 -71
  256. package/src/store.ts +0 -12
  257. package/src/units/units.scss +0 -16
  258. /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
+ }