import { scaleLinear, ScaleContinuousNumeric, ScaleLinear } from "d3-scale"; import React, { createContext, useContext, useMemo } from "react"; import h from "@macrostrat/hyper"; type HeightRange = number; type ColumnScale = ScaleContinuousNumeric | any; type ColumnScaleClamped = ScaleContinuousNumeric; export declare interface ColumnDivision { section_id: string; id: number; surface: number; bottom: number; top: number; // Extra properties that are there for legacy purposes flooding_surface_order?: number; grainsize?: string; covered?: boolean; // Used for boundary management control definite_boundary?: boolean; facies?: string; } enum ColumnAxisType { AGE = "age", HEIGHT = "height", DEPTH = "depth", ORDINAL = "ordinal", } export interface ColumnCtx { divisions: T[]; scaleClamped: ColumnScaleClamped; pixelsPerMeter: number; scale: ColumnScale; axisType?: ColumnAxisType; pixelHeight?: number; zoom: number; /** True while the column's scale/age-window is being animated. Consumers can * use this to skip expensive per-frame recalculations (e.g. label * measurement, `foreignObject` reflows) and restore them once settled. */ isTransitioning: boolean; /** When true, unit labels are hidden while `isTransitioning` (a perf escape * hatch). Defaults to false — labels stay visible through the animation. */ hideLabelsWhileTransitioning: boolean; } export const ColumnContext = createContext>({ scale: scaleLinear(), divisions: [], scaleClamped: scaleLinear().clamp(true), pixelsPerMeter: 1, zoom: 1, isTransitioning: false, hideLabelsWhileTransitioning: false, }); export interface ColumnProviderProps { pixelsPerMeter?: number; divisions: T[]; range?: HeightRange | any; height?: number; zoom?: number; width?: number; axisType?: ColumnAxisType; children?: any; scale?: ColumnScale; /** True while the column's scale/age-window is being animated. */ isTransitioning?: boolean; /** Hide unit labels while transitioning (perf escape hatch; default false). */ hideLabelsWhileTransitioning?: boolean; } function ColumnProvider( props: ColumnProviderProps, ) { /** Lays out a column on its Y (height) axis. This component would be swapped to provide eventual generalization to a Wheeler-diagram (time-domain) framework. */ let { children, pixelsPerMeter = 20, zoom = 1, height, range, divisions = [], width = 150, axisType = ColumnAxisType.HEIGHT, isTransitioning = false, hideLabelsWhileTransitioning = false, scale: _scale, ...rest } = props; // Check if "rest" actually changed // This is a hack to avoid re-rendering the column // when the "rest" props change const restStr = JSON.stringify(rest); const restRef = React.useRef(null); if (restStr !== restRef.current) { restRef.current = restStr; if (Object.keys(rest).length > 0) { console.warn( "Passing extra properties to ColumnProvider is deprecated:", rest, ); } } //# Calculate correct range and height // Range overrides height if set const value: ColumnCtx = useMemo(() => { if (range != null) { height = Math.abs(range[1] - range[0]); } else { range = [0, height]; } // same as the old `innerHeight` let scale = _scale; let pixelHeight: number; if (scale == null) { pixelHeight = height * pixelsPerMeter * zoom; scale = scaleLinear().domain(range).range([pixelHeight, 0]); } else { pixelHeight = Math.abs(scale.range()[1] - scale.range()[0]); // Remove any offset that might exist from paddings, scale breaks, etc. const r1 = scale.range().map((d) => d - scale.range()[0]); scale = _scale.copy().range(r1); } const scaleClamped = scale.copy().clamp(true); return { pixelsPerMeter, pixelHeight, zoom, range, height, scale, scaleClamped, divisions, width, axisType, isTransitioning, hideLabelsWhileTransitioning, ...rest, }; }, [ axisType, height, pixelsPerMeter, range, zoom, divisions, width, isTransitioning, hideLabelsWhileTransitioning, restRef.current, ]); return h(ColumnContext.Provider, { value }, children); } const useColumn = () => useContext(ColumnContext); const useColumnDivisions = () => useContext(ColumnContext).divisions; export { ColumnProvider, ColumnAxisType, useColumnDivisions, useColumn };