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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: d3graph
3
- Version: 2.8.2
3
+ Version: 2.9.0
4
4
  Summary: Python package to create interactive network based on d3js.
5
5
  Author-email: Erdogan Taskesen <erdogant@gmail.com>
6
6
  License-Expression: BSD-3-Clause
@@ -17,7 +17,7 @@ from d3graph.d3graph import (
17
17
 
18
18
  __author__ = 'Erdogan Tasksen'
19
19
  __email__ = 'erdogant@gmail.com'
20
- __version__ = '2.8.2'
20
+ __version__ = '2.9.0'
21
21
 
22
22
  # Setup root logger
23
23
  _logger = logging.getLogger('d3graph')
@@ -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 () { svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")") }))
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
- d3.selectAll(".node-shape").each(function(d) {
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").attr("x", function(d) { return d.x; })
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
 
@@ -130,6 +130,8 @@
130
130
  sticky: {{ sticky | lower }},
131
131
  background_color: '{{ background_color }}',
132
132
  node_text_inside: {{ node_text_inside | lower }},
133
+ max_ticks: {{ max_ticks }},
134
+ label_zoom_threshold: {{ label_zoom_threshold }},
133
135
  })
134
136
  });
135
137
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: d3graph
3
- Version: 2.8.2
3
+ Version: 2.9.0
4
4
  Summary: Python package to create interactive network based on d3js.
5
5
  Author-email: Erdogan Taskesen <erdogant@gmail.com>
6
6
  License-Expression: BSD-3-Clause
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes