• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

graphty-org / graphty-element / 20514590651

26 Dec 2025 02:37AM UTC coverage: 70.559% (-0.3%) from 70.836%
20514590651

push

github

apowers313
ci: fix npm ci

9591 of 13363 branches covered (71.77%)

Branch coverage included in aggregate %.

25136 of 35854 relevant lines covered (70.11%)

6233.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.31
/src/algorithms/BFSAlgorithm.ts
1
import {breadthFirstSearch} from "@graphty/algorithms";
15 ✔
2
import {z} from "zod/v4";
15 ✔
3

4
import {defineOptions, type OptionsSchema as ZodOptionsSchema, type SuggestedStylesConfig} from "../config";
15 ✔
5
import type {Graph} from "../Graph";
6
import {Algorithm} from "./Algorithm";
15 ✔
7
import {type OptionsSchema} from "./types/OptionSchema";
8
import {toAlgorithmGraph} from "./utils/graphConverter";
15 ✔
9

10
/**
11
 * Zod-based options schema for BFS algorithm
12
 */
13
export const bfsOptionsSchema = defineOptions({
15 ✔
14
    source: {
15 ✔
15
        schema: z.union([z.string(), z.number()]).nullable().default(null),
15 ✔
16
        meta: {
15 ✔
17
            label: "Source Node",
15 ✔
18
            description: "Starting node for BFS traversal (uses first node if not set)",
15 ✔
19
        },
15 ✔
20
    },
15 ✔
21
    targetNode: {
15 ✔
22
        schema: z.union([z.string(), z.number()]).nullable().default(null),
15 ✔
23
        meta: {
15 ✔
24
            label: "Target Node",
15 ✔
25
            description: "Target node for early termination (optional - searches all nodes if not set)",
15 ✔
26
            advanced: true,
15 ✔
27
        },
15 ✔
28
    },
15 ✔
29
});
15 ✔
30

31
/**
32
 * Options for BFS algorithm
33
 */
34
export interface BFSOptions extends Record<string, unknown> {
35
    /** Starting node for traversal (defaults to first node if not provided) */
36
    source: number | string | null;
37
    /** Target node for early termination (optional) */
38
    targetNode: number | string | null;
39
}
40

41
/**
42
 * Breadth-First Search (BFS) algorithm for graph traversal
43
 *
44
 * Performs a breadth-first traversal from a source node, computing level information
45
 * and predecessor relationships for each reachable node.
46
 */
