@macrostrat/column-views 3.6.1 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +144 -0
- package/dist/animated-age-window.cjs +51 -0
- package/dist/animated-age-window.cjs.map +1 -0
- package/dist/animated-age-window.d.ts +51 -0
- package/dist/animated-age-window.js +51 -0
- package/dist/animated-age-window.js.map +1 -0
- package/dist/column-views.css +118 -89
- package/dist/column.cjs +12 -3
- package/dist/column.cjs.map +1 -1
- package/dist/column.d.ts +11 -0
- package/dist/column.js +12 -3
- package/dist/column.js.map +1 -1
- package/dist/column.module.sass.cjs +15 -15
- package/dist/column.module.sass.js +15 -15
- package/dist/correlation-chart/main.cjs +156 -42
- package/dist/correlation-chart/main.cjs.map +1 -1
- package/dist/correlation-chart/main.d.ts +40 -2
- package/dist/correlation-chart/main.js +138 -23
- package/dist/correlation-chart/main.js.map +1 -1
- package/dist/correlation-chart/main.module.sass.cjs +23 -20
- package/dist/correlation-chart/main.module.sass.cjs.map +1 -1
- package/dist/correlation-chart/main.module.sass.js +23 -20
- package/dist/correlation-chart/main.module.sass.js.map +1 -1
- package/dist/correlation-chart/prepare-data.cjs +0 -1
- package/dist/correlation-chart/prepare-data.cjs.map +1 -1
- package/dist/correlation-chart/prepare-data.js +0 -1
- package/dist/correlation-chart/prepare-data.js.map +1 -1
- package/dist/data-provider/store.cjs +17 -4
- package/dist/data-provider/store.cjs.map +1 -1
- package/dist/data-provider/store.d.ts +6 -1
- package/dist/data-provider/store.js +17 -4
- package/dist/data-provider/store.js.map +1 -1
- package/dist/data-provider/unit-selection.cjs +4 -1
- package/dist/data-provider/unit-selection.cjs.map +1 -1
- package/dist/data-provider/unit-selection.d.ts +1 -1
- package/dist/data-provider/unit-selection.js +4 -1
- package/dist/data-provider/unit-selection.js.map +1 -1
- package/dist/index.cjs +16 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/maps/column-correlation/core.cjs +127 -6
- package/dist/maps/column-correlation/core.cjs.map +1 -1
- package/dist/maps/column-correlation/core.js +128 -7
- package/dist/maps/column-correlation/core.js.map +1 -1
- package/dist/maps/column-correlation/state.cjs +63 -2
- package/dist/maps/column-correlation/state.cjs.map +1 -1
- package/dist/maps/column-correlation/state.d.ts +31 -1
- package/dist/maps/column-correlation/state.js +63 -2
- package/dist/maps/column-correlation/state.js.map +1 -1
- package/dist/maps/layers/columns.cjs +5 -1
- package/dist/maps/layers/columns.cjs.map +1 -1
- package/dist/maps/layers/columns.js +5 -1
- package/dist/maps/layers/columns.js.map +1 -1
- package/dist/prepare-units/composite-scale.cjs +94 -8
- package/dist/prepare-units/composite-scale.cjs.map +1 -1
- package/dist/prepare-units/composite-scale.d.ts +39 -0
- package/dist/prepare-units/composite-scale.js +96 -10
- package/dist/prepare-units/composite-scale.js.map +1 -1
- package/dist/prepare-units/helpers.cjs +0 -1
- package/dist/prepare-units/helpers.cjs.map +1 -1
- package/dist/prepare-units/helpers.js +0 -1
- package/dist/prepare-units/helpers.js.map +1 -1
- package/dist/prepare-units/index.cjs +33 -12
- package/dist/prepare-units/index.cjs.map +1 -1
- package/dist/prepare-units/index.js +34 -13
- package/dist/prepare-units/index.js.map +1 -1
- package/dist/prepare-units/types.cjs.map +1 -1
- package/dist/prepare-units/types.d.ts +13 -0
- package/dist/prepare-units/types.js.map +1 -1
- package/dist/section.cjs +10 -2
- package/dist/section.cjs.map +1 -1
- package/dist/section.d.ts +5 -0
- package/dist/section.js +10 -2
- package/dist/section.js.map +1 -1
- package/dist/unit-details/age-range.cjs +2 -1
- package/dist/unit-details/age-range.cjs.map +1 -1
- package/dist/unit-details/age-range.js +2 -1
- package/dist/unit-details/age-range.js.map +1 -1
- package/dist/unit-details/index.d.ts +1 -1
- package/dist/unit-details/panel.cjs +2 -3
- package/dist/unit-details/panel.cjs.map +1 -1
- package/dist/unit-details/panel.js +1 -2
- package/dist/unit-details/panel.js.map +1 -1
- package/dist/unit-details/panel.module.sass.cjs +18 -22
- package/dist/unit-details/panel.module.sass.cjs.map +1 -1
- package/dist/unit-details/panel.module.sass.js +18 -22
- package/dist/unit-details/panel.module.sass.js.map +1 -1
- package/dist/unit-details/strat-names.cjs +1 -2
- package/dist/unit-details/strat-names.cjs.map +1 -1
- package/dist/unit-details/strat-names.js +1 -2
- package/dist/unit-details/strat-names.js.map +1 -1
- package/dist/units/boxes.cjs +20 -36
- package/dist/units/boxes.cjs.map +1 -1
- package/dist/units/boxes.js +22 -38
- package/dist/units/boxes.js.map +1 -1
- package/dist/units/composite.cjs +12 -12
- package/dist/units/composite.cjs.map +1 -1
- package/dist/units/composite.js +12 -12
- package/dist/units/composite.js.map +1 -1
- package/package.json +9 -9
- package/src/animated-age-window.ts +120 -0
- package/src/column.module.sass +6 -0
- package/src/column.ts +24 -0
- package/src/correlation-chart/main.module.sass +46 -2
- package/src/correlation-chart/main.ts +210 -17
- package/src/correlation-chart/prepare-data.ts +0 -1
- package/src/data-provider/store.ts +21 -2
- package/src/data-provider/unit-selection.ts +13 -2
- package/src/index.ts +1 -0
- package/src/maps/column-correlation/column-correlation-map.stories.ts +12 -0
- package/src/maps/column-correlation/core.ts +155 -9
- package/src/maps/column-correlation/state.ts +110 -2
- package/src/maps/layers/columns.ts +7 -1
- package/src/prepare-units/composite-scale.ts +210 -10
- package/src/prepare-units/helpers.ts +0 -2
- package/src/prepare-units/index.ts +68 -14
- package/src/prepare-units/types.ts +13 -0
- package/src/section.ts +15 -1
- package/src/unit-details/age-range.ts +3 -1
- package/src/unit-details/index.ts +7 -1
- package/src/unit-details/panel.module.sass +0 -8
- package/src/unit-details/panel.ts +6 -6
- package/src/unit-details/strat-names.ts +1 -1
- package/src/units/boxes.ts +33 -53
- package/src/units/composite.ts +21 -12
- package/dist/correlation-chart/stories/utils.d.ts +0 -6
- package/dist/unit-details/identifiers.cjs +0 -56
- package/dist/unit-details/identifiers.cjs.map +0 -1
- package/dist/unit-details/identifiers.d.ts +0 -31
- package/dist/unit-details/identifiers.js +0 -54
- package/dist/unit-details/identifiers.js.map +0 -1
- package/src/correlation-chart/stories/correlation-chart.stories.ts +0 -228
- package/src/correlation-chart/stories/namibia-correlation.stories.ts +0 -151
- package/src/correlation-chart/stories/stories.module.sass +0 -18
- package/src/correlation-chart/stories/utils.ts +0 -24
- package/src/unit-details/identifiers.ts +0 -67
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ColumnAxisType } from "@macrostrat/column-components";
|
|
2
|
-
import { ensureArray, getUnitHeightRange } from "./utils";
|
|
2
|
+
import { agesOverlap, ensureArray, getUnitHeightRange } from "./utils";
|
|
3
3
|
import { ScaleContinuousNumeric, scaleLinear } from "d3-scale";
|
|
4
4
|
import { UnitLong } from "@macrostrat/api-types";
|
|
5
5
|
import { buildHybridScale } from "./dynamic-scales";
|
|
@@ -28,10 +28,15 @@ export function buildCompositeScaleInfo(
|
|
|
28
28
|
let lastSectionTopHeight = 0;
|
|
29
29
|
|
|
30
30
|
const packages2: PackageScaleLayoutData[] = [];
|
|
31
|
-
for (
|
|
31
|
+
for (let i = 0; i < inputScales.length; i++) {
|
|
32
|
+
const group = inputScales[i];
|
|
32
33
|
const { domain, scale } = group;
|
|
33
34
|
const [b_age, t_age] = domain;
|
|
34
|
-
|
|
35
|
+
// Positionally stable key: packages are always emitted in age order, so the
|
|
36
|
+
// index is a stable identity. An age-based key (`package-${b_age}-${t_age}`)
|
|
37
|
+
// changes every frame while the age window animates, forcing every package,
|
|
38
|
+
// column, and timescale slice in the correlation chart to remount each frame.
|
|
39
|
+
const key = `package-${i}`;
|
|
35
40
|
|
|
36
41
|
packages2.push({
|
|
37
42
|
...createPackageScale(group, totalHeight),
|
|
@@ -132,6 +137,7 @@ function buildSectionScale<T extends UnitLong>(
|
|
|
132
137
|
minPixelScale = 0.2,
|
|
133
138
|
axisType,
|
|
134
139
|
minSectionHeight,
|
|
140
|
+
visibleWindow,
|
|
135
141
|
scale,
|
|
136
142
|
hybridScale,
|
|
137
143
|
} = opts;
|
|
@@ -159,7 +165,7 @@ function buildSectionScale<T extends UnitLong>(
|
|
|
159
165
|
|
|
160
166
|
if (scale == null) {
|
|
161
167
|
if (_pixelScale == null) {
|
|
162
|
-
const avgAgeRange = findAverageUnitHeight(data, axisType);
|
|
168
|
+
const avgAgeRange = findAverageUnitHeight(data, axisType, visibleWindow);
|
|
163
169
|
// Get pixel height necessary to render average unit at target height
|
|
164
170
|
_pixelScale = Math.max(targetUnitHeight / avgAgeRange, minPixelScale);
|
|
165
171
|
|
|
@@ -171,7 +177,11 @@ function buildSectionScale<T extends UnitLong>(
|
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
let height = dAge * _pixelScale;
|
|
174
|
-
// If height is less than minSectionHeight, set it to minSectionHeight
|
|
180
|
+
// If height is less than minSectionHeight, set it to minSectionHeight.
|
|
181
|
+
// Sections reach here at their *full* extent (the rendered window is applied
|
|
182
|
+
// afterwards, by `trimSectionsToWindow`), so this floor only ever inflates a
|
|
183
|
+
// genuinely small section — which is what it's for — and never a sliver that
|
|
184
|
+
// the window happens to cut.
|
|
175
185
|
const _minSectionHeight = minSectionHeight ?? targetUnitHeight ?? 0;
|
|
176
186
|
pixelHeight = Math.max(height, _minSectionHeight);
|
|
177
187
|
_pixelScale = pixelHeight / dAge;
|
|
@@ -186,6 +196,168 @@ function buildSectionScale<T extends UnitLong>(
|
|
|
186
196
|
);
|
|
187
197
|
}
|
|
188
198
|
|
|
199
|
+
/**
|
|
200
|
+
* The density each section will actually render at once the window is applied.
|
|
201
|
+
*
|
|
202
|
+
* A section the window cuts short is still being *looked at*, so
|
|
203
|
+
* `minSectionHeight` applies to what remains — it's honored by expanding the
|
|
204
|
+
* section's scale, not by showing more of it. Resolving this **before** padding
|
|
205
|
+
* matters: padding is a pixel margin measured at the density it will render at,
|
|
206
|
+
* and a section stretched to the floor afterwards would multiply the margin
|
|
207
|
+
* along with everything else (a 20 px margin in a band stretched 4× renders at
|
|
208
|
+
* 80 px, and asymmetrically so whenever one side is truncated).
|
|
209
|
+
*
|
|
210
|
+
* Sections the window leaves whole keep their laid-out density — they already
|
|
211
|
+
* met the floor when they were sized.
|
|
212
|
+
*/
|
|
213
|
+
export function resolveWindowScales<T extends UnitLong>(
|
|
214
|
+
sections: SectionInfoWithScale<T>[],
|
|
215
|
+
focalWindow: [number, number],
|
|
216
|
+
floor: number,
|
|
217
|
+
): Map<SectionInfoWithScale<T>, number> {
|
|
218
|
+
const [b_focal, t_focal] = focalWindow;
|
|
219
|
+
const scales = new Map<SectionInfoWithScale<T>, number>();
|
|
220
|
+
|
|
221
|
+
for (const section of sections) {
|
|
222
|
+
const t_age = Math.max(section.t_age, t_focal);
|
|
223
|
+
const b_age = Math.min(section.b_age, b_focal);
|
|
224
|
+
// Sections outside the focal window are visible only as padding; they keep
|
|
225
|
+
// their own density and render exactly the pixels asked for.
|
|
226
|
+
if (b_age <= t_age) continue;
|
|
227
|
+
|
|
228
|
+
const natural = section.scaleInfo.pixelScale;
|
|
229
|
+
if (!(natural > 0)) continue;
|
|
230
|
+
|
|
231
|
+
const isClipped = b_age < section.b_age || t_age > section.t_age;
|
|
232
|
+
const needed = isClipped && floor > 0 ? floor / (b_age - t_age) : 0;
|
|
233
|
+
scales.set(section, Math.max(natural, needed));
|
|
234
|
+
}
|
|
235
|
+
return scales;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Expand an age window outward by `padding` **pixels** of column, spending the
|
|
240
|
+
* budget against sections that are already laid out at full extent, at the
|
|
241
|
+
* densities they will finally render at (`scaleFor`).
|
|
242
|
+
*
|
|
243
|
+
* Because the layout is done, this is exact rather than predicted: each section
|
|
244
|
+
* contributes its own real pixel height. Padding collapses when the column runs
|
|
245
|
+
* out on that side.
|
|
246
|
+
*
|
|
247
|
+
* Unconformities between sections cost nothing. They render at a fixed height
|
|
248
|
+
* that's typically larger than the padding itself (30–60 px), so charging them
|
|
249
|
+
* would mean padding could never reach across a gap — which is the case it
|
|
250
|
+
* exists for: revealing the neighboring section keeps its timescale intervals
|
|
251
|
+
* navigable. The padding is a budget of neighboring *content*.
|
|
252
|
+
*/
|
|
253
|
+
export function padWindowByPixels<T extends UnitLong>(
|
|
254
|
+
sections: SectionInfoWithScale<T>[],
|
|
255
|
+
window: [number, number],
|
|
256
|
+
padding: number,
|
|
257
|
+
scaleFor: (section: SectionInfoWithScale<T>) => number,
|
|
258
|
+
): [number, number] {
|
|
259
|
+
if (!(padding > 0) || sections.length === 0) return window;
|
|
260
|
+
const [b_age, t_age] = window;
|
|
261
|
+
return [
|
|
262
|
+
spendOutward(sections, b_age, 1, padding, scaleFor),
|
|
263
|
+
spendOutward(sections, t_age, -1, padding, scaleFor),
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Walk out from one edge of the window through laid-out sections, spending a
|
|
268
|
+
* pixel budget, and return the age it lands on. `direction` is +1 toward older
|
|
269
|
+
* ages, -1 toward younger. */
|
|
270
|
+
function spendOutward<T extends UnitLong>(
|
|
271
|
+
sections: SectionInfoWithScale<T>[],
|
|
272
|
+
edge: number,
|
|
273
|
+
direction: 1 | -1,
|
|
274
|
+
padding: number,
|
|
275
|
+
scaleFor: (section: SectionInfoWithScale<T>) => number,
|
|
276
|
+
): number {
|
|
277
|
+
// Sections ordered outward from the edge, keeping only what lies beyond it.
|
|
278
|
+
const beyond = sections
|
|
279
|
+
.filter(
|
|
280
|
+
(s) => direction * (s.b_age - edge) > 0 || direction * (s.t_age - edge) > 0,
|
|
281
|
+
)
|
|
282
|
+
.sort((a, b) => direction * (nearEdge(a, direction) - nearEdge(b, direction)));
|
|
283
|
+
|
|
284
|
+
let bound = edge;
|
|
285
|
+
let budget = padding;
|
|
286
|
+
|
|
287
|
+
for (const section of beyond) {
|
|
288
|
+
const pixelScale = scaleFor(section);
|
|
289
|
+
if (!(pixelScale > 0)) continue;
|
|
290
|
+
|
|
291
|
+
const from =
|
|
292
|
+
direction > 0
|
|
293
|
+
? Math.max(section.t_age, bound)
|
|
294
|
+
: Math.min(section.b_age, bound);
|
|
295
|
+
const available = direction > 0 ? section.b_age - from : from - section.t_age;
|
|
296
|
+
if (available <= 0) continue;
|
|
297
|
+
|
|
298
|
+
const cost = available * pixelScale;
|
|
299
|
+
if (budget <= cost) return from + direction * (budget / pixelScale);
|
|
300
|
+
|
|
301
|
+
bound = direction > 0 ? section.b_age : section.t_age;
|
|
302
|
+
budget -= cost;
|
|
303
|
+
}
|
|
304
|
+
return bound;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** The age at which a section starts, measured from the window edge outward. */
|
|
308
|
+
function nearEdge<T extends UnitLong>(
|
|
309
|
+
section: SectionInfoWithScale<T>,
|
|
310
|
+
direction: 1 | -1,
|
|
311
|
+
): number {
|
|
312
|
+
return direction > 0 ? section.t_age : section.b_age;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Clip laid-out sections to the rendered window — the last step of layout, so
|
|
317
|
+
* that every section has already been sized (and floored) at its full extent.
|
|
318
|
+
* Each section renders at the density `scaleFor` resolved for it, which is what
|
|
319
|
+
* keeps a padding margin the same number of pixels as the band it borders is
|
|
320
|
+
* stretched to meet `minSectionHeight`.
|
|
321
|
+
*/
|
|
322
|
+
export function trimSectionsToWindow<T extends UnitLong>(
|
|
323
|
+
sections: SectionInfoWithScale<T>[],
|
|
324
|
+
window: [number, number],
|
|
325
|
+
scaleFor: (section: SectionInfoWithScale<T>) => number,
|
|
326
|
+
): SectionInfoWithScale<T>[] {
|
|
327
|
+
const [b_win, t_win] = window;
|
|
328
|
+
const out: SectionInfoWithScale<T>[] = [];
|
|
329
|
+
|
|
330
|
+
for (const section of sections) {
|
|
331
|
+
const t_age = Math.max(section.t_age, t_win);
|
|
332
|
+
const b_age = Math.min(section.b_age, b_win);
|
|
333
|
+
if (b_age <= t_age) continue;
|
|
334
|
+
|
|
335
|
+
const pixelScale = scaleFor(section);
|
|
336
|
+
if (
|
|
337
|
+
b_age === section.b_age &&
|
|
338
|
+
t_age === section.t_age &&
|
|
339
|
+
pixelScale === section.scaleInfo.pixelScale
|
|
340
|
+
) {
|
|
341
|
+
out.push(section);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
out.push({
|
|
346
|
+
...section,
|
|
347
|
+
t_age,
|
|
348
|
+
b_age,
|
|
349
|
+
units: section.units.filter((u) => agesOverlap(u, { t_age, b_age })),
|
|
350
|
+
scaleInfo: createPackageScale({
|
|
351
|
+
domain: [b_age, t_age],
|
|
352
|
+
pixelScale,
|
|
353
|
+
pixelHeight: (b_age - t_age) * pixelScale,
|
|
354
|
+
scale: null,
|
|
355
|
+
}),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
|
|
189
361
|
export function createPackageScale(
|
|
190
362
|
def: PackageScaleInfo,
|
|
191
363
|
offset: number = 0,
|
|
@@ -260,12 +432,40 @@ function findSectionHeightRange(
|
|
|
260
432
|
function findAverageUnitHeight(
|
|
261
433
|
data: UnitLong[],
|
|
262
434
|
axisType: ColumnAxisType,
|
|
435
|
+
visibleWindow?: [number, number] | null,
|
|
263
436
|
): number {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
437
|
+
/** The typical duration of a unit, which `targetUnitHeight` sizes.
|
|
438
|
+
*
|
|
439
|
+
* Measured over what the render window actually *shows* — units outside it
|
|
440
|
+
* are ignored and a unit it cuts through counts only for its visible part.
|
|
441
|
+
* That's what makes `targetUnitHeight` mean the same thing at every zoom
|
|
442
|
+
* depth: the units on screen are drawn at the target height whether they're
|
|
443
|
+
* whole or slivers of something much longer.
|
|
444
|
+
*
|
|
445
|
+
* A section the window doesn't reach has nothing visible to measure, so it
|
|
446
|
+
* falls back to its own units at full duration — it's drawn at its own scale.
|
|
447
|
+
* (Deriving it from the padded window instead would be circular: padding is
|
|
448
|
+
* measured in pixels, which is what we're trying to establish.)
|
|
449
|
+
*/
|
|
450
|
+
const durations = (clip: boolean) =>
|
|
451
|
+
data
|
|
452
|
+
.map((d) => {
|
|
453
|
+
const [b_pos, t_pos] = getUnitHeightRange(d, axisType);
|
|
454
|
+
if (!clip) return Math.abs(b_pos - t_pos);
|
|
455
|
+
const [b_win, t_win] = visibleWindow;
|
|
456
|
+
return Math.max(
|
|
457
|
+
0,
|
|
458
|
+
Math.min(b_pos, b_win) - Math.max(t_pos, t_win),
|
|
459
|
+
);
|
|
460
|
+
})
|
|
461
|
+
.filter((d) => d > 0);
|
|
462
|
+
|
|
463
|
+
const useWindow = visibleWindow != null && axisType === ColumnAxisType.AGE;
|
|
464
|
+
let heights = useWindow ? durations(true) : [];
|
|
465
|
+
if (heights.length === 0) heights = durations(false);
|
|
466
|
+
if (heights.length === 0) return 1;
|
|
467
|
+
|
|
468
|
+
return heights.reduce((a, b) => a + b, 0) / heights.length;
|
|
269
469
|
}
|
|
270
470
|
|
|
271
471
|
export interface CompositeColumnScale {
|
|
@@ -69,8 +69,6 @@ function groupUnitsByMutualOverlap<T extends BaseUnit>(
|
|
|
69
69
|
|
|
70
70
|
const sections = groupUnitsIntoSectionsByOverlap(units, axisType, tolerance);
|
|
71
71
|
|
|
72
|
-
console.log("Grouped sections", sections);
|
|
73
|
-
|
|
74
72
|
return sections.map((d) => {
|
|
75
73
|
return new UnitGroup(d.units, axisType);
|
|
76
74
|
});
|
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
collapseUnconformitiesByPixelHeight,
|
|
16
16
|
computeSectionHeights,
|
|
17
17
|
finalizeSectionHeights,
|
|
18
|
+
padWindowByPixels,
|
|
19
|
+
resolveWindowScales,
|
|
20
|
+
trimSectionsToWindow,
|
|
18
21
|
type CompositeColumnScale,
|
|
19
22
|
} from "./composite-scale";
|
|
20
23
|
import {
|
|
@@ -58,6 +61,7 @@ export function prepareColumnUnits(
|
|
|
58
61
|
collapseSmallUnconformities = false,
|
|
59
62
|
hybridScale,
|
|
60
63
|
scale,
|
|
64
|
+
windowPadding = 0,
|
|
61
65
|
} = options;
|
|
62
66
|
|
|
63
67
|
let _totalHeight: number | null = null;
|
|
@@ -76,19 +80,29 @@ export function prepareColumnUnits(
|
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
/** Age columns lay every section out at its full extent and clip to the
|
|
84
|
+
* window at the end (see below), so that a section abutting the window is
|
|
85
|
+
* sized by its own content rather than by the sliver that survives the clip.
|
|
86
|
+
* Hybrid and externally-supplied scales build their own mapping and can't be
|
|
87
|
+
* re-derived that way, so they keep the original clip-then-lay-out path. */
|
|
88
|
+
const clipBeforeLayout =
|
|
89
|
+
axisType != ColumnAxisType.AGE || hybridScale != null || scale != null;
|
|
90
|
+
|
|
79
91
|
// Start by ensuring that ages and positions are numbers
|
|
80
92
|
// also set up some values for eODP-style columns
|
|
81
93
|
let units1 = units.map(preprocessSectionUnit);
|
|
82
94
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
if (clipBeforeLayout) {
|
|
96
|
+
/** Prototype filtering to age range */
|
|
97
|
+
units1 = units1.filter((d) => {
|
|
98
|
+
// Filter units by t_age and b_age, inclusive
|
|
99
|
+
if (axisType == ColumnAxisType.AGE) {
|
|
100
|
+
return agesOverlap(d, { t_age, b_age });
|
|
101
|
+
} else {
|
|
102
|
+
return unitsOverlap(d, { t_pos, b_pos } as any, axisType);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
92
106
|
|
|
93
107
|
let mergeMode = mergeSections;
|
|
94
108
|
// if (axisType != ColumnAxisType.AGE) {
|
|
@@ -123,7 +137,7 @@ export function prepareColumnUnits(
|
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
// Limit sections to the range specified by t_age/b_age or t_pos/b_pos global options
|
|
126
|
-
for (let section of sections0) {
|
|
140
|
+
for (let section of clipBeforeLayout ? sections0 : []) {
|
|
127
141
|
if (axisType == ColumnAxisType.AGE) {
|
|
128
142
|
section.t_age = Math.max(section.t_age, t_age ?? -Infinity);
|
|
129
143
|
section.b_age = Math.min(section.b_age, b_age ?? Infinity);
|
|
@@ -152,11 +166,21 @@ export function prepareColumnUnits(
|
|
|
152
166
|
|
|
153
167
|
// SCALES
|
|
154
168
|
|
|
169
|
+
/** The window we've been asked to render. Unit density is derived from what
|
|
170
|
+
* it shows (see `findAverageUnitHeight`), so it has to be known before any
|
|
171
|
+
* section is laid out — but nothing is *clipped* to it until the very end.
|
|
172
|
+
*/
|
|
173
|
+
const focalWindow: [number, number] | null = clipBeforeLayout
|
|
174
|
+
? null
|
|
175
|
+
: [b_age ?? Infinity, t_age ?? -Infinity];
|
|
176
|
+
|
|
177
|
+
const layoutOptions = { ...options, visibleWindow: focalWindow };
|
|
178
|
+
|
|
155
179
|
/* Compute pixel scales etc. for sections
|
|
156
180
|
* We need to do this now to determine which unconformities
|
|
157
181
|
* are small enough to collapse.
|
|
158
182
|
*/
|
|
159
|
-
let sectionsWithScales = computeSectionHeights(sections,
|
|
183
|
+
let sectionsWithScales = computeSectionHeights(sections, layoutOptions);
|
|
160
184
|
|
|
161
185
|
if (collapseSmallUnconformities && hybridScale == null) {
|
|
162
186
|
// Collapse small unconformities in pixel height space
|
|
@@ -169,7 +193,39 @@ export function prepareColumnUnits(
|
|
|
169
193
|
sectionsWithScales = collapseUnconformitiesByPixelHeight(
|
|
170
194
|
sectionsWithScales,
|
|
171
195
|
threshold,
|
|
172
|
-
|
|
196
|
+
layoutOptions,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Now that every section is laid out at full extent — with its own density
|
|
201
|
+
* and `minSectionHeight` already settled — apply the rendered window. Padding
|
|
202
|
+
* is spent here, against real pixel heights, so `windowPadding` px of an
|
|
203
|
+
* abutting section is exactly that many pixels.
|
|
204
|
+
*/
|
|
205
|
+
if (focalWindow != null && (t_age != null || b_age != null)) {
|
|
206
|
+
// Resolve final densities first (a section the window cuts short is
|
|
207
|
+
// stretched to `minSectionHeight`), then spend the padding budget against
|
|
208
|
+
// them, so a margin is the pixels asked for rather than those pixels times
|
|
209
|
+
// whatever stretch its neighbor happened to need.
|
|
210
|
+
const floor = options.minSectionHeight ?? options.targetUnitHeight ?? 0;
|
|
211
|
+
const scales = resolveWindowScales(sectionsWithScales, focalWindow, floor);
|
|
212
|
+
const scaleFor = (section) =>
|
|
213
|
+
scales.get(section) ?? section.scaleInfo.pixelScale;
|
|
214
|
+
|
|
215
|
+
const window =
|
|
216
|
+
windowPadding > 0
|
|
217
|
+
? padWindowByPixels(
|
|
218
|
+
sectionsWithScales,
|
|
219
|
+
focalWindow,
|
|
220
|
+
windowPadding,
|
|
221
|
+
scaleFor,
|
|
222
|
+
)
|
|
223
|
+
: focalWindow;
|
|
224
|
+
|
|
225
|
+
sectionsWithScales = trimSectionsToWindow(
|
|
226
|
+
sectionsWithScales,
|
|
227
|
+
window,
|
|
228
|
+
scaleFor,
|
|
173
229
|
);
|
|
174
230
|
}
|
|
175
231
|
|
|
@@ -185,8 +241,6 @@ export function prepareColumnUnits(
|
|
|
185
241
|
* We do this after merging sections so that we can
|
|
186
242
|
* handle cases where there are overlapping units across sections
|
|
187
243
|
* */
|
|
188
|
-
console.log(sections2, "sections");
|
|
189
|
-
|
|
190
244
|
const sectionsOut = sections2.map((section) => {
|
|
191
245
|
return {
|
|
192
246
|
...section,
|
|
@@ -14,6 +14,19 @@ export interface ColumnHeightScaleOptions {
|
|
|
14
14
|
* needed because small sections (<1-2 units) don't necessarily have space to comfortably
|
|
15
15
|
* render two axis labels */
|
|
16
16
|
minPixelScale?: number;
|
|
17
|
+
/** The requested render window, `[b_age, t_age]`, if there is one. Set
|
|
18
|
+
* internally by `prepareColumnUnits`: unit density is derived from the units
|
|
19
|
+
* this window actually shows, so `targetUnitHeight` describes the units you
|
|
20
|
+
* can see at any zoom depth rather than the section's overall average. */
|
|
21
|
+
visibleWindow?: [number, number];
|
|
22
|
+
/** Padding around the `t_age`/`b_age` window, in **pixels** of neighboring
|
|
23
|
+
* column: how much of the abutting sections to reveal past the window.
|
|
24
|
+
*
|
|
25
|
+
* Resolved after every section is laid out at full extent, so it's exact —
|
|
26
|
+
* each section's own density and each crossed unconformity's fixed height are
|
|
27
|
+
* spent from the pixel budget. Padding collapses where the column runs out.
|
|
28
|
+
* Ignored for hybrid or externally-supplied scales. */
|
|
29
|
+
windowPadding?: number;
|
|
17
30
|
// Axis scale type
|
|
18
31
|
axisType?: ColumnAxisType;
|
|
19
32
|
// Unconformity height in pixels
|
package/src/section.ts
CHANGED
|
@@ -7,7 +7,9 @@ import { ReactNode, FunctionComponent, useMemo } from "react";
|
|
|
7
7
|
import {
|
|
8
8
|
Timescale,
|
|
9
9
|
TimescaleOrientation,
|
|
10
|
+
TimescaleClickHandler,
|
|
10
11
|
useMacrostratIntervals,
|
|
12
|
+
type IntervalStyleBuilder,
|
|
11
13
|
} from "@macrostrat/timescale";
|
|
12
14
|
import { ColumnAxisType, SVG } from "@macrostrat/column-components";
|
|
13
15
|
import hyper from "@macrostrat/hyper";
|
|
@@ -202,6 +204,9 @@ function SectionUnits(props: SectionProps) {
|
|
|
202
204
|
interface CompositeTimescaleProps {
|
|
203
205
|
levels?: [number, number] | number;
|
|
204
206
|
unconformityLabels?: boolean;
|
|
207
|
+
onClickInterval?: TimescaleClickHandler;
|
|
208
|
+
/** Per-interval style (e.g. to highlight the currently selected interval). */
|
|
209
|
+
intervalStyle?: IntervalStyleBuilder;
|
|
205
210
|
}
|
|
206
211
|
|
|
207
212
|
export function CompositeTimescale(props: CompositeTimescaleProps) {
|
|
@@ -218,10 +223,17 @@ export function CompositeTimescale(props: CompositeTimescaleProps) {
|
|
|
218
223
|
|
|
219
224
|
type CompositeTimescaleCoreProps = CompositeTimescaleProps & {
|
|
220
225
|
packages: PackageScaleLayoutData[];
|
|
226
|
+
onClickInterval?: TimescaleClickHandler;
|
|
221
227
|
};
|
|
222
228
|
|
|
223
229
|
export function CompositeTimescaleCore(props: CompositeTimescaleCoreProps) {
|
|
224
|
-
const {
|
|
230
|
+
const {
|
|
231
|
+
levels = 3,
|
|
232
|
+
packages,
|
|
233
|
+
unconformityLabels = false,
|
|
234
|
+
onClickInterval,
|
|
235
|
+
intervalStyle,
|
|
236
|
+
} = props;
|
|
225
237
|
|
|
226
238
|
// Use intervals from Macrostrat API
|
|
227
239
|
const baseURL = useMacrostratBaseURL();
|
|
@@ -254,6 +266,8 @@ export function CompositeTimescaleCore(props: CompositeTimescaleCoreProps) {
|
|
|
254
266
|
showAgeAxis: false,
|
|
255
267
|
scale,
|
|
256
268
|
intervals,
|
|
269
|
+
onClick: onClickInterval,
|
|
270
|
+
intervalStyle,
|
|
257
271
|
}),
|
|
258
272
|
],
|
|
259
273
|
);
|
|
@@ -132,8 +132,10 @@ export function IntervalProportions({
|
|
|
132
132
|
const i0 = unit.b_int_id;
|
|
133
133
|
const i1 = unit.t_int_id;
|
|
134
134
|
|
|
135
|
+
const ints = new Set([i0, i1]);
|
|
136
|
+
|
|
135
137
|
/** Get interval information */
|
|
136
|
-
const intervalMap = useMacrostratDefs("intervals");
|
|
138
|
+
const intervalMap = useMacrostratDefs("intervals", Array.from(ints), null);
|
|
137
139
|
const [int0, int1] = useMemo(() => {
|
|
138
140
|
const int0 = intervalMap?.get(i0) ?? {};
|
|
139
141
|
if (i0 === i1) {
|
|
@@ -3,4 +3,10 @@ export * from "./popover";
|
|
|
3
3
|
export * from "./modal-panel";
|
|
4
4
|
export * from "./age-range";
|
|
5
5
|
export * from "./strat-names";
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
// Backwards compatibility
|
|
8
|
+
export {
|
|
9
|
+
ClickableText,
|
|
10
|
+
Identifier,
|
|
11
|
+
UnitIdentifier,
|
|
12
|
+
} from "@macrostrat/data-components";
|
|
@@ -61,14 +61,6 @@
|
|
|
61
61
|
color: var(--secondary-color)
|
|
62
62
|
font-style: italic
|
|
63
63
|
|
|
64
|
-
.strat-name-id, .identifier
|
|
65
|
-
font-family: var(--monospace-font)
|
|
66
|
-
color: var(--secondary-color)
|
|
67
|
-
|
|
68
|
-
&::before
|
|
69
|
-
content: '#'
|
|
70
|
-
color: var(--tertiary-color)
|
|
71
|
-
|
|
72
64
|
.color-swatch
|
|
73
65
|
width: 1em
|
|
74
66
|
height: 1em
|
|
@@ -34,7 +34,11 @@ import { AgeField, Duration, IntervalProportions } from "./age-range";
|
|
|
34
34
|
import { formatRange, formatSignificance } from "./utils.ts";
|
|
35
35
|
import h from "./panel.module.sass";
|
|
36
36
|
import { StratNameField } from "./strat-names.ts";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
Identifier,
|
|
39
|
+
UnitIdentifier,
|
|
40
|
+
UnitInfo,
|
|
41
|
+
} from "@macrostrat/data-components";
|
|
38
42
|
import { LithologyIdentifier } from "@macrostrat/api-types/src/lithologies";
|
|
39
43
|
|
|
40
44
|
export interface UnitDetailsPanelProps {
|
|
@@ -196,11 +200,7 @@ export enum UnitDetailsFeature {
|
|
|
196
200
|
export type MacrostratItemClickHandler = (
|
|
197
201
|
event: MouseEvent,
|
|
198
202
|
item:
|
|
199
|
-
|
|
|
200
|
-
| Environment
|
|
201
|
-
| UnitLong
|
|
202
|
-
| Interval
|
|
203
|
-
| { strat_name_id: number },
|
|
203
|
+
Lithology | Environment | UnitLong | Interval | { strat_name_id: number },
|
|
204
204
|
) => void;
|
|
205
205
|
|
|
206
206
|
function UnitDetailsContent({
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
isClickable,
|
|
4
4
|
ItemInteractionProps,
|
|
5
5
|
useInteractionProps,
|
|
6
|
+
Identifier,
|
|
6
7
|
} from "@macrostrat/data-components";
|
|
7
8
|
import h from "./panel.module.sass";
|
|
8
9
|
import classNames from "classnames";
|
|
9
10
|
import { useStratNames } from "@macrostrat/data-provider";
|
|
10
|
-
import { Identifier } from "./identifiers.ts";
|
|
11
11
|
|
|
12
12
|
function useStratNameData(strat_name_id: number) {
|
|
13
13
|
const stratNames = useStratNames([strat_name_id]);
|