bsplot 0.0.2__py3-none-any.whl
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.
- bsplot/__init__.py +23 -0
- bsplot/anat.py +55 -0
- bsplot/animate.py +194 -0
- bsplot/bioicons.py +777 -0
- bsplot/brain.py +188 -0
- bsplot/colors.py +1935 -0
- bsplot/data/HCP_avg-SC.txt +379 -0
- bsplot/data/MNI152.rh.pial +0 -0
- bsplot/data/STN_lh.nii.gz +0 -0
- bsplot/data/STN_rh.nii.gz +0 -0
- bsplot/data/__init__.py +22 -0
- bsplot/data/left_electrode.ply +257270 -0
- bsplot/data/parcellations/HCP-MMP1.L.label.gii +442 -0
- bsplot/data/parcellations/HCP-MMP1.R.label.gii +442 -0
- bsplot/data/parcellations/code/split_hcpmmp1.sh +4 -0
- bsplot/data/right_electrode.ply +257270 -0
- bsplot/data/surface.py +112 -0
- bsplot/data/tpl-MNI152NLin2009b_atlas-hcpmmp1_desc-ordered_dseg.nii.gz +0 -0
- bsplot/figure.py +177 -0
- bsplot/graph/__init__.py +31 -0
- bsplot/graph/edges.py +312 -0
- bsplot/graph/flowchart.py +762 -0
- bsplot/graph/layout.py +201 -0
- bsplot/graph/network.py +974 -0
- bsplot/graph/nodes.py +452 -0
- bsplot/panels.py +110 -0
- bsplot/scientific_color_maps/__init__.py +0 -0
- bsplot/scientific_color_maps/_sync_from_zenodo_download.sh +1 -0
- bsplot/scientific_color_maps/batlowK.txt +256 -0
- bsplot/scientific_color_maps/batlowW.txt +256 -0
- bsplot/scientific_color_maps/cyclic/bamO.txt +256 -0
- bsplot/scientific_color_maps/cyclic/brocO.txt +256 -0
- bsplot/scientific_color_maps/cyclic/corkO.txt +256 -0
- bsplot/scientific_color_maps/cyclic/romaO.txt +256 -0
- bsplot/scientific_color_maps/cyclic/vikO.txt +256 -0
- bsplot/scientific_color_maps/diverging/bam.txt +256 -0
- bsplot/scientific_color_maps/diverging/berlin.txt +256 -0
- bsplot/scientific_color_maps/diverging/broc.txt +256 -0
- bsplot/scientific_color_maps/diverging/cork.txt +256 -0
- bsplot/scientific_color_maps/diverging/lisbon.txt +256 -0
- bsplot/scientific_color_maps/diverging/managua.txt +256 -0
- bsplot/scientific_color_maps/diverging/roma.txt +256 -0
- bsplot/scientific_color_maps/diverging/tofino.txt +256 -0
- bsplot/scientific_color_maps/diverging/vanimo.txt +256 -0
- bsplot/scientific_color_maps/diverging/vik.txt +256 -0
- bsplot/scientific_color_maps/multisequential/bukavu.txt +256 -0
- bsplot/scientific_color_maps/multisequential/fes.txt +256 -0
- bsplot/scientific_color_maps/multisequential/oleron.txt +256 -0
- bsplot/scientific_color_maps/naviaW.txt +256 -0
- bsplot/scientific_color_maps/sequential/acton.txt +256 -0
- bsplot/scientific_color_maps/sequential/bamako.txt +256 -0
- bsplot/scientific_color_maps/sequential/batlow.txt +256 -0
- bsplot/scientific_color_maps/sequential/bilbao.txt +256 -0
- bsplot/scientific_color_maps/sequential/buda.txt +256 -0
- bsplot/scientific_color_maps/sequential/davos.txt +256 -0
- bsplot/scientific_color_maps/sequential/devon.txt +256 -0
- bsplot/scientific_color_maps/sequential/glasgow.txt +256 -0
- bsplot/scientific_color_maps/sequential/grayC.txt +256 -0
- bsplot/scientific_color_maps/sequential/hawaii.txt +256 -0
- bsplot/scientific_color_maps/sequential/imola.txt +256 -0
- bsplot/scientific_color_maps/sequential/lajolla.txt +256 -0
- bsplot/scientific_color_maps/sequential/lapaz.txt +256 -0
- bsplot/scientific_color_maps/sequential/lipari.txt +256 -0
- bsplot/scientific_color_maps/sequential/navia.txt +256 -0
- bsplot/scientific_color_maps/sequential/nuuk.txt +256 -0
- bsplot/scientific_color_maps/sequential/oslo.txt +256 -0
- bsplot/scientific_color_maps/sequential/tokyo.txt +256 -0
- bsplot/scientific_color_maps/sequential/turku.txt +256 -0
- bsplot/streamlines.py +314 -0
- bsplot/style.py +791 -0
- bsplot/styles/black.mplstyle +78 -0
- bsplot/styles/bss.mplstyle +50 -0
- bsplot/styles/bwcomp.mplstyle +30 -0
- bsplot/styles/nature.mplstyle +57 -0
- bsplot/styles/transparent.mplstyle +16 -0
- bsplot/styles/tvbo.mplstyle +57 -0
- bsplot/surface.py +2370 -0
- bsplot/templates.py +270 -0
- bsplot/text.py +199 -0
- bsplot/text2obj.py +59 -0
- bsplot/timeseries.py +10 -0
- bsplot/utils.py +13 -0
- bsplot/volume.py +958 -0
- bsplot-0.0.2.dist-info/METADATA +60 -0
- bsplot-0.0.2.dist-info/RECORD +89 -0
- bsplot-0.0.2.dist-info/WHEEL +5 -0
- bsplot-0.0.2.dist-info/entry_points.txt +2 -0
- bsplot-0.0.2.dist-info/licenses/LICENSE +193 -0
- bsplot-0.0.2.dist-info/top_level.txt +1 -0
bsplot/graph/nodes.py
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import matplotlib
|
|
2
|
+
import matplotlib.colors as mcolors
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
|
+
from typing import Dict, Optional, Union, Tuple, Any
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_actual_bounds(ax, axis="x"):
|
|
10
|
+
renderer = ax.figure.canvas.get_renderer()
|
|
11
|
+
xcoords = []
|
|
12
|
+
ycoords = []
|
|
13
|
+
|
|
14
|
+
for artist in ax.get_children():
|
|
15
|
+
if not isinstance(artist, matplotlib.text.Text):
|
|
16
|
+
continue # Only process text elements
|
|
17
|
+
elif artist.get_text() == "":
|
|
18
|
+
continue
|
|
19
|
+
if hasattr(artist, "get_window_extent"):
|
|
20
|
+
try:
|
|
21
|
+
# Get the bounding box in display coordinates
|
|
22
|
+
bbox = artist.get_window_extent(renderer=renderer)
|
|
23
|
+
# print(f"Bbox for '{artist.get_text()}': {bbox}")
|
|
24
|
+
|
|
25
|
+
# Transform the bounding box corners to data coordinates
|
|
26
|
+
bbox_data = ax.transData.inverted().transform(
|
|
27
|
+
[
|
|
28
|
+
[bbox.x0, bbox.y0], # Bottom-left
|
|
29
|
+
[bbox.x1, bbox.y1], # Top-right
|
|
30
|
+
]
|
|
31
|
+
)
|
|
32
|
+
xcoords.extend([bbox_data[0, 0], bbox_data[1, 0]])
|
|
33
|
+
ycoords.extend([bbox_data[0, 1], bbox_data[1, 1]])
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(f"Error processing '{artist.get_text()}': {e}")
|
|
36
|
+
|
|
37
|
+
if axis == "x":
|
|
38
|
+
return np.min(xcoords), np.max(xcoords)
|
|
39
|
+
elif axis == "y":
|
|
40
|
+
return np.min(ycoords), np.max(ycoords)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def draw_custom_nodes(
|
|
44
|
+
G,
|
|
45
|
+
pos,
|
|
46
|
+
labels=None,
|
|
47
|
+
font_size=10,
|
|
48
|
+
ax=None,
|
|
49
|
+
node_colors=None,
|
|
50
|
+
alpha=0.8,
|
|
51
|
+
facecolor=None,
|
|
52
|
+
edgecolor=None,
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Custom function to draw nodes as text in a network graph.
|
|
56
|
+
|
|
57
|
+
Parameters:
|
|
58
|
+
----------
|
|
59
|
+
G : networkx.Graph
|
|
60
|
+
The graph on which nodes will be drawn.
|
|
61
|
+
pos : dict
|
|
62
|
+
A dictionary with nodes as keys and positions as values. Positions should be tuples of (x, y) coordinates.
|
|
63
|
+
labels : dict, optional
|
|
64
|
+
A dictionary with node labels. If None, nodes are labeled with node names.
|
|
65
|
+
font_size : int, optional
|
|
66
|
+
The font size of the node labels (default is 10).
|
|
67
|
+
ax : matplotlib.axes.Axes, optional
|
|
68
|
+
Matplotlib axes object to draw the nodes on. If None, the current axes will be used (default is None).
|
|
69
|
+
node_colors : dict or str, optional
|
|
70
|
+
A dictionary specifying the color for each node or a single color for all nodes (default is None).
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
-------
|
|
74
|
+
list
|
|
75
|
+
A list of text objects for the node labels.
|
|
76
|
+
"""
|
|
77
|
+
x = [v[0] for v in pos.values()]
|
|
78
|
+
y = [v[1] for v in pos.values()]
|
|
79
|
+
|
|
80
|
+
if ax is None:
|
|
81
|
+
ax = plt.gca()
|
|
82
|
+
|
|
83
|
+
if min(x) != max(x):
|
|
84
|
+
ax.set_xlim([min(x), max(x)])
|
|
85
|
+
if min(y) != max(y):
|
|
86
|
+
ax.set_ylim([min(y), max(y)])
|
|
87
|
+
|
|
88
|
+
if labels is None:
|
|
89
|
+
labels = {node: node for node in G.nodes()}
|
|
90
|
+
|
|
91
|
+
if node_colors is None:
|
|
92
|
+
node_colors = ["grey" for node in G.nodes()]
|
|
93
|
+
elif isinstance(node_colors, str):
|
|
94
|
+
if node_colors.startswith("#"):
|
|
95
|
+
node_colors = mcolors.to_rgba(node_colors)
|
|
96
|
+
node_colors = [node_colors for node in G.nodes()]
|
|
97
|
+
elif isinstance(node_colors, dict):
|
|
98
|
+
node_colors = [
|
|
99
|
+
(
|
|
100
|
+
mcolors.to_rgba(node_colors[node])
|
|
101
|
+
if isinstance(node_colors[node], str)
|
|
102
|
+
else node_colors[node]
|
|
103
|
+
)
|
|
104
|
+
for node in G.nodes()
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
texts = {} # To store the text objects
|
|
108
|
+
bbox_pad = 0.3 # Padding for the bounding box
|
|
109
|
+
for i, (node, position) in enumerate(pos.items()):
|
|
110
|
+
text = labels[node]
|
|
111
|
+
x, y = position
|
|
112
|
+
txt_obj = ax.text(
|
|
113
|
+
x,
|
|
114
|
+
y,
|
|
115
|
+
text,
|
|
116
|
+
bbox=dict(
|
|
117
|
+
facecolor=node_colors[i] if facecolor is None else facecolor,
|
|
118
|
+
edgecolor=node_colors[i] if edgecolor is None else edgecolor,
|
|
119
|
+
alpha=alpha,
|
|
120
|
+
boxstyle=f"round,pad={bbox_pad}",
|
|
121
|
+
),
|
|
122
|
+
ha="center",
|
|
123
|
+
va="top",
|
|
124
|
+
fontsize=font_size,
|
|
125
|
+
)
|
|
126
|
+
texts[node] = txt_obj
|
|
127
|
+
ax.figure.canvas.draw()
|
|
128
|
+
# Force rendering to ensure bounding boxes are accurate
|
|
129
|
+
bbox_positions = {
|
|
130
|
+
node: txt.get_window_extent(
|
|
131
|
+
renderer=ax.figure.canvas.get_renderer()
|
|
132
|
+
).transformed(ax.transData.inverted())
|
|
133
|
+
for node, txt in texts.items()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
xmin, xmax = get_actual_bounds(ax, axis="x")
|
|
137
|
+
ymin, ymax = get_actual_bounds(ax, axis="y")
|
|
138
|
+
ax.set_xlim([xmin - bbox_pad, xmax + bbox_pad])
|
|
139
|
+
ax.set_ylim([ymin - bbox_pad, ymax + bbox_pad])
|
|
140
|
+
|
|
141
|
+
return texts, bbox_positions
|
|
142
|
+
|
|
143
|
+
# return texts
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def draw_nodes_with_icons(
|
|
147
|
+
G,
|
|
148
|
+
pos,
|
|
149
|
+
labels: Optional[Dict[str, str]] = None,
|
|
150
|
+
icons: Optional[Dict[str, str]] = None,
|
|
151
|
+
font_size: int = 10,
|
|
152
|
+
ax=None,
|
|
153
|
+
node_colors: Optional[Union[Dict, str, list]] = None,
|
|
154
|
+
alpha: float = 0.8,
|
|
155
|
+
facecolor: Optional[str] = None,
|
|
156
|
+
edgecolor: Optional[str] = None,
|
|
157
|
+
icon_size: float = 0.15,
|
|
158
|
+
icon_position: str = "above",
|
|
159
|
+
local_icons_dir: Optional[Union[str, Path]] = None,
|
|
160
|
+
) -> Tuple[Dict, Dict, Dict]:
|
|
161
|
+
"""
|
|
162
|
+
Draw nodes with bioicons in a network graph.
|
|
163
|
+
|
|
164
|
+
This function extends draw_custom_nodes to support icons from
|
|
165
|
+
bioicons.com or local image files.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
G : networkx.Graph
|
|
170
|
+
The graph on which nodes will be drawn.
|
|
171
|
+
pos : dict
|
|
172
|
+
A dictionary with nodes as keys and positions as values.
|
|
173
|
+
labels : dict, optional
|
|
174
|
+
Node labels. If None, nodes are labeled with node names.
|
|
175
|
+
icons : dict, optional
|
|
176
|
+
Dictionary mapping node names to icon IDs or image paths.
|
|
177
|
+
Example: {"node1": "neuron", "node2": "/path/to/icon.png"}
|
|
178
|
+
font_size : int
|
|
179
|
+
Font size for node labels.
|
|
180
|
+
ax : matplotlib.axes.Axes, optional
|
|
181
|
+
Axes to draw on. If None, uses current axes.
|
|
182
|
+
node_colors : dict, str, or list, optional
|
|
183
|
+
Colors for each node.
|
|
184
|
+
alpha : float
|
|
185
|
+
Transparency of node boxes.
|
|
186
|
+
facecolor : str, optional
|
|
187
|
+
Background color for all nodes.
|
|
188
|
+
edgecolor : str, optional
|
|
189
|
+
Border color for all nodes.
|
|
190
|
+
icon_size : float
|
|
191
|
+
Size of icons relative to the plot.
|
|
192
|
+
icon_position : str
|
|
193
|
+
Where to place icons: "above", "below", "left", "right", or "center".
|
|
194
|
+
local_icons_dir : str or Path, optional
|
|
195
|
+
Directory containing local icon files.
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
texts : dict
|
|
200
|
+
Dictionary of text objects for each node.
|
|
201
|
+
bbox_positions : dict
|
|
202
|
+
Dictionary of bounding box positions for each node.
|
|
203
|
+
icon_annotations : dict
|
|
204
|
+
Dictionary of icon AnnotationBbox objects for each node.
|
|
205
|
+
|
|
206
|
+
Examples
|
|
207
|
+
--------
|
|
208
|
+
>>> import networkx as nx
|
|
209
|
+
>>> import matplotlib.pyplot as plt
|
|
210
|
+
>>> from bsplot.graph.nodes import draw_nodes_with_icons
|
|
211
|
+
>>>
|
|
212
|
+
>>> G = nx.Graph()
|
|
213
|
+
>>> G.add_nodes_from(["A", "B", "C"])
|
|
214
|
+
>>> G.add_edges_from([("A", "B"), ("B", "C")])
|
|
215
|
+
>>> pos = {"A": (0, 0), "B": (1, 0), "C": (2, 0)}
|
|
216
|
+
>>> icons = {"A": "neuron", "B": "brain", "C": "synapse"}
|
|
217
|
+
>>>
|
|
218
|
+
>>> fig, ax = plt.subplots()
|
|
219
|
+
>>> draw_nodes_with_icons(G, pos, icons=icons, ax=ax)
|
|
220
|
+
>>> plt.show()
|
|
221
|
+
"""
|
|
222
|
+
from ..bioicons import BioIcons
|
|
223
|
+
|
|
224
|
+
x_coords = [v[0] for v in pos.values()]
|
|
225
|
+
y_coords = [v[1] for v in pos.values()]
|
|
226
|
+
|
|
227
|
+
if ax is None:
|
|
228
|
+
ax = plt.gca()
|
|
229
|
+
|
|
230
|
+
# Set axis limits
|
|
231
|
+
x_margin = (max(x_coords) - min(x_coords)) * 0.2 if max(x_coords) != min(x_coords) else 1
|
|
232
|
+
y_margin = (max(y_coords) - min(y_coords)) * 0.2 if max(y_coords) != min(y_coords) else 1
|
|
233
|
+
ax.set_xlim([min(x_coords) - x_margin, max(x_coords) + x_margin])
|
|
234
|
+
ax.set_ylim([min(y_coords) - y_margin, max(y_coords) + y_margin])
|
|
235
|
+
|
|
236
|
+
if labels is None:
|
|
237
|
+
labels = {node: node for node in G.nodes()}
|
|
238
|
+
|
|
239
|
+
if icons is None:
|
|
240
|
+
icons = {}
|
|
241
|
+
|
|
242
|
+
# Process node colors
|
|
243
|
+
if node_colors is None:
|
|
244
|
+
node_colors_list = ["grey" for _ in G.nodes()]
|
|
245
|
+
elif isinstance(node_colors, str):
|
|
246
|
+
if node_colors.startswith("#"):
|
|
247
|
+
node_colors = mcolors.to_rgba(node_colors)
|
|
248
|
+
node_colors_list = [node_colors for _ in G.nodes()]
|
|
249
|
+
elif isinstance(node_colors, dict):
|
|
250
|
+
node_colors_list = [
|
|
251
|
+
(
|
|
252
|
+
mcolors.to_rgba(node_colors[node])
|
|
253
|
+
if isinstance(node_colors[node], str)
|
|
254
|
+
else node_colors[node]
|
|
255
|
+
)
|
|
256
|
+
for node in G.nodes()
|
|
257
|
+
]
|
|
258
|
+
else:
|
|
259
|
+
node_colors_list = list(node_colors)
|
|
260
|
+
|
|
261
|
+
# Initialize bioicons interface
|
|
262
|
+
bio_icons = BioIcons(
|
|
263
|
+
local_icons_dir=local_icons_dir,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
texts = {}
|
|
267
|
+
icon_annotations = {}
|
|
268
|
+
bbox_pad = 0.3
|
|
269
|
+
|
|
270
|
+
# Calculate offset based on icon position
|
|
271
|
+
def get_icon_offset(position: str, size: float) -> Tuple[float, float]:
|
|
272
|
+
offsets = {
|
|
273
|
+
"above": (0, size * 2),
|
|
274
|
+
"below": (0, -size * 2),
|
|
275
|
+
"left": (-size * 2, 0),
|
|
276
|
+
"right": (size * 2, 0),
|
|
277
|
+
"center": (0, 0),
|
|
278
|
+
}
|
|
279
|
+
return offsets.get(position, (0, size * 2))
|
|
280
|
+
|
|
281
|
+
for i, (node, position) in enumerate(pos.items()):
|
|
282
|
+
text = labels.get(node, str(node))
|
|
283
|
+
x, y = position
|
|
284
|
+
|
|
285
|
+
# Draw the text label with box
|
|
286
|
+
txt_obj = ax.text(
|
|
287
|
+
x,
|
|
288
|
+
y,
|
|
289
|
+
text,
|
|
290
|
+
bbox=dict(
|
|
291
|
+
facecolor=node_colors_list[i] if facecolor is None else facecolor,
|
|
292
|
+
edgecolor=node_colors_list[i] if edgecolor is None else edgecolor,
|
|
293
|
+
alpha=alpha,
|
|
294
|
+
boxstyle=f"round,pad={bbox_pad}",
|
|
295
|
+
),
|
|
296
|
+
ha="center",
|
|
297
|
+
va="center",
|
|
298
|
+
fontsize=font_size,
|
|
299
|
+
)
|
|
300
|
+
texts[node] = txt_obj
|
|
301
|
+
|
|
302
|
+
# Draw icon if specified for this node
|
|
303
|
+
if node in icons:
|
|
304
|
+
icon_id = icons[node]
|
|
305
|
+
dx, dy = get_icon_offset(icon_position, icon_size)
|
|
306
|
+
|
|
307
|
+
ab = bio_icons.plot(
|
|
308
|
+
icon_id,
|
|
309
|
+
ax=ax,
|
|
310
|
+
x=x + dx,
|
|
311
|
+
y=y + dy,
|
|
312
|
+
size=icon_size,
|
|
313
|
+
)
|
|
314
|
+
icon_annotations[node] = ab
|
|
315
|
+
|
|
316
|
+
ax.figure.canvas.draw()
|
|
317
|
+
|
|
318
|
+
# Get bounding boxes
|
|
319
|
+
bbox_positions = {}
|
|
320
|
+
try:
|
|
321
|
+
renderer = ax.figure.canvas.get_renderer()
|
|
322
|
+
bbox_positions = {
|
|
323
|
+
node: txt.get_window_extent(renderer=renderer).transformed(
|
|
324
|
+
ax.transData.inverted()
|
|
325
|
+
)
|
|
326
|
+
for node, txt in texts.items()
|
|
327
|
+
}
|
|
328
|
+
except Exception:
|
|
329
|
+
# Fallback if renderer is not available
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
# Adjust limits to fit everything
|
|
333
|
+
try:
|
|
334
|
+
xmin, xmax = get_actual_bounds(ax, axis="x")
|
|
335
|
+
ymin, ymax = get_actual_bounds(ax, axis="y")
|
|
336
|
+
if xmin is not None and xmax is not None:
|
|
337
|
+
ax.set_xlim([xmin - bbox_pad - icon_size, xmax + bbox_pad + icon_size])
|
|
338
|
+
if ymin is not None and ymax is not None:
|
|
339
|
+
ax.set_ylim([ymin - bbox_pad - icon_size, ymax + bbox_pad + icon_size])
|
|
340
|
+
except Exception:
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
return texts, bbox_positions, icon_annotations
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def draw_icon_nodes(
|
|
347
|
+
G,
|
|
348
|
+
pos,
|
|
349
|
+
icons: Dict[str, str],
|
|
350
|
+
labels: Optional[Dict[str, str]] = None,
|
|
351
|
+
font_size: int = 10,
|
|
352
|
+
ax=None,
|
|
353
|
+
icon_size: float = 0.2,
|
|
354
|
+
show_labels: bool = True,
|
|
355
|
+
label_offset: float = 0.15,
|
|
356
|
+
local_icons_dir: Optional[Union[str, Path]] = None,
|
|
357
|
+
) -> Tuple[Dict, Dict]:
|
|
358
|
+
"""
|
|
359
|
+
Draw network nodes using only bioicons (no text boxes).
|
|
360
|
+
|
|
361
|
+
This creates a cleaner visualization where nodes are represented
|
|
362
|
+
purely by icons with optional labels below.
|
|
363
|
+
|
|
364
|
+
Parameters
|
|
365
|
+
----------
|
|
366
|
+
G : networkx.Graph
|
|
367
|
+
The graph.
|
|
368
|
+
pos : dict
|
|
369
|
+
Node positions.
|
|
370
|
+
icons : dict
|
|
371
|
+
Dictionary mapping node names to icon IDs.
|
|
372
|
+
labels : dict, optional
|
|
373
|
+
Node labels. If None, uses node names.
|
|
374
|
+
font_size : int
|
|
375
|
+
Font size for labels.
|
|
376
|
+
ax : Axes, optional
|
|
377
|
+
Matplotlib axes.
|
|
378
|
+
icon_size : float
|
|
379
|
+
Size of icons.
|
|
380
|
+
show_labels : bool
|
|
381
|
+
Whether to show text labels below icons.
|
|
382
|
+
label_offset : float
|
|
383
|
+
Vertical offset for labels.
|
|
384
|
+
local_icons_dir : str or Path, optional
|
|
385
|
+
Local icons directory.
|
|
386
|
+
|
|
387
|
+
Returns
|
|
388
|
+
-------
|
|
389
|
+
icon_annotations : dict
|
|
390
|
+
Dictionary of icon AnnotationBbox objects.
|
|
391
|
+
texts : dict
|
|
392
|
+
Dictionary of text objects (empty if show_labels=False).
|
|
393
|
+
|
|
394
|
+
Examples
|
|
395
|
+
--------
|
|
396
|
+
>>> import networkx as nx
|
|
397
|
+
>>> G = nx.path_graph(3)
|
|
398
|
+
>>> pos = nx.spring_layout(G)
|
|
399
|
+
>>> icons = {0: "neuron", 1: "synapse", 2: "brain"}
|
|
400
|
+
>>> draw_icon_nodes(G, pos, icons)
|
|
401
|
+
"""
|
|
402
|
+
from ..bioicons import BioIcons
|
|
403
|
+
|
|
404
|
+
if ax is None:
|
|
405
|
+
ax = plt.gca()
|
|
406
|
+
|
|
407
|
+
if labels is None:
|
|
408
|
+
labels = {node: str(node) for node in G.nodes()}
|
|
409
|
+
|
|
410
|
+
# Initialize icons interface
|
|
411
|
+
bio_icons = BioIcons(
|
|
412
|
+
local_icons_dir=local_icons_dir,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
icon_annotations = {}
|
|
416
|
+
texts = {}
|
|
417
|
+
|
|
418
|
+
for node, position in pos.items():
|
|
419
|
+
x, y = position
|
|
420
|
+
|
|
421
|
+
# Draw icon
|
|
422
|
+
icon_id = icons.get(node, str(node))
|
|
423
|
+
ab = bio_icons.plot(
|
|
424
|
+
icon_id,
|
|
425
|
+
ax=ax,
|
|
426
|
+
x=x,
|
|
427
|
+
y=y,
|
|
428
|
+
size=icon_size,
|
|
429
|
+
)
|
|
430
|
+
icon_annotations[node] = ab
|
|
431
|
+
|
|
432
|
+
# Draw label if enabled
|
|
433
|
+
if show_labels:
|
|
434
|
+
txt = ax.text(
|
|
435
|
+
x,
|
|
436
|
+
y - label_offset,
|
|
437
|
+
labels.get(node, str(node)),
|
|
438
|
+
ha="center",
|
|
439
|
+
va="top",
|
|
440
|
+
fontsize=font_size,
|
|
441
|
+
color="#333333",
|
|
442
|
+
)
|
|
443
|
+
texts[node] = txt
|
|
444
|
+
|
|
445
|
+
# Set axis limits
|
|
446
|
+
x_coords = [v[0] for v in pos.values()]
|
|
447
|
+
y_coords = [v[1] for v in pos.values()]
|
|
448
|
+
margin = icon_size + label_offset + 0.1
|
|
449
|
+
ax.set_xlim([min(x_coords) - margin, max(x_coords) + margin])
|
|
450
|
+
ax.set_ylim([min(y_coords) - margin, max(y_coords) + margin])
|
|
451
|
+
|
|
452
|
+
return icon_annotations, texts
|
bsplot/panels.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import string
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_alphabet(index, uppercase=False):
|
|
5
|
+
"""
|
|
6
|
+
Get the alphabet letter(s) corresponding to an index (1-based).
|
|
7
|
+
Extends beyond 'Z' to 'AA', 'AB', etc., if needed.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
index (int): The 1-based index (1 = A, 2 = B, ..., 27 = AA).
|
|
11
|
+
uppercase (bool): If True, returns uppercase letters; otherwise, lowercase.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
str: The corresponding letter(s).
|
|
15
|
+
"""
|
|
16
|
+
alphabet = string.ascii_uppercase if uppercase else string.ascii_lowercase
|
|
17
|
+
result = ""
|
|
18
|
+
while index > 0:
|
|
19
|
+
index, remainder = divmod(index - 1, 26)
|
|
20
|
+
result = alphabet[remainder] + result
|
|
21
|
+
return result
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def add_panel_number(
|
|
25
|
+
ax,
|
|
26
|
+
label,
|
|
27
|
+
option="letters",
|
|
28
|
+
background=False,
|
|
29
|
+
loc="upper left",
|
|
30
|
+
coord="axes",
|
|
31
|
+
x_scale=1,
|
|
32
|
+
y_scale=1.01,
|
|
33
|
+
x_shift=0,
|
|
34
|
+
y_shift=0,
|
|
35
|
+
**kwargs,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Adds a label to the given axis near the upper-left corner.
|
|
39
|
+
If option is 'letters' and the label is a number, it converts it to the
|
|
40
|
+
corresponding alphabetical letter(s).
|
|
41
|
+
|
|
42
|
+
Placement coordinates:
|
|
43
|
+
- coord='axes' (default): positions are specified in axes-fraction coordinates
|
|
44
|
+
with (0,0) bottom-left and (1,1) top-right. x_shift/y_shift are interpreted
|
|
45
|
+
as fractions of the axes size (so they are data-independent).
|
|
46
|
+
- coord='data': positions are specified in data coordinates for backward
|
|
47
|
+
compatibility. In this mode x_scale/y_scale and x_shift/y_shift behave like
|
|
48
|
+
before, but are now robust to inverted axes.
|
|
49
|
+
|
|
50
|
+
Parameters:
|
|
51
|
+
ax (matplotlib.axes.Axes): The axis to add the label to.
|
|
52
|
+
label (int or str): The label to place in the upper left corner.
|
|
53
|
+
option (str): 'letters' to convert numbers to letters, 'numbers' to use label as-is.
|
|
54
|
+
background (bool): If True, adds a white rounded box behind the label.
|
|
55
|
+
loc (str): Currently unused; reserved for future placement options.
|
|
56
|
+
text_kwargs (dict): Additional keyword args forwarded to ax.text.
|
|
57
|
+
x_scale (float): Scale factor applied to the left x-limit before placement.
|
|
58
|
+
y_scale (float): Scale factor applied to the top y-limit before placement.
|
|
59
|
+
x_shift (float): Fraction of the x-axis span to shift after scaling
|
|
60
|
+
(+ moves right, - moves left), independent of data range.
|
|
61
|
+
y_shift (float): Fraction of the y-axis span to shift after scaling
|
|
62
|
+
(+ moves up, - moves down), independent of data range.
|
|
63
|
+
"""
|
|
64
|
+
kwargs = {
|
|
65
|
+
"fontsize": 16,
|
|
66
|
+
"fontweight": "bold",
|
|
67
|
+
"va": "bottom",
|
|
68
|
+
"ha": "center",
|
|
69
|
+
**kwargs,
|
|
70
|
+
}
|
|
71
|
+
if option == "letters" and isinstance(label, int):
|
|
72
|
+
label = get_alphabet(label)
|
|
73
|
+
|
|
74
|
+
if background:
|
|
75
|
+
kwargs["bbox"] = {
|
|
76
|
+
"boxstyle": "round,pad=0.1",
|
|
77
|
+
"facecolor": "white",
|
|
78
|
+
"edgecolor": "none",
|
|
79
|
+
"alpha": 0.8,
|
|
80
|
+
}
|
|
81
|
+
else:
|
|
82
|
+
kwargs["bbox"] = None
|
|
83
|
+
|
|
84
|
+
if coord == "axes":
|
|
85
|
+
# Axes-fraction coordinates: robust to inverted/scaled axes
|
|
86
|
+
ax.text(
|
|
87
|
+
0.0 + x_shift,
|
|
88
|
+
1.0 + y_shift,
|
|
89
|
+
str(label),
|
|
90
|
+
transform=ax.transAxes,
|
|
91
|
+
**kwargs,
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
# Data-coordinate placement (legacy). Robust to inverted axes.
|
|
95
|
+
x0, x1 = ax.get_xlim()
|
|
96
|
+
y0, y1 = ax.get_ylim()
|
|
97
|
+
x_left = min(x0, x1)
|
|
98
|
+
y_top = max(y0, y1)
|
|
99
|
+
|
|
100
|
+
x = x_left * x_scale
|
|
101
|
+
y = y_top * y_scale
|
|
102
|
+
x += abs(x1 - x0) * x_shift
|
|
103
|
+
y += abs(y1 - y0) * y_shift
|
|
104
|
+
|
|
105
|
+
ax.text(
|
|
106
|
+
x,
|
|
107
|
+
y,
|
|
108
|
+
str(label),
|
|
109
|
+
**kwargs,
|
|
110
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
find "/Users/leonmartin_bih/Downloads/ScientificColourMaps8" -type f -name "*.txt" ! -name "*10*" ! -name "*25*" ! -name "*50*" ! -name "*HEX*" ! -name "*S*" -exec cp {} "/Users/leonmartin_bih/tools/bsplot/bsplot/scientific_color_maps" \;
|