@graphty/layout 1.1.0 → 1.1.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.
Files changed (43) hide show
  1. package/.github/workflows/ci.yml +89 -14
  2. package/.releaserc.json +22 -0
  3. package/CHANGELOG.md +24 -0
  4. package/CLAUDE.md +104 -0
  5. package/CONTRIBUTING.md +1 -0
  6. package/README.md +609 -93
  7. package/dist/layout-helpers.d.ts +123 -0
  8. package/dist/layout-helpers.js +457 -0
  9. package/dist/layout-helpers.js.map +1 -0
  10. package/dist/layout.d.ts +275 -0
  11. package/dist/layout.js +2280 -0
  12. package/dist/layout.js.map +1 -0
  13. package/dist/vitest.config.d.ts +2 -0
  14. package/dist/vitest.config.js +30 -0
  15. package/dist/vitest.config.js.map +1 -0
  16. package/examples/bfs-layout.html +37 -39
  17. package/examples/bipartite-layout.html +77 -69
  18. package/examples/circular-layout.html +13 -34
  19. package/examples/forceatlas2-layout.html +122 -28
  20. package/examples/multipartite-layout.html +64 -51
  21. package/examples/shell-layout.html +53 -34
  22. package/examples/spring-layout.html +11 -1
  23. package/layout-helpers.ts +559 -0
  24. package/layout.ts +277 -1
  25. package/package.json +17 -6
  26. package/test/arf-layout.test.ts +443 -0
  27. package/test/bfs-layout.test.ts +427 -0
  28. package/test/bipartite-layout.test.ts +344 -0
  29. package/test/circular-layout.test.ts +300 -0
  30. package/test/forceatlas2-layout.test.ts +405 -0
  31. package/test/fruchterman-reingold-layout.test.ts +477 -0
  32. package/test/graph-generators.test.ts +450 -0
  33. package/test/kamada-kawai-layout.test.ts +351 -0
  34. package/test/multipartite-layout.test.ts +404 -0
  35. package/test/planar-layout.test.ts +266 -0
  36. package/test/random-layout.test.ts +254 -0
  37. package/test/rescale-layout.test.ts +373 -0
  38. package/test/shell-layout.test.ts +347 -0
  39. package/test/spectral-layout.test.ts +378 -0
  40. package/test/spiral-layout.test.ts +338 -0
  41. package/test/spring-layout.test.ts +241 -0
  42. package/vitest.config.ts +30 -0
  43. package/.releaserc +0 -3
package/layout.ts CHANGED
@@ -2570,6 +2570,273 @@ function rescaleLayoutDict(
2570
2570
  return scaledPos;
2571
2571
  }
2572
2572
 
2573
+ // Graph generation utilities
2574
+ /**
2575
+ * Create a complete graph with n nodes
2576
+ * @param n - Number of nodes
2577
+ * @returns Graph object with all nodes connected to all other nodes
2578
+ */
2579
+ function completeGraph(n: number): Graph {
2580
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
2581
+ const edges: Edge[] = [];
2582
+
2583
+ for (let i = 0; i < n; i++) {
2584
+ for (let j = i + 1; j < n; j++) {
2585
+ edges.push([i, j]);
2586
+ }
2587
+ }
2588
+
2589
+ return {
2590
+ nodes: () => nodes,
2591
+ edges: () => edges
2592
+ };
2593
+ }
2594
+
2595
+ /**
2596
+ * Create a cycle graph with n nodes
2597
+ * @param n - Number of nodes
2598
+ * @returns Graph object with nodes connected in a cycle
2599
+ */
2600
+ function cycleGraph(n: number): Graph {
2601
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
2602
+ const edges: Edge[] = [];
2603
+
2604
+ for (let i = 0; i < n; i++) {
2605
+ edges.push([i, (i + 1) % n]);
2606
+ }
2607
+
2608
+ return {
2609
+ nodes: () => nodes,
2610
+ edges: () => edges
2611
+ };
2612
+ }
2613
+
2614
+ /**
2615
+ * Create a star graph with n nodes (1 center + n-1 leaves)
2616
+ * @param n - Total number of nodes
2617
+ * @returns Graph object with star topology
2618
+ */
2619
+ function starGraph(n: number): Graph {
2620
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
2621
+ const edges: Edge[] = [];
2622
+
2623
+ // Connect all nodes to node 0 (center)
2624
+ for (let i = 1; i < n; i++) {
2625
+ edges.push([0, i]);
2626
+ }
2627
+
2628
+ return {
2629
+ nodes: () => nodes,
2630
+ edges: () => edges
2631
+ };
2632
+ }
2633
+
2634
+ /**
2635
+ * Create a wheel graph with n nodes (1 center + n-1 rim nodes)
2636
+ * @param n - Total number of nodes
2637
+ * @returns Graph object with wheel topology
2638
+ */
2639
+ function wheelGraph(n: number): Graph {
2640
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
2641
+ const edges: Edge[] = [];
2642
+
2643
+ // Connect all rim nodes to center (node 0)
2644
+ for (let i = 1; i < n; i++) {
2645
+ edges.push([0, i]);
2646
+ }
2647
+
2648
+ // Connect rim nodes in a cycle
2649
+ for (let i = 1; i < n - 1; i++) {
2650
+ edges.push([i, i + 1]);
2651
+ }
2652
+ if (n > 2) {
2653
+ edges.push([n - 1, 1]);
2654
+ }
2655
+
2656
+ return {
2657
+ nodes: () => nodes,
2658
+ edges: () => edges
2659
+ };
2660
+ }
2661
+
2662
+ /**
2663
+ * Create a grid graph with rows x cols nodes
2664
+ * @param rows - Number of rows
2665
+ * @param cols - Number of columns
2666
+ * @returns Graph object with grid topology
2667
+ */
2668
+ function gridGraph(rows: number, cols: number): Graph {
2669
+ const nodes: Node[] = [];
2670
+ const edges: Edge[] = [];
2671
+
2672
+ // Create nodes
2673
+ for (let i = 0; i < rows; i++) {
2674
+ for (let j = 0; j < cols; j++) {
2675
+ nodes.push(`${i},${j}`);
2676
+ }
2677
+ }
2678
+
2679
+ // Create edges
2680
+ for (let i = 0; i < rows; i++) {
2681
+ for (let j = 0; j < cols; j++) {
2682
+ // Connect to right neighbor
2683
+ if (j < cols - 1) {
2684
+ edges.push([`${i},${j}`, `${i},${j + 1}`]);
2685
+ }
2686
+ // Connect to bottom neighbor
2687
+ if (i < rows - 1) {
2688
+ edges.push([`${i},${j}`, `${i + 1},${j}`]);
2689
+ }
2690
+ }
2691
+ }
2692
+
2693
+ return {
2694
+ nodes: () => nodes,
2695
+ edges: () => edges
2696
+ };
2697
+ }
2698
+
2699
+ /**
2700
+ * Create a random graph with n nodes and given edge probability
2701
+ * @param n - Number of nodes
2702
+ * @param p - Probability of edge between any two nodes (0-1)
2703
+ * @param seed - Random seed for reproducibility
2704
+ * @returns Graph object with random edges
2705
+ */
2706
+ function randomGraph(n: number, p: number, seed?: number): Graph {
2707
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
2708
+ const edges: Edge[] = [];
2709
+
2710
+ // Simple deterministic pseudo-random if seed provided
2711
+ let currentSeed = seed;
2712
+ let random = seed !== undefined
2713
+ ? () => {
2714
+ currentSeed = (currentSeed! * 9301 + 49297) % 233280;
2715
+ return currentSeed / 233280;
2716
+ }
2717
+ : Math.random;
2718
+
2719
+ for (let i = 0; i < n; i++) {
2720
+ for (let j = i + 1; j < n; j++) {
2721
+ if (random() < p) {
2722
+ edges.push([i, j]);
2723
+ }
2724
+ }
2725
+ }
2726
+
2727
+ return {
2728
+ nodes: () => nodes,
2729
+ edges: () => edges
2730
+ };
2731
+ }
2732
+
2733
+ /**
2734
+ * Create a bipartite graph with two sets of nodes
2735
+ * @param n1 - Number of nodes in first set
2736
+ * @param n2 - Number of nodes in second set
2737
+ * @param p - Probability of edge between nodes in different sets
2738
+ * @param seed - Random seed for reproducibility
2739
+ * @returns Graph object with bipartite structure and setA/setB properties
2740
+ */
2741
+ function bipartiteGraph(n1: number, n2: number, p: number, seed?: number): Graph & { setA: Node[], setB: Node[] } {
2742
+ const setA: Node[] = Array.from({ length: n1 }, (_, i) => `A${i}`);
2743
+ const setB: Node[] = Array.from({ length: n2 }, (_, i) => `B${i}`);
2744
+ const nodes = [...setA, ...setB];
2745
+ const edges: Edge[] = [];
2746
+
2747
+ // Simple deterministic pseudo-random if seed provided
2748
+ let currentSeed = seed;
2749
+ let random = seed !== undefined
2750
+ ? () => {
2751
+ currentSeed = (currentSeed! * 9301 + 49297) % 233280;
2752
+ return currentSeed / 233280;
2753
+ }
2754
+ : Math.random;
2755
+
2756
+ // Only connect nodes between sets
2757
+ for (const a of setA) {
2758
+ for (const b of setB) {
2759
+ if (random() < p) {
2760
+ edges.push([a, b]);
2761
+ }
2762
+ }
2763
+ }
2764
+
2765
+ return {
2766
+ nodes: () => nodes,
2767
+ edges: () => edges,
2768
+ setA,
2769
+ setB
2770
+ };
2771
+ }
2772
+
2773
+ /**
2774
+ * Create a scale-free graph using Barabási-Albert model
2775
+ * @param n - Total number of nodes
2776
+ * @param m - Number of edges to attach from new node
2777
+ * @param seed - Random seed for reproducibility
2778
+ * @returns Graph object with scale-free properties
2779
+ */
2780
+ function scaleFreeGraph(n: number, m: number, seed?: number): Graph {
2781
+ if (m >= n) {
2782
+ throw new Error('m must be less than n');
2783
+ }
2784
+
2785
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
2786
+ const edges: Edge[] = [];
2787
+ const degrees = new Array(n).fill(0);
2788
+
2789
+ // Simple deterministic pseudo-random if seed provided
2790
+ let currentSeed = seed;
2791
+ let random = seed !== undefined
2792
+ ? () => {
2793
+ currentSeed = (currentSeed! * 9301 + 49297) % 233280;
2794
+ return currentSeed / 233280;
2795
+ }
2796
+ : Math.random;
2797
+
2798
+ // Start with complete graph of m+1 nodes
2799
+ for (let i = 0; i <= m; i++) {
2800
+ for (let j = i + 1; j <= m; j++) {
2801
+ edges.push([i, j]);
2802
+ degrees[i]++;
2803
+ degrees[j]++;
2804
+ }
2805
+ }
2806
+
2807
+ // Add remaining nodes
2808
+ for (let i = m + 1; i < n; i++) {
2809
+ const targets = new Set<number>();
2810
+ const totalDegree = degrees.reduce((sum, d) => sum + d, 0);
2811
+
2812
+ // Choose m targets based on preferential attachment
2813
+ while (targets.size < m) {
2814
+ let r = random() * totalDegree;
2815
+ let cumSum = 0;
2816
+
2817
+ for (let j = 0; j < i; j++) {
2818
+ cumSum += degrees[j];
2819
+ if (r <= cumSum && !targets.has(j)) {
2820
+ targets.add(j);
2821
+ break;
2822
+ }
2823
+ }
2824
+ }
2825
+
2826
+ // Add edges to targets
2827
+ for (const target of targets) {
2828
+ edges.push([i, target]);
2829
+ degrees[i]++;
2830
+ degrees[target]++;
2831
+ }
2832
+ }
2833
+
2834
+ return {
2835
+ nodes: () => nodes,
2836
+ edges: () => edges
2837
+ };
2838
+ }
2839
+
2573
2840
  // Export the layout functions
