@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.
- package/.github/workflows/ci.yml +89 -14
- package/.releaserc.json +22 -0
- package/CHANGELOG.md +24 -0
- package/CLAUDE.md +104 -0
- package/CONTRIBUTING.md +1 -0
- package/README.md +609 -93
- package/dist/layout-helpers.d.ts +123 -0
- package/dist/layout-helpers.js +457 -0
- package/dist/layout-helpers.js.map +1 -0
- package/dist/layout.d.ts +275 -0
- package/dist/layout.js +2280 -0
- package/dist/layout.js.map +1 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +30 -0
- package/dist/vitest.config.js.map +1 -0
- package/examples/bfs-layout.html +37 -39
- package/examples/bipartite-layout.html +77 -69
- package/examples/circular-layout.html +13 -34
- package/examples/forceatlas2-layout.html +122 -28
- package/examples/multipartite-layout.html +64 -51
- package/examples/shell-layout.html +53 -34
- package/examples/spring-layout.html +11 -1
- package/layout-helpers.ts +559 -0
- package/layout.ts +277 -1
- package/package.json +17 -6
- package/test/arf-layout.test.ts +443 -0
- package/test/bfs-layout.test.ts +427 -0
- package/test/bipartite-layout.test.ts +344 -0
- package/test/circular-layout.test.ts +300 -0
- package/test/forceatlas2-layout.test.ts +405 -0
- package/test/fruchterman-reingold-layout.test.ts +477 -0
- package/test/graph-generators.test.ts +450 -0
- package/test/kamada-kawai-layout.test.ts +351 -0
- package/test/multipartite-layout.test.ts +404 -0
- package/test/planar-layout.test.ts +266 -0
- package/test/random-layout.test.ts +254 -0
- package/test/rescale-layout.test.ts +373 -0
- package/test/shell-layout.test.ts +347 -0
- package/test/spectral-layout.test.ts +378 -0
- package/test/spiral-layout.test.ts +338 -0
- package/test/spring-layout.test.ts +241 -0
- package/vitest.config.ts +30 -0
- 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.
|
|
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": "
|
|
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
|
-
"
|
|
51
|
+
"semantic-release": "^24.2.7",
|
|
52
|
+
"typescript": "^5.3.3",
|
|
53
|
+
"vitest": "^3.2.4"
|
|
43
54
|
}
|
|
44
|
-
}
|
|
55
|
+
}
|