47
export class BFSAlgorithm extends Algorithm<BFSOptions> {
15 ✔
48
    static namespace = "graphty";
17 ✔
49
    static type = "bfs";
17 ✔
50

51
    static zodOptionsSchema: ZodOptionsSchema = bfsOptionsSchema;
17 ✔
52

53
    /**
54
     * Options schema for BFS algorithm
55
     */
56
    static optionsSchema: OptionsSchema = {
17 ✔
57
        source: {
17 ✔
58
            type: "nodeId",
17 ✔
59
            default: null,
17 ✔
60
            label: "Source Node",
17 ✔
61
            description: "Starting node for BFS traversal (uses first node if not set)",
17 ✔
62
            required: false,
17 ✔
63
        },
17 ✔
64
        targetNode: {
17 ✔
65
            type: "nodeId",
17 ✔
66
            default: null,
17 ✔
67
            label: "Target Node",
17 ✔
68
            description: "Target node for early termination (optional - searches all nodes if not set)",
17 ✔
69
            required: false,
17 ✔
70
            advanced: true,
17 ✔
71
        },
17 ✔
72
    };
17 ✔
73

74
    /**
75
     * Legacy options set via configure() for backward compatibility
76
     */
77
    private legacyOptions: {source: number | string} | null = null;
17 ✔
78

79
    static suggestedStyles = (): SuggestedStylesConfig => ({
120 ✔
80
        layers: [
10 ✔
81
            {
10 ✔
82
                node: {
10 ✔
83
                    selector: "",
10 ✔
84
                    style: {enabled: true},
10 ✔
85
                    calculatedStyle: {
10 ✔
86
                        inputs: ["algorithmResults.graphty.bfs.levelPct"],
10 ✔
87
                        output: "style.texture.color",
10 ✔
88
                        expr: "{ return StyleHelpers.color.sequential.viridis(arguments[0] ?? 0) }",
10 ✔
89
                    },
10 ✔
90
                },
10 ✔
91
                metadata: {
10 ✔
92
                    name: "BFS - Level Colors",
10 ✔
93
                    description: "Colors nodes by BFS level from source (viridis gradient)",
10 ✔
94
                },
10 ✔
95
            },
10 ✔
96
        ],
10 ✔
97
        description: "Visualizes breadth-first traversal levels from source node",
10 ✔
98
        category: "hierarchy",
10 ✔
99
    });
10 ✔
100

101
    /**
102
     * Configure the algorithm with source node
103
     * @param options - Configuration options
104
     * @param options.source - The source node to start BFS from
105
     * @returns This algorithm instance for chaining
106
     * @deprecated Use constructor options instead. This method is kept for backward compatibility.
107
     */
108
    configure(options: {source: number | string}): this {
19 ✔
109
        this.legacyOptions = options;
8 ✔
110
        return this;
8 ✔
111
    }
8 ✔
112

113
    /**
114
     * Executes the BFS algorithm on the graph
115
     *
116
     * Computes BFS level, predecessor, and distance information for all reachable nodes.
117
     */
118
    async run(): Promise<void> {
19 ✔
119
        const g = this.graph;
13 ✔
120
        const dm = g.getDataManager();
13 ✔
121
        const nodes = Array.from(dm.nodes.keys());
13 ✔
122
        const n = nodes.length;
13 ✔
123

124
        if (n === 0) {
13 !
125
            return;
1 ✔
126
        }
1 ✔
127

128
        // Get source from legacy options, schema options, or use first node as default
129
        // Legacy configure() takes precedence for backward compatibility
130
        const source = this.legacyOptions?.source ?? this._schemaOptions.source ?? nodes[0];
13 !
131
        const targetNode = this._schemaOptions.targetNode ?? undefined;
13 ✔
132

133
        // Convert to @graphty/algorithms format
134
        // Using directed=false so the converter adds reverse edges for undirected traversal
135
        const graphData = toAlgorithmGraph(g as unknown as Graph, {directed: false});
13 ✔
136

137
        // Check if source exists
138
        if (!graphData.hasNode(source)) {
13 !
139
            // Source not in graph - nothing to do
140
            return;
×
141
        }
✔
142

143
        // Track levels and visit order manually since breadthFirstSearch doesn't return them directly
144
        const levels = new Map<string | number, number>();
12 ✔
145
        const visitOrders = new Map<string | number, number>();
12 ✔
146
        let visitOrder = 0;
12 ✔
147
        let maxLevel = 0;
12 ✔
148
        let foundTarget = false;
12 ✔
149

150
        // Use visitCallback to track levels
151
        breadthFirstSearch(graphData, source, {
12 ✔
152
            targetNode,
12 ✔
153
            visitCallback: (node, level) => {
12 ✔
154
                levels.set(node, level);
569 ✔
155
                visitOrders.set(node, visitOrder);
569 ✔
156
                visitOrder++;
569 ✔
157
                if (level > maxLevel) {
569 ✔
158
                    maxLevel = level;
31 ✔
159
                }
31 ✔
160

161
                if (targetNode !== undefined && node === targetNode) {
569 !
162
                    foundTarget = true;
×
163
                }
×
164
            },
569 ✔
165
        });
12 ✔
166

167
        // Store results on nodes
168
        for (const nodeId of nodes) {
13 ✔
169
            const level = levels.get(nodeId);
588 ✔
170
            const order = visitOrders.get(nodeId);
588 ✔
171

172
            if (level !== undefined) {
588 ✔
173
                this.addNodeResult(nodeId, "level", level);
569 ✔
174
                // Normalize level to percentage
175
                const levelPct = maxLevel > 0 ? level / maxLevel : 0;
569 !
176
                this.addNodeResult(nodeId, "levelPct", levelPct);
569 ✔
177
            }
569 ✔
178

179
            if (order !== undefined) {
588 ✔
180
                this.addNodeResult(nodeId, "visitOrder", order);
569 ✔
181
            }
569 ✔
182
        }
588 ✔
183

184
        // Store graph-level results
185
        this.addGraphResult("maxLevel", maxLevel);
12 ✔
186
        this.addGraphResult("visitedCount", levels.size);
12 ✔
187
        if (targetNode !== undefined) {
13 !
188
            this.addGraphResult("targetFound", foundTarget);
×
189
        }
×
190
    }
13 ✔
191
}
19 ✔
192

193
// Auto-register this algorithm when the module is imported
194
Algorithm.register(BFSAlgorithm);
15 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc