d3graph 2.6.2__tar.gz → 2.7.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.6.2
3
+ Version: 2.7.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
@@ -15,7 +15,7 @@ from d3graph.d3graph import (
15
15
 
16
16
  __author__ = 'Erdogan Tasksen'
17
17
  __email__ = 'erdogant@gmail.com'
18
- __version__ = '2.6.2'
18
+ __version__ = '2.7.0'
19
19
 
20
20
  # Setup root logger
21
21
  _logger = logging.getLogger('d3graph')
@@ -63,7 +63,6 @@ Examples
63
63
  References
64
64
  ----------
65
65
  * D3Graph: erdogant.medium.com
66
- * D3Blocks: https://medium.com/data-science-collective/d3blocks-the-python-library-to-create-interactive-standalone-and-beautiful-d3-js-charts-ef8c65286e86
67
66
  * Github : https://github.com/erdogant/d3graph
68
67
  * Documentation: https://erdogant.github.io/d3graph/
69
68
 
@@ -12,7 +12,7 @@ import webbrowser
12
12
  from json import dumps
13
13
  from pathlib import Path
14
14
  from sys import platform
15
- from tempfile import TemporaryDirectory, gettempdir
15
+ from tempfile import gettempdir
16
16
  from unicodedata import normalize
17
17
  import uuid
18
18
 
@@ -27,9 +27,6 @@ from packaging import version
27
27
  import datazets as dz
28
28
 
29
29
  logger = logging.getLogger(__name__)
30
- if not logger.hasHandlers():
31
- logging.basicConfig(level=logging.INFO, format='[{asctime}] [{name}] [{levelname}] {msg}', style='{', datefmt='%d-%m-%Y %H:%M:%S')
32
-
33
30
 
34
31
  # %%
35
32
  class d3graph:
@@ -58,8 +55,7 @@ class d3graph:
58
55
 
59
56
  References
60
57
  ----------
61
- * D3Graph: https://erdogant.medium.com/
62
- * D3Blocks: https://medium.com/data-science-collective/d3blocks-the-python-library-to-create-interactive-standalone-and-beautiful-d3-js-charts-ef8c65286e86
58
+ * Medium: https://erdogant.medium.com/
63
59
  * Github : https://github.com/erdogant/d3graph
64
60
  * Documentation: https://erdogant.github.io/d3graph/
65
61
 
@@ -70,8 +66,17 @@ class d3graph:
70
66
  charge: int = 600,
71
67
  slider=None,
72
68
  support: str = 'text',
69
+ link_tension: float = 1,
70
+ sticky: bool = True,
73
71
  verbose: int = 20) -> None:
74
- """Initialize d3graph."""
72
+ """Initialize d3graph.
73
+
74
+ Parameters (additional to existing)
75
+ ------------------------------------
76
+ sticky : bool, (default: False)
77
+ When True, nodes stay fixed in place after being dragged.
78
+ Right-click a fixed node to release it back into the simulation.
79
+ """
75
80
  if slider is None: slider = [None, None]
76
81
  # Cleaning
77
82
  self._clean()
@@ -85,6 +90,8 @@ class d3graph:
85
90
  self.config['charge'] = -abs(charge)
86
91
  self.config['slider'] = slider
87
92
  self.config['support'] = get_support(support)
93
+ self.config['link_tension'] = link_tension
94
+ self.config['sticky'] = sticky
88
95
  # Set paths
89
96
  self.config['curpath'] = os.path.dirname(os.path.abspath(__file__))
90
97
  self.config['d3_library'] = os.path.abspath(os.path.join(self.config['curpath'], 'd3js/d3.v3.js'))
@@ -110,6 +117,8 @@ class d3graph:
110
117
  dark_mode = False,
111
118
  notebook: bool = False,
112
119
  save_button: bool = True,
120
+ link_tension: float = None,
121
+ sticky: bool = None,
113
122
  ) -> None:
