# core/plugins/diagram.invocation.plugin.ts

> 128 lines of code and 29 definitions.

Tree: GovLab Docs
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-plugins-diagram-invocation-plugin-ts
Source text: https://banes-lab.com/source/docs/core/plugins/diagram.invocation.plugin.ts.txt

Listed in [core/plugins](https://banes-lab.com/api/source/docs/core/plugins.md), after [core/plugins/diagram.dependency.plugin.ts](https://banes-lab.com/source/docs/core/plugins/diagram.dependency.plugin.ts.md) and before [core/plugins/diagram.machine.plugin.ts](https://banes-lab.com/source/docs/core/plugins/diagram.machine.plugin.ts.md).

## Definitions

- `modelEdges` (lexical_declaration, line 42)
- `modelNodes` (lexical_declaration, line 32)
- `scopeOf` (lexical_declaration, line 121)
- `partSources` (lexical_declaration, line 62)
- `renderFlow` (lexical_declaration, line 104)
- `graphNodeOf` (lexical_declaration, line 28)
- `buildModel` (lexical_declaration, line 50)
- `render` (method_definition, line 131, exported)
- `flowPart` (lexical_declaration, line 73)
- `MORE_ID` (lexical_declaration, line 24)
- `MIN_FLOW_NODES` (lexical_declaration, line 25)
- `MIN_REACHABLE` (lexical_declaration, line 26)
- `nodes` (lexical_declaration, line 33)
- `node` (lexical_declaration, line 68)
- `order` (lexical_declaration, line 74)
- `kept` (lexical_declaration, line 78)
- `caption` (lexical_declaration, line 79)
- `model` (lexical_declaration, line 80)
- `dedupeSources` (lexical_declaration, line 92)
- `seen` (lexical_declaration, line 93)
- `out` (lexical_declaration, line 94)
- `flowParts` (lexical_declaration, line 105)
- `part` (lexical_declaration, line 106)
- `sources` (lexical_declaration, line 112)
- `parts` (lexical_declaration, line 113)
- `[single]` (lexical_declaration, line 114)
- `diagram` (lexical_declaration, line 125, exported)
- `appliesTo` (method_definition, line 126, exported)
- `graph` (lexical_declaration, line 135, exported)

## Contained in

- [core/plugins](https://banes-lab.com/anatomy/docs/folder-docs-core-plugins.md)

## Uses

- [configuration/strings/figure.strings.ts](https://banes-lab.com/source/docs/configuration/strings/figure.strings.ts.md)
- [core/analyzers/invocation.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/invocation.analyzer.ts.md)
- [core/converters/diagram.converter.ts](https://banes-lab.com/source/docs/core/converters/diagram.converter.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/docs/folder-docs-configuration-strings.md)
- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)
- [core/converters](https://banes-lab.com/anatomy/docs/folder-docs-core-converters.md)
- [core/plugins](https://banes-lab.com/anatomy/docs/folder-docs-core-plugins.md)

## Source

```typescript
import { CLASS_DEFS, EDGE_BUDGET } from "#configuration/constants/graph.constants";
import type { CodeGraph, CodeNode } from "#types/graph.types";
import {
    DIAGRAM_DESCRIPTIONS,
    DIAGRAM_TITLES,
    KIND_LEGEND,
    flowTitle,
    moreNodes,
} from "#configuration/strings/figure.strings";
import type {
    DiagramKind,
    DiagramSource,
    FlowModelInput,
    FlowPart,
    FlowScope,
    RenderedDiagram,
} from "#types/figure.types";
import type { GraphEdge, GraphModel, GraphNode } from "#types/diagram.types";
import { adjacencyOf, reachableFrom, rootIds, selectWithinEdgeBudget } from "#core/analyzers/invocation.analyzer";
import { classForKind, dedupEdges, shapeForKind, weightForKind } from "#core/converters/diagram.converter";
import { collapseByLabel } from "#core/converters/graph.converter";
import { emitGraph } from "#core/formatters/diagram.formatter";

const MORE_ID = "z_more";
const MIN_FLOW_NODES = 3;
const MIN_REACHABLE = 3;

const graphNodeOf = function graphNodeOf(node: CodeNode): GraphNode {
    return { class: classForKind(node.kind), id: node.id, label: node.label, shape: shapeForKind(node.kind) };
};

const modelNodes = function modelNodes(input: FlowModelInput): GraphNode[] {
    const nodes = input.keptOrder
        .filter((id) => input.keptIds.has(id))
        .flatMap((id) => {
            const node = input.nodeById.get(id);
            return node ? [graphNodeOf(node)] : [];
        });
    return input.truncated > 0 ? [...nodes, { id: MORE_ID, label: moreNodes(input.truncated), shape: "rect" }] : nodes;
};

const modelEdges = function modelEdges(input: FlowModelInput): GraphEdge[] {
    return dedupEdges(
        input.graph.edges
            .filter((edge) => input.keptIds.has(edge.from) && input.keptIds.has(edge.to))
            .map((edge) => ({ from: edge.from, label: edge.label, to: edge.to, weight: weightForKind(edge.kind) })),
    );
};

const buildModel = function buildModel(input: FlowModelInput): GraphModel {
    return {
        accDescr: DIAGRAM_DESCRIPTIONS.flow,
        accTitle: input.accTitle,
        classDefs: [...CLASS_DEFS],
        direction: input.direction,
        edges: modelEdges(input),
        kind: "flowchart",
        nodes: modelNodes(input),
    };
};

const partSources = function partSources(
    order: readonly string[],
    kept: ReadonlySet<string>,
    nodeById: ReadonlyMap<string, CodeNode>,
): DiagramSource[] {
    return order.flatMap((id) => {
        const node = nodeById.get(id);
        return kept.has(id) && node ? [{ file: node.source.file, label: node.label, line: node.source.line }] : [];
    });
};

const flowPart = function flowPart(root: string, scope: FlowScope): FlowPart | null {
    const order = reachableFrom(root, scope.adj);
    if (order.length < MIN_REACHABLE) {
        return null;
    }
    const kept = selectWithinEdgeBudget(order, scope.graph.edges, EDGE_BUDGET);
    const caption = scope.nodeById.get(root)?.label ?? root;
    const model = buildModel({
        accTitle: flowTitle(scope.context.moduleName, caption),
        direction: scope.context.layout.direction,
        graph: scope.graph,
        keptIds: kept,
        keptOrder: order,
        nodeById: scope.nodeById,
        truncated: order.length - kept.size,
    });
    return { part: { caption, mermaid: emitGraph(model) }, sources: partSources(order, kept, scope.nodeById) };
};

const dedupeSources = function dedupeSources(sources: readonly DiagramSource[]): DiagramSource[] {
    const seen = new Set<string>();
    const out: DiagramSource[] = [];
    for (const source of sources) {
        if (!seen.has(source.label)) {
            seen.add(source.label);
            out.push(source);
        }
    }
    return out;
};

const renderFlow = function renderFlow(scope: FlowScope): RenderedDiagram | null {
    const flowParts = rootIds(scope.graph).flatMap((root) => {
        const part = flowPart(root, scope);
        return part === null ? [] : [part];
    });
    if (flowParts.length === 0) {
        return null;
    }
    const sources = dedupeSources(flowParts.flatMap((part) => part.sources));
    const parts = flowParts.map((part) => part.part);
    const [single] = parts;
    if (parts.length === 1 && single) {
        return { legend: KIND_LEGEND, mermaid: single.mermaid, sources };
    }
    return { legend: KIND_LEGEND, mermaid: "", parts, sources };
};

const scopeOf = function scopeOf(context: FlowScope["context"], graph: CodeGraph): FlowScope {
    return { adj: adjacencyOf(graph), context, graph, nodeById: new Map(graph.nodes.map((node) => [node.id, node])) };
};

export const diagram: DiagramKind = {
    appliesTo(context) {
        return context.codeGraph !== null && context.codeGraph.nodes.length >= MIN_FLOW_NODES;
    },
    id: "flow",
    order: 2,
    render(context) {
        if (context.codeGraph === null) {
            return null;
        }
        const graph = collapseByLabel(context.codeGraph);
        return graph.nodes.length === 0 ? null : renderFlow(scopeOf(context, graph));
    },
    title: DIAGRAM_TITLES.flow,
};
```
