@macrostrat/column-views 3.12.0 → 3.12.2

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 (55) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/age-axis.cjs +1 -2
  3. package/dist/age-axis.cjs.map +1 -1
  4. package/dist/age-axis.js +1 -2
  5. package/dist/age-axis.js.map +1 -1
  6. package/dist/age-model/details.cjs +2 -3
  7. package/dist/age-model/details.cjs.map +1 -1
  8. package/dist/age-model/details.js +2 -3
  9. package/dist/age-model/details.js.map +1 -1
  10. package/dist/age-model/surfaces.cjs +1 -2
  11. package/dist/age-model/surfaces.cjs.map +1 -1
  12. package/dist/age-model/surfaces.js +1 -2
  13. package/dist/age-model/surfaces.js.map +1 -1
  14. package/dist/column-views.css +17 -17
  15. package/dist/column.cjs +11 -1
  16. package/dist/column.cjs.map +1 -1
  17. package/dist/column.js +11 -1
  18. package/dist/column.js.map +1 -1
  19. package/dist/index.cjs +25 -16
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +3 -3
  22. package/dist/prepare-units/composite-scale.cjs +16 -6
  23. package/dist/prepare-units/composite-scale.cjs.map +1 -1
  24. package/dist/prepare-units/composite-scale.d.ts +1 -1
  25. package/dist/prepare-units/composite-scale.js +16 -6
  26. package/dist/prepare-units/composite-scale.js.map +1 -1
  27. package/dist/prepare-units/density.cjs +21 -13
  28. package/dist/prepare-units/density.cjs.map +1 -1
  29. package/dist/prepare-units/density.d.ts +18 -11
  30. package/dist/prepare-units/density.js +21 -13
  31. package/dist/prepare-units/density.js.map +1 -1
  32. package/dist/prepare-units/helpers.cjs +16 -1
  33. package/dist/prepare-units/helpers.cjs.map +1 -1
  34. package/dist/prepare-units/helpers.js +16 -1
  35. package/dist/prepare-units/helpers.js.map +1 -1
  36. package/dist/prepare-units/index.cjs.map +1 -1
  37. package/dist/prepare-units/index.js.map +1 -1
  38. package/dist/unit-details/age-range.cjs +14 -44
  39. package/dist/unit-details/age-range.cjs.map +1 -1
  40. package/dist/unit-details/age-range.d.ts +2 -18
  41. package/dist/unit-details/age-range.js +6 -44
  42. package/dist/unit-details/age-range.js.map +1 -1
  43. package/dist/unit-details/utils.cjs +0 -5
  44. package/dist/unit-details/utils.cjs.map +1 -1
  45. package/dist/unit-details/utils.js +0 -5
  46. package/dist/unit-details/utils.js.map +1 -1
  47. package/package.json +3 -3
  48. package/src/age-model/details.ts +2 -2
  49. package/src/age-model/surfaces.ts +2 -2
  50. package/src/column.ts +17 -1
  51. package/src/prepare-units/composite-scale.ts +22 -9
  52. package/src/prepare-units/density.ts +36 -25
  53. package/src/prepare-units/helpers.ts +35 -1
  54. package/src/prepare-units/index.ts +0 -1
  55. package/src/unit-details/age-range.ts +13 -59
@@ -1 +1 @@
1
- {"version":3,"file":"density.js","sources":["../../src/prepare-units/density.ts"],"sourcesContent":["/** How tall a column draws comes down to one quantity: **density**, the pixels\n * given to one unit of the axis — a Myr on an age column, a metre otherwise.\n *\n * Every option that sets a column's height names that quantity. Most are\n * floors, differing only in the units they're stated in: room for a unit,\n * for a section, or for the density itself. They combine by taking the\n * largest, so each says \"at least this much\", whichever ends up binding.\n */\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\nimport type { UnitLong } from \"@macrostrat/api-types\";\n\nexport interface SectionDensityContext {\n /** The section's extent, in axis units */\n extent: number;\n /** Extents of the units the render window actually shows, each clipped to\n * it. Measuring what's on screen is what makes a unit-height rule mean the\n * same thing at every zoom depth. */\n unitExtents: number[];\n /** The section's units, unclipped, for a rule that needs more than their\n * extents */\n units: UnitLong[];\n sectionID?: number;\n axisType: ColumnAxisType;\n}\n\n/** Pixels per axis unit, for one section */\nexport type SectionDensity = (ctx: SectionDensityContext) => number;\n\nexport type SectionDensityLike = number | SectionDensity;\n\n/** Room for a typical unit on screen: `px` tall, whatever its duration.\n *\n * \"Typical\" is the median of the visible extents, which is the only summary\n * that survives both tails. Unit durations are spread over orders of\n * magnitude: the arithmetic mean sits above any unit you would point at, so a\n * couple of long ones squeeze everything else below the target — and the\n * geometric mean fails the other way, since a single hair-thin unit (a\n * Holocene sliver beside a Pliocene terrace, say) drags the log-average down\n * and stretches the whole section to give that sliver its 20 pixels.\n */\nexport function unitHeight(px: number): SectionDensity {\n return (ctx) => px / typicalExtent(ctx.unitExtents);\n}\n\n/** Room for the section: `px` tall, whatever its extent. */\nexport function sectionHeight(px: number): SectionDensity {\n return (ctx) => px / ctx.extent;\n}\n\n/** A density stated outright, in pixels per axis unit. */\nexport function fixedDensity(px: number): SectionDensity {\n return () => px;\n}\n\n/** The rule that asks for the most room. Rules that don't resolve to a finite\n * number — a section of no extent, a window showing no units — are passed\n * over rather than swallowing the rest. */\nexport function atLeast(...rules: SectionDensityLike[]): SectionDensity {\n return (ctx) => {\n let density = 0;\n for (const rule of rules) {\n const value = resolveDensity(rule, ctx);\n if (!Number.isFinite(value)) continue;\n density = Math.max(density, value);\n }\n return density;\n };\n}\n\nexport function resolveDensity(\n rule: SectionDensityLike,\n ctx: SectionDensityContext,\n): number {\n if (typeof rule === \"number\") return rule;\n return rule(ctx);\n}\n\nfunction typicalExtent(extents: number[]): number {\n const sorted = extents.filter((d) => d > 0).sort((a, b) => a - b);\n if (sorted.length === 0) return NaN;\n const mid = (sorted.length - 1) / 2;\n const lower = Math.floor(mid);\n const upper = Math.ceil(mid);\n return (sorted[lower] + sorted[upper]) / 2;\n}\n\n/** Mirrors the default in `sectionDensity` */\nexport const DEFAULT_TARGET_UNIT_HEIGHT = 20;\n/** Small sections often have a unit or two, and no room for axis labels */\nconst DEFAULT_MIN_PIXEL_SCALE = 0.2;\n\n/** What a section is sized by. Stated for the column as a whole, and again\n * per section by `sectionOptions` where one needs different treatment. */\nexport interface SectionSizeOptions {\n /** A density stated outright, in pixels per axis unit: every section drawn\n * to the same scale whatever it holds. Rarely what you want within one\n * column, and the only way to make several of them comparable.\n *\n * Left out — the usual case — the density comes from the section's own\n * units, via `targetUnitHeight` and the floors. Set, it *is* the density:\n * the floors don't apply, since they would undo the scale you stated.\n *\n * It also takes a rule, for what the options here can't describe.\n *\n * This one is stated in whatever the axis measures, so it means a different\n * thing on an age axis than on a depth one. A view that switches between\n * them wants `pixelsPerMyr` and `pixelsPerMeter` instead. */\n pixelScale?: SectionDensityLike;\n /** Pixels per million years, on an age axis.\n *\n * The axis-specific spellings of `pixelScale`, and what to reach for when\n * the axis can change under you: useful values differ by orders of\n * magnitude between the two, so one number cannot serve both, any more than\n * `t_age` could serve as `t_pos`. Both can be held at once — the axis picks\n * — and either takes precedence over `pixelScale`. */\n pixelsPerMyr?: number;\n /** Pixels per metre, on a depth or height axis. Also the scale for an\n * approximate-height column, whose units are metres of measured thickness\n * however its axis is labeled. */\n pixelsPerMeter?: number;\n /** Room for a typical unit on screen. The knob that usually decides a\n * column, and the one to reach for to draw it larger or smaller. */\n targetUnitHeight?: number;\n /** Room for a section, however little it holds */\n minSectionHeight?: number;\n /** A floor on the density itself */\n minPixelScale?: number;\n}\n\n/** Sizing for one section, or a rule that works it out from what the section\n * holds. What it returns overrides the column-wide options for that section;\n * anything it leaves out keeps the column's value. */\nexport type SectionOptionsLike =\n | SectionSizeOptions\n | ((ctx: SectionDensityContext) => SectionSizeOptions);\n\nexport interface SectionDensityOptions extends SectionSizeOptions {\n /** Sizing decided per section */\n sectionOptions?: SectionOptionsLike;\n}\n\n/** The density rule a column uses, resolved per section so that\n * `sectionOptions` gets to see a section before saying how to draw it. */\nexport function sectionDensity(opts: SectionDensityOptions): SectionDensity {\n const { sectionOptions } = opts;\n if (sectionOptions == null) {\n const rule = densityRule(opts);\n return (ctx) => rule(ctx);\n }\n\n return (ctx) => {\n let overrides = sectionOptions;\n if (typeof sectionOptions === \"function\") overrides = sectionOptions(ctx);\n return densityRule({ ...opts, ...overrides })(ctx);\n };\n}\n\n/** The floors, combined: whichever asks for the most room wins. */\nfunction densityRule(opts: SectionSizeOptions): SectionDensity {\n const {\n targetUnitHeight = DEFAULT_TARGET_UNIT_HEIGHT,\n minPixelScale = DEFAULT_MIN_PIXEL_SCALE,\n minSectionHeight,\n } = opts;\n\n const derived = atLeast(\n unitHeight(targetUnitHeight),\n fixedDensity(minPixelScale),\n sectionHeight(minSectionHeight ?? targetUnitHeight ?? 0),\n );\n\n return (ctx) => {\n // A density given outright answers for the section by itself. The floors\n // exist to keep a column readable when its own contents decide the\n // density; a scale you stated is a decision already made, and a floor\n // would quietly undo it — which is the whole point of stating one,\n // usually so that several columns can be compared.\n const stated = statedDensity(opts, ctx.axisType);\n if (typeof stated === \"function\") return stated(ctx);\n if (stated != null) return stated;\n return derived(ctx);\n };\n}\n\n/** The density this axis was given, if any. The axis-specific spellings win\n * over the generic one, which is what lets both be held while the axis\n * changes. */\nexport function statedDensity(\n opts: SectionSizeOptions,\n axisType: ColumnAxisType,\n): SectionDensityLike | undefined {\n const { pixelScale, pixelsPerMyr, pixelsPerMeter } = opts;\n if (axisType === ColumnAxisType.AGE) {\n return pixelsPerMyr ?? pixelScale;\n }\n return pixelsPerMeter ?? pixelScale;\n}\n"],"names":[],"mappings":";AAwCO,SAAS,WAAW,IAA4B;AACrD,SAAO,CAAC,QAAQ,KAAK,cAAc,IAAI,WAAW;AACpD;AAGO,SAAS,cAAc,IAA4B;AACxD,SAAO,CAAC,QAAQ,KAAK,IAAI;AAC3B;AAGO,SAAS,aAAa,IAA4B;AACvD,SAAO,MAAM;AACf;AAKO,SAAS,WAAW,OAA6C;AACtE,SAAO,CAAC,QAAQ;AACd,QAAI,UAAU;AACd,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,eAAe,MAAM,GAAG;AACtC,UAAI,CAAC,OAAO,SAAS,KAAK,EAAG;AAC7B,gBAAU,KAAK,IAAI,SAAS,KAAK;AAAA,IACnC;AACA,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eACd,MACA,KACQ;AACR,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,SAAO,KAAK,GAAG;AACjB;AAEA,SAAS,cAAc,SAA2B;AAChD,QAAM,SAAS,QAAQ,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAChE,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,OAAO,OAAO,SAAS,KAAK;AAClC,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAM,QAAQ,KAAK,KAAK,GAAG;AAC3B,UAAQ,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK;AAC3C;AAGO,MAAM,6BAA6B;AAE1C,MAAM,0BAA0B;AAsDzB,SAAS,eAAe,MAA6C;AAC1E,QAAM,EAAE,mBAAmB;AAC3B,MAAI,kBAAkB,MAAM;AAC1B,UAAM,OAAO,YAAY,IAAI;AAC7B,WAAO,CAAC,QAAQ,KAAK,GAAG;AAAA,EAC1B;AAEA,SAAO,CAAC,QAAQ;AACd,QAAI,YAAY;AAChB,QAAI,OAAO,mBAAmB,WAAY,aAAY,eAAe,GAAG;AACxE,WAAO,YAAY,EAAE,GAAG,MAAM,GAAG,UAAA,CAAW,EAAE,GAAG;AAAA,EACnD;AACF;AAGA,SAAS,YAAY,MAA0C;AAC7D,QAAM;AAAA,IACJ,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB;AAAA,EAAA,IACE;AAEJ,QAAM,UAAU;AAAA,IACd,WAAW,gBAAgB;AAAA,IAC3B,aAAa,aAAa;AAAA,IAC1B,cAAc,oBAAoB,oBAAoB,CAAC;AAAA,EAAA;AAGzD,SAAO,CAAC,QAAQ;AAMd,UAAM,SAAS,cAAc,MAAM,IAAI,QAAQ;AAC/C,QAAI,OAAO,WAAW,WAAY,QAAO,OAAO,GAAG;AACnD,QAAI,UAAU,KAAM,QAAO;AAC3B,WAAO,QAAQ,GAAG;AAAA,EACpB;AACF;AAKO,SAAS,cACd,MACA,UACgC;AAChC,QAAM,EAAE,YAAY,cAAc,eAAA,IAAmB;AACrD,MAAI,aAAa,eAAe,KAAK;AACnC,WAAO,gBAAgB;AAAA,EACzB;AACA,SAAO,kBAAkB;AAC3B;"}
1
+ {"version":3,"file":"density.js","sources":["../../src/prepare-units/density.ts"],"sourcesContent":["/** How tall a column draws comes down to one quantity: **density**, the pixels\n * given to one unit of the axis — a Myr on an age column, a metre otherwise.\n *\n * Every option that sets a column's height names that quantity. Most are\n * floors, differing only in the units they're stated in: room for a unit,\n * for a section, or for the density itself. They combine by taking the\n * largest, so each says \"at least this much\", whichever ends up binding.\n */\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\nimport type { UnitLong } from \"@macrostrat/api-types\";\n\nexport interface SectionDensityContext {\n /** The section's extent, in axis units */\n extent: number;\n /** The section's shown extent, in axis units */\n visibleExtent: number;\n /** Extents of the units the render window actually shows, each clipped to\n * it. Measuring what's on screen is what makes a unit-height rule mean the\n * same thing at every zoom depth. */\n unitExtents: number[];\n /** The section's units, unclipped, for a rule that needs more than their\n * extents */\n units: UnitLong[];\n sectionID?: number;\n axisType: ColumnAxisType;\n}\n\n/** Pixels per axis unit, for one section */\nexport type SectionDensity = (ctx: SectionDensityContext) => number;\n\nexport type SectionDensityLike = number | SectionDensity;\n\nenum UnitHeightMode {\n MEDIAN = \"median\",\n MEAN = \"mean\",\n}\n\n/** Room for the section: `px` tall, whatever its extent. */\nexport function sectionHeight(px: number): SectionDensity {\n return (ctx) => px / ctx.visibleExtent;\n}\n\n/** A density stated outright, in pixels per axis unit. */\nexport function fixedDensity(px: number): SectionDensity {\n return () => px;\n}\n\n/** The rule that asks for the most room. Rules that don't resolve to a finite\n * number — a section of no extent, a window showing no units — are passed\n * over rather than swallowing the rest. */\nexport function atLeast(...rules: SectionDensityLike[]): SectionDensity {\n return (ctx) => {\n let density = 0;\n for (const rule of rules) {\n const value = resolveDensity(rule, ctx);\n if (!Number.isFinite(value)) continue;\n density = Math.max(density, value);\n }\n return density;\n };\n}\n\nexport function resolveDensity(\n rule: SectionDensityLike,\n ctx: SectionDensityContext,\n): number {\n if (typeof rule === \"number\") return rule;\n return rule(ctx);\n}\n\n/** Mirrors the default in `sectionDensity` */\nexport const DEFAULT_TARGET_UNIT_HEIGHT = 20;\n/** Small sections often have a unit or two, and no room for axis labels */\nconst DEFAULT_MIN_PIXEL_SCALE = 0.2;\n\n/** What a section is sized by. Stated for the column as a whole, and again\n * per section by `sectionOptions` where one needs different treatment. */\nexport interface SectionSizeOptions {\n /** A density stated outright, in pixels per axis unit: every section drawn\n * to the same scale whatever it holds. Rarely what you want within one\n * column, and the only way to make several of them comparable.\n *\n * Left out — the usual case — the density comes from the section's own\n * units, via `targetUnitHeight` and the floors. Set, it *is* the density:\n * the floors don't apply, since they would undo the scale you stated.\n *\n * It also takes a rule, for what the options here can't describe.\n *\n * This one is stated in whatever the axis measures, so it means a different\n * thing on an age axis than on a depth one. A view that switches between\n * them wants `pixelsPerMyr` and `pixelsPerMeter` instead. */\n pixelScale?: SectionDensityLike;\n /** Pixels per million years, on an age axis.\n *\n * The axis-specific spellings of `pixelScale`, and what to reach for when\n * the axis can change under you: useful values differ by orders of\n * magnitude between the two, so one number cannot serve both, any more than\n * `t_age` could serve as `t_pos`. Both can be held at once — the axis picks\n * — and either takes precedence over `pixelScale`. */\n pixelsPerMyr?: number;\n /** Pixels per metre, on a depth or height axis. Also the scale for an\n * approximate-height column, whose units are metres of measured thickness\n * however its axis is labeled. */\n pixelsPerMeter?: number;\n /** Room for a typical unit on screen. The knob that usually decides a\n * column, and the one to reach for to draw it larger or smaller. */\n targetUnitHeight?: number;\n /** Room for a section, however little it holds */\n minSectionHeight?: number;\n /** A floor on the density itself */\n minPixelScale?: number;\n}\n\n/** Sizing for one section, or a rule that works it out from what the section\n * holds. What it returns overrides the column-wide options for that section;\n * anything it leaves out keeps the column's value. */\nexport type SectionOptionsLike =\n SectionSizeOptions | ((ctx: SectionDensityContext) => SectionSizeOptions);\n\nexport interface SectionDensityOptions extends SectionSizeOptions {\n /** Sizing decided per section */\n sectionOptions?: SectionOptionsLike;\n}\n\n/** The density rule a column uses, resolved per section so that\n * `sectionOptions` gets to see a section before saying how to draw it. */\nexport function sectionDensity(opts: SectionDensityOptions): SectionDensity {\n const { sectionOptions } = opts;\n if (sectionOptions == null) {\n const rule = densityRule(opts);\n return (ctx) => rule(ctx);\n }\n\n return (ctx) => {\n let overrides = sectionOptions;\n if (typeof sectionOptions === \"function\") overrides = sectionOptions(ctx);\n return densityRule({ ...opts, ...overrides })(ctx);\n };\n}\n\n/** The floors, combined: whichever asks for the most room wins. */\nfunction densityRule(opts: SectionSizeOptions): SectionDensity {\n const {\n targetUnitHeight = DEFAULT_TARGET_UNIT_HEIGHT,\n minPixelScale = DEFAULT_MIN_PIXEL_SCALE,\n minSectionHeight,\n } = opts;\n\n const derived = atLeast(\n unitHeight(targetUnitHeight, UnitHeightMode.MEAN),\n fixedDensity(minPixelScale),\n sectionHeight(minSectionHeight ?? targetUnitHeight ?? 0),\n );\n\n return (ctx) => {\n // A density given outright answers for the section by itself. The floors\n // exist to keep a column readable when its own contents decide the\n // density; a scale you stated is a decision already made, and a floor\n // would quietly undo it — which is the whole point of stating one,\n // usually so that several columns can be compared.\n const stated = statedDensity(opts, ctx.axisType);\n if (typeof stated === \"function\") return stated(ctx);\n if (stated != null) return stated;\n return derived(ctx);\n };\n}\n\n/** Room for a typical unit on screen: `px` tall, whatever its duration.\n *\n * \"Typical\" is the median of the visible extents, which is the only summary\n * that survives both tails. Unit durations are spread over orders of\n * magnitude: the arithmetic mean sits above any unit you would point at, so a\n * couple of long ones squeeze everything else below the target — and the\n * geometric mean fails the other way, since a single hair-thin unit (a\n * Holocene sliver beside a Pliocene terrace, say) drags the log-average down\n * and stretches the whole section to give that sliver its 20 pixels.\n */\nexport function unitHeight(px: number, mode: UnitHeightMode): SectionDensity {\n return (ctx) => px / typicalExtent(ctx.unitExtents, mode);\n}\n\nfunction typicalExtent(extents: number[], mode: UnitHeightMode): number {\n /** The typical extent for a unit, the median of its extents. */\n const filtered = extents.filter((d) => d > 0);\n if (filtered.length === 0) return NaN;\n if (mode === UnitHeightMode.MEAN) {\n return filtered.reduce((sum, val) => sum + val, 0) / filtered.length;\n }\n const sorted = filtered.sort((a, b) => a - b);\n const mid = (sorted.length - 1) / 2;\n const lower = Math.floor(mid);\n const upper = Math.ceil(mid);\n return (sorted[lower] + sorted[upper]) / 2;\n}\n\n/** The density this axis was given, if any. The axis-specific spellings win\n * over the generic one, which is what lets both be held while the axis\n * changes. */\nexport function statedDensity(\n opts: SectionSizeOptions,\n axisType: ColumnAxisType,\n): SectionDensityLike | undefined {\n const { pixelScale, pixelsPerMyr, pixelsPerMeter } = opts;\n if (axisType === ColumnAxisType.AGE) {\n return pixelsPerMyr ?? pixelScale;\n }\n return pixelsPerMeter ?? pixelScale;\n}\n"],"names":[],"mappings":";AAsCO,SAAS,cAAc,IAA4B;AACxD,SAAO,CAAC,QAAQ,KAAK,IAAI;AAC3B;AAGO,SAAS,aAAa,IAA4B;AACvD,SAAO,MAAM;AACf;AAKO,SAAS,WAAW,OAA6C;AACtE,SAAO,CAAC,QAAQ;AACd,QAAI,UAAU;AACd,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,eAAe,MAAM,GAAG;AACtC,UAAI,CAAC,OAAO,SAAS,KAAK,EAAG;AAC7B,gBAAU,KAAK,IAAI,SAAS,KAAK;AAAA,IACnC;AACA,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eACd,MACA,KACQ;AACR,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,SAAO,KAAK,GAAG;AACjB;AAGO,MAAM,6BAA6B;AAE1C,MAAM,0BAA0B;AAqDzB,SAAS,eAAe,MAA6C;AAC1E,QAAM,EAAE,mBAAmB;AAC3B,MAAI,kBAAkB,MAAM;AAC1B,UAAM,OAAO,YAAY,IAAI;AAC7B,WAAO,CAAC,QAAQ,KAAK,GAAG;AAAA,EAC1B;AAEA,SAAO,CAAC,QAAQ;AACd,QAAI,YAAY;AAChB,QAAI,OAAO,mBAAmB,WAAY,aAAY,eAAe,GAAG;AACxE,WAAO,YAAY,EAAE,GAAG,MAAM,GAAG,UAAA,CAAW,EAAE,GAAG;AAAA,EACnD;AACF;AAGA,SAAS,YAAY,MAA0C;AAC7D,QAAM;AAAA,IACJ,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB;AAAA,EAAA,IACE;AAEJ,QAAM,UAAU;AAAA,IACd;AAAA,MAAW;AAAA,MAAkB;AAAA;AAAA,IAAA;AAAA,IAC7B,aAAa,aAAa;AAAA,IAC1B,cAAc,oBAAoB,oBAAoB,CAAC;AAAA,EAAA;AAGzD,SAAO,CAAC,QAAQ;AAMd,UAAM,SAAS,cAAc,MAAM,IAAI,QAAQ;AAC/C,QAAI,OAAO,WAAW,WAAY,QAAO,OAAO,GAAG;AACnD,QAAI,UAAU,KAAM,QAAO;AAC3B,WAAO,QAAQ,GAAG;AAAA,EACpB;AACF;AAYO,SAAS,WAAW,IAAY,MAAsC;AAC3E,SAAO,CAAC,QAAQ,KAAK,cAAc,IAAI,aAAa,IAAI;AAC1D;AAEA,SAAS,cAAc,SAAmB,MAA8B;AAEtE,QAAM,WAAW,QAAQ,OAAO,CAAC,MAAM,IAAI,CAAC;AAC5C,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,MAAI,SAAS,QAAqB;AAChC,WAAO,SAAS,OAAO,CAAC,KAAK,QAAQ,MAAM,KAAK,CAAC,IAAI,SAAS;AAAA,EAChE;AACA,QAAM,SAAS,SAAS,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC5C,QAAM,OAAO,OAAO,SAAS,KAAK;AAClC,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAM,QAAQ,KAAK,KAAK,GAAG;AAC3B,UAAQ,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK;AAC3C;AAKO,SAAS,cACd,MACA,UACgC;AAChC,QAAM,EAAE,YAAY,cAAc,eAAA,IAAmB;AACrD,MAAI,aAAa,eAAe,KAAK;AACnC,WAAO,gBAAgB;AAAA,EACzB;AACA,SAAO,kBAAkB;AAC3B;"}
@@ -160,7 +160,7 @@ function groupUnitsIntoSectionsByOverlap(units, axisType = columnComponents.Colu
160
160
  break;
161
161
  }
162
162
  }
163
- return finalSectionList.map((section, i) => {
163
+ const s1 = finalSectionList.map((section, i) => {
164
164
  const [b_age, t_age] = getSectionAgeRange(section.units);
165
165
  const [b_pos, t_pos] = getSectionPosRange(section.units, axisType);
166
166
  return {
@@ -173,6 +173,7 @@ function groupUnitsIntoSectionsByOverlap(units, axisType = columnComponents.Colu
173
173
  units: section.units
174
174
  };
175
175
  });
176
+ return sortUnits(s1, axisType);
176
177
  }
177
178
  function getSectionPosRange(units, axisType) {
178
179
  const t_positions = units.map((d) => {
@@ -208,6 +209,20 @@ function getSectionPosRange(units, axisType) {
208
209
  function getSectionAgeRange(units) {
209
210
  return getSectionPosRange(units, columnComponents.ColumnAxisType.AGE);
210
211
  }
212
+ function sortUnits(units, axisType) {
213
+ return units.sort((a, b) => {
214
+ if (axisType === columnComponents.ColumnAxisType.HEIGHT && b.t_pos !== void 0 && a.t_pos !== void 0) {
215
+ return b.t_pos - a.t_pos;
216
+ }
217
+ if (axisType === columnComponents.ColumnAxisType.DEPTH && b.t_pos !== void 0 && a.t_pos !== void 0) {
218
+ return a.t_pos - b.t_pos;
219
+ }
220
+ if (axisType === columnComponents.ColumnAxisType.AGE) {
221
+ return a.t_age - b.t_age;
222
+ }
223
+ return 0;
224
+ });
225
+ }
211
226
  function mergeOverlappingSections(sections) {
212
227
  const [firstSection, ...rest] = sections;
213
228
  const newSections = [firstSection];
@@ -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 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 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 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(\n lastSection,\n section,\n );\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
+ {"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 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 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(\n lastSection,\n section,\n );\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 const s1 = 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 return sortUnits(s1, axisType);\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\nfunction forEachSection(fn, sections, ...args) {\n return sections.map((section) => {\n return {\n ...section,\n units: fn(section.units, ...args),\n };\n });\n}\n\nfunction sortUnits(units: BaseUnit[], axisType: ColumnAxisType) {\n return units.sort((a, b) => {\n if (\n axisType === ColumnAxisType.HEIGHT &&\n b.t_pos !== undefined &&\n a.t_pos !== undefined\n ) {\n return b.t_pos - a.t_pos;\n }\n if (\n axisType === ColumnAxisType.DEPTH &&\n b.t_pos !== undefined &&\n a.t_pos !== undefined\n ) {\n return a.t_pos - b.t_pos;\n }\n if (axisType === ColumnAxisType.AGE) {\n return a.t_age - b.t_age;\n }\n return 0;\n });\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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"}
@@ -158,7 +158,7 @@ function groupUnitsIntoSectionsByOverlap(units, axisType = ColumnAxisType.AGE, t
158
158
  break;
159
159
  }
160
160
  }
161
- return finalSectionList.map((section, i) => {
161
+ const s1 = finalSectionList.map((section, i) => {
162
162
  const [b_age, t_age] = getSectionAgeRange(section.units);
163
163
  const [b_pos, t_pos] = getSectionPosRange(section.units, axisType);
164
164
  return {
@@ -171,6 +171,7 @@ function groupUnitsIntoSectionsByOverlap(units, axisType = ColumnAxisType.AGE, t
171
171
  units: section.units
172
172
  };
173
173
  });
174
+ return sortUnits(s1, axisType);
174
175
  }
175
176
  function getSectionPosRange(units, axisType) {
176
177
  const t_positions = units.map((d) => {
@@ -206,6 +207,20 @@ function getSectionPosRange(units, axisType) {
206
207
  function getSectionAgeRange(units) {
207
208
  return getSectionPosRange(units, ColumnAxisType.AGE);
208
209
  }
210
+ function sortUnits(units, axisType) {
211
+ return units.sort((a, b) => {
212
+ if (axisType === ColumnAxisType.HEIGHT && b.t_pos !== void 0 && a.t_pos !== void 0) {
213
+ return b.t_pos - a.t_pos;
214
+ }
215
+ if (axisType === ColumnAxisType.DEPTH && b.t_pos !== void 0 && a.t_pos !== void 0) {
216
+ return a.t_pos - b.t_pos;
217
+ }
218
+ if (axisType === ColumnAxisType.AGE) {
219
+ return a.t_age - b.t_age;
220
+ }
221
+ return 0;
222
+ });
223
+ }
209
224
  function mergeOverlappingSections(sections) {
210
225
  const [firstSection, ...rest] = sections;
211
226
  const newSections = [firstSection];
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","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 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 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(\n lastSection,\n section,\n );\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":["group"],"mappings":"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"}
1
+ {"version":3,"file":"helpers.js","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 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 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(\n lastSection,\n section,\n );\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 const s1 = 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 return sortUnits(s1, axisType);\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\nfunction forEachSection(fn, sections, ...args) {\n return sections.map((section) => {\n return {\n ...section,\n units: fn(section.units, ...args),\n };\n });\n}\n\nfunction sortUnits(units: BaseUnit[], axisType: ColumnAxisType) {\n return units.sort((a, b) => {\n if (\n axisType === ColumnAxisType.HEIGHT &&\n b.t_pos !== undefined &&\n a.t_pos !== undefined\n ) {\n return b.t_pos - a.t_pos;\n }\n if (\n axisType === ColumnAxisType.DEPTH &&\n b.t_pos !== undefined &&\n a.t_pos !== undefined\n ) {\n return a.t_pos - b.t_pos;\n }\n if (axisType === ColumnAxisType.AGE) {\n return a.t_age - b.t_age;\n }\n return 0;\n });\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":["group"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/prepare-units/index.ts"],"sourcesContent":["import {\n getSectionAgeRange,\n getSectionPosRange,\n groupUnitsIntoSectionsByOverlap,\n groupUnitsIntoSectionsBySectionID,\n mergeOverlappingSections,\n preprocessSectionUnit,\n preprocessUnits,\n UnitWithLayoutHints,\n} from \"./helpers\";\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\nimport { useMemo } from \"react\";\nimport { UnitLong } from \"@macrostrat/api-types\";\nimport {\n collapseUnconformitiesByPixelHeight,\n computeSectionHeights,\n finalizeSectionHeights,\n padWindowByPixels,\n resolveWindowScales,\n trimSectionsToWindow,\n type CompositeColumnScale,\n} from \"./composite-scale\";\nimport {\n agesOverlap,\n MergeSectionsMode,\n PrepareColumnOptions,\n PreparedColumnData,\n unitsOverlap,\n} from \"./utils\";\nimport { type ColumnHeightScaleOptions, SectionInfo } from \"./types\";\n\nexport * from \"./utils\";\nexport * from \"./types\";\nexport * from \"./density\";\nexport { preprocessUnits };\nexport type { CompositeColumnScale };\n\nexport function usePreparedColumnUnits(\n data: UnitLong[],\n options: PrepareColumnOptions,\n): PreparedColumnData {\n /** This function wraps and memoizes all preparation steps for converting\n * an array of units from the /units route to a form ready for usage.\n */\n return useMemo(() => {\n return prepareColumnUnits(data, options);\n }, [data, ...Object.values(options)]);\n}\n\nexport function prepareColumnUnits(\n units: UnitWithLayoutHints<UnitLong>[],\n options: PrepareColumnOptions,\n): PreparedColumnData {\n /** Prepare units for rendering into Macrostrat columns */\n\n let { t_age, b_age, t_pos, b_pos } = options;\n\n const {\n mergeSections = MergeSectionsMode.OVERLAPPING,\n axisType,\n unconformityHeight,\n collapseSmallUnconformities = false,\n hybridScale,\n scale,\n windowPadding = 0,\n } = options;\n\n let _totalHeight: number | null = null;\n\n if (scale != null) {\n // Set t_age and b_age based on scale domain if not already set\n const domain = scale.domain();\n if (axisType == ColumnAxisType.AGE) {\n if (t_age == null) t_age = Math.min(...domain);\n if (b_age == null) b_age = Math.max(...domain);\n _totalHeight = Math.abs(scale(b_age) - scale(t_age));\n } else {\n if (t_pos == null) t_pos = Math.min(...domain);\n if (b_pos == null) b_pos = Math.max(...domain);\n _totalHeight = Math.abs(scale(b_pos) - scale(t_pos));\n }\n }\n\n /** Age columns lay every section out at its full extent and clip to the\n * window at the end (see below), so that a section abutting the window is\n * sized by its own content rather than by the sliver that survives the clip.\n * Hybrid and externally-supplied scales build their own mapping and can't be\n * re-derived that way, so they keep the original clip-then-lay-out path. */\n const clipBeforeLayout =\n axisType != ColumnAxisType.AGE || hybridScale != null || scale != null;\n\n // Start by ensuring that ages and positions are numbers\n // also set up some values for eODP-style columns\n let units1 = units.map(preprocessSectionUnit);\n\n /** A bound that isn't set doesn't bound anything. Passing `null` for one is\n * the ordinary way to say \"no window\", and comparing against it as though\n * it were an age would throw every unit out. */\n const window = {\n t_age: t_age ?? -Infinity,\n b_age: b_age ?? Infinity,\n t_pos: t_pos ?? -Infinity,\n b_pos: b_pos ?? Infinity,\n };\n const isWindowed =\n axisType == ColumnAxisType.AGE\n ? t_age != null || b_age != null\n : t_pos != null || b_pos != null;\n\n if (clipBeforeLayout && isWindowed) {\n /** Prototype filtering to age range */\n units1 = units1.filter((d) => {\n // Filter units by t_age and b_age, inclusive\n if (axisType == ColumnAxisType.AGE) {\n return agesOverlap(d, window);\n } else {\n return unitsOverlap(d, window as any, axisType);\n }\n });\n }\n\n let mergeMode = mergeSections;\n // if (axisType != ColumnAxisType.AGE) {\n // // For non-age columns, we always merge sections.\n // // This is because the \"groupUnitsIntoSections\" function is not well-defined\n // // for non-age columns.\n // mergeMode = MergeSectionsMode.ALL;\n // }\n\n let sections0: SectionInfo<UnitLong>[];\n if (mergeMode == MergeSectionsMode.ALL) {\n // For the \"merge sections\" mode, we need to create a single section\n const [b_unit_pos, t_unit_pos] = getSectionPosRange(units1, axisType);\n const [b_unit_age, t_unit_age] = getSectionAgeRange(units1);\n sections0 = [\n {\n section_id: 0,\n /**\n * If ages limits are directly specified, use them to define the section bounds.\n * */\n t_pos: t_unit_pos,\n b_pos: b_unit_pos,\n t_age: t_unit_age,\n b_age: b_unit_age,\n units: units1,\n },\n ];\n } else if (axisType == ColumnAxisType.AGE) {\n sections0 = groupUnitsIntoSectionsBySectionID(units1, axisType);\n } else {\n sections0 = groupUnitsIntoSectionsByOverlap(units1, axisType);\n }\n\n // Limit sections to the range specified by t_age/b_age or t_pos/b_pos global options\n for (let section of clipBeforeLayout ? sections0 : []) {\n if (axisType == ColumnAxisType.AGE) {\n section.t_age = Math.max(section.t_age, t_age ?? -Infinity);\n section.b_age = Math.min(section.b_age, b_age ?? Infinity);\n } else if (axisType == ColumnAxisType.DEPTH) {\n section.t_pos = Math.max(section.t_pos, t_pos ?? -Infinity);\n section.b_pos = Math.min(section.b_pos, b_pos ?? Infinity);\n } else if (axisType == ColumnAxisType.HEIGHT) {\n section.t_pos = Math.max(section.t_pos, t_pos ?? -Infinity);\n section.b_pos = Math.min(section.b_pos, b_pos ?? Infinity);\n }\n }\n\n /** Merging overlapping sections really only makes sense for age/height/depth\n * columns. Ordinal columns are numbered by section so merging them\n * results in collisions.\n */\n let sections = sections0;\n if (\n mergeSections == MergeSectionsMode.OVERLAPPING &&\n axisType == ColumnAxisType.AGE\n ) {\n sections = mergeOverlappingSections(sections);\n }\n // Filter out undefined sections just in case\n sections = sections.filter((d) => d != null);\n\n // SCALES\n\n /** The window we've been asked to render. Unit density is derived from what\n * it shows (see `findAverageUnitHeight`), so it has to be known before any\n * section is laid out — but nothing is *clipped* to it until the very end.\n */\n const focalWindow: [number, number] | null = clipBeforeLayout\n ? null\n : [b_age ?? Infinity, t_age ?? -Infinity];\n\n const layoutOptions = { ...options, visibleWindow: focalWindow };\n\n /* Compute pixel scales etc. for sections\n * We need to do this now to determine which unconformities\n * are small enough to collapse.\n */\n let sectionsWithScales = computeSectionHeights(sections, layoutOptions);\n\n if (collapseSmallUnconformities && hybridScale == null) {\n // Collapse small unconformities in pixel height space\n // TODO: this doesn't seem to work properly for non-age columns?\n let threshold = unconformityHeight ?? 30;\n if (typeof collapseSmallUnconformities == \"number\") {\n threshold = collapseSmallUnconformities;\n }\n\n sectionsWithScales = collapseUnconformitiesByPixelHeight(\n sectionsWithScales,\n threshold,\n layoutOptions,\n );\n }\n\n /** Now that every section is laid out at full extent — with its own density\n * and `minSectionHeight` already settled — apply the rendered window. Padding\n * is spent here, against real pixel heights, so `windowPadding` px of an\n * abutting section is exactly that many pixels.\n */\n if (focalWindow != null && (t_age != null || b_age != null)) {\n // Resolve final densities first (a section the window cuts short is\n // stretched to `minSectionHeight`), then spend the padding budget against\n // them, so a margin is the pixels asked for rather than those pixels times\n // whatever stretch its neighbor happened to need.\n const floor =\n layoutOptions.minSectionHeight ?? layoutOptions.targetUnitHeight ?? 0;\n const scales = resolveWindowScales(sectionsWithScales, focalWindow, floor);\n const natural = (section) =>\n scales.get(section) ?? section.scaleInfo.pixelScale;\n\n\n const scaleFor = natural;\n\n const window =\n windowPadding > 0\n ? padWindowByPixels(\n sectionsWithScales,\n focalWindow,\n windowPadding,\n scaleFor,\n )\n : focalWindow;\n\n sectionsWithScales = trimSectionsToWindow(\n sectionsWithScales,\n window,\n scaleFor,\n );\n }\n\n /** Prepare section scale information using groups.\n * Total height is computed from section scales.\n * */\n let { totalHeight, sections: sections2 } = finalizeSectionHeights(\n sectionsWithScales,\n unconformityHeight,\n );\n\n /** For each section, find units that are overlapping.\n * We do this after merging sections so that we can\n * handle cases where there are overlapping units across sections\n * */\n const sectionsOut = sections2.map((section) => {\n return {\n ...section,\n units: preprocessUnits(section, axisType),\n };\n });\n\n /** Reconstitute the units so that they are sorted by section and properly enhanced.\n * This is mostly important so that unit keyboard navigation\n * predictably selects adjacent units.\n */\n const units2 = sectionsOut.reduce((acc, group) => {\n const { units } = group;\n for (const unit of units) {\n acc.push(unit);\n }\n return acc;\n }, []);\n\n return {\n units: units2,\n totalHeight: _totalHeight ?? totalHeight,\n sections: sectionsOut,\n };\n}\n"],"names":["useMemo","MergeSectionsMode","ColumnAxisType","preprocessSectionUnit","agesOverlap","unitsOverlap","getSectionPosRange","getSectionAgeRange","groupUnitsIntoSectionsBySectionID","groupUnitsIntoSectionsByOverlap","mergeOverlappingSections","computeSectionHeights","collapseUnconformitiesByPixelHeight","resolveWindowScales","window","padWindowByPixels","trimSectionsToWindow","finalizeSectionHeights","preprocessUnits","units"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/prepare-units/index.ts"],"sourcesContent":["import {\n getSectionAgeRange,\n getSectionPosRange,\n groupUnitsIntoSectionsByOverlap,\n groupUnitsIntoSectionsBySectionID,\n mergeOverlappingSections,\n preprocessSectionUnit,\n preprocessUnits,\n UnitWithLayoutHints,\n} from \"./helpers\";\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\nimport { useMemo } from \"react\";\nimport { UnitLong } from \"@macrostrat/api-types\";\nimport {\n collapseUnconformitiesByPixelHeight,\n computeSectionHeights,\n finalizeSectionHeights,\n padWindowByPixels,\n resolveWindowScales,\n trimSectionsToWindow,\n type CompositeColumnScale,\n} from \"./composite-scale\";\nimport {\n agesOverlap,\n MergeSectionsMode,\n PrepareColumnOptions,\n PreparedColumnData,\n unitsOverlap,\n} from \"./utils\";\nimport { type ColumnHeightScaleOptions, SectionInfo } from \"./types\";\n\nexport * from \"./utils\";\nexport * from \"./types\";\nexport * from \"./density\";\nexport { preprocessUnits };\nexport type { CompositeColumnScale };\n\nexport function usePreparedColumnUnits(\n data: UnitLong[],\n options: PrepareColumnOptions,\n): PreparedColumnData {\n /** This function wraps and memoizes all preparation steps for converting\n * an array of units from the /units route to a form ready for usage.\n */\n return useMemo(() => {\n return prepareColumnUnits(data, options);\n }, [data, ...Object.values(options)]);\n}\n\nexport function prepareColumnUnits(\n units: UnitWithLayoutHints<UnitLong>[],\n options: PrepareColumnOptions,\n): PreparedColumnData {\n /** Prepare units for rendering into Macrostrat columns */\n\n let { t_age, b_age, t_pos, b_pos } = options;\n\n const {\n mergeSections = MergeSectionsMode.OVERLAPPING,\n axisType,\n unconformityHeight,\n collapseSmallUnconformities = false,\n hybridScale,\n scale,\n windowPadding = 0,\n } = options;\n\n let _totalHeight: number | null = null;\n\n if (scale != null) {\n // Set t_age and b_age based on scale domain if not already set\n const domain = scale.domain();\n if (axisType == ColumnAxisType.AGE) {\n if (t_age == null) t_age = Math.min(...domain);\n if (b_age == null) b_age = Math.max(...domain);\n _totalHeight = Math.abs(scale(b_age) - scale(t_age));\n } else {\n if (t_pos == null) t_pos = Math.min(...domain);\n if (b_pos == null) b_pos = Math.max(...domain);\n _totalHeight = Math.abs(scale(b_pos) - scale(t_pos));\n }\n }\n\n /** Age columns lay every section out at its full extent and clip to the\n * window at the end (see below), so that a section abutting the window is\n * sized by its own content rather than by the sliver that survives the clip.\n * Hybrid and externally-supplied scales build their own mapping and can't be\n * re-derived that way, so they keep the original clip-then-lay-out path. */\n const clipBeforeLayout =\n axisType != ColumnAxisType.AGE || hybridScale != null || scale != null;\n\n // Start by ensuring that ages and positions are numbers\n // also set up some values for eODP-style columns\n let units1 = units.map(preprocessSectionUnit);\n\n /** A bound that isn't set doesn't bound anything. Passing `null` for one is\n * the ordinary way to say \"no window\", and comparing against it as though\n * it were an age would throw every unit out. */\n const window = {\n t_age: t_age ?? -Infinity,\n b_age: b_age ?? Infinity,\n t_pos: t_pos ?? -Infinity,\n b_pos: b_pos ?? Infinity,\n };\n const isWindowed =\n axisType == ColumnAxisType.AGE\n ? t_age != null || b_age != null\n : t_pos != null || b_pos != null;\n\n if (clipBeforeLayout && isWindowed) {\n /** Prototype filtering to age range */\n units1 = units1.filter((d) => {\n // Filter units by t_age and b_age, inclusive\n if (axisType == ColumnAxisType.AGE) {\n return agesOverlap(d, window);\n } else {\n return unitsOverlap(d, window as any, axisType);\n }\n });\n }\n\n let mergeMode = mergeSections;\n // if (axisType != ColumnAxisType.AGE) {\n // // For non-age columns, we always merge sections.\n // // This is because the \"groupUnitsIntoSections\" function is not well-defined\n // // for non-age columns.\n // mergeMode = MergeSectionsMode.ALL;\n // }\n\n let sections0: SectionInfo<UnitLong>[];\n if (mergeMode == MergeSectionsMode.ALL) {\n // For the \"merge sections\" mode, we need to create a single section\n const [b_unit_pos, t_unit_pos] = getSectionPosRange(units1, axisType);\n const [b_unit_age, t_unit_age] = getSectionAgeRange(units1);\n sections0 = [\n {\n section_id: 0,\n /**\n * If ages limits are directly specified, use them to define the section bounds.\n * */\n t_pos: t_unit_pos,\n b_pos: b_unit_pos,\n t_age: t_unit_age,\n b_age: b_unit_age,\n units: units1,\n },\n ];\n } else if (axisType == ColumnAxisType.AGE) {\n sections0 = groupUnitsIntoSectionsBySectionID(units1, axisType);\n } else {\n sections0 = groupUnitsIntoSectionsByOverlap(units1, axisType);\n }\n\n // Limit sections to the range specified by t_age/b_age or t_pos/b_pos global options\n for (let section of clipBeforeLayout ? sections0 : []) {\n if (axisType == ColumnAxisType.AGE) {\n section.t_age = Math.max(section.t_age, t_age ?? -Infinity);\n section.b_age = Math.min(section.b_age, b_age ?? Infinity);\n } else if (axisType == ColumnAxisType.DEPTH) {\n section.t_pos = Math.max(section.t_pos, t_pos ?? -Infinity);\n section.b_pos = Math.min(section.b_pos, b_pos ?? Infinity);\n } else if (axisType == ColumnAxisType.HEIGHT) {\n section.t_pos = Math.max(section.t_pos, t_pos ?? -Infinity);\n section.b_pos = Math.min(section.b_pos, b_pos ?? Infinity);\n }\n }\n\n /** Merging overlapping sections really only makes sense for age/height/depth\n * columns. Ordinal columns are numbered by section so merging them\n * results in collisions.\n */\n let sections = sections0;\n if (\n mergeSections == MergeSectionsMode.OVERLAPPING &&\n axisType == ColumnAxisType.AGE\n ) {\n sections = mergeOverlappingSections(sections);\n }\n // Filter out undefined sections just in case\n sections = sections.filter((d) => d != null);\n\n // SCALES\n\n /** The window we've been asked to render. Unit density is derived from what\n * it shows (see `findAverageUnitHeight`), so it has to be known before any\n * section is laid out — but nothing is *clipped* to it until the very end.\n */\n const focalWindow: [number, number] | null = clipBeforeLayout\n ? null\n : [b_age ?? Infinity, t_age ?? -Infinity];\n\n const layoutOptions = { ...options, visibleWindow: focalWindow };\n\n /* Compute pixel scales etc. for sections\n * We need to do this now to determine which unconformities\n * are small enough to collapse.\n */\n let sectionsWithScales = computeSectionHeights(sections, layoutOptions);\n\n if (collapseSmallUnconformities && hybridScale == null) {\n // Collapse small unconformities in pixel height space\n // TODO: this doesn't seem to work properly for non-age columns?\n let threshold = unconformityHeight ?? 30;\n if (typeof collapseSmallUnconformities == \"number\") {\n threshold = collapseSmallUnconformities;\n }\n\n sectionsWithScales = collapseUnconformitiesByPixelHeight(\n sectionsWithScales,\n threshold,\n layoutOptions,\n );\n }\n\n /** Now that every section is laid out at full extent — with its own density\n * and `minSectionHeight` already settled — apply the rendered window. Padding\n * is spent here, against real pixel heights, so `windowPadding` px of an\n * abutting section is exactly that many pixels.\n */\n if (focalWindow != null && (t_age != null || b_age != null)) {\n // Resolve final densities first (a section the window cuts short is\n // stretched to `minSectionHeight`), then spend the padding budget against\n // them, so a margin is the pixels asked for rather than those pixels times\n // whatever stretch its neighbor happened to need.\n const floor =\n layoutOptions.minSectionHeight ?? layoutOptions.targetUnitHeight ?? 0;\n const scales = resolveWindowScales(sectionsWithScales, focalWindow, floor);\n const natural = (section) =>\n scales.get(section) ?? section.scaleInfo.pixelScale;\n\n const scaleFor = natural;\n\n const window =\n windowPadding > 0\n ? padWindowByPixels(\n sectionsWithScales,\n focalWindow,\n windowPadding,\n scaleFor,\n )\n : focalWindow;\n\n sectionsWithScales = trimSectionsToWindow(\n sectionsWithScales,\n window,\n scaleFor,\n );\n }\n\n /** Prepare section scale information using groups.\n * Total height is computed from section scales.\n * */\n let { totalHeight, sections: sections2 } = finalizeSectionHeights(\n sectionsWithScales,\n unconformityHeight,\n );\n\n /** For each section, find units that are overlapping.\n * We do this after merging sections so that we can\n * handle cases where there are overlapping units across sections\n * */\n const sectionsOut = sections2.map((section) => {\n return {\n ...section,\n units: preprocessUnits(section, axisType),\n };\n });\n\n /** Reconstitute the units so that they are sorted by section and properly enhanced.\n * This is mostly important so that unit keyboard navigation\n * predictably selects adjacent units.\n */\n const units2 = sectionsOut.reduce((acc, group) => {\n const { units } = group;\n for (const unit of units) {\n acc.push(unit);\n }\n return acc;\n }, []);\n\n return {\n units: units2,\n totalHeight: _totalHeight ?? totalHeight,\n sections: sectionsOut,\n };\n}\n"],"names":["useMemo","MergeSectionsMode","ColumnAxisType","preprocessSectionUnit","agesOverlap","unitsOverlap","getSectionPosRange","getSectionAgeRange","groupUnitsIntoSectionsBySectionID","groupUnitsIntoSectionsByOverlap","mergeOverlappingSections","computeSectionHeights","collapseUnconformitiesByPixelHeight","resolveWindowScales","window","padWindowByPixels","trimSectionsToWindow","finalizeSectionHeights","preprocessUnits","units"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/prepare-units/index.ts"],"sourcesContent":["import {\n getSectionAgeRange,\n getSectionPosRange,\n groupUnitsIntoSectionsByOverlap,\n groupUnitsIntoSectionsBySectionID,\n mergeOverlappingSections,\n preprocessSectionUnit,\n preprocessUnits,\n UnitWithLayoutHints,\n} from \"./helpers\";\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\nimport { useMemo } from \"react\";\nimport { UnitLong } from \"@macrostrat/api-types\";\nimport {\n collapseUnconformitiesByPixelHeight,\n computeSectionHeights,\n finalizeSectionHeights,\n padWindowByPixels,\n resolveWindowScales,\n trimSectionsToWindow,\n type CompositeColumnScale,\n} from \"./composite-scale\";\nimport {\n agesOverlap,\n MergeSectionsMode,\n PrepareColumnOptions,\n PreparedColumnData,\n unitsOverlap,\n} from \"./utils\";\nimport { type ColumnHeightScaleOptions, SectionInfo } from \"./types\";\n\nexport * from \"./utils\";\nexport * from \"./types\";\nexport * from \"./density\";\nexport { preprocessUnits };\nexport type { CompositeColumnScale };\n\nexport function usePreparedColumnUnits(\n data: UnitLong[],\n options: PrepareColumnOptions,\n): PreparedColumnData {\n /** This function wraps and memoizes all preparation steps for converting\n * an array of units from the /units route to a form ready for usage.\n */\n return useMemo(() => {\n return prepareColumnUnits(data, options);\n }, [data, ...Object.values(options)]);\n}\n\nexport function prepareColumnUnits(\n units: UnitWithLayoutHints<UnitLong>[],\n options: PrepareColumnOptions,\n): PreparedColumnData {\n /** Prepare units for rendering into Macrostrat columns */\n\n let { t_age, b_age, t_pos, b_pos } = options;\n\n const {\n mergeSections = MergeSectionsMode.OVERLAPPING,\n axisType,\n unconformityHeight,\n collapseSmallUnconformities = false,\n hybridScale,\n scale,\n windowPadding = 0,\n } = options;\n\n let _totalHeight: number | null = null;\n\n if (scale != null) {\n // Set t_age and b_age based on scale domain if not already set\n const domain = scale.domain();\n if (axisType == ColumnAxisType.AGE) {\n if (t_age == null) t_age = Math.min(...domain);\n if (b_age == null) b_age = Math.max(...domain);\n _totalHeight = Math.abs(scale(b_age) - scale(t_age));\n } else {\n if (t_pos == null) t_pos = Math.min(...domain);\n if (b_pos == null) b_pos = Math.max(...domain);\n _totalHeight = Math.abs(scale(b_pos) - scale(t_pos));\n }\n }\n\n /** Age columns lay every section out at its full extent and clip to the\n * window at the end (see below), so that a section abutting the window is\n * sized by its own content rather than by the sliver that survives the clip.\n * Hybrid and externally-supplied scales build their own mapping and can't be\n * re-derived that way, so they keep the original clip-then-lay-out path. */\n const clipBeforeLayout =\n axisType != ColumnAxisType.AGE || hybridScale != null || scale != null;\n\n // Start by ensuring that ages and positions are numbers\n // also set up some values for eODP-style columns\n let units1 = units.map(preprocessSectionUnit);\n\n /** A bound that isn't set doesn't bound anything. Passing `null` for one is\n * the ordinary way to say \"no window\", and comparing against it as though\n * it were an age would throw every unit out. */\n const window = {\n t_age: t_age ?? -Infinity,\n b_age: b_age ?? Infinity,\n t_pos: t_pos ?? -Infinity,\n b_pos: b_pos ?? Infinity,\n };\n const isWindowed =\n axisType == ColumnAxisType.AGE\n ? t_age != null || b_age != null\n : t_pos != null || b_pos != null;\n\n if (clipBeforeLayout && isWindowed) {\n /** Prototype filtering to age range */\n units1 = units1.filter((d) => {\n // Filter units by t_age and b_age, inclusive\n if (axisType == ColumnAxisType.AGE) {\n return agesOverlap(d, window);\n } else {\n return unitsOverlap(d, window as any, axisType);\n }\n });\n }\n\n let mergeMode = mergeSections;\n // if (axisType != ColumnAxisType.AGE) {\n // // For non-age columns, we always merge sections.\n // // This is because the \"groupUnitsIntoSections\" function is not well-defined\n // // for non-age columns.\n // mergeMode = MergeSectionsMode.ALL;\n // }\n\n let sections0: SectionInfo<UnitLong>[];\n if (mergeMode == MergeSectionsMode.ALL) {\n // For the \"merge sections\" mode, we need to create a single section\n const [b_unit_pos, t_unit_pos] = getSectionPosRange(units1, axisType);\n const [b_unit_age, t_unit_age] = getSectionAgeRange(units1);\n sections0 = [\n {\n section_id: 0,\n /**\n * If ages limits are directly specified, use them to define the section bounds.\n * */\n t_pos: t_unit_pos,\n b_pos: b_unit_pos,\n t_age: t_unit_age,\n b_age: b_unit_age,\n units: units1,\n },\n ];\n } else if (axisType == ColumnAxisType.AGE) {\n sections0 = groupUnitsIntoSectionsBySectionID(units1, axisType);\n } else {\n sections0 = groupUnitsIntoSectionsByOverlap(units1, axisType);\n }\n\n // Limit sections to the range specified by t_age/b_age or t_pos/b_pos global options\n for (let section of clipBeforeLayout ? sections0 : []) {\n if (axisType == ColumnAxisType.AGE) {\n section.t_age = Math.max(section.t_age, t_age ?? -Infinity);\n section.b_age = Math.min(section.b_age, b_age ?? Infinity);\n } else if (axisType == ColumnAxisType.DEPTH) {\n section.t_pos = Math.max(section.t_pos, t_pos ?? -Infinity);\n section.b_pos = Math.min(section.b_pos, b_pos ?? Infinity);\n } else if (axisType == ColumnAxisType.HEIGHT) {\n section.t_pos = Math.max(section.t_pos, t_pos ?? -Infinity);\n section.b_pos = Math.min(section.b_pos, b_pos ?? Infinity);\n }\n }\n\n /** Merging overlapping sections really only makes sense for age/height/depth\n * columns. Ordinal columns are numbered by section so merging them\n * results in collisions.\n */\n let sections = sections0;\n if (\n mergeSections == MergeSectionsMode.OVERLAPPING &&\n axisType == ColumnAxisType.AGE\n ) {\n sections = mergeOverlappingSections(sections);\n }\n // Filter out undefined sections just in case\n sections = sections.filter((d) => d != null);\n\n // SCALES\n\n /** The window we've been asked to render. Unit density is derived from what\n * it shows (see `findAverageUnitHeight`), so it has to be known before any\n * section is laid out — but nothing is *clipped* to it until the very end.\n */\n const focalWindow: [number, number] | null = clipBeforeLayout\n ? null\n : [b_age ?? Infinity, t_age ?? -Infinity];\n\n const layoutOptions = { ...options, visibleWindow: focalWindow };\n\n /* Compute pixel scales etc. for sections\n * We need to do this now to determine which unconformities\n * are small enough to collapse.\n */\n let sectionsWithScales = computeSectionHeights(sections, layoutOptions);\n\n if (collapseSmallUnconformities && hybridScale == null) {\n // Collapse small unconformities in pixel height space\n // TODO: this doesn't seem to work properly for non-age columns?\n let threshold = unconformityHeight ?? 30;\n if (typeof collapseSmallUnconformities == \"number\") {\n threshold = collapseSmallUnconformities;\n }\n\n sectionsWithScales = collapseUnconformitiesByPixelHeight(\n sectionsWithScales,\n threshold,\n layoutOptions,\n );\n }\n\n /** Now that every section is laid out at full extent — with its own density\n * and `minSectionHeight` already settled — apply the rendered window. Padding\n * is spent here, against real pixel heights, so `windowPadding` px of an\n * abutting section is exactly that many pixels.\n */\n if (focalWindow != null && (t_age != null || b_age != null)) {\n // Resolve final densities first (a section the window cuts short is\n // stretched to `minSectionHeight`), then spend the padding budget against\n // them, so a margin is the pixels asked for rather than those pixels times\n // whatever stretch its neighbor happened to need.\n const floor =\n layoutOptions.minSectionHeight ?? layoutOptions.targetUnitHeight ?? 0;\n const scales = resolveWindowScales(sectionsWithScales, focalWindow, floor);\n const natural = (section) =>\n scales.get(section) ?? section.scaleInfo.pixelScale;\n\n\n const scaleFor = natural;\n\n const window =\n windowPadding > 0\n ? padWindowByPixels(\n sectionsWithScales,\n focalWindow,\n windowPadding,\n scaleFor,\n )\n : focalWindow;\n\n sectionsWithScales = trimSectionsToWindow(\n sectionsWithScales,\n window,\n scaleFor,\n );\n }\n\n /** Prepare section scale information using groups.\n * Total height is computed from section scales.\n * */\n let { totalHeight, sections: sections2 } = finalizeSectionHeights(\n sectionsWithScales,\n unconformityHeight,\n );\n\n /** For each section, find units that are overlapping.\n * We do this after merging sections so that we can\n * handle cases where there are overlapping units across sections\n * */\n const sectionsOut = sections2.map((section) => {\n return {\n ...section,\n units: preprocessUnits(section, axisType),\n };\n });\n\n /** Reconstitute the units so that they are sorted by section and properly enhanced.\n * This is mostly important so that unit keyboard navigation\n * predictably selects adjacent units.\n */\n const units2 = sectionsOut.reduce((acc, group) => {\n const { units } = group;\n for (const unit of units) {\n acc.push(unit);\n }\n return acc;\n }, []);\n\n return {\n units: units2,\n totalHeight: _totalHeight ?? totalHeight,\n sections: sectionsOut,\n };\n}\n"],"names":["window","units"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/prepare-units/index.ts"],"sourcesContent":["import {\n getSectionAgeRange,\n getSectionPosRange,\n groupUnitsIntoSectionsByOverlap,\n groupUnitsIntoSectionsBySectionID,\n mergeOverlappingSections,\n preprocessSectionUnit,\n preprocessUnits,\n UnitWithLayoutHints,\n} from \"./helpers\";\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\nimport { useMemo } from \"react\";\nimport { UnitLong } from \"@macrostrat/api-types\";\nimport {\n collapseUnconformitiesByPixelHeight,\n computeSectionHeights,\n finalizeSectionHeights,\n padWindowByPixels,\n resolveWindowScales,\n trimSectionsToWindow,\n type CompositeColumnScale,\n} from \"./composite-scale\";\nimport {\n agesOverlap,\n MergeSectionsMode,\n PrepareColumnOptions,\n PreparedColumnData,\n unitsOverlap,\n} from \"./utils\";\nimport { type ColumnHeightScaleOptions, SectionInfo } from \"./types\";\n\nexport * from \"./utils\";\nexport * from \"./types\";\nexport * from \"./density\";\nexport { preprocessUnits };\nexport type { CompositeColumnScale };\n\nexport function usePreparedColumnUnits(\n data: UnitLong[],\n options: PrepareColumnOptions,\n): PreparedColumnData {\n /** This function wraps and memoizes all preparation steps for converting\n * an array of units from the /units route to a form ready for usage.\n */\n return useMemo(() => {\n return prepareColumnUnits(data, options);\n }, [data, ...Object.values(options)]);\n}\n\nexport function prepareColumnUnits(\n units: UnitWithLayoutHints<UnitLong>[],\n options: PrepareColumnOptions,\n): PreparedColumnData {\n /** Prepare units for rendering into Macrostrat columns */\n\n let { t_age, b_age, t_pos, b_pos } = options;\n\n const {\n mergeSections = MergeSectionsMode.OVERLAPPING,\n axisType,\n unconformityHeight,\n collapseSmallUnconformities = false,\n hybridScale,\n scale,\n windowPadding = 0,\n } = options;\n\n let _totalHeight: number | null = null;\n\n if (scale != null) {\n // Set t_age and b_age based on scale domain if not already set\n const domain = scale.domain();\n if (axisType == ColumnAxisType.AGE) {\n if (t_age == null) t_age = Math.min(...domain);\n if (b_age == null) b_age = Math.max(...domain);\n _totalHeight = Math.abs(scale(b_age) - scale(t_age));\n } else {\n if (t_pos == null) t_pos = Math.min(...domain);\n if (b_pos == null) b_pos = Math.max(...domain);\n _totalHeight = Math.abs(scale(b_pos) - scale(t_pos));\n }\n }\n\n /** Age columns lay every section out at its full extent and clip to the\n * window at the end (see below), so that a section abutting the window is\n * sized by its own content rather than by the sliver that survives the clip.\n * Hybrid and externally-supplied scales build their own mapping and can't be\n * re-derived that way, so they keep the original clip-then-lay-out path. */\n const clipBeforeLayout =\n axisType != ColumnAxisType.AGE || hybridScale != null || scale != null;\n\n // Start by ensuring that ages and positions are numbers\n // also set up some values for eODP-style columns\n let units1 = units.map(preprocessSectionUnit);\n\n /** A bound that isn't set doesn't bound anything. Passing `null` for one is\n * the ordinary way to say \"no window\", and comparing against it as though\n * it were an age would throw every unit out. */\n const window = {\n t_age: t_age ?? -Infinity,\n b_age: b_age ?? Infinity,\n t_pos: t_pos ?? -Infinity,\n b_pos: b_pos ?? Infinity,\n };\n const isWindowed =\n axisType == ColumnAxisType.AGE\n ? t_age != null || b_age != null\n : t_pos != null || b_pos != null;\n\n if (clipBeforeLayout && isWindowed) {\n /** Prototype filtering to age range */\n units1 = units1.filter((d) => {\n // Filter units by t_age and b_age, inclusive\n if (axisType == ColumnAxisType.AGE) {\n return agesOverlap(d, window);\n } else {\n return unitsOverlap(d, window as any, axisType);\n }\n });\n }\n\n let mergeMode = mergeSections;\n // if (axisType != ColumnAxisType.AGE) {\n // // For non-age columns, we always merge sections.\n // // This is because the \"groupUnitsIntoSections\" function is not well-defined\n // // for non-age columns.\n // mergeMode = MergeSectionsMode.ALL;\n // }\n\n let sections0: SectionInfo<UnitLong>[];\n if (mergeMode == MergeSectionsMode.ALL) {\n // For the \"merge sections\" mode, we need to create a single section\n const [b_unit_pos, t_unit_pos] = getSectionPosRange(units1, axisType);\n const [b_unit_age, t_unit_age] = getSectionAgeRange(units1);\n sections0 = [\n {\n section_id: 0,\n /**\n * If ages limits are directly specified, use them to define the section bounds.\n * */\n t_pos: t_unit_pos,\n b_pos: b_unit_pos,\n t_age: t_unit_age,\n b_age: b_unit_age,\n units: units1,\n },\n ];\n } else if (axisType == ColumnAxisType.AGE) {\n sections0 = groupUnitsIntoSectionsBySectionID(units1, axisType);\n } else {\n sections0 = groupUnitsIntoSectionsByOverlap(units1, axisType);\n }\n\n // Limit sections to the range specified by t_age/b_age or t_pos/b_pos global options\n for (let section of clipBeforeLayout ? sections0 : []) {\n if (axisType == ColumnAxisType.AGE) {\n section.t_age = Math.max(section.t_age, t_age ?? -Infinity);\n section.b_age = Math.min(section.b_age, b_age ?? Infinity);\n } else if (axisType == ColumnAxisType.DEPTH) {\n section.t_pos = Math.max(section.t_pos, t_pos ?? -Infinity);\n section.b_pos = Math.min(section.b_pos, b_pos ?? Infinity);\n } else if (axisType == ColumnAxisType.HEIGHT) {\n section.t_pos = Math.max(section.t_pos, t_pos ?? -Infinity);\n section.b_pos = Math.min(section.b_pos, b_pos ?? Infinity);\n }\n }\n\n /** Merging overlapping sections really only makes sense for age/height/depth\n * columns. Ordinal columns are numbered by section so merging them\n * results in collisions.\n */\n let sections = sections0;\n if (\n mergeSections == MergeSectionsMode.OVERLAPPING &&\n axisType == ColumnAxisType.AGE\n ) {\n sections = mergeOverlappingSections(sections);\n }\n // Filter out undefined sections just in case\n sections = sections.filter((d) => d != null);\n\n // SCALES\n\n /** The window we've been asked to render. Unit density is derived from what\n * it shows (see `findAverageUnitHeight`), so it has to be known before any\n * section is laid out — but nothing is *clipped* to it until the very end.\n */\n const focalWindow: [number, number] | null = clipBeforeLayout\n ? null\n : [b_age ?? Infinity, t_age ?? -Infinity];\n\n const layoutOptions = { ...options, visibleWindow: focalWindow };\n\n /* Compute pixel scales etc. for sections\n * We need to do this now to determine which unconformities\n * are small enough to collapse.\n */\n let sectionsWithScales = computeSectionHeights(sections, layoutOptions);\n\n if (collapseSmallUnconformities && hybridScale == null) {\n // Collapse small unconformities in pixel height space\n // TODO: this doesn't seem to work properly for non-age columns?\n let threshold = unconformityHeight ?? 30;\n if (typeof collapseSmallUnconformities == \"number\") {\n threshold = collapseSmallUnconformities;\n }\n\n sectionsWithScales = collapseUnconformitiesByPixelHeight(\n sectionsWithScales,\n threshold,\n layoutOptions,\n );\n }\n\n /** Now that every section is laid out at full extent — with its own density\n * and `minSectionHeight` already settled — apply the rendered window. Padding\n * is spent here, against real pixel heights, so `windowPadding` px of an\n * abutting section is exactly that many pixels.\n */\n if (focalWindow != null && (t_age != null || b_age != null)) {\n // Resolve final densities first (a section the window cuts short is\n // stretched to `minSectionHeight`), then spend the padding budget against\n // them, so a margin is the pixels asked for rather than those pixels times\n // whatever stretch its neighbor happened to need.\n const floor =\n layoutOptions.minSectionHeight ?? layoutOptions.targetUnitHeight ?? 0;\n const scales = resolveWindowScales(sectionsWithScales, focalWindow, floor);\n const natural = (section) =>\n scales.get(section) ?? section.scaleInfo.pixelScale;\n\n const scaleFor = natural;\n\n const window =\n windowPadding > 0\n ? padWindowByPixels(\n sectionsWithScales,\n focalWindow,\n windowPadding,\n scaleFor,\n )\n : focalWindow;\n\n sectionsWithScales = trimSectionsToWindow(\n sectionsWithScales,\n window,\n scaleFor,\n );\n }\n\n /** Prepare section scale information using groups.\n * Total height is computed from section scales.\n * */\n let { totalHeight, sections: sections2 } = finalizeSectionHeights(\n sectionsWithScales,\n unconformityHeight,\n );\n\n /** For each section, find units that are overlapping.\n * We do this after merging sections so that we can\n * handle cases where there are overlapping units across sections\n * */\n const sectionsOut = sections2.map((section) => {\n return {\n ...section,\n units: preprocessUnits(section, axisType),\n };\n });\n\n /** Reconstitute the units so that they are sorted by section and properly enhanced.\n * This is mostly important so that unit keyboard navigation\n * predictably selects adjacent units.\n */\n const units2 = sectionsOut.reduce((acc, group) => {\n const { units } = group;\n for (const unit of units) {\n acc.push(unit);\n }\n return acc;\n }, []);\n\n return {\n units: units2,\n totalHeight: _totalHeight ?? totalHeight,\n sections: sectionsOut,\n };\n}\n"],"names":["window","units"],"mappings":"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"}
@@ -32,19 +32,6 @@ function AgeRange({
32
32
  className
33
33
  });
34
34
  }
35
- function AgeLabel({
36
- age,
37
- maximumFractionDigits = 2,
38
- minimumFractionDigits = 0,
39
- className
40
- }) {
41
- const [value, unit] = getAge(age);
42
- const _value = value.toLocaleString("en-US", {
43
- maximumFractionDigits,
44
- minimumFractionDigits
45
- });
46
- return ageRange_module.default(dataComponents.Value, { value: _value, unit, className });
47
- }
48
35
  function Duration({
49
36
  value,
50
37
  maximumFractionDigits = 2,
@@ -110,9 +97,9 @@ function IntervalProportions({
110
97
  let p0 = null;
111
98
  let p1 = null;
112
99
  if (_showProps !== false) {
113
- p1 = ageRange_module.default(Proportion, { value: t_prop });
100
+ p1 = ageRange_module.default(dataComponents.IntervalProportion, { value: t_prop });
114
101
  if (i0 !== i1 || b_prop !== 0 || t_prop !== 1) {
115
- p0 = ageRange_module.default(Proportion, { value: b_prop });
102
+ p0 = ageRange_module.default(dataComponents.IntervalProportion, { value: b_prop });
116
103
  }
117
104
  if (i0 === i1 && (b_prop !== 0 || t_prop !== 1)) {
118
105
  p0 = ageRange_module.default("span.joint-proportion", [p0, " ", ageRange_module.default("span.sep", "to"), " ", p1]);
@@ -145,17 +132,6 @@ function IntervalProportions({
145
132
  function getProportion(age, interval) {
146
133
  return (interval.b_age - age) / (interval.b_age - interval.t_age);
147
134
  }
148
- function Proportion({ value }) {
149
- let content = null;
150
- if (value == 0) {
151
- content = "base";
152
- } else if (value == 1) {
153
- content = "top";
154
- } else {
155
- content = utils.formatProportion(value * 100) + "%";
156
- }
157
- return ageRange_module.default("span.proportion", content);
158
- }
159
135
  function getAgeRange(_unit) {
160
136
  let b_age = _unit.b_age;
161
137
  let t_age = _unit.t_age;
@@ -171,27 +147,21 @@ function getAgeRange(_unit) {
171
147
  }
172
148
  return [b_age, t_age, unit];
173
149
  }
174
- function getAge(value) {
175
- let unit = "Ma";
176
- if (value < 0.8) {
177
- unit = "ka";
178
- value *= 1e3;
179
- if (value < 5) {
180
- unit = "yr";
181
- value *= 1e3;
182
- }
183
- } else if (value > 1e3) {
184
- unit = "Ga";
185
- value /= 1e3;
186
- }
187
- return [value, unit];
188
- }
150
+ Object.defineProperty(exports, "AgeLabel", {
151
+ enumerable: true,
152
+ get: () => dataComponents.AgeLabel
153
+ });
154
+ Object.defineProperty(exports, "Proportion", {
155
+ enumerable: true,
156
+ get: () => dataComponents.IntervalProportion
157
+ });
158
+ Object.defineProperty(exports, "getAge", {
159
+ enumerable: true,
160
+ get: () => dataComponents.getAge
161
+ });
189
162
  exports.AgeField = AgeField;
190
- exports.AgeLabel = AgeLabel;
191
163
  exports.AgeRange = AgeRange;
192
164
  exports.Duration = Duration;
193
165
  exports.IntervalProportions = IntervalProportions;
194
- exports.Proportion = Proportion;
195
- exports.getAge = getAge;
196
166
  exports.getAgeRange = getAgeRange;
197
167
  //# sourceMappingURL=age-range.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"age-range.cjs","sources":["../../src/unit-details/age-range.ts"],"sourcesContent":["import {\n DataField,\n IntervalShort,\n IntervalTag,\n type IntervalTagProps,\n ItemList,\n Value,\n} from \"@macrostrat/data-components\";\nimport { useMacrostratDefs } from \"@macrostrat/data-provider\";\nimport h from \"./age-range.module.sass\";\nimport { formatProportion, formatRange } from \"./utils\";\nimport classNames from \"classnames\";\nimport { ReactNode, useMemo } from \"react\";\n\nexport function AgeField({ unit, children, ...rest }) {\n const [_b_age, _t_age, _unit] = getAgeRange(unit);\n\n return h(\n DataField,\n {\n label: \"Age\",\n value: formatRange(_b_age, _t_age),\n unit: _unit,\n ...rest,\n },\n children,\n );\n}\n\nexport function AgeRange({\n data,\n className,\n}: {\n data: {\n t_age: number;\n b_age: number;\n };\n className?: string;\n}) {\n const [_b_age, _t_age, _unit] = getAgeRange(data);\n\n return h(Value, {\n value: formatRange(_b_age, _t_age),\n unit: _unit,\n className,\n });\n}\n\nexport function AgeLabel({\n age,\n maximumFractionDigits = 2,\n minimumFractionDigits = 0,\n className,\n}: {\n age: number;\n className?: string;\n maximumFractionDigits?: number;\n minimumFractionDigits?: number;\n}) {\n /** Component to display a single age value with unit conversion from\n * Ma to ka or Ga as appropriate.\n */\n const [value, unit] = getAge(age);\n\n const _value = value.toLocaleString(\"en-US\", {\n maximumFractionDigits,\n minimumFractionDigits,\n });\n\n return h(Value, { value: _value, unit, className });\n}\n\nexport function Duration({\n value,\n maximumFractionDigits = 2,\n minimumFractionDigits = 0,\n}) {\n let unit = \"Myr\";\n if (value < 0.8) {\n unit = \"kyr\";\n value *= 1000;\n if (value < 5) {\n unit = \"yr\";\n value *= 1000;\n }\n } else if (value > 1000) {\n unit = \"Gyr\";\n value /= 1000;\n }\n\n let _value = value.toLocaleString(\"en-US\", {\n maximumFractionDigits,\n minimumFractionDigits,\n });\n\n return h(Value, { value: _value, unit });\n}\n\ninterface UnitIntervalConstraints {\n b_int_id?: number;\n t_int_id?: number;\n b_prop?: number;\n t_prop?: number;\n b_int_name?: string;\n t_int_name?: string;\n b_age?: number;\n t_age?: number;\n}\n\nexport interface IntervalProportionsProps extends Omit<\n IntervalTagProps,\n \"interval\"\n> {\n unit: UnitIntervalConstraints;\n showProportions?: boolean;\n calculateProportionsFromAges?: boolean;\n onClickItem?: any;\n}\n\nexport function IntervalProportions({\n unit,\n className,\n showProportions,\n calculateProportionsFromAges = true,\n ...rest\n}: IntervalProportionsProps) {\n /** Display the proportions of the unit that belong to the base and top intervals, if they are different */\n if (unit.b_int_id == null && unit.t_int_id == null) return null;\n\n let _showProps = showProportions;\n\n const i0 = unit.b_int_id;\n const i1 = unit.t_int_id;\n\n const ints = new Set([i0, i1]);\n\n /** Get interval information */\n const intervalMap = useMacrostratDefs(\"intervals\", Array.from(ints), null);\n const [int0, int1] = useMemo(() => {\n const int0 = intervalMap?.get(i0) ?? {};\n if (i0 === i1) {\n return [int0, int0];\n }\n const int1 = intervalMap?.get(i1) ?? {};\n return [int0, int1];\n }, [intervalMap, i0, i1]);\n\n const interval0: IntervalShort = {\n id: i0,\n name: unit.b_int_name,\n ...int0,\n };\n\n let b_prop = unit.b_prop;\n let t_prop = unit.t_prop;\n\n // If we have b_age and t_age, then we can calculate proportions if they are not given\n if (b_prop == null && unit.b_age != null && calculateProportionsFromAges) {\n b_prop = getProportion(unit.b_age, int0);\n }\n if (t_prop == null && unit.t_age != null && calculateProportionsFromAges) {\n t_prop = getProportion(unit.t_age, int1);\n }\n\n // If there are no proportions given and we are not explicitly directed to show them,\n // then we set them to hidden.\n if (b_prop == null && t_prop == null) {\n _showProps ??= false;\n }\n\n /*\n Set default proportions to 0 and 1 if they are not given,\n so that we can display \"base\" and \"top\" labels\n */\n b_prop ??= 0;\n t_prop ??= 1;\n\n let p0: any = null;\n let p1: any = null;\n\n if (_showProps !== false) {\n p1 = h(Proportion, { value: t_prop });\n\n if (i0 !== i1 || b_prop !== 0 || t_prop !== 1) {\n // We have a single interval with undefined proportions\n p0 = h(Proportion, { value: b_prop });\n }\n\n if (i0 === i1 && (b_prop !== 0 || t_prop !== 1)) {\n p0 = h(\"span.joint-proportion\", [p0, \" \", h(\"span.sep\", \"to\"), \" \", p1]);\n }\n }\n\n return h(\n ItemList,\n { className: classNames(\"interval-proportions\", className) },\n [\n h(IntervalTag, {\n interval: interval0,\n prefix: p0,\n ...rest,\n }),\n h.if(i0 != i1)(\"span.discourage-break\", [\n h(\"span.sep\", \" to \"),\n h(IntervalTag, {\n interval: {\n id: i1,\n name: unit.t_int_name,\n ...int1,\n },\n prefix: p1,\n ...rest,\n }),\n ]),\n ],\n );\n}\n\nfunction getProportion(age: number, interval: IntervalShort): number | null {\n /** Get proportion with the age */\n return (interval.b_age - age) / (interval.b_age - interval.t_age);\n}\n\nexport function Proportion({ value }: { value: number }) {\n let content: ReactNode | null = null;\n if (value == 0) {\n content = \"base\";\n } else if (value == 1) {\n content = \"top\";\n } else {\n content = formatProportion(value * 100) + \"%\";\n }\n\n return h(\"span.proportion\", content);\n}\n\nexport function getAgeRange(_unit) {\n let b_age = _unit.b_age;\n let t_age = _unit.t_age;\n let unit = \"Ma\";\n\n if (b_age < 0.8 && t_age < 1.2) {\n b_age *= 1000;\n t_age *= 1000;\n unit = \"ka\";\n } else if (b_age > 800 && t_age > 1200) {\n b_age /= 1000;\n t_age /= 1000;\n unit = \"Ga\";\n }\n\n return [b_age, t_age, unit];\n}\n\nexport function getAge(value) {\n /** Get the age value in Ma, ka, or Ga as appropriate */\n let unit = \"Ma\";\n if (value < 0.8) {\n unit = \"ka\";\n value *= 1000;\n if (value < 5) {\n unit = \"yr\";\n value *= 1000;\n }\n } else if (value > 1000) {\n unit = \"Ga\";\n value /= 1000;\n }\n\n return [value, unit];\n}\n"],"names":["h","DataField","formatRange","Value","useMacrostratDefs","useMemo","int0","int1","ItemList","classNames","IntervalTag","formatProportion"],"mappings":"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"}
1
+ {"version":3,"file":"age-range.cjs","sources":["../../src/unit-details/age-range.ts"],"sourcesContent":["import {\n DataField,\n IntervalProportion,\n IntervalShort,\n IntervalTag,\n type IntervalTagProps,\n ItemList,\n Value,\n} from \"@macrostrat/data-components\";\nimport { useMacrostratDefs } from \"@macrostrat/data-provider\";\nimport h from \"./age-range.module.sass\";\nimport { formatRange } from \"./utils\";\nimport classNames from \"classnames\";\nimport { useMemo } from \"react\";\n\n// The interval tag, its proportion and single-age label live in\n// `@macrostrat/data-components`; re-exported under their names here.\nexport {\n AgeLabel,\n getAge,\n IntervalProportion as Proportion,\n} from \"@macrostrat/data-components\";\n\nexport function AgeField({ unit, children, ...rest }) {\n const [_b_age, _t_age, _unit] = getAgeRange(unit);\n\n return h(\n DataField,\n {\n label: \"Age\",\n value: formatRange(_b_age, _t_age),\n unit: _unit,\n ...rest,\n },\n children,\n );\n}\n\nexport function AgeRange({\n data,\n className,\n}: {\n data: {\n t_age: number;\n b_age: number;\n };\n className?: string;\n}) {\n const [_b_age, _t_age, _unit] = getAgeRange(data);\n\n return h(Value, {\n value: formatRange(_b_age, _t_age),\n unit: _unit,\n className,\n });\n}\n\nexport function Duration({\n value,\n maximumFractionDigits = 2,\n minimumFractionDigits = 0,\n}) {\n let unit = \"Myr\";\n if (value < 0.8) {\n unit = \"kyr\";\n value *= 1000;\n if (value < 5) {\n unit = \"yr\";\n value *= 1000;\n }\n } else if (value > 1000) {\n unit = \"Gyr\";\n value /= 1000;\n }\n\n let _value = value.toLocaleString(\"en-US\", {\n maximumFractionDigits,\n minimumFractionDigits,\n });\n\n return h(Value, { value: _value, unit });\n}\n\ninterface UnitIntervalConstraints {\n b_int_id?: number;\n t_int_id?: number;\n b_prop?: number;\n t_prop?: number;\n b_int_name?: string;\n t_int_name?: string;\n b_age?: number;\n t_age?: number;\n}\n\nexport interface IntervalProportionsProps extends Omit<\n IntervalTagProps,\n \"interval\"\n> {\n unit: UnitIntervalConstraints;\n showProportions?: boolean;\n calculateProportionsFromAges?: boolean;\n onClickItem?: any;\n}\n\nexport function IntervalProportions({\n unit,\n className,\n showProportions,\n calculateProportionsFromAges = true,\n ...rest\n}: IntervalProportionsProps) {\n /** Display the proportions of the unit that belong to the base and top intervals, if they are different */\n if (unit.b_int_id == null && unit.t_int_id == null) return null;\n\n let _showProps = showProportions;\n\n const i0 = unit.b_int_id;\n const i1 = unit.t_int_id;\n\n const ints = new Set([i0, i1]);\n\n /** Get interval information */\n const intervalMap = useMacrostratDefs(\"intervals\", Array.from(ints), null);\n const [int0, int1] = useMemo(() => {\n const int0 = intervalMap?.get(i0) ?? {};\n if (i0 === i1) {\n return [int0, int0];\n }\n const int1 = intervalMap?.get(i1) ?? {};\n return [int0, int1];\n }, [intervalMap, i0, i1]);\n\n const interval0: IntervalShort = {\n id: i0,\n name: unit.b_int_name,\n ...int0,\n };\n\n let b_prop = unit.b_prop;\n let t_prop = unit.t_prop;\n\n // If we have b_age and t_age, then we can calculate proportions if they are not given\n if (b_prop == null && unit.b_age != null && calculateProportionsFromAges) {\n b_prop = getProportion(unit.b_age, int0);\n }\n if (t_prop == null && unit.t_age != null && calculateProportionsFromAges) {\n t_prop = getProportion(unit.t_age, int1);\n }\n\n // If there are no proportions given and we are not explicitly directed to show them,\n // then we set them to hidden.\n if (b_prop == null && t_prop == null) {\n _showProps ??= false;\n }\n\n /*\n Set default proportions to 0 and 1 if they are not given,\n so that we can display \"base\" and \"top\" labels\n */\n b_prop ??= 0;\n t_prop ??= 1;\n\n let p0: any = null;\n let p1: any = null;\n\n if (_showProps !== false) {\n p1 = h(IntervalProportion, { value: t_prop });\n\n if (i0 !== i1 || b_prop !== 0 || t_prop !== 1) {\n // We have a single interval with undefined proportions\n p0 = h(IntervalProportion, { value: b_prop });\n }\n\n if (i0 === i1 && (b_prop !== 0 || t_prop !== 1)) {\n p0 = h(\"span.joint-proportion\", [p0, \" \", h(\"span.sep\", \"to\"), \" \", p1]);\n }\n }\n\n return h(\n ItemList,\n { className: classNames(\"interval-proportions\", className) },\n [\n h(IntervalTag, {\n interval: interval0,\n prefix: p0,\n ...rest,\n }),\n h.if(i0 != i1)(\"span.discourage-break\", [\n h(\"span.sep\", \" to \"),\n h(IntervalTag, {\n interval: {\n id: i1,\n name: unit.t_int_name,\n ...int1,\n },\n prefix: p1,\n ...rest,\n }),\n ]),\n ],\n );\n}\n\nfunction getProportion(age: number, interval: IntervalShort): number | null {\n /** Get proportion with the age */\n return (interval.b_age - age) / (interval.b_age - interval.t_age);\n}\n\nexport function getAgeRange(_unit) {\n let b_age = _unit.b_age;\n let t_age = _unit.t_age;\n let unit = \"Ma\";\n\n if (b_age < 0.8 && t_age < 1.2) {\n b_age *= 1000;\n t_age *= 1000;\n unit = \"ka\";\n } else if (b_age > 800 && t_age > 1200) {\n b_age /= 1000;\n t_age /= 1000;\n unit = \"Ga\";\n }\n\n return [b_age, t_age, unit];\n}\n"],"names":["h","DataField","formatRange","Value","useMacrostratDefs","useMemo","int0","int1","IntervalProportion","ItemList","classNames","IntervalTag"],"mappings":";;;;;;;;;;AAuBO,SAAS,SAAS,EAAE,MAAM,UAAU,GAAG,QAAQ;AACpD,QAAM,CAAC,QAAQ,QAAQ,KAAK,IAAI,YAAY,IAAI;AAEhD,SAAOA,gBAAAA;AAAAA,IACLC,eAAAA;AAAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAOC,MAAAA,YAAY,QAAQ,MAAM;AAAA,MACjC,MAAM;AAAA,MACN,GAAG;AAAA,IAAA;AAAA,IAEL;AAAA,EAAA;AAEJ;AAEO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AACF,GAMG;AACD,QAAM,CAAC,QAAQ,QAAQ,KAAK,IAAI,YAAY,IAAI;AAEhD,SAAOF,gBAAAA,QAAEG,eAAAA,OAAO;AAAA,IACd,OAAOD,MAAAA,YAAY,QAAQ,MAAM;AAAA,IACjC,MAAM;AAAA,IACN;AAAA,EAAA,CACD;AACH;AAEO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA,wBAAwB;AAAA,EACxB,wBAAwB;AAC1B,GAAG;AACD,MAAI,OAAO;AACX,MAAI,QAAQ,KAAK;AACf,WAAO;AACP,aAAS;AACT,QAAI,QAAQ,GAAG;AACb,aAAO;AACP,eAAS;AAAA,IACX;AAAA,EACF,WAAW,QAAQ,KAAM;AACvB,WAAO;AACP,aAAS;AAAA,EACX;AAEA,MAAI,SAAS,MAAM,eAAe,SAAS;AAAA,IACzC;AAAA,IACA;AAAA,EAAA,CACD;AAED,SAAOF,gBAAAA,QAAEG,eAAAA,OAAO,EAAE,OAAO,QAAQ,MAAM;AACzC;AAuBO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA,+BAA+B;AAAA,EAC/B,GAAG;AACL,GAA6B;AAE3B,MAAI,KAAK,YAAY,QAAQ,KAAK,YAAY,KAAM,QAAO;AAE3D,MAAI,aAAa;AAEjB,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,KAAK;AAEhB,QAAM,OAAO,oBAAI,IAAI,CAAC,IAAI,EAAE,CAAC;AAG7B,QAAM,cAAcC,aAAAA,kBAAkB,aAAa,MAAM,KAAK,IAAI,GAAG,IAAI;AACzE,QAAM,CAAC,MAAM,IAAI,IAAIC,MAAAA,QAAQ,MAAM;AACjC,UAAMC,QAAO,aAAa,IAAI,EAAE,KAAK,CAAA;AACrC,QAAI,OAAO,IAAI;AACb,aAAO,CAACA,OAAMA,KAAI;AAAA,IACpB;AACA,UAAMC,QAAO,aAAa,IAAI,EAAE,KAAK,CAAA;AACrC,WAAO,CAACD,OAAMC,KAAI;AAAA,EACpB,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC;AAExB,QAAM,YAA2B;AAAA,IAC/B,IAAI;AAAA,IACJ,MAAM,KAAK;AAAA,IACX,GAAG;AAAA,EAAA;AAGL,MAAI,SAAS,KAAK;AAClB,MAAI,SAAS,KAAK;AAGlB,MAAI,UAAU,QAAQ,KAAK,SAAS,QAAQ,8BAA8B;AACxE,aAAS,cAAc,KAAK,OAAO,IAAI;AAAA,EACzC;AACA,MAAI,UAAU,QAAQ,KAAK,SAAS,QAAQ,8BAA8B;AACxE,aAAS,cAAc,KAAK,OAAO,IAAI;AAAA,EACzC;AAIA,MAAI,UAAU,QAAQ,UAAU,MAAM;AACpC,mBAAe;AAAA,EACjB;AAMA,aAAW;AACX,aAAW;AAEX,MAAI,KAAU;AACd,MAAI,KAAU;AAEd,MAAI,eAAe,OAAO;AACxB,SAAKP,gBAAAA,QAAEQ,eAAAA,oBAAoB,EAAE,OAAO,QAAQ;AAE5C,QAAI,OAAO,MAAM,WAAW,KAAK,WAAW,GAAG;AAE7C,WAAKR,gBAAAA,QAAEQ,eAAAA,oBAAoB,EAAE,OAAO,QAAQ;AAAA,IAC9C;AAEA,QAAI,OAAO,OAAO,WAAW,KAAK,WAAW,IAAI;AAC/C,WAAKR,gBAAAA,QAAE,yBAAyB,CAAC,IAAI,KAAKA,wBAAE,YAAY,IAAI,GAAG,KAAK,EAAE,CAAC;AAAA,IACzE;AAAA,EACF;AAEA,SAAOA,gBAAAA;AAAAA,IACLS,eAAAA;AAAAA,IACA,EAAE,WAAWC,oBAAAA,QAAW,wBAAwB,SAAS,EAAA;AAAA,IACzD;AAAA,MACEV,gBAAAA,QAAEW,eAAAA,aAAa;AAAA,QACb,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,GAAG;AAAA,MAAA,CACJ;AAAA,MACDX,gBAAAA,QAAE,GAAG,MAAM,EAAE,EAAE,yBAAyB;AAAA,QACtCA,gBAAAA,QAAE,YAAY,MAAM;AAAA,QACpBA,gBAAAA,QAAEW,eAAAA,aAAa;AAAA,UACb,UAAU;AAAA,YACR,IAAI;AAAA,YACJ,MAAM,KAAK;AAAA,YACX,GAAG;AAAA,UAAA;AAAA,UAEL,QAAQ;AAAA,UACR,GAAG;AAAA,QAAA,CACJ;AAAA,MAAA,CACF;AAAA,IAAA;AAAA,EACH;AAEJ;AAEA,SAAS,cAAc,KAAa,UAAwC;AAE1E,UAAQ,SAAS,QAAQ,QAAQ,SAAS,QAAQ,SAAS;AAC7D;AAEO,SAAS,YAAY,OAAO;AACjC,MAAI,QAAQ,MAAM;AAClB,MAAI,QAAQ,MAAM;AAClB,MAAI,OAAO;AAEX,MAAI,QAAQ,OAAO,QAAQ,KAAK;AAC9B,aAAS;AACT,aAAS;AACT,WAAO;AAAA,EACT,WAAW,QAAQ,OAAO,QAAQ,MAAM;AACtC,aAAS;AACT,aAAS;AACT,WAAO;AAAA,EACT;AAEA,SAAO,CAAC,OAAO,OAAO,IAAI;AAC5B;;;;;;;;;;;;;;;;;;"}