d3graph 2.7.2__tar.gz → 2.8.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.7.2/d3graph.egg-info → d3graph-2.8.0}/PKG-INFO +1 -1
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph/__init__.py +2 -2
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph/d3graph.py +57 -9
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph/d3js/d3graphscript.js +210 -31
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph/d3js/index.html.j2 +2 -1
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph/d3js/style.css +7 -2
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph/examples.py +29 -5
- {d3graph-2.7.2 → d3graph-2.8.0/d3graph.egg-info}/PKG-INFO +1 -1
- {d3graph-2.7.2 → d3graph-2.8.0}/LICENSE +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/MANIFEST.in +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/README.md +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph/d3js/d3.v3.js +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph/examples_docs.py +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph.egg-info/SOURCES.txt +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph.egg-info/dependency_links.txt +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph.egg-info/requires.txt +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/d3graph.egg-info/top_level.txt +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/pyproject.toml +0 -0
- {d3graph-2.7.2 → d3graph-2.8.0}/setup.cfg +0 -0
|
@@ -17,7 +17,7 @@ from d3graph.d3graph import (
|
|
|
17
17
|
|
|
18
18
|
__author__ = 'Erdogan Tasksen'
|
|
19
19
|
__email__ = 'erdogant@gmail.com'
|
|
20
|
-
__version__ = '2.
|
|
20
|
+
__version__ = '2.8.0'
|
|
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
|
-
*
|
|
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
|
|
|
@@ -1653,20 +1658,63 @@ def _compute_centrality(adjmat):
|
|
|
1653
1658
|
# opacity = opacity[:-2]
|
|
1654
1659
|
return opacity
|
|
1655
1660
|
|
|
1661
|
+
MARKERS_SUPPORTED = {
|
|
1662
|
+
'circle',
|
|
1663
|
+
'ellipse',
|
|
1664
|
+
'rect', 'rectangle', 'square',
|
|
1665
|
+
'rect-wide', 'rectangle-wide',
|
|
1666
|
+
'triangle', 'triangle-up', 'triangle-down',
|
|
1667
|
+
'diamond',
|
|
1668
|
+
'star',
|
|
1669
|
+
'hexagon',
|
|
1670
|
+
'pentagon',
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
|
|
1656
1674
|
def _set_marker(self, marker, nodecount):
|
|
1657
|
-
|
|
1675
|
+
"""Validate and broadcast the node marker/shape.
|
|
1676
|
+
|
|
1677
|
+
Supported shapes
|
|
1678
|
+
----------------
|
|
1679
|
+
'circle' : Circle (default)
|
|
1680
|
+
'ellipse' : Ellipse (wider than tall)
|
|
1681
|
+
'rect' / 'rectangle' / 'square' : Square
|
|
1682
|
+
'rect-wide' / 'rectangle-wide' : Wide rectangle
|
|
1683
|
+
'triangle' / 'triangle-up' : Triangle pointing up
|
|
1684
|
+
'triangle-down' : Triangle pointing down
|
|
1685
|
+
'diamond' : Diamond (rotated square)
|
|
1686
|
+
'star' : 5-pointed star
|
|
1687
|
+
'hexagon' : Regular hexagon
|
|
1688
|
+
'pentagon' : Regular pentagon
|
|
1689
|
+
"""
|
|
1690
|
+
if marker is None:
|
|
1658
1691
|
marker = 'circle'
|
|
1659
1692
|
|
|
1660
|
-
if isinstance(marker, list) and len(marker)==nodecount:
|
|
1661
|
-
marker = np.array(marker)
|
|
1662
|
-
|
|
1693
|
+
if isinstance(marker, (list, np.ndarray, pd.Series)) and len(marker) == nodecount:
|
|
1694
|
+
marker = np.array([str(m).lower() for m in marker])
|
|
1695
|
+
invalid = set(marker) - MARKERS_SUPPORTED
|
|
1696
|
+
if invalid:
|
|
1697
|
+
logger.warning(
|
|
1698
|
+
'Unknown marker(s) %s. Supported: %s. Falling back to "circle" for those.',
|
|
1699
|
+
sorted(invalid), sorted(MARKERS_SUPPORTED)
|
|
1700
|
+
)
|
|
1701
|
+
marker = np.where(np.isin(marker, list(MARKERS_SUPPORTED)), marker, 'circle')
|
|
1702
|
+
elif 'numpy' in str(type(marker)) and len(marker) == nodecount:
|
|
1663
1703
|
pass
|
|
1664
1704
|
elif isinstance(marker, str):
|
|
1705
|
+
marker = marker.lower()
|
|
1706
|
+
if marker not in MARKERS_SUPPORTED:
|
|
1707
|
+
logger.warning(
|
|
1708
|
+
'Unknown marker "%s". Supported: %s. Falling back to "circle".',
|
|
1709
|
+
marker, sorted(MARKERS_SUPPORTED)
|
|
1710
|
+
)
|
|
1711
|
+
marker = 'circle'
|
|
1665
1712
|
marker = np.repeat(marker, nodecount)
|
|
1713
|
+
else:
|
|
1714
|
+
marker = np.repeat('circle', nodecount)
|
|
1666
1715
|
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
# Return
|
|
1716
|
+
if len(marker) != nodecount:
|
|
1717
|
+
raise ValueError("Node marker must be of same length as the number of nodes")
|
|
1670
1718
|
return marker
|
|
1671
1719
|
|
|
1672
1720
|
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("
|
|
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("
|
|
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
|
|
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
|
|
154
|
-
.
|
|
155
|
-
.style("
|
|
156
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
//
|
|
422
|
+
// Reset all nodes to their original styles
|
|
267
423
|
d3.selectAll(".node")
|
|
268
|
-
.select("
|
|
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
|
-
|
|
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
|
-
//
|
|
279
|
-
d3.select(this).select("
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
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')
|
|
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
|