• 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

94.61
/src/algorithms/DFSAlgorithm.ts
1
import {depthFirstSearch} 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 {Algorithm} from "./Algorithm";
15✔
6
import {type OptionsSchema} from "./types/OptionSchema";
7
import {toAlgorithmGraph} from "./utils/graphConverter";
15✔
8

9
/**
10
 * Zod-based options schema for DFS algorithm
11
 */
12
export const dfsOptionsSchema = defineOptions({
15✔
13
    source: {
15✔
14
        schema: z.union([z.string(), z.number()]).nullable().default(null),
15✔
15
        meta: {
15✔
16
            label: "Source Node",
15✔
17
            description: "Starting node for DFS traversal (uses first node if not set)",
15✔
18
        },
15✔
19
    },
15✔
20
    targetNode: {
15✔
21
        schema: z.union([z.string(), z.number()]).nullable().default(null),
15✔
22
        meta: {
15✔
23
            label: "Target Node",
15✔
24
            description: "Target node for early termination (optional - searches all nodes if not set)",
15✔
25
            advanced: true,
15✔
26
        },
15✔
27
    },
15✔
28
    recursive: {
15✔
29
        schema: z.boolean().default(false),
15✔
30
        meta: {
15✔
31
            label: "Recursive",
15✔
32
            description: "Use recursive implementation instead of iterative (may cause stack overflow on large graphs)",
15✔
33
            advanced: true,
15✔
34
        },
15✔
35
    },
15✔
36
    preOrder: {
15✔
37
        schema: z.boolean().default(true),
15✔
38
        meta: {
15✔
39
            label: "Pre-Order",
15✔
40
            description: "Visit nodes before their children (pre-order) vs after (post-order)",
15✔
41
            advanced: true,
15✔
42
        },
15✔
43
    },
15✔
44
});
15✔
45

46
/**
47
 * Options for DFS algorithm
48
 */
49
export interface DFSOptions extends Record<string, unknown> {
50
    /** Starting node for traversal (defaults to first node if not provided) */
51
    source: number | string | null;
52
    /** Target node for early termination (optional) */
53
    targetNode: number | string | null;
54
    /** Use recursive implementation vs iterative */
55
    recursive: boolean;
56
    /** Use pre-order traversal (visit before children) vs post-order */
57
    preOrder: boolean;
58
}
59

60
/**
61
 * Depth-First Search (DFS) algorithm for graph traversal
62
 *
63
 * Performs a depth-first traversal from a source node, computing discovery time,
64
 * finish time, and predecessor relationships for each reachable node.
65
 */
