@macrostrat/column-views 3.11.0 → 3.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +184 -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/data.cjs +57 -0
  7. package/dist/age-model/data.cjs.map +1 -0
  8. package/dist/age-model/data.d.ts +29 -0
  9. package/dist/age-model/data.js +57 -0
  10. package/dist/age-model/data.js.map +1 -0
  11. package/dist/age-model/details.cjs +260 -0
  12. package/dist/age-model/details.cjs.map +1 -0
  13. package/dist/age-model/details.d.ts +39 -0
  14. package/dist/age-model/details.js +257 -0
  15. package/dist/age-model/details.js.map +1 -0
  16. package/dist/age-model/details.module.sass.cjs +42 -0
  17. package/dist/age-model/details.module.sass.cjs.map +1 -0
  18. package/dist/age-model/details.module.sass.js +40 -0
  19. package/dist/age-model/details.module.sass.js.map +1 -0
  20. package/dist/age-model/index.d.ts +5 -0
  21. package/dist/age-model/legacy.cjs +28 -0
  22. package/dist/age-model/legacy.cjs.map +1 -0
  23. package/dist/age-model/legacy.d.ts +6 -0
  24. package/dist/age-model/legacy.js +26 -0
  25. package/dist/age-model/legacy.js.map +1 -0
  26. package/dist/age-model/surfaces.cjs +354 -0
  27. package/dist/age-model/surfaces.cjs.map +1 -0
  28. package/dist/age-model/surfaces.d.ts +68 -0
  29. package/dist/age-model/surfaces.js +351 -0
  30. package/dist/age-model/surfaces.js.map +1 -0
  31. package/dist/age-model/surfaces.module.sass.cjs +45 -0
  32. package/dist/age-model/surfaces.module.sass.cjs.map +1 -0
  33. package/dist/age-model/surfaces.module.sass.js +43 -0
  34. package/dist/age-model/surfaces.module.sass.js.map +1 -0
  35. package/dist/age-model/types.cjs +229 -0
  36. package/dist/age-model/types.cjs.map +1 -0
  37. package/dist/age-model/types.d.ts +112 -0
  38. package/dist/age-model/types.js +229 -0
  39. package/dist/age-model/types.js.map +1 -0
  40. package/dist/column-views.css +406 -74
  41. package/dist/column.cjs +20 -13
  42. package/dist/column.cjs.map +1 -1
  43. package/dist/column.d.ts +21 -6
  44. package/dist/column.js +21 -14
  45. package/dist/column.js.map +1 -1
  46. package/dist/column.module.sass.cjs +19 -15
  47. package/dist/column.module.sass.cjs.map +1 -1
  48. package/dist/column.module.sass.js +19 -15
  49. package/dist/column.module.sass.js.map +1 -1
  50. package/dist/data-provider/store.cjs +34 -1
  51. package/dist/data-provider/store.cjs.map +1 -1
  52. package/dist/data-provider/store.d.ts +5 -0
  53. package/dist/data-provider/store.js +35 -2
  54. package/dist/data-provider/store.js.map +1 -1
  55. package/dist/index.cjs +78 -18
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.ts +3 -1
  58. package/dist/index.js +55 -4
  59. package/dist/index.js.map +1 -1
  60. package/dist/notes.cjs +11 -2
  61. package/dist/notes.cjs.map +1 -1
  62. package/dist/notes.d.ts +11 -1
  63. package/dist/notes.js +11 -2
  64. package/dist/notes.js.map +1 -1
  65. package/dist/prepare-units/composite-scale.cjs +18 -16
  66. package/dist/prepare-units/composite-scale.cjs.map +1 -1
  67. package/dist/prepare-units/composite-scale.js +18 -16
  68. package/dist/prepare-units/composite-scale.js.map +1 -1
  69. package/dist/prepare-units/density.cjs +83 -0
  70. package/dist/prepare-units/density.cjs.map +1 -0
  71. package/dist/prepare-units/density.d.ts +92 -0
  72. package/dist/prepare-units/density.js +83 -0
  73. package/dist/prepare-units/density.js.map +1 -0
  74. package/dist/prepare-units/index.cjs +15 -7
  75. package/dist/prepare-units/index.cjs.map +1 -1
  76. package/dist/prepare-units/index.d.ts +1 -0
  77. package/dist/prepare-units/index.js +15 -7
  78. package/dist/prepare-units/index.js.map +1 -1
  79. package/dist/prepare-units/types.cjs.map +1 -1
  80. package/dist/prepare-units/types.d.ts +34 -13
  81. package/dist/prepare-units/types.js.map +1 -1
  82. package/dist/section.cjs +108 -24
  83. package/dist/section.cjs.map +1 -1
  84. package/dist/section.d.ts +66 -6
  85. package/dist/section.js +109 -25
  86. package/dist/section.js.map +1 -1
  87. package/dist/timescale-zoom.cjs +191 -0
  88. package/dist/timescale-zoom.cjs.map +1 -0
  89. package/dist/timescale-zoom.d.ts +74 -0
  90. package/dist/timescale-zoom.js +191 -0
  91. package/dist/timescale-zoom.js.map +1 -0
  92. package/dist/unit-details/age-range.cjs +14 -43
  93. package/dist/unit-details/age-range.cjs.map +1 -1
  94. package/dist/unit-details/age-range.d.ts +1 -13
  95. package/dist/unit-details/age-range.js +6 -43
  96. package/dist/unit-details/age-range.js.map +1 -1
  97. package/dist/unit-details/utils.cjs +0 -5
  98. package/dist/unit-details/utils.cjs.map +1 -1
  99. package/dist/unit-details/utils.js +0 -5
  100. package/dist/unit-details/utils.js.map +1 -1
  101. package/dist/units/composite.cjs +4 -1
  102. package/dist/units/composite.cjs.map +1 -1
  103. package/dist/units/composite.d.ts +2 -0
  104. package/dist/units/composite.js +4 -1
  105. package/dist/units/composite.js.map +1 -1
  106. package/package.json +8 -8
  107. package/src/age-model/data.ts +98 -0
  108. package/src/age-model/details.module.sass +108 -0
  109. package/src/age-model/details.ts +333 -0
  110. package/src/age-model/index.ts +5 -0
  111. package/src/age-model/legacy.ts +30 -0
  112. package/src/age-model/surfaces.module.sass +168 -0
  113. package/src/age-model/surfaces.ts +568 -0
  114. package/src/age-model/types.ts +392 -0
  115. package/src/column.module.sass +59 -8
  116. package/src/column.ts +49 -23
  117. package/src/data-provider/store.ts +50 -1
  118. package/src/index.ts +5 -1
  119. package/src/notes.ts +20 -2
  120. package/src/prepare-units/composite-scale.ts +34 -31
  121. package/src/prepare-units/density.ts +197 -0
  122. package/src/prepare-units/index.ts +25 -6
  123. package/src/prepare-units/types.ts +34 -13
  124. package/src/section.ts +219 -33
  125. package/src/timescale-zoom.ts +341 -0
  126. package/src/unit-details/age-range.ts +12 -58
  127. package/src/units/composite.ts +11 -0
  128. package/dist/age-model-overlay.cjs +0 -50
  129. package/dist/age-model-overlay.cjs.map +0 -1
  130. package/dist/age-model-overlay.d.ts +0 -24
  131. package/dist/age-model-overlay.js +0 -48
  132. package/dist/age-model-overlay.js.map +0 -1
  133. package/dist/age-model-overlay.module.sass.cjs +0 -12
  134. package/dist/age-model-overlay.module.sass.cjs.map +0 -1
  135. package/dist/age-model-overlay.module.sass.js +0 -11
  136. package/dist/age-model-overlay.module.sass.js.map +0 -1
  137. package/src/age-model-overlay.module.sass +0 -11
  138. package/src/age-model-overlay.ts +0 -96
@@ -4,6 +4,7 @@ const columnComponents = require("@macrostrat/column-components");
4
4
  const utils = require("./utils.cjs");
5
5
  const d3Scale = require("d3-scale");
6
6
  const dynamicScales = require("./dynamic-scales.cjs");
7
+ const density = require("./density.cjs");
7
8
  const types = require("./types.cjs");