2574
2841
  export {
2575
2842
  randomLayout,
@@ -2587,5 +2854,14 @@ export {
2587
2854
  forceatlas2Layout,
2588
2855
  arfLayout,
2589
2856
  rescaleLayout,
2590
- rescaleLayoutDict
2857
+ rescaleLayoutDict,
2858
+ // Graph generation utilities
2859
+ completeGraph,
2860
+ cycleGraph,
2861
+ starGraph,
2862
+ wheelGraph,
2863
+ gridGraph,
2864
+ randomGraph,
2865
+ bipartiteGraph,
2866
+ scaleFreeGraph
2591
2867
  };
package/package.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "@graphty/layout",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "graph layout algorithms based on networkx",
5
- "main": "layout.js",
5
+ "main": "dist/layout.js",
6
6
  "type": "module",
7
7
  "directories": {
8
8
  "example": "examples"
9
9
  },
10
10
  "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1",
11
+ "test": "vitest",
12
+ "test:ui": "vitest --ui",
13
+ "test:run": "vitest run",
14
+ "test:coverage": "vitest run --coverage",
12
15
  "prepare": "husky",
13
16
  "build": "tsc",
14
17
  "watch": "tsc --watch",
15
- "dev": "tsc --watch"
18
+ "dev": "tsc --watch",
19
+ "commit": "cz"
16
20
  },
17
21
  "repository": {
18
22
  "type": "git",
@@ -37,8 +41,15 @@
37
41
  "devDependencies": {
38
42
  "@commitlint/cli": "^19.8.1",
39
43
  "@commitlint/config-conventional": "^19.8.1",
44
+ "@semantic-release/changelog": "^6.0.3",
45
+ "@semantic-release/git": "^10.0.1",
46
+ "@vitest/coverage-v8": "^3.2.4",
47
+ "@vitest/ui": "^3.2.4",
40
48
  "cz-conventional-changelog": "^3.3.0",
49
+ "happy-dom": "^18.0.1",
41
50
  "husky": "^9.1.7",
42
- "typescript": "^5.3.3"
51
+ "semantic-release": "^24.2.7",
52
+ "typescript": "^5.3.3",
53
+ "vitest": "^3.2.4"
43
54
  }
44
- }
55
+ }