@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 CHANGED
@@ -5,6 +5,7 @@
5
5
  [![npm version](https://img.shields.io/npm/v/@graphty/algorithms.svg)](https://www.npmjs.com/package/@graphty/algorithms)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Documentation](https://img.shields.io/badge/docs-vitepress-blue)](https://graphty.app/docs/algorithms/)
8
+ [![Storybook](https://img.shields.io/badge/storybook-interactive%20demos-ff4785)](https://graphty.app/storybook/algorithms/)
8
9
  [![Examples](https://img.shields.io/badge/demo-github%20pages-blue)](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:
@@ -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 [source, target] = edgeKey.split("|");
6382
- if (source && target) {
6383
- edgesToRemove.push({ source, target });
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 (!nodeId) {
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
- let clusterIndex = 0;
9226
+ const attractorToCommunity = /* @__PURE__ */ new Map();
9225
9227
  for (let j = 0; j < n; j++) {
9226
- const clusterNodes = [];
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 > 0 && !nodeToCluster.has(i)) {
9234
- clusterNodes.push(i);
9235
- nodeToCluster.set(i, clusterIndex);
9236
+ if (val !== void 0 && val > maxVal) {
9237
+ maxVal = val;
9238
+ attractorRow = i;
9236
9239
  }
9237
9240
  }
9238
- if (clusterNodes.length > 0) {
9239
- communities.push(
9240
- clusterNodes.map((idx) => {
9241
- const nodeId = nodeIds[idx];
9242
- return nodeId;
9243
- }).filter((node) => node !== void 0)
9244
- );
9245
- clusterIndex++;
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 (let i = 0; i < n; i++) {
9249
- if (!nodeToCluster.has(i)) {
9250
- const nodeId = nodeIds[i];
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(() => Math.random() - 0.5);
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(Math.random() * n);
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] = Math.random() - 0.5;
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(() => Math.random() - 0.5);
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(() => Math.random() - 0.5);
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));