22This module provides functionalities to visualize the Mapper graph based on
33pyvis.
44"""
5+ import math
56
67from pyvis .network import Network
78
89import matplotlib .pyplot as plt
910import matplotlib .colors as mcolors
1011
12+ import plotly .graph_objects as go
13+ import plotly .io as pio
14+ import plotly .colors as pc
15+
1116from tdamapper .core import (
1217 aggregate_graph ,
1318)
1419
1520
21+ _EDGE_WIDTH = 0.75
22+
23+ _EDGE_COLOR = '#777'
24+
25+ _TICKS_NUM = 10
26+
27+
28+ def __fmt (x , max_len = 3 ):
29+ fmt = f'.{ max_len } g'
30+ return f'{ x :{fmt }} '
31+
32+
33+ def _colorbar (height , cmap , cmin , cmax , title ):
34+ colorbar_fig = go .Figure ()
35+ colorbar_fig .add_trace (go .Scatter (
36+ x = [None ], y = [None ],
37+ mode = 'markers' ,
38+ marker = dict (
39+ showscale = True ,
40+ reversescale = False ,
41+ line_colorscale = cmap ,
42+ colorscale = cmap ,
43+ cmin = cmin ,
44+ cmax = cmax ,
45+ colorbar = dict (
46+ showticklabels = True ,
47+ outlinewidth = 1 ,
48+ borderwidth = 0 ,
49+ orientation = 'v' ,
50+ thickness = 20 ,
51+ thicknessmode = 'fraction' ,
52+ xanchor = 'left' ,
53+ titleside = 'right' ,
54+ tickwidth = 1 ,
55+ tickformat = '.2g' ,
56+ nticks = _TICKS_NUM ,
57+ tickmode = 'auto' ,
58+ title = title ,
59+ ),
60+ ),
61+ ))
62+ colorbar_fig .update_layout (
63+ xaxis = dict (visible = False ),
64+ yaxis = dict (visible = False ),
65+ margin = dict (l = 0 , r = 100 , t = 0 , b = 0 ),
66+ width = 80 ,
67+ height = height ,
68+ plot_bgcolor = 'rgba(0,0,0,0)' ,
69+ paper_bgcolor = 'rgba(0,0,0,0)' ,
70+ )
71+ return colorbar_fig
72+
73+
74+ def _combine (network , colorbar ):
75+ network_html = network .generate_html ()
76+ colorbar_html = pio .to_html (
77+ colorbar ,
78+ include_plotlyjs = 'cdn' ,
79+ full_html = False ,
80+ config = {
81+ 'displayModeBar' : False
82+ },
83+ )
84+ combined_html = f"""
85+ <!DOCTYPE html>
86+ <html>
87+ <head>
88+ <title>Network with Colorbar</title>
89+ <style>
90+ body {{
91+ margin: 0;
92+ height: 100vh;
93+ display: flex;
94+ }}
95+
96+ .combined {{
97+ display: flex;
98+ flex-direction: row;
99+ margin: 0;
100+ }}
101+
102+ .network {{
103+ display: flex;
104+ align-items: start;
105+ }}
106+
107+ .colorbar {{
108+ display: flex;
109+ align-items: start;
110+ }}
111+
112+ .network .card {{
113+ border-width: 0;
114+ }}
115+
116+ .network #mynetwork {{
117+ border-width: 0;
118+ }}
119+ </style>
120+ </head>
121+ <body>
122+ <div class="combined">
123+ <div class="network">
124+ { network_html }
125+ </div>
126+ <div class="colorbar">
127+ { colorbar_html }
128+ </div>
129+ </div>
130+ </body>
131+ </html>
132+ """
133+ return combined_html
134+
135+
16136def plot_pyvis (
17137 mapper_plot ,
18- notebook ,
19138 output_file ,
20139 colors ,
21140 agg ,
@@ -24,39 +143,47 @@ def plot_pyvis(
24143 height ,
25144 cmap ,
26145):
27- net = _compute_net (
146+ net , cmin , cmax = _compute_net (
28147 mapper_plot = mapper_plot ,
29148 width = width ,
30149 height = height ,
31150 colors = colors ,
32151 agg = agg ,
33152 cmap = cmap ,
34- notebook = notebook ,
35153 )
36- net .show (output_file , notebook = notebook )
154+ colorbar = _colorbar (
155+ height = height ,
156+ cmap = cmap ,
157+ cmin = cmin ,
158+ cmax = cmax ,
159+ title = title
160+ )
161+ combined_html = _combine (net , colorbar )
162+ with open (output_file , 'w' ) as file :
163+ file .write (combined_html )
37164
38165
39166def _compute_net (
40167 mapper_plot ,
41- notebook ,
42168 colors ,
43169 agg ,
44170 width ,
45171 height ,
46172 cmap ,
47173):
48174 net = Network (
49- height = height ,
50- width = width ,
175+ height = f' { height } px' ,
176+ width = f' { width } px' ,
51177 directed = False ,
52- notebook = notebook ,
53- select_menu = True ,
54- filter_menu = True ,
178+ notebook = True ,
179+ select_menu = False ,
180+ filter_menu = False ,
55181 neighborhood_highlight = True ,
56182 )
57183 net .toggle_physics (False )
58184 graph = mapper_plot .graph
59185 nodes = graph .nodes
186+ cmap_colorscale = pc .get_colorscale (cmap )
60187
61188 min_node_size = float ('inf' )
62189 max_node_size = - float ('inf' )
@@ -81,10 +208,10 @@ def _compute_net(
81208
82209 def _size (node ):
83210 if max_node_size == min_node_size :
84- node_size_norm = 12
211+ node_size_norm = 25.0
85212 else :
86213 node_size = int (nodes [node ]['size' ])
87- node_size_norm = (node_size - min_node_size ) / ( max_node_size - min_node_size ) * 25
214+ node_size_norm = 25.0 * math . sqrt (node_size / max_node_size )
88215 return int (round (node_size_norm ))
89216
90217 def _color (node ):
@@ -93,39 +220,33 @@ def _color(node):
93220 else :
94221 node_color = node_colors [node ]
95222 node_color = (node_color - min_node_color ) / (max_node_color - min_node_color )
96- node_color = colormap (node_color )
97- return mcolors .to_hex (node_color )
98-
99- def _blend_color (source , target ):
100- if max_node_color == min_node_color :
101- blend_color = 0.5
102- else :
103- source_color = node_colors [source ]
104- source_color = (source_color - min_node_color ) / (max_node_color - min_node_color )
105- target_color = node_colors [target ]
106- target_color = (target_color - min_node_color ) / (max_node_color - min_node_color )
107- blend_color = (source_color + target_color ) / 2.0
108- blend_color = colormap (blend_color )
109- return mcolors .to_hex (blend_color )
223+ node_color = max (0.0 , min (1.0 , node_color ))
224+ node_color_hex = pc .sample_colorscale (cmap_colorscale , node_color )[0 ]
225+ return node_color_hex
110226
111227 for node in nodes :
112228 node_id = int (node )
113229 node_size = _size (node )
114230 node_color = _color (node )
231+ node_stats = __fmt (node_colors [node ])
232+ node_label = f'color: { node_stats } \n node: { node_id } \n size: { node_size } '
115233 node_pos = mapper_plot .positions [node ]
116234 net .add_node (
117235 node_id ,
118236 label = node_id ,
119237 size = node_size ,
120238 color = node_color ,
121- x = node_pos [0 ] * 1000.0 ,
122- y = node_pos [1 ] * 1000.0 ,
239+ title = node_label ,
240+ x = node_pos [0 ] * width ,
241+ y = - node_pos [1 ] * height ,
123242 )
124243
125244 for edge in graph .edges :
126245 source_id = int (edge [0 ])
127246 target_id = int (edge [1 ])
128- edge_color = _blend_color (edge [0 ], edge [1 ])
129- net .add_edge (source_id , target_id , color = edge_color )
247+ edge_color = _EDGE_COLOR
248+ edge_width = _EDGE_WIDTH
249+ edge_width = 1.5
250+ net .add_edge (source_id , target_id , color = edge_color , width = edge_width )
130251
131- return net
252+ return net , min_node_color , max_node_color
0 commit comments