UNPKG

mermaid

Version:

Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.

223 lines (222 loc) 9.6 kB
import { type CommonLayoutRenderContext } from '../common/index.js'; import type { LayoutData } from '../../types.js'; import { type TreeData } from './find-common-ancestor.js'; import { type P, type RectLike } from './geometry.js'; type Node = LayoutData['nodes'][number]; interface LabelData { width: number; height: number; wrappingWidth?: number; } interface ElkNodeOffset { posX: number; posY: number; x: number; y: number; depth: number; width: number; height: number; } interface NodeWithVertex { id: string; dir?: string; height?: number; intersect?: (point: P) => P | null; isGroup?: boolean; /** * Where ELK put this container, kept when `evenGroupFrames` moves the drawn * frame. Edge sections resolve against this, never against the moved frame. */ elkOrigin?: { posX: number; posY: number; }; padding?: number; parentId?: string; shape?: string; width?: number; x?: number; y?: number; [key: string]: any; children?: NodeWithVertex[]; labelData?: LabelData; labels?: { text?: string; width: number; height: number; }[]; layoutOptions?: Record<string, unknown>; offset?: ElkNodeOffset; } interface ElkSubgraphConfig { mergeEdges?: boolean; straightenEdges?: boolean; preset?: string; layeringStrategy?: string; layeringLayerBound?: number; nodePlacementAlignment?: string; nodePlacementStrategy?: string; cycleBreakingStrategy?: string; } interface ElkPreparedLayout { algorithm?: string; } interface ElkLayoutContext { algorithm?: string; /** * Extra root-graph `layoutOptions`, merged last over * {@link createRootElkGraph}'s defaults. * * NOT user-facing config: nothing in `config.schema.yaml` writes it and * production `render()` never sets it. It exists so the DDLT configuration * sweep can try ELK options that are currently hardcoded here — spacings, * edge routing, node placement — WITHOUT forking the layout pipeline. A * sweep that reimplemented `createRootElkGraph` would be measuring a graph * the browser never builds, which is the exact failure the single-pipeline * rule exists to prevent. * * Promote a winning option to a real default in `createRootElkGraph`, or to * a `config.elk.*` key if it should be author-controlled. Do not reach for * this from product code. */ rootLayoutOptions?: Record<string, unknown>; common: { lineBreakRegex: RegExp; }; getConfig: () => any; interpolateToCurve: (interpolate: string | undefined, defaultCurve: unknown) => unknown; log: { debug: (...args: unknown[]) => void; error: (...args: unknown[]) => void; info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; }; } interface ElkLayoutState { elkGraph: any; nodeDb: Record<string, NodeWithVertex>; parentLookupDb: TreeData; } interface ElkLayoutResult { children?: any[]; edges?: any[]; } /** * Undo the algorithm-scoped options on a container, restoring the values a * plain subgraph would have had. */ export declare function clearContainerAlgorithmOptions(layoutOptions: Record<string, unknown>): void; /** * Resolve a container's requested layout algorithm, or `undefined` when the * request is absent, not a string, or not a supported ELK algorithm. */ export declare function resolveContainerAlgorithm(requested: unknown, log?: ElkLayoutContext['log']): string | undefined; export declare function dir2ElkDirection(dir: unknown): 'RIGHT' | 'LEFT' | 'DOWN' | 'UP'; export declare function buildSubgraphLayoutOptions(node: { dir?: string; shape?: string; padding?: number; labelData?: LabelData; metadata?: { algorithm?: unknown; } & Record<string, unknown>; }, elkConfig: ElkSubgraphConfig | undefined, algorithm: string | undefined, log?: ElkLayoutContext['log']): Record<string, unknown>; /** * Identify the entry node of each recursive flow so it can be pinned to the top. * * `elk.layered` must break cycles before it can rank nodes, and its default * cycle-breaking heuristic is purely degree-based — it has no notion of an * "entry point". So as soon as a flow loops back on itself (recursion), the * first-declared node can be ranked in the middle of the layout, scrambling the * reading order and hiding where the flow starts. * * For each container (grouped by `parentId`) we look only at edges internal to * that container and find its weakly-connected components. A component with no * natural source — no node with in-degree 0 once self-loops are ignored — must * contain a cycle. For such components we break cycles greedily in edge * declaration order: an edge that would close a directed cycle is treated as a * back-edge and skipped, and the entry is the first node in declaration order * that is a source of the remaining forward edges. Raw in-degree alone cannot * find it — a back-edge feeding the true entry hides it, and nominating by * node declaration order instead scrambles the layout (#79). Acyclic * components always have a source and nominate nothing, leaving their layout * untouched. The caller pins each nominee to the first layer with * `elk.layered.layering.layerConstraint = FIRST`. * * @param nodes - layout nodes in declaration order * @param edges - layout edges referencing node ids via `source`/`target` * @returns the ids of nodes to constrain to the first layer */ export declare function findCyclicEntryNodes(nodes: { id: string; parentId?: string; }[], edges: { source?: string | number; target?: string | number; }[]): Set<string>; export declare function prepareLayoutForElk(data4Layout: LayoutData, context: CommonLayoutRenderContext<ElkPreparedLayout>): ElkPreparedLayout; export declare function runElkLayoutCore(data4Layout: LayoutData, context: CommonLayoutRenderContext<ElkPreparedLayout>): Promise<ElkLayoutResult>; export declare function buildElkGraphFromLayoutData(data4Layout: LayoutData, elkContext: ElkLayoutContext): ElkLayoutState; export declare const render: (data4Layout: LayoutData, svg: import("../../../mermaid.js").SVG, helpers?: import("../../../internals.js").InternalHelpers, options?: import("../../render.js").RenderOptions) => Promise<void>; /** * Resolve a preset name, falling back to `default` for an unknown one. * * `Object.hasOwn` rather than a plain lookup: the schema's enum only guards the * config path, and a directive or a programmatic config can still put anything * here. `ELK_PRESETS['__proto__']` is truthy, so an indexed lookup would return * `Object.prototype` and every strategy read off it would come back `undefined` * — a silently strategy-less layout rather than the documented fallback. */ export declare function resolveElkPreset(name: string | undefined): { layering: string; placement: string; containerPlacement: string; alignment: string; cycleBreaking: string; }; /** * Sit each group's frame an even distance from its own contents. * * ELK sizes a container around everything it put inside, edges included. An * edge that runs against the flow of the layout gets routed back around the * outside, and when that happens inside a frame the frame grows to hold the * lane — on one side only, since that is where the edge leaves. The result is a * group with 76px of space on the right and 24px on the left, which reads as a * mistake because nothing visible occupies it. * * The lane is real and the edge still needs it, so the fix is not to reclaim * the space but to stop drawing the frame around it. The frame is pulled in to * `SUBGRAPH_PADDING` from the children on the left, right and bottom, and the * edge keeps its lane just outside — which is what an edge routed around a * group should look like anyway. * * The top is left exactly as ELK set it. It carries the subgraph's title strip, * and there is no way from here to tell how much of that padding is the label * and how much is spare, so tightening it risks clipping the title. * * Runs deepest-first, so a parent measures against children that have already * been pulled in rather than against their original boxes. */ export declare function collectDescendantIds(elkNode: any, into?: Set<string>): Set<string>; export declare function evenGroupFrames(elkNodes: any[], layoutState: ElkLayoutState, nodeById: Map<string, Node>, graph?: ElkLayoutResult): void; /** * Straighten the port-to-channel staircase at either end of a clipped route, * leaving both ports where they are. * * Returns the original array when nothing applies, so callers can compare by * identity. */ export declare function straightenTerminalJogs(points: P[]): P[]; export declare function sanitizeElkEdgePoints(points: P[], startNode: NodeWithVertex, endNode: NodeWithVertex, log: ElkLayoutContext['log']): P[]; /** * Mirror of `ensureEndMarkerSegmentLength` for the start of the path: the * start marker's pull-back walks forward along the first segment, so a short * on-border stub there flips the start marker the same way. */ export declare function ensureStartMarkerSegmentLength(points: P[], startBounds: RectLike, markerOffset: number, log: { debug: (...args: unknown[]) => void; }): P[]; export declare function ensureEndMarkerSegmentLength(points: P[], endBounds: RectLike, markerOffset: number, log: { debug: (...args: unknown[]) => void; }): P[]; export {};