@macrostrat/column-components
Version:
React rendering primitives for stratigraphic columns
197 lines (177 loc) • 4.67 kB
text/typescript
import { scaleLinear, scaleOrdinal } from "d3-scale";
import { Component, createContext } from "react";
import h from "@macrostrat/hyper";
import {
ColumnContext,
useColumnDivisions,
ColumnCtx,
ColumnDivision,
} from "./column";
import { useContext, useMemo, useCallback } from "react";
import { ReactNode } from "react";
//# This isn't really used yet...
export interface ColumnLayoutCtx<T extends ColumnDivision>
extends ColumnCtx<T> {
width: number;
grainSizes?: string[];
grainsizeScale?: (d: string) => number;
xScale: any;
widthForDivision: (d: ColumnDivision) => number;
}
const ColumnLayoutContext = createContext<ColumnLayoutCtx<ColumnDivision>>({
scale: scaleLinear(),
scaleClamped: scaleLinear().clamp(true),
width: 0,
divisions: [],
grainSizes: [],
grainsizeScale: (d) => 40,
xScale: null,
widthForDivision: (d) => 0,
pixelsPerMeter: 1,
zoom: 1,
});
export interface ColumnLayoutProviderProps<T extends ColumnDivision>
extends Partial<ColumnCtx<T>> {
grainSizes?: string[];
grainsizeScale?: (d: string) => number;
width: number;
// @deprecated
xScale?: any;
children?: ReactNode;
}
function ColumnLayoutProvider<T extends ColumnDivision>({
children,
width,
grainSizes = [],
grainsizeScale = (d) => 40,
xScale,
}: ColumnLayoutProviderProps<T>) {
const ctx = useContext(ColumnContext);
return h(
ColumnLayoutContext.Provider,
{
value: {
...ctx,
width,
grainSizes,
grainsizeScale,
xScale,
widthForDivision: (d) => width,
},
},
children,
);
}
export interface CrossAxisLayoutProviderProps {
width: number;
domain: number[];
range?: number[];
children?: ReactNode;
}
class CrossAxisLayoutProvider extends Component<CrossAxisLayoutProviderProps> {
static contextType = ColumnContext;
context: ColumnCtx<ColumnDivision>;
render() {
let { domain, range, width, children } = this.props;
if (range == null) {
range = [0, width];
}
const xScale = scaleLinear().domain(domain).range(range);
return h(ColumnLayoutProvider, {
...this.context,
xScale,
width,
children,
});
}
}
export type GrainsizeLayoutProps = {
grainSizes: string[];
children?: React.ReactNode;
width?: number;
grainsizeScaleStart?: number;
grainsizeScaleRange?: [number, number];
tickPositions?: number[];
};
function GrainsizeLayoutProvider({
width,
grainSizes = ["ms", "s", "vf", "f", "m", "c", "vc", "p"],
grainsizeScaleStart = 50,
grainsizeScaleRange,
tickPositions,
children,
}: GrainsizeLayoutProps) {
/**
Right now this provides a ColumnLayoutContext
but it could be reworked to provide a
separate "GrainsizeLayoutContext" if that seemed
appropriate.
*/
if (grainsizeScaleRange == null) {
console.warn(
"GrainsizeLayoutProvider: grainsizeScaleStart and width are deprecated in favor of grainsizeScaleRange",
);
}
grainsizeScaleRange ??= [grainsizeScaleStart, width];
const divisions = useColumnDivisions();
const grainsizeScale: any = useMemo(() => {
const scale = scaleLinear()
.domain([0, grainSizes.length - 1])
.range(grainsizeScaleRange);
tickPositions ??= grainSizes.map((d, i) => scale(i));
return scaleOrdinal().domain(grainSizes).range(tickPositions);
}, [grainSizes, grainsizeScaleRange, tickPositions]);
// This function should probably be moved up a level
const grainsizeForDivision = useCallback(
(division) => {
let ix = divisions.indexOf(division);
// Search backwards through divisions
while (ix > 0) {
const { grainsize } = divisions[ix];
if (grainsize != null) {
return grainsize;
}
ix -= 1;
}
},
[divisions],
);
const widthForDivision = useCallback(
(division) => {
if (division == null) {
return width;
}
return grainsizeScale(grainsizeForDivision(division));
},
[grainsizeForDivision, grainsizeScale, width],
);
// This is slow to run each iteration
return h(
ColumnLayoutProvider,
{
width,
grainSizes,
grainsizeScale,
grainsizeScaleStart,
grainsizeScaleRange,
grainsizeForDivision,
widthForDivision,
} as any,
children,
);
}
export function useColumnLayout() {
const ctx = useContext(ColumnLayoutContext);
if (ctx == null) {
throw new Error(
"useColumnLayout must be used within a ColumnLayoutProvider",
);
}
return ctx;
}
export {
ColumnLayoutContext,
ColumnLayoutProvider,
CrossAxisLayoutProvider,
GrainsizeLayoutProvider,
};