1+ import  base64 
2+ 
3+ from  sio3pack .visualizer  import  cytoscope 
4+ 
15try :
26    import  dash 
37    import  dash_cytoscape  as  cyto 
4-     from  dash  import  Input , Output , html 
8+     from  dash  import  Input , Output , State ,  dcc ,  html 
59except  ImportError :
610    raise  ImportError ("Please install the 'dash' and 'dash-cytoscape' packages to use the visualizer." )
711
1115
1216
1317def  main ():
14-     if  len (sys .argv ) !=  2 :
15-         print ("Usage: python -m sio3pack.visualizer <workflow file>" )
16-         sys .exit (1 )
17-     file_path  =  sys .argv [1 ]
18-     if  not  file_path .endswith (".json" ):
19-         print ("The file must be a JSON file." )
20-         sys .exit (1 )
21-     if  not  os .path .isfile (file_path ):
22-         print ("The file does not exist." )
23-         sys .exit (1 )
24- 
25-     graph  =  json .load (open (file_path ))
26-     elements  =  []
27-     ins  =  {}
28-     rendered_registers  =  set ()
29- 
30-     # Create nodes for observable registers. 
31-     for  register  in  range (graph ["observable_registers" ]):
32-         elements .append (
33-             {
34-                 "data" : {
35-                     "id" : f"obs_register_{ register }  ,
36-                     "label" : f"Observable register { register }  ,
37-                     "info" : "This is an observable register. It's an output of a workflow." ,
38-                 },
39-                 "classes" : "register" ,
40-             }
41-         )
42-         ins [register ] =  [f"obs_register_{ register }  ]
43-         rendered_registers .add (register )
44- 
45-     script_i  =  0 
46-     execution_i  =  0 
47-     # First pass to create nodes and mark input registers. 
48-     for  task  in  graph ["tasks" ]:
49-         if  task ["type" ] ==  "script" :
50-             id  =  f"script_{ script_i }  
51-             elements .append (
52-                 {"data" : {"id" : id , "label" : task .get ("name" , f"Script { script_i }  ), "info" : task }, "classes" : "script" }
53-             )
54-             if  task ["reactive" ]:
55-                 elements [- 1 ]["classes" ] +=  " reactive" 
56-             script_i  +=  1 
57-             for  register  in  task ["input_registers" ]:
58-                 if  register  not  in ins :
59-                     ins [register ] =  []
60-                 ins [register ].append (id )
61-         elif  task ["type" ] ==  "execution" :
62-             id  =  f"execution_{ execution_i }  
63-             elements .append (
64-                 {
65-                     "data" : {"id" : id , "label" : task .get ("name" , f"Execution { execution_i }  ), "info" : task },
66-                     "classes" : "execution" ,
67-                 }
68-             )
69-             if  task ["exclusive" ]:
70-                 elements [- 1 ]["classes" ] +=  " exclusive" 
71- 
72-             # To delete, final spec is different 
73-             if  "input_register"  in  task :
74-                 register  =  task ["input_register" ]
75-                 if  register  not  in ins :
76-                     ins [register ] =  []
77-                 ins [register ].append (id )
78-             execution_i  +=  1 
79- 
80-     # Second pass to create edges. 
81-     script_i  =  0 
82-     execution_i  =  0 
83-     for  task  in  graph ["tasks" ]:
84-         if  task ["type" ] ==  "script" :
85-             registers  =  task ["output_registers" ]
86-         elif  task ["type" ] ==  "execution" :
87-             registers  =  [task ["output_register" ]]
88-         else :
89-             raise 
90- 
91-         for  register  in  registers :
92-             if  register  not  in ins :
93-                 elements .append (
94-                     {
95-                         "data" : {
96-                             "id" : f"register_{ register }  ,
97-                             "label" : f"Register { register }  ,
98-                             "info" : f"This is a register. It's an intermediate value in a workflow." ,
99-                         },
100-                         "classes" : "register" ,
101-                     }
102-                 )
103-                 ins [register ] =  [f"register_{ register }  ]
104-                 rendered_registers .add (register )
105-             for  id  in  ins [register ]:
106-                 if  task ["type" ] ==  "script" :
107-                     elements .append (
108-                         {
109-                             "data" : {
110-                                 "source" : f"script_{ script_i }  ,
111-                                 "target" : id ,
112-                             }
113-                         }
114-                     )
115-                 elif  task ["type" ] ==  "execution" :
116-                     elements .append (
117-                         {
118-                             "data" : {
119-                                 "source" : f"execution_{ execution_i }  ,
120-                                 "target" : id ,
121-                             }
122-                         }
123-                     )
124-                 if  register  not  in rendered_registers :
125-                     elements [- 1 ]["data" ]["label" ] =  f"via register { register }  
126- 
127-         if  task ["type" ] ==  "script" :
128-             script_i  +=  1 
129-         elif  task ["type" ] ==  "execution" :
130-             execution_i  +=  1 
131- 
13218    app  =  dash .Dash (__name__ )
13319    app .layout  =  html .Div (
13420        [
13521            html .Div (
13622                [
137-                     cyto .Cytoscape (
138-                         id = "cytoscape" ,
139-                         layout = {"name" : "breadthfirst" , "directed" : True },
140-                         style = {"width" : "100%" , "height" : "100vh" },
141-                         elements = elements ,
142-                         stylesheet = [
143-                             {
144-                                 "selector" : "node" ,
145-                                 "style" : {
146-                                     "label" : "data(label)" ,
147-                                     "text-valign" : "center" ,
148-                                     "text-margin-y" : "-20px" ,
149-                                 },
150-                             },
151-                             {
152-                                 "selector" : "edge" ,
153-                                 "style" : {
154-                                     "curve-style" : "bezier" ,  # Makes edges curved for better readability 
155-                                     "target-arrow-shape" : "triangle" ,  # Adds an arrowhead to indicate direction 
156-                                     "arrow-scale" : 1.5 ,  # Makes the arrow larger 
157-                                     "line-color" : "#0074D9" ,  # Edge color 
158-                                     "target-arrow-color" : "#0074D9" ,  # Arrow color 
159-                                     "width" : 2 ,  # Line thickness 
160-                                     "content" : "data(label)" ,  # Show edge label on hover 
161-                                     "font-size" : "12px" ,
162-                                     "color" : "#ff4136" ,
163-                                     "text-background-opacity" : 1 ,
164-                                     "text-background-color" : "white" ,
165-                                     "text-background-shape" : "roundrectangle" ,
166-                                     "text-border-opacity" : 1 ,
167-                                     "text-border-width" : 1 ,
168-                                     "text-border-color" : "#ff4136" ,
169-                                 },
170-                             },
171-                             {
172-                                 "selector" : ".register" ,
173-                                 "style" : {
174-                                     "shape" : "rectangle" ,
175-                                 },
176-                             },
177-                             {
178-                                 "selector" : ".script" ,
179-                                 "style" : {
180-                                     "shape" : "roundrectangle" ,
181-                                 },
182-                             },
183-                             {
184-                                 "selector" : ".execution" ,
185-                                 "style" : {
186-                                     "shape" : "ellipse" ,
187-                                 },
188-                             },
189-                             {
190-                                 "selector" : ".reactive" ,
191-                                 "style" : {
192-                                     "background-color" : "#ff851b" ,
193-                                 },
194-                             },
195-                             {
196-                                 "selector" : ".exclusive" ,
197-                                 "style" : {
198-                                     "background-color" : "#ff4136" ,
23+                     html .Div (
24+                         [],
25+                         style = {"flex" : "3" , "height" : "100vh" },
26+                         id = "graph-div" ,
27+                     ),
28+                     html .Div (
29+                         [
30+                             html .Pre (
31+                                 id = "node-data" ,
32+                                 style = {
33+                                     "padding" : "10px" ,
34+                                     "whiteSpace" : "pre" ,
35+                                     "overflow" : "auto" ,
36+                                     "maxHeight" : "95vh" ,
37+                                     "maxWidth" : "100%" ,
19938                                },
200-                             }, 
39+                             ) 
20140                        ],
41+                         style = {"flex" : "1" , "height" : "100vh" , "backgroundColor" : "#f7f7f7" },
20242                    ),
20343                ],
204-                 style = {"flex" : "3" , "height" : "100vh" },
44+                 id = "graph" ,
45+                 style = {"display" : "flex" , "flexDirection" : "row" , "height" : "100vh" },
20546            ),
20647            html .Div (
20748                [
208-                     html .Pre (
209-                         id = "node-data" ,
210-                         style = {
211-                             "padding" : "10px" ,
212-                             "white-space" : "pre" ,
213-                             "overflow" : "auto" ,
214-                             "max-height" : "95vh" ,
215-                             "max-width" : "100%" ,
216-                         },
217-                     )
49+                     html .Div (
50+                         [
51+                             html .H1 ("SIO3Worker Visualizer" ),
52+                             html .P (
53+                                 "This is a visualizer for SIO3Worker's graph representation. <br>" 
54+                                 "Paste a JSON representation of the workflow in the text area below or upload a file." 
55+                             ),
56+                         ],
57+                         style = {"padding" : "10px" , "backgroundColor" : "#f7f7f7" },
58+                     ),
59+                     html .Div (
60+                         [
61+                             dcc .Textarea (id = "graph-input" , placeholder = "JSON description of the workflow" ),
62+                             dcc .Upload (
63+                                 id = "graph-file" ,
64+                                 children = html .Button ("Upload File" ),
65+                                 multiple = False ,
66+                             ),
67+                             html .Button ("Load" , id = "load-button" , n_clicks = 0 ),
68+                         ]
69+                     ),
21870                ],
219-                 style = { "flex" :  "1" ,  "height" :  "100vh" ,  "background-color" :  "#f7f7f7" } ,
71+                 id = "input-container" ,
22072            ),
73+         ]
74+     )
75+ 
76+     @app .callback ( 
77+         [ 
78+             Output ("graph" , "style" ), 
79+             Output ("graph-div" , "children" ), 
80+             Output ("input-container" , "style" ), 
81+         ], 
82+         Input ("load-button" , "n_clicks" ), 
83+         [ 
84+             State ("graph-input" , "value" ), 
85+             State ("graph-file" , "contents" ), 
22186        ], 
222-         style = {"display" : "flex" , "flex-direction" : "row" , "height" : "100vh" },
22387    ) 
88+     def  show_graph (n_clicks , value , contents ):
89+         if  n_clicks  >  0 :
90+             if  not  value  and  not  contents :
91+                 return  {"display" : "flex" }, [], {"display" : "block" }
92+             if  value :
93+                 file_content  =  value 
94+             else :
95+                 try :
96+                     content_type , content_string  =  contents .split ("," )
97+                     file_content  =  base64 .b64decode (content_string ).decode ("utf-8" )
98+                 except  Exception  as  e :
99+                     print (e )
100+                     return  {"display" : "flex" }, [], {"display" : "block" }
101+             graph  =  json .loads (file_content )
102+             elements  =  cytoscope .get_elements (graph )
103+             instance  =  cyto .Cytoscape (
104+                 id = "cytoscape" ,
105+                 layout = {"name" : "breadthfirst" , "directed" : True },
106+                 style = {"width" : "100%" , "height" : "100vh" },
107+                 elements = elements ,
108+                 stylesheet = [
109+                     {
110+                         "selector" : "node" ,
111+                         "style" : {
112+                             "label" : "data(label)" ,
113+                             "text-valign" : "center" ,
114+                             "text-margin-y" : "-20px" ,
115+                         },
116+                     },
117+                     {
118+                         "selector" : "edge" ,
119+                         "style" : {
120+                             "curve-style" : "bezier" ,  # Makes edges curved for better readability 
121+                             "target-arrow-shape" : "triangle" ,  # Adds an arrowhead to indicate direction 
122+                             "arrow-scale" : 1.5 ,  # Makes the arrow larger 
123+                             "line-color" : "#0074D9" ,  # Edge color 
124+                             "target-arrow-color" : "#0074D9" ,  # Arrow color 
125+                             "width" : 2 ,  # Line thickness 
126+                             "content" : "data(label)" ,  # Show edge label on hover 
127+                             "font-size" : "12px" ,
128+                             "color" : "#ff4136" ,
129+                             "text-background-opacity" : 1 ,
130+                             "text-background-color" : "white" ,
131+                             "text-background-shape" : "roundrectangle" ,
132+                             "text-border-opacity" : 1 ,
133+                             "text-border-width" : 1 ,
134+                             "text-border-color" : "#ff4136" ,
135+                         },
136+                     },
137+                     {
138+                         "selector" : ".register" ,
139+                         "style" : {
140+                             "shape" : "rectangle" ,
141+                         },
142+                     },
143+                     {
144+                         "selector" : ".script" ,
145+                         "style" : {
146+                             "shape" : "roundrectangle" ,
147+                         },
148+                     },
149+                     {
150+                         "selector" : ".execution" ,
151+                         "style" : {
152+                             "shape" : "ellipse" ,
153+                         },
154+                     },
155+                     {
156+                         "selector" : ".reactive" ,
157+                         "style" : {
158+                             "background-color" : "#ff851b" ,
159+                         },
160+                     },
161+                     {
162+                         "selector" : ".exclusive" ,
163+                         "style" : {
164+                             "background-color" : "#ff4136" ,
165+                         },
166+                     },
167+                 ],
168+             )
169+             return  (
170+                 {"display" : "flex" , "flex-direction" : "row" , "height" : "100vh" },
171+                 instance ,
172+                 {"display" : "none" },
173+             )
174+         return  (
175+             {"display" : "none" },
176+             None ,
177+             {"display" : "block" },
178+         )
224179
225180    @app .callback (Output ("node-data" , "children" ), Input ("cytoscape" , "tapNodeData" )) 
226181    def  display_task_info (data ):
@@ -230,4 +185,4 @@ def display_task_info(data):
230185            return  json .dumps (data ["info" ], indent = 4 )
231186        return  data ["info" ]
232187
233-     app .run_server ( debug = True )
188+     app .run ( )
0 commit comments