d3graph 2.7.2__tar.gz → 2.8.1__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.7.2
3
+ Version: 2.8.1
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.7.2'
20
+ __version__ = '2.8.1'
21
21
 
22
22
  # Setup root logger
23
23
  _logger = logging.getLogger('d3graph')
@@ -60,7 +60,7 @@ Examples
60
60
  >>> d3.set_edge_properties(directed=True)
61
61
  >>>
62
62
  >>> # Plot
63
- >>> d3.show()
63
+ >>> d3.show(node_text_inside=True)
64
64
 
65
65
  References
66
66
  ----------
@@ -120,6 +120,7 @@ class d3graph:
120
120
  save_button: bool = True,
121
121
  link_tension: float = None,
122
122
  sticky: bool = None,
123
+ node_text_inside: bool = False,
123
124
  ) -> None:
124
125
  """Build and show the graph.
125
126
 
@@ -186,6 +187,8 @@ class d3graph:
186
187
  self.config['save_button'] = save_button
187
188
  self.config['background_color'] = background_color
188
189
  self.config['dark_mode'] = dark_mode
190
+ self.config['node_text_inside'] = node_text_inside
191
+
189
192
  # Allow show() to override the link_tension set at __init__ time
190
193
  if link_tension is not None:
191
194
  self.config['link_tension'] = link_tension
@@ -396,7 +399,8 @@ class d3graph:
396
399
  * ['label 1','label 2','label 3', ...]
397
400
  marker : list of markers (default: 'circle')
398
401
  The marker that is used for the Node.
399
- * ['circle','rect','rect', ...]
402
+ * 'circle' or 'hexagon' etc
403
+ * ['circle', 'star', 'diamond', 'square', 'pentagon', 'hexagon', 'rectangle', 'triangle-down', 'triangle']
400
404
  tooltip : list of names (default: None)
401
405
  The text that is shown when hovering over the Node.
402
406
  If not specified, the text will inherit from the label.
@@ -795,6 +799,7 @@ class d3graph:
795
799
  'collision': self.config['collision'],
796
800
  'link_tension': self.config.get('link_tension', 1.0),
797
801
  'sticky': self.config.get('sticky', False),
802
+ 'node_text_inside': self.config.get('node_text_inside', False),
798
803
  'CLICK_COMMENT': CLICK_COMMENT,
799
804
  'CLICK_FILL': click_properties['fill'],
800
805
  'CLICK_STROKE': click_properties['stroke'],
@@ -1411,7 +1416,7 @@ def library_compatibility_checks() -> None:
1411
1416
  None.
1412
1417
 
1413
1418
  """
1414
- if not version.parse(nx.__version__) >= version.parse('2.5'):
1419
+ if not version.parse(nx.__version__) >= version.parse('2.5.0'):
1415
1420
  logger.error('Networkx version should be >= 2.5')
1416
1421
  logger.info('Hint: pip install -U networkx')
1417
1422
 
@@ -1597,20 +1602,52 @@ def adjmat2vec(adjmat, min_weight: float = 1.0) -> pd.DataFrame:
1597
1602
  return adjmat
1598
1603
 
1599
1604
 
1605
+ # def _check_hex_color(color, n=None, cmap='Set1'):
1606
+ # if isinstance(color, str) and len(color) != 7:
1607
+ # logger.warning('Input parameter [color] has wrong format. Must be like color="#000000" <auto-fixing>')
1608
+ # return get_hex_color(color, cmap=cmap)[0]
1609
+ # if isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and len(color) == 0:
1610
+ # logger.warning('Input parameter [color] has wrong format and length. Must be like: color=["#000000", "...", "#000000"] <auto-fixing>')
1611
+ # return get_hex_color(color, cmap=cmap)[0]
1612
+ # if isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and (not np.all(list(map(lambda x: len(x) == 7, color)))):
1613
+ # logger.warning('[color] contains incorrect hex-colors. Hex must be of length 7: ["#000000", "#000000", etc] <auto-fixing>')
1614
+ # return get_hex_color(color, cmap=cmap)[0]
1615
+ # if (n is not None) and isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and len(color) != n:
1616
+ # logger.warning(f'Input parameter [color] has wrong length. Must be of length: {str(n)} <auto-fixing>')
1617
+ # return get_hex_color(color, cmap=cmap)[0]
1618
+ # # Return original input
1619
+ # return color
1620
+
1600
1621
  def _check_hex_color(color, n=None, cmap='Set1'):