66
export class DFSAlgorithm extends Algorithm<DFSOptions> {
15✔
67
    static namespace = "graphty";
19✔
68
    static type = "dfs";
19✔
69

70
    static zodOptionsSchema: ZodOptionsSchema = dfsOptionsSchema;
19✔
71

72
    /**
73
     * Options schema for DFS algorithm
74
     */
75
    static optionsSchema: OptionsSchema = {
19✔
76
        source: {
19✔
77
            type: "nodeId",
19✔
78
            default: null,
19✔
79
            label: "Source Node",
19✔
80
            description: "Starting node for DFS traversal (uses first node if not set)",
19✔
81
            required: false,
19✔
82
        },
19✔
83
        targetNode: {
19✔
84
            type: "nodeId",
19✔
85
            default: null,
19✔
86
            label: "Target Node",
19✔
87
            description: "Target node for early termination (optional - searches all nodes if not set)",
19✔
88
            required: false,
19✔
89
            advanced: true,
19✔
90
        },
19✔
91
        recursive: {
19✔
92
            type: "boolean",
19✔
93
            default: false,
19✔
94
            label: "Recursive",
19✔
95
            description: "Use recursive implementation instead of iterative (may cause stack overflow on large graphs)",
19✔
96
            advanced: true,
19✔
97
        },
19✔
98
        preOrder: {
19✔
99
            type: "boolean",
19✔
100
            default: true,
19✔
101
            label: "Pre-Order",
19✔
102
            description: "Visit nodes before their children (pre-order) vs after (post-order)",
19✔
103
            advanced: true,
19✔
104
        },
19✔
105
    };
19✔
106

107
    /**
108
     * Legacy options set via configure() for backward compatibility
109
     */
110
    private legacyOptions: {source: number | string} | null = null;
19✔
111

112
    static suggestedStyles = (): SuggestedStylesConfig => ({
120✔
113
        layers: [
10✔
114
            {
10✔
115
                node: {
10✔
116
                    selector: "",
10✔
117
                    style: {enabled: true},
10✔
118
                    calculatedStyle: {
10✔
119
                        inputs: ["algorithmResults.graphty.dfs.discoveryTimePct"],
10✔
120
                        output: "style.texture.color",
10✔
121
                        expr: "{ return StyleHelpers.color.sequential.inferno(arguments[0] ?? 0) }",
10✔
122
                    },
10✔
123
                },
10✔
124
                metadata: {
10✔
125
                    name: "DFS - Discovery Time Colors",
10✔
126
                    description: "Colors nodes by DFS discovery time (inferno gradient: black to yellow)",
10✔
127
                },
10✔
128
            },
10✔
129
        ],
10✔
130
        description: "Visualizes depth-first traversal discovery order from source node",
10✔
131
        category: "hierarchy",
10✔
132
    });
10✔
133

134
    /**
135
     * Configure the algorithm with source node
136
     * @param options - Configuration options
137
     * @param options.source - The source node to start DFS from
138
     * @returns This algorithm instance for chaining
139
     * @deprecated Use constructor options instead. This method is kept for backward compatibility.
140
     */
141
    configure(options: {source: number | string}): this {
19✔
142
        this.legacyOptions = options;
10✔
143
        return this;
10✔
144
    }
10✔
145

146
    /**
147
     * Executes the DFS algorithm on the graph
148
     *
149
     * Computes discovery time, finish time, and predecessor information for all reachable nodes.
150
     */
151
    async run(): Promise<void> {
19✔
152
        const g = this.graph;
15✔
153
        const dm = g.getDataManager();
15✔
154
        const nodes = Array.from(dm.nodes.keys());
15✔
155

156
        if (nodes.length === 0) {
15!
157
            return;
1✔
158
        }
1✔
159

160
        // Get source from legacy options, schema options, or use first node as default
161
        // Legacy configure() takes precedence for backward compatibility
162
        const source = this.legacyOptions?.source ?? this._schemaOptions.source ?? nodes[0];
15!
163
        const {targetNode, recursive, preOrder} = this._schemaOptions;
15✔
164

165
        // Check if source exists
166
        if (!dm.nodes.has(source)) {
15!
167
            return;
×
168
        }
✔
169

170
        // Convert to @graphty/algorithms format (undirected for traversal)
171
        const graphData = toAlgorithmGraph(g);
14✔
172

173
        // Run DFS algorithm - returns {visited: Set, order: NodeId[], tree?: Map}
174
        const result = depthFirstSearch(graphData, source, {
14✔
175
            targetNode: targetNode ?? undefined,
14✔
176
            recursive,
15✔
177
            preOrder,
15✔
178
        });
15✔
179

180
        // Build discovery time map from order array (index = discovery time)
181
        const discoveryTimeMap = new Map<number | string, number>();
15✔
182
        for (let i = 0; i < result.order.length; i++) {
15✔
183
            discoveryTimeMap.set(result.order[i], i);
723✔
184
        }
723✔
185

186
        // Max discovery time for normalization
187
        const maxTime = result.order.length > 0 ? result.order.length - 1 : 0;
15!
188

189
        // Store results on nodes
190
        for (const nodeId of nodes) {
15✔
191
            const discoveryTime = discoveryTimeMap.get(nodeId);
742✔
192
            const isVisited = result.visited.has(nodeId);
742✔
193

194
            this.addNodeResult(nodeId, "visited", isVisited);
742✔
195

196
            if (discoveryTime !== undefined) {
742✔
197
                this.addNodeResult(nodeId, "discoveryTime", discoveryTime);
723✔
198
                // Normalize discovery time to percentage
199
                const discoveryTimePct = maxTime > 0 ? discoveryTime / maxTime : 0;
723!
200
                this.addNodeResult(nodeId, "discoveryTimePct", discoveryTimePct);
723✔
201
            }
723✔
202
        }
742✔
203

204
        // Store graph-level results
205
        this.addGraphResult("maxTime", maxTime);
14✔
206
        this.addGraphResult("visitedCount", result.visited.size);
14✔
207
    }
15✔
208
}
19✔
209

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

© 2025 Coveralls, Inc