polly-graph 0.1.4 → 0.1.5
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 +82 -159
- package/dist/index.cjs +78 -124
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +78 -124
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,14 +4,10 @@ Reusable D3-based graph visualization SDK with configurable nodes, links, labels
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Drag and zoom interactions
|
|
12
|
-
* Arrow markers and relationship visualization
|
|
13
|
-
* Framework-agnostic API
|
|
14
|
-
* Angular and React compatible
|
|
7
|
+
* **Managed Root Architecture**: Provide one host element; the SDK internally manages the SVG canvas and the HTML UI overlay.
|
|
8
|
+
* **Smart Positioning**: Controls and legends use a class-based system (`pg-pos-top-right`) with CSS variable overrides for precision offsets.
|
|
9
|
+
* **Declarative Styling**: Fully customizable node and link aesthetics via style objects.
|
|
10
|
+
* **Animated UI**: Legends feature directional "retraction" animations that sync with their anchor position.
|
|
15
11
|
|
|
16
12
|
---
|
|
17
13
|
|
|
@@ -21,89 +17,46 @@ Reusable D3-based graph visualization SDK with configurable nodes, links, labels
|
|
|
21
17
|
npm install polly-graph
|
|
22
18
|
```
|
|
23
19
|
|
|
24
|
-
No separate D3 installation is required.
|
|
25
|
-
|
|
26
20
|
---
|
|
27
21
|
|
|
28
22
|
## Basic Usage
|
|
29
23
|
|
|
30
24
|
### HTML
|
|
31
|
-
|
|
32
25
|
```html
|
|
33
|
-
<div
|
|
34
|
-
<svg id="graph-container"></svg>
|
|
35
|
-
</div>
|
|
26
|
+
<div id="graph-viewport" style="position: relative; width: 100%; height: 600px;"></div>
|
|
36
27
|
```
|
|
37
28
|
|
|
38
29
|
### TypeScript
|
|
39
|
-
|
|
40
30
|
```ts
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
GraphLink,
|
|
45
|
-
} from 'polly-graph';
|
|
46
|
-
|
|
47
|
-
const container = document.getElementById(
|
|
48
|
-
'graph-container',
|
|
49
|
-
) as SVGSVGElement;
|
|
50
|
-
|
|
51
|
-
const nodes: GraphNode[] = [
|
|
52
|
-
{
|
|
53
|
-
id: 'users',
|
|
54
|
-
label: 'Users',
|
|
55
|
-
style: {
|
|
56
|
-
radius: 24,
|
|
57
|
-
fill: '#7c3aed',
|
|
58
|
-
stroke: '#6d28d9',
|
|
59
|
-
strokeWidth: 2,
|
|
60
|
-
textColor: '#ffffff',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: 'orders',
|
|
65
|
-
label: 'Orders',
|
|
66
|
-
style: {
|
|
67
|
-
radius: 24,
|
|
68
|
-
fill: '#2563eb',
|
|
69
|
-
stroke: '#1d4ed8',
|
|
70
|
-
strokeWidth: 2,
|
|
71
|
-
textColor: '#ffffff',
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
const links: GraphLink[] = [
|
|
77
|
-
{
|
|
78
|
-
source: 'users',
|
|
79
|
-
target: 'orders',
|
|
80
|
-
label: 'has_many',
|
|
81
|
-
style: {
|
|
82
|
-
stroke: '#94a3b8',
|
|
83
|
-
strokeWidth: 2,
|
|
84
|
-
opacity: 1,
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
];
|
|
31
|
+
import { createGraph } from 'polly-graph';
|
|
32
|
+
|
|
33
|
+
const viewport = document.getElementById('graph-viewport') as HTMLElement;
|
|
88
34
|
|
|
89
35
|
const graph = createGraph({
|
|
90
|
-
container,
|
|
91
|
-
nodes
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
36
|
+
container: viewport,
|
|
37
|
+
nodes: [
|
|
38
|
+
{
|
|
39
|
+
id: 'n1',
|
|
40
|
+
label: 'Core Service',
|
|
41
|
+
style: {
|
|
42
|
+
radius: 30,
|
|
43
|
+
fill: '#7c3aed',
|
|
44
|
+
stroke: '#5b21b6',
|
|
45
|
+
strokeWidth: 2,
|
|
46
|
+
textColor: '#ffffff',
|
|
47
|
+
fontSize: 12
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
links: [
|
|
52
|
+
{
|
|
53
|
+
source: 'n1',
|
|
54
|
+
target: 'n2',
|
|
55
|
+
style: { stroke: '#94a3b8', strokeWidth: 2, opacity: 0.6 }
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
controls: { enabled: true, position: 'top-right' },
|
|
59
|
+
legend: { enabled: true, position: 'bottom-left' }
|
|
107
60
|
});
|
|
108
61
|
|
|
109
62
|
graph.render();
|
|
@@ -111,107 +64,77 @@ graph.render();
|
|
|
111
64
|
|
|
112
65
|
---
|
|
113
66
|
|
|
114
|
-
##
|
|
115
|
-
|
|
116
|
-
```ts
|
|
117
|
-
interaction: {
|
|
118
|
-
hover: {
|
|
119
|
-
enabled: true,
|
|
120
|
-
tooltip: {
|
|
121
|
-
enabled: true,
|
|
122
|
-
theme: 'dark',
|
|
123
|
-
},
|
|
124
|
-
nodeStyle: {
|
|
125
|
-
stroke: '#16a34a',
|
|
126
|
-
strokeWidth: 3,
|
|
127
|
-
opacity: 1,
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
---
|
|
67
|
+
## Styling & Customization
|
|
134
68
|
|
|
135
|
-
|
|
69
|
+
### Node Styles
|
|
70
|
+
Every node can have a unique appearance defined in its `style` object.
|
|
71
|
+
| Property | Type | Description |
|
|
72
|
+
| :--- | :--- | :--- |
|
|
73
|
+
| `radius` | `number` | The size of the node. |
|
|
74
|
+
| `fill` | `string` | Background color (hex/rgb). |
|
|
75
|
+
| `stroke` | `string` | Border color. |
|
|
76
|
+
| `strokeWidth`| `number` | Thickness of the border. |
|
|
77
|
+
| `textColor` | `string` | Label color. |
|
|
136
78
|
|
|
79
|
+
### Link Styles
|
|
80
|
+
Links support custom coloring, thickness, and arrow markers.
|
|
137
81
|
```ts
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
strokeWidth: 4,
|
|
144
|
-
opacity: 1,
|
|
145
|
-
},
|
|
82
|
+
style: {
|
|
83
|
+
stroke: '#cbd5e1',
|
|
84
|
+
strokeWidth: 1.5,
|
|
85
|
+
opacity: 0.8,
|
|
86
|
+
dashed: false // Coming soon
|
|
146
87
|
}
|
|
147
88
|
```
|
|
148
89
|
|
|
149
90
|
---
|
|
150
91
|
|
|
151
|
-
##
|
|
92
|
+
## Positioning Logic
|
|
152
93
|
|
|
153
|
-
|
|
94
|
+
The UI components (Controls & Legend) use a hybrid positioning system.
|
|
154
95
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<svg #svgRef></svg>
|
|
158
|
-
</div>
|
|
159
|
-
```
|
|
96
|
+
### 1. Corner Anchoring
|
|
97
|
+
Use the `position` property to anchor elements to viewport corners. The SDK applies classes like `.pg-pos-top-right` which handles the layout logic.
|
|
160
98
|
|
|
161
|
-
|
|
99
|
+
Available positions:
|
|
100
|
+
* `top-left`
|
|
101
|
+
* `top-right`
|
|
102
|
+
* `bottom-left`
|
|
103
|
+
* `bottom-right`
|
|
162
104
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
container: this.svgRef.nativeElement,
|
|
166
|
-
nodes,
|
|
167
|
-
links,
|
|
168
|
-
interaction,
|
|
169
|
-
});
|
|
105
|
+
### 2. Custom Offsets
|
|
106
|
+
While the corners provide the anchor, you can use the `offset` object for fine-tuning. This values are passed into CSS variables `--pg-offset-x` and `--pg-offset-y` internally.
|
|
170
107
|
|
|
171
|
-
|
|
108
|
+
```ts
|
|
109
|
+
controls: {
|
|
110
|
+
position: 'top-right',
|
|
111
|
+
offset: { x: 24, y: 24 } // 24px away from the top and right edges
|
|
112
|
+
}
|
|
172
113
|
```
|
|
173
114
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
### Main Exports
|
|
179
|
-
|
|
180
|
-
* `createGraph`
|
|
181
|
-
* `GraphInstance`
|
|
182
|
-
* `GraphNode`
|
|
183
|
-
* `GraphLink`
|
|
184
|
-
* `GraphConfig`
|
|
185
|
-
* `GraphInteractionConfig`
|
|
115
|
+
### 3. Directional Legend Retraction
|
|
116
|
+
The Legend component is aware of its position.
|
|
117
|
+
* If anchored **Left**: It collapses to the left; the toggle icon points **Right** to expand.
|
|
118
|
+
* If anchored **Right**: It collapses to the right; the toggle icon points **Left** to expand.
|
|
186
119
|
|
|
187
120
|
---
|
|
188
121
|
|
|
189
|
-
##
|
|
122
|
+
## Framework Integration
|
|
190
123
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
124
|
+
### Angular
|
|
125
|
+
```ts
|
|
126
|
+
@ViewChild('viewport') viewport!: ElementRef;
|
|
127
|
+
|
|
128
|
+
ngAfterViewInit() {
|
|
129
|
+
this.graph = createGraph({
|
|
130
|
+
container: this.viewport.nativeElement,
|
|
131
|
+
// ... config
|
|
132
|
+
});
|
|
133
|
+
this.graph.render();
|
|
134
|
+
}
|
|
198
135
|
```
|
|
199
136
|
|
|
200
137
|
---
|
|
201
138
|
|
|
202
|
-
## Publish Checklist
|
|
203
|
-
|
|
204
|
-
* Package name available on npm
|
|
205
|
-
* README completed
|
|
206
|
-
* LICENSE added
|
|
207
|
-
* Build succeeds
|
|
208
|
-
* Tests pass
|
|
209
|
-
* npm token configured
|
|
210
|
-
* GitHub workflow passes
|
|
211
|
-
* Repository is public
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
139
|
## License
|
|
216
|
-
|
|
217
|
-
MIT
|
|
140
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -3644,68 +3644,6 @@ function manyBody_default() {
|
|
|
3644
3644
|
return force;
|
|
3645
3645
|
}
|
|
3646
3646
|
|
|
3647
|
-
// node_modules/d3-force/src/x.js
|
|
3648
|
-
function x_default2(x3) {
|
|
3649
|
-
var strength = constant_default5(0.1), nodes, strengths, xz;
|
|
3650
|
-
if (typeof x3 !== "function") x3 = constant_default5(x3 == null ? 0 : +x3);
|
|
3651
|
-
function force(alpha) {
|
|
3652
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
3653
|
-
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha;
|
|
3654
|
-
}
|
|
3655
|
-
}
|
|
3656
|
-
function initialize() {
|
|
3657
|
-
if (!nodes) return;
|
|
3658
|
-
var i, n = nodes.length;
|
|
3659
|
-
strengths = new Array(n);
|
|
3660
|
-
xz = new Array(n);
|
|
3661
|
-
for (i = 0; i < n; ++i) {
|
|
3662
|
-
strengths[i] = isNaN(xz[i] = +x3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
force.initialize = function(_) {
|
|
3666
|
-
nodes = _;
|
|
3667
|
-
initialize();
|
|
3668
|
-
};
|
|
3669
|
-
force.strength = function(_) {
|
|
3670
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
|
|
3671
|
-
};
|
|
3672
|
-
force.x = function(_) {
|
|
3673
|
-
return arguments.length ? (x3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : x3;
|
|
3674
|
-
};
|
|
3675
|
-
return force;
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
|
-
// node_modules/d3-force/src/y.js
|
|
3679
|
-
function y_default2(y3) {
|
|
3680
|
-
var strength = constant_default5(0.1), nodes, strengths, yz;
|
|
3681
|
-
if (typeof y3 !== "function") y3 = constant_default5(y3 == null ? 0 : +y3);
|
|
3682
|
-
function force(alpha) {
|
|
3683
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
3684
|
-
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha;
|
|
3685
|
-
}
|
|
3686
|
-
}
|
|
3687
|
-
function initialize() {
|
|
3688
|
-
if (!nodes) return;
|
|
3689
|
-
var i, n = nodes.length;
|
|
3690
|
-
strengths = new Array(n);
|
|
3691
|
-
yz = new Array(n);
|
|
3692
|
-
for (i = 0; i < n; ++i) {
|
|
3693
|
-
strengths[i] = isNaN(yz[i] = +y3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
force.initialize = function(_) {
|
|
3697
|
-
nodes = _;
|
|
3698
|
-
initialize();
|
|
3699
|
-
};
|
|
3700
|
-
force.strength = function(_) {
|
|
3701
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
|
|
3702
|
-
};
|
|
3703
|
-
force.y = function(_) {
|
|
3704
|
-
return arguments.length ? (y3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : y3;
|
|
3705
|
-
};
|
|
3706
|
-
return force;
|
|
3707
|
-
}
|
|
3708
|
-
|
|
3709
3647
|
// src/core/create-graph-layers.ts
|
|
3710
3648
|
function createGraphLayers(host) {
|
|
3711
3649
|
host.innerHTML = "";
|
|
@@ -3814,10 +3752,17 @@ function createGraphSimulation(config) {
|
|
|
3814
3752
|
});
|
|
3815
3753
|
const simulation = simulation_default(config.nodes).alpha(0.9).alphaDecay(0.12).alphaMin(0.03).velocityDecay(0.5).force(
|
|
3816
3754
|
"link",
|
|
3817
|
-
link_default(config.links).id((d) => d.id).distance(
|
|
3755
|
+
link_default(config.links).id((d) => d.id).distance((d) => {
|
|
3756
|
+
const source = d.source;
|
|
3757
|
+
const target = d.target;
|
|
3758
|
+
const sourceR = source.style?.radius || 20;
|
|
3759
|
+
const targetR = target.style?.radius || 20;
|
|
3760
|
+
const labelBuffer = d.style?.label?.height || 40;
|
|
3761
|
+
return (sourceR + targetR + labelBuffer) * 2;
|
|
3762
|
+
}).strength(0.8)
|
|
3818
3763
|
).force("charge", manyBody_default().strength(-220)).force(
|
|
3819
3764
|
"collide",
|
|
3820
|
-
collide_default().radius((node) => (node.style?.radius ?? 12) + 10).
|
|
3765
|
+
collide_default().radius((node) => (node.style?.radius ?? 12) + 10).iterations(2)
|
|
3821
3766
|
).force("center", center_default(centerX, centerY).strength(0.08));
|
|
3822
3767
|
return { simulation };
|
|
3823
3768
|
}
|
|
@@ -4000,6 +3945,7 @@ var DEFAULT_LINK_STYLE = {
|
|
|
4000
3945
|
},
|
|
4001
3946
|
label: {
|
|
4002
3947
|
enabled: true,
|
|
3948
|
+
visibility: "always",
|
|
4003
3949
|
backgroundFill: "color-mix(in srgb, #8E42EE, #FFFFFF 90%)",
|
|
4004
3950
|
borderColor: "color-mix(in srgb, #8E42EE, #FFFFFF 10%)",
|
|
4005
3951
|
borderWidth: 1.5,
|
|
@@ -4035,6 +3981,7 @@ function mergeLinkStyle(base, override) {
|
|
|
4035
3981
|
},
|
|
4036
3982
|
label: {
|
|
4037
3983
|
enabled: override?.label?.enabled ?? base.label.enabled,
|
|
3984
|
+
visibility: override?.label?.visibility ?? base.label.visibility,
|
|
4038
3985
|
backgroundFill: override?.label?.backgroundFill ?? base.label.backgroundFill,
|
|
4039
3986
|
borderColor: override?.label?.borderColor ?? base.label.borderColor,
|
|
4040
3987
|
borderWidth: override?.label?.borderWidth ?? base.label.borderWidth,
|
|
@@ -4135,7 +4082,14 @@ function getLinkKey(link) {
|
|
|
4135
4082
|
}
|
|
4136
4083
|
function renderLinks(ctx, links) {
|
|
4137
4084
|
const renderableLinks = createRenderableLinks(ctx, links);
|
|
4138
|
-
|
|
4085
|
+
const linkSelection = ctx.root.select('[data-layer="links"]').selectAll("line").data(renderableLinks, (item) => getLinkKey(item.link)).join("line").attr("class", "graph-link").attr("stroke", (item) => item.style.stroke).attr("stroke-width", (item) => item.style.strokeWidth).attr("opacity", (item) => item.style.opacity).attr("marker-end", (item) => item.markerEnd).style("pointer-events", "stroke");
|
|
4086
|
+
const labelSelection = ctx.root.selectAll(".link-label");
|
|
4087
|
+
linkSelection.on("mouseenter.label-hover", (_event, d) => {
|
|
4088
|
+
labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 1);
|
|
4089
|
+
}).on("mouseleave.label-hover", (_event, d) => {
|
|
4090
|
+
labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 0);
|
|
4091
|
+
});
|
|
4092
|
+
return linkSelection;
|
|
4139
4093
|
}
|
|
4140
4094
|
|
|
4141
4095
|
// src/renderer/nodes.ts
|
|
@@ -4205,7 +4159,10 @@ function renderNodeLabels(ctx, nodes) {
|
|
|
4205
4159
|
// src/renderer/link-labels.ts
|
|
4206
4160
|
function createRenderableLinks2(params, links) {
|
|
4207
4161
|
return links.map(
|
|
4208
|
-
(link) => ({
|
|
4162
|
+
(link) => ({
|
|
4163
|
+
link,
|
|
4164
|
+
style: resolveLinkStyle({ link, interaction: params.interaction })
|
|
4165
|
+
})
|
|
4209
4166
|
).filter(
|
|
4210
4167
|
(item) => item.style.label.enabled && Boolean(item.link.label)
|
|
4211
4168
|
);
|
|
@@ -4217,7 +4174,13 @@ function getLinkKey2(link) {
|
|
|
4217
4174
|
}
|
|
4218
4175
|
function renderLinkLabels(params, links) {
|
|
4219
4176
|
const renderableLinks = createRenderableLinks2(params, links);
|
|
4220
|
-
const labelSelection = params.root.select('[data-layer="link-labels"]').selectAll(".link-label").data(renderableLinks, (item) => getLinkKey2(item.link)).join("g").attr("class", "link-label").
|
|
4177
|
+
const labelSelection = params.root.select('[data-layer="link-labels"]').selectAll(".link-label").data(renderableLinks, (item) => getLinkKey2(item.link)).join("g").attr("class", "link-label").style("opacity", (item) => {
|
|
4178
|
+
const visibility = item.style.label.visibility ?? "always";
|
|
4179
|
+
return visibility === "always" ? 1 : 0;
|
|
4180
|
+
}).style("pointer-events", (item) => {
|
|
4181
|
+
const visibility = item.style.label.visibility ?? "always";
|
|
4182
|
+
return visibility === "always" ? "auto" : "none";
|
|
4183
|
+
}).style("cursor", "pointer");
|
|
4221
4184
|
labelSelection.selectAll("rect").data((item) => [item]).join("rect").attr("rx", (item) => item.style.label.borderRadius).attr("ry", (item) => item.style.label.borderRadius).attr("height", (item) => item.style.label.height).attr("fill", (item) => item.style.label.backgroundFill).attr("stroke", (item) => item.style.label.borderColor).attr("stroke-width", (item) => item.style.label.borderWidth);
|
|
4222
4185
|
labelSelection.selectAll("text").data((item) => [item]).join("text").attr("text-anchor", "middle").attr("dominant-baseline", "middle").attr("font-size", (item) => item.style.label.fontSize).attr("fill", (item) => item.style.label.textColor).text((item) => item.link.label ?? "");
|
|
4223
4186
|
return labelSelection;
|
|
@@ -4246,58 +4209,35 @@ function createDragBehavior(simulation) {
|
|
|
4246
4209
|
|
|
4247
4210
|
// src/interactions/create-node-hover.ts
|
|
4248
4211
|
function createNodeHover(nodeSelection, hoverStyle) {
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
"mouseenter.hover",
|
|
4254
|
-
function(_event, node) {
|
|
4212
|
+
const firstNode = nodeSelection.node();
|
|
4213
|
+
if (!firstNode) return;
|
|
4214
|
+
if (hoverStyle) {
|
|
4215
|
+
nodeSelection.on("mouseenter.hover", function(_event, node) {
|
|
4255
4216
|
const circle = this;
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
"stroke",
|
|
4261
|
-
hoverStroke
|
|
4262
|
-
);
|
|
4263
|
-
circle.setAttribute(
|
|
4264
|
-
"stroke-width",
|
|
4265
|
-
String(
|
|
4266
|
-
hoverStrokeWidth
|
|
4267
|
-
)
|
|
4268
|
-
);
|
|
4269
|
-
circle.setAttribute(
|
|
4270
|
-
"opacity",
|
|
4271
|
-
String(
|
|
4272
|
-
hoverOpacity
|
|
4273
|
-
)
|
|
4274
|
-
);
|
|
4275
|
-
}
|
|
4276
|
-
).on(
|
|
4277
|
-
"mouseleave.hover",
|
|
4278
|
-
function(_event, node) {
|
|
4217
|
+
circle.setAttribute("stroke", hoverStyle.stroke ?? node.style?.stroke ?? "#ffffff");
|
|
4218
|
+
circle.setAttribute("stroke-width", String(hoverStyle.strokeWidth ?? node.style?.strokeWidth ?? 1.5));
|
|
4219
|
+
circle.setAttribute("opacity", String(hoverStyle.opacity ?? node.style?.opacity ?? 1));
|
|
4220
|
+
}).on("mouseleave.hover", function(_event, node) {
|
|
4279
4221
|
const circle = this;
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
}
|
|
4300
|
-
);
|
|
4222
|
+
circle.setAttribute("stroke", node.style?.stroke ?? "#ffffff");
|
|
4223
|
+
circle.setAttribute("stroke-width", String(node.style?.strokeWidth ?? 1.5));
|
|
4224
|
+
circle.setAttribute("opacity", String(node.style?.opacity ?? 1));
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
const svgElement = firstNode.ownerSVGElement;
|
|
4228
|
+
if (!svgElement) return;
|
|
4229
|
+
const root2 = select_default2(svgElement);
|
|
4230
|
+
const labelSelection = root2.selectAll(".link-label");
|
|
4231
|
+
nodeSelection.on("mouseenter.labels", (_event, d) => {
|
|
4232
|
+
labelSelection.filter((item) => {
|
|
4233
|
+
if (item.style.label.visibility !== "hover") return false;
|
|
4234
|
+
const s = item.link.source;
|
|
4235
|
+
const t = item.link.target;
|
|
4236
|
+
return s.id === d.id || t.id === d.id;
|
|
4237
|
+
}).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
|
|
4238
|
+
}).on("mouseleave.labels", (_event) => {
|
|
4239
|
+
labelSelection.filter((item) => item.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
|
|
4240
|
+
});
|
|
4301
4241
|
}
|
|
4302
4242
|
|
|
4303
4243
|
// src/utils/resolve-tooltip-position.ts
|
|
@@ -4589,6 +4529,7 @@ function createGraph(config) {
|
|
|
4589
4529
|
let tooltipBinding = null;
|
|
4590
4530
|
let controls = null;
|
|
4591
4531
|
let legendCleanup = null;
|
|
4532
|
+
let fitViewTimer = null;
|
|
4592
4533
|
let dimensions = { width: 0, height: 0 };
|
|
4593
4534
|
let rootGroup = null;
|
|
4594
4535
|
let svgElement = null;
|
|
@@ -4605,9 +4546,17 @@ function createGraph(config) {
|
|
|
4605
4546
|
layers.svg.setAttribute("height", String(height));
|
|
4606
4547
|
layers.interactionRect.setAttribute("width", String(width));
|
|
4607
4548
|
layers.interactionRect.setAttribute("height", String(height));
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4549
|
+
if (simulation) {
|
|
4550
|
+
simulation.force("center", center_default(width / 2, height / 2));
|
|
4551
|
+
simulation.alpha(0.3).restart();
|
|
4552
|
+
}
|
|
4553
|
+
if (fitViewTimer) {
|
|
4554
|
+
clearTimeout(fitViewTimer);
|
|
4555
|
+
}
|
|
4556
|
+
fitViewTimer = setTimeout(() => {
|
|
4557
|
+
fitView();
|
|
4558
|
+
fitViewTimer = null;
|
|
4559
|
+
}, 150);
|
|
4611
4560
|
});
|
|
4612
4561
|
const zoomResult = createZoom({
|
|
4613
4562
|
svg: layers.svg,
|
|
@@ -4625,8 +4574,9 @@ function createGraph(config) {
|
|
|
4625
4574
|
const simulationConfig = {
|
|
4626
4575
|
nodes: config.nodes,
|
|
4627
4576
|
links: config.links,
|
|
4628
|
-
|
|
4629
|
-
|
|
4577
|
+
// Uses the observed dimensions to ensure physics are calculated on actual container size
|
|
4578
|
+
width: dimensions.width || config.container.clientWidth,
|
|
4579
|
+
height: dimensions.height || config.container.clientHeight
|
|
4630
4580
|
};
|
|
4631
4581
|
const simulationResult = createGraphSimulation(simulationConfig);
|
|
4632
4582
|
simulation = simulationResult.simulation;
|
|
@@ -4682,7 +4632,7 @@ function createGraph(config) {
|
|
|
4682
4632
|
}
|
|
4683
4633
|
function resetView() {
|
|
4684
4634
|
if (!zoomBehavior || !svgElement) return;
|
|
4685
|
-
select_default2(svgElement).transition().call(zoomBehavior.transform, identity2);
|
|
4635
|
+
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, identity2);
|
|
4686
4636
|
}
|
|
4687
4637
|
function fitView() {
|
|
4688
4638
|
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) return;
|
|
@@ -4692,7 +4642,7 @@ function createGraph(config) {
|
|
|
4692
4642
|
const translateX = (dimensions.width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4693
4643
|
const translateY = (dimensions.height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
4694
4644
|
const transform2 = identity2.translate(translateX, translateY).scale(scale);
|
|
4695
|
-
select_default2(svgElement).transition().call(zoomBehavior.transform, transform2);
|
|
4645
|
+
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, transform2);
|
|
4696
4646
|
}
|
|
4697
4647
|
function zoomIn() {
|
|
4698
4648
|
if (!zoomBehavior || !svgElement) return;
|
|
@@ -4711,6 +4661,10 @@ function createGraph(config) {
|
|
|
4711
4661
|
});
|
|
4712
4662
|
}
|
|
4713
4663
|
function destroy() {
|
|
4664
|
+
if (fitViewTimer) {
|
|
4665
|
+
clearTimeout(fitViewTimer);
|
|
4666
|
+
fitViewTimer = null;
|
|
4667
|
+
}
|
|
4714
4668
|
if (cleanupResize) {
|
|
4715
4669
|
cleanupResize();
|
|
4716
4670
|
cleanupResize = null;
|
package/dist/index.d.cts
CHANGED
|
@@ -66,6 +66,7 @@ interface LinkArrowStyle {
|
|
|
66
66
|
}
|
|
67
67
|
interface LinkLabelStyle {
|
|
68
68
|
readonly enabled?: boolean;
|
|
69
|
+
readonly visibility?: 'always' | 'hover' | 'selection';
|
|
69
70
|
readonly backgroundFill?: string;
|
|
70
71
|
readonly borderColor?: string;
|
|
71
72
|
readonly borderWidth?: number;
|
|
@@ -117,6 +118,13 @@ interface GraphConfig {
|
|
|
117
118
|
readonly container: HTMLElement;
|
|
118
119
|
readonly nodes: GraphNode[];
|
|
119
120
|
readonly links: GraphLink[];
|
|
121
|
+
readonly autoFit?: boolean;
|
|
122
|
+
readonly responsive?: boolean;
|
|
123
|
+
readonly simulation?: {
|
|
124
|
+
readonly alpha?: number;
|
|
125
|
+
readonly gravity?: number;
|
|
126
|
+
readonly linkDistance?: number | ((link: GraphLink) => number);
|
|
127
|
+
};
|
|
120
128
|
readonly interaction?: GraphInteractionConfig;
|
|
121
129
|
readonly controls?: GraphControlsConfig;
|
|
122
130
|
readonly legend?: LegendConfig;
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ interface LinkArrowStyle {
|
|
|
66
66
|
}
|
|
67
67
|
interface LinkLabelStyle {
|
|
68
68
|
readonly enabled?: boolean;
|
|
69
|
+
readonly visibility?: 'always' | 'hover' | 'selection';
|
|
69
70
|
readonly backgroundFill?: string;
|
|
70
71
|
readonly borderColor?: string;
|
|
71
72
|
readonly borderWidth?: number;
|
|
@@ -117,6 +118,13 @@ interface GraphConfig {
|
|
|
117
118
|
readonly container: HTMLElement;
|
|
118
119
|
readonly nodes: GraphNode[];
|
|
119
120
|
readonly links: GraphLink[];
|
|
121
|
+
readonly autoFit?: boolean;
|
|
122
|
+
readonly responsive?: boolean;
|
|
123
|
+
readonly simulation?: {
|
|
124
|
+
readonly alpha?: number;
|
|
125
|
+
readonly gravity?: number;
|
|
126
|
+
readonly linkDistance?: number | ((link: GraphLink) => number);
|
|
127
|
+
};
|
|
120
128
|
readonly interaction?: GraphInteractionConfig;
|
|
121
129
|
readonly controls?: GraphControlsConfig;
|
|
122
130
|
readonly legend?: LegendConfig;
|
package/dist/index.js
CHANGED
|
@@ -3608,68 +3608,6 @@ function manyBody_default() {
|
|
|
3608
3608
|
return force;
|
|
3609
3609
|
}
|
|
3610
3610
|
|
|
3611
|
-
// node_modules/d3-force/src/x.js
|
|
3612
|
-
function x_default2(x3) {
|
|
3613
|
-
var strength = constant_default5(0.1), nodes, strengths, xz;
|
|
3614
|
-
if (typeof x3 !== "function") x3 = constant_default5(x3 == null ? 0 : +x3);
|
|
3615
|
-
function force(alpha) {
|
|
3616
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
3617
|
-
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha;
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
function initialize() {
|
|
3621
|
-
if (!nodes) return;
|
|
3622
|
-
var i, n = nodes.length;
|
|
3623
|
-
strengths = new Array(n);
|
|
3624
|
-
xz = new Array(n);
|
|
3625
|
-
for (i = 0; i < n; ++i) {
|
|
3626
|
-
strengths[i] = isNaN(xz[i] = +x3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
force.initialize = function(_) {
|
|
3630
|
-
nodes = _;
|
|
3631
|
-
initialize();
|
|
3632
|
-
};
|
|
3633
|
-
force.strength = function(_) {
|
|
3634
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
|
|
3635
|
-
};
|
|
3636
|
-
force.x = function(_) {
|
|
3637
|
-
return arguments.length ? (x3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : x3;
|
|
3638
|
-
};
|
|
3639
|
-
return force;
|
|
3640
|
-
}
|
|
3641
|
-
|
|
3642
|
-
// node_modules/d3-force/src/y.js
|
|
3643
|
-
function y_default2(y3) {
|
|
3644
|
-
var strength = constant_default5(0.1), nodes, strengths, yz;
|
|
3645
|
-
if (typeof y3 !== "function") y3 = constant_default5(y3 == null ? 0 : +y3);
|
|
3646
|
-
function force(alpha) {
|
|
3647
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
3648
|
-
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha;
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
function initialize() {
|
|
3652
|
-
if (!nodes) return;
|
|
3653
|
-
var i, n = nodes.length;
|
|
3654
|
-
strengths = new Array(n);
|
|
3655
|
-
yz = new Array(n);
|
|
3656
|
-
for (i = 0; i < n; ++i) {
|
|
3657
|
-
strengths[i] = isNaN(yz[i] = +y3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
3658
|
-
}
|
|
3659
|
-
}
|
|
3660
|
-
force.initialize = function(_) {
|
|
3661
|
-
nodes = _;
|
|
3662
|
-
initialize();
|
|
3663
|
-
};
|
|
3664
|
-
force.strength = function(_) {
|
|
3665
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
|
|
3666
|
-
};
|
|
3667
|
-
force.y = function(_) {
|
|
3668
|
-
return arguments.length ? (y3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : y3;
|
|
3669
|
-
};
|
|
3670
|
-
return force;
|
|
3671
|
-
}
|
|
3672
|
-
|
|
3673
3611
|
// src/core/create-graph-layers.ts
|
|
3674
3612
|
function createGraphLayers(host) {
|
|
3675
3613
|
host.innerHTML = "";
|
|
@@ -3778,10 +3716,17 @@ function createGraphSimulation(config) {
|
|
|
3778
3716
|
});
|
|
3779
3717
|
const simulation = simulation_default(config.nodes).alpha(0.9).alphaDecay(0.12).alphaMin(0.03).velocityDecay(0.5).force(
|
|
3780
3718
|
"link",
|
|
3781
|
-
link_default(config.links).id((d) => d.id).distance(
|
|
3719
|
+
link_default(config.links).id((d) => d.id).distance((d) => {
|
|
3720
|
+
const source = d.source;
|
|
3721
|
+
const target = d.target;
|
|
3722
|
+
const sourceR = source.style?.radius || 20;
|
|
3723
|
+
const targetR = target.style?.radius || 20;
|
|
3724
|
+
const labelBuffer = d.style?.label?.height || 40;
|
|
3725
|
+
return (sourceR + targetR + labelBuffer) * 2;
|
|
3726
|
+
}).strength(0.8)
|
|
3782
3727
|
).force("charge", manyBody_default().strength(-220)).force(
|
|
3783
3728
|
"collide",
|
|
3784
|
-
collide_default().radius((node) => (node.style?.radius ?? 12) + 10).
|
|
3729
|
+
collide_default().radius((node) => (node.style?.radius ?? 12) + 10).iterations(2)
|
|
3785
3730
|
).force("center", center_default(centerX, centerY).strength(0.08));
|
|
3786
3731
|
return { simulation };
|
|
3787
3732
|
}
|
|
@@ -3964,6 +3909,7 @@ var DEFAULT_LINK_STYLE = {
|
|
|
3964
3909
|
},
|
|
3965
3910
|
label: {
|
|
3966
3911
|
enabled: true,
|
|
3912
|
+
visibility: "always",
|
|
3967
3913
|
backgroundFill: "color-mix(in srgb, #8E42EE, #FFFFFF 90%)",
|
|
3968
3914
|
borderColor: "color-mix(in srgb, #8E42EE, #FFFFFF 10%)",
|
|
3969
3915
|
borderWidth: 1.5,
|
|
@@ -3999,6 +3945,7 @@ function mergeLinkStyle(base, override) {
|
|
|
3999
3945
|
},
|
|
4000
3946
|
label: {
|
|
4001
3947
|
enabled: override?.label?.enabled ?? base.label.enabled,
|
|
3948
|
+
visibility: override?.label?.visibility ?? base.label.visibility,
|
|
4002
3949
|
backgroundFill: override?.label?.backgroundFill ?? base.label.backgroundFill,
|
|
4003
3950
|
borderColor: override?.label?.borderColor ?? base.label.borderColor,
|
|
4004
3951
|
borderWidth: override?.label?.borderWidth ?? base.label.borderWidth,
|
|
@@ -4099,7 +4046,14 @@ function getLinkKey(link) {
|
|
|
4099
4046
|
}
|
|
4100
4047
|
function renderLinks(ctx, links) {
|
|
4101
4048
|
const renderableLinks = createRenderableLinks(ctx, links);
|
|
4102
|
-
|
|
4049
|
+
const linkSelection = ctx.root.select('[data-layer="links"]').selectAll("line").data(renderableLinks, (item) => getLinkKey(item.link)).join("line").attr("class", "graph-link").attr("stroke", (item) => item.style.stroke).attr("stroke-width", (item) => item.style.strokeWidth).attr("opacity", (item) => item.style.opacity).attr("marker-end", (item) => item.markerEnd).style("pointer-events", "stroke");
|
|
4050
|
+
const labelSelection = ctx.root.selectAll(".link-label");
|
|
4051
|
+
linkSelection.on("mouseenter.label-hover", (_event, d) => {
|
|
4052
|
+
labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 1);
|
|
4053
|
+
}).on("mouseleave.label-hover", (_event, d) => {
|
|
4054
|
+
labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 0);
|
|
4055
|
+
});
|
|
4056
|
+
return linkSelection;
|
|
4103
4057
|
}
|
|
4104
4058
|
|
|
4105
4059
|
// src/renderer/nodes.ts
|
|
@@ -4169,7 +4123,10 @@ function renderNodeLabels(ctx, nodes) {
|
|
|
4169
4123
|
// src/renderer/link-labels.ts
|
|
4170
4124
|
function createRenderableLinks2(params, links) {
|
|
4171
4125
|
return links.map(
|
|
4172
|
-
(link) => ({
|
|
4126
|
+
(link) => ({
|
|
4127
|
+
link,
|
|
4128
|
+
style: resolveLinkStyle({ link, interaction: params.interaction })
|
|
4129
|
+
})
|
|
4173
4130
|
).filter(
|
|
4174
4131
|
(item) => item.style.label.enabled && Boolean(item.link.label)
|
|
4175
4132
|
);
|
|
@@ -4181,7 +4138,13 @@ function getLinkKey2(link) {
|
|
|
4181
4138
|
}
|
|
4182
4139
|
function renderLinkLabels(params, links) {
|
|
4183
4140
|
const renderableLinks = createRenderableLinks2(params, links);
|
|
4184
|
-
const labelSelection = params.root.select('[data-layer="link-labels"]').selectAll(".link-label").data(renderableLinks, (item) => getLinkKey2(item.link)).join("g").attr("class", "link-label").
|
|
4141
|
+
const labelSelection = params.root.select('[data-layer="link-labels"]').selectAll(".link-label").data(renderableLinks, (item) => getLinkKey2(item.link)).join("g").attr("class", "link-label").style("opacity", (item) => {
|
|
4142
|
+
const visibility = item.style.label.visibility ?? "always";
|
|
4143
|
+
return visibility === "always" ? 1 : 0;
|
|
4144
|
+
}).style("pointer-events", (item) => {
|
|
4145
|
+
const visibility = item.style.label.visibility ?? "always";
|
|
4146
|
+
return visibility === "always" ? "auto" : "none";
|
|
4147
|
+
}).style("cursor", "pointer");
|
|
4185
4148
|
labelSelection.selectAll("rect").data((item) => [item]).join("rect").attr("rx", (item) => item.style.label.borderRadius).attr("ry", (item) => item.style.label.borderRadius).attr("height", (item) => item.style.label.height).attr("fill", (item) => item.style.label.backgroundFill).attr("stroke", (item) => item.style.label.borderColor).attr("stroke-width", (item) => item.style.label.borderWidth);
|
|
4186
4149
|
labelSelection.selectAll("text").data((item) => [item]).join("text").attr("text-anchor", "middle").attr("dominant-baseline", "middle").attr("font-size", (item) => item.style.label.fontSize).attr("fill", (item) => item.style.label.textColor).text((item) => item.link.label ?? "");
|
|
4187
4150
|
return labelSelection;
|
|
@@ -4210,58 +4173,35 @@ function createDragBehavior(simulation) {
|
|
|
4210
4173
|
|
|
4211
4174
|
// src/interactions/create-node-hover.ts
|
|
4212
4175
|
function createNodeHover(nodeSelection, hoverStyle) {
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
"mouseenter.hover",
|
|
4218
|
-
function(_event, node) {
|
|
4176
|
+
const firstNode = nodeSelection.node();
|
|
4177
|
+
if (!firstNode) return;
|
|
4178
|
+
if (hoverStyle) {
|
|
4179
|
+
nodeSelection.on("mouseenter.hover", function(_event, node) {
|
|
4219
4180
|
const circle = this;
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
"stroke",
|
|
4225
|
-
hoverStroke
|
|
4226
|
-
);
|
|
4227
|
-
circle.setAttribute(
|
|
4228
|
-
"stroke-width",
|
|
4229
|
-
String(
|
|
4230
|
-
hoverStrokeWidth
|
|
4231
|
-
)
|
|
4232
|
-
);
|
|
4233
|
-
circle.setAttribute(
|
|
4234
|
-
"opacity",
|
|
4235
|
-
String(
|
|
4236
|
-
hoverOpacity
|
|
4237
|
-
)
|
|
4238
|
-
);
|
|
4239
|
-
}
|
|
4240
|
-
).on(
|
|
4241
|
-
"mouseleave.hover",
|
|
4242
|
-
function(_event, node) {
|
|
4181
|
+
circle.setAttribute("stroke", hoverStyle.stroke ?? node.style?.stroke ?? "#ffffff");
|
|
4182
|
+
circle.setAttribute("stroke-width", String(hoverStyle.strokeWidth ?? node.style?.strokeWidth ?? 1.5));
|
|
4183
|
+
circle.setAttribute("opacity", String(hoverStyle.opacity ?? node.style?.opacity ?? 1));
|
|
4184
|
+
}).on("mouseleave.hover", function(_event, node) {
|
|
4243
4185
|
const circle = this;
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
}
|
|
4264
|
-
);
|
|
4186
|
+
circle.setAttribute("stroke", node.style?.stroke ?? "#ffffff");
|
|
4187
|
+
circle.setAttribute("stroke-width", String(node.style?.strokeWidth ?? 1.5));
|
|
4188
|
+
circle.setAttribute("opacity", String(node.style?.opacity ?? 1));
|
|
4189
|
+
});
|
|
4190
|
+
}
|
|
4191
|
+
const svgElement = firstNode.ownerSVGElement;
|
|
4192
|
+
if (!svgElement) return;
|
|
4193
|
+
const root2 = select_default2(svgElement);
|
|
4194
|
+
const labelSelection = root2.selectAll(".link-label");
|
|
4195
|
+
nodeSelection.on("mouseenter.labels", (_event, d) => {
|
|
4196
|
+
labelSelection.filter((item) => {
|
|
4197
|
+
if (item.style.label.visibility !== "hover") return false;
|
|
4198
|
+
const s = item.link.source;
|
|
4199
|
+
const t = item.link.target;
|
|
4200
|
+
return s.id === d.id || t.id === d.id;
|
|
4201
|
+
}).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
|
|
4202
|
+
}).on("mouseleave.labels", (_event) => {
|
|
4203
|
+
labelSelection.filter((item) => item.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
|
|
4204
|
+
});
|
|
4265
4205
|
}
|
|
4266
4206
|
|
|
4267
4207
|
// src/utils/resolve-tooltip-position.ts
|
|
@@ -4553,6 +4493,7 @@ function createGraph(config) {
|
|
|
4553
4493
|
let tooltipBinding = null;
|
|
4554
4494
|
let controls = null;
|
|
4555
4495
|
let legendCleanup = null;
|
|
4496
|
+
let fitViewTimer = null;
|
|
4556
4497
|
let dimensions = { width: 0, height: 0 };
|
|
4557
4498
|
let rootGroup = null;
|
|
4558
4499
|
let svgElement = null;
|
|
@@ -4569,9 +4510,17 @@ function createGraph(config) {
|
|
|
4569
4510
|
layers.svg.setAttribute("height", String(height));
|
|
4570
4511
|
layers.interactionRect.setAttribute("width", String(width));
|
|
4571
4512
|
layers.interactionRect.setAttribute("height", String(height));
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4513
|
+
if (simulation) {
|
|
4514
|
+
simulation.force("center", center_default(width / 2, height / 2));
|
|
4515
|
+
simulation.alpha(0.3).restart();
|
|
4516
|
+
}
|
|
4517
|
+
if (fitViewTimer) {
|
|
4518
|
+
clearTimeout(fitViewTimer);
|
|
4519
|
+
}
|
|
4520
|
+
fitViewTimer = setTimeout(() => {
|
|
4521
|
+
fitView();
|
|
4522
|
+
fitViewTimer = null;
|
|
4523
|
+
}, 150);
|
|
4575
4524
|
});
|
|
4576
4525
|
const zoomResult = createZoom({
|
|
4577
4526
|
svg: layers.svg,
|
|
@@ -4589,8 +4538,9 @@ function createGraph(config) {
|
|
|
4589
4538
|
const simulationConfig = {
|
|
4590
4539
|
nodes: config.nodes,
|
|
4591
4540
|
links: config.links,
|
|
4592
|
-
|
|
4593
|
-
|
|
4541
|
+
// Uses the observed dimensions to ensure physics are calculated on actual container size
|
|
4542
|
+
width: dimensions.width || config.container.clientWidth,
|
|
4543
|
+
height: dimensions.height || config.container.clientHeight
|
|
4594
4544
|
};
|
|
4595
4545
|
const simulationResult = createGraphSimulation(simulationConfig);
|
|
4596
4546
|
simulation = simulationResult.simulation;
|
|
@@ -4646,7 +4596,7 @@ function createGraph(config) {
|
|
|
4646
4596
|
}
|
|
4647
4597
|
function resetView() {
|
|
4648
4598
|
if (!zoomBehavior || !svgElement) return;
|
|
4649
|
-
select_default2(svgElement).transition().call(zoomBehavior.transform, identity2);
|
|
4599
|
+
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, identity2);
|
|
4650
4600
|
}
|
|
4651
4601
|
function fitView() {
|
|
4652
4602
|
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) return;
|
|
@@ -4656,7 +4606,7 @@ function createGraph(config) {
|
|
|
4656
4606
|
const translateX = (dimensions.width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4657
4607
|
const translateY = (dimensions.height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
4658
4608
|
const transform2 = identity2.translate(translateX, translateY).scale(scale);
|
|
4659
|
-
select_default2(svgElement).transition().call(zoomBehavior.transform, transform2);
|
|
4609
|
+
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, transform2);
|
|
4660
4610
|
}
|
|
4661
4611
|
function zoomIn() {
|
|
4662
4612
|
if (!zoomBehavior || !svgElement) return;
|
|
@@ -4675,6 +4625,10 @@ function createGraph(config) {
|
|
|
4675
4625
|
});
|
|
4676
4626
|
}
|
|
4677
4627
|
function destroy() {
|
|
4628
|
+
if (fitViewTimer) {
|
|
4629
|
+
clearTimeout(fitViewTimer);
|
|
4630
|
+
fitViewTimer = null;
|
|
4631
|
+
}
|
|
4678
4632
|
if (cleanupResize) {
|
|
4679
4633
|
cleanupResize();
|
|
4680
4634
|
cleanupResize = null;
|
package/package.json
CHANGED