1601
- if isinstance(color, str) and len(color) != 7:
1602
- logger.warning('Input parameter [color] has wrong format. Must be like color="#000000" <auto-fixing>')
1603
- return get_hex_color(color, cmap=cmap)[0]
1604
- if isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and len(color) == 0:
1605
- logger.warning('Input parameter [color] has wrong format and length. Must be like: color=["#000000", "...", "#000000"] <auto-fixing>')
1606
- return get_hex_color(color, cmap=cmap)[0]
1607
- if isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and (not np.all(list(map(lambda x: len(x) == 7, color)))):
1608
- logger.warning('[color] contains incorrect hex-colors. Hex must be of length 7: ["#000000", "#000000", etc] <auto-fixing>')
1609
- return get_hex_color(color, cmap=cmap)[0]
1610
- if (n is not None) and isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and len(color) != n:
1611
- logger.warning(f'Input parameter [color] has wrong length. Must be of length: {str(n)} <auto-fixing>')
1612
- return get_hex_color(color, cmap=cmap)[0]
1613
- # Return original input
1622
+ seq_types = (list, np.ndarray, pd.Series, StringArray)
1623
+
1624
+ # Single string
1625
+ if isinstance(color, str):
1626
+ if len(color) != 7 or not color.startswith('#'):
1627
+ logger.warning('Invalid [color] format. Expected "#000000" <auto-fixing>')
1628
+ return get_hex_color(color, cmap=cmap)[0]
1629
+ return color
1630
+
1631
+ # Sequence input
1632
+ if isinstance(color, seq_types):
1633
+
1634
+ if len(color) == 0:
1635
+ logger.warning('Invalid [color] sequence. Expected ["#000000", ...] <auto-fixing>')
1636
+ return get_hex_color(color, cmap=cmap)[0]
1637
+
1638
+ valid = np.all([
1639
+ isinstance(x, str) and len(x) == 7 and x.startswith('#')
1640
+ for x in color
1641
+ ])
1642
+
1643
+ if not valid:
1644
+ logger.warning('Invalid hex colors in [color]. Expected ["#000000", ...] <auto-fixing>')
1645
+ return get_hex_color(color, cmap=cmap)[0]
1646
+
1647
+ if (n is not None) and (len(color) != n):
1648
+ logger.warning(f'Invalid [color] length. Expected length={n} <auto-fixing>')
1649
+ return get_hex_color(color, cmap=cmap)[0]
1650
+
1614
1651
  return color
1615
1652
 
1616
1653
 
@@ -1653,20 +1690,63 @@ def _compute_centrality(adjmat):
1653
1690
  # opacity = opacity[:-2]
1654
1691
  return opacity
1655
1692
 
1693
+ MARKERS_SUPPORTED = {
1694
+ 'circle',
1695
+ 'ellipse',
1696
+ 'rect', 'rectangle', 'square',
1697
+ 'rect-wide', 'rectangle-wide',
1698
+ 'triangle', 'triangle-up', 'triangle-down',
1699
+ 'diamond',
1700
+ 'star',
1701
+ 'hexagon',
1702
+ 'pentagon',
1703
+ }
1704
+
1705
+
1656
1706
  def _set_marker(self, marker, nodecount):
