@graphty/algorithms 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/algorithms.js +46 -35
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +10 -7
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +33 -21
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/clustering/spectral.d.ts +1 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +18 -13
- package/dist/src/clustering/spectral.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +32 -4
- package/src/algorithms/community/girvan-newman.ts +19 -8
- package/src/clustering/mcl.ts +38 -24
- package/src/clustering/spectral.ts +20 -10
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@graphty/algorithms)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://graphty.app/docs/algorithms/)
|
|
8
|
+
[](https://graphty.app/storybook/algorithms/)
|
|
8
9
|
[](https://graphty.app/algorithms/)
|
|
9
10
|
|
|
10
11
|
A comprehensive TypeScript graph algorithms library with 98 algorithms optimized for browser environments and visualization applications.
|
|
@@ -1028,6 +1029,18 @@ const result = grsbm(graph, {
|
|
|
1028
1029
|
|
|
1029
1030
|
## Interactive Examples
|
|
1030
1031
|
|
|
1032
|
+
### Storybook (Recommended)
|
|
1033
|
+
|
|
1034
|
+
Explore algorithms with interactive Storybook demos: **[View Storybook →](https://graphty.app/storybook/algorithms/)**
|
|
1035
|
+
|
|
1036
|
+
Storybook provides:
|
|
1037
|
+
- **Animated visualizations** showing how each algorithm works step-by-step
|
|
1038
|
+
- **Interactive controls** to modify parameters and graph types
|
|
1039
|
+
- **Deterministic output** with seeded randomness for reproducibility
|
|
1040
|
+
- **Visual testing** with Chromatic for regression detection
|
|
1041
|
+
|
|
1042
|
+
### Legacy HTML Examples
|
|
1043
|
+
|
|
1031
1044
|
Try out all algorithms with interactive visualizations: **[Live Demo →](https://graphty.app/algorithms/)**
|
|
1032
1045
|
|
|
1033
1046
|
The library includes comprehensive examples demonstrating each algorithm. You can:
|
package/dist/algorithms.js
CHANGED
|
@@ -6370,7 +6370,7 @@ function girvanNewman(graph, options = {}) {
|
|
|
6370
6370
|
});
|
|
6371
6371
|
while (Array.from(workingGraph.edges()).length > 0 && iterations < maxIterations) {
|
|
6372
6372
|
iterations++;
|
|
6373
|
-
const edgeBetweenness = calculateEdgeBetweenness(workingGraph);
|
|
6373
|
+
const { betweenness: edgeBetweenness, edgeEndpoints } = calculateEdgeBetweenness(workingGraph);
|
|
6374
6374
|
if (edgeBetweenness.size === 0) {
|
|
6375
6375
|
break;
|
|
6376
6376
|
}
|
|
@@ -6378,9 +6378,9 @@ function girvanNewman(graph, options = {}) {
|
|
|
6378
6378
|
const edgesToRemove = [];
|
|
6379
6379
|
for (const [edgeKey, centrality] of edgeBetweenness) {
|
|
6380
6380
|
if (Math.abs(centrality - maxBetweenness) < 1e-10) {
|
|
6381
|
-
const
|
|
6382
|
-
if (
|
|
6383
|
-
edgesToRemove.push(
|
|
6381
|
+
const endpoints = edgeEndpoints.get(edgeKey);
|
|
6382
|
+
if (endpoints) {
|
|
6383
|
+
edgesToRemove.push(endpoints);
|
|
6384
6384
|
}
|
|
6385
6385
|
}
|
|
6386
6386
|
}
|
|
@@ -6405,9 +6405,11 @@ function girvanNewman(graph, options = {}) {
|
|
|
6405
6405
|
}
|
|
6406
6406
|
function calculateEdgeBetweenness(graph) {
|
|
6407
6407
|
const edgeBetweenness = /* @__PURE__ */ new Map();
|
|
6408
|
+
const edgeEndpoints = /* @__PURE__ */ new Map();
|
|
6408
6409
|
for (const edge of graph.edges()) {
|
|
6409
6410
|
const edgeKey = getEdgeKey(edge.source, edge.target);
|
|
6410
6411
|
edgeBetweenness.set(edgeKey, 0);
|
|
6412
|
+
edgeEndpoints.set(edgeKey, { source: edge.source, target: edge.target });
|
|
6411
6413
|
}
|
|
6412
6414
|
for (const sourceNode of graph.nodes()) {
|
|
6413
6415
|
const source = sourceNode.id;
|
|
@@ -6458,7 +6460,7 @@ function calculateEdgeBetweenness(graph) {
|
|
|
6458
6460
|
edgeBetweenness.set(edgeKey, betweenness / 2);
|
|
6459
6461
|
}
|
|
6460
6462
|
}
|
|
6461
|
-
return edgeBetweenness;
|
|
6463
|
+
return { betweenness: edgeBetweenness, edgeEndpoints };
|
|
6462
6464
|
}
|
|
6463
6465
|
function findAllShortestPaths(graph, source) {
|
|
6464
6466
|
const distances = /* @__PURE__ */ new Map();
|
|
@@ -9022,7 +9024,7 @@ function buildTransitionMatrix(graph, nodeIds, selfLoops) {
|
|
|
9022
9024
|
nodeIds.forEach((id, index) => nodeToIndex.set(id, index));
|
|
9023
9025
|
for (let i = 0; i < n; i++) {
|
|
9024
9026
|
const nodeId = nodeIds[i];
|
|
9025
|
-
if (
|
|
9027
|
+
if (nodeId === void 0) {
|
|
9026
9028
|
continue;
|
|
9027
9029
|
}
|
|
9028
9030
|
const neighbors = graph.neighbors(nodeId);
|
|
@@ -9221,37 +9223,45 @@ function extractClusters(matrix, nodeIds) {
|
|
|
9221
9223
|
attractors.add(nodeId);
|
|
9222
9224
|
}
|
|
9223
9225
|
}
|
|
9224
|
-
|
|
9226
|
+
const attractorToCommunity = /* @__PURE__ */ new Map();
|
|
9225
9227
|
for (let j = 0; j < n; j++) {
|
|
9226
|
-
|
|
9228
|
+
let maxVal = 0;
|
|
9229
|
+
let attractorRow = -1;
|
|
9227
9230
|
for (let i = 0; i < n; i++) {
|
|
9228
9231
|
const matrixRow = matrix[i];
|
|
9229
9232
|
if (!matrixRow) {
|
|
9230
9233
|
continue;
|
|
9231
9234
|
}
|
|
9232
9235
|
const val = matrixRow[j];
|
|
9233
|
-
if (val !== void 0 && val >
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
+
if (val !== void 0 && val > maxVal) {
|
|
9237
|
+
maxVal = val;
|
|
9238
|
+
attractorRow = i;
|
|
9236
9239
|
}
|
|
9237
9240
|
}
|
|
9238
|
-
if (
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
);
|
|
9245
|
-
|
|
9241
|
+
if (attractorRow >= 0) {
|
|
9242
|
+
let community = attractorToCommunity.get(attractorRow);
|
|
9243
|
+
if (!community) {
|
|
9244
|
+
community = [];
|
|
9245
|
+
attractorToCommunity.set(attractorRow, community);
|
|
9246
|
+
}
|
|
9247
|
+
community.push(j);
|
|
9248
|
+
nodeToCluster.set(j, attractorRow);
|
|
9246
9249
|
}
|
|
9247
9250
|
}
|
|
9248
|
-
for (
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
+
for (const [, memberIndices] of attractorToCommunity) {
|
|
9252
|
+
communities.push(
|
|
9253
|
+
memberIndices.map((idx) => {
|
|
9254
|
+
const nodeId = nodeIds[idx];
|
|
9255
|
+
return nodeId;
|
|
9256
|
+
}).filter((node) => node !== void 0)
|
|
9257
|
+
);
|
|
9258
|
+
}
|
|
9259
|
+
for (let j = 0; j < n; j++) {
|
|
9260
|
+
if (!nodeToCluster.has(j)) {
|
|
9261
|
+
const nodeId = nodeIds[j];
|
|
9251
9262
|
if (nodeId !== void 0) {
|
|
9252
9263
|
communities.push([nodeId]);
|
|
9253
9264
|
}
|
|
9254
|
-
nodeToCluster.set(i, clusterIndex++);
|
|
9255
9265
|
}
|
|
9256
9266
|
}
|
|
9257
9267
|
return { communities, attractors };
|
|
@@ -9281,7 +9291,8 @@ function calculateMCLModularity(graph, communities) {
|
|
|
9281
9291
|
return modularity / (2 * m);
|
|
9282
9292
|
}
|
|
9283
9293
|
function spectralClustering(graph, options) {
|
|
9284
|
-
const { k, laplacianType = "normalized", maxIterations = 100, tolerance = 1e-4 } = options;
|
|
9294
|
+
const { k, laplacianType = "normalized", maxIterations = 100, tolerance = 1e-4, seed } = options;
|
|
9295
|
+
const random = seed !== void 0 ? SeededRandom.createGenerator(seed) : Math.random;
|
|
9285
9296
|
if (k < 1 || !Number.isInteger(k)) {
|
|
9286
9297
|
throw new Error("k must be a positive integer");
|
|
9287
9298
|
}
|
|
@@ -9296,7 +9307,7 @@ function spectralClustering(graph, options) {
|
|
|
9296
9307
|
}
|
|
9297
9308
|
const adjacencyMatrix = buildAdjacencyMatrix(graph, nodeIds);
|
|
9298
9309
|
const laplacianMatrix = buildLaplacianMatrix(adjacencyMatrix, laplacianType);
|
|
9299
|
-
const eigenResult = findSmallestEigenvectors(laplacianMatrix, k);
|
|
9310
|
+
const eigenResult = findSmallestEigenvectors(laplacianMatrix, k, random);
|
|
9300
9311
|
const dataPoints = [];
|
|
9301
9312
|
for (let i = 0; i < nodeIds.length; i++) {
|
|
9302
9313
|
const point = [];
|
|
@@ -9311,7 +9322,7 @@ function spectralClustering(graph, options) {
|
|
|
9311
9322
|
if (laplacianType === "normalized") {
|
|
9312
9323
|
normalizeRows(dataPoints);
|
|
9313
9324
|
}
|
|
9314
|
-
const kmeans = kMeansClustering(dataPoints, k, maxIterations, tolerance);
|
|
9325
|
+
const kmeans = kMeansClustering(dataPoints, k, maxIterations, tolerance, random);
|
|
9315
9326
|
const communities = Array.from({ length: k }, () => []);
|
|
9316
9327
|
const clusterAssignments = /* @__PURE__ */ new Map();
|
|
9317
9328
|
for (let i = 0; i < nodeIds.length; i++) {
|
|
@@ -9462,19 +9473,19 @@ function buildLaplacianMatrix(adjacency, type) {
|
|
|
9462
9473
|
}
|
|
9463
9474
|
return laplacian;
|
|
9464
9475
|
}
|
|
9465
|
-
function findSmallestEigenvectors(matrix, k) {
|
|
9476
|
+
function findSmallestEigenvectors(matrix, k, random) {
|
|
9466
9477
|
const n = matrix.length;
|
|
9467
9478
|
if (n === 0 || k === 0) {
|
|
9468
9479
|
return { eigenvalues: [], eigenvectors: [] };
|
|
9469
9480
|
}
|
|
9470
9481
|
if (k <= 3 && n > k) {
|
|
9471
|
-
return computeSmallestEigenvectorsSimple(matrix, k, n);
|
|
9482
|
+
return computeSmallestEigenvectorsSimple(matrix, k, n, random);
|
|
9472
9483
|
}
|
|
9473
9484
|
const eigenvectors = [];
|
|
9474
9485
|
const eigenvalues = [];
|
|
9475
9486
|
const maxIterations = 100;
|
|
9476
9487
|
for (let eigIdx = 0; eigIdx < k; eigIdx++) {
|
|
9477
|
-
let vector = Array(n).fill(0).map(() =>
|
|
9488
|
+
let vector = Array(n).fill(0).map(() => random() - 0.5);
|
|
9478
9489
|
const initNorm = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
|
|
9479
9490
|
if (initNorm > 0) {
|
|
9480
9491
|
vector = vector.map((val) => val / initNorm);
|
|
@@ -9562,7 +9573,7 @@ function normalizeRows(matrix) {
|
|
|
9562
9573
|
}
|
|
9563
9574
|
}
|
|
9564
9575
|
}
|
|
9565
|
-
function kMeansClustering(data, k, maxIterations, tolerance = 1e-4) {
|
|
9576
|
+
function kMeansClustering(data, k, maxIterations, tolerance = 1e-4, random = Math.random) {
|
|
9566
9577
|
const n = data.length;
|
|
9567
9578
|
const d = data[0]?.length ?? 0;
|
|
9568
9579
|
if (n === 0 || k === 0) {
|
|
@@ -9577,7 +9588,7 @@ function kMeansClustering(data, k, maxIterations, tolerance = 1e-4) {
|
|
|
9577
9588
|
const centroids = [];
|
|
9578
9589
|
const selectedIndices = /* @__PURE__ */ new Set();
|
|
9579
9590
|
while (centroids.length < k && selectedIndices.size < n) {
|
|
9580
|
-
const idx = Math.floor(
|
|
9591
|
+
const idx = Math.floor(random() * n);
|
|
9581
9592
|
if (!selectedIndices.has(idx) && data[idx]) {
|
|
9582
9593
|
selectedIndices.add(idx);
|
|
9583
9594
|
centroids.push([...data[idx]]);
|
|
@@ -9586,7 +9597,7 @@ function kMeansClustering(data, k, maxIterations, tolerance = 1e-4) {
|
|
|
9586
9597
|
while (centroids.length < k) {
|
|
9587
9598
|
const centroid = Array(d).fill(0);
|
|
9588
9599
|
for (let j = 0; j < d; j++) {
|
|
9589
|
-
centroid[j] =
|
|
9600
|
+
centroid[j] = random() - 0.5;
|
|
9590
9601
|
}
|
|
9591
9602
|
centroids.push(centroid);
|
|
9592
9603
|
}
|
|
@@ -9682,7 +9693,7 @@ function kMeansClustering(data, k, maxIterations, tolerance = 1e-4) {
|
|
|
9682
9693
|
}
|
|
9683
9694
|
return { assignments, centroids };
|
|
9684
9695
|
}
|
|
9685
|
-
function computeSmallestEigenvectorsSimple(matrix, k, n) {
|
|
9696
|
+
function computeSmallestEigenvectorsSimple(matrix, k, n, random) {
|
|
9686
9697
|
const eigenvectors = [];
|
|
9687
9698
|
const eigenvalues = [];
|
|
9688
9699
|
const firstVector = Array(n).fill(1 / Math.sqrt(n));
|
|
@@ -9690,7 +9701,7 @@ function computeSmallestEigenvectorsSimple(matrix, k, n) {
|
|
|
9690
9701
|
eigenvalues.push(0);
|
|
9691
9702
|
if (k >= 2) {
|
|
9692
9703
|
const maxEig = 2;
|
|
9693
|
-
let vector = Array(n).fill(0).map(() =>
|
|
9704
|
+
let vector = Array(n).fill(0).map(() => random() - 0.5);
|
|
9694
9705
|
const dot1 = vector.reduce((sum, val) => sum + val / Math.sqrt(n), 0);
|
|
9695
9706
|
vector = vector.map((val) => val - dot1 / Math.sqrt(n));
|
|
9696
9707
|
for (let iter = 0; iter < 100; iter++) {
|
|
@@ -9718,7 +9729,7 @@ function computeSmallestEigenvectorsSimple(matrix, k, n) {
|
|
|
9718
9729
|
eigenvalues.push(0.1);
|
|
9719
9730
|
}
|
|
9720
9731
|
if (k >= 3) {
|
|
9721
|
-
let vector = Array(n).fill(0).map(() =>
|
|
9732
|
+
let vector = Array(n).fill(0).map(() => random() - 0.5);
|
|
9722
9733
|
for (const prev of eigenvectors) {
|
|
9723
9734
|
const dot = vector.reduce((sum, val, idx) => sum + val * (prev[idx] ?? 0), 0);
|
|
9724
9735
|
vector = vector.map((val, idx) => val - dot * (prev[idx] ?? 0));
|