@macrostrat/column-views 3.10.0 → 3.11.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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.11.0] - 2026-09-04 [_changes_](https://github.com/UW-Macrostrat/web-components/compare/@macrostrat/column-views-v3.10.0...@macrostrat/column-views-v3.11.0)
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `AgeWindowTag`, a compact, clearable indicator of the age window a column
8
+ or
9
+ [0dfa4399](https://github.com/UW-Macrostrat/web-components/commit/0dfa4399e47298a2cb37aafa5785149a5f5b994d)
10
+ correlation chart is focused on — an interval tag with its range (or the
11
+ refined range when the window is narrower than the interval), or a bare age
12
+ range — for use in a chart's `axisTopContent` or page chrome.
13
+
14
+ Add `intervalShortFromTimescale`, adapting the `Interval` delivered by a
15
+ `Timescale` click (`eag`/`lag`/`nam`/`col`) to the `IntervalShort` shape the
16
+ `@macrostrat/data-components` tags take.
17
+
18
+ Add `timescaleIntervalStyle` to `CorrelationChart`, forwarding a per-interval
19
+ style to its timescale so the selected interval can be highlighted, matching
20
+ the existing `Column` prop.
21
+
3
22
  ## [3.10.0] - 2026-09-01 [_changes_](https://github.com/UW-Macrostrat/web-components/compare/@macrostrat/column-views-v3.9.0...@macrostrat/column-views-v3.10.0)
4
23
 
5
24
  ### Minor Changes
@@ -441,6 +441,16 @@ body.dark-mode ._column-container_1j8hh_1 svg pattern image {
441
441
  right: 0;
442
442
  position: absolute;
443
443
  border-top: 1px solid lightgreen;
444
+ }._age-window-tag_1tskh_1 {
445
+ display: inline-flex;
446
+ align-items: center;
447
+ gap: 0.15em;
448
+ white-space: nowrap;
449
+ vertical-align: middle;
450
+ }
451
+ ._age-window-tag_1tskh_1 ._clear-button_1tskh_8 {
452
+ min-width: 0;
453
+ min-height: 0;
444
454
  }._truncated-list_120po_1 {
445
455
  padding-left: 0.2em;
446
456
  font-size: 0.9em;
@@ -47,6 +47,7 @@ function CorrelationChart({
47
47
  columnHeaderComponent,
48
48
  axisTopContent,
49
49
  onClickTimescaleInterval,
50
+ timescaleIntervalStyle,
50
51
  onColumnMouseOver,
51
52
  onColumnClick,
52
53
  isTransitioning = false,
@@ -82,7 +83,8 @@ function CorrelationChart({
82
83
  main_module.default(TimescaleColumn, {
83
84
  scaleInfo,
84
85
  unconformityLabels,
85
- onClickInterval: onClickTimescaleInterval
86
+ onClickInterval: onClickTimescaleInterval,
87
+ intervalStyle: timescaleIntervalStyle
86
88
  }),
87
89
  main_module.default(MainChartArea, [
88
90
  main_module.default(
@@ -305,13 +307,19 @@ function ColumnHeaderCell({
305
307
  );
306
308
  }
307
309
  function TimescaleColumn(props) {
308
- const { scaleInfo, unconformityLabels = true, onClickInterval } = props;
310
+ const {
311
+ scaleInfo,
312
+ unconformityLabels = true,
313
+ onClickInterval,
314
+ intervalStyle
315
+ } = props;
309
316
  return main_module.default("div.column-container.age-axis-container", [
310
317
  main_module.default(ageAxis.CompositeAgeAxisCore, { ...scaleInfo }),
311
318
  main_module.default(section.CompositeTimescaleCore, {
312
319
  ...scaleInfo,
313
320
  unconformityLabels,
314
- onClickInterval
321
+ onClickInterval,
322
+ intervalStyle
315
323
  })
316
324
  ]);
317
325
  }
@@ -1 +1 @@
1
- {"version":3,"file":"main.cjs","sources":["../../src/correlation-chart/main.ts"],"sourcesContent":["/** Correlation chart */\nimport {\n UnitSelectionProvider,\n UnitKeyboardNavigation,\n useUnitSelectionDispatch,\n useColumnRef,\n} from \"../data-provider\";\nimport { UnitDetailsFeature, UnitSelectionPopover } from \"../unit-details\";\nimport h from \"./main.module.sass\";\nimport { useMemo } from \"react\";\nimport { useInDarkMode } from \"@macrostrat/ui-components\";\nimport { CompositeTimescaleCore } from \"../section\";\nimport classNames from \"classnames\";\nimport {\n findLaterallyExtensiveUnits,\n splitStratIntoBoxes,\n UnitGroupBox,\n CorrelationChartSettings,\n buildCorrelationChartData,\n} from \"./prepare-data\";\nimport {\n CompositeAgeAxisCore,\n CompositeStratigraphicScaleInfo,\n} from \"../age-axis\";\nimport {\n ColumnAxisType,\n ColumnProvider,\n SVG,\n} from \"@macrostrat/column-components\";\nimport { ColoredUnitComponent } from \"../units\";\nimport { UnitBoxes } from \"../units/boxes\";\nimport { ColumnContainer } from \"../column\";\nimport type { ColumnData } from \"@macrostrat/data-provider\";\nimport { BaseUnit } from \"@macrostrat/api-types\";\nimport { ScaleContinuousNumeric } from \"d3-scale\";\nimport { ExtUnit } from \"../prepare-units/types\";\nimport type { TimescaleClickHandler } from \"@macrostrat/timescale\";\nimport { useMacrostratColumnInfo } from \"@macrostrat/data-provider\";\n\n/** Standard props passed to a `columnHeaderComponent`. Everything a header\n * needs is provided here, so the component can be a pure function of its props\n * (no hooks required). Column-level hover/click are handled by the chart. */\nexport interface ColumnHeaderProps {\n /** The column data (units + identifier) for this column */\n column: ColumnData;\n /** The column's ID (col_id) */\n columnID: number;\n /** The column's index (left-to-right) within the chart */\n columnIndex: number;\n /** The units within the column */\n units: ExtUnit[];\n /** The rendered width of the column, in pixels */\n width: number;\n /** The column's name, resolved from column metadata when available */\n columnName?: string | null;\n}\n\nexport interface CorrelationChartProps extends CorrelationChartSettings {\n data: ColumnData[];\n columnWidth?: number;\n columnSpacing?: number;\n targetUnitHeight?: number;\n unconformityLabels?: boolean;\n selectedUnit?: number | null;\n showUnitPopover?: boolean;\n unitComponent?: any;\n /** Arbitrary content (e.g. column title and/or ID) rendered above each column */\n columnHeaderComponent?: React.ComponentType<ColumnHeaderProps>;\n /** Content rendered above the timescale axis (top-left), beside the column\n * headers — e.g. a zoom/filter indicator. */\n axisTopContent?: React.ReactNode;\n /** True while the age window is animating (e.g. from `useAnimatedAgeWindow`).\n * Propagated to each column's context so per-frame label/pattern work can be\n * skipped during the transition. */\n isTransitioning?: boolean;\n /** Hide unit labels while transitioning (perf escape hatch; default false —\n * labels stay visible through the animation). */\n hideLabelsWhileTransitioning?: boolean;\n onUnitSelected?: (unitID: number | null, unit: BaseUnit | null) => void;\n /** Called when a timescale interval is clicked (e.g. to zoom the age range) */\n onClickTimescaleInterval?: TimescaleClickHandler;\n /** Called when the pointer enters/leaves a column (header or body). Passes\n * the column ID, or null on leave. */\n onColumnMouseOver?: (columnID: number | null) => void;\n /** Called when a column header is clicked (e.g. to frame it on a map) */\n onColumnClick?: (columnID: number) => void;\n}\n\n/** Horizontal padding of the main chart SVG. Column headers are aligned to\n * this so that they line up with the columns beneath them. */\nconst chartPaddingH = 4;\n\n/** Default scale settings for the correlation chart. */\nconst defaultCorrelationChartScaleProps = {\n targetUnitHeight: 10,\n unconformityHeight: 60,\n minSectionHeight: 60,\n collapseSmallUnconformities: true,\n};\n\nfunction MainChartArea({ children }) {\n const columnRef = useColumnRef();\n return h(\"div.main-chart\", { ref: columnRef }, children);\n}\n\nconst unitPopoverFeatures = new Set([\n UnitDetailsFeature.AdjacentUnits,\n UnitDetailsFeature.OutcropType,\n UnitDetailsFeature.DepthRange,\n UnitDetailsFeature.ColumnName,\n]);\n\nexport function CorrelationChart({\n data,\n columnSpacing = 0,\n columnWidth = 130,\n unconformityLabels = true,\n showUnitPopover = true,\n selectedUnit,\n onUnitSelected,\n unitComponent,\n columnHeaderComponent,\n axisTopContent,\n onClickTimescaleInterval,\n onColumnMouseOver,\n onColumnClick,\n isTransitioning = false,\n hideLabelsWhileTransitioning = false,\n ...scaleProps\n}: CorrelationChartProps) {\n const chartData = useMemo(() => {\n if (!data) return null;\n return buildCorrelationChartData(data, {\n ...defaultCorrelationChartScaleProps,\n ...scaleProps,\n });\n }, [data, ...Object.values(scaleProps)]);\n\n // A flattened units array is used to support keyboard navigation\n const units = useMemo(() => {\n return data?.map((d0) => d0.units).flat() ?? [];\n }, [data]);\n\n const inDarkMode = useInDarkMode();\n\n const className = classNames({\n \"dark-mode\": inDarkMode,\n });\n\n if (chartData == null) {\n return null;\n }\n\n const { packages, scaleInfo, nColumns } = chartData;\n\n const mainWidth = (columnWidth + columnSpacing) * nColumns;\n\n return h(\n ColumnContainer,\n { className: \"correlation-diagram\" },\n h(\n UnitSelectionProvider,\n { selectedUnit, onUnitSelected, units },\n h(ChartArea, [\n h(TimescaleColumn, {\n scaleInfo,\n unconformityLabels,\n onClickInterval: onClickTimescaleInterval,\n }),\n h(MainChartArea, [\n h(\n SVG,\n {\n className,\n innerWidth: mainWidth,\n height: scaleInfo.totalHeight,\n paddingH: chartPaddingH,\n },\n packages.map((pkg, i) => {\n const { offset, domain, pixelScale, scale, key } =\n scaleInfo.packages[i];\n return h(Package, {\n columnData: pkg.columnData,\n key,\n columnWidth,\n columnSpacing,\n offset,\n domain,\n pixelScale,\n scale,\n unitComponent,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n });\n }),\n ),\n h.if(showUnitPopover)(UnitSelectionPopover, {\n features: unitPopoverFeatures,\n }),\n // Navigation only works within a column for now...\n h(UnitKeyboardNavigation, { columnData: data }),\n ]),\n // Rendered last so the sticky header paints above the unit boxes\n // (positioned siblings paint in document order at the same z-index)\n h(ColumnHeaderRow, {\n data,\n columnWidth,\n columnSpacing,\n columnHeaderComponent,\n axisTopContent,\n onColumnMouseOver,\n onColumnClick,\n }),\n ]),\n ),\n );\n}\n\nfunction Package({\n columnData,\n columnSpacing,\n columnWidth,\n unitComponent,\n offset,\n domain,\n pixelScale,\n scale,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n}) {\n return h(\"g.package\", { transform: `translate(0 ${offset})` }, [\n // Disable the SVG overlay for now\n //h(PackageSVGOverlay, { columnData, columnSpacing }),\n h(\"g.column-units\", [\n columnData.map((data, i) => {\n return h(Column, {\n units: data.units,\n columnID: data.columnID,\n unitComponent,\n width: columnWidth,\n key: i,\n domain,\n pixelScale,\n scale,\n offsetLeft: i * (columnWidth + columnSpacing),\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n });\n }),\n ]),\n ]);\n}\n\ninterface ColumnProps {\n units: ExtUnit[];\n columnID?: number;\n unitComponent?: React.FunctionComponent<any>;\n unitComponentProps?: any;\n showLabels?: boolean;\n width?: number;\n columnWidth?: number;\n columnSpacing?: number;\n targetUnitHeight?: number;\n offsetLeft?: number;\n domain: [number, number];\n pixelScale: number;\n scale?: ScaleContinuousNumeric<number, number>;\n onColumnMouseOver?: (columnID: number | null) => void;\n isTransitioning?: boolean;\n hideLabelsWhileTransitioning?: boolean;\n}\n\nfunction Column(props: ColumnProps) {\n const {\n units,\n columnID,\n width = 150,\n offsetLeft,\n domain,\n pixelScale,\n scale,\n unitComponent = ColoredUnitComponent,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n } = props;\n\n const columnWidth = width;\n\n if (units.length == 0) {\n return null;\n }\n\n const hoverHandlers =\n onColumnMouseOver != null && columnID != null\n ? {\n onMouseEnter: () => onColumnMouseOver(columnID),\n onMouseLeave: () => onColumnMouseOver(null),\n }\n : {};\n\n return h(\n \"g.section\",\n {\n transform: `translate(${offsetLeft} 0)`,\n ...hoverHandlers,\n },\n h(\n ColumnProvider,\n {\n // Need to tighten up types here...\n divisions: units as any[],\n range: domain,\n scale,\n pixelsPerMeter: pixelScale, // Actually pixels per myr\n axisType: ColumnAxisType.AGE,\n isTransitioning,\n hideLabelsWhileTransitioning,\n },\n h(UnitBoxes, {\n unitComponent,\n unitComponentProps: {\n width: columnWidth,\n showLabel: true,\n },\n }),\n ),\n );\n}\n\nfunction PackageSVGOverlay({ data, columnWidth = 100, columnSpacing = 0 }) {\n const { b_age, t_age, bestPixelScale, columnData } = data;\n\n const width = (columnWidth + columnSpacing) * columnData.length;\n const height = Math.ceil((b_age - t_age) * bestPixelScale) + 2;\n\n const extensiveUnits = findLaterallyExtensiveUnits(data);\n\n const scale = (val: number) => {\n return (val - t_age) * bestPixelScale;\n };\n\n return h(\n \"div.package-overlay\",\n { style: { width, height } },\n extensiveUnits.map((d) => {\n return h(LaterallyExtensiveUnit, {\n data: d,\n scale,\n pixelScale: bestPixelScale,\n columnSpacing,\n });\n }),\n );\n}\n\nfunction LaterallyExtensiveUnit({ data, scale, pixelScale, columnSpacing }) {\n const { b_age, t_age, strat_name_long, units } = data;\n // Build boxes by column groups\n const boxes: UnitGroupBox[] = splitStratIntoBoxes(data);\n\n return h(\n \"div.laterally-extensive-unit\",\n boxes.map((d, i) => {\n return h(StratColSpan, {\n scale,\n data: d,\n pixelScale,\n key: i,\n columnSpacing,\n });\n }),\n );\n}\n\nfunction StratColSpan({\n data,\n scale,\n columnWidth = 100,\n columnSpacing = 0,\n pixelScale = 1,\n}) {\n const { startCol, endCol, strat_name_long, t_age, b_age } = data;\n const top = scale(t_age);\n const left = startCol * (columnWidth + columnSpacing);\n const width = (endCol - startCol + 1) * (columnWidth + columnSpacing);\n const height = (b_age - t_age) * pixelScale;\n return h(\n \"div.strat-col-span\",\n { style: { top, height, width, left } },\n strat_name_long,\n );\n}\n\nfunction ChartArea({ children }) {\n const setSelectedUnit = useUnitSelectionDispatch();\n\n return h(\n \"div.correlation-chart-inner\",\n {\n onClick() {\n setSelectedUnit(null, null);\n },\n },\n children,\n );\n}\n\ninterface ColumnHeaderRowProps {\n data: ColumnData[];\n columnWidth: number;\n columnSpacing: number;\n columnHeaderComponent?: React.ComponentType<ColumnHeaderProps>;\n axisTopContent?: React.ReactNode;\n onColumnMouseOver?: (columnID: number | null) => void;\n onColumnClick?: (columnID: number) => void;\n}\n\nfunction ColumnHeaderRow({\n data,\n columnWidth,\n columnSpacing,\n columnHeaderComponent,\n axisTopContent,\n onColumnMouseOver,\n onColumnClick,\n}: ColumnHeaderRowProps) {\n /** The top grid row: arbitrary content above the timescale axis (top-left)\n * and, when a header component is provided, per-column headers aligned with\n * the columns. Collapses to nothing when neither is present. */\n const Component = columnHeaderComponent;\n if (Component == null && axisTopContent == null) {\n return null;\n }\n\n return h([\n h(\"div.column-header-spacer\", axisTopContent),\n h.if(Component != null)(\n \"div.column-header-row\",\n {\n style: {\n paddingLeft: chartPaddingH,\n paddingRight: chartPaddingH,\n gap: columnSpacing,\n },\n },\n data.map((column, i) => {\n return h(ColumnHeaderCell, {\n key: column.columnID ?? i,\n column,\n columnIndex: i,\n columnWidth,\n Component: Component!,\n onColumnMouseOver,\n onColumnClick,\n });\n }),\n ),\n ]);\n}\n\nfunction ColumnHeaderCell({\n column,\n columnIndex,\n columnWidth,\n Component,\n onColumnMouseOver,\n onColumnClick,\n}: {\n column: ColumnData;\n columnIndex: number;\n columnWidth: number;\n Component: React.ComponentType<ColumnHeaderProps>;\n onColumnMouseOver?: (columnID: number | null) => void;\n onColumnClick?: (columnID: number) => void;\n}) {\n /** Resolves the column name and wires column-level hover/click so the header\n * component itself can stay a pure function of props. */\n const columnID = column.columnID;\n const info = useMacrostratColumnInfo(columnID);\n\n return h(\n \"div.column-header-cell\",\n {\n style: {\n width: columnWidth,\n minWidth: columnWidth,\n maxWidth: columnWidth,\n },\n onMouseEnter: () => onColumnMouseOver?.(columnID),\n onMouseLeave: () => onColumnMouseOver?.(null),\n onClick: onColumnClick\n ? (e: React.MouseEvent) => {\n e.stopPropagation();\n onColumnClick(columnID);\n }\n : undefined,\n },\n h(Component, {\n column,\n columnID,\n columnIndex,\n units: column.units as ExtUnit[],\n width: columnWidth,\n columnName: info?.col_name ?? null,\n }),\n );\n}\n\ninterface TimescaleColumnProps {\n scaleInfo: CompositeStratigraphicScaleInfo;\n showLabels?: boolean;\n unconformityLabels?: boolean;\n onClickInterval?: TimescaleClickHandler;\n}\n\nfunction TimescaleColumn(props: TimescaleColumnProps) {\n const { scaleInfo, unconformityLabels = true, onClickInterval } = props;\n return h(\"div.column-container.age-axis-container\", [\n h(CompositeAgeAxisCore, { ...scaleInfo }),\n h(CompositeTimescaleCore, {\n ...scaleInfo,\n unconformityLabels,\n onClickInterval,\n }),\n ]);\n}\n"],"names":["useColumnRef","h","UnitDetailsFeature","useMemo","buildCorrelationChartData","useInDarkMode","classNames","ColumnContainer","UnitSelectionProvider","SVG","UnitSelectionPopover","UnitKeyboardNavigation","ColoredUnitComponent","ColumnProvider","ColumnAxisType","UnitBoxes","useUnitSelectionDispatch","column","useMacrostratColumnInfo","CompositeAgeAxisCore","CompositeTimescaleCore"],"mappings":"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"}
1
+ {"version":3,"file":"main.cjs","sources":["../../src/correlation-chart/main.ts"],"sourcesContent":["/** Correlation chart */\nimport {\n UnitSelectionProvider,\n UnitKeyboardNavigation,\n useUnitSelectionDispatch,\n useColumnRef,\n} from \"../data-provider\";\nimport { UnitDetailsFeature, UnitSelectionPopover } from \"../unit-details\";\nimport h from \"./main.module.sass\";\nimport { useMemo } from \"react\";\nimport { useInDarkMode } from \"@macrostrat/ui-components\";\nimport { CompositeTimescaleCore } from \"../section\";\nimport classNames from \"classnames\";\nimport {\n findLaterallyExtensiveUnits,\n splitStratIntoBoxes,\n UnitGroupBox,\n CorrelationChartSettings,\n buildCorrelationChartData,\n} from \"./prepare-data\";\nimport {\n CompositeAgeAxisCore,\n CompositeStratigraphicScaleInfo,\n} from \"../age-axis\";\nimport {\n ColumnAxisType,\n ColumnProvider,\n SVG,\n} from \"@macrostrat/column-components\";\nimport { ColoredUnitComponent } from \"../units\";\nimport { UnitBoxes } from \"../units/boxes\";\nimport { ColumnContainer } from \"../column\";\nimport type { ColumnData } from \"@macrostrat/data-provider\";\nimport { BaseUnit } from \"@macrostrat/api-types\";\nimport { ScaleContinuousNumeric } from \"d3-scale\";\nimport { ExtUnit } from \"../prepare-units/types\";\nimport type {\n IntervalStyleBuilder,\n TimescaleClickHandler,\n} from \"@macrostrat/timescale\";\nimport { useMacrostratColumnInfo } from \"@macrostrat/data-provider\";\n\n/** Standard props passed to a `columnHeaderComponent`. Everything a header\n * needs is provided here, so the component can be a pure function of its props\n * (no hooks required). Column-level hover/click are handled by the chart. */\nexport interface ColumnHeaderProps {\n /** The column data (units + identifier) for this column */\n column: ColumnData;\n /** The column's ID (col_id) */\n columnID: number;\n /** The column's index (left-to-right) within the chart */\n columnIndex: number;\n /** The units within the column */\n units: ExtUnit[];\n /** The rendered width of the column, in pixels */\n width: number;\n /** The column's name, resolved from column metadata when available */\n columnName?: string | null;\n}\n\nexport interface CorrelationChartProps extends CorrelationChartSettings {\n data: ColumnData[];\n columnWidth?: number;\n columnSpacing?: number;\n targetUnitHeight?: number;\n unconformityLabels?: boolean;\n selectedUnit?: number | null;\n showUnitPopover?: boolean;\n unitComponent?: any;\n /** Arbitrary content (e.g. column title and/or ID) rendered above each column */\n columnHeaderComponent?: React.ComponentType<ColumnHeaderProps>;\n /** Content rendered above the timescale axis (top-left), beside the column\n * headers — e.g. a zoom/filter indicator. */\n axisTopContent?: React.ReactNode;\n /** True while the age window is animating (e.g. from `useAnimatedAgeWindow`).\n * Propagated to each column's context so per-frame label/pattern work can be\n * skipped during the transition. */\n isTransitioning?: boolean;\n /** Hide unit labels while transitioning (perf escape hatch; default false —\n * labels stay visible through the animation). */\n hideLabelsWhileTransitioning?: boolean;\n onUnitSelected?: (unitID: number | null, unit: BaseUnit | null) => void;\n /** Called when a timescale interval is clicked (e.g. to zoom the age range) */\n onClickTimescaleInterval?: TimescaleClickHandler;\n /** Called when the pointer enters/leaves a column (header or body). Passes\n * the column ID, or null on leave. */\n onColumnMouseOver?: (columnID: number | null) => void;\n /** Called when a column header is clicked (e.g. to frame it on a map) */\n onColumnClick?: (columnID: number) => void;\n}\n\n/** Horizontal padding of the main chart SVG. Column headers are aligned to\n * this so that they line up with the columns beneath them. */\nconst chartPaddingH = 4;\n\n/** Default scale settings for the correlation chart. */\nconst defaultCorrelationChartScaleProps = {\n targetUnitHeight: 10,\n unconformityHeight: 60,\n minSectionHeight: 60,\n collapseSmallUnconformities: true,\n};\n\nfunction MainChartArea({ children }) {\n const columnRef = useColumnRef();\n return h(\"div.main-chart\", { ref: columnRef }, children);\n}\n\nconst unitPopoverFeatures = new Set([\n UnitDetailsFeature.AdjacentUnits,\n UnitDetailsFeature.OutcropType,\n UnitDetailsFeature.DepthRange,\n UnitDetailsFeature.ColumnName,\n]);\n\nexport function CorrelationChart({\n data,\n columnSpacing = 0,\n columnWidth = 130,\n unconformityLabels = true,\n showUnitPopover = true,\n selectedUnit,\n onUnitSelected,\n unitComponent,\n columnHeaderComponent,\n axisTopContent,\n onClickTimescaleInterval,\n timescaleIntervalStyle,\n onColumnMouseOver,\n onColumnClick,\n isTransitioning = false,\n hideLabelsWhileTransitioning = false,\n ...scaleProps\n}: CorrelationChartProps) {\n const chartData = useMemo(() => {\n if (!data) return null;\n return buildCorrelationChartData(data, {\n ...defaultCorrelationChartScaleProps,\n ...scaleProps,\n });\n }, [data, ...Object.values(scaleProps)]);\n\n // A flattened units array is used to support keyboard navigation\n const units = useMemo(() => {\n return data?.map((d0) => d0.units).flat() ?? [];\n }, [data]);\n\n const inDarkMode = useInDarkMode();\n\n const className = classNames({\n \"dark-mode\": inDarkMode,\n });\n\n if (chartData == null) {\n return null;\n }\n\n const { packages, scaleInfo, nColumns } = chartData;\n\n const mainWidth = (columnWidth + columnSpacing) * nColumns;\n\n return h(\n ColumnContainer,\n { className: \"correlation-diagram\" },\n h(\n UnitSelectionProvider,\n { selectedUnit, onUnitSelected, units },\n h(ChartArea, [\n h(TimescaleColumn, {\n scaleInfo,\n unconformityLabels,\n onClickInterval: onClickTimescaleInterval,\n intervalStyle: timescaleIntervalStyle,\n }),\n h(MainChartArea, [\n h(\n SVG,\n {\n className,\n innerWidth: mainWidth,\n height: scaleInfo.totalHeight,\n paddingH: chartPaddingH,\n },\n packages.map((pkg, i) => {\n const { offset, domain, pixelScale, scale, key } =\n scaleInfo.packages[i];\n return h(Package, {\n columnData: pkg.columnData,\n key,\n columnWidth,\n columnSpacing,\n offset,\n domain,\n pixelScale,\n scale,\n unitComponent,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n });\n }),\n ),\n h.if(showUnitPopover)(UnitSelectionPopover, {\n features: unitPopoverFeatures,\n }),\n // Navigation only works within a column for now...\n h(UnitKeyboardNavigation, { columnData: data }),\n ]),\n // Rendered last so the sticky header paints above the unit boxes\n // (positioned siblings paint in document order at the same z-index)\n h(ColumnHeaderRow, {\n data,\n columnWidth,\n columnSpacing,\n columnHeaderComponent,\n axisTopContent,\n onColumnMouseOver,\n onColumnClick,\n }),\n ]),\n ),\n );\n}\n\nfunction Package({\n columnData,\n columnSpacing,\n columnWidth,\n unitComponent,\n offset,\n domain,\n pixelScale,\n scale,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n}) {\n return h(\"g.package\", { transform: `translate(0 ${offset})` }, [\n // Disable the SVG overlay for now\n //h(PackageSVGOverlay, { columnData, columnSpacing }),\n h(\"g.column-units\", [\n columnData.map((data, i) => {\n return h(Column, {\n units: data.units,\n columnID: data.columnID,\n unitComponent,\n width: columnWidth,\n key: i,\n domain,\n pixelScale,\n scale,\n offsetLeft: i * (columnWidth + columnSpacing),\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n });\n }),\n ]),\n ]);\n}\n\ninterface ColumnProps {\n units: ExtUnit[];\n columnID?: number;\n unitComponent?: React.FunctionComponent<any>;\n unitComponentProps?: any;\n showLabels?: boolean;\n width?: number;\n columnWidth?: number;\n columnSpacing?: number;\n targetUnitHeight?: number;\n offsetLeft?: number;\n domain: [number, number];\n pixelScale: number;\n scale?: ScaleContinuousNumeric<number, number>;\n onColumnMouseOver?: (columnID: number | null) => void;\n isTransitioning?: boolean;\n hideLabelsWhileTransitioning?: boolean;\n}\n\nfunction Column(props: ColumnProps) {\n const {\n units,\n columnID,\n width = 150,\n offsetLeft,\n domain,\n pixelScale,\n scale,\n unitComponent = ColoredUnitComponent,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n } = props;\n\n const columnWidth = width;\n\n if (units.length == 0) {\n return null;\n }\n\n const hoverHandlers =\n onColumnMouseOver != null && columnID != null\n ? {\n onMouseEnter: () => onColumnMouseOver(columnID),\n onMouseLeave: () => onColumnMouseOver(null),\n }\n : {};\n\n return h(\n \"g.section\",\n {\n transform: `translate(${offsetLeft} 0)`,\n ...hoverHandlers,\n },\n h(\n ColumnProvider,\n {\n // Need to tighten up types here...\n divisions: units as any[],\n range: domain,\n scale,\n pixelsPerMeter: pixelScale, // Actually pixels per myr\n axisType: ColumnAxisType.AGE,\n isTransitioning,\n hideLabelsWhileTransitioning,\n },\n h(UnitBoxes, {\n unitComponent,\n unitComponentProps: {\n width: columnWidth,\n showLabel: true,\n },\n }),\n ),\n );\n}\n\nfunction PackageSVGOverlay({ data, columnWidth = 100, columnSpacing = 0 }) {\n const { b_age, t_age, bestPixelScale, columnData } = data;\n\n const width = (columnWidth + columnSpacing) * columnData.length;\n const height = Math.ceil((b_age - t_age) * bestPixelScale) + 2;\n\n const extensiveUnits = findLaterallyExtensiveUnits(data);\n\n const scale = (val: number) => {\n return (val - t_age) * bestPixelScale;\n };\n\n return h(\n \"div.package-overlay\",\n { style: { width, height } },\n extensiveUnits.map((d) => {\n return h(LaterallyExtensiveUnit, {\n data: d,\n scale,\n pixelScale: bestPixelScale,\n columnSpacing,\n });\n }),\n );\n}\n\nfunction LaterallyExtensiveUnit({ data, scale, pixelScale, columnSpacing }) {\n const { b_age, t_age, strat_name_long, units } = data;\n // Build boxes by column groups\n const boxes: UnitGroupBox[] = splitStratIntoBoxes(data);\n\n return h(\n \"div.laterally-extensive-unit\",\n boxes.map((d, i) => {\n return h(StratColSpan, {\n scale,\n data: d,\n pixelScale,\n key: i,\n columnSpacing,\n });\n }),\n );\n}\n\nfunction StratColSpan({\n data,\n scale,\n columnWidth = 100,\n columnSpacing = 0,\n pixelScale = 1,\n}) {\n const { startCol, endCol, strat_name_long, t_age, b_age } = data;\n const top = scale(t_age);\n const left = startCol * (columnWidth + columnSpacing);\n const width = (endCol - startCol + 1) * (columnWidth + columnSpacing);\n const height = (b_age - t_age) * pixelScale;\n return h(\n \"div.strat-col-span\",\n { style: { top, height, width, left } },\n strat_name_long,\n );\n}\n\nfunction ChartArea({ children }) {\n const setSelectedUnit = useUnitSelectionDispatch();\n\n return h(\n \"div.correlation-chart-inner\",\n {\n onClick() {\n setSelectedUnit(null, null);\n },\n },\n children,\n );\n}\n\ninterface ColumnHeaderRowProps {\n data: ColumnData[];\n columnWidth: number;\n columnSpacing: number;\n columnHeaderComponent?: React.ComponentType<ColumnHeaderProps>;\n axisTopContent?: React.ReactNode;\n onColumnMouseOver?: (columnID: number | null) => void;\n onColumnClick?: (columnID: number) => void;\n}\n\nfunction ColumnHeaderRow({\n data,\n columnWidth,\n columnSpacing,\n columnHeaderComponent,\n axisTopContent,\n onColumnMouseOver,\n onColumnClick,\n}: ColumnHeaderRowProps) {\n /** The top grid row: arbitrary content above the timescale axis (top-left)\n * and, when a header component is provided, per-column headers aligned with\n * the columns. Collapses to nothing when neither is present. */\n const Component = columnHeaderComponent;\n if (Component == null && axisTopContent == null) {\n return null;\n }\n\n return h([\n h(\"div.column-header-spacer\", axisTopContent),\n h.if(Component != null)(\n \"div.column-header-row\",\n {\n style: {\n paddingLeft: chartPaddingH,\n paddingRight: chartPaddingH,\n gap: columnSpacing,\n },\n },\n data.map((column, i) => {\n return h(ColumnHeaderCell, {\n key: column.columnID ?? i,\n column,\n columnIndex: i,\n columnWidth,\n Component: Component!,\n onColumnMouseOver,\n onColumnClick,\n });\n }),\n ),\n ]);\n}\n\nfunction ColumnHeaderCell({\n column,\n columnIndex,\n columnWidth,\n Component,\n onColumnMouseOver,\n onColumnClick,\n}: {\n column: ColumnData;\n columnIndex: number;\n columnWidth: number;\n Component: React.ComponentType<ColumnHeaderProps>;\n onColumnMouseOver?: (columnID: number | null) => void;\n onColumnClick?: (columnID: number) => void;\n}) {\n /** Resolves the column name and wires column-level hover/click so the header\n * component itself can stay a pure function of props. */\n const columnID = column.columnID;\n const info = useMacrostratColumnInfo(columnID);\n\n return h(\n \"div.column-header-cell\",\n {\n style: {\n width: columnWidth,\n minWidth: columnWidth,\n maxWidth: columnWidth,\n },\n onMouseEnter: () => onColumnMouseOver?.(columnID),\n onMouseLeave: () => onColumnMouseOver?.(null),\n onClick: onColumnClick\n ? (e: React.MouseEvent) => {\n e.stopPropagation();\n onColumnClick(columnID);\n }\n : undefined,\n },\n h(Component, {\n column,\n columnID,\n columnIndex,\n units: column.units as ExtUnit[],\n width: columnWidth,\n columnName: info?.col_name ?? null,\n }),\n );\n}\n\ninterface TimescaleColumnProps {\n scaleInfo: CompositeStratigraphicScaleInfo;\n showLabels?: boolean;\n unconformityLabels?: boolean;\n onClickInterval?: TimescaleClickHandler;\n intervalStyle?: IntervalStyleBuilder;\n}\n\nfunction TimescaleColumn(props: TimescaleColumnProps) {\n const {\n scaleInfo,\n unconformityLabels = true,\n onClickInterval,\n intervalStyle,\n } = props;\n return h(\"div.column-container.age-axis-container\", [\n h(CompositeAgeAxisCore, { ...scaleInfo }),\n h(CompositeTimescaleCore, {\n ...scaleInfo,\n unconformityLabels,\n onClickInterval,\n intervalStyle,\n }),\n ]);\n}\n"],"names":["useColumnRef","h","UnitDetailsFeature","useMemo","buildCorrelationChartData","useInDarkMode","classNames","ColumnContainer","UnitSelectionProvider","SVG","UnitSelectionPopover","UnitKeyboardNavigation","ColoredUnitComponent","ColumnProvider","ColumnAxisType","UnitBoxes","useUnitSelectionDispatch","column","useMacrostratColumnInfo","CompositeAgeAxisCore","CompositeTimescaleCore"],"mappings":"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"}
@@ -50,4 +50,4 @@ export interface CorrelationChartProps extends CorrelationChartSettings {
50
50
  /** Called when a column header is clicked (e.g. to frame it on a map) */
51
51
  onColumnClick?: (columnID: number) => void;
52
52
  }
53
- export declare function CorrelationChart({ data, columnSpacing, columnWidth, unconformityLabels, showUnitPopover, selectedUnit, onUnitSelected, unitComponent, columnHeaderComponent, axisTopContent, onClickTimescaleInterval, onColumnMouseOver, onColumnClick, isTransitioning, hideLabelsWhileTransitioning, ...scaleProps }: CorrelationChartProps): import('react').FunctionComponentElement<import('..').ColumnContainerProps> | null;
53
+ export declare function CorrelationChart({ data, columnSpacing, columnWidth, unconformityLabels, showUnitPopover, selectedUnit, onUnitSelected, unitComponent, columnHeaderComponent, axisTopContent, onClickTimescaleInterval, timescaleIntervalStyle, onColumnMouseOver, onColumnClick, isTransitioning, hideLabelsWhileTransitioning, ...scaleProps }: CorrelationChartProps): import('react').FunctionComponentElement<import('..').ColumnContainerProps> | null;
@@ -43,6 +43,7 @@ function CorrelationChart({
43
43
  columnHeaderComponent,
44
44
  axisTopContent,
45
45
  onClickTimescaleInterval,
46
+ timescaleIntervalStyle,
46
47
  onColumnMouseOver,
47
48
  onColumnClick,
48
49
  isTransitioning = false,
@@ -78,7 +79,8 @@ function CorrelationChart({
78
79
  h(TimescaleColumn, {
79
80
  scaleInfo,
80
81
  unconformityLabels,
81
- onClickInterval: onClickTimescaleInterval
82
+ onClickInterval: onClickTimescaleInterval,
83
+ intervalStyle: timescaleIntervalStyle
82
84
  }),
83
85
  h(MainChartArea, [
84
86
  h(
@@ -301,13 +303,19 @@ function ColumnHeaderCell({
301
303
  );
302
304
  }
303
305
  function TimescaleColumn(props) {
304
- const { scaleInfo, unconformityLabels = true, onClickInterval } = props;
306
+ const {
307
+ scaleInfo,
308
+ unconformityLabels = true,
309
+ onClickInterval,
310
+ intervalStyle
311
+ } = props;
305
312
  return h("div.column-container.age-axis-container", [
306
313
  h(CompositeAgeAxisCore, { ...scaleInfo }),
307
314
  h(CompositeTimescaleCore, {
308
315
  ...scaleInfo,
309
316
  unconformityLabels,
310
- onClickInterval
317
+ onClickInterval,
318
+ intervalStyle
311
319
  })
312
320
  ]);
313
321
  }
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sources":["../../src/correlation-chart/main.ts"],"sourcesContent":["/** Correlation chart */\nimport {\n UnitSelectionProvider,\n UnitKeyboardNavigation,\n useUnitSelectionDispatch,\n useColumnRef,\n} from \"../data-provider\";\nimport { UnitDetailsFeature, UnitSelectionPopover } from \"../unit-details\";\nimport h from \"./main.module.sass\";\nimport { useMemo } from \"react\";\nimport { useInDarkMode } from \"@macrostrat/ui-components\";\nimport { CompositeTimescaleCore } from \"../section\";\nimport classNames from \"classnames\";\nimport {\n findLaterallyExtensiveUnits,\n splitStratIntoBoxes,\n UnitGroupBox,\n CorrelationChartSettings,\n buildCorrelationChartData,\n} from \"./prepare-data\";\nimport {\n CompositeAgeAxisCore,\n CompositeStratigraphicScaleInfo,\n} from \"../age-axis\";\nimport {\n ColumnAxisType,\n ColumnProvider,\n SVG,\n} from \"@macrostrat/column-components\";\nimport { ColoredUnitComponent } from \"../units\";\nimport { UnitBoxes } from \"../units/boxes\";\nimport { ColumnContainer } from \"../column\";\nimport type { ColumnData } from \"@macrostrat/data-provider\";\nimport { BaseUnit } from \"@macrostrat/api-types\";\nimport { ScaleContinuousNumeric } from \"d3-scale\";\nimport { ExtUnit } from \"../prepare-units/types\";\nimport type { TimescaleClickHandler } from \"@macrostrat/timescale\";\nimport { useMacrostratColumnInfo } from \"@macrostrat/data-provider\";\n\n/** Standard props passed to a `columnHeaderComponent`. Everything a header\n * needs is provided here, so the component can be a pure function of its props\n * (no hooks required). Column-level hover/click are handled by the chart. */\nexport interface ColumnHeaderProps {\n /** The column data (units + identifier) for this column */\n column: ColumnData;\n /** The column's ID (col_id) */\n columnID: number;\n /** The column's index (left-to-right) within the chart */\n columnIndex: number;\n /** The units within the column */\n units: ExtUnit[];\n /** The rendered width of the column, in pixels */\n width: number;\n /** The column's name, resolved from column metadata when available */\n columnName?: string | null;\n}\n\nexport interface CorrelationChartProps extends CorrelationChartSettings {\n data: ColumnData[];\n columnWidth?: number;\n columnSpacing?: number;\n targetUnitHeight?: number;\n unconformityLabels?: boolean;\n selectedUnit?: number | null;\n showUnitPopover?: boolean;\n unitComponent?: any;\n /** Arbitrary content (e.g. column title and/or ID) rendered above each column */\n columnHeaderComponent?: React.ComponentType<ColumnHeaderProps>;\n /** Content rendered above the timescale axis (top-left), beside the column\n * headers — e.g. a zoom/filter indicator. */\n axisTopContent?: React.ReactNode;\n /** True while the age window is animating (e.g. from `useAnimatedAgeWindow`).\n * Propagated to each column's context so per-frame label/pattern work can be\n * skipped during the transition. */\n isTransitioning?: boolean;\n /** Hide unit labels while transitioning (perf escape hatch; default false —\n * labels stay visible through the animation). */\n hideLabelsWhileTransitioning?: boolean;\n onUnitSelected?: (unitID: number | null, unit: BaseUnit | null) => void;\n /** Called when a timescale interval is clicked (e.g. to zoom the age range) */\n onClickTimescaleInterval?: TimescaleClickHandler;\n /** Called when the pointer enters/leaves a column (header or body). Passes\n * the column ID, or null on leave. */\n onColumnMouseOver?: (columnID: number | null) => void;\n /** Called when a column header is clicked (e.g. to frame it on a map) */\n onColumnClick?: (columnID: number) => void;\n}\n\n/** Horizontal padding of the main chart SVG. Column headers are aligned to\n * this so that they line up with the columns beneath them. */\nconst chartPaddingH = 4;\n\n/** Default scale settings for the correlation chart. */\nconst defaultCorrelationChartScaleProps = {\n targetUnitHeight: 10,\n unconformityHeight: 60,\n minSectionHeight: 60,\n collapseSmallUnconformities: true,\n};\n\nfunction MainChartArea({ children }) {\n const columnRef = useColumnRef();\n return h(\"div.main-chart\", { ref: columnRef }, children);\n}\n\nconst unitPopoverFeatures = new Set([\n UnitDetailsFeature.AdjacentUnits,\n UnitDetailsFeature.OutcropType,\n UnitDetailsFeature.DepthRange,\n UnitDetailsFeature.ColumnName,\n]);\n\nexport function CorrelationChart({\n data,\n columnSpacing = 0,\n columnWidth = 130,\n unconformityLabels = true,\n showUnitPopover = true,\n selectedUnit,\n onUnitSelected,\n unitComponent,\n columnHeaderComponent,\n axisTopContent,\n onClickTimescaleInterval,\n onColumnMouseOver,\n onColumnClick,\n isTransitioning = false,\n hideLabelsWhileTransitioning = false,\n ...scaleProps\n}: CorrelationChartProps) {\n const chartData = useMemo(() => {\n if (!data) return null;\n return buildCorrelationChartData(data, {\n ...defaultCorrelationChartScaleProps,\n ...scaleProps,\n });\n }, [data, ...Object.values(scaleProps)]);\n\n // A flattened units array is used to support keyboard navigation\n const units = useMemo(() => {\n return data?.map((d0) => d0.units).flat() ?? [];\n }, [data]);\n\n const inDarkMode = useInDarkMode();\n\n const className = classNames({\n \"dark-mode\": inDarkMode,\n });\n\n if (chartData == null) {\n return null;\n }\n\n const { packages, scaleInfo, nColumns } = chartData;\n\n const mainWidth = (columnWidth + columnSpacing) * nColumns;\n\n return h(\n ColumnContainer,\n { className: \"correlation-diagram\" },\n h(\n UnitSelectionProvider,\n { selectedUnit, onUnitSelected, units },\n h(ChartArea, [\n h(TimescaleColumn, {\n scaleInfo,\n unconformityLabels,\n onClickInterval: onClickTimescaleInterval,\n }),\n h(MainChartArea, [\n h(\n SVG,\n {\n className,\n innerWidth: mainWidth,\n height: scaleInfo.totalHeight,\n paddingH: chartPaddingH,\n },\n packages.map((pkg, i) => {\n const { offset, domain, pixelScale, scale, key } =\n scaleInfo.packages[i];\n return h(Package, {\n columnData: pkg.columnData,\n key,\n columnWidth,\n columnSpacing,\n offset,\n domain,\n pixelScale,\n scale,\n unitComponent,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n });\n }),\n ),\n h.if(showUnitPopover)(UnitSelectionPopover, {\n features: unitPopoverFeatures,\n }),\n // Navigation only works within a column for now...\n h(UnitKeyboardNavigation, { columnData: data }),\n ]),\n // Rendered last so the sticky header paints above the unit boxes\n // (positioned siblings paint in document order at the same z-index)\n h(ColumnHeaderRow, {\n data,\n columnWidth,\n columnSpacing,\n columnHeaderComponent,\n axisTopContent,\n onColumnMouseOver,\n onColumnClick,\n }),\n ]),\n ),\n );\n}\n\nfunction Package({\n columnData,\n columnSpacing,\n columnWidth,\n unitComponent,\n offset,\n domain,\n pixelScale,\n scale,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n}) {\n return h(\"g.package\", { transform: `translate(0 ${offset})` }, [\n // Disable the SVG overlay for now\n //h(PackageSVGOverlay, { columnData, columnSpacing }),\n h(\"g.column-units\", [\n columnData.map((data, i) => {\n return h(Column, {\n units: data.units,\n columnID: data.columnID,\n unitComponent,\n width: columnWidth,\n key: i,\n domain,\n pixelScale,\n scale,\n offsetLeft: i * (columnWidth + columnSpacing),\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n });\n }),\n ]),\n ]);\n}\n\ninterface ColumnProps {\n units: ExtUnit[];\n columnID?: number;\n unitComponent?: React.FunctionComponent<any>;\n unitComponentProps?: any;\n showLabels?: boolean;\n width?: number;\n columnWidth?: number;\n columnSpacing?: number;\n targetUnitHeight?: number;\n offsetLeft?: number;\n domain: [number, number];\n pixelScale: number;\n scale?: ScaleContinuousNumeric<number, number>;\n onColumnMouseOver?: (columnID: number | null) => void;\n isTransitioning?: boolean;\n hideLabelsWhileTransitioning?: boolean;\n}\n\nfunction Column(props: ColumnProps) {\n const {\n units,\n columnID,\n width = 150,\n offsetLeft,\n domain,\n pixelScale,\n scale,\n unitComponent = ColoredUnitComponent,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n } = props;\n\n const columnWidth = width;\n\n if (units.length == 0) {\n return null;\n }\n\n const hoverHandlers =\n onColumnMouseOver != null && columnID != null\n ? {\n onMouseEnter: () => onColumnMouseOver(columnID),\n onMouseLeave: () => onColumnMouseOver(null),\n }\n : {};\n\n return h(\n \"g.section\",\n {\n transform: `translate(${offsetLeft} 0)`,\n ...hoverHandlers,\n },\n h(\n ColumnProvider,\n {\n // Need to tighten up types here...\n divisions: units as any[],\n range: domain,\n scale,\n pixelsPerMeter: pixelScale, // Actually pixels per myr\n axisType: ColumnAxisType.AGE,\n isTransitioning,\n hideLabelsWhileTransitioning,\n },\n h(UnitBoxes, {\n unitComponent,\n unitComponentProps: {\n width: columnWidth,\n showLabel: true,\n },\n }),\n ),\n );\n}\n\nfunction PackageSVGOverlay({ data, columnWidth = 100, columnSpacing = 0 }) {\n const { b_age, t_age, bestPixelScale, columnData } = data;\n\n const width = (columnWidth + columnSpacing) * columnData.length;\n const height = Math.ceil((b_age - t_age) * bestPixelScale) + 2;\n\n const extensiveUnits = findLaterallyExtensiveUnits(data);\n\n const scale = (val: number) => {\n return (val - t_age) * bestPixelScale;\n };\n\n return h(\n \"div.package-overlay\",\n { style: { width, height } },\n extensiveUnits.map((d) => {\n return h(LaterallyExtensiveUnit, {\n data: d,\n scale,\n pixelScale: bestPixelScale,\n columnSpacing,\n });\n }),\n );\n}\n\nfunction LaterallyExtensiveUnit({ data, scale, pixelScale, columnSpacing }) {\n const { b_age, t_age, strat_name_long, units } = data;\n // Build boxes by column groups\n const boxes: UnitGroupBox[] = splitStratIntoBoxes(data);\n\n return h(\n \"div.laterally-extensive-unit\",\n boxes.map((d, i) => {\n return h(StratColSpan, {\n scale,\n data: d,\n pixelScale,\n key: i,\n columnSpacing,\n });\n }),\n );\n}\n\nfunction StratColSpan({\n data,\n scale,\n columnWidth = 100,\n columnSpacing = 0,\n pixelScale = 1,\n}) {\n const { startCol, endCol, strat_name_long, t_age, b_age } = data;\n const top = scale(t_age);\n const left = startCol * (columnWidth + columnSpacing);\n const width = (endCol - startCol + 1) * (columnWidth + columnSpacing);\n const height = (b_age - t_age) * pixelScale;\n return h(\n \"div.strat-col-span\",\n { style: { top, height, width, left } },\n strat_name_long,\n );\n}\n\nfunction ChartArea({ children }) {\n const setSelectedUnit = useUnitSelectionDispatch();\n\n return h(\n \"div.correlation-chart-inner\",\n {\n onClick() {\n setSelectedUnit(null, null);\n },\n },\n children,\n );\n}\n\ninterface ColumnHeaderRowProps {\n data: ColumnData[];\n columnWidth: number;\n columnSpacing: number;\n columnHeaderComponent?: React.ComponentType<ColumnHeaderProps>;\n axisTopContent?: React.ReactNode;\n onColumnMouseOver?: (columnID: number | null) => void;\n onColumnClick?: (columnID: number) => void;\n}\n\nfunction ColumnHeaderRow({\n data,\n columnWidth,\n columnSpacing,\n columnHeaderComponent,\n axisTopContent,\n onColumnMouseOver,\n onColumnClick,\n}: ColumnHeaderRowProps) {\n /** The top grid row: arbitrary content above the timescale axis (top-left)\n * and, when a header component is provided, per-column headers aligned with\n * the columns. Collapses to nothing when neither is present. */\n const Component = columnHeaderComponent;\n if (Component == null && axisTopContent == null) {\n return null;\n }\n\n return h([\n h(\"div.column-header-spacer\", axisTopContent),\n h.if(Component != null)(\n \"div.column-header-row\",\n {\n style: {\n paddingLeft: chartPaddingH,\n paddingRight: chartPaddingH,\n gap: columnSpacing,\n },\n },\n data.map((column, i) => {\n return h(ColumnHeaderCell, {\n key: column.columnID ?? i,\n column,\n columnIndex: i,\n columnWidth,\n Component: Component!,\n onColumnMouseOver,\n onColumnClick,\n });\n }),\n ),\n ]);\n}\n\nfunction ColumnHeaderCell({\n column,\n columnIndex,\n columnWidth,\n Component,\n onColumnMouseOver,\n onColumnClick,\n}: {\n column: ColumnData;\n columnIndex: number;\n columnWidth: number;\n Component: React.ComponentType<ColumnHeaderProps>;\n onColumnMouseOver?: (columnID: number | null) => void;\n onColumnClick?: (columnID: number) => void;\n}) {\n /** Resolves the column name and wires column-level hover/click so the header\n * component itself can stay a pure function of props. */\n const columnID = column.columnID;\n const info = useMacrostratColumnInfo(columnID);\n\n return h(\n \"div.column-header-cell\",\n {\n style: {\n width: columnWidth,\n minWidth: columnWidth,\n maxWidth: columnWidth,\n },\n onMouseEnter: () => onColumnMouseOver?.(columnID),\n onMouseLeave: () => onColumnMouseOver?.(null),\n onClick: onColumnClick\n ? (e: React.MouseEvent) => {\n e.stopPropagation();\n onColumnClick(columnID);\n }\n : undefined,\n },\n h(Component, {\n column,\n columnID,\n columnIndex,\n units: column.units as ExtUnit[],\n width: columnWidth,\n columnName: info?.col_name ?? null,\n }),\n );\n}\n\ninterface TimescaleColumnProps {\n scaleInfo: CompositeStratigraphicScaleInfo;\n showLabels?: boolean;\n unconformityLabels?: boolean;\n onClickInterval?: TimescaleClickHandler;\n}\n\nfunction TimescaleColumn(props: TimescaleColumnProps) {\n const { scaleInfo, unconformityLabels = true, onClickInterval } = props;\n return h(\"div.column-container.age-axis-container\", [\n h(CompositeAgeAxisCore, { ...scaleInfo }),\n h(CompositeTimescaleCore, {\n ...scaleInfo,\n unconformityLabels,\n onClickInterval,\n }),\n ]);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.js","sources":["../../src/correlation-chart/main.ts"],"sourcesContent":["/** Correlation chart */\nimport {\n UnitSelectionProvider,\n UnitKeyboardNavigation,\n useUnitSelectionDispatch,\n useColumnRef,\n} from \"../data-provider\";\nimport { UnitDetailsFeature, UnitSelectionPopover } from \"../unit-details\";\nimport h from \"./main.module.sass\";\nimport { useMemo } from \"react\";\nimport { useInDarkMode } from \"@macrostrat/ui-components\";\nimport { CompositeTimescaleCore } from \"../section\";\nimport classNames from \"classnames\";\nimport {\n findLaterallyExtensiveUnits,\n splitStratIntoBoxes,\n UnitGroupBox,\n CorrelationChartSettings,\n buildCorrelationChartData,\n} from \"./prepare-data\";\nimport {\n CompositeAgeAxisCore,\n CompositeStratigraphicScaleInfo,\n} from \"../age-axis\";\nimport {\n ColumnAxisType,\n ColumnProvider,\n SVG,\n} from \"@macrostrat/column-components\";\nimport { ColoredUnitComponent } from \"../units\";\nimport { UnitBoxes } from \"../units/boxes\";\nimport { ColumnContainer } from \"../column\";\nimport type { ColumnData } from \"@macrostrat/data-provider\";\nimport { BaseUnit } from \"@macrostrat/api-types\";\nimport { ScaleContinuousNumeric } from \"d3-scale\";\nimport { ExtUnit } from \"../prepare-units/types\";\nimport type {\n IntervalStyleBuilder,\n TimescaleClickHandler,\n} from \"@macrostrat/timescale\";\nimport { useMacrostratColumnInfo } from \"@macrostrat/data-provider\";\n\n/** Standard props passed to a `columnHeaderComponent`. Everything a header\n * needs is provided here, so the component can be a pure function of its props\n * (no hooks required). Column-level hover/click are handled by the chart. */\nexport interface ColumnHeaderProps {\n /** The column data (units + identifier) for this column */\n column: ColumnData;\n /** The column's ID (col_id) */\n columnID: number;\n /** The column's index (left-to-right) within the chart */\n columnIndex: number;\n /** The units within the column */\n units: ExtUnit[];\n /** The rendered width of the column, in pixels */\n width: number;\n /** The column's name, resolved from column metadata when available */\n columnName?: string | null;\n}\n\nexport interface CorrelationChartProps extends CorrelationChartSettings {\n data: ColumnData[];\n columnWidth?: number;\n columnSpacing?: number;\n targetUnitHeight?: number;\n unconformityLabels?: boolean;\n selectedUnit?: number | null;\n showUnitPopover?: boolean;\n unitComponent?: any;\n /** Arbitrary content (e.g. column title and/or ID) rendered above each column */\n columnHeaderComponent?: React.ComponentType<ColumnHeaderProps>;\n /** Content rendered above the timescale axis (top-left), beside the column\n * headers — e.g. a zoom/filter indicator. */\n axisTopContent?: React.ReactNode;\n /** True while the age window is animating (e.g. from `useAnimatedAgeWindow`).\n * Propagated to each column's context so per-frame label/pattern work can be\n * skipped during the transition. */\n isTransitioning?: boolean;\n /** Hide unit labels while transitioning (perf escape hatch; default false —\n * labels stay visible through the animation). */\n hideLabelsWhileTransitioning?: boolean;\n onUnitSelected?: (unitID: number | null, unit: BaseUnit | null) => void;\n /** Called when a timescale interval is clicked (e.g. to zoom the age range) */\n onClickTimescaleInterval?: TimescaleClickHandler;\n /** Called when the pointer enters/leaves a column (header or body). Passes\n * the column ID, or null on leave. */\n onColumnMouseOver?: (columnID: number | null) => void;\n /** Called when a column header is clicked (e.g. to frame it on a map) */\n onColumnClick?: (columnID: number) => void;\n}\n\n/** Horizontal padding of the main chart SVG. Column headers are aligned to\n * this so that they line up with the columns beneath them. */\nconst chartPaddingH = 4;\n\n/** Default scale settings for the correlation chart. */\nconst defaultCorrelationChartScaleProps = {\n targetUnitHeight: 10,\n unconformityHeight: 60,\n minSectionHeight: 60,\n collapseSmallUnconformities: true,\n};\n\nfunction MainChartArea({ children }) {\n const columnRef = useColumnRef();\n return h(\"div.main-chart\", { ref: columnRef }, children);\n}\n\nconst unitPopoverFeatures = new Set([\n UnitDetailsFeature.AdjacentUnits,\n UnitDetailsFeature.OutcropType,\n UnitDetailsFeature.DepthRange,\n UnitDetailsFeature.ColumnName,\n]);\n\nexport function CorrelationChart({\n data,\n columnSpacing = 0,\n columnWidth = 130,\n unconformityLabels = true,\n showUnitPopover = true,\n selectedUnit,\n onUnitSelected,\n unitComponent,\n columnHeaderComponent,\n axisTopContent,\n onClickTimescaleInterval,\n timescaleIntervalStyle,\n onColumnMouseOver,\n onColumnClick,\n isTransitioning = false,\n hideLabelsWhileTransitioning = false,\n ...scaleProps\n}: CorrelationChartProps) {\n const chartData = useMemo(() => {\n if (!data) return null;\n return buildCorrelationChartData(data, {\n ...defaultCorrelationChartScaleProps,\n ...scaleProps,\n });\n }, [data, ...Object.values(scaleProps)]);\n\n // A flattened units array is used to support keyboard navigation\n const units = useMemo(() => {\n return data?.map((d0) => d0.units).flat() ?? [];\n }, [data]);\n\n const inDarkMode = useInDarkMode();\n\n const className = classNames({\n \"dark-mode\": inDarkMode,\n });\n\n if (chartData == null) {\n return null;\n }\n\n const { packages, scaleInfo, nColumns } = chartData;\n\n const mainWidth = (columnWidth + columnSpacing) * nColumns;\n\n return h(\n ColumnContainer,\n { className: \"correlation-diagram\" },\n h(\n UnitSelectionProvider,\n { selectedUnit, onUnitSelected, units },\n h(ChartArea, [\n h(TimescaleColumn, {\n scaleInfo,\n unconformityLabels,\n onClickInterval: onClickTimescaleInterval,\n intervalStyle: timescaleIntervalStyle,\n }),\n h(MainChartArea, [\n h(\n SVG,\n {\n className,\n innerWidth: mainWidth,\n height: scaleInfo.totalHeight,\n paddingH: chartPaddingH,\n },\n packages.map((pkg, i) => {\n const { offset, domain, pixelScale, scale, key } =\n scaleInfo.packages[i];\n return h(Package, {\n columnData: pkg.columnData,\n key,\n columnWidth,\n columnSpacing,\n offset,\n domain,\n pixelScale,\n scale,\n unitComponent,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n });\n }),\n ),\n h.if(showUnitPopover)(UnitSelectionPopover, {\n features: unitPopoverFeatures,\n }),\n // Navigation only works within a column for now...\n h(UnitKeyboardNavigation, { columnData: data }),\n ]),\n // Rendered last so the sticky header paints above the unit boxes\n // (positioned siblings paint in document order at the same z-index)\n h(ColumnHeaderRow, {\n data,\n columnWidth,\n columnSpacing,\n columnHeaderComponent,\n axisTopContent,\n onColumnMouseOver,\n onColumnClick,\n }),\n ]),\n ),\n );\n}\n\nfunction Package({\n columnData,\n columnSpacing,\n columnWidth,\n unitComponent,\n offset,\n domain,\n pixelScale,\n scale,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n}) {\n return h(\"g.package\", { transform: `translate(0 ${offset})` }, [\n // Disable the SVG overlay for now\n //h(PackageSVGOverlay, { columnData, columnSpacing }),\n h(\"g.column-units\", [\n columnData.map((data, i) => {\n return h(Column, {\n units: data.units,\n columnID: data.columnID,\n unitComponent,\n width: columnWidth,\n key: i,\n domain,\n pixelScale,\n scale,\n offsetLeft: i * (columnWidth + columnSpacing),\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n });\n }),\n ]),\n ]);\n}\n\ninterface ColumnProps {\n units: ExtUnit[];\n columnID?: number;\n unitComponent?: React.FunctionComponent<any>;\n unitComponentProps?: any;\n showLabels?: boolean;\n width?: number;\n columnWidth?: number;\n columnSpacing?: number;\n targetUnitHeight?: number;\n offsetLeft?: number;\n domain: [number, number];\n pixelScale: number;\n scale?: ScaleContinuousNumeric<number, number>;\n onColumnMouseOver?: (columnID: number | null) => void;\n isTransitioning?: boolean;\n hideLabelsWhileTransitioning?: boolean;\n}\n\nfunction Column(props: ColumnProps) {\n const {\n units,\n columnID,\n width = 150,\n offsetLeft,\n domain,\n pixelScale,\n scale,\n unitComponent = ColoredUnitComponent,\n onColumnMouseOver,\n isTransitioning,\n hideLabelsWhileTransitioning,\n } = props;\n\n const columnWidth = width;\n\n if (units.length == 0) {\n return null;\n }\n\n const hoverHandlers =\n onColumnMouseOver != null && columnID != null\n ? {\n onMouseEnter: () => onColumnMouseOver(columnID),\n onMouseLeave: () => onColumnMouseOver(null),\n }\n : {};\n\n return h(\n \"g.section\",\n {\n transform: `translate(${offsetLeft} 0)`,\n ...hoverHandlers,\n },\n h(\n ColumnProvider,\n {\n // Need to tighten up types here...\n divisions: units as any[],\n range: domain,\n scale,\n pixelsPerMeter: pixelScale, // Actually pixels per myr\n axisType: ColumnAxisType.AGE,\n isTransitioning,\n hideLabelsWhileTransitioning,\n },\n h(UnitBoxes, {\n unitComponent,\n unitComponentProps: {\n width: columnWidth,\n showLabel: true,\n },\n }),\n ),\n );\n}\n\nfunction PackageSVGOverlay({ data, columnWidth = 100, columnSpacing = 0 }) {\n const { b_age, t_age, bestPixelScale, columnData } = data;\n\n const width = (columnWidth + columnSpacing) * columnData.length;\n const height = Math.ceil((b_age - t_age) * bestPixelScale) + 2;\n\n const extensiveUnits = findLaterallyExtensiveUnits(data);\n\n const scale = (val: number) => {\n return (val - t_age) * bestPixelScale;\n };\n\n return h(\n \"div.package-overlay\",\n { style: { width, height } },\n extensiveUnits.map((d) => {\n return h(LaterallyExtensiveUnit, {\n data: d,\n scale,\n pixelScale: bestPixelScale,\n columnSpacing,\n });\n }),\n );\n}\n\nfunction LaterallyExtensiveUnit({ data, scale, pixelScale, columnSpacing }) {\n const { b_age, t_age, strat_name_long, units } = data;\n // Build boxes by column groups\n const boxes: UnitGroupBox[] = splitStratIntoBoxes(data);\n\n return h(\n \"div.laterally-extensive-unit\",\n boxes.map((d, i) => {\n return h(StratColSpan, {\n scale,\n data: d,\n pixelScale,\n key: i,\n columnSpacing,\n });\n }),\n );\n}\n\nfunction StratColSpan({\n data,\n scale,\n columnWidth = 100,\n columnSpacing = 0,\n pixelScale = 1,\n}) {\n const { startCol, endCol, strat_name_long, t_age, b_age } = data;\n const top = scale(t_age);\n const left = startCol * (columnWidth + columnSpacing);\n const width = (endCol - startCol + 1) * (columnWidth + columnSpacing);\n const height = (b_age - t_age) * pixelScale;\n return h(\n \"div.strat-col-span\",\n { style: { top, height, width, left } },\n strat_name_long,\n );\n}\n\nfunction ChartArea({ children }) {\n const setSelectedUnit = useUnitSelectionDispatch();\n\n return h(\n \"div.correlation-chart-inner\",\n {\n onClick() {\n setSelectedUnit(null, null);\n },\n },\n children,\n );\n}\n\ninterface ColumnHeaderRowProps {\n data: ColumnData[];\n columnWidth: number;\n columnSpacing: number;\n columnHeaderComponent?: React.ComponentType<ColumnHeaderProps>;\n axisTopContent?: React.ReactNode;\n onColumnMouseOver?: (columnID: number | null) => void;\n onColumnClick?: (columnID: number) => void;\n}\n\nfunction ColumnHeaderRow({\n data,\n columnWidth,\n columnSpacing,\n columnHeaderComponent,\n axisTopContent,\n onColumnMouseOver,\n onColumnClick,\n}: ColumnHeaderRowProps) {\n /** The top grid row: arbitrary content above the timescale axis (top-left)\n * and, when a header component is provided, per-column headers aligned with\n * the columns. Collapses to nothing when neither is present. */\n const Component = columnHeaderComponent;\n if (Component == null && axisTopContent == null) {\n return null;\n }\n\n return h([\n h(\"div.column-header-spacer\", axisTopContent),\n h.if(Component != null)(\n \"div.column-header-row\",\n {\n style: {\n paddingLeft: chartPaddingH,\n paddingRight: chartPaddingH,\n gap: columnSpacing,\n },\n },\n data.map((column, i) => {\n return h(ColumnHeaderCell, {\n key: column.columnID ?? i,\n column,\n columnIndex: i,\n columnWidth,\n Component: Component!,\n onColumnMouseOver,\n onColumnClick,\n });\n }),\n ),\n ]);\n}\n\nfunction ColumnHeaderCell({\n column,\n columnIndex,\n columnWidth,\n Component,\n onColumnMouseOver,\n onColumnClick,\n}: {\n column: ColumnData;\n columnIndex: number;\n columnWidth: number;\n Component: React.ComponentType<ColumnHeaderProps>;\n onColumnMouseOver?: (columnID: number | null) => void;\n onColumnClick?: (columnID: number) => void;\n}) {\n /** Resolves the column name and wires column-level hover/click so the header\n * component itself can stay a pure function of props. */\n const columnID = column.columnID;\n const info = useMacrostratColumnInfo(columnID);\n\n return h(\n \"div.column-header-cell\",\n {\n style: {\n width: columnWidth,\n minWidth: columnWidth,\n maxWidth: columnWidth,\n },\n onMouseEnter: () => onColumnMouseOver?.(columnID),\n onMouseLeave: () => onColumnMouseOver?.(null),\n onClick: onColumnClick\n ? (e: React.MouseEvent) => {\n e.stopPropagation();\n onColumnClick(columnID);\n }\n : undefined,\n },\n h(Component, {\n column,\n columnID,\n columnIndex,\n units: column.units as ExtUnit[],\n width: columnWidth,\n columnName: info?.col_name ?? null,\n }),\n );\n}\n\ninterface TimescaleColumnProps {\n scaleInfo: CompositeStratigraphicScaleInfo;\n showLabels?: boolean;\n unconformityLabels?: boolean;\n onClickInterval?: TimescaleClickHandler;\n intervalStyle?: IntervalStyleBuilder;\n}\n\nfunction TimescaleColumn(props: TimescaleColumnProps) {\n const {\n scaleInfo,\n unconformityLabels = true,\n onClickInterval,\n intervalStyle,\n } = props;\n return h(\"div.column-container.age-axis-container\", [\n h(CompositeAgeAxisCore, { ...scaleInfo }),\n h(CompositeTimescaleCore, {\n ...scaleInfo,\n unconformityLabels,\n onClickInterval,\n intervalStyle,\n }),\n ]);\n}\n"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -9,6 +9,7 @@ const ageModelOverlay = require("./age-model-overlay.cjs");
9
9
  const notes = require("./notes.cjs");
10
10
  const mapViews = require("@macrostrat/map-views");
11
11
  const ageRange = require("./unit-details/age-range.cjs");
12
+ const ageWindowTag = require("./unit-details/age-window-tag.cjs");
12
13
  const composite = require("./units/composite.cjs");
13
14
  const base = require("./facets/measurements/base.cjs");
14
15
  const dataComponents = require("@macrostrat/data-components");
@@ -60,6 +61,8 @@ exports.Duration = ageRange.Duration;
60
61
  exports.IntervalProportions = ageRange.IntervalProportions;
61
62
  exports.getAge = ageRange.getAge;
62
63
  exports.getAgeRange = ageRange.getAgeRange;
64
+ exports.AgeWindowTag = ageWindowTag.AgeWindowTag;
65
+ exports.intervalShortFromTimescale = ageWindowTag.intervalShortFromTimescale;
63
66
  exports.AnnotatedUnitsColumn = composite.AnnotatedUnitsColumn;
64
67
  exports.ColumnNotesProvider = composite.ColumnNotesProvider;
65
68
  exports.CompositeUnitComponent = composite.CompositeUnitComponent;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { BoundaryAgeModelOverlay, ComputedSurfacesOverlay } from "./age-model-ov
7
7
  import { ColumnNotes } from "./notes.js";
8
8
  export * from "@macrostrat/map-views";
9
9
  import { AgeField, AgeLabel, AgeRange, Duration, IntervalProportions, getAge, getAgeRange } from "./unit-details/age-range.js";
10
+ import { AgeWindowTag, intervalShortFromTimescale } from "./unit-details/age-window-tag.js";
10
11
  import { AnnotatedUnitsColumn, ColumnNotesProvider, CompositeUnitComponent, CompositeUnitsColumn, LabelTrackerProvider, SectionLabelsColumn, TrackedLabeledUnit } from "./units/composite.js";
11
12
  import { BaseMeasurementsColumn, TruncatedList, groupNotesByPixelDistance, mergeHeightRanges, standardizeMeasurementHeight } from "./facets/measurements/base.js";
12
13
  import { ClickableText, Identifier, UnitIdentifier } from "@macrostrat/data-components";
@@ -34,6 +35,7 @@ export {
34
35
  AgeField,
35
36
  AgeLabel,
36
37
  AgeRange,
38
+ AgeWindowTag,
37
39
  AnnotatedUnitsColumn,
38
40
  ApproximateHeightAxis,
39
41
  BaseMeasurementsColumn,
@@ -107,6 +109,7 @@ export {
107
109
  getPositionWithinUnit,
108
110
  getUnitHeightRange,
109
111
  groupNotesByPixelDistance,
112
+ intervalShortFromTimescale,
110
113
  mergeHeightRanges,
111
114
  parseLineFromString,
112
115
  prepareColumnUnits,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const core = require("@blueprintjs/core");
4
+ const dataComponents = require("@macrostrat/data-components");
5
+ const classNames = require("classnames");
6
+ const ageWindowTag_module = require("./age-window-tag.module.sass.cjs");
7
+ const ageRange = require("./age-range.cjs");
8
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
9
+ const classNames__default = /* @__PURE__ */ _interopDefault(classNames);
10
+ function intervalShortFromTimescale(interval) {
11
+ return {
12
+ id: interval.int_id ?? interval.oid,
13
+ name: interval.nam,
14
+ color: interval.col,
15
+ b_age: interval.eag,
16
+ t_age: interval.lag,
17
+ rank: interval.lvl
18
+ };
19
+ }
20
+ function AgeWindowTag(props) {
21
+ const {
22
+ interval = null,
23
+ window = null,
24
+ onClear,
25
+ size = dataComponents.TagSize.Small,
26
+ className
27
+ } = props;
28
+ if (interval == null && window == null) return null;
29
+ let tag = null;
30
+ if (interval == null) {
31
+ tag = ageWindowTag_module(dataComponents.Tag, { size, name: ageWindowTag_module(ageRange.AgeRange, { data: window }) });
32
+ } else if (window == null || windowMatchesInterval(window, interval)) {
33
+ tag = ageWindowTag_module(dataComponents.IntervalTag, { interval, showAgeRange: true, size });
34
+ } else {
35
+ tag = ageWindowTag_module(RefinedIntervalTag, { interval, window, size });
36
+ }
37
+ return ageWindowTag_module("span.age-window-tag", { className: classNames__default.default(className) }, [
38
+ tag,
39
+ ageWindowTag_module.if(onClear != null)(core.Button, {
40
+ className: "clear-button",
41
+ icon: "cross",
42
+ minimal: true,
43
+ small: true,
44
+ title: "Clear",
45
+ onClick: onClear
46
+ })
47
+ ]);
48
+ }
49
+ function RefinedIntervalTag({
50
+ interval,
51
+ window,
52
+ size
53
+ }) {
54
+ const interaction = dataComponents.useInteractionProps({ int_id: interval.id });
55
+ return ageWindowTag_module(dataComponents.Tag, {
56
+ ...interaction,
57
+ size,
58
+ name: interval.name,
59
+ color: interval.color,
60
+ details: ageWindowTag_module(ageRange.AgeRange, { data: window })
61
+ });
62
+ }
63
+ function windowMatchesInterval(window, interval) {
64
+ return window.t_age === interval.t_age && window.b_age === interval.b_age;
65
+ }
66
+ exports.AgeWindowTag = AgeWindowTag;
67
+ exports.intervalShortFromTimescale = intervalShortFromTimescale;
68
+ //# sourceMappingURL=age-window-tag.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age-window-tag.cjs","sources":["../../src/unit-details/age-window-tag.ts"],"sourcesContent":["import { Button } from \"@blueprintjs/core\";\nimport {\n IntervalShort,\n IntervalTag,\n Tag,\n TagSize,\n useInteractionProps,\n} from \"@macrostrat/data-components\";\nimport type { Interval } from \"@macrostrat/timescale\";\nimport classNames from \"classnames\";\nimport h from \"./age-window-tag.module.sass\";\nimport { AgeRange } from \"./age-range\";\nimport type { AgeWindow } from \"../animated-age-window\";\n\n/** Adapt a timescale `Interval` (as delivered by a `Timescale` click, with\n * `eag`/`lag`/`nam`/`col`) to the `IntervalShort` shape the tag components\n * take. The id prefers the Macrostrat `int_id` when the interval tree was built\n * from the API, falling back to the interval's own `oid`. */\nexport function intervalShortFromTimescale(interval: Interval): IntervalShort {\n return {\n id: interval.int_id ?? interval.oid,\n name: interval.nam,\n color: interval.col,\n b_age: interval.eag,\n t_age: interval.lag,\n rank: interval.lvl,\n };\n}\n\nexport interface AgeWindowTagProps {\n /** The interval the window was set from, if any. */\n interval?: IntervalShort | null;\n /** The rendered window. When it differs from the interval's own span (a\n * refined window) it is shown in place of the interval's range. */\n window?: AgeWindow | null;\n /** Renders a clear button when given. */\n onClear?: () => void;\n size?: TagSize;\n className?: string;\n}\n\n/** A compact, clearable indicator of the age window a column or correlation\n * chart is focused on — an interval tag (linking to the interval, with its\n * range) or a bare age range. Renders nothing without an interval or window,\n * so it can live permanently in a chart's `axisTopContent`. */\nexport function AgeWindowTag(props: AgeWindowTagProps) {\n const {\n interval = null,\n window = null,\n onClear,\n size = TagSize.Small,\n className,\n } = props;\n if (interval == null && window == null) return null;\n\n let tag = null;\n if (interval == null) {\n tag = h(Tag, { size, name: h(AgeRange, { data: window }) });\n } else if (window == null || windowMatchesInterval(window, interval)) {\n tag = h(IntervalTag, { interval, showAgeRange: true, size });\n } else {\n tag = h(RefinedIntervalTag, { interval, window, size });\n }\n\n return h(\"span.age-window-tag\", { className: classNames(className) }, [\n tag,\n h.if(onClear != null)(Button, {\n className: \"clear-button\",\n icon: \"cross\",\n minimal: true,\n small: true,\n title: \"Clear\",\n onClick: onClear,\n }),\n ]);\n}\n\nfunction RefinedIntervalTag({\n interval,\n window,\n size,\n}: {\n interval: IntervalShort;\n window: AgeWindow;\n size: TagSize;\n}) {\n const interaction = useInteractionProps({ int_id: interval.id });\n return h(Tag, {\n ...interaction,\n size,\n name: interval.name,\n color: interval.color,\n details: h(AgeRange, { data: window }),\n });\n}\n\nfunction windowMatchesInterval(\n window: AgeWindow,\n interval: IntervalShort,\n): boolean {\n return window.t_age === interval.t_age && window.b_age === interval.b_age;\n}\n"],"names":["TagSize","h","Tag","AgeRange","IntervalTag","classNames","Button","useInteractionProps"],"mappings":";;;;;;;;;AAkBO,SAAS,2BAA2B,UAAmC;AAC5E,SAAO;AAAA,IACL,IAAI,SAAS,UAAU,SAAS;AAAA,IAChC,MAAM,SAAS;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,MAAM,SAAS;AAAA,EAAA;AAEnB;AAkBO,SAAS,aAAa,OAA0B;AACrD,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,SAAS;AAAA,IACT;AAAA,IACA,OAAOA,eAAAA,QAAQ;AAAA,IACf;AAAA,EAAA,IACE;AACJ,MAAI,YAAY,QAAQ,UAAU,KAAM,QAAO;AAE/C,MAAI,MAAM;AACV,MAAI,YAAY,MAAM;AACpB,UAAMC,oBAAEC,oBAAK,EAAE,MAAM,MAAMD,oBAAEE,SAAAA,UAAU,EAAE,MAAM,OAAA,CAAQ,EAAA,CAAG;AAAA,EAC5D,WAAW,UAAU,QAAQ,sBAAsB,QAAQ,QAAQ,GAAG;AACpE,UAAMF,oBAAEG,4BAAa,EAAE,UAAU,cAAc,MAAM,MAAM;AAAA,EAC7D,OAAO;AACL,UAAMH,oBAAE,oBAAoB,EAAE,UAAU,QAAQ,MAAM;AAAA,EACxD;AAEA,SAAOA,oBAAE,uBAAuB,EAAE,WAAWI,oBAAAA,QAAW,SAAS,KAAK;AAAA,IACpE;AAAA,IACAJ,oBAAE,GAAG,WAAW,IAAI,EAAEK,KAAAA,QAAQ;AAAA,MAC5B,WAAW;AAAA,MACX,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,OAAO;AAAA,MACP,SAAS;AAAA,IAAA,CACV;AAAA,EAAA,CACF;AACH;AAEA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,cAAcC,eAAAA,oBAAoB,EAAE,QAAQ,SAAS,IAAI;AAC/D,SAAON,oBAAEC,eAAAA,KAAK;AAAA,IACZ,GAAG;AAAA,IACH;AAAA,IACA,MAAM,SAAS;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,SAASD,oBAAEE,SAAAA,UAAU,EAAE,MAAM,QAAQ;AAAA,EAAA,CACtC;AACH;AAEA,SAAS,sBACP,QACA,UACS;AACT,SAAO,OAAO,UAAU,SAAS,SAAS,OAAO,UAAU,SAAS;AACtE;;;"}
@@ -0,0 +1,26 @@
1
+ import { IntervalShort, TagSize } from '@macrostrat/data-components';
2
+ import { Interval } from '@macrostrat/timescale';
3
+ import { AgeWindow } from '../animated-age-window';
4
+ /** Adapt a timescale `Interval` (as delivered by a `Timescale` click, with
5
+ * `eag`/`lag`/`nam`/`col`) to the `IntervalShort` shape the tag components
6
+ * take. The id prefers the Macrostrat `int_id` when the interval tree was built
7
+ * from the API, falling back to the interval's own `oid`. */
8
+ export declare function intervalShortFromTimescale(interval: Interval): IntervalShort;
9
+ export interface AgeWindowTagProps {
10
+ /** The interval the window was set from, if any. */
11
+ interval?: IntervalShort | null;
12
+ /** The rendered window. When it differs from the interval's own span (a
13
+ * refined window) it is shown in place of the interval's range. */
14
+ window?: AgeWindow | null;
15
+ /** Renders a clear button when given. */
16
+ onClear?: () => void;
17
+ size?: TagSize;
18
+ className?: string;
19
+ }
20
+ /** A compact, clearable indicator of the age window a column or correlation
21
+ * chart is focused on — an interval tag (linking to the interval, with its
22
+ * range) or a bare age range. Renders nothing without an interval or window,
23
+ * so it can live permanently in a chart's `axisTopContent`. */
24
+ export declare function AgeWindowTag(props: AgeWindowTagProps): import('react').ReactElement<{
25
+ className: string;
26
+ }, string | import('react').JSXElementConstructor<any>> | null;
@@ -0,0 +1,66 @@
1
+ import { Button } from "@blueprintjs/core";
2
+ import { TagSize, Tag, IntervalTag, useInteractionProps } from "@macrostrat/data-components";
3
+ import classNames from "classnames";
4
+ import h from "./age-window-tag.module.sass.js";
5
+ import { AgeRange } from "./age-range.js";
6
+ function intervalShortFromTimescale(interval) {
7
+ return {
8
+ id: interval.int_id ?? interval.oid,
9
+ name: interval.nam,
10
+ color: interval.col,
11
+ b_age: interval.eag,
12
+ t_age: interval.lag,
13
+ rank: interval.lvl
14
+ };
15
+ }
16
+ function AgeWindowTag(props) {
17
+ const {
18
+ interval = null,
19
+ window = null,
20
+ onClear,
21
+ size = TagSize.Small,
22
+ className
23
+ } = props;
24
+ if (interval == null && window == null) return null;
25
+ let tag = null;
26
+ if (interval == null) {
27
+ tag = h(Tag, { size, name: h(AgeRange, { data: window }) });
28
+ } else if (window == null || windowMatchesInterval(window, interval)) {
29
+ tag = h(IntervalTag, { interval, showAgeRange: true, size });
30
+ } else {
31
+ tag = h(RefinedIntervalTag, { interval, window, size });
32
+ }
33
+ return h("span.age-window-tag", { className: classNames(className) }, [
34
+ tag,
35
+ h.if(onClear != null)(Button, {
36
+ className: "clear-button",
37
+ icon: "cross",
38
+ minimal: true,
39
+ small: true,
40
+ title: "Clear",
41
+ onClick: onClear
42
+ })
43
+ ]);
44
+ }
45
+ function RefinedIntervalTag({
46
+ interval,
47
+ window,
48
+ size
49
+ }) {
50
+ const interaction = useInteractionProps({ int_id: interval.id });
51
+ return h(Tag, {
52
+ ...interaction,
53
+ size,
54
+ name: interval.name,
55
+ color: interval.color,
56
+ details: h(AgeRange, { data: window })
57
+ });
58
+ }
59
+ function windowMatchesInterval(window, interval) {
60
+ return window.t_age === interval.t_age && window.b_age === interval.b_age;
61
+ }
62
+ export {
63
+ AgeWindowTag,
64
+ intervalShortFromTimescale
65
+ };
66
+ //# sourceMappingURL=age-window-tag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age-window-tag.js","sources":["../../src/unit-details/age-window-tag.ts"],"sourcesContent":["import { Button } from \"@blueprintjs/core\";\nimport {\n IntervalShort,\n IntervalTag,\n Tag,\n TagSize,\n useInteractionProps,\n} from \"@macrostrat/data-components\";\nimport type { Interval } from \"@macrostrat/timescale\";\nimport classNames from \"classnames\";\nimport h from \"./age-window-tag.module.sass\";\nimport { AgeRange } from \"./age-range\";\nimport type { AgeWindow } from \"../animated-age-window\";\n\n/** Adapt a timescale `Interval` (as delivered by a `Timescale` click, with\n * `eag`/`lag`/`nam`/`col`) to the `IntervalShort` shape the tag components\n * take. The id prefers the Macrostrat `int_id` when the interval tree was built\n * from the API, falling back to the interval's own `oid`. */\nexport function intervalShortFromTimescale(interval: Interval): IntervalShort {\n return {\n id: interval.int_id ?? interval.oid,\n name: interval.nam,\n color: interval.col,\n b_age: interval.eag,\n t_age: interval.lag,\n rank: interval.lvl,\n };\n}\n\nexport interface AgeWindowTagProps {\n /** The interval the window was set from, if any. */\n interval?: IntervalShort | null;\n /** The rendered window. When it differs from the interval's own span (a\n * refined window) it is shown in place of the interval's range. */\n window?: AgeWindow | null;\n /** Renders a clear button when given. */\n onClear?: () => void;\n size?: TagSize;\n className?: string;\n}\n\n/** A compact, clearable indicator of the age window a column or correlation\n * chart is focused on — an interval tag (linking to the interval, with its\n * range) or a bare age range. Renders nothing without an interval or window,\n * so it can live permanently in a chart's `axisTopContent`. */\nexport function AgeWindowTag(props: AgeWindowTagProps) {\n const {\n interval = null,\n window = null,\n onClear,\n size = TagSize.Small,\n className,\n } = props;\n if (interval == null && window == null) return null;\n\n let tag = null;\n if (interval == null) {\n tag = h(Tag, { size, name: h(AgeRange, { data: window }) });\n } else if (window == null || windowMatchesInterval(window, interval)) {\n tag = h(IntervalTag, { interval, showAgeRange: true, size });\n } else {\n tag = h(RefinedIntervalTag, { interval, window, size });\n }\n\n return h(\"span.age-window-tag\", { className: classNames(className) }, [\n tag,\n h.if(onClear != null)(Button, {\n className: \"clear-button\",\n icon: \"cross\",\n minimal: true,\n small: true,\n title: \"Clear\",\n onClick: onClear,\n }),\n ]);\n}\n\nfunction RefinedIntervalTag({\n interval,\n window,\n size,\n}: {\n interval: IntervalShort;\n window: AgeWindow;\n size: TagSize;\n}) {\n const interaction = useInteractionProps({ int_id: interval.id });\n return h(Tag, {\n ...interaction,\n size,\n name: interval.name,\n color: interval.color,\n details: h(AgeRange, { data: window }),\n });\n}\n\nfunction windowMatchesInterval(\n window: AgeWindow,\n interval: IntervalShort,\n): boolean {\n return window.t_age === interval.t_age && window.b_age === interval.b_age;\n}\n"],"names":[],"mappings":";;;;;AAkBO,SAAS,2BAA2B,UAAmC;AAC5E,SAAO;AAAA,IACL,IAAI,SAAS,UAAU,SAAS;AAAA,IAChC,MAAM,SAAS;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,MAAM,SAAS;AAAA,EAAA;AAEnB;AAkBO,SAAS,aAAa,OAA0B;AACrD,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,SAAS;AAAA,IACT;AAAA,IACA,OAAO,QAAQ;AAAA,IACf;AAAA,EAAA,IACE;AACJ,MAAI,YAAY,QAAQ,UAAU,KAAM,QAAO;AAE/C,MAAI,MAAM;AACV,MAAI,YAAY,MAAM;AACpB,UAAM,EAAE,KAAK,EAAE,MAAM,MAAM,EAAE,UAAU,EAAE,MAAM,OAAA,CAAQ,EAAA,CAAG;AAAA,EAC5D,WAAW,UAAU,QAAQ,sBAAsB,QAAQ,QAAQ,GAAG;AACpE,UAAM,EAAE,aAAa,EAAE,UAAU,cAAc,MAAM,MAAM;AAAA,EAC7D,OAAO;AACL,UAAM,EAAE,oBAAoB,EAAE,UAAU,QAAQ,MAAM;AAAA,EACxD;AAEA,SAAO,EAAE,uBAAuB,EAAE,WAAW,WAAW,SAAS,KAAK;AAAA,IACpE;AAAA,IACA,EAAE,GAAG,WAAW,IAAI,EAAE,QAAQ;AAAA,MAC5B,WAAW;AAAA,MACX,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,OAAO;AAAA,MACP,SAAS;AAAA,IAAA,CACV;AAAA,EAAA,CACF;AACH;AAEA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,cAAc,oBAAoB,EAAE,QAAQ,SAAS,IAAI;AAC/D,SAAO,EAAE,KAAK;AAAA,IACZ,GAAG;AAAA,IACH;AAAA,IACA,MAAM,SAAS;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ;AAAA,EAAA,CACtC;AACH;AAEA,SAAS,sBACP,QACA,UACS;AACT,SAAO,OAAO,UAAU,SAAS,SAAS,OAAO,UAAU,SAAS;AACtE;"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ const h$1 = require("@macrostrat/hyper");
3
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
4
+ const h__default = /* @__PURE__ */ _interopDefault(h$1);
5
+ const styles = {
6
+ "age-window-tag": "_age-window-tag_1tskh_1",
7
+ "clear-button": "_clear-button_1tskh_8"
8
+ };
9
+ let h = h__default.default.styled(styles);
10
+ Object.assign(h, styles);
11
+ module.exports = h;
12
+ //# sourceMappingURL=age-window-tag.module.sass.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age-window-tag.module.sass.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
@@ -0,0 +1,11 @@
1
+ import h$1 from "@macrostrat/hyper";
2
+ const styles = {
3
+ "age-window-tag": "_age-window-tag_1tskh_1",
4
+ "clear-button": "_clear-button_1tskh_8"
5
+ };
6
+ let h = h$1.styled(styles);
7
+ Object.assign(h, styles);
8
+ export {
9
+ h as default
10
+ };
11
+ //# sourceMappingURL=age-window-tag.module.sass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age-window-tag.module.sass.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
@@ -2,5 +2,6 @@ export * from './panel';
2
2
  export * from './popover';
3
3
  export * from './modal-panel';
4
4
  export * from './age-range';
5
+ export * from './age-window-tag';
5
6
  export * from './strat-names';
6
7
  export { ClickableText, Identifier, UnitIdentifier, } from '@macrostrat/data-components';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@macrostrat/column-views",
3
- "version": "3.10.0",
3
+ "version": "3.11.0",
4
4
  "description": "Data views for Macrostrat stratigraphic columns",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,7 +34,10 @@ import type { ColumnData } from "@macrostrat/data-provider";
34
34
  import { BaseUnit } from "@macrostrat/api-types";
35
35
  import { ScaleContinuousNumeric } from "d3-scale";
36
36
  import { ExtUnit } from "../prepare-units/types";
37
- import type { TimescaleClickHandler } from "@macrostrat/timescale";
37
+ import type {
38
+ IntervalStyleBuilder,
39
+ TimescaleClickHandler,
40
+ } from "@macrostrat/timescale";
38
41
  import { useMacrostratColumnInfo } from "@macrostrat/data-provider";
39
42
 
40
43
  /** Standard props passed to a `columnHeaderComponent`. Everything a header
@@ -122,6 +125,7 @@ export function CorrelationChart({
122
125
  columnHeaderComponent,
123
126
  axisTopContent,
124
127
  onClickTimescaleInterval,
128
+ timescaleIntervalStyle,
125
129
  onColumnMouseOver,
126
130
  onColumnClick,
127
131
  isTransitioning = false,
@@ -166,6 +170,7 @@ export function CorrelationChart({
166
170
  scaleInfo,
167
171
  unconformityLabels,
168
172
  onClickInterval: onClickTimescaleInterval,
173
+ intervalStyle: timescaleIntervalStyle,
169
174
  }),
170
175
  h(MainChartArea, [
171
176
  h(
@@ -515,16 +520,23 @@ interface TimescaleColumnProps {
515
520
  showLabels?: boolean;
516
521
  unconformityLabels?: boolean;
517
522
  onClickInterval?: TimescaleClickHandler;
523
+ intervalStyle?: IntervalStyleBuilder;
518
524
  }
519
525
 
520
526
  function TimescaleColumn(props: TimescaleColumnProps) {
521
- const { scaleInfo, unconformityLabels = true, onClickInterval } = props;
527
+ const {
528
+ scaleInfo,
529
+ unconformityLabels = true,
530
+ onClickInterval,
531
+ intervalStyle,
532
+ } = props;
522
533
  return h("div.column-container.age-axis-container", [
523
534
  h(CompositeAgeAxisCore, { ...scaleInfo }),
524
535
  h(CompositeTimescaleCore, {
525
536
  ...scaleInfo,
526
537
  unconformityLabels,
527
538
  onClickInterval,
539
+ intervalStyle,
528
540
  }),
529
541
  ]);
530
542
  }
@@ -0,0 +1,10 @@
1
+ .age-window-tag
2
+ display: inline-flex
3
+ align-items: center
4
+ gap: 0.15em
5
+ white-space: nowrap
6
+ vertical-align: middle
7
+
8
+ .clear-button
9
+ min-width: 0
10
+ min-height: 0
@@ -0,0 +1,102 @@
1
+ import { Button } from "@blueprintjs/core";
2
+ import {
3
+ IntervalShort,
4
+ IntervalTag,
5
+ Tag,
6
+ TagSize,
7
+ useInteractionProps,
8
+ } from "@macrostrat/data-components";
9
+ import type { Interval } from "@macrostrat/timescale";
10
+ import classNames from "classnames";
11
+ import h from "./age-window-tag.module.sass";
12
+ import { AgeRange } from "./age-range";
13
+ import type { AgeWindow } from "../animated-age-window";
14
+
15
+ /** Adapt a timescale `Interval` (as delivered by a `Timescale` click, with
16
+ * `eag`/`lag`/`nam`/`col`) to the `IntervalShort` shape the tag components
17
+ * take. The id prefers the Macrostrat `int_id` when the interval tree was built
18
+ * from the API, falling back to the interval's own `oid`. */
19
+ export function intervalShortFromTimescale(interval: Interval): IntervalShort {
20
+ return {
21
+ id: interval.int_id ?? interval.oid,
22
+ name: interval.nam,
23
+ color: interval.col,
24
+ b_age: interval.eag,
25
+ t_age: interval.lag,
26
+ rank: interval.lvl,
27
+ };
28
+ }
29
+
30
+ export interface AgeWindowTagProps {
31
+ /** The interval the window was set from, if any. */
32
+ interval?: IntervalShort | null;
33
+ /** The rendered window. When it differs from the interval's own span (a
34
+ * refined window) it is shown in place of the interval's range. */
35
+ window?: AgeWindow | null;
36
+ /** Renders a clear button when given. */
37
+ onClear?: () => void;
38
+ size?: TagSize;
39
+ className?: string;
40
+ }
41
+
42
+ /** A compact, clearable indicator of the age window a column or correlation
43
+ * chart is focused on — an interval tag (linking to the interval, with its
44
+ * range) or a bare age range. Renders nothing without an interval or window,
45
+ * so it can live permanently in a chart's `axisTopContent`. */
46
+ export function AgeWindowTag(props: AgeWindowTagProps) {
47
+ const {
48
+ interval = null,
49
+ window = null,
50
+ onClear,
51
+ size = TagSize.Small,
52
+ className,
53
+ } = props;
54
+ if (interval == null && window == null) return null;
55
+
56
+ let tag = null;
57
+ if (interval == null) {
58
+ tag = h(Tag, { size, name: h(AgeRange, { data: window }) });
59
+ } else if (window == null || windowMatchesInterval(window, interval)) {
60
+ tag = h(IntervalTag, { interval, showAgeRange: true, size });
61
+ } else {
62
+ tag = h(RefinedIntervalTag, { interval, window, size });
63
+ }
64
+
65
+ return h("span.age-window-tag", { className: classNames(className) }, [
66
+ tag,
67
+ h.if(onClear != null)(Button, {
68
+ className: "clear-button",
69
+ icon: "cross",
70
+ minimal: true,
71
+ small: true,
72
+ title: "Clear",
73
+ onClick: onClear,
74
+ }),
75
+ ]);
76
+ }
77
+
78
+ function RefinedIntervalTag({
79
+ interval,
80
+ window,
81
+ size,
82
+ }: {
83
+ interval: IntervalShort;
84
+ window: AgeWindow;
85
+ size: TagSize;
86
+ }) {
87
+ const interaction = useInteractionProps({ int_id: interval.id });
88
+ return h(Tag, {
89
+ ...interaction,
90
+ size,
91
+ name: interval.name,
92
+ color: interval.color,
93
+ details: h(AgeRange, { data: window }),
94
+ });
95
+ }
96
+
97
+ function windowMatchesInterval(
98
+ window: AgeWindow,
99
+ interval: IntervalShort,
100
+ ): boolean {
101
+ return window.t_age === interval.t_age && window.b_age === interval.b_age;
102
+ }
@@ -2,6 +2,7 @@ export * from "./panel";
2
2
  export * from "./popover";
3
3
  export * from "./modal-panel";
4
4
  export * from "./age-range";
5
+ export * from "./age-window-tag";
5
6
  export * from "./strat-names";
6
7
 
7
8
  // Backwards compatibility