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

graphty-org / graphty-element / 20390753610

20 Dec 2025 06:53AM UTC coverage: 82.423% (-1.2%) from 83.666%
20390753610

push

github

apowers313
Merge branch 'master' of https://github.com/graphty-org/graphty-element

5162 of 6088 branches covered (84.79%)

Branch coverage included in aggregate %.

24775 of 30233 relevant lines covered (81.95%)

6480.4 hits per line

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

86.01
/src/layout/NGraphLayoutEngine.ts
1
import ngraphCreateLayout, {Layout as NGraphLayout} from "ngraph.forcelayout";
2!
2
import createGraph, {Graph as NGraph, Link as NGraphLink, Node as NGraphNode} from "ngraph.graph";
2!
3
import random from "ngraph.random";
2!
4
import {z} from "zod/v4";
2✔
5

6
import {defineOptions, type OptionsSchema} from "../config";
2✔
7
import type {Edge} from "../Edge";
8
import type {Node} from "../Node";
9
import {EdgePosition, LayoutEngine, Position} from "./LayoutEngine";
2✔
10

11
/**
12
 * Zod-based options schema for NGraph Force Layout
13
 */
14
export const ngraphLayoutOptionsSchema = defineOptions({
2✔
15
    dim: {
2✔
16
        schema: z.number().int().min(2).max(3).default(3),
2✔
17
        meta: {
2✔
18
            label: "Dimensions",
2✔
19
            description: "Layout dimensionality (2D or 3D)",
2✔
20
        },
2✔
21
    },
2✔
22
    springLength: {
2✔
23
        schema: z.number().positive().default(30),
2✔
24
        meta: {
2✔
25
            label: "Spring Length",
2✔
26
            description: "Ideal spring length between connected nodes",
2✔
27
        },
2✔
28
    },
2✔
29
    springCoefficient: {
2✔
30
        schema: z.number().positive().default(0.0008),
2✔
31
        meta: {
2✔
32
            label: "Spring Coefficient",
2✔
33
            description: "Spring stiffness coefficient",
2✔
34
            step: 0.0001,
2✔
35
            advanced: true,
2✔
36
        },
2✔
37
    },
2✔
38
    gravity: {
2✔
39
        schema: z.number().default(-1.2),
2✔
40
        meta: {
2✔
41
            label: "Gravity",
2✔
42
            description: "Gravity strength (negative for repulsion)",
2✔
43
            step: 0.1,
2✔
44
        },
2✔
45
    },
2✔
46
    theta: {
2✔
47
        schema: z.number().positive().default(0.8),
2✔
48
        meta: {
2✔
49
            label: "Theta",
2✔
50
            description: "Barnes-Hut approximation parameter",
2✔
51
            step: 0.1,
2✔
52
            advanced: true,
2✔
53
        },
2✔
54
    },
2✔
55
    dragCoefficient: {
2✔
56
        schema: z.number().positive().default(0.02),
2✔
57
        meta: {
2✔
58
            label: "Drag Coefficient",
2✔
59
            description: "Velocity damping coefficient",
2✔
60
            step: 0.01,
2✔
61
            advanced: true,
2✔
62
        },
2✔
63
    },
2✔
64
    timeStep: {
2✔
65
        schema: z.number().positive().default(20),
2✔
66
        meta: {
2✔
67
            label: "Time Step",
2✔
68
            description: "Simulation time step size",
2✔
69
            advanced: true,
2✔
70
        },
2✔
71
    },
2✔
72
    seed: {
2✔
73
        schema: z.number().int().positive().nullable().default(null),
2✔
74
        meta: {
2✔
75
            label: "Random Seed",
2✔
76
            description: "Seed for reproducible layout",
2✔
77
            advanced: true,
2✔
78
        },
2✔
79
    },
2✔
80
});
2✔
81