1657
- if isinstance(marker, type(None)):
1707
+ """Validate and broadcast the node marker/shape.
1708
+
1709
+ Supported shapes
1710
+ ----------------
1711
+ 'circle' : Circle (default)
1712
+ 'ellipse' : Ellipse (wider than tall)
1713
+ 'rect' / 'rectangle' / 'square' : Square
1714
+ 'rect-wide' / 'rectangle-wide' : Wide rectangle
1715
+ 'triangle' / 'triangle-up' : Triangle pointing up
1716
+ 'triangle-down' : Triangle pointing down
1717
+ 'diamond' : Diamond (rotated square)
1718
+ 'star' : 5-pointed star
1719
+ 'hexagon' : Regular hexagon
1720
+ 'pentagon' : Regular pentagon
1721
+ """
1722
+ if marker is None:
1658
1723
  marker = 'circle'
1659
1724
 
1660
- if isinstance(marker, list) and len(marker)==nodecount:
1661
- marker = np.array(marker)
1662
- elif 'numpy' in str(type(marker)) and len(marker)==nodecount:
1725
+ if isinstance(marker, (list, np.ndarray, pd.Series)) and len(marker) == nodecount:
1726
+ marker = np.array([str(m).lower() for m in marker])
1727
+ invalid = set(marker) - MARKERS_SUPPORTED
1728
+ if invalid:
1729
+ logger.warning(
1730
+ 'Unknown marker(s) %s. Supported: %s. Falling back to "circle" for those.',
1731
+ sorted(invalid), sorted(MARKERS_SUPPORTED)
1732
+ )
1733
+ marker = np.where(np.isin(marker, list(MARKERS_SUPPORTED)), marker, 'circle')
1734
+ elif 'numpy' in str(type(marker)) and len(marker) == nodecount:
1663
1735
  pass
1664
1736
  elif isinstance(marker, str):
1737
+ marker = marker.lower()
1738
+ if marker not in MARKERS_SUPPORTED:
1739
+ logger.warning(
1740
+ 'Unknown marker "%s". Supported: %s. Falling back to "circle".',
1741
+ marker, sorted(MARKERS_SUPPORTED)
1742
+ )
1743
+ marker = 'circle'
1665
1744
  marker = np.repeat(marker, nodecount)
1745
+ else:
1746
+ marker = np.repeat('circle', nodecount)
1666
1747
 
1667
- # Make check
1668
- if len(marker) != nodecount: raise ValueError("Node size must be of same length as the number of nodes")
1669
- # Return
1748
+ if len(marker) != nodecount:
1749
+ raise ValueError("Node marker must be of same length as the number of nodes")
1670
1750
  return marker
1671
1751
 
1672
1752
  def _set_node_size(self, size, minmax, nodecount):
