@macrostrat/column-components
Version:
React rendering primitives for stratigraphic columns
126 lines (103 loc) • 2.86 kB
text/typescript
import { useContext, useEffect, useRef } from "react";
import h from "./hyper";
import { select } from "d3-selection";
import { axisLeft } from "d3-axis";
import { ScaleContinuousNumeric, scaleLinear, ScaleLinear } from "d3-scale";
import { useColumn } from "./context";
interface ColumnAxisProps {
ticks?: number;
tickArguments?: any;
tickValues?: any;
tickFormat?: any;
tickSize?: any;
tickSizeInner?: any;
tickSizeOuter?: any;
tickPadding?: any;
tickSpacing?: number;
showLabel?: (d: any) => boolean;
showDomain?: boolean;
className?: string;
}
interface AgeAxisProps extends ColumnAxisProps {
scale?: ScaleContinuousNumeric<number, number>;
minTickSpacing?: number;
}
const __d3axisKeys = [
"ticks",
"tickArguments",
"tickValues",
"tickFormat",
"tickSize",
"tickSizeInner",
"tickSizeOuter",
"tickPadding",
];
export function ColumnAxis(props: ColumnAxisProps) {
const { scale } = useColumn();
return h(AgeAxis, { scale, ...props });
}
export function AgeAxis(props: AgeAxisProps) {
const {
showLabel,
className,
showDomain = true,
tickSpacing = 60,
minTickSpacing = 20,
scale,
} = props;
const range = scale.range();
const pixelHeight = Math.abs(range[0] - range[range.length - 1]);
let tickValues: number[] = undefined;
let ticks = Math.round(pixelHeight / tickSpacing);
if (pixelHeight < 3 * tickSpacing) {
// Push ticks towards extrema (we need more than 2 to be resolved)
let t0: number[] = [];
while (t0.length <= 2) {
ticks += 1;
t0 = scale.ticks(ticks);
}
tickValues = t0;
if (pixelHeight < 2 * tickSpacing) {
// Only show first and last ticks
tickValues = [t0[0], t0[t0.length - 1]];
}
}
if (pixelHeight < minTickSpacing) {
ticks = 1;
tickValues = scale.ticks(1);
// Get the last tick value only
tickValues = [tickValues[0]];
}
const defaultProps = {
ticks,
// Suppress domain endpoints
tickSizeOuter: 0,
tickValues,
};
const ref = useRef(null);
const axisRef = useRef(axisLeft());
const deps = __d3axisKeys.map((k) => props[k]);
useEffect(() => {
const el = ref.current;
if (!el) return;
axisRef.current.scale(scale);
for (let k of __d3axisKeys) {
const val = props[k] ?? defaultProps[k];
if (val == null) continue;
axisRef.current[k](val);
}
const ax = select(el).call(axisRef.current);
if (!showDomain) {
ax.select(".domain").remove();
}
ax.selectAll(".tick text").each(function (d) {
if (!(showLabel?.(d) ?? true)) {
select(this).attr("visibility", "hidden");
}
});
return () => {
select(el).selectAll("*").remove();
};
}, [scale, ref.current, showDomain, showLabel, ...deps]);
return h("g.y.axis.column-axis", { className, ref });
}