@macrostrat/column-views 3.8.0 → 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.
Files changed (68) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/animated-age-window.cjs +51 -0
  3. package/dist/animated-age-window.cjs.map +1 -0
  4. package/dist/animated-age-window.d.ts +51 -0
  5. package/dist/animated-age-window.js +51 -0
  6. package/dist/animated-age-window.js.map +1 -0
  7. package/dist/column.cjs +11 -1
  8. package/dist/column.cjs.map +1 -1
  9. package/dist/column.d.ts +11 -0
  10. package/dist/column.js +11 -1
  11. package/dist/column.js.map +1 -1
  12. package/dist/correlation-chart/main.cjs +24 -12
  13. package/dist/correlation-chart/main.cjs.map +1 -1
  14. package/dist/correlation-chart/main.d.ts +8 -1
  15. package/dist/correlation-chart/main.js +24 -12
  16. package/dist/correlation-chart/main.js.map +1 -1
  17. package/dist/data-provider/store.cjs +17 -4
  18. package/dist/data-provider/store.cjs.map +1 -1
  19. package/dist/data-provider/store.d.ts +6 -1
  20. package/dist/data-provider/store.js +17 -4
  21. package/dist/data-provider/store.js.map +1 -1
  22. package/dist/index.cjs +2 -0
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/prepare-units/composite-scale.cjs +94 -8
  28. package/dist/prepare-units/composite-scale.cjs.map +1 -1
  29. package/dist/prepare-units/composite-scale.d.ts +39 -0
  30. package/dist/prepare-units/composite-scale.js +96 -10
  31. package/dist/prepare-units/composite-scale.js.map +1 -1
  32. package/dist/prepare-units/helpers.cjs +0 -1
  33. package/dist/prepare-units/helpers.cjs.map +1 -1
  34. package/dist/prepare-units/helpers.js +0 -1
  35. package/dist/prepare-units/helpers.js.map +1 -1
  36. package/dist/prepare-units/index.cjs +33 -12
  37. package/dist/prepare-units/index.cjs.map +1 -1
  38. package/dist/prepare-units/index.js +34 -13
  39. package/dist/prepare-units/index.js.map +1 -1
  40. package/dist/prepare-units/types.cjs.map +1 -1
  41. package/dist/prepare-units/types.d.ts +13 -0
  42. package/dist/prepare-units/types.js.map +1 -1
  43. package/dist/section.cjs +4 -2
  44. package/dist/section.cjs.map +1 -1
  45. package/dist/section.d.ts +4 -1
  46. package/dist/section.js +4 -2
  47. package/dist/section.js.map +1 -1
  48. package/dist/units/boxes.cjs +19 -36
  49. package/dist/units/boxes.cjs.map +1 -1
  50. package/dist/units/boxes.js +21 -38
  51. package/dist/units/boxes.js.map +1 -1
  52. package/dist/units/composite.cjs +12 -12
  53. package/dist/units/composite.cjs.map +1 -1
  54. package/dist/units/composite.js +12 -12
  55. package/dist/units/composite.js.map +1 -1
  56. package/package.json +6 -6
  57. package/src/animated-age-window.ts +120 -0
  58. package/src/column.ts +24 -0
  59. package/src/correlation-chart/main.ts +30 -8
  60. package/src/data-provider/store.ts +21 -2
  61. package/src/index.ts +1 -0
  62. package/src/prepare-units/composite-scale.ts +210 -10
  63. package/src/prepare-units/helpers.ts +0 -2
  64. package/src/prepare-units/index.ts +68 -14
  65. package/src/prepare-units/types.ts +13 -0
  66. package/src/section.ts +6 -0
  67. package/src/units/boxes.ts +30 -52
  68. package/src/units/composite.ts +21 -12
