UNPKG

@macrostrat/column-components

Version:

React rendering primitives for stratigraphic columns

233 lines (204 loc) 5 kB
import { Component, createElement, createContext, useContext, useRef, ReactNode, } from "react"; import hyper from "@macrostrat/hyper"; import { path } from "d3-path"; import { ColumnLayoutContext, ColumnLayoutCtx } from "./context"; import styles from "./frame.module.sass"; const h = hyper.styled(styles); let sequence = 0; // Initialize a sequence counter function getUniqueIdentifier() { // Generate a unique identifier using a sequential method that is stable across repeated // re-renders. This evolves from a UUID-based approach for SSR (server-side rendering) compatibility. const id = `uuid-${sequence}`; sequence += 1; // Increment the sequence for the next call return id; } class UUIDComponent<T> extends Component<T> { UUID: string; constructor(props: T) { super(props); this.UUID = getUniqueIdentifier(); } } const UUIDContext = createContext<string | null>(null); function useBasicUUID() { const ref = useRef<string>(getUniqueIdentifier()); return ref.current; } const useUUID = function () { const uuid = useContext(UUIDContext); if (uuid == null) { return useBasicUUID(); } return uuid; }; function UUIDProvider({ children }) { const ref = useRef<string>(getUniqueIdentifier()); return h(UUIDContext.Provider, { value: ref.current, children }); } interface FrameProps { id: string; className?: string; } function SimpleFrame(props: FrameProps) { const { pixelHeight: height, width } = useContext(ColumnLayoutContext); let { id: frameID, className } = props; if (frameID.startsWith("#")) { frameID = frameID.slice(1); } return h("rect", { id: frameID, x: 0, y: 0, width, height, className, }); } interface GrainsizeFrameProps { id: string; } function GrainsizeFrame(props: GrainsizeFrameProps) { let { id: frameID } = props; const { scale, divisions, grainsizeScale: gs, } = useContext(ColumnLayoutContext); if (frameID.startsWith("#")) { frameID = frameID.slice(1); } if (divisions.length === 0) { return null; } const [bottomOfSection, topOfSection] = scale.domain(); const topOf = function (d) { let { top } = d; if (top > topOfSection) { top = topOfSection; } return scale(top); }; const bottomOf = function (d) { let { bottom } = d; if (bottom < bottomOfSection) { bottom = bottomOfSection; } return scale(bottom); }; const filteredDivisions = divisions.filter(function (d) { if (d.top <= bottomOfSection) { return false; } if (d.bottom > topOfSection) { return false; } return true; }); let _ = null; let currentGrainsize = "m"; let div: any = null; for (div of Array.from(filteredDivisions)) { if (_ == null) { _ = path(); _.moveTo(0, bottomOf(div)); } if (div.grainsize != null) { currentGrainsize = div.grainsize; } const x = gs(currentGrainsize); _.lineTo(x, bottomOf(div)); _.lineTo(x, topOf(div)); } _.lineTo(0, topOf(div)); _.closePath(); return h("path", { id: frameID, key: frameID, d: _.toString(), }); } const ClipPath = function (props) { let { id, children, ...rest } = props; if (id.startsWith("#")) { id = id.slice(1); } return createElement("clipPath", { id, key: id, ...rest }, children); }; const UseFrame = function (props) { const { id: frameID, ...rest } = props; return h("use.frame", { xlinkHref: frameID, fill: "transparent", key: "frame", ...rest, }); }; const prefixID = function ( uuid: string, prefixes: string[], ): Record<string, string> { const res = {}; for (let prefix of Array.from(prefixes)) { res[prefix + "ID"] = `#${uuid}-${prefix}`; } return res; }; export interface ClipToFrameProps { left: number; shiftY: number; onClick?: () => void; frame?: any; width?: number; className?: string; children?: ReactNode; clip?: boolean; } export function ClippingFrame(props: ClipToFrameProps) { const { left = 0, shiftY = 0, className, onClick, children, frame = SimpleFrame, clip = true, } = props; const uuid = useUUID(); const { frameID, clipID } = prefixID(uuid, ["frame", "clip"]); let transform = null; if (left != null) { transform = `translate(${left} ${shiftY})`; } const frameClassName = "frame"; let _frame: ReactNode = h(frame, { id: frameID, className: frameClassName }); let defs = null; let clipPath = null; if (clip) { defs = h("defs", { key: "defs" }, [ _frame, h(ClipPath, { id: clipID }, h(UseFrame, { id: frameID })), ]); clipPath = `url(${clipID})`; _frame = h(UseFrame, { id: frameID, className: frameClassName }); } return h("g", { className, transform, onClick }, [ defs, _frame, h("g.inner", { clipPath }, children), ]); } export { SimpleFrame, GrainsizeFrame, ClipPath, UUIDComponent, UUIDProvider, useUUID, };