82
export class NGraphEngine extends LayoutEngine {
2✔
83
    static type = "ngraph";
2✔
84
    static maxDimensions = 3;
2✔
85
    static zodOptionsSchema: OptionsSchema = ngraphLayoutOptionsSchema;
2✔
86
    ngraph: NGraph;
87
    ngraphLayout: NGraphLayout<NGraph>;
88

89
    static getOptionsForDimension(dimension: 2 | 3): object {
2✔
90
        return {dim: dimension};
2,809✔
91
    }
2,809✔
92
    nodeMapping = new Map<Node, NGraphNode>();
2✔
93
    edgeMapping = new Map<Edge, NGraphLink>();
2✔
94
    _settled = true;
2✔
95
    _stepCount = 0;
2✔
96
    _lastMoves: number[] = [];
2✔
97

98
    constructor(config: object = {}) {
2✔
99
        super();
1,504✔
100
        this.ngraph = createGraph();
1,504✔
101

102
        // Cast config to a more specific type for property access
103
        const typedConfig = config as Record<string, unknown>;
1,504✔
104

105
        // Build ngraph configuration from provided config
106
        const ngraphConfig: Record<string, unknown> = {
1,504✔
107
            dimensions: typedConfig.dim !== undefined ? typedConfig.dim : 3,
1,504!
108
        };
1,504✔
109

110
        // Map from layout config to ngraph parameters
111
        if (typedConfig.springLength !== undefined) {
1,504!
112
            ngraphConfig.springLength = typedConfig.springLength;
×
113
        }
×
114

115
        if (typedConfig.springCoefficient !== undefined) {
1,504!
116
            ngraphConfig.springCoefficient = typedConfig.springCoefficient;
×
117
        }
×
118

119
        if (typedConfig.gravity !== undefined) {
1,504!
120
            ngraphConfig.gravity = typedConfig.gravity;
×
121
        }
×
122

123
        if (typedConfig.theta !== undefined) {
1,504!
124
            ngraphConfig.theta = typedConfig.theta;
×
125
        }
×
126

127
        if (typedConfig.dragCoefficient !== undefined) {
1,504!
128
            ngraphConfig.dragCoefficient = typedConfig.dragCoefficient;
×
129
        }
×
130

131
        if (typedConfig.timeStep !== undefined) {
1,504!
132
            ngraphConfig.timeStep = typedConfig.timeStep;
×
133
        }
×
134

135
        // Add random number generator with seed if provided
136
        if (typedConfig.seed !== undefined && typeof typedConfig.seed === "number") {
1,504✔
137
            ngraphConfig.random = random(typedConfig.seed);
72✔
138
        }
72✔
139

140
        this.ngraphLayout = ngraphCreateLayout(this.ngraph, ngraphConfig);
1,504✔
141
    }
1,504✔
142

143
    // eslint-disable-next-line @typescript-eslint/no-empty-function
144
    async init(): Promise<void> {}
2✔
145

146
    step(): void {
2✔
147
        const ngraphSettled = this.ngraphLayout.step();
6,371✔
148
        const {lastMove} = this.ngraphLayout;
6,371✔
149
        const nodeCount = this.nodeMapping.size;
6,371✔
150
        const ratio = nodeCount > 0 ? lastMove / nodeCount : 0;
6,371!
151

152
        this._stepCount++;
6,371✔
153

154
        // Keep track of last 10 moves for averaging
155
        this._lastMoves.push(ratio);
6,371✔
156
        if (this._lastMoves.length > 10) {
6,371✔
157
            this._lastMoves.shift();
3,753✔
158
        }
3,753✔
159

160
        // Calculate average movement over last 10 steps
161
        const avgMovement = this._lastMoves.length > 0 ?
6,371✔
162
            this._lastMoves.reduce((a, b) => a + b, 0) / this._lastMoves.length :
6,371!
163
            0;
×
164

165
        // Use a more forgiving threshold or force settling after many steps
166
        const customThreshold = 0.05; // More forgiving than ngraph's 0.01
6,371✔
167
        const maxSteps = 1000; // Force settling after 1000 steps
6,371✔
168

169
        this._settled = ngraphSettled ||
6,371✔
170
                       avgMovement <= customThreshold ||
6,318✔
171
                       this._stepCount >= maxSteps;
6,149✔
172
    }
6,371✔
173

174
    get isSettled(): boolean {
2✔
175
        return this._settled;
25,194✔
176
    }
25,194✔
177

178
    addNode(n: Node): void {
2✔
179
        const ngraphNode: NGraphNode = this.ngraph.addNode(n.id, {parentNode: n});
4,578✔
180
        this.nodeMapping.set(n, ngraphNode);
4,578✔
181
        this._settled = false;
4,578✔
182
        this._stepCount = 0;
4,578✔
183
        this._lastMoves = [];
4,578✔
184
    }
4,578✔
185

186
    addEdge(e: Edge): void {
2✔
187
        const ngraphEdge = this.ngraph.addLink(e.srcId, e.dstId, {parentEdge: this});
6,571✔
188
        this.edgeMapping.set(e, ngraphEdge);
6,571✔
189
        this._settled = false;
6,571✔
190
        this._stepCount = 0;
6,571✔
191
        this._lastMoves = [];
6,571✔
192
    }
6,571✔
193

194
    getNodePosition(n: Node): Position {
2✔
195
        const ngraphNode = this._getMappedNode(n);
86,021✔
196
        return this.ngraphLayout.getNodePosition(ngraphNode.id);
86,021✔
197
    }
86,021✔
198

199
    setNodePosition(n: Node, newPos: Position): void {
2✔
200
        const ngraphNode = this._getMappedNode(n);
6✔
201
        const currPos = this.ngraphLayout.getNodePosition(ngraphNode.id);
6✔
202
        currPos.x = newPos.x;
6✔
203
        currPos.y = newPos.y;
6✔
204
        currPos.z = newPos.z;
6✔
205
    }
6✔
206

207
    getEdgePosition(e: Edge): EdgePosition {
2✔
208
        const ngraphEdge = this._getMappedEdge(e);
220,609✔
209
        const pos = this.ngraphLayout.getLinkPosition(ngraphEdge.id);
220,609✔
210
        return {
220,609✔
211
            src: {
220,609✔
212
                x: pos.from.x,
220,609✔
213
                y: pos.from.y,
220,609✔
214
                z: pos.from.z,
220,609✔
215
            },
220,609✔
216
            dst: {
220,609✔
217
                x: pos.to.x,
220,609✔
218
                y: pos.to.y,
220,609✔
219
                z: pos.to.z,
220,609✔
220
            },
220,609✔
221
        };
220,609✔
222
    }
220,609✔
223

224
    get nodes(): Iterable<Node> {
2✔
225
        // ...is this cheating?
226
        return this.nodeMapping.keys();
5,920✔
227
    }
5,920✔
228

229
    get edges(): Iterable<Edge> {
2✔
230
        return this.edgeMapping.keys();
31,563✔
231
    }
31,563✔
232

233
    pin(n: Node): void {
2✔
234
        const ngraphNode = this._getMappedNode(n);
9✔
235
        this.ngraphLayout.pinNode(ngraphNode, true);
9✔
236
    }
9✔
237

238
    unpin(n: Node): void {
2✔
239
        const ngraphNode = this._getMappedNode(n);
×
240
        this.ngraphLayout.pinNode(ngraphNode, false);
×
241
    }
×
242

243
    private _getMappedNode(n: Node): NGraphNode {
2✔
244
        const ngraphNode = this.nodeMapping.get(n);
86,036✔
245
        if (!ngraphNode) {
86,036!
246
            throw new Error("Internal error: Node not found in NGraphEngine");
×
247
        }
×
248

249
        return ngraphNode;
86,036✔
250
    }
86,036✔
251

252
    private _getMappedEdge(e: Edge): NGraphLink {
2✔
253
        const ngraphNode = this.edgeMapping.get(e);
220,609✔
254
        if (!ngraphNode) {
220,609!
255
            throw new Error("Internal error: Edge not found in NGraphEngine");
×
256
        }
×
257

258
        return ngraphNode;
220,609✔
259
    }
220,609✔
260
}
2✔
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

© 2026 Coveralls, Inc