@@ -14,22 +14,22 @@ function LabelTrackerProvider(props) {
14
14
  const { children, units, findUnitID = findMacrostratUnitID } = props;
15
15
  const [unlabeledDivisions, setUnlabeledDivisions] = useState(null);
16
16
  const labelTrackerRef = useRef({});
17
+ const recompute = useCallback(() => {
18
+ const tracker = labelTrackerRef.current;
19
+ const allReported = units.every((d) => findUnitID(d) in tracker);
20
+ if (!allReported) return;
21
+ setUnlabeledDivisions(units.filter((d) => tracker[findUnitID(d)] === false));
22
+ }, [units, findUnitID]);
17
23
  const trackLabelVisibility = useCallback(
18
24
  (div, visible) => {
19
- const id = findUnitID(div);
20
- labelTrackerRef.current[id] = visible;
21
- if (Object.keys(labelTrackerRef.current).length == units.length) {
22
- setUnlabeledDivisions(
23
- // @ts-ignore
24
- units.filter((d) => {
25
- const id2 = findUnitID(d);
26
- return labelTrackerRef.current[id2] == false;
27
- })
28
- );
29
- }
25
+ labelTrackerRef.current[findUnitID(div)] = visible;
26
+ recompute();
30
27
  },
31
- [labelTrackerRef, units, findUnitID]
28
+ [recompute, findUnitID]
32
29
  );
30
+ useEffect(() => {
31
+ recompute();
32
+ }, [recompute]);
33
33
  const value = trackLabelVisibility;
34
34
  return h(
35
35
  LabelTrackerContext.Provider,
@@ -1 +1 @@
1
- {"version":3,"file":"composite.js","sources":["../../src/units/composite.ts"],"sourcesContent":["import { hyperStyled } from \"@macrostrat/hyper\";\nimport {\n ColumnContext,\n LithologyColumn,\n SVG,\n} from \"@macrostrat/column-components\";\nimport { defaultNameFunction, UnitNamesColumn } from \"./names\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { BaseUnit } from \"@macrostrat/api-types\";\nimport { LabeledUnit, UnitBoxes } from \"./boxes\";\nimport styles from \"./composite.module.sass\";\nimport {\n useCompositeScale,\n useMacrostratColumnData,\n useUnitSelectionDispatch,\n} from \"../data-provider\";\n\nconst h = hyperStyled(styles);\n\ninterface LabelTracker {\n [key: number]: boolean;\n}\n\nconst LabelTrackerContext = createContext(null);\nconst UnlabeledDivisionsContext = createContext(null);\n\nconst findMacrostratUnitID = (u) => u.unit_id;\n\nexport function LabelTrackerProvider(props) {\n /** Tracker for units to handle state for whether their labels fit inline or not.\n * Designed to work for single or composite columns.\n */\n const { children, units, findUnitID = findMacrostratUnitID } = props;\n const [unlabeledDivisions, setUnlabeledDivisions] = useState<\n BaseUnit[] | null\n >(null);\n const labelTrackerRef = useRef<LabelTracker>({});\n const trackLabelVisibility = useCallback(\n (div, visible) => {\n const id = findUnitID(div);\n labelTrackerRef.current[id] = visible;\n if (Object.keys(labelTrackerRef.current).length == units.length) {\n setUnlabeledDivisions(\n // @ts-ignore\n units.filter((d) => {\n const id = findUnitID(d);\n return labelTrackerRef.current[id] == false;\n }),\n );\n }\n },\n [labelTrackerRef, units, findUnitID],\n );\n\n const value = trackLabelVisibility;\n return h(\n LabelTrackerContext.Provider,\n { value },\n h(\n UnlabeledDivisionsContext.Provider,\n { value: unlabeledDivisions },\n children,\n ),\n );\n}\n\nfunction TrackedLabeledUnit({\n division,\n nameForDivision = defaultNameFunction,\n ...rest\n}) {\n const trackLabelVisibility = useContext(LabelTrackerContext);\n return h(LabeledUnit, {\n division,\n label: nameForDivision(division),\n onLabelUpdated(label, visible) {\n // If there is al LabelTrackerContext, update the label visibility\n trackLabelVisibility?.(division, visible);\n },\n ...rest,\n });\n}\n\nexport const CompositeUnitComponent = TrackedLabeledUnit;\n\ntype BaseUnitProps = {\n width: number;\n showLabels?: boolean;\n columnWidth?: number;\n clipToFrame?: boolean;\n};\n\ntype ICompositeUnitProps = BaseUnitProps & {\n gutterWidth?: number;\n labelOffset?: number;\n paddingLeft?: number;\n nameForDivision?: (division: BaseUnit) => string;\n children?: React.ReactNode;\n unitComponent?: React.FC<any>;\n unitComponentProps?: any;\n noteMode?: \"labeled\" | \"unlabeled\";\n showLabelColumn?: boolean;\n noteComponent?: React.FC<any>;\n shouldRenderNote?: (d: any) => boolean;\n};\n\ninterface CompositeUnitProps {\n unitComponent: React.FC<any>;\n unitComponentProps?: any;\n width: number;\n showLabels?: boolean;\n clipToFrame?: boolean;\n}\n\nexport function CompositeUnitsColumn(props: CompositeUnitProps) {\n /*\n A column with units and names either\n overlapping or offset to the right\n */\n const {\n width,\n unitComponent = TrackedLabeledUnit,\n unitComponentProps,\n clipToFrame,\n ...rest\n } = props;\n\n return h(LithologyColumn, { width, clipToFrame }, [\n h(UnitBoxes, {\n unitComponent,\n unitComponentProps,\n ...rest,\n }),\n ]);\n}\n\nexport function AnnotatedUnitsColumn(props: ICompositeUnitProps) {\n useEffect(() => {\n console.warn(\n \"AnnotatedUnitsColumn is deprecated and currently renders a no-op for transition purposes\",\n );\n }, []);\n return null;\n}\n\nexport function SectionLabelsColumn(props: ICompositeUnitProps) {\n // Section with \"squishy\" time scale\n const {\n width = 300,\n noteMode = \"unlabeled\",\n labelOffset = 30,\n noteComponent,\n shouldRenderNote,\n } = props;\n\n const { totalHeight, axisType } = useMacrostratColumnData();\n const _compositeScale = useCompositeScale();\n\n const unlabeledUnits = useContext(UnlabeledDivisionsContext);\n const unitsToLabel = noteMode == \"unlabeled\" ? unlabeledUnits : undefined;\n\n const selectUnit = useUnitSelectionDispatch();\n\n return h(\"div.section-labels-column\", [\n h(\n ColumnNotesProvider,\n {\n units: [],\n scale: _compositeScale,\n totalHeight,\n axisType,\n },\n h(\n SVG,\n {\n height: totalHeight,\n innerWidth: width - 4,\n paddingH: 1,\n paddingLeft: 3,\n },\n h(UnitNamesColumn, {\n divisions: unitsToLabel,\n paddingLeft: labelOffset,\n width,\n noteComponent,\n shouldRenderNote,\n scale: _compositeScale,\n onClickNote(note) {\n selectUnit(note.data, null, null);\n },\n }),\n ),\n ),\n ]);\n}\n\nexport function ColumnNotesProvider(props) {\n // A fake column axis provider that allows scales to cross\n const { children, scale, totalHeight, pixelScale } = props;\n\n const { axisType } = useMacrostratColumnData();\n\n return h(\n ColumnContext.Provider,\n {\n value: {\n divisions: [],\n scale,\n scaleClamped: scale,\n pixelHeight: totalHeight,\n pixelsPerMeter: pixelScale,\n axisType,\n zoom: 1,\n },\n },\n children,\n );\n}\n\nexport { TrackedLabeledUnit, ICompositeUnitProps };\n"],"names":["styles","id"],"mappings":"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"}
1
+ {"version":3,"file":"composite.js","sources":["../../src/units/composite.ts"],"sourcesContent":["import { hyperStyled } from \"@macrostrat/hyper\";\nimport {\n ColumnContext,\n LithologyColumn,\n SVG,\n} from \"@macrostrat/column-components\";\nimport { defaultNameFunction, UnitNamesColumn } from \"./names\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { BaseUnit } from \"@macrostrat/api-types\";\nimport { LabeledUnit, UnitBoxes } from \"./boxes\";\nimport styles from \"./composite.module.sass\";\nimport {\n useCompositeScale,\n useMacrostratColumnData,\n useUnitSelectionDispatch,\n} from \"../data-provider\";\n\nconst h = hyperStyled(styles);\n\ninterface LabelTracker {\n [key: number]: boolean;\n}\n\nconst LabelTrackerContext = createContext(null);\nconst UnlabeledDivisionsContext = createContext(null);\n\nconst findMacrostratUnitID = (u) => u.unit_id;\n\nexport function LabelTrackerProvider(props) {\n /** Tracker for units to handle state for whether their labels fit inline or not.\n * Designed to work for single or composite columns.\n */\n const { children, units, findUnitID = findMacrostratUnitID } = props;\n const [unlabeledDivisions, setUnlabeledDivisions] = useState<\n BaseUnit[] | null\n >(null);\n const labelTrackerRef = useRef<LabelTracker>({});\n\n const recompute = useCallback(() => {\n // Recompute over the *current* unit set (which changes as the column zooms).\n // The tracker ref accumulates entries for units seen at any zoom level, so\n // we gate on whether every currently-visible unit has reported — not on the\n // total number of tracked units, which would go stale and freeze the notes.\n const tracker = labelTrackerRef.current;\n const allReported = units.every((d) => findUnitID(d) in tracker);\n if (!allReported) return;\n setUnlabeledDivisions(units.filter((d) => tracker[findUnitID(d)] === false));\n }, [units, findUnitID]);\n\n const trackLabelVisibility = useCallback(\n (div, visible) => {\n labelTrackerRef.current[findUnitID(div)] = visible;\n recompute();\n },\n [recompute, findUnitID],\n );\n\n // Re-sync when the visible unit set changes (e.g. on zoom), so a unit whose\n // label now fits inline stops also rendering as a note, and vice versa.\n useEffect(() => {\n recompute();\n }, [recompute]);\n\n const value = trackLabelVisibility;\n return h(\n LabelTrackerContext.Provider,\n { value },\n h(\n UnlabeledDivisionsContext.Provider,\n { value: unlabeledDivisions },\n children,\n ),\n );\n}\n\nfunction TrackedLabeledUnit({\n division,\n nameForDivision = defaultNameFunction,\n ...rest\n}) {\n const trackLabelVisibility = useContext(LabelTrackerContext);\n return h(LabeledUnit, {\n division,\n label: nameForDivision(division),\n onLabelUpdated(label, visible) {\n // If there is al LabelTrackerContext, update the label visibility\n trackLabelVisibility?.(division, visible);\n },\n ...rest,\n });\n}\n\nexport const CompositeUnitComponent = TrackedLabeledUnit;\n\ntype BaseUnitProps = {\n width: number;\n showLabels?: boolean;\n columnWidth?: number;\n clipToFrame?: boolean;\n};\n\ntype ICompositeUnitProps = BaseUnitProps & {\n gutterWidth?: number;\n labelOffset?: number;\n paddingLeft?: number;\n nameForDivision?: (division: BaseUnit) => string;\n children?: React.ReactNode;\n unitComponent?: React.FC<any>;\n unitComponentProps?: any;\n noteMode?: \"labeled\" | \"unlabeled\";\n showLabelColumn?: boolean;\n noteComponent?: React.FC<any>;\n shouldRenderNote?: (d: any) => boolean;\n};\n\ninterface CompositeUnitProps {\n unitComponent: React.FC<any>;\n unitComponentProps?: any;\n width: number;\n showLabels?: boolean;\n clipToFrame?: boolean;\n}\n\nexport function CompositeUnitsColumn(props: CompositeUnitProps) {\n /*\n A column with units and names either\n overlapping or offset to the right\n */\n const {\n width,\n unitComponent = TrackedLabeledUnit,\n unitComponentProps,\n clipToFrame,\n ...rest\n } = props;\n\n return h(LithologyColumn, { width, clipToFrame }, [\n h(UnitBoxes, {\n unitComponent,\n unitComponentProps,\n ...rest,\n }),\n ]);\n}\n\nexport function AnnotatedUnitsColumn(props: ICompositeUnitProps) {\n useEffect(() => {\n console.warn(\n \"AnnotatedUnitsColumn is deprecated and currently renders a no-op for transition purposes\",\n );\n }, []);\n return null;\n}\n\nexport function SectionLabelsColumn(props: ICompositeUnitProps) {\n // Section with \"squishy\" time scale\n const {\n width = 300,\n noteMode = \"unlabeled\",\n labelOffset = 30,\n noteComponent,\n shouldRenderNote,\n } = props;\n\n const { totalHeight, axisType } = useMacrostratColumnData();\n const _compositeScale = useCompositeScale();\n\n const unlabeledUnits = useContext(UnlabeledDivisionsContext);\n const unitsToLabel = noteMode == \"unlabeled\" ? unlabeledUnits : undefined;\n\n const selectUnit = useUnitSelectionDispatch();\n\n return h(\"div.section-labels-column\", [\n h(\n ColumnNotesProvider,\n {\n units: [],\n scale: _compositeScale,\n totalHeight,\n axisType,\n },\n h(\n SVG,\n {\n height: totalHeight,\n innerWidth: width - 4,\n paddingH: 1,\n paddingLeft: 3,\n },\n h(UnitNamesColumn, {\n divisions: unitsToLabel,\n paddingLeft: labelOffset,\n width,\n noteComponent,\n shouldRenderNote,\n scale: _compositeScale,\n onClickNote(note) {\n selectUnit(note.data, null, null);\n },\n }),\n ),\n ),\n ]);\n}\n\nexport function ColumnNotesProvider(props) {\n // A fake column axis provider that allows scales to cross\n const { children, scale, totalHeight, pixelScale } = props;\n\n const { axisType } = useMacrostratColumnData();\n\n return h(\n ColumnContext.Provider,\n {\n value: {\n divisions: [],\n scale,\n scaleClamped: scale,\n pixelHeight: totalHeight,\n pixelsPerMeter: pixelScale,\n axisType,\n zoom: 1,\n },\n },\n children,\n );\n}\n\nexport { TrackedLabeledUnit, ICompositeUnitProps };\n"],"names":["styles"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@macrostrat/column-views",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "Data views for Macrostrat stratigraphic columns",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,19 +48,19 @@
48
48
  "@blueprintjs/core": "^6.14.0",
49
49
  "@macrostrat/api-types": "^1.2.0",
50
50
  "@macrostrat/color-utils": "^1.2.2",
51
- "@macrostrat/column-components": "^2.0.10",
52
- "@macrostrat/data-components": "^1.5.0",
51
+ "@macrostrat/column-components": "^2.1.0",
52
+ "@macrostrat/data-components": "^1.5.1",
53
53
  "@macrostrat/data-provider": "^1.2.0",
54
54
  "@macrostrat/hyper": "^3.1.1",
55
55
  "@macrostrat/map-interface": "^2.3.1",
56
- "@macrostrat/map-styles": "^2.2.6",
56
+ "@macrostrat/map-styles": "^2.2.7",
57
57
  "@macrostrat/mapbox-react": "^3.3.3",
58
58
  "@macrostrat/mapbox-utils": "^1.8.0",
59
59
  "@macrostrat/scoped-store": "^1.0.1",
60
60
  "@macrostrat/stratigraphy-utils": "^1.3.0",
61
61
  "@macrostrat/svg-map-components": "^2.0.3",
62
- "@macrostrat/timescale": "^3.1.6",
63
- "@macrostrat/ui-components": "^5.1.0",
62
+ "@macrostrat/timescale": "^3.2.0",
63
+ "@macrostrat/ui-components": "^5.2.0",
64
64
  "@turf/boolean-point-in-polygon": "^7.3.2",
65
65
  "@turf/buffer": "^7.3.2",
66
66
  "@turf/centroid": "^7.3.2",
@@ -0,0 +1,120 @@
1
+ import { useCallback, useMemo } from "react";
2
+ import { AgeDomain, useAnimatedDomain } from "@macrostrat/timescale";
3
+
4
+ /** A rendered age window, ordered as the column props expect:
5
+ * `t_age` is the younger (top) bound, `b_age` the older (bottom) bound. */
6
+ export interface AgeWindow {
7
+ t_age: number;
8
+ b_age: number;
9
+ }
10
+
11
+ export interface AnimateWindowOptions {
12
+ duration?: number;
13
+ }
14
+
15
+ export interface UseAnimatedAgeWindowOptions {
16
+ /** The full data extent — the window to reset to, and the initial window.
17
+ * `null` until the data (and hence extent) is known. */
18
+ fullExtent: AgeWindow | null;
19
+ /** Animation duration in ms (default 750). */
20
+ duration?: number;
21
+ }
22
+
23
+ export interface AnimatedAgeWindow {
24
+ /** The current animated window to feed to `t_age`/`b_age` column props
25
+ * (`null` until `fullExtent` is known). */
26
+ window: AgeWindow | null;
27
+ isAnimating: boolean;
28
+ /** True when the window is (approximately) the full extent. */
29
+ isFullExtent: boolean;
30
+ /** Pan-and-contract to an explicit window (clamped to the full extent).
31
+ * Density (`pixelScale`) is left untouched — this is not a zoom. */
32
+ zoomToWindow(target: AgeWindow, opts?: AnimateWindowOptions): void;
33
+ /** Pan-and-contract to a timescale interval, using `eag`/`lag`. */
34
+ zoomToInterval(
35
+ interval: { eag: number; lag: number },
36
+ opts?: AnimateWindowOptions,
37
+ ): void;
38
+ /** Ease back to the full extent. */
39
+ reset(opts?: AnimateWindowOptions): void;
40
+ }
41
+
42
+ /**
43
+ * Animate the rendered age window (`t_age`/`b_age`) of a column or correlation
44
+ * chart. This is the "animate the current zoom approach" model: narrowing the
45
+ * window re-lays-out the column with explicit clipping — units past the bounds
46
+ * get a zig-zag edge, unconformities keep their fixed `unconformityHeight`, and
47
+ * `pixelScale` (density) is untouched, so a sub-interval click **pans and
48
+ * contracts** at constant density rather than zooming. Joint domain/range
49
+ * change (true zoom) stays under the user's direct control via `pixelScale`.
50
+ *
51
+ * The animation itself is `useAnimatedDomain` from `@macrostrat/timescale` —
52
+ * the same state machine that drives the timescale's `useZoomableScale`. This
53
+ * hook only maps an `[older, younger]` domain onto `{ b_age, t_age }`; it
54
+ * animates the window you ask for and nothing more. Revealing a margin of the
55
+ * neighboring column is the *layout's* job, via the `windowPadding` prop (px),
56
+ * which is resolved against real section heights rather than predicted here.
57
+ */
58
+ export function useAnimatedAgeWindow(
59
+ options: UseAnimatedAgeWindowOptions,
60
+ ): AnimatedAgeWindow {
61
+ const { fullExtent, duration } = options;
62
+
63
+ const extent = useMemo(() => toDomain(fullExtent), [
64
+ fullExtent?.t_age,
65
+ fullExtent?.b_age,
66
+ ]);
67
+
68
+ const anim = useAnimatedDomain({ extent, duration });
69
+
70
+ const zoomToWindow = useCallback(
71
+ (target: AgeWindow, opts: AnimateWindowOptions = {}) => {
72
+ anim.animateTo([target.b_age, target.t_age], {
73
+ duration: opts.duration,
74
+ });
75
+ },
76
+ [anim.animateTo],
77
+ );
78
+
79
+ const zoomToInterval = useCallback(
80
+ (
81
+ interval: { eag: number; lag: number },
82
+ opts: AnimateWindowOptions = {},
83
+ ) => {
84
+ zoomToWindow({ t_age: interval.lag, b_age: interval.eag }, opts);
85
+ },
86
+ [zoomToWindow],
87
+ );
88
+
89
+ const reset = useCallback(
90
+ (opts: AnimateWindowOptions = {}) => {
91
+ if (fullExtent == null) return;
92
+ zoomToWindow(fullExtent, opts);
93
+ },
94
+ [zoomToWindow, fullExtent?.t_age, fullExtent?.b_age],
95
+ );
96
+
97
+ return {
98
+ window: toWindow(anim.domain),
99
+ isAnimating: anim.isAnimating,
100
+ isFullExtent: anim.isFullExtent,
101
+ zoomToWindow,
102
+ zoomToInterval,
103
+ reset,
104
+ };
105
+ }
106
+
107
+ /** `{t_age, b_age}` ↔ the core's `[older, younger]` domain. */
108
+ function toDomain(w: AgeWindow): AgeDomain;
109
+ function toDomain(w: AgeWindow | null): AgeDomain | null;
110
+ function toDomain(w: AgeWindow | null): AgeDomain | null {
111
+ if (w == null) return null;
112
+ return [w.b_age, w.t_age];
113
+ }
114
+
115
+ function toWindow(d: AgeDomain): AgeWindow;
116
+ function toWindow(d: AgeDomain | null): AgeWindow | null;
117
+ function toWindow(d: AgeDomain | null): AgeWindow | null {
118
+ if (d == null) return null;
119
+ return { t_age: d[1], b_age: d[0] };
120
+ }
package/src/column.ts CHANGED
@@ -45,6 +45,10 @@ import {
45
45
  ColumnHeightScaleOptions,
46
46
  } from "./prepare-units";
47
47
  import { UnitLong } from "@macrostrat/api-types";
48
+ import type {
49
+ TimescaleClickHandler,
50
+ IntervalStyleBuilder,
51
+ } from "@macrostrat/timescale";
48
52
  import { NonIdealState } from "@blueprintjs/core";
49
53
  import { DataField } from "@macrostrat/data-components";
50
54
  import { ScaleContinuousNumeric } from "d3-scale";
@@ -61,6 +65,10 @@ interface BaseColumnProps extends Omit<
61
65
  // Timescale properties
62
66
  showTimescale?: boolean;
63
67
  timescaleLevels?: number | [number, number];
68
+ /** Called when a timescale interval is clicked (e.g. to zoom the age range). */
69
+ onClickTimescaleInterval?: TimescaleClickHandler;
70
+ /** Per-interval style for the timescale (e.g. to bold the selected interval). */
71
+ timescaleIntervalStyle?: IntervalStyleBuilder;
64
72
  unconformityLabels?: boolean | UnconformityLabelPlacement;
65
73
  onMouseOver?: (
66
74
  unit: UnitLong | null,
@@ -87,6 +95,12 @@ export interface ColumnProps
87
95
  // Unconformity height in pixels
88
96
  unconformityHeight?: number;
89
97
  scale?: ScaleContinuousNumeric<number, number>;
98
+ /** True while the age window is animating (e.g. from `useAnimatedAgeWindow`),
99
+ * so per-frame label/pattern recalculation can be skipped. */
100
+ isTransitioning?: boolean;
101
+ /** Hide unit labels while transitioning (perf escape hatch; default false —
102
+ * labels stay visible through the animation). */
103
+ hideLabelsWhileTransitioning?: boolean;
90
104
  ref?: RefObject<ColumnRef>;
91
105
  }
92
106
 
@@ -108,11 +122,14 @@ export function Column(props: ColumnProps) {
108
122
  pixelScale,
109
123
  minPixelScale = 0.2,
110
124
  minSectionHeight = 50,
125
+ windowPadding = 0,
111
126
  collapseSmallUnconformities = true,
112
127
  allowUnitSelection = false,
113
128
  hybridScale,
114
129
  scale,
115
130
  axisType,
131
+ isTransitioning,
132
+ hideLabelsWhileTransitioning,
116
133
  ref,
117
134
  ...rest
118
135
  } = props;
@@ -152,6 +169,7 @@ export function Column(props: ColumnProps) {
152
169
  pixelScale,
153
170
  minPixelScale: _minPixelScale,
154
171
  minSectionHeight: _minSectionHeight,
172
+ windowPadding,
155
173
  collapseSmallUnconformities,
156
174
  // TODO: consider unifying scale and hybridScale options
157
175
  scale,
@@ -179,6 +197,8 @@ export function Column(props: ColumnProps) {
179
197
  allowUnitSelection: showUnitPopover || allowUnitSelection,
180
198
  onUnitSelected,
181
199
  selectedUnit,
200
+ isTransitioning,
201
+ hideLabelsWhileTransitioning,
182
202
  ref,
183
203
  },
184
204
  h(ColumnInner, { ageAxisComponent, ...rest }, [
@@ -218,6 +238,8 @@ function ColumnInner(props: ColumnInnerProps) {
218
238
  timescaleLevels,
219
239
  maxInternalColumns,
220
240
  onMouseOver,
241
+ onClickTimescaleInterval,
242
+ timescaleIntervalStyle,
221
243
  ageAxisComponent = CompositeAgeAxis,
222
244
  } = props;
223
245
 
@@ -262,6 +284,8 @@ function ColumnInner(props: ColumnInnerProps) {
262
284
  h.if(_showTimescale)(CompositeTimescale, {
263
285
  levels: timescaleLevels,
264
286
  unconformityLabels: _timescaleUnconformityLabels,
287
+ onClickInterval: onClickTimescaleInterval,
288
+ intervalStyle: timescaleIntervalStyle,
265
289
  }),
266
290
  h(SectionsColumn, {
267
291
  unitComponent,
@@ -69,6 +69,13 @@ export interface CorrelationChartProps extends CorrelationChartSettings {
69
69
  /** Content rendered above the timescale axis (top-left), beside the column
70
70
  * headers — e.g. a zoom/filter indicator. */
71
71
  axisTopContent?: React.ReactNode;
72
+ /** True while the age window is animating (e.g. from `useAnimatedAgeWindow`).
73
+ * Propagated to each column's context so per-frame label/pattern work can be
74
+ * skipped during the transition. */
75
+ isTransitioning?: boolean;
76
+ /** Hide unit labels while transitioning (perf escape hatch; default false —
77
+ * labels stay visible through the animation). */
78
+ hideLabelsWhileTransitioning?: boolean;
72
79
  onUnitSelected?: (unitID: number | null, unit: BaseUnit | null) => void;
73
80
  /** Called when a timescale interval is clicked (e.g. to zoom the age range) */
74
81
  onClickTimescaleInterval?: TimescaleClickHandler;
@@ -83,6 +90,14 @@ export interface CorrelationChartProps extends CorrelationChartSettings {
83
90
  * this so that they line up with the columns beneath them. */
84
91
  const chartPaddingH = 4;
85
92
 
93
+ /** Default scale settings for the correlation chart. */
94
+ const defaultCorrelationChartScaleProps = {
95
+ targetUnitHeight: 10,
96
+ unconformityHeight: 60,
97
+ minSectionHeight: 60,
98
+ collapseSmallUnconformities: true,
99
+ };
100
+
86
101
  function MainChartArea({ children }) {
87
102
  const columnRef = useColumnRef();
88
103
  return h("div.main-chart", { ref: columnRef }, children);
@@ -109,19 +124,14 @@ export function CorrelationChart({
109
124
  onClickTimescaleInterval,
110
125
  onColumnMouseOver,
111
126
  onColumnClick,
127
+ isTransitioning = false,
128
+ hideLabelsWhileTransitioning = false,
112
129
  ...scaleProps
113
130
  }: CorrelationChartProps) {
114
- const defaultScaleProps = {
115
- targetUnitHeight: 10,
116
- unconformityHeight: 60,
117
- minSectionHeight: 60,
118
- collapseSmallUnconformities: true,
119
- };
120
-
121
131
  const chartData = useMemo(() => {
122
132
  if (!data) return null;
123
133
  return buildCorrelationChartData(data, {
124
- ...defaultScaleProps,
134
+ ...defaultCorrelationChartScaleProps,
125
135
  ...scaleProps,
126
136
  });
127
137
  }, [data, ...Object.values(scaleProps)]);
@@ -180,6 +190,8 @@ export function CorrelationChart({
180
190
  scale,
181
191
  unitComponent,
182
192
  onColumnMouseOver,
193
+ isTransitioning,
194
+ hideLabelsWhileTransitioning,
183
195
  });
184
196
  }),
185
197
  ),
@@ -215,6 +227,8 @@ function Package({
215
227
  pixelScale,
216
228
  scale,
217
229
  onColumnMouseOver,
230
+ isTransitioning,
231
+ hideLabelsWhileTransitioning,
218
232
  }) {
219
233
  return h("g.package", { transform: `translate(0 ${offset})` }, [
220
234
  // Disable the SVG overlay for now
@@ -232,6 +246,8 @@ function Package({
232
246
  scale,
233
247
  offsetLeft: i * (columnWidth + columnSpacing),
234
248
  onColumnMouseOver,
249
+ isTransitioning,
250
+ hideLabelsWhileTransitioning,
235
251
  });
236
252
  }),
237
253
  ]),
@@ -253,6 +269,8 @@ interface ColumnProps {
253
269
  pixelScale: number;
254
270
  scale?: ScaleContinuousNumeric<number, number>;
255
271
  onColumnMouseOver?: (columnID: number | null) => void;
272
+ isTransitioning?: boolean;
273
+ hideLabelsWhileTransitioning?: boolean;
256
274
  }
257
275
 
258
276
  function Column(props: ColumnProps) {
@@ -266,6 +284,8 @@ function Column(props: ColumnProps) {
266
284
  scale,
267
285
  unitComponent = ColoredUnitComponent,
268
286
  onColumnMouseOver,
287
+ isTransitioning,
288
+ hideLabelsWhileTransitioning,
269
289
  } = props;
270
290
 
271
291
  const columnWidth = width;
@@ -297,6 +317,8 @@ function Column(props: ColumnProps) {
297
317
  scale,
298
318
  pixelsPerMeter: pixelScale, // Actually pixels per myr
299
319
  axisType: ColumnAxisType.AGE,
320
+ isTransitioning,
321
+ hideLabelsWhileTransitioning,
300
322
  },
301
323
  h(UnitBoxes, {
302
324
  unitComponent,
@@ -118,6 +118,11 @@ export interface MacrostratColumnDataContext<T extends BaseUnit> {
118
118
  totalHeight?: number;
119
119
  axisType?: ColumnAxisType;
120
120
  allowUnitSelection?: boolean;
121
+ /** True while the age window is animating; propagated to each section's
122
+ * `ColumnProvider` so per-frame label/pattern work can be skipped. */
123
+ isTransitioning?: boolean;
124
+ /** Hide unit labels while transitioning (perf escape hatch; default false). */
125
+ hideLabelsWhileTransitioning?: boolean;
121
126
  }
122
127
 
123
128
  export interface ColumnDataProviderProps<T extends BaseUnit>
@@ -138,6 +143,8 @@ export function MacrostratColumnDataProvider<T extends BaseUnit>({
138
143
  allowUnitSelection,
139
144
  onUnitSelected,
140
145
  selectedUnit,
146
+ isTransitioning,
147
+ hideLabelsWhileTransitioning,
141
148
  ref,
142
149
  }: ColumnDataProviderProps<T>) {
143
150
  /** Internal provider for Macrostrat column data.
@@ -158,8 +165,17 @@ export function MacrostratColumnDataProvider<T extends BaseUnit>({
158
165
  sections,
159
166
  totalHeight,
160
167
  axisType,
168
+ isTransitioning,
169
+ hideLabelsWhileTransitioning,
161
170
  };
162
- }, [units, sections, totalHeight, axisType]);
171
+ }, [
172
+ units,
173
+ sections,
174
+ totalHeight,
175
+ axisType,
176
+ isTransitioning,
177
+ hideLabelsWhileTransitioning,
178
+ ]);
163
179
 
164
180
  return h(
165
181
  MacrostratColumnStateProvider,
@@ -242,7 +258,8 @@ export function MacrostratColumnProvider(props) {
242
258
  * generic concepts to Macrostrat-specific ones.
243
259
  */
244
260
 
245
- const { axisType } = useMacrostratColumnData();
261
+ const { axisType, isTransitioning, hideLabelsWhileTransitioning } =
262
+ useMacrostratColumnData();
246
263
  const { units, domain, pixelScale, scale, children } = props;
247
264
  return h(
248
265
  ColumnProvider,
@@ -252,6 +269,8 @@ export function MacrostratColumnProvider(props) {
252
269
  range: domain,
253
270
  pixelsPerMeter: pixelScale,
254
271
  scale,
272
+ isTransitioning,
273
+ hideLabelsWhileTransitioning,
255
274
  },
256
275
  children,
257
276
  );
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./units";
2
2
  export * from "./data-provider";
3
3
  export * from "./age-axis";
4
4
  export * from "./prepare-units";
5
+ export * from "./animated-age-window";
5
6
  export * from "./column";
6
7
  export * from "./unit-details";
7
8
  export * from "./maps";