d3graph 2.8.2__tar.gz → 2.9.0__tar.gz
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.
- {d3graph-2.8.2/d3graph.egg-info → d3graph-2.9.0}/PKG-INFO +1 -1
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph/__init__.py +1 -1
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph/d3graph.py +15 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph/d3js/d3graphscript.js +47 -5
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph/d3js/index.html.j2 +2 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph/d3js/style.css +8 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph/examples.py +14 -0
- {d3graph-2.8.2 → d3graph-2.9.0/d3graph.egg-info}/PKG-INFO +1 -1
- {d3graph-2.8.2 → d3graph-2.9.0}/LICENSE +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/MANIFEST.in +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/README.md +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph/d3js/d3.v3.js +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph/examples_docs.py +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph.egg-info/SOURCES.txt +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph.egg-info/dependency_links.txt +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph.egg-info/requires.txt +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/d3graph.egg-info/top_level.txt +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/pyproject.toml +0 -0
- {d3graph-2.8.2 → d3graph-2.9.0}/setup.cfg +0 -0
|
@@ -121,6 +121,8 @@ class d3graph:
|
|
|
121
121
|
link_tension: float = None,
|
|
122
122
|
sticky: bool = None,
|
|
123
123
|
node_text_inside: bool = False,
|
|
124
|
+
max_ticks: int = 300,
|
|
125
|
+
label_zoom_threshold: float = 0.4,
|
|
124
126
|
) -> None:
|
|
125
127
|
"""Build and show the graph.
|
|
126
128
|
|
|
@@ -166,6 +168,15 @@ class d3graph:
|
|
|
166
168
|
When False, nodes are released after dragging (default simulation behaviour).
|
|
167
169
|
When None, the value set in __init__ is used.
|
|
168
170
|
Right-click a fixed node to release it back into the simulation.
|
|
171
|
+
max_ticks : int, (default: 300)
|
|
172
|
+
Caps how many simulation ticks run before the force layout auto-stops, instead
|
|
173
|
+
of letting it cool down naturally (which can take thousands of ticks on large
|
|
174
|
+
graphs, each re-running collision detection over every node).
|
|
175
|
+
0 or None: disable the cap and run to natural cooldown.
|
|
176
|
+
label_zoom_threshold : float, (default: 0.6)
|
|
177
|
+
Node and edge labels are hidden below this zoom scale (unreadable anyway, and
|
|
178
|
+
costly to keep rendering for large graphs) and reappear once zoomed back in
|
|
179
|
+
past it. Uses a single CSS class toggle, not per-label work. 0: never hide.
|
|
169
180
|
|
|
170
181
|
Returns
|
|
171
182
|
-------
|
|
@@ -188,6 +199,8 @@ class d3graph:
|
|
|
188
199
|
self.config['background_color'] = background_color
|
|
189
200
|
self.config['dark_mode'] = dark_mode
|
|
190
201
|
self.config['node_text_inside'] = node_text_inside
|
|
202
|
+
self.config['max_ticks'] = max_ticks
|
|
203
|
+
self.config['label_zoom_threshold'] = label_zoom_threshold
|
|
191
204
|
|
|
192
205
|
# Allow show() to override the link_tension set at __init__ time
|
|
193
206
|
if link_tension is not None:
|
|
@@ -801,6 +814,8 @@ class d3graph:
|
|
|
801
814
|
'collision': self.config['collision'],
|
|
802
815
|
'link_tension': self.config.get('link_tension', 1.0),
|
|
803
816
|
'sticky': self.config.get('sticky', False),
|
|
817
|
+
'max_ticks': self.config.get('max_ticks', 300),
|
|
818
|
+
'label_zoom_threshold': self.config.get('label_zoom_threshold', 0.6),
|
|
804
819
|
'node_text_inside': self.config.get('node_text_inside', False),
|
|
805
820
|
'CLICK_COMMENT': CLICK_COMMENT,
|
|
806
821
|
'CLICK_FILL': click_properties['fill'],
|
|
@@ -10,6 +10,8 @@ function d3graphscript(config = {
|
|
|
10
10
|
sticky: false,
|
|
11
11
|
background_color: '#FFFFFF',
|
|
12
12
|
node_text_inside: false,
|
|
13
|
+
max_ticks: 300,
|
|
14
|
+
label_zoom_threshold: 0.6,
|
|
13
15
|
}) {
|
|
14
16
|
|
|
15
17
|
//Constants for the SVG
|
|
@@ -17,6 +19,17 @@ function d3graphscript(config = {
|
|
|
17
19
|
var height = config.height;
|
|
18
20
|
var background_color = config.background_color || '#FFFFFF';
|
|
19
21
|
var sticky = config.sticky || false;
|
|
22
|
+
// Cap how many simulation ticks run before auto-stopping, instead of
|
|
23
|
+
// letting a large graph's force layout cool down naturally over
|
|
24
|
+
// thousands of ticks (each one re-running collision detection over
|
|
25
|
+
// every node). Restarting the simulation (drag, slider changes) resets
|
|
26
|
+
// this counter so it still settles again after each change.
|
|
27
|
+
var maxTicks = (config.max_ticks !== undefined && config.max_ticks !== null) ? config.max_ticks : 300;
|
|
28
|
+
var tickCount = 0;
|
|
29
|
+
// Below this zoom scale, labels are unreadable anyway — hide them (via a
|
|
30
|
+
// single CSS class, not per-element work) instead of rendering thousands
|
|
31
|
+
// of illegible <text> nodes. They reappear once zoomed back in past it.
|
|
32
|
+
var labelZoomThreshold = (config.label_zoom_threshold !== undefined && config.label_zoom_threshold !== null) ? config.label_zoom_threshold : 0.6;
|
|
20
33
|
|
|
21
34
|
// Set the body background color
|
|
22
35
|
document.body.style.backgroundColor = background_color;
|
|
@@ -42,6 +55,7 @@ function d3graphscript(config = {
|
|
|
42
55
|
d3.select(this).classed("dragging", true);
|
|
43
56
|
if (sticky) {
|
|
44
57
|
d.fixed = true;
|
|
58
|
+
tickCount = 0;
|
|
45
59
|
force.start();
|
|
46
60
|
}
|
|
47
61
|
}
|
|
@@ -214,12 +228,26 @@ function d3graphscript(config = {
|
|
|
214
228
|
.attr("width", width)
|
|
215
229
|
.attr("height", height)
|
|
216
230
|
.style("background-color", background_color)
|
|
217
|
-
.call(d3.behavior.zoom().on("zoom", function () {
|
|
231
|
+
.call(d3.behavior.zoom().on("zoom", function () {
|
|
232
|
+
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
|
|
233
|
+
// Single class toggle (cheap) rather than iterating every label on
|
|
234
|
+
// every zoom/pan event — CSS handles hiding all descendant <text>.
|
|
235
|
+
svg.classed("labels-hidden", d3.event.scale < labelZoomThreshold);
|
|
236
|
+
}))
|
|
218
237
|
.on("dblclick.zoom", null)
|
|
219
238
|
.append("g")
|
|
220
239
|
|
|
221
|
-
graphRec = JSON.parse(JSON.stringify(graph));
|
|
222
|
-
|
|
240
|
+
graphRec = JSON.parse(JSON.stringify(graph)); // Full, unfiltered copy — used by the slider to restore edges later
|
|
241
|
+
|
|
242
|
+
// Apply the initial slider threshold BEFORE building any DOM elements.
|
|
243
|
+
// Without this, the browser would create a <line>/<title>/<text> for every
|
|
244
|
+
// single edge (e.g. 100,000+) and immediately delete most of them once the
|
|
245
|
+
// slider filter ran — doubling the work and freezing the page in the meantime.
|
|
246
|
+
(function applyInitialThreshold() {
|
|
247
|
+
var initialThresh = {{ SET_SLIDER }};
|
|
248
|
+
graph.links = graph.links.filter(function(d) { return d.edge_weight > initialThresh; });
|
|
249
|
+
})();
|
|
250
|
+
|
|
223
251
|
//Creates the graph data structure out of the json data
|
|
224
252
|
force.nodes(graph.nodes)
|
|
225
253
|
.links(graph.links)
|
|
@@ -268,6 +296,7 @@ function d3graphscript(config = {
|
|
|
268
296
|
d3.select(this).select(".node-shape")
|
|
269
297
|
.style("stroke-dasharray", null)
|
|
270
298
|
.style("stroke-width", function(d) { return d.node_size_edge; });
|
|
299
|
+
tickCount = 0;
|
|
271
300
|
force.resume();
|
|
272
301
|
});
|
|
273
302
|
}
|
|
@@ -309,6 +338,14 @@ function d3graphscript(config = {
|
|
|
309
338
|
|
|
310
339
|
//Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements
|
|
311
340
|
force.on("tick", function() {
|
|
341
|
+
// Auto-stop after maxTicks so a large graph doesn't keep re-running
|
|
342
|
+
// collision detection / layout math for thousands of frames while
|
|
343
|
+
// settling. maxTicks <= 0 disables the cap (run to natural cooldown).
|
|
344
|
+
if (maxTicks > 0 && ++tickCount > maxTicks) {
|
|
345
|
+
force.stop();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
312
349
|
link.attr("x1", function(d) { return d.source.x; })
|
|
313
350
|
.attr("y1", function(d) { return d.source.y; })
|
|
314
351
|
.attr("x2", function(d) { return d.target.x; })
|
|
@@ -317,7 +354,9 @@ function d3graphscript(config = {
|
|
|
317
354
|
// Position each shape according to its SVG element type:
|
|
318
355
|
// circle / ellipse → cx / cy attributes
|
|
319
356
|
// path → transform translate(x, y)
|
|
320
|
-
|
|
357
|
+
// Scoped to the bound `node` selection instead of re-querying the
|
|
358
|
+
// entire DOM every tick (was: d3.selectAll(".node-shape")).
|
|
359
|
+
node.select(".node-shape").each(function(d) {
|
|
321
360
|
var el = d3.select(this);
|
|
322
361
|
var tag = this.tagName.toLowerCase();
|
|
323
362
|
if (tag === 'circle' || tag === 'ellipse') {
|
|
@@ -327,7 +366,9 @@ function d3graphscript(config = {
|
|
|
327
366
|
}
|
|
328
367
|
});
|
|
329
368
|
|
|
330
|
-
d3.selectAll("text")
|
|
369
|
+
// Scoped to node labels only (was: d3.selectAll("text"), which also
|
|
370
|
+
// re-matched every link-text element on every tick).
|
|
371
|
+
node.select("text").attr("x", function(d) { return d.x; })
|
|
331
372
|
.attr("y", function(d) { return d.y; })
|
|
332
373
|
linkText.attr("x", function(d) { return (d.source.x + d.target.x) / 2; }) // ADD TEXT ON THE EDGES (PART 2/2)
|
|
333
374
|
.attr("y", function(d) { return (d.source.y + d.target.y) / 2; })
|
|
@@ -534,6 +575,7 @@ function d3graphscript(config = {
|
|
|
534
575
|
|
|
535
576
|
node = node.data(graph.nodes);
|
|
536
577
|
node.enter().insert("circle", ".cursor").attr("class", "node").attr("r", 5).call(force.drag);
|
|
578
|
+
tickCount = 0;
|
|
537
579
|
force.start();
|
|
538
580
|
}
|
|
539
581
|
|
|
@@ -27,4 +27,12 @@ h3 {
|
|
|
27
27
|
/* Shared pointer cursor for all node shapes */
|
|
28
28
|
.node .node-shape {
|
|
29
29
|
cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Zoom-linked label visibility (performance): when zoomed far out, node and
|
|
33
|
+
edge labels are unreadable anyway, so they're hidden via this single class
|
|
34
|
+
toggle instead of being permanently removed. Placed after .node text so it
|
|
35
|
+
wins on specificity ties. */
|
|
36
|
+
.labels-hidden text {
|
|
37
|
+
display: none;
|
|
30
38
|
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# %%
|
|
2
|
+
from d3graph import d3graph, vec2adjmat, import_example
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
df = pd.read_csv('https://github.com/d3blocks/d3blocks/files/11995798/Df.csv', sep=',', index_col=False)
|
|
6
|
+
del df['Unnamed: 0']
|
|
7
|
+
df = df[0:5000]
|
|
8
|
+
adjmat = vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'])
|
|
9
|
+
|
|
10
|
+
# sticky=False — classic spring-back behaviour
|
|
11
|
+
d3 = d3graph()
|
|
12
|
+
d3.graph(adjmat)
|
|
13
|
+
d3.show()
|
|
14
|
+
|
|
1
15
|
# %% SET PATH ISSUE https://github.com/erdogant/d3graph/issues/42
|
|
2
16
|
from pathlib import Path
|
|
3
17
|
from d3graph import d3graph, vec2adjmat, import_example
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|