risk-network 0.0.8b7__tar.gz → 0.0.8b8__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.
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/PKG-INFO +1 -1
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/__init__.py +1 -1
- risk_network-0.0.8b8/risk/network/plot/__init__.py +6 -0
- risk_network-0.0.8b8/risk/network/plot/canvas.py +226 -0
- risk_network-0.0.8b8/risk/network/plot/contour.py +319 -0
- risk_network-0.0.8b8/risk/network/plot/labels.py +848 -0
- risk_network-0.0.8b8/risk/network/plot/network.py +269 -0
- risk_network-0.0.8b8/risk/network/plot/plotter.py +120 -0
- risk_network-0.0.8b8/risk/network/plot/utils.py +153 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk_network.egg-info/PKG-INFO +1 -1
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk_network.egg-info/SOURCES.txt +7 -1
- risk_network-0.0.8b7/risk/network/plot/base.py +0 -1809
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/LICENSE +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/MANIFEST.in +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/README.md +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/pyproject.toml +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/annotations/__init__.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/annotations/annotations.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/annotations/io.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/constants.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/log/__init__.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/log/config.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/log/params.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/neighborhoods/__init__.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/neighborhoods/community.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/neighborhoods/domains.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/neighborhoods/neighborhoods.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/network/__init__.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/network/geometry.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/network/graph.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/network/io.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/risk.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/stats/__init__.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/stats/hypergeom.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/stats/permutation/__init__.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/stats/permutation/permutation.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/stats/permutation/test_functions.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/stats/poisson.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk/stats/stats.py +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk_network.egg-info/dependency_links.txt +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk_network.egg-info/requires.txt +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/risk_network.egg-info/top_level.txt +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/setup.cfg +0 -0
- {risk_network-0.0.8b7 → risk_network-0.0.8b8}/setup.py +0 -0
@@ -0,0 +1,226 @@
|
|
1
|
+
"""
|
2
|
+
risk/network/plot/canvas
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
4
|
+
"""
|
5
|
+
|
6
|
+
from typing import List, Tuple, Union
|
7
|
+
|
8
|
+
import matplotlib.pyplot as plt
|
9
|
+
import numpy as np
|
10
|
+
|
11
|
+
from risk.log import params
|
12
|
+
from risk.network.graph import NetworkGraph
|
13
|
+
from risk.network.plot.utils import calculate_bounding_box, to_rgba
|
14
|
+
|
15
|
+
|
16
|
+
class Canvas:
|
17
|
+
"""A class for laying out the canvas in a network graph."""
|
18
|
+
|
19
|
+
def __init__(self, graph: NetworkGraph, ax: plt.Axes) -> None:
|
20
|
+
"""Initialize the Canvas with a NetworkGraph and axis for plotting.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
graph (NetworkGraph): The NetworkGraph object containing the network data.
|
24
|
+
ax (plt.Axes): The axis to plot the canvas on.
|
25
|
+
"""
|
26
|
+
self.graph = graph
|
27
|
+
self.ax = ax
|
28
|
+
|
29
|
+
def plot_title(
|
30
|
+
self,
|
31
|
+
title: Union[str, None] = None,
|
32
|
+
subtitle: Union[str, None] = None,
|
33
|
+
title_fontsize: int = 20,
|
34
|
+
subtitle_fontsize: int = 14,
|
35
|
+
font: str = "Arial",
|
36
|
+
title_color: str = "black",
|
37
|
+
subtitle_color: str = "gray",
|
38
|
+
title_y: float = 0.975,
|
39
|
+
title_space_offset: float = 0.075,
|
40
|
+
subtitle_offset: float = 0.025,
|
41
|
+
) -> None:
|
42
|
+
"""Plot title and subtitle on the network graph with customizable parameters.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
title (str, optional): Title of the plot. Defaults to None.
|
46
|
+
subtitle (str, optional): Subtitle of the plot. Defaults to None.
|
47
|
+
title_fontsize (int, optional): Font size for the title. Defaults to 16.
|
48
|
+
subtitle_fontsize (int, optional): Font size for the subtitle. Defaults to 12.
|
49
|
+
font (str, optional): Font family used for both title and subtitle. Defaults to "Arial".
|
50
|
+
title_color (str, optional): Color of the title text. Defaults to "black".
|
51
|
+
subtitle_color (str, optional): Color of the subtitle text. Defaults to "gray".
|
52
|
+
title_y (float, optional): Y-axis position of the title. Defaults to 0.975.
|
53
|
+
title_space_offset (float, optional): Fraction of figure height to leave for the space above the plot. Defaults to 0.075.
|
54
|
+
subtitle_offset (float, optional): Offset factor to position the subtitle below the title. Defaults to 0.025.
|
55
|
+
"""
|
56
|
+
# Log the title and subtitle parameters
|
57
|
+
params.log_plotter(
|
58
|
+
title=title,
|
59
|
+
subtitle=subtitle,
|
60
|
+
title_fontsize=title_fontsize,
|
61
|
+
subtitle_fontsize=subtitle_fontsize,
|
62
|
+
title_subtitle_font=font,
|
63
|
+
title_color=title_color,
|
64
|
+
subtitle_color=subtitle_color,
|
65
|
+
subtitle_offset=subtitle_offset,
|
66
|
+
title_y=title_y,
|
67
|
+
title_space_offset=title_space_offset,
|
68
|
+
)
|
69
|
+
|
70
|
+
# Get the current figure and axis dimensions
|
71
|
+
fig = self.ax.figure
|
72
|
+
# Use a tight layout to ensure that title and subtitle do not overlap with the original plot
|
73
|
+
fig.tight_layout(
|
74
|
+
rect=[0, 0, 1, 1 - title_space_offset]
|
75
|
+
) # Leave space above the plot for title
|
76
|
+
|
77
|
+
# Plot title if provided
|
78
|
+
if title:
|
79
|
+
# Set the title using figure's suptitle to ensure centering
|
80
|
+
self.ax.figure.suptitle(
|
81
|
+
title,
|
82
|
+
fontsize=title_fontsize,
|
83
|
+
color=title_color,
|
84
|
+
fontname=font,
|
85
|
+
x=0.5, # Center the title horizontally
|
86
|
+
ha="center",
|
87
|
+
va="top",
|
88
|
+
y=title_y,
|
89
|
+
)
|
90
|
+
|
91
|
+
# Plot subtitle if provided
|
92
|
+
if subtitle:
|
93
|
+
# Calculate the subtitle's y position based on title's position and subtitle_offset
|
94
|
+
subtitle_y_position = title_y - subtitle_offset
|
95
|
+
self.ax.figure.text(
|
96
|
+
0.5, # Ensure horizontal centering for subtitle
|
97
|
+
subtitle_y_position,
|
98
|
+
subtitle,
|
99
|
+
ha="center",
|
100
|
+
va="top",
|
101
|
+
fontname=font,
|
102
|
+
fontsize=subtitle_fontsize,
|
103
|
+
color=subtitle_color,
|
104
|
+
)
|
105
|
+
|
106
|
+
def plot_circle_perimeter(
|
107
|
+
self,
|
108
|
+
scale: float = 1.0,
|
109
|
+
linestyle: str = "dashed",
|
110
|
+
linewidth: float = 1.5,
|
111
|
+
color: Union[str, List, Tuple, np.ndarray] = "black",
|
112
|
+
outline_alpha: Union[float, None] = 1.0,
|
113
|
+
fill_alpha: Union[float, None] = 0.0,
|
114
|
+
) -> None:
|
115
|
+
"""Plot a circle around the network graph to represent the network perimeter.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
scale (float, optional): Scaling factor for the perimeter diameter. Defaults to 1.0.
|
119
|
+
linestyle (str, optional): Line style for the network perimeter circle (e.g., dashed, solid). Defaults to "dashed".
|
120
|
+
linewidth (float, optional): Width of the circle's outline. Defaults to 1.5.
|
121
|
+
color (str, list, tuple, or np.ndarray, optional): Color of the network perimeter circle. Defaults to "black".
|
122
|
+
outline_alpha (float, None, optional): Transparency level of the circle outline. If provided, it overrides any existing alpha
|
123
|
+
values found in color. Defaults to 1.0.
|
124
|
+
fill_alpha (float, None, optional): Transparency level of the circle fill. If provided, it overrides any existing alpha values
|
125
|
+
found in color. Defaults to 0.0.
|
126
|
+
"""
|
127
|
+
# Log the circle perimeter plotting parameters
|
128
|
+
params.log_plotter(
|
129
|
+
perimeter_type="circle",
|
130
|
+
perimeter_scale=scale,
|
131
|
+
perimeter_linestyle=linestyle,
|
132
|
+
perimeter_linewidth=linewidth,
|
133
|
+
perimeter_color=(
|
134
|
+
"custom" if isinstance(color, (list, tuple, np.ndarray)) else color
|
135
|
+
), # np.ndarray usually indicates custom colors
|
136
|
+
perimeter_outline_alpha=outline_alpha,
|
137
|
+
perimeter_fill_alpha=fill_alpha,
|
138
|
+
)
|
139
|
+
|
140
|
+
# Convert color to RGBA using the to_rgba helper function - use outline_alpha for the perimeter
|
141
|
+
color = to_rgba(color=color, alpha=outline_alpha)
|
142
|
+
# Set the fill_alpha to 0 if not provided
|
143
|
+
fill_alpha = fill_alpha if fill_alpha is not None else 0.0
|
144
|
+
# Extract node coordinates from the network graph
|
145
|
+
node_coordinates = self.graph.node_coordinates
|
146
|
+
# Calculate the center and radius of the bounding box around the network
|
147
|
+
center, radius = calculate_bounding_box(node_coordinates)
|
148
|
+
# Scale the radius by the scale factor
|
149
|
+
scaled_radius = radius * scale
|
150
|
+
|
151
|
+
# Draw a circle to represent the network perimeter
|
152
|
+
circle = plt.Circle(
|
153
|
+
center,
|
154
|
+
scaled_radius,
|
155
|
+
linestyle=linestyle,
|
156
|
+
linewidth=linewidth,
|
157
|
+
color=color,
|
158
|
+
fill=fill_alpha > 0, # Fill the circle if fill_alpha is greater than 0
|
159
|
+
)
|
160
|
+
# Set the transparency of the fill if applicable
|
161
|
+
if fill_alpha > 0:
|
162
|
+
circle.set_facecolor(to_rgba(color=color, alpha=fill_alpha))
|
163
|
+
|
164
|
+
self.ax.add_artist(circle)
|
165
|
+
|
166
|
+
def plot_contour_perimeter(
|
167
|
+
self,
|
168
|
+
scale: float = 1.0,
|
169
|
+
levels: int = 3,
|
170
|
+
bandwidth: float = 0.8,
|
171
|
+
grid_size: int = 250,
|
172
|
+
color: Union[str, List, Tuple, np.ndarray] = "black",
|
173
|
+
linestyle: str = "solid",
|
174
|
+
linewidth: float = 1.5,
|
175
|
+
outline_alpha: Union[float, None] = 1.0,
|
176
|
+
fill_alpha: Union[float, None] = 0.0,
|
177
|
+
) -> None:
|
178
|
+
"""
|
179
|
+
Plot a KDE-based contour around the network graph to represent the network perimeter.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
scale (float, optional): Scaling factor for the perimeter size. Defaults to 1.0.
|
183
|
+
levels (int, optional): Number of contour levels. Defaults to 3.
|
184
|
+
bandwidth (float, optional): Bandwidth for the KDE. Controls smoothness. Defaults to 0.8.
|
185
|
+
grid_size (int, optional): Grid resolution for the KDE. Higher values yield finer contours. Defaults to 250.
|
186
|
+
color (str, list, tuple, or np.ndarray, optional): Color of the network perimeter contour. Defaults to "black".
|
187
|
+
linestyle (str, optional): Line style for the network perimeter contour (e.g., dashed, solid). Defaults to "solid".
|
188
|
+
linewidth (float, optional): Width of the contour's outline. Defaults to 1.5.
|
189
|
+
outline_alpha (float, None, optional): Transparency level of the contour outline. If provided, it overrides any existing
|
190
|
+
alpha values found in color. Defaults to 1.0.
|
191
|
+
fill_alpha (float, None, optional): Transparency level of the contour fill. If provided, it overrides any existing alpha
|
192
|
+
values found in color. Defaults to 0.0.
|
193
|
+
"""
|
194
|
+
# Log the contour perimeter plotting parameters
|
195
|
+
params.log_plotter(
|
196
|
+
perimeter_type="contour",
|
197
|
+
perimeter_scale=scale,
|
198
|
+
perimeter_levels=levels,
|
199
|
+
perimeter_bandwidth=bandwidth,
|
200
|
+
perimeter_grid_size=grid_size,
|
201
|
+
perimeter_linestyle=linestyle,
|
202
|
+
perimeter_linewidth=linewidth,
|
203
|
+
perimeter_color=("custom" if isinstance(color, (list, tuple, np.ndarray)) else color),
|
204
|
+
perimeter_outline_alpha=outline_alpha,
|
205
|
+
perimeter_fill_alpha=fill_alpha,
|
206
|
+
)
|
207
|
+
|
208
|
+
# Convert color to RGBA using the to_rgba helper function - use outline_alpha for the perimeter
|
209
|
+
color = to_rgba(color=color, alpha=outline_alpha)
|
210
|
+
# Extract node coordinates from the network graph
|
211
|
+
node_coordinates = self.graph.node_coordinates
|
212
|
+
# Scale the node coordinates if needed
|
213
|
+
scaled_coordinates = node_coordinates * scale
|
214
|
+
# Use the existing _draw_kde_contour method
|
215
|
+
self._draw_kde_contour(
|
216
|
+
ax=self.ax,
|
217
|
+
pos=scaled_coordinates,
|
218
|
+
nodes=list(range(len(node_coordinates))), # All nodes are included
|
219
|
+
levels=levels,
|
220
|
+
bandwidth=bandwidth,
|
221
|
+
grid_size=grid_size,
|
222
|
+
color=color,
|
223
|
+
linestyle=linestyle,
|
224
|
+
linewidth=linewidth,
|
225
|
+
alpha=fill_alpha,
|
226
|
+
)
|
@@ -0,0 +1,319 @@
|
|
1
|
+
"""
|
2
|
+
risk/network/plot/contour
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
4
|
+
"""
|
5
|
+
|
6
|
+
from typing import List, Tuple, Union
|
7
|
+
|
8
|
+
import matplotlib.pyplot as plt
|
9
|
+
import numpy as np
|
10
|
+
from scipy import linalg
|
11
|
+
from scipy.ndimage import label
|
12
|
+
from scipy.stats import gaussian_kde
|
13
|
+
|
14
|
+
from risk.log import params, logger
|
15
|
+
from risk.network.graph import NetworkGraph
|
16
|
+
from risk.network.plot.utils import get_annotated_domain_colors, to_rgba
|
17
|
+
|
18
|
+
|
19
|
+
class Contour:
|
20
|
+
"""Class to generate Kernel Density Estimate (KDE) contours for nodes in a network graph."""
|
21
|
+
|
22
|
+
def __init__(self, graph: NetworkGraph, ax: plt.Axes) -> None:
|
23
|
+
"""Initialize the Contour with a NetworkGraph and axis for plotting.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
graph (NetworkGraph): The NetworkGraph object containing the network data.
|
27
|
+
ax (plt.Axes): The axis to plot the contours on.
|
28
|
+
"""
|
29
|
+
self.graph = graph
|
30
|
+
self.ax = ax
|
31
|
+
|
32
|
+
def plot_contours(
|
33
|
+
self,
|
34
|
+
levels: int = 5,
|
35
|
+
bandwidth: float = 0.8,
|
36
|
+
grid_size: int = 250,
|
37
|
+
color: Union[str, List, Tuple, np.ndarray] = "white",
|
38
|
+
linestyle: str = "solid",
|
39
|
+
linewidth: float = 1.5,
|
40
|
+
alpha: Union[float, None] = 1.0,
|
41
|
+
fill_alpha: Union[float, None] = None,
|
42
|
+
) -> None:
|
43
|
+
"""Draw KDE contours for nodes in various domains of a network graph, highlighting areas of high density.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
levels (int, optional): Number of contour levels to plot. Defaults to 5.
|
47
|
+
bandwidth (float, optional): Bandwidth for KDE. Controls the smoothness of the contour. Defaults to 0.8.
|
48
|
+
grid_size (int, optional): Resolution of the grid for KDE. Higher values create finer contours. Defaults to 250.
|
49
|
+
color (str, list, tuple, or np.ndarray, optional): Color of the contours. Can be a single color or an array of colors.
|
50
|
+
Defaults to "white".
|
51
|
+
linestyle (str, optional): Line style for the contours. Defaults to "solid".
|
52
|
+
linewidth (float, optional): Line width for the contours. Defaults to 1.5.
|
53
|
+
alpha (float, None, optional): Transparency level of the contour lines. If provided, it overrides any existing alpha values
|
54
|
+
found in color. Defaults to 1.0.
|
55
|
+
fill_alpha (float, None, optional): Transparency level of the contour fill. If provided, it overrides any existing alpha
|
56
|
+
values found in color. Defaults to None.
|
57
|
+
"""
|
58
|
+
# Log the contour plotting parameters
|
59
|
+
params.log_plotter(
|
60
|
+
contour_levels=levels,
|
61
|
+
contour_bandwidth=bandwidth,
|
62
|
+
contour_grid_size=grid_size,
|
63
|
+
contour_color=(
|
64
|
+
"custom" if isinstance(color, np.ndarray) else color
|
65
|
+
), # np.ndarray usually indicates custom colors
|
66
|
+
contour_alpha=alpha,
|
67
|
+
contour_fill_alpha=fill_alpha,
|
68
|
+
)
|
69
|
+
|
70
|
+
# Ensure color is converted to RGBA with repetition matching the number of domains
|
71
|
+
color = to_rgba(
|
72
|
+
color=color, alpha=alpha, num_repeats=len(self.graph.domain_id_to_node_ids_map)
|
73
|
+
)
|
74
|
+
# Extract node coordinates from the network graph
|
75
|
+
node_coordinates = self.graph.node_coordinates
|
76
|
+
# Draw contours for each domain in the network
|
77
|
+
for idx, (_, node_ids) in enumerate(self.graph.domain_id_to_node_ids_map.items()):
|
78
|
+
if len(node_ids) > 1:
|
79
|
+
self._draw_kde_contour(
|
80
|
+
self.ax,
|
81
|
+
node_coordinates,
|
82
|
+
node_ids,
|
83
|
+
color=color[idx],
|
84
|
+
levels=levels,
|
85
|
+
bandwidth=bandwidth,
|
86
|
+
grid_size=grid_size,
|
87
|
+
linestyle=linestyle,
|
88
|
+
linewidth=linewidth,
|
89
|
+
alpha=alpha,
|
90
|
+
fill_alpha=fill_alpha,
|
91
|
+
)
|
92
|
+
|
93
|
+
def plot_subcontour(
|
94
|
+
self,
|
95
|
+
nodes: Union[List, Tuple, np.ndarray],
|
96
|
+
levels: int = 5,
|
97
|
+
bandwidth: float = 0.8,
|
98
|
+
grid_size: int = 250,
|
99
|
+
color: Union[str, List, Tuple, np.ndarray] = "white",
|
100
|
+
linestyle: str = "solid",
|
101
|
+
linewidth: float = 1.5,
|
102
|
+
alpha: Union[float, None] = 1.0,
|
103
|
+
fill_alpha: Union[float, None] = None,
|
104
|
+
) -> None:
|
105
|
+
"""Plot a subcontour for a given set of nodes or a list of node sets using Kernel Density Estimation (KDE).
|
106
|
+
|
107
|
+
Args:
|
108
|
+
nodes (list, tuple, or np.ndarray): List of node labels or list of lists of node labels to plot the contour for.
|
109
|
+
levels (int, optional): Number of contour levels to plot. Defaults to 5.
|
110
|
+
bandwidth (float, optional): Bandwidth for KDE. Controls the smoothness of the contour. Defaults to 0.8.
|
111
|
+
grid_size (int, optional): Resolution of the grid for KDE. Higher values create finer contours. Defaults to 250.
|
112
|
+
color (str, list, tuple, or np.ndarray, optional): Color of the contour. Can be a string (e.g., 'white') or RGBA array.
|
113
|
+
Defaults to "white".
|
114
|
+
linestyle (str, optional): Line style for the contour. Defaults to "solid".
|
115
|
+
linewidth (float, optional): Line width for the contour. Defaults to 1.5.
|
116
|
+
alpha (float, None, optional): Transparency level of the contour lines. If provided, it overrides any existing alpha values
|
117
|
+
found in color. Defaults to 1.0.
|
118
|
+
fill_alpha (float, None, optional): Transparency level of the contour fill. If provided, it overrides any existing alpha
|
119
|
+
values found in color. Defaults to None.
|
120
|
+
|
121
|
+
Raises:
|
122
|
+
ValueError: If no valid nodes are found in the network graph.
|
123
|
+
"""
|
124
|
+
# Check if nodes is a list of lists or a flat list
|
125
|
+
if any(isinstance(item, (list, tuple, np.ndarray)) for item in nodes):
|
126
|
+
# If it's a list of lists, iterate over sublists
|
127
|
+
node_groups = nodes
|
128
|
+
else:
|
129
|
+
# If it's a flat list of nodes, treat it as a single group
|
130
|
+
node_groups = [nodes]
|
131
|
+
|
132
|
+
# Convert color to RGBA using the to_rgba helper function
|
133
|
+
color_rgba = to_rgba(color=color, alpha=alpha)
|
134
|
+
|
135
|
+
# Iterate over each group of nodes (either sublists or flat list)
|
136
|
+
for sublist in node_groups:
|
137
|
+
# Filter to get node IDs and their coordinates for each sublist
|
138
|
+
node_ids = [
|
139
|
+
self.graph.node_label_to_node_id_map.get(node)
|
140
|
+
for node in sublist
|
141
|
+
if node in self.graph.node_label_to_node_id_map
|
142
|
+
]
|
143
|
+
if not node_ids or len(node_ids) == 1:
|
144
|
+
raise ValueError(
|
145
|
+
"No nodes found in the network graph or insufficient nodes to plot."
|
146
|
+
)
|
147
|
+
|
148
|
+
# Draw the KDE contour for the specified nodes
|
149
|
+
node_coordinates = self.graph.node_coordinates
|
150
|
+
self._draw_kde_contour(
|
151
|
+
self.ax,
|
152
|
+
node_coordinates,
|
153
|
+
node_ids,
|
154
|
+
color=color_rgba,
|
155
|
+
levels=levels,
|
156
|
+
bandwidth=bandwidth,
|
157
|
+
grid_size=grid_size,
|
158
|
+
linestyle=linestyle,
|
159
|
+
linewidth=linewidth,
|
160
|
+
alpha=alpha,
|
161
|
+
fill_alpha=fill_alpha,
|
162
|
+
)
|
163
|
+
|
164
|
+
def _draw_kde_contour(
|
165
|
+
self,
|
166
|
+
ax: plt.Axes,
|
167
|
+
pos: np.ndarray,
|
168
|
+
nodes: List,
|
169
|
+
levels: int = 5,
|
170
|
+
bandwidth: float = 0.8,
|
171
|
+
grid_size: int = 250,
|
172
|
+
color: Union[str, np.ndarray] = "white",
|
173
|
+
linestyle: str = "solid",
|
174
|
+
linewidth: float = 1.5,
|
175
|
+
alpha: Union[float, None] = 1.0,
|
176
|
+
fill_alpha: Union[float, None] = 0.2,
|
177
|
+
) -> None:
|
178
|
+
"""Draw a Kernel Density Estimate (KDE) contour plot for a set of nodes on a given axis.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
ax (plt.Axes): The axis to draw the contour on.
|
182
|
+
pos (np.ndarray): Array of node positions (x, y).
|
183
|
+
nodes (list): List of node indices to include in the contour.
|
184
|
+
levels (int, optional): Number of contour levels. Defaults to 5.
|
185
|
+
bandwidth (float, optional): Bandwidth for the KDE. Controls smoothness. Defaults to 0.8.
|
186
|
+
grid_size (int, optional): Grid resolution for the KDE. Higher values yield finer contours. Defaults to 250.
|
187
|
+
color (str or np.ndarray): Color for the contour. Can be a string or RGBA array. Defaults to "white".
|
188
|
+
linestyle (str, optional): Line style for the contour. Defaults to "solid".
|
189
|
+
linewidth (float, optional): Line width for the contour. Defaults to 1.5.
|
190
|
+
alpha (float, None, optional): Transparency level for the contour lines. If provided, it overrides any existing alpha
|
191
|
+
values found in color. Defaults to 1.0.
|
192
|
+
fill_alpha (float, None, optional): Transparency level for the contour fill. If provided, it overrides any existing
|
193
|
+
alpha values found in color. Defaults to 0.2.
|
194
|
+
"""
|
195
|
+
# Extract the positions of the specified nodes
|
196
|
+
points = np.array([pos[n] for n in nodes])
|
197
|
+
if len(points) <= 1:
|
198
|
+
return None # Not enough points to form a contour
|
199
|
+
|
200
|
+
# Check if the KDE forms a single connected component
|
201
|
+
connected = False
|
202
|
+
z = None # Initialize z to None to avoid UnboundLocalError
|
203
|
+
while not connected and bandwidth <= 100.0:
|
204
|
+
try:
|
205
|
+
# Perform KDE on the points with the given bandwidth
|
206
|
+
kde = gaussian_kde(points.T, bw_method=bandwidth)
|
207
|
+
xmin, ymin = points.min(axis=0) - bandwidth
|
208
|
+
xmax, ymax = points.max(axis=0) + bandwidth
|
209
|
+
x, y = np.mgrid[
|
210
|
+
xmin : xmax : complex(0, grid_size), ymin : ymax : complex(0, grid_size)
|
211
|
+
]
|
212
|
+
z = kde(np.vstack([x.ravel(), y.ravel()])).reshape(x.shape)
|
213
|
+
# Check if the KDE forms a single connected component
|
214
|
+
connected = _is_connected(z)
|
215
|
+
if not connected:
|
216
|
+
bandwidth += 0.05 # Increase bandwidth slightly and retry
|
217
|
+
except linalg.LinAlgError:
|
218
|
+
bandwidth += 0.05 # Increase bandwidth and retry
|
219
|
+
except Exception as e:
|
220
|
+
# Catch any other exceptions and log them
|
221
|
+
logger.error(f"Unexpected error when drawing KDE contour: {e}")
|
222
|
+
return None
|
223
|
+
|
224
|
+
# If z is still None, the KDE computation failed
|
225
|
+
if z is None:
|
226
|
+
logger.error("Failed to compute KDE. Skipping contour plot for these nodes.")
|
227
|
+
return None
|
228
|
+
|
229
|
+
# Define contour levels based on the density
|
230
|
+
min_density, max_density = z.min(), z.max()
|
231
|
+
if min_density == max_density:
|
232
|
+
logger.warning(
|
233
|
+
"Contour levels could not be created due to lack of variation in density."
|
234
|
+
)
|
235
|
+
return None
|
236
|
+
|
237
|
+
# Create contour levels based on the density values
|
238
|
+
contour_levels = np.linspace(min_density, max_density, levels)[1:]
|
239
|
+
if len(contour_levels) < 2 or not np.all(np.diff(contour_levels) > 0):
|
240
|
+
logger.error("Contour levels must be strictly increasing. Skipping contour plot.")
|
241
|
+
return None
|
242
|
+
|
243
|
+
# Set the contour color and linestyle
|
244
|
+
contour_colors = [color for _ in range(levels - 1)]
|
245
|
+
# Plot the filled contours using fill_alpha for transparency
|
246
|
+
if fill_alpha and fill_alpha > 0:
|
247
|
+
ax.contourf(
|
248
|
+
x,
|
249
|
+
y,
|
250
|
+
z,
|
251
|
+
levels=contour_levels,
|
252
|
+
colors=contour_colors,
|
253
|
+
antialiased=True,
|
254
|
+
alpha=fill_alpha,
|
255
|
+
)
|
256
|
+
|
257
|
+
# Plot the contour lines with the specified alpha for transparency
|
258
|
+
c = ax.contour(
|
259
|
+
x,
|
260
|
+
y,
|
261
|
+
z,
|
262
|
+
levels=contour_levels,
|
263
|
+
colors=contour_colors,
|
264
|
+
linestyles=linestyle,
|
265
|
+
linewidths=linewidth,
|
266
|
+
alpha=alpha,
|
267
|
+
)
|
268
|
+
|
269
|
+
# Set linewidth for the contour lines to 0 for levels other than the base level
|
270
|
+
for i in range(1, len(contour_levels)):
|
271
|
+
c.collections[i].set_linewidth(0)
|
272
|
+
|
273
|
+
def get_annotated_contour_colors(
|
274
|
+
self,
|
275
|
+
cmap: str = "gist_rainbow",
|
276
|
+
color: Union[str, None] = None,
|
277
|
+
min_scale: float = 0.8,
|
278
|
+
max_scale: float = 1.0,
|
279
|
+
scale_factor: float = 1.0,
|
280
|
+
random_seed: int = 888,
|
281
|
+
) -> np.ndarray:
|
282
|
+
"""Get colors for the contours based on node annotations or a specified colormap.
|
283
|
+
|
284
|
+
Args:
|
285
|
+
cmap (str, optional): Name of the colormap to use for generating contour colors. Defaults to "gist_rainbow".
|
286
|
+
color (str or None, optional): Color to use for the contours. If None, the colormap will be used. Defaults to None.
|
287
|
+
min_scale (float, optional): Minimum intensity scale for the colors generated by the colormap.
|
288
|
+
Controls the dimmest colors. Defaults to 0.8.
|
289
|
+
max_scale (float, optional): Maximum intensity scale for the colors generated by the colormap.
|
290
|
+
Controls the brightest colors. Defaults to 1.0.
|
291
|
+
scale_factor (float, optional): Exponent for adjusting color scaling based on enrichment scores.
|
292
|
+
A higher value increases contrast by dimming lower scores more. Defaults to 1.0.
|
293
|
+
random_seed (int, optional): Seed for random number generation to ensure reproducibility. Defaults to 888.
|
294
|
+
|
295
|
+
Returns:
|
296
|
+
np.ndarray: Array of RGBA colors for contour annotations.
|
297
|
+
"""
|
298
|
+
return get_annotated_domain_colors(
|
299
|
+
graph=self.graph,
|
300
|
+
cmap=cmap,
|
301
|
+
color=color,
|
302
|
+
min_scale=min_scale,
|
303
|
+
max_scale=max_scale,
|
304
|
+
scale_factor=scale_factor,
|
305
|
+
random_seed=random_seed,
|
306
|
+
)
|
307
|
+
|
308
|
+
|
309
|
+
def _is_connected(z: np.ndarray) -> bool:
|
310
|
+
"""Determine if a thresholded grid represents a single, connected component.
|
311
|
+
|
312
|
+
Args:
|
313
|
+
z (np.ndarray): A binary grid where the component connectivity is evaluated.
|
314
|
+
|
315
|
+
Returns:
|
316
|
+
bool: True if the grid represents a single connected component, False otherwise.
|
317
|
+
"""
|
318
|
+
_, num_features = label(z)
|
319
|
+
return num_features == 1 # Return True if only one connected component is found
|