@@ -8,7 +8,8 @@ function d3graphscript(config = {
8
8
  collision: 0.5,
9
9
  link_tension: 1,
10
10
  sticky: false,
11
- background_color: '#FFFFFF'
11
+ background_color: '#FFFFFF',
12
+ node_text_inside: false,
12
13
  }) {
13
14
 
14
15
  //Constants for the SVG
@@ -59,7 +60,7 @@ function d3graphscript(config = {
59
60
  if (sticky) {
60
61
  // Keep the node fixed and apply a visual "pinned" cue (dashed border)
61
62
  d.fixed = true;
62
- d3.select(this).select("circle")
63
+ d3.select(this).select(".node-shape")
63
64
  .style("stroke-dasharray", "4,2")
64
65
  .style("stroke-width", function(d) { return Math.max(parseFloat(d.node_size_edge) || 1, 2); });
65
66
  }
@@ -72,7 +73,142 @@ function d3graphscript(config = {
72
73
  .on("dragend", dragended);
73
74
 
74
75
  // ---- END DRAGGING ----
75
-
76
+
77
+ // =====================================================================
78
+ // ---- SHAPE RENDERING ------------------------------------------------
79
+ // Supported node_marker values:
80
+ // 'circle' – SVG <circle> (default)
81
+ // 'ellipse' – SVG <ellipse> (wider than tall)
82
+ // 'square' – square via <path>
83
+ // 'rectangle' – wide rectangle via <path>
84
+ // 'triangle' / 'triangle-up' – upward equilateral triangle
85
+ // 'triangle-down' – downward triangle
86
+ // 'diamond' – rotated square
87
+ // 'star' – 5-pointed star
88
+ // 'hexagon' – regular hexagon
89
+ // 'pentagon' – regular pentagon
90
+ // =====================================================================
91
+
92
+ /**
93
+ * Returns an SVG path `d` string for polygon/star shapes, centred at (0,0).
94
+ * Returns null for 'circle' and 'ellipse' (handled as native SVG elements).
95
+ */
96
+ function shapePathD(marker, r) {
97
+ r = parseFloat(r) || 8;
98
+ var m = (marker || 'circle').toLowerCase();
99
+ switch (m) {
100
+
101
+ case 'triangle':
102
+ case 'triangle-up': {
103
+ var h = r * Math.sqrt(3);
104
+ return 'M 0,' + (-r) +
105
+ ' L ' + (h / 2) + ',' + (r / 2) +
106
+ ' L ' + (-h / 2) + ',' + (r / 2) + ' Z';
107
+ }
108
+
109
+ case 'triangle-down': {
110
+ var h = r * Math.sqrt(3);
111
+ return 'M 0,' + r +
112
+ ' L ' + (h / 2) + ',' + (-r / 2) +
113
+ ' L ' + (-h / 2) + ',' + (-r / 2) + ' Z';
114
+ }
115
+
116
+ case 'square': {
117
+ var s = r * 1.4;
118
+ return 'M ' + (-s) + ',' + (-s) +
119
+ ' L ' + s + ',' + (-s) +
120
+ ' L ' + s + ',' + s +
121
+ ' L ' + (-s) + ',' + s + ' Z';
122
+ }
123
+
124
+ case 'rect':
125
+ case 'rectangle': {
126
+ var w = r * 2.0, h = r * 1.0;
127
+ return 'M ' + (-w) + ',' + (-h) +
128
+ ' L ' + w + ',' + (-h) +
129
+ ' L ' + w + ',' + h +
130
+ ' L ' + (-w) + ',' + h + ' Z';
131
+ }
132
+
133
+ case 'diamond': {
134
+ var rx = r * 1.0, ry = r * 1.4;
135
+ return 'M 0,' + (-ry) +
136
+ ' L ' + rx + ',0' +
137
+ ' L 0,' + ry +
138
+ ' L ' + (-rx) + ',0 Z';
139
+ }
140
+
141
+ case 'star': {
142
+ var outerR = r * 1.3, innerR = r * 0.5, pts = [];
143
+ for (var i = 0; i < 10; i++) {
144
+ var angle = (Math.PI / 5) * i - Math.PI / 2;
145
+ var rad = (i % 2 === 0) ? outerR : innerR;
146
+ pts.push(+(rad * Math.cos(angle)).toFixed(3) + ',' + +(rad * Math.sin(angle)).toFixed(3));
147
+ }
148
+ return 'M ' + pts.join(' L ') + ' Z';
149
+ }
150
+
151
+ case 'hexagon': {
152
+ var pts = [];
153
+ for (var i = 0; i < 6; i++) {
154
+ var angle = (Math.PI / 3) * i - Math.PI / 6;
155
+ pts.push(+(r * 1.1 * Math.cos(angle)).toFixed(3) + ',' + +(r * 1.1 * Math.sin(angle)).toFixed(3));
156
+ }
157
+ return 'M ' + pts.join(' L ') + ' Z';
158
+ }
159
+
160
+ case 'pentagon': {
161
+ var pts = [];
162
+ for (var i = 0; i < 5; i++) {
163
+ var angle = (2 * Math.PI / 5) * i - Math.PI / 2;
164
+ pts.push(+(r * 1.15 * Math.cos(angle)).toFixed(3) + ',' + +(r * 1.15 * Math.sin(angle)).toFixed(3));
165
+ }
166
+ return 'M ' + pts.join(' L ') + ' Z';
167
+ }
168
+
169
+ default:
170
+ return null; // circle or ellipse — caller uses native SVG element
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Appends the correct SVG shape child to every node <g>.
176
+ * All shapes receive the CSS class "node-shape" for uniform selection.
177
+ * circle / ellipse → native SVG elements positioned via cx/cy in tick
178
+ * everything else → <path> positioned via transform="translate()" in tick
179
+ */
180
+ function appendShape(nodeSelection) {
181
+ nodeSelection.each(function(d) {
182
+ var g = d3.select(this);
183
+ var marker = (d.node_marker || 'circle').toLowerCase();
184
+ var r = parseFloat(d.node_size) || 8;
185
+ var shape;
186
+
187
+ if (marker === 'ellipse') {
188
+ shape = g.append('ellipse')
189
+ .attr('class', 'node-shape')
190
+ .attr('rx', r * 1.6)
191
+ .attr('ry', r);
192
+ } else if (marker === 'circle' || shapePathD(marker, r) === null) {
193
+ shape = g.append('circle')
194
+ .attr('class', 'node-shape')
195
+ .attr('r', r);
196
+ } else {
197
+ shape = g.append('path')
198
+ .attr('class', 'node-shape')
199
+ .attr('d', shapePathD(marker, r));
200
+ }
201
+
202
+ shape
203
+ .style('fill', d.node_color)
204
+ .style('opacity', d.node_opacity)
205
+ .style('stroke-width', d.node_size_edge)
206
+ .style('stroke', d.node_color_edge);
207
+ });
208
+ }
209
+
210
+ // ---- END SHAPE RENDERING ----
211
+
76
212
  //Append a SVG to the body of the html page. Assign this SVG as an object to svg
77
213
  var svg = d3.select("body").append("svg")
78
214
  .attr("width", width)
@@ -129,7 +265,7 @@ function d3graphscript(config = {
129
265
  d3.event.preventDefault();
130
266
  d.fixed = false;
131
267
  // Remove pinned visual cue
132
- d3.select(this).select("circle")
268
+ d3.select(this).select(".node-shape")
133
269
  .style("stroke-dasharray", null)
134
270
  .style("stroke-width", function(d) { return d.node_size_edge; });
135
271
  force.resume();
@@ -137,23 +273,31 @@ function d3graphscript(config = {
137
273
  }
138
274
 
139
275
  {{ CLICK_COMMENT }} node.on('click', color_on_click); // ON CLICK HANDLER
140
-
141
-
142
- node.append("circle")
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
276
+
277
+ // Append the correct shape per node (circle, ellipse, rect, triangle, etc.)
278
+ appendShape(node);
148
279
 
149
280
  // Text in nodes
150
281
  node.append("text")
151
- .attr("dx", 10)
282
+ .attr("dx", function(d) { return config.node_text_inside ? 0 : 10; })
152
283
  .attr("dy", ".35em")
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");
284
+ .attr("text-anchor", function(d) { return config.node_text_inside ? "middle" : "start"; })
285
+ .text(function(d) { return d.node_name; }) // NODE-TEXT
286
+ .style("font-size", function(d) {
287
+ if (config.node_text_inside) {
288
+ // Auto-shrink font so the label fits within the circle diameter
289
+ var r = parseFloat(d.node_size) || 8;
290
+ var label = d.node_name || "";
291
+ // Approximate: 0.6 * fontsize ≈ char width; fit label in 2*r with some padding
292
+ var maxFontSize = parseFloat(d.node_fontsize) || 12;
293
+ var fitSize = Math.min(maxFontSize, (1.8 * r) / Math.max(label.length * 0.6, 1));
294
+ return Math.max(fitSize, 6) + "px";
295
+ }
296
+ return d.node_fontsize + "px"; // NODE FONT SIZE
297
+ })
298
+ .style("fill", function(d) {return d.node_fontcolor;}) // NODE FONT COLOR
299
+ .style("font-family", "monospace")
300
+ .style("pointer-events", "none");
157
301
 
158
302
  let showInHover = ["node_tooltip"]; // Tooltip
159
303
  node.append("title")
@@ -169,8 +313,20 @@ function d3graphscript(config = {
169
313
  .attr("y1", function(d) { return d.source.y; })
170
314
  .attr("x2", function(d) { return d.target.x; })
171
315
  .attr("y2", function(d) { return d.target.y; });
172
- d3.selectAll("circle").attr("cx", function(d) { return d.x; })
173
- .attr("cy", function(d) { return d.y; });
316
+
317
+ // Position each shape according to its SVG element type:
318
+ // circle / ellipse → cx / cy attributes
319
+ // path → transform translate(x, y)
320
+ d3.selectAll(".node-shape").each(function(d) {
321
+ var el = d3.select(this);
322
+ var tag = this.tagName.toLowerCase();
323
+ if (tag === 'circle' || tag === 'ellipse') {
324
+ el.attr("cx", d.x).attr("cy", d.y);
325
+ } else {
326
+ el.attr("transform", "translate(" + d.x + "," + d.y + ")");
327
+ }
328
+ });
329
+
174
330
  d3.selectAll("text").attr("x", function(d) { return d.x; })
175
331
  .attr("y", function(d) { return d.y; })
176
332
  linkText.attr("x", function(d) { return (d.source.x + d.target.x) / 2; }) // ADD TEXT ON THE EDGES (PART 2/2)
@@ -263,25 +419,48 @@ function d3graphscript(config = {
263
419
 
264
420
  // COLOR ON CLICK
265
421
  function color_on_click() {
266
- // Give the original color back to all nodes
422
+ // Reset all nodes to their original styles
267
423
  d3.selectAll(".node")
268
- .select("circle")
424
+ .select(".node-shape")
269
425
  .style("fill", function(d) {return d.node_color;})
270
426
  .style("opacity", function(d) {return d.node_opacity;})
271
427
  .style("stroke", function(d) {return d.node_color_edge;})
272
428
  .style("stroke-width", function(d) {return d.node_size_edge;})
273
429
  // 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
- ;
430
+ .style("stroke-dasharray", function(d) { return (sticky && d.fixed) ? "4,2" : null; });
431
+
432
+ // Reset circle radii
433
+ d3.selectAll(".node").select("circle.node-shape")
434
+ .attr("r", function(d) { return d.node_size; });
277
435
 
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
- ;}
436
+ // Apply click styling to the selected node's shape
437
+ var clickedShape = d3.select(this).select(".node-shape");
438
+ clickedShape
439
+ .style("fill", {{ CLICK_FILL }})
440
+ .style("stroke", "{{ CLICK_STROKE }}")
441
+ .style("stroke-width", {{ CLICK_STROKEW }});
442
+
443
+ // Scale up the shape — strategy depends on element type
444
+ var shapeNode = clickedShape.node();
445
+ if (!shapeNode) return;
446
+ var tag = shapeNode.tagName.toLowerCase();
447
+ if (tag === 'circle') {
448
+ clickedShape.attr("r", function(d) { return d.node_size * {{ CLICK_SIZE }}; });
449
+ } else if (tag === 'ellipse') {
450
+ clickedShape.each(function(d) {
451
+ var r = parseFloat(d.node_size) || 8;
452
+ d3.select(this)
453
+ .attr("rx", r * 1.6 * {{ CLICK_SIZE }})
454
+ .attr("ry", r * {{ CLICK_SIZE }});
455
+ });
456
+ } else {
457
+ // <path>: re-compute path with a scaled radius
458
+ clickedShape.each(function(d) {
459
+ var r = (parseFloat(d.node_size) || 8) * {{ CLICK_SIZE }};
460
+ d3.select(this).attr("d", shapePathD(d.node_marker, r));
461
+ });
462
+ }
463
+ }
285
464
 
286
465
 
287
466
 
@@ -128,7 +128,8 @@
128
128
  collision: {{ collision }},
129
129
  link_tension: {{ link_tension }},
130
130
  sticky: {{ sticky | lower }},
131
- background_color: '{{ background_color }}'
131
+ background_color: '{{ background_color }}',
132
+ node_text_inside: {{ node_text_inside | lower }},
132
133
  })
133
134
  });
134
135
 
@@ -18,8 +18,13 @@ h3 {
18
18
  font-family: "Helvetica";
19
19
  }
20
20
 
21
- /* Sticky / fixed node indicator: dashed outline ring */
22
- circle.node-fixed {
21
+ /* Sticky / fixed node indicator: dashed outline ring — works for all shape types */
22
+ .node .node-shape.node-fixed {
23
23
  stroke-dasharray: 4, 2;
24
24
  stroke-width: 2.5px;
25
+ }
26
+
27
+ /* Shared pointer cursor for all node shapes */
28
+ .node .node-shape {
29
+ cursor: pointer;
25
30
  }
@@ -1,3 +1,27 @@
1
+ from d3graph import d3graph, import_example, vec2adjmat
2
+
3
+ d3 = d3graph()
4
+ adjmat, df = import_example('karate')
5
+ d3.graph(adjmat)
6
+
7
+ # Mix shapes per node
8
+ markers = ['circle', 'star', 'diamond', 'square', 'pentagon', 'hexagon', 'rectangle', 'triangle-down', 'triangle'] * 8
9
+ d3.set_node_properties(marker=markers[:len(adjmat.columns)], color=df['label'].values, cmap='Set1')
10
+ d3.show(filepath="d:/repos/erdogant.github.io/docs/d3graph/d3graph/node_shapes.html")
11
+
12
+ # %%
13
+ from d3graph import d3graph, import_example
14
+
15
+ d3 = d3graph()
16
+ adjmat, df = import_example('karate')
17
+ d3.graph(adjmat)
18
+
19
+ # Set shape for node
20
+ d3.set_node_properties(marker='hexagon', color=df['label'].values, cmap='Set1')
21
+ d3.show(filepath="d:/repos/erdogant.github.io/docs/d3graph/d3graph/node_shape.html")
22
+
23
+ # %%
24
+
1
25
  from d3graph import d3graph, import_example, get_hex_color
2
26
 
3
27
  # Initialize
@@ -44,10 +68,6 @@ d3.show()
44
68
  d3 = d3graph(adjmat, sticky=False)
45
69
  d3.show()
46
70
 
47
- # %%
48
-
49
-
50
-
51
71
 
52
72
  # %%
53
73
 
@@ -97,7 +117,7 @@ adjmat = vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'
97
117
  d3.graph(adjmat)
98
118
 
99
119
  # Set Node properties
100
- d3.set_node_properties(edge_color='#000000', cmap='Blues', minmax=[5, 13], fontcolor='#808080')
120
+ d3.set_node_properties(marker='square', edge_color='#000000', cmap='Blues', minmax=[5, 13], fontcolor='#808080')
101
121
  # Show the graph
102
122
  # d3.show()
103
123
 
@@ -106,6 +126,7 @@ d3.set_node_properties(edge_color='#000000', cmap='Blues', minmax=[5, 13], fontc
106
126
  # Set edge properties
107
127
  d3.set_edge_properties(directed=True, marker_end='arrow')
108
128
  d3.show()
129
+ d3.show(node_text_inside=True)
109
130
 
110
131
 
111
132
 
@@ -207,6 +228,9 @@ from d3graph import d3graph, adjmat2vec, vec2adjmat
207
228
 
208
229
  #%%
209
230
  from d3graph import check_logger
231
+ import numpy as np
232
+ import pandas as pd
233
+
210
234
  check_logger(verbose='debug')
211
235
  check_logger(verbose='info')
212
236
  check_logger(verbose='info')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: d3graph
3
- Version: 2.7.2
3
+ Version: 2.8.1
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