114
123
  """Build and show the graph.
115
124
 
@@ -150,6 +159,11 @@ class d3graph:
150
159
  save_button : bool, (default: True)
151
160
  True: Save button is shown in the HTML to save the image in svg.
152
161
  False: No save button is shown in the HTML.
162
+ sticky : bool, (default: None)
163
+ When True, nodes stay fixed in place after being dragged (overrides the value set in __init__).
164
+ When False, nodes are released after dragging (default simulation behaviour).
165
+ When None, the value set in __init__ is used.
166
+ Right-click a fixed node to release it back into the simulation.
153
167
 
154
168
  Returns
155
169
  -------
@@ -171,6 +185,12 @@ class d3graph:
171
185
  self.config['save_button'] = save_button
172
186
  self.config['background_color'] = background_color
173
187
  self.config['dark_mode'] = dark_mode
188
+ # Allow show() to override the link_tension set at __init__ time
189
+ if link_tension is not None:
190
+ self.config['link_tension'] = link_tension
191
+ # Override sticky only when explicitly passed
192
+ if sticky is not None:
193
+ self.config['sticky'] = sticky
174
194
  # if self.config.get('filepath', None) != 'd3graph.html':
175
195
  self.config['filepath'] = self.set_path(filepath)
176
196
 
@@ -308,6 +328,10 @@ class d3graph:
308
328
  self.config['label'] = label
309
329
  self.config['label_color'] = label_color
310
330
  self.config['label_fontsize'] = label_fontsize
331
+
332
+ if not hasattr(self, 'adjmat'):
333
+ logger.error('adjmat is missing. Initialize first with d3 = d3graph(adjmat)')
334
+ return
311
335
 
312
336
  if (not directed) and (marker_end is not None) or (marker_start is not None):
313
337
  logger.info('Set directed=True to see the markers!')
@@ -447,6 +471,10 @@ class d3graph:
447
471
  'edge_color': edge_color of the node
448
472
 
449
473
  """
474
+ if not hasattr(self, 'adjmat'):
475
+ logger.error('adjmat is missing. Initialize first with d3 = d3graph(adjmat)')
476
+ return
477
+
450
478
  if minmax is None: minmax = [8, 13]
451
479
  node_names = self.adjmat.columns.astype(str)
452
480
  nodecount = self.adjmat.shape[0]
@@ -763,6 +791,8 @@ class d3graph:
763
791
  'max_slider': self.config['slider'][1],
764
792
  'directed': self.config['directed'],
765
793
  'collision': self.config['collision'],
794
+ 'link_tension': self.config.get('link_tension', 1.0),
795
+ 'sticky': self.config.get('sticky', False),
766
796
  'CLICK_COMMENT': CLICK_COMMENT,
767
797
  'CLICK_FILL': click_properties['fill'],
768
798
  'CLICK_STROKE': click_properties['stroke'],