8
9
  function buildCompositeScaleInfo(inputScales, unconformityHeight) {
9
10
  let totalHeight = unconformityHeight / 2;
@@ -67,7 +68,8 @@ function addScaleToSection(group, opts) {
67
68
  }
68
69
  const scaleInfo = buildSectionScale(units, {
69
70
  ...opts,
70
- domain: _range
71
+ domain: _range,
72
+ sectionID: group.section_id
71
73
  });
72
74
  return {
73
75
  ...group,
@@ -77,12 +79,11 @@ function addScaleToSection(group, opts) {
77
79
  function buildSectionScale(data, opts) {
78
80
  const {
79
81
  targetUnitHeight = 20,
80
- minPixelScale = 0.2,
81
82
  axisType,
82
- minSectionHeight,
83
83
  visibleWindow,
84
84
  scale,
85
- hybridScale
85
+ hybridScale,
86
+ sectionID
86
87
  } = opts;
87
88
  const domain = opts.domain ?? findSectionHeightRange(data, axisType);
88
89
  const dAge = Math.abs(domain[0] - domain[1]);
@@ -91,6 +92,8 @@ function buildSectionScale(data, opts) {
91
92
  if (hybridScale != null) {
92
93
  if (hybridScale.type === types.HybridScaleType.EquidistantSurfaces) {
93
94
  _pixelScale ??= targetUnitHeight;
95
+ } else {
96
+ _pixelScale = opts.pixelsPerMeter ?? _pixelScale;
94
97
  }
95
98
  return dynamicScales.buildHybridScale(hybridScale, data, domain, {
96
99
  pixelOffset: 0,
@@ -98,14 +101,14 @@ function buildSectionScale(data, opts) {
98
101
  });
99
102
  }
100
103
  if (scale == null) {
101
- if (_pixelScale == null) {
102
- const avgAgeRange = findAverageUnitHeight(data, axisType, visibleWindow);
103
- _pixelScale = Math.max(targetUnitHeight / avgAgeRange, minPixelScale);
104
- }
105
- let height = dAge * _pixelScale;
106
- const _minSectionHeight = minSectionHeight ?? targetUnitHeight ?? 0;
107
- pixelHeight = Math.max(height, _minSectionHeight);
108
- _pixelScale = pixelHeight / dAge;
104
+ _pixelScale = density.sectionDensity(opts)({
105
+ extent: dAge,
106
+ unitExtents: visibleUnitExtents(data, axisType, visibleWindow),
107
+ units: data,
108
+ sectionID,
109
+ axisType
110
+ });
111
+ pixelHeight = dAge * _pixelScale;
109
112
  } else {
110
113
  pixelHeight = Math.abs(scale(domain[0]) - scale(domain[1]));
111
114
  }
@@ -233,7 +236,7 @@ function findSectionHeightRange(data, axisType) {
233
236
  return [b_pos, t_pos];
234
237
  }
235
238
  }
236
- function findAverageUnitHeight(data, axisType, visibleWindow) {
239
+ function visibleUnitExtents(data, axisType, visibleWindow) {
237
240
  const durations = (clip) => data.map((d) => {
238
241
  const [b_pos, t_pos] = utils.getUnitHeightRange(d, axisType);
239
242
  if (!clip) return Math.abs(b_pos - t_pos);
@@ -246,8 +249,7 @@ function findAverageUnitHeight(data, axisType, visibleWindow) {
246
249
  const useWindow = visibleWindow != null && axisType === columnComponents.ColumnAxisType.AGE;
247
250
  let heights = useWindow ? durations(true) : [];
248
251
  if (heights.length === 0) heights = durations(false);
249
- if (heights.length === 0) return 1;
250
- return heights.reduce((a, b) => a + b, 0) / heights.length;
252
+ return heights;
251
253
  }
252
254
  function createCompositeScale(sections, interpolateUnconformities = false) {
253
255
  const scales = [];
@@ -324,7 +326,7 @@ function collapseUnconformitiesByPixelHeight(sections, threshold, opts) {
324
326
  _diff(heights.map(nextSection.scaleInfo.scale)),
325
327
  _diff(heights.map(currentSection.scaleInfo.scale))
326
328
  ];
327
- const pxHeight = Math.min(...pxHeights);
329
+ const pxHeight = Math.max(...pxHeights);
328
330
  if (pxHeight < threshold) {
329
331
  let t_pos;
330
332
  let b_pos;
@@ -1 +1 @@
1
- {"version":3,"file":"composite-scale.cjs","sources":["../../src/prepare-units/composite-scale.ts"],"sourcesContent":["import { ColumnAxisType } from \"@macrostrat/column-components\";\nimport { agesOverlap, ensureArray, getUnitHeightRange } from \"./utils\";\nimport { ScaleContinuousNumeric, scaleLinear } from \"d3-scale\";\nimport { UnitLong } from \"@macrostrat/api-types\";\nimport { buildHybridScale } from \"./dynamic-scales\";\nimport { ExtUnit, HybridScaleType, SectionInfo } from \"./types\";\nimport type {\n ColumnScaleOptions,\n CompositeColumnData,\n CompositeScaleData,\n PackageLayoutData,\n PackageScaleInfo,\n PackageScaleLayoutData,\n SectionScaleOptions,\n} from \"./types\";\n\n// Composite scale information augmented with units in each package\n\nexport function buildCompositeScaleInfo(\n inputScales: PackageScaleInfo[],\n unconformityHeight: number,\n): CompositeScaleData {\n /** Finalize the heights of sections, including the heights of unconformities\n * between them.\n */\n\n let totalHeight = unconformityHeight / 2;\n let lastSectionTopHeight = 0;\n\n const packages2: PackageScaleLayoutData[] = [];\n for (let i = 0; i < inputScales.length; i++) {\n const group = inputScales[i];\n const { domain, scale } = group;\n const [b_age, t_age] = domain;\n // Positionally stable key: packages are always emitted in age order, so the\n // index is a stable identity. An age-based key (`package-${b_age}-${t_age}`)\n // changes every frame while the age window animates, forcing every package,\n // column, and timescale slice in the correlation chart to remount each frame.\n const key = `package-${i}`;\n\n packages2.push({\n ...createPackageScale(group, totalHeight),\n key,\n offset: totalHeight,\n // Unconformity height above this particular section\n paddingTop: totalHeight - lastSectionTopHeight,\n });\n\n const pixelHeight = Math.abs(scale(b_age) - scale(t_age));\n lastSectionTopHeight = totalHeight + pixelHeight;\n totalHeight = lastSectionTopHeight + unconformityHeight;\n }\n totalHeight += unconformityHeight / 2;\n return {\n totalHeight,\n sections: packages2,\n };\n}\n\nexport function finalizeSectionHeights<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n unconformityHeight: number,\n): CompositeColumnData<T> {\n /** Finalize the heights of sections, including the heights of unconformities\n * between them.\n */\n\n const sectionScales = sections.map((d) => d.scaleInfo);\n\n const { totalHeight, sections: packages } = buildCompositeScaleInfo(\n sectionScales,\n unconformityHeight,\n );\n\n // This could perhaps be simplified.\n const sections1: PackageLayoutData<T>[] = [];\n for (const i in sections) {\n const group = sections[i];\n const scaleInfo = packages[i];\n sections1.push({\n ...group,\n key: scaleInfo.key,\n scaleInfo,\n });\n }\n return {\n totalHeight,\n sections: sections1,\n };\n}\n\nexport interface SectionInfoWithScale<\n T extends UnitLong = ExtUnit,\n> extends SectionInfo<T> {\n scaleInfo: PackageScaleInfo;\n}\n\nexport function computeSectionHeights<T extends UnitLong>(\n sections: SectionInfo<T>[],\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T>[] {\n return sections.map((group) => {\n return addScaleToSection<T>(group, opts);\n });\n}\n\nfunction addScaleToSection<T extends UnitLong = ExtUnit>(\n group: SectionInfo<T>,\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T> {\n const { t_age, b_age, t_pos, b_pos, units } = group;\n let _range = null;\n // if t_age and b_age are set for a group, use them to define the range...\n if (opts.axisType == ColumnAxisType.AGE) {\n _range = [b_age, t_age];\n } else {\n _range = [b_pos, t_pos];\n }\n\n const scaleInfo = buildSectionScale<T>(units, {\n ...opts,\n domain: _range,\n });\n\n return {\n ...group,\n scaleInfo,\n };\n}\n\nfunction buildSectionScale<T extends UnitLong>(\n data: T[],\n opts: SectionScaleOptions,\n): PackageScaleInfo {\n const {\n targetUnitHeight = 20,\n minPixelScale = 0.2,\n axisType,\n minSectionHeight,\n visibleWindow,\n scale,\n hybridScale,\n } = opts;\n const domain = opts.domain ?? findSectionHeightRange(data, axisType);\n\n const dAge = Math.abs(domain[0] - domain[1]);\n\n let _pixelScale = opts.pixelScale;\n let pixelHeight: number;\n\n if (hybridScale != null) {\n /** In an equidistant surfaces scale, we want to determine the heights of surfaces\n * and then distribute units evenly between them.\n * This is somewhat like an ordinal scale\n */\n if (hybridScale.type === HybridScaleType.EquidistantSurfaces) {\n _pixelScale ??= targetUnitHeight;\n }\n\n return buildHybridScale(hybridScale, data, domain, {\n pixelOffset: 0,\n pixelScale: _pixelScale,\n });\n }\n\n if (scale == null) {\n if (_pixelScale == null) {\n const avgAgeRange = findAverageUnitHeight(data, axisType, visibleWindow);\n // Get pixel height necessary to render average unit at target height\n _pixelScale = Math.max(targetUnitHeight / avgAgeRange, minPixelScale);\n\n // OLD METHOD that cares about overall section height vs. individual unit height\n // 0.2 pixel per myr is the floor scale\n //const targetHeight = targetUnitHeight * data.length;\n // 1 pixel per myr is the floor scale\n //_pixelScale = Math.max(targetHeight / dAge, minPixelScale);\n }\n\n let height = dAge * _pixelScale;\n // If height is less than minSectionHeight, set it to minSectionHeight.\n // Sections reach here at their *full* extent (the rendered window is applied\n // afterwards, by `trimSectionsToWindow`), so this floor only ever inflates a\n // genuinely small section — which is what it's for — and never a sliver that\n // the window happens to cut.\n const _minSectionHeight = minSectionHeight ?? targetUnitHeight ?? 0;\n pixelHeight = Math.max(height, _minSectionHeight);\n _pixelScale = pixelHeight / dAge;\n } else {\n // If a scale is provided, use it to compute pixel height\n pixelHeight = Math.abs(scale(domain[0]) - scale(domain[1]));\n }\n\n return createPackageScale(\n { scale, domain, pixelHeight, pixelScale: _pixelScale },\n 0,\n );\n}\n\n/**\n * The density each section will actually render at once the window is applied.\n *\n * A section the window cuts short is still being *looked at*, so\n * `minSectionHeight` applies to what remains — it's honored by expanding the\n * section's scale, not by showing more of it. Resolving this **before** padding\n * matters: padding is a pixel margin measured at the density it will render at,\n * and a section stretched to the floor afterwards would multiply the margin\n * along with everything else (a 20 px margin in a band stretched 4× renders at\n * 80 px, and asymmetrically so whenever one side is truncated).\n *\n * Sections the window leaves whole keep their laid-out density — they already\n * met the floor when they were sized.\n */\nexport function resolveWindowScales<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n focalWindow: [number, number],\n floor: number,\n): Map<SectionInfoWithScale<T>, number> {\n const [b_focal, t_focal] = focalWindow;\n const scales = new Map<SectionInfoWithScale<T>, number>();\n\n for (const section of sections) {\n const t_age = Math.max(section.t_age, t_focal);\n const b_age = Math.min(section.b_age, b_focal);\n // Sections outside the focal window are visible only as padding; they keep\n // their own density and render exactly the pixels asked for.\n if (b_age <= t_age) continue;\n\n const natural = section.scaleInfo.pixelScale;\n if (!(natural > 0)) continue;\n\n const isClipped = b_age < section.b_age || t_age > section.t_age;\n const needed = isClipped && floor > 0 ? floor / (b_age - t_age) : 0;\n scales.set(section, Math.max(natural, needed));\n }\n return scales;\n}\n\n/**\n * Expand an age window outward by `padding` **pixels** of column, spending the\n * budget against sections that are already laid out at full extent, at the\n * densities they will finally render at (`scaleFor`).\n *\n * Because the layout is done, this is exact rather than predicted: each section\n * contributes its own real pixel height. Padding collapses when the column runs\n * out on that side.\n *\n * Unconformities between sections cost nothing. They render at a fixed height\n * that's typically larger than the padding itself (30–60 px), so charging them\n * would mean padding could never reach across a gap — which is the case it\n * exists for: revealing the neighboring section keeps its timescale intervals\n * navigable. The padding is a budget of neighboring *content*.\n */\nexport function padWindowByPixels<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n window: [number, number],\n padding: number,\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): [number, number] {\n if (!(padding > 0) || sections.length === 0) return window;\n const [b_age, t_age] = window;\n return [\n spendOutward(sections, b_age, 1, padding, scaleFor),\n spendOutward(sections, t_age, -1, padding, scaleFor),\n ];\n}\n\n/** Walk out from one edge of the window through laid-out sections, spending a\n * pixel budget, and return the age it lands on. `direction` is +1 toward older\n * ages, -1 toward younger. */\nfunction spendOutward<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n edge: number,\n direction: 1 | -1,\n padding: number,\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): number {\n // Sections ordered outward from the edge, keeping only what lies beyond it.\n const beyond = sections\n .filter(\n (s) => direction * (s.b_age - edge) > 0 || direction * (s.t_age - edge) > 0,\n )\n .sort((a, b) => direction * (nearEdge(a, direction) - nearEdge(b, direction)));\n\n let bound = edge;\n let budget = padding;\n\n for (const section of beyond) {\n const pixelScale = scaleFor(section);\n if (!(pixelScale > 0)) continue;\n\n const from =\n direction > 0\n ? Math.max(section.t_age, bound)\n : Math.min(section.b_age, bound);\n const available = direction > 0 ? section.b_age - from : from - section.t_age;\n if (available <= 0) continue;\n\n const cost = available * pixelScale;\n if (budget <= cost) return from + direction * (budget / pixelScale);\n\n bound = direction > 0 ? section.b_age : section.t_age;\n budget -= cost;\n }\n return bound;\n}\n\n/** The age at which a section starts, measured from the window edge outward. */\nfunction nearEdge<T extends UnitLong>(\n section: SectionInfoWithScale<T>,\n direction: 1 | -1,\n): number {\n return direction > 0 ? section.t_age : section.b_age;\n}\n\n/**\n * Clip laid-out sections to the rendered window — the last step of layout, so\n * that every section has already been sized (and floored) at its full extent.\n * Each section renders at the density `scaleFor` resolved for it, which is what\n * keeps a padding margin the same number of pixels as the band it borders is\n * stretched to meet `minSectionHeight`.\n */\nexport function trimSectionsToWindow<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n window: [number, number],\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): SectionInfoWithScale<T>[] {\n const [b_win, t_win] = window;\n const out: SectionInfoWithScale<T>[] = [];\n\n for (const section of sections) {\n const t_age = Math.max(section.t_age, t_win);\n const b_age = Math.min(section.b_age, b_win);\n if (b_age <= t_age) continue;\n\n const pixelScale = scaleFor(section);\n if (\n b_age === section.b_age &&\n t_age === section.t_age &&\n pixelScale === section.scaleInfo.pixelScale\n ) {\n out.push(section);\n continue;\n }\n\n out.push({\n ...section,\n t_age,\n b_age,\n units: section.units.filter((u) => agesOverlap(u, { t_age, b_age })),\n scaleInfo: createPackageScale({\n domain: [b_age, t_age],\n pixelScale,\n pixelHeight: (b_age - t_age) * pixelScale,\n scale: null,\n }),\n });\n }\n return out;\n}\n\nexport function createPackageScale(\n def: PackageScaleInfo,\n offset: number = 0,\n): PackageScaleInfo {\n /** Build a section scale */\n // Domain should be oriented from bottom to top, but scale is oriented from top to bottom\n const { domain, pixelScale, pixelHeight, scale, heightScale } = def;\n\n if (scale == null && pixelScale == null) {\n throw new Error(\"Either scale or pixelScale must be provided\");\n }\n\n let _scale: ScaleContinuousNumeric<number, number>;\n if (scale == null) {\n _scale = scaleLinear()\n .domain([domain[1], domain[0]])\n .range([offset, pixelHeight + offset]);\n } else {\n const domain0 = scale.domain();\n const range0 = scale.range();\n _scale = scale\n .copy()\n .domain(domain0)\n .range(range0.map((d) => d + offset));\n }\n\n _scale.clamp();\n\n let _heightScale = null;\n if (heightScale != null) {\n // Adjust height scale as well\n const range0 = scale.range();\n _heightScale = heightScale.copy().range(range0.map((d) => d + offset));\n _heightScale.clamp();\n }\n\n return {\n domain,\n pixelScale,\n pixelHeight,\n scale: _scale,\n // Internal details for hybrid scales. TODO: improve this\n heightScale: _heightScale,\n };\n}\n\nfunction findSectionHeightRange(\n data: UnitLong[],\n axisType: ColumnAxisType,\n): [number, number] {\n if (axisType == null) {\n throw new Error(\"Axis type is not set\");\n }\n if (axisType === ColumnAxisType.AGE) {\n const t_age = Math.min(...data.map((d) => d.t_age));\n const b_age = Math.max(...data.map((d) => d.b_age));\n return [b_age, t_age];\n } else if (\n axisType == ColumnAxisType.DEPTH ||\n axisType == ColumnAxisType.ORDINAL\n ) {\n const t_pos = Math.min(...data.map((d) => d.t_pos));\n const b_pos = Math.max(...data.map((d) => d.b_pos));\n return [b_pos, t_pos];\n } else if (axisType == ColumnAxisType.HEIGHT) {\n const t_pos = Math.max(...data.map((d) => d.t_pos));\n const b_pos = Math.min(...data.map((d) => d.b_pos));\n return [b_pos, t_pos];\n }\n}\n\nfunction findAverageUnitHeight(\n data: UnitLong[],\n axisType: ColumnAxisType,\n visibleWindow?: [number, number] | null,\n): number {\n /** The typical duration of a unit, which `targetUnitHeight` sizes.\n *\n * Measured over what the render window actually *shows* — units outside it\n * are ignored and a unit it cuts through counts only for its visible part.\n * That's what makes `targetUnitHeight` mean the same thing at every zoom\n * depth: the units on screen are drawn at the target height whether they're\n * whole or slivers of something much longer.\n *\n * A section the window doesn't reach has nothing visible to measure, so it\n * falls back to its own units at full duration — it's drawn at its own scale.\n * (Deriving it from the padded window instead would be circular: padding is\n * measured in pixels, which is what we're trying to establish.)\n */\n const durations = (clip: boolean) =>\n data\n .map((d) => {\n const [b_pos, t_pos] = getUnitHeightRange(d, axisType);\n if (!clip) return Math.abs(b_pos - t_pos);\n const [b_win, t_win] = visibleWindow;\n return Math.max(\n 0,\n Math.min(b_pos, b_win) - Math.max(t_pos, t_win),\n );\n })\n .filter((d) => d > 0);\n\n const useWindow = visibleWindow != null && axisType === ColumnAxisType.AGE;\n let heights = useWindow ? durations(true) : [];\n if (heights.length === 0) heights = durations(false);\n if (heights.length === 0) return 1;\n\n return heights.reduce((a, b) => a + b, 0) / heights.length;\n}\n\nexport interface CompositeColumnScale {\n (age: number): number | null;\n copy(): CompositeColumnScale;\n domain(): [number, number];\n invert(pixelHeight: number): number | null;\n clamp(clamp: boolean): void;\n}\n\nexport function createCompositeScale(\n sections: PackageLayoutData[],\n interpolateUnconformities: boolean = false,\n): CompositeColumnScale {\n /** Create a scale that works across multiple packages */\n\n const scales: ScaleContinuousNumeric<number, number>[] = [];\n\n let lastScale: ScaleContinuousNumeric<number, number> | null = null;\n for (const section of sections) {\n const _scale = section.scaleInfo.scale.copy().clamp(true);\n scales.push(_scale);\n if (lastScale != null && interpolateUnconformities) {\n // Add a new scale that interpolates between lastScale and _scale\n const lastDomain = lastScale.domain();\n const lastRange = lastScale.range();\n const currentDomain = _scale.domain();\n const currentRange = _scale.range();\n\n const interpScale = scaleLinear()\n .domain([lastDomain[lastDomain.length - 1], currentDomain[0]])\n .range([lastRange[lastRange.length - 1], currentRange[0]])\n .clamp(true);\n scales.push(interpScale);\n }\n lastScale = _scale;\n }\n\n const scale: CompositeColumnScale = (age) => {\n for (const s of scales) {\n const domain = s.domain();\n if (\n domain[0] < domain[domain.length - 1] &&\n age >= domain[0] &&\n age <= domain[domain.length - 1]\n ) {\n // Age axes\n return s(age);\n } else if (age <= domain[0] && age >= domain[domain.length - 1]) {\n // Normal axes like height\n return s(age);\n }\n }\n return null;\n };\n\n scale.copy = () => {\n return createCompositeScale(sections, interpolateUnconformities);\n };\n\n scale.domain = () => {\n /** Return the domain of the scale */\n const vals = sections.flatMap((d) => d.scaleInfo.domain);\n if (vals[0] < vals[vals.length - 1]) {\n // age axes\n return [Math.min(...vals), Math.max(...vals)];\n }\n // Normal axes like height\n return [Math.max(...vals), Math.min(...vals)];\n };\n\n scale.invert = (pixelHeight) => {\n /** Invert the scale to get the age at a given pixel height */\n // Iterate through the sections to find the correct one\n for (const scale of scales) {\n const range = scale.range();\n if (\n pixelHeight > Math.min(...range) &&\n pixelHeight <= Math.max(...range)\n ) {\n //console.log(\"Inverting scale at pixel height\", pixelHeight);\n return scale.invert(pixelHeight);\n }\n }\n return null;\n };\n\n scale.clamp = (clamp: boolean) => {\n /** Clamp all constituent scales */\n for (const s of scales) {\n s.clamp(clamp);\n }\n };\n\n return scale;\n}\n\n/** Collapse sections separated by unconformities that are smaller than a given pixel height. */\nexport function collapseUnconformitiesByPixelHeight<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n threshold: number,\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T>[] {\n const newSections = [];\n let currentSection: SectionInfoWithScale<T> | null = null;\n for (const nextSection of sections) {\n if (currentSection == null) {\n currentSection = nextSection;\n continue;\n }\n let heights: [number, number];\n let pxHeights: [number, number];\n if (opts.axisType !== ColumnAxisType.AGE) {\n heights = [nextSection.t_pos, currentSection.b_pos];\n } else {\n heights = [nextSection.t_age, currentSection.b_age];\n }\n\n const _diff = (vals: number[]) => {\n return Math.abs(vals[0] - vals[1]);\n };\n\n pxHeights = [\n _diff(heights.map(nextSection.scaleInfo.scale)),\n _diff(heights.map(currentSection.scaleInfo.scale)),\n ];\n\n const pxHeight = Math.min(...pxHeights);\n\n if (pxHeight < threshold) {\n let t_pos: number;\n let b_pos: number;\n if (\n opts.axisType == ColumnAxisType.AGE ||\n opts.axisType == ColumnAxisType.DEPTH\n ) {\n t_pos = Math.min(currentSection.t_pos, nextSection.t_pos);\n b_pos = Math.max(currentSection.b_pos, nextSection.b_pos);\n } else {\n t_pos = Math.max(currentSection.t_pos, nextSection.t_pos);\n b_pos = Math.min(currentSection.b_pos, nextSection.b_pos);\n }\n\n // We need to merge the sections\n const compositeSection0: SectionInfo<T> = {\n units: [...currentSection.units, ...nextSection.units],\n section_id: [\n ...ensureArray(currentSection.section_id),\n ...ensureArray(nextSection.section_id),\n ],\n t_age: Math.min(currentSection.t_age, nextSection.t_age),\n b_age: Math.max(currentSection.b_age, nextSection.b_age),\n t_pos,\n b_pos,\n };\n\n currentSection = addScaleToSection(compositeSection0, opts);\n } else {\n // We need to keep the section\n newSections.push(currentSection);\n currentSection = nextSection;\n }\n }\n if (currentSection != null) {\n newSections.push(currentSection);\n }\n\n return newSections;\n}\n"],"names":["ColumnAxisType","HybridScaleType","buildHybridScale","agesOverlap","scaleLinear","getUnitHeightRange","scale","ensureArray"],"mappings":"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"}
1
+ {"version":3,"file":"composite-scale.cjs","sources":["../../src/prepare-units/composite-scale.ts"],"sourcesContent":["import { ColumnAxisType } from \"@macrostrat/column-components\";\nimport { agesOverlap, ensureArray, getUnitHeightRange } from \"./utils\";\nimport { ScaleContinuousNumeric, scaleLinear } from \"d3-scale\";\nimport { UnitLong } from \"@macrostrat/api-types\";\nimport { buildHybridScale } from \"./dynamic-scales\";\nimport { sectionDensity } from \"./density\";\nimport { ExtUnit, HybridScaleType, SectionInfo } from \"./types\";\nimport type {\n ColumnScaleOptions,\n CompositeColumnData,\n CompositeScaleData,\n PackageLayoutData,\n PackageScaleInfo,\n PackageScaleLayoutData,\n SectionScaleOptions,\n} from \"./types\";\n\n// Composite scale information augmented with units in each package\n\nexport function buildCompositeScaleInfo(\n inputScales: PackageScaleInfo[],\n unconformityHeight: number,\n): CompositeScaleData {\n /** Finalize the heights of sections, including the heights of unconformities\n * between them.\n */\n\n let totalHeight = unconformityHeight / 2;\n let lastSectionTopHeight = 0;\n\n const packages2: PackageScaleLayoutData[] = [];\n for (let i = 0; i < inputScales.length; i++) {\n const group = inputScales[i];\n const { domain, scale } = group;\n const [b_age, t_age] = domain;\n // Positionally stable key: packages are always emitted in age order, so the\n // index is a stable identity. An age-based key (`package-${b_age}-${t_age}`)\n // changes every frame while the age window animates, forcing every package,\n // column, and timescale slice in the correlation chart to remount each frame.\n const key = `package-${i}`;\n\n packages2.push({\n ...createPackageScale(group, totalHeight),\n key,\n offset: totalHeight,\n // Unconformity height above this particular section\n paddingTop: totalHeight - lastSectionTopHeight,\n });\n\n const pixelHeight = Math.abs(scale(b_age) - scale(t_age));\n lastSectionTopHeight = totalHeight + pixelHeight;\n totalHeight = lastSectionTopHeight + unconformityHeight;\n }\n totalHeight += unconformityHeight / 2;\n return {\n totalHeight,\n sections: packages2,\n };\n}\n\nexport function finalizeSectionHeights<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n unconformityHeight: number,\n): CompositeColumnData<T> {\n /** Finalize the heights of sections, including the heights of unconformities\n * between them.\n */\n\n const sectionScales = sections.map((d) => d.scaleInfo);\n\n const { totalHeight, sections: packages } = buildCompositeScaleInfo(\n sectionScales,\n unconformityHeight,\n );\n\n // This could perhaps be simplified.\n const sections1: PackageLayoutData<T>[] = [];\n for (const i in sections) {\n const group = sections[i];\n const scaleInfo = packages[i];\n sections1.push({\n ...group,\n key: scaleInfo.key,\n scaleInfo,\n });\n }\n return {\n totalHeight,\n sections: sections1,\n };\n}\n\nexport interface SectionInfoWithScale<\n T extends UnitLong = ExtUnit,\n> extends SectionInfo<T> {\n scaleInfo: PackageScaleInfo;\n}\n\nexport function computeSectionHeights<T extends UnitLong>(\n sections: SectionInfo<T>[],\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T>[] {\n return sections.map((group) => {\n return addScaleToSection<T>(group, opts);\n });\n}\n\nfunction addScaleToSection<T extends UnitLong = ExtUnit>(\n group: SectionInfo<T>,\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T> {\n const { t_age, b_age, t_pos, b_pos, units } = group;\n let _range = null;\n // if t_age and b_age are set for a group, use them to define the range...\n if (opts.axisType == ColumnAxisType.AGE) {\n _range = [b_age, t_age];\n } else {\n _range = [b_pos, t_pos];\n }\n\n const scaleInfo = buildSectionScale<T>(units, {\n ...opts,\n domain: _range,\n sectionID: group.section_id,\n });\n\n return {\n ...group,\n scaleInfo,\n };\n}\n\nfunction buildSectionScale<T extends UnitLong>(\n data: T[],\n opts: SectionScaleOptions,\n): PackageScaleInfo {\n const {\n targetUnitHeight = 20,\n axisType,\n visibleWindow,\n scale,\n hybridScale,\n sectionID,\n } = opts;\n const domain = opts.domain ?? findSectionHeightRange(data, axisType);\n\n const dAge = Math.abs(domain[0] - domain[1]);\n\n let _pixelScale: any = opts.pixelScale;\n let pixelHeight: number;\n\n if (hybridScale != null) {\n /** In an equidistant surfaces scale, we want to determine the heights of surfaces\n * and then distribute units evenly between them.\n * This is somewhat like an ordinal scale\n */\n if (hybridScale.type === HybridScaleType.EquidistantSurfaces) {\n // Pixels between one surface and the next: neither axis's units\n _pixelScale ??= targetUnitHeight;\n } else {\n // An approximate-height column is drawn from measured thickness, so it\n // is metres per pixel whatever the axis is labeled in\n _pixelScale = opts.pixelsPerMeter ?? _pixelScale;\n }\n\n return buildHybridScale(hybridScale, data, domain, {\n pixelOffset: 0,\n pixelScale: _pixelScale,\n });\n }\n\n if (scale == null) {\n /** Every rule that sets a section's height resolves to one density (see\n * `./density`). Sections reach here at their *full* extent — the rendered\n * window is applied afterwards, by `trimSectionsToWindow` — so a floor on\n * the section's height only ever inflates a genuinely small section, which\n * is what it's for, and never a sliver the window happens to cut. */\n _pixelScale = sectionDensity(opts)({\n extent: dAge,\n unitExtents: visibleUnitExtents(data, axisType, visibleWindow),\n units: data,\n sectionID,\n axisType,\n });\n pixelHeight = dAge * _pixelScale;\n } else {\n // If a scale is provided, use it to compute pixel height\n pixelHeight = Math.abs(scale(domain[0]) - scale(domain[1]));\n }\n\n return createPackageScale(\n { scale, domain, pixelHeight, pixelScale: _pixelScale },\n 0,\n );\n}\n\n/**\n * The density each section will actually render at once the window is applied.\n *\n * A section the window cuts short is still being *looked at*, so\n * `minSectionHeight` applies to what remains — it's honored by expanding the\n * section's scale, not by showing more of it. Resolving this **before** padding\n * matters: padding is a pixel margin measured at the density it will render at,\n * and a section stretched to the floor afterwards would multiply the margin\n * along with everything else (a 20 px margin in a band stretched 4× renders at\n * 80 px, and asymmetrically so whenever one side is truncated).\n *\n * Sections the window leaves whole keep their laid-out density — they already\n * met the floor when they were sized.\n */\nexport function resolveWindowScales<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n focalWindow: [number, number],\n floor: number,\n): Map<SectionInfoWithScale<T>, number> {\n const [b_focal, t_focal] = focalWindow;\n const scales = new Map<SectionInfoWithScale<T>, number>();\n\n for (const section of sections) {\n const t_age = Math.max(section.t_age, t_focal);\n const b_age = Math.min(section.b_age, b_focal);\n // Sections outside the focal window are visible only as padding; they keep\n // their own density and render exactly the pixels asked for.\n if (b_age <= t_age) continue;\n\n const natural = section.scaleInfo.pixelScale;\n if (!(natural > 0)) continue;\n\n const isClipped = b_age < section.b_age || t_age > section.t_age;\n const needed = isClipped && floor > 0 ? floor / (b_age - t_age) : 0;\n scales.set(section, Math.max(natural, needed));\n }\n return scales;\n}\n\n/**\n * Expand an age window outward by `padding` **pixels** of column, spending the\n * budget against sections that are already laid out at full extent, at the\n * densities they will finally render at (`scaleFor`).\n *\n * Because the layout is done, this is exact rather than predicted: each section\n * contributes its own real pixel height. Padding collapses when the column runs\n * out on that side.\n *\n * Unconformities between sections cost nothing. They render at a fixed height\n * that's typically larger than the padding itself (30–60 px), so charging them\n * would mean padding could never reach across a gap — which is the case it\n * exists for: revealing the neighboring section keeps its timescale intervals\n * navigable. The padding is a budget of neighboring *content*.\n */\nexport function padWindowByPixels<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n window: [number, number],\n padding: number,\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): [number, number] {\n if (!(padding > 0) || sections.length === 0) return window;\n const [b_age, t_age] = window;\n return [\n spendOutward(sections, b_age, 1, padding, scaleFor),\n spendOutward(sections, t_age, -1, padding, scaleFor),\n ];\n}\n\n/** Walk out from one edge of the window through laid-out sections, spending a\n * pixel budget, and return the age it lands on. `direction` is +1 toward older\n * ages, -1 toward younger. */\nfunction spendOutward<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n edge: number,\n direction: 1 | -1,\n padding: number,\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): number {\n // Sections ordered outward from the edge, keeping only what lies beyond it.\n const beyond = sections\n .filter(\n (s) => direction * (s.b_age - edge) > 0 || direction * (s.t_age - edge) > 0,\n )\n .sort((a, b) => direction * (nearEdge(a, direction) - nearEdge(b, direction)));\n\n let bound = edge;\n let budget = padding;\n\n for (const section of beyond) {\n const pixelScale = scaleFor(section);\n if (!(pixelScale > 0)) continue;\n\n const from =\n direction > 0\n ? Math.max(section.t_age, bound)\n : Math.min(section.b_age, bound);\n const available = direction > 0 ? section.b_age - from : from - section.t_age;\n if (available <= 0) continue;\n\n const cost = available * pixelScale;\n if (budget <= cost) return from + direction * (budget / pixelScale);\n\n bound = direction > 0 ? section.b_age : section.t_age;\n budget -= cost;\n }\n return bound;\n}\n\n/** The age at which a section starts, measured from the window edge outward. */\nfunction nearEdge<T extends UnitLong>(\n section: SectionInfoWithScale<T>,\n direction: 1 | -1,\n): number {\n return direction > 0 ? section.t_age : section.b_age;\n}\n\n/**\n * Clip laid-out sections to the rendered window — the last step of layout, so\n * that every section has already been sized (and floored) at its full extent.\n * Each section renders at the density `scaleFor` resolved for it, which is what\n * keeps a padding margin the same number of pixels as the band it borders is\n * stretched to meet `minSectionHeight`.\n */\nexport function trimSectionsToWindow<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n window: [number, number],\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): SectionInfoWithScale<T>[] {\n const [b_win, t_win] = window;\n const out: SectionInfoWithScale<T>[] = [];\n\n for (const section of sections) {\n const t_age = Math.max(section.t_age, t_win);\n const b_age = Math.min(section.b_age, b_win);\n if (b_age <= t_age) continue;\n\n const pixelScale = scaleFor(section);\n if (\n b_age === section.b_age &&\n t_age === section.t_age &&\n pixelScale === section.scaleInfo.pixelScale\n ) {\n out.push(section);\n continue;\n }\n\n out.push({\n ...section,\n t_age,\n b_age,\n units: section.units.filter((u) => agesOverlap(u, { t_age, b_age })),\n scaleInfo: createPackageScale({\n domain: [b_age, t_age],\n pixelScale,\n pixelHeight: (b_age - t_age) * pixelScale,\n scale: null,\n }),\n });\n }\n return out;\n}\n\nexport function createPackageScale(\n def: PackageScaleInfo,\n offset: number = 0,\n): PackageScaleInfo {\n /** Build a section scale */\n // Domain should be oriented from bottom to top, but scale is oriented from top to bottom\n const { domain, pixelScale, pixelHeight, scale, heightScale } = def;\n\n if (scale == null && pixelScale == null) {\n throw new Error(\"Either scale or pixelScale must be provided\");\n }\n\n let _scale: ScaleContinuousNumeric<number, number>;\n if (scale == null) {\n _scale = scaleLinear()\n .domain([domain[1], domain[0]])\n .range([offset, pixelHeight + offset]);\n } else {\n const domain0 = scale.domain();\n const range0 = scale.range();\n _scale = scale\n .copy()\n .domain(domain0)\n .range(range0.map((d) => d + offset));\n }\n\n _scale.clamp();\n\n let _heightScale = null;\n if (heightScale != null) {\n // Adjust height scale as well\n const range0 = scale.range();\n _heightScale = heightScale.copy().range(range0.map((d) => d + offset));\n _heightScale.clamp();\n }\n\n return {\n domain,\n pixelScale,\n pixelHeight,\n scale: _scale,\n // Internal details for hybrid scales. TODO: improve this\n heightScale: _heightScale,\n };\n}\n\nfunction findSectionHeightRange(\n data: UnitLong[],\n axisType: ColumnAxisType,\n): [number, number] {\n if (axisType == null) {\n throw new Error(\"Axis type is not set\");\n }\n if (axisType === ColumnAxisType.AGE) {\n const t_age = Math.min(...data.map((d) => d.t_age));\n const b_age = Math.max(...data.map((d) => d.b_age));\n return [b_age, t_age];\n } else if (\n axisType == ColumnAxisType.DEPTH ||\n axisType == ColumnAxisType.ORDINAL\n ) {\n const t_pos = Math.min(...data.map((d) => d.t_pos));\n const b_pos = Math.max(...data.map((d) => d.b_pos));\n return [b_pos, t_pos];\n } else if (axisType == ColumnAxisType.HEIGHT) {\n const t_pos = Math.max(...data.map((d) => d.t_pos));\n const b_pos = Math.min(...data.map((d) => d.b_pos));\n return [b_pos, t_pos];\n }\n}\n\nfunction visibleUnitExtents(\n data: UnitLong[],\n axisType: ColumnAxisType,\n visibleWindow?: [number, number] | null,\n): number[] {\n /** The durations of the units a density rule gets to size.\n *\n * Measured over what the render window actually *shows* — units outside it\n * are ignored and a unit it cuts through counts only for its visible part.\n * That's what makes `targetUnitHeight` mean the same thing at every zoom\n * depth: the units on screen are drawn at the target height whether they're\n * whole or slivers of something much longer.\n *\n * A section the window doesn't reach has nothing visible to measure, so it\n * falls back to its own units at full duration — it's drawn at its own scale.\n * (Deriving it from the padded window instead would be circular: padding is\n * measured in pixels, which is what we're trying to establish.)\n */\n const durations = (clip: boolean) =>\n data\n .map((d) => {\n const [b_pos, t_pos] = getUnitHeightRange(d, axisType);\n if (!clip) return Math.abs(b_pos - t_pos);\n const [b_win, t_win] = visibleWindow;\n return Math.max(\n 0,\n Math.min(b_pos, b_win) - Math.max(t_pos, t_win),\n );\n })\n .filter((d) => d > 0);\n\n const useWindow = visibleWindow != null && axisType === ColumnAxisType.AGE;\n let heights = useWindow ? durations(true) : [];\n if (heights.length === 0) heights = durations(false);\n return heights;\n}\n\nexport interface CompositeColumnScale {\n (age: number): number | null;\n copy(): CompositeColumnScale;\n domain(): [number, number];\n invert(pixelHeight: number): number | null;\n clamp(clamp: boolean): void;\n}\n\nexport function createCompositeScale(\n sections: PackageLayoutData[],\n interpolateUnconformities: boolean = false,\n): CompositeColumnScale {\n /** Create a scale that works across multiple packages */\n\n const scales: ScaleContinuousNumeric<number, number>[] = [];\n\n let lastScale: ScaleContinuousNumeric<number, number> | null = null;\n for (const section of sections) {\n const _scale = section.scaleInfo.scale.copy().clamp(true);\n scales.push(_scale);\n if (lastScale != null && interpolateUnconformities) {\n // Add a new scale that interpolates between lastScale and _scale\n const lastDomain = lastScale.domain();\n const lastRange = lastScale.range();\n const currentDomain = _scale.domain();\n const currentRange = _scale.range();\n\n const interpScale = scaleLinear()\n .domain([lastDomain[lastDomain.length - 1], currentDomain[0]])\n .range([lastRange[lastRange.length - 1], currentRange[0]])\n .clamp(true);\n scales.push(interpScale);\n }\n lastScale = _scale;\n }\n\n const scale: CompositeColumnScale = (age) => {\n for (const s of scales) {\n const domain = s.domain();\n if (\n domain[0] < domain[domain.length - 1] &&\n age >= domain[0] &&\n age <= domain[domain.length - 1]\n ) {\n // Age axes\n return s(age);\n } else if (age <= domain[0] && age >= domain[domain.length - 1]) {\n // Normal axes like height\n return s(age);\n }\n }\n return null;\n };\n\n scale.copy = () => {\n return createCompositeScale(sections, interpolateUnconformities);\n };\n\n scale.domain = () => {\n /** Return the domain of the scale */\n const vals = sections.flatMap((d) => d.scaleInfo.domain);\n if (vals[0] < vals[vals.length - 1]) {\n // age axes\n return [Math.min(...vals), Math.max(...vals)];\n }\n // Normal axes like height\n return [Math.max(...vals), Math.min(...vals)];\n };\n\n scale.invert = (pixelHeight) => {\n /** Invert the scale to get the age at a given pixel height */\n // Iterate through the sections to find the correct one\n for (const scale of scales) {\n const range = scale.range();\n if (\n pixelHeight > Math.min(...range) &&\n pixelHeight <= Math.max(...range)\n ) {\n //console.log(\"Inverting scale at pixel height\", pixelHeight);\n return scale.invert(pixelHeight);\n }\n }\n return null;\n };\n\n scale.clamp = (clamp: boolean) => {\n /** Clamp all constituent scales */\n for (const s of scales) {\n s.clamp(clamp);\n }\n };\n\n return scale;\n}\n\n/** Collapse sections separated by unconformities that are smaller than a given pixel height. */\nexport function collapseUnconformitiesByPixelHeight<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n threshold: number,\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T>[] {\n const newSections = [];\n let currentSection: SectionInfoWithScale<T> | null = null;\n for (const nextSection of sections) {\n if (currentSection == null) {\n currentSection = nextSection;\n continue;\n }\n let heights: [number, number];\n let pxHeights: [number, number];\n if (opts.axisType !== ColumnAxisType.AGE) {\n heights = [nextSection.t_pos, currentSection.b_pos];\n } else {\n heights = [nextSection.t_age, currentSection.b_age];\n }\n\n const _diff = (vals: number[]) => {\n return Math.abs(vals[0] - vals[1]);\n };\n\n pxHeights = [\n _diff(heights.map(nextSection.scaleInfo.scale)),\n _diff(heights.map(currentSection.scaleInfo.scale)),\n ];\n\n /** The gap has no density of its own — it falls between two sections that\n * may be drawn at very different ones — so it is judged at the finer of\n * the two. Taking the smaller estimate let a sparse neighbor speak for a\n * gap the other neighbor would have drawn many times larger: a 16 Myr\n * hiatus in column 22 read as 26px against one section and 166px against\n * the other, and collapsed. A gap is only worth hiding when neither scale\n * would give it more room than the break that replaces it. */\n const pxHeight = Math.max(...pxHeights);\n\n if (pxHeight < threshold) {\n let t_pos: number;\n let b_pos: number;\n if (\n opts.axisType == ColumnAxisType.AGE ||\n opts.axisType == ColumnAxisType.DEPTH\n ) {\n t_pos = Math.min(currentSection.t_pos, nextSection.t_pos);\n b_pos = Math.max(currentSection.b_pos, nextSection.b_pos);\n } else {\n t_pos = Math.max(currentSection.t_pos, nextSection.t_pos);\n b_pos = Math.min(currentSection.b_pos, nextSection.b_pos);\n }\n\n // We need to merge the sections\n const compositeSection0: SectionInfo<T> = {\n units: [...currentSection.units, ...nextSection.units],\n section_id: [\n ...ensureArray(currentSection.section_id),\n ...ensureArray(nextSection.section_id),\n ],\n t_age: Math.min(currentSection.t_age, nextSection.t_age),\n b_age: Math.max(currentSection.b_age, nextSection.b_age),\n t_pos,\n b_pos,\n };\n\n currentSection = addScaleToSection(compositeSection0, opts);\n } else {\n // We need to keep the section\n newSections.push(currentSection);\n currentSection = nextSection;\n }\n }\n if (currentSection != null) {\n newSections.push(currentSection);\n }\n\n return newSections;\n}\n"],"names":["ColumnAxisType","HybridScaleType","buildHybridScale","sectionDensity","agesOverlap","scaleLinear","getUnitHeightRange","scale","ensureArray"],"mappings":"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"}
@@ -2,6 +2,7 @@ import { ColumnAxisType } from "@macrostrat/column-components";
2
2
  import { ensureArray, agesOverlap, getUnitHeightRange } from "./utils.js";
3
3
  import { scaleLinear } from "d3-scale";
4
4
  import { buildHybridScale } from "./dynamic-scales.js";
5
+ import { sectionDensity } from "./density.js";
5
6
  import { HybridScaleType } from "./types.js";
6
7
  function buildCompositeScaleInfo(inputScales, unconformityHeight) {
7
8
  let totalHeight = unconformityHeight / 2;
@@ -65,7 +66,8 @@ function addScaleToSection(group, opts) {
65
66
  }
66
67
  const scaleInfo = buildSectionScale(units, {
67
68
  ...opts,
68
- domain: _range
69
+ domain: _range,
70
+ sectionID: group.section_id
69
71
  });
70
72
  return {
71
73
  ...group,
@@ -75,12 +77,11 @@ function addScaleToSection(group, opts) {
75
77
  function buildSectionScale(data, opts) {
76
78
  const {
77
79
  targetUnitHeight = 20,
78
- minPixelScale = 0.2,
79
80
  axisType,
80
- minSectionHeight,
81
81
  visibleWindow,
82
82
  scale,
83
- hybridScale
83
+ hybridScale,
84
+ sectionID
84
85
  } = opts;
85
86
  const domain = opts.domain ?? findSectionHeightRange(data, axisType);
86
87
  const dAge = Math.abs(domain[0] - domain[1]);
@@ -89,6 +90,8 @@ function buildSectionScale(data, opts) {
89
90
  if (hybridScale != null) {
90
91
  if (hybridScale.type === HybridScaleType.EquidistantSurfaces) {
91
92
  _pixelScale ??= targetUnitHeight;
93
+ } else {
94
+ _pixelScale = opts.pixelsPerMeter ?? _pixelScale;
92
95
  }
93
96
  return buildHybridScale(hybridScale, data, domain, {
94
97
  pixelOffset: 0,
@@ -96,14 +99,14 @@ function buildSectionScale(data, opts) {
96
99
  });
97
100
  }
98
101
  if (scale == null) {
99
- if (_pixelScale == null) {
100
- const avgAgeRange = findAverageUnitHeight(data, axisType, visibleWindow);
101
- _pixelScale = Math.max(targetUnitHeight / avgAgeRange, minPixelScale);
102
- }
103
- let height = dAge * _pixelScale;
104
- const _minSectionHeight = minSectionHeight ?? targetUnitHeight ?? 0;
105
- pixelHeight = Math.max(height, _minSectionHeight);
106
- _pixelScale = pixelHeight / dAge;
102
+ _pixelScale = sectionDensity(opts)({
103
+ extent: dAge,
104
+ unitExtents: visibleUnitExtents(data, axisType, visibleWindow),
105
+ units: data,
106
+ sectionID,
107
+ axisType
108
+ });
109
+ pixelHeight = dAge * _pixelScale;
107
110
  } else {
108
111
  pixelHeight = Math.abs(scale(domain[0]) - scale(domain[1]));
109
112
  }
@@ -231,7 +234,7 @@ function findSectionHeightRange(data, axisType) {
231
234
  return [b_pos, t_pos];
232
235
  }
233
236
  }
234
- function findAverageUnitHeight(data, axisType, visibleWindow) {
237
+ function visibleUnitExtents(data, axisType, visibleWindow) {
235
238
  const durations = (clip) => data.map((d) => {
236
239
  const [b_pos, t_pos] = getUnitHeightRange(d, axisType);
237
240
  if (!clip) return Math.abs(b_pos - t_pos);
@@ -244,8 +247,7 @@ function findAverageUnitHeight(data, axisType, visibleWindow) {
244
247
  const useWindow = visibleWindow != null && axisType === ColumnAxisType.AGE;
245
248
  let heights = useWindow ? durations(true) : [];
246
249
  if (heights.length === 0) heights = durations(false);
247
- if (heights.length === 0) return 1;
248
- return heights.reduce((a, b) => a + b, 0) / heights.length;
250
+ return heights;
249
251
  }
250
252
  function createCompositeScale(sections, interpolateUnconformities = false) {
251
253
  const scales = [];
@@ -322,7 +324,7 @@ function collapseUnconformitiesByPixelHeight(sections, threshold, opts) {
322
324
  _diff(heights.map(nextSection.scaleInfo.scale)),
323
325
  _diff(heights.map(currentSection.scaleInfo.scale))
324
326
  ];
325
- const pxHeight = Math.min(...pxHeights);
327
+ const pxHeight = Math.max(...pxHeights);
326
328
  if (pxHeight < threshold) {
327
329
  let t_pos;
328
330
  let b_pos;
@@ -1 +1 @@
1
- {"version":3,"file":"composite-scale.js","sources":["../../src/prepare-units/composite-scale.ts"],"sourcesContent":["import { ColumnAxisType } from \"@macrostrat/column-components\";\nimport { agesOverlap, ensureArray, getUnitHeightRange } from \"./utils\";\nimport { ScaleContinuousNumeric, scaleLinear } from \"d3-scale\";\nimport { UnitLong } from \"@macrostrat/api-types\";\nimport { buildHybridScale } from \"./dynamic-scales\";\nimport { ExtUnit, HybridScaleType, SectionInfo } from \"./types\";\nimport type {\n ColumnScaleOptions,\n CompositeColumnData,\n CompositeScaleData,\n PackageLayoutData,\n PackageScaleInfo,\n PackageScaleLayoutData,\n SectionScaleOptions,\n} from \"./types\";\n\n// Composite scale information augmented with units in each package\n\nexport function buildCompositeScaleInfo(\n inputScales: PackageScaleInfo[],\n unconformityHeight: number,\n): CompositeScaleData {\n /** Finalize the heights of sections, including the heights of unconformities\n * between them.\n */\n\n let totalHeight = unconformityHeight / 2;\n let lastSectionTopHeight = 0;\n\n const packages2: PackageScaleLayoutData[] = [];\n for (let i = 0; i < inputScales.length; i++) {\n const group = inputScales[i];\n const { domain, scale } = group;\n const [b_age, t_age] = domain;\n // Positionally stable key: packages are always emitted in age order, so the\n // index is a stable identity. An age-based key (`package-${b_age}-${t_age}`)\n // changes every frame while the age window animates, forcing every package,\n // column, and timescale slice in the correlation chart to remount each frame.\n const key = `package-${i}`;\n\n packages2.push({\n ...createPackageScale(group, totalHeight),\n key,\n offset: totalHeight,\n // Unconformity height above this particular section\n paddingTop: totalHeight - lastSectionTopHeight,\n });\n\n const pixelHeight = Math.abs(scale(b_age) - scale(t_age));\n lastSectionTopHeight = totalHeight + pixelHeight;\n totalHeight = lastSectionTopHeight + unconformityHeight;\n }\n totalHeight += unconformityHeight / 2;\n return {\n totalHeight,\n sections: packages2,\n };\n}\n\nexport function finalizeSectionHeights<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n unconformityHeight: number,\n): CompositeColumnData<T> {\n /** Finalize the heights of sections, including the heights of unconformities\n * between them.\n */\n\n const sectionScales = sections.map((d) => d.scaleInfo);\n\n const { totalHeight, sections: packages } = buildCompositeScaleInfo(\n sectionScales,\n unconformityHeight,\n );\n\n // This could perhaps be simplified.\n const sections1: PackageLayoutData<T>[] = [];\n for (const i in sections) {\n const group = sections[i];\n const scaleInfo = packages[i];\n sections1.push({\n ...group,\n key: scaleInfo.key,\n scaleInfo,\n });\n }\n return {\n totalHeight,\n sections: sections1,\n };\n}\n\nexport interface SectionInfoWithScale<\n T extends UnitLong = ExtUnit,\n> extends SectionInfo<T> {\n scaleInfo: PackageScaleInfo;\n}\n\nexport function computeSectionHeights<T extends UnitLong>(\n sections: SectionInfo<T>[],\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T>[] {\n return sections.map((group) => {\n return addScaleToSection<T>(group, opts);\n });\n}\n\nfunction addScaleToSection<T extends UnitLong = ExtUnit>(\n group: SectionInfo<T>,\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T> {\n const { t_age, b_age, t_pos, b_pos, units } = group;\n let _range = null;\n // if t_age and b_age are set for a group, use them to define the range...\n if (opts.axisType == ColumnAxisType.AGE) {\n _range = [b_age, t_age];\n } else {\n _range = [b_pos, t_pos];\n }\n\n const scaleInfo = buildSectionScale<T>(units, {\n ...opts,\n domain: _range,\n });\n\n return {\n ...group,\n scaleInfo,\n };\n}\n\nfunction buildSectionScale<T extends UnitLong>(\n data: T[],\n opts: SectionScaleOptions,\n): PackageScaleInfo {\n const {\n targetUnitHeight = 20,\n minPixelScale = 0.2,\n axisType,\n minSectionHeight,\n visibleWindow,\n scale,\n hybridScale,\n } = opts;\n const domain = opts.domain ?? findSectionHeightRange(data, axisType);\n\n const dAge = Math.abs(domain[0] - domain[1]);\n\n let _pixelScale = opts.pixelScale;\n let pixelHeight: number;\n\n if (hybridScale != null) {\n /** In an equidistant surfaces scale, we want to determine the heights of surfaces\n * and then distribute units evenly between them.\n * This is somewhat like an ordinal scale\n */\n if (hybridScale.type === HybridScaleType.EquidistantSurfaces) {\n _pixelScale ??= targetUnitHeight;\n }\n\n return buildHybridScale(hybridScale, data, domain, {\n pixelOffset: 0,\n pixelScale: _pixelScale,\n });\n }\n\n if (scale == null) {\n if (_pixelScale == null) {\n const avgAgeRange = findAverageUnitHeight(data, axisType, visibleWindow);\n // Get pixel height necessary to render average unit at target height\n _pixelScale = Math.max(targetUnitHeight / avgAgeRange, minPixelScale);\n\n // OLD METHOD that cares about overall section height vs. individual unit height\n // 0.2 pixel per myr is the floor scale\n //const targetHeight = targetUnitHeight * data.length;\n // 1 pixel per myr is the floor scale\n //_pixelScale = Math.max(targetHeight / dAge, minPixelScale);\n }\n\n let height = dAge * _pixelScale;\n // If height is less than minSectionHeight, set it to minSectionHeight.\n // Sections reach here at their *full* extent (the rendered window is applied\n // afterwards, by `trimSectionsToWindow`), so this floor only ever inflates a\n // genuinely small section — which is what it's for — and never a sliver that\n // the window happens to cut.\n const _minSectionHeight = minSectionHeight ?? targetUnitHeight ?? 0;\n pixelHeight = Math.max(height, _minSectionHeight);\n _pixelScale = pixelHeight / dAge;\n } else {\n // If a scale is provided, use it to compute pixel height\n pixelHeight = Math.abs(scale(domain[0]) - scale(domain[1]));\n }\n\n return createPackageScale(\n { scale, domain, pixelHeight, pixelScale: _pixelScale },\n 0,\n );\n}\n\n/**\n * The density each section will actually render at once the window is applied.\n *\n * A section the window cuts short is still being *looked at*, so\n * `minSectionHeight` applies to what remains — it's honored by expanding the\n * section's scale, not by showing more of it. Resolving this **before** padding\n * matters: padding is a pixel margin measured at the density it will render at,\n * and a section stretched to the floor afterwards would multiply the margin\n * along with everything else (a 20 px margin in a band stretched 4× renders at\n * 80 px, and asymmetrically so whenever one side is truncated).\n *\n * Sections the window leaves whole keep their laid-out density — they already\n * met the floor when they were sized.\n */\nexport function resolveWindowScales<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n focalWindow: [number, number],\n floor: number,\n): Map<SectionInfoWithScale<T>, number> {\n const [b_focal, t_focal] = focalWindow;\n const scales = new Map<SectionInfoWithScale<T>, number>();\n\n for (const section of sections) {\n const t_age = Math.max(section.t_age, t_focal);\n const b_age = Math.min(section.b_age, b_focal);\n // Sections outside the focal window are visible only as padding; they keep\n // their own density and render exactly the pixels asked for.\n if (b_age <= t_age) continue;\n\n const natural = section.scaleInfo.pixelScale;\n if (!(natural > 0)) continue;\n\n const isClipped = b_age < section.b_age || t_age > section.t_age;\n const needed = isClipped && floor > 0 ? floor / (b_age - t_age) : 0;\n scales.set(section, Math.max(natural, needed));\n }\n return scales;\n}\n\n/**\n * Expand an age window outward by `padding` **pixels** of column, spending the\n * budget against sections that are already laid out at full extent, at the\n * densities they will finally render at (`scaleFor`).\n *\n * Because the layout is done, this is exact rather than predicted: each section\n * contributes its own real pixel height. Padding collapses when the column runs\n * out on that side.\n *\n * Unconformities between sections cost nothing. They render at a fixed height\n * that's typically larger than the padding itself (30–60 px), so charging them\n * would mean padding could never reach across a gap — which is the case it\n * exists for: revealing the neighboring section keeps its timescale intervals\n * navigable. The padding is a budget of neighboring *content*.\n */\nexport function padWindowByPixels<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n window: [number, number],\n padding: number,\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): [number, number] {\n if (!(padding > 0) || sections.length === 0) return window;\n const [b_age, t_age] = window;\n return [\n spendOutward(sections, b_age, 1, padding, scaleFor),\n spendOutward(sections, t_age, -1, padding, scaleFor),\n ];\n}\n\n/** Walk out from one edge of the window through laid-out sections, spending a\n * pixel budget, and return the age it lands on. `direction` is +1 toward older\n * ages, -1 toward younger. */\nfunction spendOutward<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n edge: number,\n direction: 1 | -1,\n padding: number,\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): number {\n // Sections ordered outward from the edge, keeping only what lies beyond it.\n const beyond = sections\n .filter(\n (s) => direction * (s.b_age - edge) > 0 || direction * (s.t_age - edge) > 0,\n )\n .sort((a, b) => direction * (nearEdge(a, direction) - nearEdge(b, direction)));\n\n let bound = edge;\n let budget = padding;\n\n for (const section of beyond) {\n const pixelScale = scaleFor(section);\n if (!(pixelScale > 0)) continue;\n\n const from =\n direction > 0\n ? Math.max(section.t_age, bound)\n : Math.min(section.b_age, bound);\n const available = direction > 0 ? section.b_age - from : from - section.t_age;\n if (available <= 0) continue;\n\n const cost = available * pixelScale;\n if (budget <= cost) return from + direction * (budget / pixelScale);\n\n bound = direction > 0 ? section.b_age : section.t_age;\n budget -= cost;\n }\n return bound;\n}\n\n/** The age at which a section starts, measured from the window edge outward. */\nfunction nearEdge<T extends UnitLong>(\n section: SectionInfoWithScale<T>,\n direction: 1 | -1,\n): number {\n return direction > 0 ? section.t_age : section.b_age;\n}\n\n/**\n * Clip laid-out sections to the rendered window — the last step of layout, so\n * that every section has already been sized (and floored) at its full extent.\n * Each section renders at the density `scaleFor` resolved for it, which is what\n * keeps a padding margin the same number of pixels as the band it borders is\n * stretched to meet `minSectionHeight`.\n */\nexport function trimSectionsToWindow<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n window: [number, number],\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): SectionInfoWithScale<T>[] {\n const [b_win, t_win] = window;\n const out: SectionInfoWithScale<T>[] = [];\n\n for (const section of sections) {\n const t_age = Math.max(section.t_age, t_win);\n const b_age = Math.min(section.b_age, b_win);\n if (b_age <= t_age) continue;\n\n const pixelScale = scaleFor(section);\n if (\n b_age === section.b_age &&\n t_age === section.t_age &&\n pixelScale === section.scaleInfo.pixelScale\n ) {\n out.push(section);\n continue;\n }\n\n out.push({\n ...section,\n t_age,\n b_age,\n units: section.units.filter((u) => agesOverlap(u, { t_age, b_age })),\n scaleInfo: createPackageScale({\n domain: [b_age, t_age],\n pixelScale,\n pixelHeight: (b_age - t_age) * pixelScale,\n scale: null,\n }),\n });\n }\n return out;\n}\n\nexport function createPackageScale(\n def: PackageScaleInfo,\n offset: number = 0,\n): PackageScaleInfo {\n /** Build a section scale */\n // Domain should be oriented from bottom to top, but scale is oriented from top to bottom\n const { domain, pixelScale, pixelHeight, scale, heightScale } = def;\n\n if (scale == null && pixelScale == null) {\n throw new Error(\"Either scale or pixelScale must be provided\");\n }\n\n let _scale: ScaleContinuousNumeric<number, number>;\n if (scale == null) {\n _scale = scaleLinear()\n .domain([domain[1], domain[0]])\n .range([offset, pixelHeight + offset]);\n } else {\n const domain0 = scale.domain();\n const range0 = scale.range();\n _scale = scale\n .copy()\n .domain(domain0)\n .range(range0.map((d) => d + offset));\n }\n\n _scale.clamp();\n\n let _heightScale = null;\n if (heightScale != null) {\n // Adjust height scale as well\n const range0 = scale.range();\n _heightScale = heightScale.copy().range(range0.map((d) => d + offset));\n _heightScale.clamp();\n }\n\n return {\n domain,\n pixelScale,\n pixelHeight,\n scale: _scale,\n // Internal details for hybrid scales. TODO: improve this\n heightScale: _heightScale,\n };\n}\n\nfunction findSectionHeightRange(\n data: UnitLong[],\n axisType: ColumnAxisType,\n): [number, number] {\n if (axisType == null) {\n throw new Error(\"Axis type is not set\");\n }\n if (axisType === ColumnAxisType.AGE) {\n const t_age = Math.min(...data.map((d) => d.t_age));\n const b_age = Math.max(...data.map((d) => d.b_age));\n return [b_age, t_age];\n } else if (\n axisType == ColumnAxisType.DEPTH ||\n axisType == ColumnAxisType.ORDINAL\n ) {\n const t_pos = Math.min(...data.map((d) => d.t_pos));\n const b_pos = Math.max(...data.map((d) => d.b_pos));\n return [b_pos, t_pos];\n } else if (axisType == ColumnAxisType.HEIGHT) {\n const t_pos = Math.max(...data.map((d) => d.t_pos));\n const b_pos = Math.min(...data.map((d) => d.b_pos));\n return [b_pos, t_pos];\n }\n}\n\nfunction findAverageUnitHeight(\n data: UnitLong[],\n axisType: ColumnAxisType,\n visibleWindow?: [number, number] | null,\n): number {\n /** The typical duration of a unit, which `targetUnitHeight` sizes.\n *\n * Measured over what the render window actually *shows* — units outside it\n * are ignored and a unit it cuts through counts only for its visible part.\n * That's what makes `targetUnitHeight` mean the same thing at every zoom\n * depth: the units on screen are drawn at the target height whether they're\n * whole or slivers of something much longer.\n *\n * A section the window doesn't reach has nothing visible to measure, so it\n * falls back to its own units at full duration — it's drawn at its own scale.\n * (Deriving it from the padded window instead would be circular: padding is\n * measured in pixels, which is what we're trying to establish.)\n */\n const durations = (clip: boolean) =>\n data\n .map((d) => {\n const [b_pos, t_pos] = getUnitHeightRange(d, axisType);\n if (!clip) return Math.abs(b_pos - t_pos);\n const [b_win, t_win] = visibleWindow;\n return Math.max(\n 0,\n Math.min(b_pos, b_win) - Math.max(t_pos, t_win),\n );\n })\n .filter((d) => d > 0);\n\n const useWindow = visibleWindow != null && axisType === ColumnAxisType.AGE;\n let heights = useWindow ? durations(true) : [];\n if (heights.length === 0) heights = durations(false);\n if (heights.length === 0) return 1;\n\n return heights.reduce((a, b) => a + b, 0) / heights.length;\n}\n\nexport interface CompositeColumnScale {\n (age: number): number | null;\n copy(): CompositeColumnScale;\n domain(): [number, number];\n invert(pixelHeight: number): number | null;\n clamp(clamp: boolean): void;\n}\n\nexport function createCompositeScale(\n sections: PackageLayoutData[],\n interpolateUnconformities: boolean = false,\n): CompositeColumnScale {\n /** Create a scale that works across multiple packages */\n\n const scales: ScaleContinuousNumeric<number, number>[] = [];\n\n let lastScale: ScaleContinuousNumeric<number, number> | null = null;\n for (const section of sections) {\n const _scale = section.scaleInfo.scale.copy().clamp(true);\n scales.push(_scale);\n if (lastScale != null && interpolateUnconformities) {\n // Add a new scale that interpolates between lastScale and _scale\n const lastDomain = lastScale.domain();\n const lastRange = lastScale.range();\n const currentDomain = _scale.domain();\n const currentRange = _scale.range();\n\n const interpScale = scaleLinear()\n .domain([lastDomain[lastDomain.length - 1], currentDomain[0]])\n .range([lastRange[lastRange.length - 1], currentRange[0]])\n .clamp(true);\n scales.push(interpScale);\n }\n lastScale = _scale;\n }\n\n const scale: CompositeColumnScale = (age) => {\n for (const s of scales) {\n const domain = s.domain();\n if (\n domain[0] < domain[domain.length - 1] &&\n age >= domain[0] &&\n age <= domain[domain.length - 1]\n ) {\n // Age axes\n return s(age);\n } else if (age <= domain[0] && age >= domain[domain.length - 1]) {\n // Normal axes like height\n return s(age);\n }\n }\n return null;\n };\n\n scale.copy = () => {\n return createCompositeScale(sections, interpolateUnconformities);\n };\n\n scale.domain = () => {\n /** Return the domain of the scale */\n const vals = sections.flatMap((d) => d.scaleInfo.domain);\n if (vals[0] < vals[vals.length - 1]) {\n // age axes\n return [Math.min(...vals), Math.max(...vals)];\n }\n // Normal axes like height\n return [Math.max(...vals), Math.min(...vals)];\n };\n\n scale.invert = (pixelHeight) => {\n /** Invert the scale to get the age at a given pixel height */\n // Iterate through the sections to find the correct one\n for (const scale of scales) {\n const range = scale.range();\n if (\n pixelHeight > Math.min(...range) &&\n pixelHeight <= Math.max(...range)\n ) {\n //console.log(\"Inverting scale at pixel height\", pixelHeight);\n return scale.invert(pixelHeight);\n }\n }\n return null;\n };\n\n scale.clamp = (clamp: boolean) => {\n /** Clamp all constituent scales */\n for (const s of scales) {\n s.clamp(clamp);\n }\n };\n\n return scale;\n}\n\n/** Collapse sections separated by unconformities that are smaller than a given pixel height. */\nexport function collapseUnconformitiesByPixelHeight<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n threshold: number,\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T>[] {\n const newSections = [];\n let currentSection: SectionInfoWithScale<T> | null = null;\n for (const nextSection of sections) {\n if (currentSection == null) {\n currentSection = nextSection;\n continue;\n }\n let heights: [number, number];\n let pxHeights: [number, number];\n if (opts.axisType !== ColumnAxisType.AGE) {\n heights = [nextSection.t_pos, currentSection.b_pos];\n } else {\n heights = [nextSection.t_age, currentSection.b_age];\n }\n\n const _diff = (vals: number[]) => {\n return Math.abs(vals[0] - vals[1]);\n };\n\n pxHeights = [\n _diff(heights.map(nextSection.scaleInfo.scale)),\n _diff(heights.map(currentSection.scaleInfo.scale)),\n ];\n\n const pxHeight = Math.min(...pxHeights);\n\n if (pxHeight < threshold) {\n let t_pos: number;\n let b_pos: number;\n if (\n opts.axisType == ColumnAxisType.AGE ||\n opts.axisType == ColumnAxisType.DEPTH\n ) {\n t_pos = Math.min(currentSection.t_pos, nextSection.t_pos);\n b_pos = Math.max(currentSection.b_pos, nextSection.b_pos);\n } else {\n t_pos = Math.max(currentSection.t_pos, nextSection.t_pos);\n b_pos = Math.min(currentSection.b_pos, nextSection.b_pos);\n }\n\n // We need to merge the sections\n const compositeSection0: SectionInfo<T> = {\n units: [...currentSection.units, ...nextSection.units],\n section_id: [\n ...ensureArray(currentSection.section_id),\n ...ensureArray(nextSection.section_id),\n ],\n t_age: Math.min(currentSection.t_age, nextSection.t_age),\n b_age: Math.max(currentSection.b_age, nextSection.b_age),\n t_pos,\n b_pos,\n };\n\n currentSection = addScaleToSection(compositeSection0, opts);\n } else {\n // We need to keep the section\n newSections.push(currentSection);\n currentSection = nextSection;\n }\n }\n if (currentSection != null) {\n newSections.push(currentSection);\n }\n\n return newSections;\n}\n"],"names":["scale"],"mappings":"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"}
1
+ {"version":3,"file":"composite-scale.js","sources":["../../src/prepare-units/composite-scale.ts"],"sourcesContent":["import { ColumnAxisType } from \"@macrostrat/column-components\";\nimport { agesOverlap, ensureArray, getUnitHeightRange } from \"./utils\";\nimport { ScaleContinuousNumeric, scaleLinear } from \"d3-scale\";\nimport { UnitLong } from \"@macrostrat/api-types\";\nimport { buildHybridScale } from \"./dynamic-scales\";\nimport { sectionDensity } from \"./density\";\nimport { ExtUnit, HybridScaleType, SectionInfo } from \"./types\";\nimport type {\n ColumnScaleOptions,\n CompositeColumnData,\n CompositeScaleData,\n PackageLayoutData,\n PackageScaleInfo,\n PackageScaleLayoutData,\n SectionScaleOptions,\n} from \"./types\";\n\n// Composite scale information augmented with units in each package\n\nexport function buildCompositeScaleInfo(\n inputScales: PackageScaleInfo[],\n unconformityHeight: number,\n): CompositeScaleData {\n /** Finalize the heights of sections, including the heights of unconformities\n * between them.\n */\n\n let totalHeight = unconformityHeight / 2;\n let lastSectionTopHeight = 0;\n\n const packages2: PackageScaleLayoutData[] = [];\n for (let i = 0; i < inputScales.length; i++) {\n const group = inputScales[i];\n const { domain, scale } = group;\n const [b_age, t_age] = domain;\n // Positionally stable key: packages are always emitted in age order, so the\n // index is a stable identity. An age-based key (`package-${b_age}-${t_age}`)\n // changes every frame while the age window animates, forcing every package,\n // column, and timescale slice in the correlation chart to remount each frame.\n const key = `package-${i}`;\n\n packages2.push({\n ...createPackageScale(group, totalHeight),\n key,\n offset: totalHeight,\n // Unconformity height above this particular section\n paddingTop: totalHeight - lastSectionTopHeight,\n });\n\n const pixelHeight = Math.abs(scale(b_age) - scale(t_age));\n lastSectionTopHeight = totalHeight + pixelHeight;\n totalHeight = lastSectionTopHeight + unconformityHeight;\n }\n totalHeight += unconformityHeight / 2;\n return {\n totalHeight,\n sections: packages2,\n };\n}\n\nexport function finalizeSectionHeights<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n unconformityHeight: number,\n): CompositeColumnData<T> {\n /** Finalize the heights of sections, including the heights of unconformities\n * between them.\n */\n\n const sectionScales = sections.map((d) => d.scaleInfo);\n\n const { totalHeight, sections: packages } = buildCompositeScaleInfo(\n sectionScales,\n unconformityHeight,\n );\n\n // This could perhaps be simplified.\n const sections1: PackageLayoutData<T>[] = [];\n for (const i in sections) {\n const group = sections[i];\n const scaleInfo = packages[i];\n sections1.push({\n ...group,\n key: scaleInfo.key,\n scaleInfo,\n });\n }\n return {\n totalHeight,\n sections: sections1,\n };\n}\n\nexport interface SectionInfoWithScale<\n T extends UnitLong = ExtUnit,\n> extends SectionInfo<T> {\n scaleInfo: PackageScaleInfo;\n}\n\nexport function computeSectionHeights<T extends UnitLong>(\n sections: SectionInfo<T>[],\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T>[] {\n return sections.map((group) => {\n return addScaleToSection<T>(group, opts);\n });\n}\n\nfunction addScaleToSection<T extends UnitLong = ExtUnit>(\n group: SectionInfo<T>,\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T> {\n const { t_age, b_age, t_pos, b_pos, units } = group;\n let _range = null;\n // if t_age and b_age are set for a group, use them to define the range...\n if (opts.axisType == ColumnAxisType.AGE) {\n _range = [b_age, t_age];\n } else {\n _range = [b_pos, t_pos];\n }\n\n const scaleInfo = buildSectionScale<T>(units, {\n ...opts,\n domain: _range,\n sectionID: group.section_id,\n });\n\n return {\n ...group,\n scaleInfo,\n };\n}\n\nfunction buildSectionScale<T extends UnitLong>(\n data: T[],\n opts: SectionScaleOptions,\n): PackageScaleInfo {\n const {\n targetUnitHeight = 20,\n axisType,\n visibleWindow,\n scale,\n hybridScale,\n sectionID,\n } = opts;\n const domain = opts.domain ?? findSectionHeightRange(data, axisType);\n\n const dAge = Math.abs(domain[0] - domain[1]);\n\n let _pixelScale: any = opts.pixelScale;\n let pixelHeight: number;\n\n if (hybridScale != null) {\n /** In an equidistant surfaces scale, we want to determine the heights of surfaces\n * and then distribute units evenly between them.\n * This is somewhat like an ordinal scale\n */\n if (hybridScale.type === HybridScaleType.EquidistantSurfaces) {\n // Pixels between one surface and the next: neither axis's units\n _pixelScale ??= targetUnitHeight;\n } else {\n // An approximate-height column is drawn from measured thickness, so it\n // is metres per pixel whatever the axis is labeled in\n _pixelScale = opts.pixelsPerMeter ?? _pixelScale;\n }\n\n return buildHybridScale(hybridScale, data, domain, {\n pixelOffset: 0,\n pixelScale: _pixelScale,\n });\n }\n\n if (scale == null) {\n /** Every rule that sets a section's height resolves to one density (see\n * `./density`). Sections reach here at their *full* extent — the rendered\n * window is applied afterwards, by `trimSectionsToWindow` — so a floor on\n * the section's height only ever inflates a genuinely small section, which\n * is what it's for, and never a sliver the window happens to cut. */\n _pixelScale = sectionDensity(opts)({\n extent: dAge,\n unitExtents: visibleUnitExtents(data, axisType, visibleWindow),\n units: data,\n sectionID,\n axisType,\n });\n pixelHeight = dAge * _pixelScale;\n } else {\n // If a scale is provided, use it to compute pixel height\n pixelHeight = Math.abs(scale(domain[0]) - scale(domain[1]));\n }\n\n return createPackageScale(\n { scale, domain, pixelHeight, pixelScale: _pixelScale },\n 0,\n );\n}\n\n/**\n * The density each section will actually render at once the window is applied.\n *\n * A section the window cuts short is still being *looked at*, so\n * `minSectionHeight` applies to what remains — it's honored by expanding the\n * section's scale, not by showing more of it. Resolving this **before** padding\n * matters: padding is a pixel margin measured at the density it will render at,\n * and a section stretched to the floor afterwards would multiply the margin\n * along with everything else (a 20 px margin in a band stretched 4× renders at\n * 80 px, and asymmetrically so whenever one side is truncated).\n *\n * Sections the window leaves whole keep their laid-out density — they already\n * met the floor when they were sized.\n */\nexport function resolveWindowScales<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n focalWindow: [number, number],\n floor: number,\n): Map<SectionInfoWithScale<T>, number> {\n const [b_focal, t_focal] = focalWindow;\n const scales = new Map<SectionInfoWithScale<T>, number>();\n\n for (const section of sections) {\n const t_age = Math.max(section.t_age, t_focal);\n const b_age = Math.min(section.b_age, b_focal);\n // Sections outside the focal window are visible only as padding; they keep\n // their own density and render exactly the pixels asked for.\n if (b_age <= t_age) continue;\n\n const natural = section.scaleInfo.pixelScale;\n if (!(natural > 0)) continue;\n\n const isClipped = b_age < section.b_age || t_age > section.t_age;\n const needed = isClipped && floor > 0 ? floor / (b_age - t_age) : 0;\n scales.set(section, Math.max(natural, needed));\n }\n return scales;\n}\n\n/**\n * Expand an age window outward by `padding` **pixels** of column, spending the\n * budget against sections that are already laid out at full extent, at the\n * densities they will finally render at (`scaleFor`).\n *\n * Because the layout is done, this is exact rather than predicted: each section\n * contributes its own real pixel height. Padding collapses when the column runs\n * out on that side.\n *\n * Unconformities between sections cost nothing. They render at a fixed height\n * that's typically larger than the padding itself (30–60 px), so charging them\n * would mean padding could never reach across a gap — which is the case it\n * exists for: revealing the neighboring section keeps its timescale intervals\n * navigable. The padding is a budget of neighboring *content*.\n */\nexport function padWindowByPixels<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n window: [number, number],\n padding: number,\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): [number, number] {\n if (!(padding > 0) || sections.length === 0) return window;\n const [b_age, t_age] = window;\n return [\n spendOutward(sections, b_age, 1, padding, scaleFor),\n spendOutward(sections, t_age, -1, padding, scaleFor),\n ];\n}\n\n/** Walk out from one edge of the window through laid-out sections, spending a\n * pixel budget, and return the age it lands on. `direction` is +1 toward older\n * ages, -1 toward younger. */\nfunction spendOutward<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n edge: number,\n direction: 1 | -1,\n padding: number,\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): number {\n // Sections ordered outward from the edge, keeping only what lies beyond it.\n const beyond = sections\n .filter(\n (s) => direction * (s.b_age - edge) > 0 || direction * (s.t_age - edge) > 0,\n )\n .sort((a, b) => direction * (nearEdge(a, direction) - nearEdge(b, direction)));\n\n let bound = edge;\n let budget = padding;\n\n for (const section of beyond) {\n const pixelScale = scaleFor(section);\n if (!(pixelScale > 0)) continue;\n\n const from =\n direction > 0\n ? Math.max(section.t_age, bound)\n : Math.min(section.b_age, bound);\n const available = direction > 0 ? section.b_age - from : from - section.t_age;\n if (available <= 0) continue;\n\n const cost = available * pixelScale;\n if (budget <= cost) return from + direction * (budget / pixelScale);\n\n bound = direction > 0 ? section.b_age : section.t_age;\n budget -= cost;\n }\n return bound;\n}\n\n/** The age at which a section starts, measured from the window edge outward. */\nfunction nearEdge<T extends UnitLong>(\n section: SectionInfoWithScale<T>,\n direction: 1 | -1,\n): number {\n return direction > 0 ? section.t_age : section.b_age;\n}\n\n/**\n * Clip laid-out sections to the rendered window — the last step of layout, so\n * that every section has already been sized (and floored) at its full extent.\n * Each section renders at the density `scaleFor` resolved for it, which is what\n * keeps a padding margin the same number of pixels as the band it borders is\n * stretched to meet `minSectionHeight`.\n */\nexport function trimSectionsToWindow<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n window: [number, number],\n scaleFor: (section: SectionInfoWithScale<T>) => number,\n): SectionInfoWithScale<T>[] {\n const [b_win, t_win] = window;\n const out: SectionInfoWithScale<T>[] = [];\n\n for (const section of sections) {\n const t_age = Math.max(section.t_age, t_win);\n const b_age = Math.min(section.b_age, b_win);\n if (b_age <= t_age) continue;\n\n const pixelScale = scaleFor(section);\n if (\n b_age === section.b_age &&\n t_age === section.t_age &&\n pixelScale === section.scaleInfo.pixelScale\n ) {\n out.push(section);\n continue;\n }\n\n out.push({\n ...section,\n t_age,\n b_age,\n units: section.units.filter((u) => agesOverlap(u, { t_age, b_age })),\n scaleInfo: createPackageScale({\n domain: [b_age, t_age],\n pixelScale,\n pixelHeight: (b_age - t_age) * pixelScale,\n scale: null,\n }),\n });\n }\n return out;\n}\n\nexport function createPackageScale(\n def: PackageScaleInfo,\n offset: number = 0,\n): PackageScaleInfo {\n /** Build a section scale */\n // Domain should be oriented from bottom to top, but scale is oriented from top to bottom\n const { domain, pixelScale, pixelHeight, scale, heightScale } = def;\n\n if (scale == null && pixelScale == null) {\n throw new Error(\"Either scale or pixelScale must be provided\");\n }\n\n let _scale: ScaleContinuousNumeric<number, number>;\n if (scale == null) {\n _scale = scaleLinear()\n .domain([domain[1], domain[0]])\n .range([offset, pixelHeight + offset]);\n } else {\n const domain0 = scale.domain();\n const range0 = scale.range();\n _scale = scale\n .copy()\n .domain(domain0)\n .range(range0.map((d) => d + offset));\n }\n\n _scale.clamp();\n\n let _heightScale = null;\n if (heightScale != null) {\n // Adjust height scale as well\n const range0 = scale.range();\n _heightScale = heightScale.copy().range(range0.map((d) => d + offset));\n _heightScale.clamp();\n }\n\n return {\n domain,\n pixelScale,\n pixelHeight,\n scale: _scale,\n // Internal details for hybrid scales. TODO: improve this\n heightScale: _heightScale,\n };\n}\n\nfunction findSectionHeightRange(\n data: UnitLong[],\n axisType: ColumnAxisType,\n): [number, number] {\n if (axisType == null) {\n throw new Error(\"Axis type is not set\");\n }\n if (axisType === ColumnAxisType.AGE) {\n const t_age = Math.min(...data.map((d) => d.t_age));\n const b_age = Math.max(...data.map((d) => d.b_age));\n return [b_age, t_age];\n } else if (\n axisType == ColumnAxisType.DEPTH ||\n axisType == ColumnAxisType.ORDINAL\n ) {\n const t_pos = Math.min(...data.map((d) => d.t_pos));\n const b_pos = Math.max(...data.map((d) => d.b_pos));\n return [b_pos, t_pos];\n } else if (axisType == ColumnAxisType.HEIGHT) {\n const t_pos = Math.max(...data.map((d) => d.t_pos));\n const b_pos = Math.min(...data.map((d) => d.b_pos));\n return [b_pos, t_pos];\n }\n}\n\nfunction visibleUnitExtents(\n data: UnitLong[],\n axisType: ColumnAxisType,\n visibleWindow?: [number, number] | null,\n): number[] {\n /** The durations of the units a density rule gets to size.\n *\n * Measured over what the render window actually *shows* — units outside it\n * are ignored and a unit it cuts through counts only for its visible part.\n * That's what makes `targetUnitHeight` mean the same thing at every zoom\n * depth: the units on screen are drawn at the target height whether they're\n * whole or slivers of something much longer.\n *\n * A section the window doesn't reach has nothing visible to measure, so it\n * falls back to its own units at full duration — it's drawn at its own scale.\n * (Deriving it from the padded window instead would be circular: padding is\n * measured in pixels, which is what we're trying to establish.)\n */\n const durations = (clip: boolean) =>\n data\n .map((d) => {\n const [b_pos, t_pos] = getUnitHeightRange(d, axisType);\n if (!clip) return Math.abs(b_pos - t_pos);\n const [b_win, t_win] = visibleWindow;\n return Math.max(\n 0,\n Math.min(b_pos, b_win) - Math.max(t_pos, t_win),\n );\n })\n .filter((d) => d > 0);\n\n const useWindow = visibleWindow != null && axisType === ColumnAxisType.AGE;\n let heights = useWindow ? durations(true) : [];\n if (heights.length === 0) heights = durations(false);\n return heights;\n}\n\nexport interface CompositeColumnScale {\n (age: number): number | null;\n copy(): CompositeColumnScale;\n domain(): [number, number];\n invert(pixelHeight: number): number | null;\n clamp(clamp: boolean): void;\n}\n\nexport function createCompositeScale(\n sections: PackageLayoutData[],\n interpolateUnconformities: boolean = false,\n): CompositeColumnScale {\n /** Create a scale that works across multiple packages */\n\n const scales: ScaleContinuousNumeric<number, number>[] = [];\n\n let lastScale: ScaleContinuousNumeric<number, number> | null = null;\n for (const section of sections) {\n const _scale = section.scaleInfo.scale.copy().clamp(true);\n scales.push(_scale);\n if (lastScale != null && interpolateUnconformities) {\n // Add a new scale that interpolates between lastScale and _scale\n const lastDomain = lastScale.domain();\n const lastRange = lastScale.range();\n const currentDomain = _scale.domain();\n const currentRange = _scale.range();\n\n const interpScale = scaleLinear()\n .domain([lastDomain[lastDomain.length - 1], currentDomain[0]])\n .range([lastRange[lastRange.length - 1], currentRange[0]])\n .clamp(true);\n scales.push(interpScale);\n }\n lastScale = _scale;\n }\n\n const scale: CompositeColumnScale = (age) => {\n for (const s of scales) {\n const domain = s.domain();\n if (\n domain[0] < domain[domain.length - 1] &&\n age >= domain[0] &&\n age <= domain[domain.length - 1]\n ) {\n // Age axes\n return s(age);\n } else if (age <= domain[0] && age >= domain[domain.length - 1]) {\n // Normal axes like height\n return s(age);\n }\n }\n return null;\n };\n\n scale.copy = () => {\n return createCompositeScale(sections, interpolateUnconformities);\n };\n\n scale.domain = () => {\n /** Return the domain of the scale */\n const vals = sections.flatMap((d) => d.scaleInfo.domain);\n if (vals[0] < vals[vals.length - 1]) {\n // age axes\n return [Math.min(...vals), Math.max(...vals)];\n }\n // Normal axes like height\n return [Math.max(...vals), Math.min(...vals)];\n };\n\n scale.invert = (pixelHeight) => {\n /** Invert the scale to get the age at a given pixel height */\n // Iterate through the sections to find the correct one\n for (const scale of scales) {\n const range = scale.range();\n if (\n pixelHeight > Math.min(...range) &&\n pixelHeight <= Math.max(...range)\n ) {\n //console.log(\"Inverting scale at pixel height\", pixelHeight);\n return scale.invert(pixelHeight);\n }\n }\n return null;\n };\n\n scale.clamp = (clamp: boolean) => {\n /** Clamp all constituent scales */\n for (const s of scales) {\n s.clamp(clamp);\n }\n };\n\n return scale;\n}\n\n/** Collapse sections separated by unconformities that are smaller than a given pixel height. */\nexport function collapseUnconformitiesByPixelHeight<T extends UnitLong>(\n sections: SectionInfoWithScale<T>[],\n threshold: number,\n opts: ColumnScaleOptions,\n): SectionInfoWithScale<T>[] {\n const newSections = [];\n let currentSection: SectionInfoWithScale<T> | null = null;\n for (const nextSection of sections) {\n if (currentSection == null) {\n currentSection = nextSection;\n continue;\n }\n let heights: [number, number];\n let pxHeights: [number, number];\n if (opts.axisType !== ColumnAxisType.AGE) {\n heights = [nextSection.t_pos, currentSection.b_pos];\n } else {\n heights = [nextSection.t_age, currentSection.b_age];\n }\n\n const _diff = (vals: number[]) => {\n return Math.abs(vals[0] - vals[1]);\n };\n\n pxHeights = [\n _diff(heights.map(nextSection.scaleInfo.scale)),\n _diff(heights.map(currentSection.scaleInfo.scale)),\n ];\n\n /** The gap has no density of its own — it falls between two sections that\n * may be drawn at very different ones — so it is judged at the finer of\n * the two. Taking the smaller estimate let a sparse neighbor speak for a\n * gap the other neighbor would have drawn many times larger: a 16 Myr\n * hiatus in column 22 read as 26px against one section and 166px against\n * the other, and collapsed. A gap is only worth hiding when neither scale\n * would give it more room than the break that replaces it. */\n const pxHeight = Math.max(...pxHeights);\n\n if (pxHeight < threshold) {\n let t_pos: number;\n let b_pos: number;\n if (\n opts.axisType == ColumnAxisType.AGE ||\n opts.axisType == ColumnAxisType.DEPTH\n ) {\n t_pos = Math.min(currentSection.t_pos, nextSection.t_pos);\n b_pos = Math.max(currentSection.b_pos, nextSection.b_pos);\n } else {\n t_pos = Math.max(currentSection.t_pos, nextSection.t_pos);\n b_pos = Math.min(currentSection.b_pos, nextSection.b_pos);\n }\n\n // We need to merge the sections\n const compositeSection0: SectionInfo<T> = {\n units: [...currentSection.units, ...nextSection.units],\n section_id: [\n ...ensureArray(currentSection.section_id),\n ...ensureArray(nextSection.section_id),\n ],\n t_age: Math.min(currentSection.t_age, nextSection.t_age),\n b_age: Math.max(currentSection.b_age, nextSection.b_age),\n t_pos,\n b_pos,\n };\n\n currentSection = addScaleToSection(compositeSection0, opts);\n } else {\n // We need to keep the section\n newSections.push(currentSection);\n currentSection = nextSection;\n }\n }\n if (currentSection != null) {\n newSections.push(currentSection);\n }\n\n return newSections;\n}\n"],"names":["scale"],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const columnComponents = require("@macrostrat/column-components");
4
+ function unitHeight(px) {
5
+ return (ctx) => px / typicalExtent(ctx.unitExtents);
6
+ }
7
+ function sectionHeight(px) {
8
+ return (ctx) => px / ctx.extent;
9
+ }
10
+ function fixedDensity(px) {
11
+ return () => px;
12
+ }
13
+ function atLeast(...rules) {
14
+ return (ctx) => {
15
+ let density = 0;
16
+ for (const rule of rules) {
17
+ const value = resolveDensity(rule, ctx);
18
+ if (!Number.isFinite(value)) continue;
19
+ density = Math.max(density, value);
20
+ }
21
+ return density;
22
+ };
23
+ }
24
+ function resolveDensity(rule, ctx) {
25
+ if (typeof rule === "number") return rule;
26
+ return rule(ctx);
27
+ }
28
+ function typicalExtent(extents) {
29
+ const sorted = extents.filter((d) => d > 0).sort((a, b) => a - b);
30
+ if (sorted.length === 0) return NaN;
31
+ const mid = (sorted.length - 1) / 2;
32
+ const lower = Math.floor(mid);
33
+ const upper = Math.ceil(mid);
34
+ return (sorted[lower] + sorted[upper]) / 2;
35
+ }
36
+ const DEFAULT_TARGET_UNIT_HEIGHT = 20;
37
+ const DEFAULT_MIN_PIXEL_SCALE = 0.2;
38
+ function sectionDensity(opts) {
39
+ const { sectionOptions } = opts;
40
+ if (sectionOptions == null) {
41
+ const rule = densityRule(opts);
42
+ return (ctx) => rule(ctx);
43
+ }
44
+ return (ctx) => {
45
+ let overrides = sectionOptions;
46
+ if (typeof sectionOptions === "function") overrides = sectionOptions(ctx);
47
+ return densityRule({ ...opts, ...overrides })(ctx);
48
+ };
49
+ }
50
+ function densityRule(opts) {
51
+ const {
52
+ targetUnitHeight = DEFAULT_TARGET_UNIT_HEIGHT,
53
+ minPixelScale = DEFAULT_MIN_PIXEL_SCALE,
54
+ minSectionHeight
55
+ } = opts;
56
+ const derived = atLeast(
57
+ unitHeight(targetUnitHeight),
58
+ fixedDensity(minPixelScale),
59
+ sectionHeight(minSectionHeight ?? targetUnitHeight ?? 0)
60
+ );
61
+ return (ctx) => {
62
+ const stated = statedDensity(opts, ctx.axisType);
63
+ if (typeof stated === "function") return stated(ctx);
64
+ if (stated != null) return stated;
65
+ return derived(ctx);
66
+ };
67
+ }
68
+ function statedDensity(opts, axisType) {
69
+ const { pixelScale, pixelsPerMyr, pixelsPerMeter } = opts;
70
+ if (axisType === columnComponents.ColumnAxisType.AGE) {
71
+ return pixelsPerMyr ?? pixelScale;
72
+ }
73
+ return pixelsPerMeter ?? pixelScale;
74
+ }
75
+ exports.DEFAULT_TARGET_UNIT_HEIGHT = DEFAULT_TARGET_UNIT_HEIGHT;
76
+ exports.atLeast = atLeast;
77
+ exports.fixedDensity = fixedDensity;
78
+ exports.resolveDensity = resolveDensity;
79
+ exports.sectionDensity = sectionDensity;
80
+ exports.sectionHeight = sectionHeight;
81
+ exports.statedDensity = statedDensity;
82
+ exports.unitHeight = unitHeight;
83
+ //# sourceMappingURL=density.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"density.cjs","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":["ColumnAxisType"],"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,aAAaA,iBAAAA,eAAe,KAAK;AACnC,WAAO,gBAAgB;AAAA,EACzB;AACA,SAAO,kBAAkB;AAC3B;;;;;;;;;"}