@@ -1193,6 +1223,7 @@ def adjmat2dict(adjmat: pd.DataFrame,
1193
1223
  df['label_color']=label_color
1194
1224
  df['label_fontsize']=label_fontsize
1195
1225
  df['edge_style']=edge_style
1226
+ df['edge_color']=edge_color
1196
1227
  df['edge_opacity'] = edge_opacity
1197
1228
  df['tooltip'] = df['weight'].astype(str)
1198
1229
 
@@ -1357,6 +1388,7 @@ def _normalize_size(getsizes, minscale=0.5, maxscale=4, scaler: str = 'zscore'):
1357
1388
  # Instead of Min-Max scaling, that shrinks any distribution in the [0, 1] interval, scaling the variables to
1358
1389
  # Z-scores is better. Min-Max Scaling is too sensitive to outlier observations and generates unseen problems.
1359
1390
 
1391
+ getsizes = getsizes.copy()
1360
1392
  # Set sizes to 0 if not available
1361
1393
  getsizes[np.isinf(getsizes)]=0
1362
1394
  getsizes[np.isnan(getsizes)]=0
@@ -1734,4 +1766,4 @@ def get_support(support):
1734
1766
  script = script + '\n' + "<div data-ea-publisher='erdogantgithubio' data-ea-type='{TYPE}' data-ea-style='stickybox'></div>".replace('{TYPE}', support)
1735
1767
 
1736
1768
  # Return
1737
- return script
1769
+ return script
@@ -1,4 +1,3 @@
1
-
2
1
  function d3graphscript(config = {
3
2
  // Default values
4
3
  width: 800,
@@ -7,6 +6,8 @@ function d3graphscript(config = {
7
6
  distance: 0,
8
7
  directed: false,
9
8
  collision: 0.5,
9
+ link_tension: 1,
10
+ sticky: false,
10
11
  background_color: '#FFFFFF'
11
12
  }) {
12
13
 
@@ -14,6 +15,7 @@ function d3graphscript(config = {
14
15
  var width = config.width;
15
16
  var height = config.height;
16
17
  var background_color = config.background_color || '#FFFFFF';
18
+ var sticky = config.sticky || false;
17
19
 
18
20
  // Set the body background color
19
21
  document.body.style.backgroundColor = background_color;
@@ -25,20 +27,42 @@ function d3graphscript(config = {
25
27
  .charge(config.charge)
26
28
  .linkDistance((d) => d.edge_distance || config.distance)
27
29
  //.linkDistance((d) => config.distance > 0 ? config.distance : d.edge_weight)
30
+ .linkStrength(config.link_tension !== undefined ? config.link_tension : 1)
28
31
  .size([width, height]);
29
32
 
30
- // DRAGGING START
33
+ // ---- DRAGGING ----
34
+ // Sticky mode: dragstart fixes the node so the simulation stops pulling it.
35
+ // dragend keeps it pinned (dashed stroke indicator).
36
+ // Right-click a pinned node to release it back into the simulation.
37
+ // Normal mode: standard free-drag behaviour is preserved.
38
+
31
39
  function dragstarted(d) {
32
40
  d3.event.sourceEvent.stopPropagation();
33
41
  d3.select(this).classed("dragging", true);
42
+ if (sticky) {
43
+ d.fixed = true;
44
+ force.start();
45
+ }
34
46
  }
35
47
 
36
48
  function dragged(d) {
37
- d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
49
+ if (sticky) {
50
+ d.x = d.px = d3.event.x;
51
+ d.y = d.py = d3.event.y;
52
+ } else {
53
+ d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
54
+ }
38
55
  }
39
56
 
40
57
  function dragended(d) {
41
58
  d3.select(this).classed("dragging", false);
59
+ if (sticky) {
60
+ // Keep the node fixed and apply a visual "pinned" cue (dashed border)
61
+ d.fixed = true;
62
+ d3.select(this).select("circle")
63
+ .style("stroke-dasharray", "4,2")
64
+ .style("stroke-width", function(d) { return Math.max(parseFloat(d.node_size_edge) || 1, 2); });
65
+ }
42
66
  }
43
67
 
44
68
  var drag = force.drag()
@@ -47,7 +71,7 @@ function d3graphscript(config = {
47
71
  .on("drag", dragged)
48
72
  .on("dragend", dragended);
49
73
 
50
- // DRAGGING STOP
74
+ // ---- END DRAGGING ----
51
75
 
52
76
  //Append a SVG to the body of the html page. Assign this SVG as an object to svg
53
77
  var svg = d3.select("body").append("svg")
@@ -58,8 +82,6 @@ function d3graphscript(config = {
58
82
  .on("dblclick.zoom", null)
59
83
  .append("g")
60
84
 
61
- //.on("dblclick", threshold); // EXPLODE ALL CONNECTED POINTS
62
-
63
85
  graphRec = JSON.parse(JSON.stringify(graph));
64
86
 
65
87
  //Creates the graph data structure out of the json data
@@ -67,33 +89,31 @@ function d3graphscript(config = {
67
89
  .links(graph.links)
68
90
  .start();
69
91
 
70
- // Create all the line svgs but without locations yet
92
+ // Create all the line svgs but without locations yet
71
93
  var link = svg.selectAll(".link")
72
94
  .data(graph.links)
73
95
  .enter().append("line")
74
96
  .attr("class", "link")
75
97
  .attr('marker-start', function(d){ return 'url(#marker_' + d.marker_start + ')' })
76
- .attr("marker-end", function(d) {
77
- if (config.directed) {return 'url(#marker_' + d.marker_end + ')' }})
98
+ .attr("marker-end", function(d) {
99
+ if (config.directed) {return 'url(#marker_' + d.marker_end + ')' }})
78
100
  .style("stroke-width", function(d) {return d.edge_width;}) // LINK-WIDTH
79
101
  .style("stroke", function(d) {return d.edge_color;}) // EDGE-COLORS
80
102
  .style("stroke-dasharray", function(d) {return d.edge_style;}) // EDGE-STYLE
81
103
  .style("opacity", function(d) {return d.edge_opacity;}) // EDGE-OPACITY
82
- // .style("stroke-width", 1); // WIDTH OF THE LINKS
83
104
  ;
84
105
 
85
106
  link.append("title").text(function(d) { return d.tooltip; });
86
107
 
87
108
  // ADD TEXT ON THE EDGES (PART 1/2)
88
- var linkText = svg.selectAll(".link-text")
89
- .data(graph.links)
90
- .enter().append("text")
91
- .attr("class", "link-text")
92
- .attr("font-size", function(d) {return d.label_fontsize + "px";})
93
- .style("fill", function(d) {return d.label_color;})
94
- .style("font-family", "Arial")
95
- //.attr("transform", "rotate(90)")
96
- .text(function(d) { return d.label; });
109
+ var linkText = svg.selectAll(".link-text")
110
+ .data(graph.links)
111
+ .enter().append("text")
112
+ .attr("class", "link-text")
113
+ .attr("font-size", function(d) {return d.label_fontsize + "px";})
114
+ .style("fill", function(d) {return d.label_color;})
115
+ .style("font-family", "Arial")
116
+ .text(function(d) { return d.label; });
97
117
 
98
118
  //Do the same with the circles for the nodes
99
119
  var node = svg.selectAll(".node")
@@ -103,26 +123,37 @@ function d3graphscript(config = {
103
123
  .call(drag)
104
124
  .on('dblclick', connectedNodes); // HIGHLIGHT ON/OFF
105
125
 
126
+ // Right-click handler: release a pinned node back into the simulation (sticky mode only)
127
+ if (sticky) {
128
+ node.on('contextmenu', function(d) {
129
+ d3.event.preventDefault();
130
+ d.fixed = false;
131
+ // Remove pinned visual cue
132
+ d3.select(this).select("circle")
133
+ .style("stroke-dasharray", null)
134
+ .style("stroke-width", function(d) { return d.node_size_edge; });
135
+ force.resume();
136
+ });
137
+ }
138
+
106
139
  {{ CLICK_COMMENT }} node.on('click', color_on_click); // ON CLICK HANDLER
107
140
 
108
141
 
109
142
  node.append("circle")
110
- .attr("r", function(d) { return d.node_size; }) // NODE SIZE
111
- .style("fill", function(d) {return d.node_color;}) // NODE-COLOR
112
- .style("opacity", function(d) {return d.node_opacity;}) // NODE-OPACITY
113
- .style("stroke-width", function(d) {return d.node_size_edge;}) // NODE-EDGE-SIZE
114
- .style("stroke", function(d) {return d.node_color_edge;}) // NODE-COLOR-EDGE
115
- // .style("stroke", '#000') // NODE-EDGE-COLOR (all black)
143
+ .attr("r", function(d) { return d.node_size; }) // NODE SIZE
144
+ .style("fill", function(d) {return d.node_color;}) // NODE-COLOR
145
+ .style("opacity", function(d) {return d.node_opacity;}) // NODE-OPACITY
146
+ .style("stroke-width", function(d) {return d.node_size_edge;}) // NODE-EDGE-SIZE
147
+ .style("stroke", function(d) {return d.node_color_edge;}) // NODE-COLOR-EDGE
116
148
 
117
149
  // Text in nodes
118
150
  node.append("text")
119
151
  .attr("dx", 10)
120
152
  .attr("dy", ".35em")
121
- .text(function(d) {return d.node_name}) // NODE-TEXT
122
- .style("font-size", function(d) {return d.node_fontsize + "px";}) // set font size equal to node edge size
123
- .style("fill", function(d) {return d.node_fontcolor;}) // set the text fill color to the same as node color
124
- .style("font-family", "monospace");
125
- // .style("stroke", "gray");
153
+ .text(function(d) {return d.node_name}) // NODE-TEXT
154
+ .style("font-size", function(d) {return d.node_fontsize + "px";}) // NODE FONT SIZE
155
+ .style("fill", function(d) {return d.node_fontcolor;}) // NODE FONT COLOR
156
+ .style("font-family", "monospace");
126
157
 
127
158
  let showInHover = ["node_tooltip"]; // Tooltip
128
159
  node.append("title")
@@ -142,10 +173,9 @@ function d3graphscript(config = {
142
173
  .attr("cy", function(d) { return d.y; });
143
174
  d3.selectAll("text").attr("x", function(d) { return d.x; })
144
175
  .attr("y", function(d) { return d.y; })
145
- linkText.attr("x", function(d) { return (d.source.x + d.target.x) / 2; }) // ADD TEXT ON THE EDGES (PART 2/2)
176
+ linkText.attr("x", function(d) { return (d.source.x + d.target.x) / 2; }) // ADD TEXT ON THE EDGES (PART 2/2)
146
177
  .attr("y", function(d) { return (d.source.y + d.target.y) / 2; })
147
178
  .attr("text-anchor", "middle");
148
- ;
149
179
 
150
180
  node.each(collide(config.collision)); //COLLISION DETECTION. High means a big fight to get untouchable nodes (default=0.5)
151
181
 
@@ -160,8 +190,6 @@ function d3graphscript(config = {
160
190
  , { id: 3, name: 'stub', path: 'M 0,0 m -1,-5 L 1,-5 L 1,5 L -1,5 Z', viewbox: '-1 -5 2 10' }
161
191
  ]
162
192
 
163
- //console.log(JSON.stringify(link))
164
-
165
193
  svg.append("defs").selectAll("marker")
166
194
  .data(data_marker)
167
195
  .enter()
@@ -169,17 +197,13 @@ function d3graphscript(config = {
169
197
  .attr('id', function(d){ return 'marker_' + d.name})
170
198
  .attr('markerHeight', 10)
171
199
  .attr('markerWidth', 10)
172
- //.attr('markerUnits', 'strokeWidth')
173
200
  .attr("markerUnits", "userSpaceOnUse") // Fix marker width
174
201
  .attr('orient', 'auto')
175
- //.attr('refX', -15) // Offset marker-start
176
202
  .attr('refX', 15) // Offset marker-end
177
203
  .attr('refY', 0)
178
204
  .attr('viewBox', function(d){ return d.viewbox })
179
205
  .append('svg:path')
180
- //.attr("transform", "rotate(180)") // Marker-start mirrored
181
206
  .attr('d', function(d){ return d.path }) // Marker type
182
- //.style("fill", function(d) {return d.marker_color;}) // Marker color
183
207
  .style("fill", '#808080') // Marker color
184
208
  .style("stroke", '#808080') // Marker edge-color
185
209
  .style("opacity", 0.95) // Marker opacity
@@ -221,7 +245,7 @@ function d3graphscript(config = {
221
245
  // collision detection end
222
246
 
223
247
 
224
- //Toggle stores whether the highlighting is on **********************
248
+ //Toggle stores whether the highlighting is on
225
249
  var toggle = 0;
226
250
  //Create an array logging what is connected to what
227
251
  var linkedByIndex = {};
@@ -237,25 +261,27 @@ function d3graphscript(config = {
237
261
  }
238
262
 
239
263
 
240
- // COLOR ON CLICK
241
- function color_on_click() {
242
- // Give the original color back
243
- d3.selectAll(".node")
244
- .select("circle")
245
- .style("fill", function(d) {return d.node_color;})
246
- .style("opacity", function(d) {return d.node_opacity;})
247
- .style("stroke", function(d) {return d.node_color_edge;})
248
- .style("stroke-width", function(d) {return d.edge_width;})
249
- .attr("r", function(d) { return d.node_size; })
250
- ;
264
+ // COLOR ON CLICK
265
+ function color_on_click() {
266
+ // Give the original color back to all nodes
267
+ d3.selectAll(".node")
268
+ .select("circle")
269
+ .style("fill", function(d) {return d.node_color;})
270
+ .style("opacity", function(d) {return d.node_opacity;})
271
+ .style("stroke", function(d) {return d.node_color_edge;})
272
+ .style("stroke-width", function(d) {return d.node_size_edge;})
273
+ // Restore pinned cue on still-fixed nodes
274
+ .style("stroke-dasharray", function(d) { return (sticky && d.fixed) ? "4,2" : null; })
275
+ .attr("r", function(d) { return d.node_size; })
276
+ ;
251
277
 
252
- // Set the color on click
253
- d3.select(this).select("circle")
254
- .style("fill", {{ CLICK_FILL }})
255
- .style("stroke", "{{ CLICK_STROKE }}")
256
- .style("stroke-width", {{ CLICK_STROKEW }})
257
- .attr("r", function(d) { return d.node_size*{{ CLICK_SIZE }}; })
258
- ;}
278
+ // Set the color on the clicked node
279
+ d3.select(this).select("circle")
280
+ .style("fill", {{ CLICK_FILL }})
281
+ .style("stroke", "{{ CLICK_STROKE }}")
282
+ .style("stroke-width", {{ CLICK_STROKEW }})
283
+ .attr("r", function(d) { return d.node_size*{{ CLICK_SIZE }}; })
284
+ ;}
259
285
 
260
286
 
261
287
 
@@ -269,7 +295,6 @@ function d3graphscript(config = {
269
295
  link.style("opacity", function(o) {
270
296
  return d.index == o.source.index | d.index == o.target.index ? 1 : 0.1;
271
297
  });
272
- //Reduce the op
273
298
  toggle = 1;
274
299
  } else {
275
300
  //Put them back to opacity=1
@@ -279,14 +304,12 @@ function d3graphscript(config = {
279
304
  toggle = 0;
280
305
  }
281
306
  }
282
- //*************************************************************
283
307
 
284
308
 
285
309
  //adjust threshold
286
310
  function threshold() {
287
311
  let thresh = this.value;
288
312
 
289
- // console.log('Setting threshold', thresh)
290
313
  graph.links.splice(0, graph.links.length);
291
314
  linkText = linkText.data([]); // CLEAR EDGE-LABELS: Clear the linkText selection
292
315
  linkText.exit().remove(); // CLEAR EDGE-LABELS: Clear the linkText elements from the DOM
@@ -314,12 +337,11 @@ function d3graphscript(config = {
314
337
  link.exit().remove();
315
338
  link.enter().insert("line", ".node").attr("class", "link");
316
339
  link.style("stroke-width", function(d) {return d.edge_width;}); // LINK-WIDTH AFTER BREAKING WITH SLIDER
317
- //link.style('marker-start', function(d){ return 'url(#marker_' + d.marker_start + ')' })
318
- link.style("marker-end", function(d) { // Include the markers.
319
- if (config.directed) {return 'url(#marker_' + d.marker_end + ')' }})
320
- link.style("stroke", function(d) {return d.edge_color;}); // EDGE-COLOR AFTER BREAKING WITH SLIDER
321
- link.style("stroke-dasharray", function(d) {return d.edge_style;}) // EDGE-STYLE
322
- link.style("opacity", function(d) {return d.edge_opacity;}); // EDGE-OPACITY AFTER BREAKING WITH SLIDER
340
+ link.style("marker-end", function(d) { // Include the markers.
341
+ if (config.directed) {return 'url(#marker_' + d.marker_end + ')' }})
342
+ link.style("stroke", function(d) {return d.edge_color;}); // EDGE-COLOR AFTER BREAKING WITH SLIDER
343
+ link.style("stroke-dasharray", function(d) {return d.edge_style;}) // EDGE-STYLE
344
+ link.style("opacity", function(d) {return d.edge_opacity;}); // EDGE-OPACITY AFTER BREAKING WITH SLIDER
323
345
 
324
346
  // Update EDGE-LABELS
325
347
  linkText = linkText.data(graph.links);
@@ -336,4 +358,4 @@ function d3graphscript(config = {
336
358
  force.start();
337
359
  }
338
360
 
339
- }
361
+ }
@@ -126,6 +126,8 @@
126
126
  distance: {{ edge_distance }},
127
127
  directed: {{ directed | lower }},
128
128
  collision: {{ collision }},
129
+ link_tension: {{ link_tension }},
130
+ sticky: {{ sticky | lower }},
129
131
  background_color: '{{ background_color }}'
130
132
  })
131
133
  });
@@ -16,4 +16,10 @@ h3 {
16
16
  font-style: italic;
17
17
  font-size: 14px;
18
18
  font-family: "Helvetica";
19
+ }
20
+
21
+ /* Sticky / fixed node indicator: dashed outline ring */
22
+ circle.node-fixed {
23
+ stroke-dasharray: 4, 2;
24
+ stroke-width: 2.5px;
19
25
  }
@@ -1,6 +1,108 @@
1
1
  # Import library
2
2
  from d3graph import d3graph, vec2adjmat
3
3
 
4
+ # Initialize with default settings
5
+ d3 = d3graph(link_tension=0.2)
6
+
7
+ # Load example data
8
+ df = d3.import_example('stormofswords')
9
+ adjmat = vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'])
10
+
11
+
12
+ # sticky=True (default) — drag to pin, right-click to release
13
+ d3 = d3graph(adjmat, sticky=True)
14
+ d3.show()
15
+
16
+ # sticky=False — classic spring-back behaviour
17
+ d3 = d3graph(adjmat, sticky=False)
18
+ d3.show()
19
+
20
+ # Can also be overridden per-render:
21
+ d3.show(adjmat, sticky=False)
22
+ d3.show()
23
+
24
+
25
+ # %%
26
+
27
+
28
+ # Import library
29
+ from d3graph import d3graph, vec2adjmat
30
+
31
+ # Initialize with default settings
32
+ d3 = d3graph(sticky=True, link_tension=0.2, collision=1)
33
+
34
+ # Load example data
35
+ df = d3.import_example('stormofswords')
36
+
37
+ # Convert df to adjmat
38
+ adjmat = vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'])
39
+ # adjmat = np.exp(adjmat)
40
+ # adjmat = adjmat-1
41
+
42
+ # Create the network
43
+ d3.graph(adjmat)
44
+
45
+ # Set Node properties
46
+ d3.set_node_properties(edge_color='#000000', cmap='Blues', minmax=[5, 13], fontcolor='#808080')
47
+ # Show the graph
48
+ # d3.show()
49
+
50
+ # d3.set_edge_properties(directed=False, marker_end='arrow', edge_color='#ff0000', edge_opacity=[0.1, 0.2])
51
+
52
+ # Set edge properties
53
+ d3.set_edge_properties(directed=True, marker_end='arrow')
54
+ d3.show()
55
+
56
+
57
+
58
+
59
+ # Import library
60
+ # from d3graph import d3graph, vec2adjmat
61
+
62
+ # Initialize with default settings
63
+ # d3 = d3graph(support=None)
64
+
65
+ # Load example data
66
+ # df = d3.import_example('stormofswords')
67
+
68
+ # Convert df to adjmat
69
+ # adjmat = vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'])
70
+ # adjmat = np.exp(adjmat)
71
+ # adjmat = adjmat-1
72
+
73
+ # Create the network
74
+ # d3.graph(adjmat)
75
+
76
+ # # Set Node properties
77
+ # # d3.set_node_properties(edge_color='#000000', cmap='Blues', minmax=[5, 13], fontcolor='#808080')
78
+ # d3.set_edge_properties(edge_color='#FF0000')
79
+
80
+ # # Show the graph
81
+ # d3.show()
82
+
83
+ # # %%
84
+
85
+
86
+ # %% opacity
87
+ from d3graph import d3graph, adjmat2vec, vec2adjmat
88
+ # intialize to load example dataset
89
+ d3 = d3graph()
90
+ #
91
+ df = d3.import_example(data='energy')
92
+ df=vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'])
93
+ # d3.graph(df, size='degree', opacity='degree', color='cluster', scaler='zscore')
94
+ d3.graph(df)
95
+
96
+ # d3.set_node_properties(opacity='centrality')
97
+ d3.show(filepath='c://temp/network.html')
98
+
99
+
100
+ # %%
101
+
102
+
103
+ # Import library
104
+ from d3graph import d3graph, vec2adjmat
105
+
4
106
  # Initialize with default settings
5
107
  d3 = d3graph(support=None)
6
108
 
@@ -74,7 +176,7 @@ df_edges_c['isin']=0
74
176
 
75
177
  for index, edge in df_edges.iterrows():
76
178
  Iloc = np.sum(edge==df_edges_c[df_edges.columns], axis=1)==len(df_edges.columns)
77
- df_edges_c['isin'][Iloc]=1
179
+ df_edges_c.loc[Iloc, 'isin'] = 1
78
180
 
79
181
  assert df_edges_c['isin'].sum()==df_edges.shape[0]
80
182
 
@@ -162,19 +264,6 @@ d3.set_node_properties(marker=['circle', 'circle', 'circle', 'rect', 'rect', 're
162
264
  d3.show(filepath=r'c:\temp\\d3graph\circle.html', set_slider=5, save_button=False)
163
265
 
164
266
 
165
- # %% opacity
166
- from d3graph import d3graph, adjmat2vec
167
- # intialize to load example dataset
168
- d3 = d3graph()
169
- #
170
- df = d3.import_example(data='energy')
171
- df=vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'])
172
- # d3.graph(df, size='degree', opacity='degree', color='cluster', scaler='zscore')
173
- d3.graph(df)
174
-
175
- # d3.set_node_properties(opacity='centrality')
176
- d3.show(filepath='c://temp/network.html')
177
-
178
267
 
179
268
  # %% Edge distance
180
269
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: d3graph
3
- Version: 2.6.2
3
+ Version: 2.7.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
@@ -14,5 +14,4 @@ d3graph.egg-info/top_level.txt
14
14
  d3graph/d3js/d3.v3.js
15
15
  d3graph/d3js/d3graphscript.js
16
16
  d3graph/d3js/index.html.j2
17
- d3graph/d3js/style.css
18
- tests/test_d3graph.py
17
+ d3graph/d3js/style.css
@@ -1,39 +0,0 @@
1
- from copy import deepcopy
2
- import numpy as np
3
- import pandas as pd
4
- from d3graph import d3graph, adjmat2vec, vec2adjmat
5
-
6
-
7
- def test_instantiate_d3graph_no_args() -> None:
8
- """Test instantiation works with defaults"""
9
- d3 = d3graph()
10
- assert isinstance(d3, type(d3graph()))
11
-
12
-
13
- def test_clean(d3, helpers) -> None:
14
- """Test _clean method deletes the attributes in clean_fields"""
15
- clean_fields: tuple = ('adjmat', 'config', 'edge_properties', 'G', 'node_properties')
16
-
17
- # Set attrs to dummy value (i.e., 0) and assert they exist in the object
18
- original_attrs = {field: 0 for field in clean_fields}
19
-
20
- # Convert the array to a DataFrame for comparison
21
- def test_adjmat2vec_and_back() -> None:
22
- edges = np.array([('Cloudy', 'Sprinkler'), ('Cloudy', 'Rain'), ('Sprinkler', 'Wet_Grass'), ('Rain', 'Wet_Grass')])
23
- weight = [1,2,3,4]
24
- df_edges = pd.DataFrame(edges, columns=['source', 'target'])
25
- df_edges['weight']=weight
26
-
27
- df_edges_c = adjmat2vec(vec2adjmat(source=df_edges['source'], target=df_edges['target'], weight=df_edges['weight']))
28
- adjmat = vec2adjmat(source=df_edges_c['source'], target=df_edges_c['target'], weight=df_edges_c['weight'])
29
- df_edges_c = adjmat2vec(adjmat)
30
-
31
- df_edges_c = df_edges_c.sort_values(by='weight')
32
- df_edges_c['isin']=0
33
-
34
- for index, edge in df_edges.iterrows():
35
- Iloc = np.sum(edge==df_edges_c[df_edges.columns], axis=1)==len(df_edges.columns)
36
- df_edges_c['isin'][Iloc]=1
37
-
38
- assert df_edges_c['isin'].sum()==df_edges.shape[0]
39
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes