Commit 9f9f17883ce16db5a1cfa46f0dfa21853c0f4f60

Authored by Pavel Govyadinov
0 parents

clead up version of the GUI submitted to vis 2019

GraphCanvas.py 0 → 100644
  1 +++ a/GraphCanvas.py
  1 +#!/usr/bin/env python3
  2 +# -*- coding: utf-8 -*-
  3 +"""
  4 +Created on Mon Aug 5 15:43:59 2019
  5 +
  6 +@author: pavel
  7 + The GraphCanvas class that extends the scene class in vispy in order to draw
  8 + the graph object. This class is wrapped in a QTcanvas.
  9 +"""
  10 +
  11 +from vispy import gloo, scene
  12 +from vispy.gloo import set_viewport, set_state, clear, set_blend_color
  13 +from vispy.util.transforms import perspective, translate, rotate, scale
  14 +import vispy.gloo.gl as glcore
  15 +
  16 +import numpy as np
  17 +import math
  18 +import network_dep as nwt
  19 +
  20 +#import the graph shaders.
  21 +from graph_shaders import vert, frag, vs, fs
  22 +from subgraph_shaders import vert_s, frag_s, vs_s, fs_s
  23 +
  24 +#The graph canvas class that
  25 +class GraphCanvas(scene.SceneCanvas):
  26 +
  27 + """
  28 + Initialization method.
  29 + Generates the 512x512 canvas, makes it available for drawing the fills all
  30 + the GLSL shaders with dummy data.
  31 + """
  32 + def __init__(self, **kwargs):
  33 + # Initialize the canvas for real
  34 + scene.SceneCanvas.__init__(self, size=(512, 512), **kwargs)
  35 + #Unfreeze the canvas to make dynamic interaction possible
  36 + self.unfreeze()
  37 +
  38 + #initialize all the boolean and dictionary variables.
  39 + ps = self.pixel_scale
  40 + self.subgraphs = False
  41 + self.position = 50, 50
  42 + self.down=False;
  43 +
  44 + #Dictionaries to store the unique color ID, cluster ID, and edge-to-ID
  45 + #dictionaries.
  46 + self.color_dict = {}
  47 + self.cluster_dict = {}
  48 + self.edge_dict = {}
  49 +
  50 + #Booleans the storage for the current "Path", i.e. edges the user selected.
  51 + self.pathing = False
  52 + self.path = []
  53 + self.full_path = []
  54 +
  55 + #utility variables used for storing the cluster being moved and all the
  56 + #nodes and edges that belong to that cluster and move along with it.
  57 + self.moving = False
  58 + self.moving_cluster = False
  59 + self.selection = False
  60 +
  61 + n = 10
  62 + ne = 10
  63 + #Init dummy structures
  64 + self.uniforms = [('u_graph_size', np.float32, 3)]
  65 + self.data = np.zeros(n, dtype=[('a_position', np.float32, 3),
  66 + ('a_fg_color', np.float32, 4),
  67 + ('a_bg_color', np.float32, 4),
  68 + ('a_size', np.float32, 1),
  69 + ('a_linewidth', np.float32, 1),
  70 + ('a_unique_id', np.float32, 4),
  71 + ])
  72 +
  73 + self.clusters = np.zeros(n, dtype=[('a_position', np.float32, 3),
  74 + ('a_bg_color', np.float32, 4),
  75 + ('a_value', np.float32, 2),
  76 + ('a_unique_id', np.float32, 4),
  77 + ])
  78 +
  79 + self.edges = np.random.randint(size=(ne, 2), low=0,
  80 + high=n-1).astype(np.uint32)
  81 + self.edges_s = np.random.randint(size=(ne, 4), low=0,
  82 + high=n-1).astype(np.uint32)
  83 + self.data['a_position'] = np.hstack((0.25 * np.random.randn(n, 2),
  84 + np.zeros((n, 1))))
  85 + self.data['a_fg_color'] = 0, 0, 0, 1.0
  86 + color = np.random.uniform(0.5, 1., (n, 3))
  87 + self.data['a_bg_color'] = np.hstack((color, np.zeros((n, 1))))
  88 + self.data['a_size'] = np.random.randint(size=n, low=8*ps, high=20*ps)
  89 + self.data['a_linewidth'] = 8.*ps
  90 + self.data['a_unique_id'] = np.hstack((color, np.ones((n, 1))))
  91 + #self.uniforms['u_graph_size'] = [1.0, 1.0, 1.0]
  92 + self.translate = [0,0,0]
  93 + self.scale = [1,1,1]
  94 + #color = np.random.uniform(0.5, 1., (ne, 3))
  95 + #self.linecolor = np.hstack((color, np.ones((ne, 1))))
  96 + #color = np.random.uniform(0.5, 1., (ne, 3))
  97 + #self.linecolor = np.hstack((color, np.ones((ne, 1))))
  98 + self.u_antialias = 1
  99 +
  100 + #init dummy vertex and index buffers.
  101 + self.vbo = gloo.VertexBuffer(self.data)
  102 + self.vbo_s = gloo.VertexBuffer(self.clusters)
  103 +
  104 + #Need to initialize thick lines.
  105 + self.index = gloo.IndexBuffer(self.edges)
  106 + self.index_s = gloo.IndexBuffer(self.edges_s)
  107 +
  108 + #Set the view matrices.
  109 + self.view = np.eye(4, dtype=np.float32)
  110 + self.model = np.eye(4, dtype=np.float32)
  111 + self.projection = np.eye(4, dtype=np.float32)
  112 +
  113 + #init shaders used for vertices of the full graph.
  114 + self.program = gloo.Program(vert, frag)
  115 + self.program.bind(self.vbo)
  116 + self.program['u_size'] = 1
  117 + self.program['u_antialias'] = self.u_antialias
  118 + self.program['u_model'] = self.model
  119 + self.program['u_view'] = self.view
  120 + self.program['u_projection'] = self.projection
  121 + self.program['u_graph_size'] = [1.0, 1.0, 1.0]
  122 + self.program['u_picking'] = False
  123 +
  124 + #init shades used for the edges in the graph
  125 + self.program_e = gloo.Program(vs, fs)
  126 + self.program_e['u_size'] = 1
  127 + self.program_e['u_model'] = self.model
  128 + self.program_e['u_view'] = self.view
  129 + self.program_e['u_projection'] = self.projection
  130 + #self.program_e['l_color'] = self.linecolor.astype(np.float32)
  131 + self.program_e.bind(self.vbo)
  132 +
  133 + #init shaders used to the vertices in the subgraph graph.
  134 + self.program_s = gloo.Program(vert_s, frag_s)
  135 + self.program_s.bind(self.vbo_s)
  136 + self.program_s['u_model'] = self.model
  137 + self.program_s['u_view'] = self.view
  138 + self.program_s['u_projection'] = self.projection
  139 + self.program_s['u_graph_size'] = [1.0, 1.0, 1.0]
  140 + self.program_s['u_picking'] = False
  141 +
  142 + #init shaders used for the subgraph-edges
  143 + self.program_e_s = gloo.Program(vs_s, fs_s)
  144 + self.program_e_s['u_size'] = 1
  145 + self.program_e_s['u_model'] = self.model
  146 + self.program_e_s['u_view'] = self.view
  147 + self.program_e_s['u_projection'] = self.projection
  148 + #self.program_e['l_color'] = self.linecolor.astype(np.float32)
  149 + self.program_e_s.bind(self.vbo_s)
  150 +
  151 +
  152 + #set up the viewport and the gl state.
  153 + set_viewport(0, 0, *self.physical_size)
  154 +
  155 + set_state(clear_color='white', depth_test=True, blend=True,
  156 + blend_func=('src_alpha', 'one_minus_src_alpha'), depth_func = ('lequal'))
  157 +
  158 + """
  159 + Function that recolors vertices based on the selected statistic
  160 + Maps the statisic stored in G to a colormap passed to the function
  161 + Then updates the necessary color array.
  162 + """
  163 + def color_vertices(self, G, vertex_property, dtype = False, cm = 'plasma'):
  164 + #if we are visualing the clusters we should use a discrete colormap
  165 + #otherwise use the passed colormap
  166 + if dtype == True:
  167 + G.vertex_properties["RGBA"] = nwt.Network.map_property_to_color(G, G.vertex_properties["clusters"])
  168 + else:
  169 + G.vertex_properties["RGBA"] = nwt.Network.map_property_to_color(G, G.vertex_properties[vertex_property], colormap=cm)
  170 + #set the color and update the Vertices.
  171 + self.current_color = vertex_property
  172 + color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T
  173 + self.data['a_bg_color'] = color
  174 + self.vbo = gloo.VertexBuffer(self.data)
  175 + self.program.bind(self.vbo)
  176 + #self.program_e.bind(self.vbo)
  177 + self.update()
  178 +
  179 + """
  180 + Maps a statistic of the vertices based on the size of the canvas to size of
  181 + the drawn object.
  182 + """
  183 + def size_vertices(self, G, propertymap):
  184 + size = nwt.Network.map_vertices_to_range(G, [30*self.pixel_scale, 8*self.pixel_scale], propertymap).get_array()
  185 + self.data['a_size'] = size
  186 + self.vbo = gloo.VertexBuffer(self.data)
  187 + self.program.bind(self.vbo)
  188 + #self.program_e.bind(self.vbo)
  189 + self.update()
  190 +
  191 +
  192 + """
  193 + Function to dim all nodes and edges that do not belong to a cluster chosen
  194 + in the graph view. Returns a copy of the graph with the alpha channel saved.
  195 + OPTMIZE HERE: could just return an alpha array to reduce memory usage.
  196 + """
  197 + def focus_on_cluster(self, G, c_id):
  198 + G_copy = nwt.gt.Graph(G, directed=False)
  199 + e_color = G_copy.edge_properties["RGBA"].get_2d_array(range(4)).T
  200 + vertices = np.argwhere(self.labels != c_id)
  201 + for v in range(vertices.shape[0]):
  202 + idx = vertices[v][0]
  203 + vtx = G_copy.vertex(idx)
  204 + for e in vtx.all_edges():
  205 + if (int(e.source()), int(e.target())) in self.edge_dict.keys():
  206 + index = int(self.edge_dict[int(e.source()), int(e.target())])
  207 + if vtx == int(e.source()):
  208 + e_color[index][3] = 0.05
  209 + elif vtx == int(e.target()):
  210 + e_color[index][3] = 0.05
  211 + else:
  212 + index = int(self.edge_dict[int(e.target()), int(e.source())])
  213 + if vtx == int(e.target()):
  214 + e_color[index][3] = 0.05
  215 + elif vtx == int(e.source()):
  216 + e_color[index][3] = 0.05
  217 +
  218 + G_copy.edge_properties["RGBA"] = G_copy.new_edge_property("vector<double>", vals = e_color)
  219 +
  220 + return G_copy
  221 +
  222 + """
  223 + Function that sets the size of the vertices based on the distance from the
  224 + camera.
  225 + """
  226 + def vertexSizeFromDistance(self, G, camera_pos):
  227 + location = G.vertex_properties["p"].get_2d_array(range(3)).T
  228 + cam_array = np.zeros(location.shape, dtype=np.float32)
  229 + len_array = np.zeros(location.shape[0])
  230 + offset_array = np.zeros(location.shape, dtype=np.float32)
  231 + cam_array[:][0:3] = camera_pos
  232 + offset = [(bbu[0]-bbl[0])/2, (bbu[1]-bbl[1])/2, (bbu[2]-bbl[2])/2]
  233 + location = location - offset
  234 + location = location - camera_pos
  235 + for i in range(location.shape[0]):
  236 + len_array[i] = np.sqrt(np.power(location[i][0],2) + np.power(location[i][1],2) + np.power(location[i][2],2))
  237 +
  238 + G.vertex_properties['dist_from_camera'] = G.new_vertex_property('float', vals=len_array)
  239 + self.data['a_size'] = nwt.Network.map_vertices_to_range(G, [1*self.pixel_scale, 60*self.pixel_scale], 'dist_from_camera').get_array()
  240 +
  241 +
  242 + size = nwt.Network.map_vertices_to_range(G, [1.0, 0.5], 'dist_from_camera').get_array()
  243 + edges = G.get_edges()
  244 + for e in range(edges.shape[0]):
  245 + idx = int(4*edges[e][2])
  246 + self.line_data['a_linewidth'][idx] = size[edges[e][0]]
  247 + self.line_data['a_linewidth'][idx+1] = size[edges[e][1]]
  248 + self.line_data['a_linewidth'][idx+2] = size[edges[e][0]]
  249 + self.line_data['a_linewidth'][idx+3] = size[edges[e][1]]
  250 +
  251 +
  252 + #self.vbo = gloo.VertexBuffer(self.data)
  253 + #self.vbo_line = gloo.VertexBuffer(self.line_data)
  254 + #self.program.bind(self.vbo)
  255 + #self.program_e.bind(self.vbo_line)
  256 + #self.update()
  257 +
  258 + """
  259 + Function that scales the alpha channel of each vertex in the graph based on
  260 + The distance from the camera.
  261 + Sometimes needs to be done separetly.
  262 + """
  263 + def vertexAlphaFromDistance(self, G, camera_pos):
  264 + location = G.vertex_properties["p"].get_2d_array(range(3)).T
  265 + cam_array = np.zeros(location.shape, dtype=np.float32)
  266 + len_array = np.zeros(location.shape[0])
  267 + #offset_array = np.zeros(location.shape, dtype=np.float32)
  268 + cam_array[:][0:3] = camera_pos
  269 + offset = [(bbu[0]-bbl[0])/2, (bbu[1]-bbl[1])/2, (bbu[2]-bbl[2])/2]
  270 + location = location - offset
  271 + location = location - camera_pos
  272 + for i in range(location.shape[0]):
  273 + len_array[i] = np.sqrt(np.power(location[i][0],2) + np.power(location[i][1],2) + np.power(location[i][2],2))
  274 +
  275 +
  276 + test = nwt.Network.map_vertices_to_range(G, [0.0, 1.0], 'dist_from_camera').get_array()
  277 + color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T
  278 + for i in range(location.shape[0]):
  279 + color[i][3] = test[i]
  280 + G.vertex_properties["RGBA"] = G.new_vertex_property("vector<double>", vals = color)
  281 + self.data['a_bg_color'] = color
  282 +
  283 + edges = G.get_edges()
  284 + for e in range(edges.shape[0]):
  285 + idx = int(4*edges[e][2])
  286 + self.line_data['a_fg_color'][idx] = color[edges[e][0]]
  287 + self.line_data['a_fg_color'][idx+1] = color[edges[e][1]]
  288 + self.line_data['a_fg_color'][idx+2] = color[edges[e][0]]
  289 + self.line_data['a_fg_color'][idx+3] = color[edges[e][1]]
  290 +
  291 +
  292 +
  293 + self.vbo = gloo.VertexBuffer(self.data)
  294 + self.vbo_line = gloo.VertexBuffer(self.line_data)
  295 + self.program.bind(self.vbo)
  296 + self.program_e.bind(self.vbo_line)
  297 + self.update()
  298 +
  299 + """
  300 + Sets the edge color based on the the cluster the vertices belongs to
  301 + Propertymap is a VERTEXPROPERTYMAP since the color of the edges is based
  302 + on the clusters the edges belong to.
  303 + """
  304 + def color_edges(self, G, propertymap="clusters"):
  305 + if propertymap == "clusters":
  306 + for e in G.edges():
  307 + if G.vertex_properties[propertymap][e.source()] == G.vertex_properties[propertymap][e.target()]:
  308 + G.edge_properties["RGBA"][e] = G.vertex_properties["RGBA"][e.source()]
  309 + else:
  310 + G.edge_properties["RGBA"][e] = [0.0, 0.0, 0.0, 0.8]
  311 +
  312 +
  313 + """
  314 + Helper function that generates the framebuffer object that stores the vertices
  315 + Generates the vertex buffer based on the graph G that is passed to the function
  316 + Sets the color, generates the graph and subgraph color if necessary.
  317 + """
  318 + def gen_vertex_vbo(self, G):
  319 + color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T
  320 + size = nwt.Network.map_vertices_to_range(G, [30*self.pixel_scale, 8*self.pixel_scale], 'degree').get_array()
  321 +
  322 + position = G.vertex_properties["pos"].get_2d_array(range(3)).T
  323 + #for p in range(position.shape[0]):
  324 + # position[p][0] = position[p][0] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][0]
  325 + # position[p][1] = position[p][1] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][1]
  326 + # position[p][2] = position[p][2] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][2]
  327 + #G.vertex_properties["pos"] = G.new_vertex_property("vector<double>", vals = position)
  328 + edges = G.get_edges();
  329 + edges = edges[:, 0:2]
  330 + #width = nwt.Network.map_edges_to_range(G, [1*self.pixel_scale, 5*self.pixel_scale], 'volume').get_array()
  331 + #ecolor = G.edge_properties["RGBA"].get_2d_array(range(4)).T
  332 +
  333 + self.data = np.zeros(G.num_vertices(), dtype=[('a_position', np.float32, 3),
  334 + ('a_fg_color', np.float32, 4),
  335 + ('a_bg_color', np.float32, 4),
  336 + ('a_size', np.float32, 1),
  337 + ('a_linewidth', np.float32, 1),
  338 + ('a_unique_id', np.float32, 4),
  339 + ('a_selection', np.float32, 1),
  340 + ])
  341 +
  342 + #self.edges = edges.astype(np.uint32)
  343 + self.data['a_position'] = position
  344 + #fg color is the color of the ring
  345 + self.data['a_fg_color'] = 0, 0, 0, 1
  346 + self.data['a_bg_color'] = color
  347 + self.data['a_size'] = size
  348 + self.data['a_linewidth'] = 4.*self.pixel_scale
  349 + self.data['a_unique_id'] = self.gen_vertex_id(G)
  350 + self.data['a_selection'] = G.vertex_properties["selection"].get_array()
  351 + #self.data['a_graph_size'] = [bbu-bbl]
  352 +
  353 + self.program['u_graph_size'] = [bbu-bbl]
  354 +
  355 + self.vbo = gloo.VertexBuffer(self.data)
  356 + self.gen_line_vbo(G)
  357 + if(self.subgraphs):
  358 + self.vbo_s = gloo.VertexBuffer(self.clusters)
  359 + self.index_s = gloo.IndexBuffer(self.edges_s)
  360 + #self.index = gloo.IndexBuffer(self.edges)
  361 + self.program_e.bind(self.vbo_line)
  362 + self.program.bind(self.vbo)
  363 + if(self.subgraphs):
  364 + #self.program_e_s.bind(self.vbo_s)
  365 + self.program_s.bind(self.vbo_s)
  366 + print(self.view)
  367 + self.update()
  368 +
  369 + """
  370 + Helper function that creates colored "block" lines based on the edges
  371 + in the graph. Generates the framebuffer object and fills it with the relavant data.
  372 + Note that each line segment is saved as a two triangles that share the same
  373 + two points on the centerline, but are offset according to the normal of the
  374 + line segmente to control thickness dynamically.
  375 + """
  376 + def gen_line_vbo(self, G):
  377 + #Set the data.
  378 + self.line_data = np.zeros(G.num_edges()*4, dtype=[('a_position', np.float32, 3),
  379 + ('a_normal', np.float32, 2),
  380 + ('a_fg_color', np.float32, 4),
  381 + ('a_linewidth', np.float32, 1),
  382 + ])
  383 + self.edges = np.random.randint(size=(G.num_edges()*2, 3), low=0,
  384 + high=(G.num_edges()-1)).astype(np.uint32)
  385 + color = G.edge_properties["RGBA"].get_2d_array(range(4)).T
  386 + edges = G.get_edges()
  387 + #size need to be changed to the size based on the current property map
  388 + size = nwt.Network.map_vertices_to_range(G, [1.0, 0.5], 'degree').get_array()
  389 + for e in range(edges.shape[0]):
  390 + idx = int(4*edges[e][2])
  391 + p0 = G.vertex_properties["pos"][G.vertex(edges[e][0])]
  392 + p1 = G.vertex_properties["pos"][G.vertex(edges[e][1])]
  393 + d = np.subtract(p1, p0)
  394 + #d_norm = np.multiply(d, 1/np.sqrt(np.power(d[0],2) + np.power(d[1],2)))
  395 + d_norm = d[0:2]
  396 + d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2))
  397 + norm = np.zeros((2,), dtype=np.float32)
  398 + norm[0] = d_norm[1]
  399 + norm[1] = d_norm[0]*-1
  400 + #print(np.sqrt(norm[0]*norm[0] + norm[1]*norm[1]))
  401 + #thickness = G.edge_properties["thickness"][e]
  402 + thickness = 1.0
  403 + self.edge_dict[int(edges[e][0]), int(edges[e][1])] = int(edges[e][2])
  404 + self.line_data['a_position'][idx] = p0
  405 + self.line_data['a_normal'][idx] = norm
  406 + self.line_data['a_fg_color'][idx] = color[edges[e][2]]
  407 + #a_linewidth is a vector.
  408 + self.line_data['a_linewidth'][idx] = size[edges[e][0]]
  409 +
  410 + self.line_data['a_position'][idx+1] = p1
  411 + self.line_data['a_normal'][idx+1] = norm
  412 + self.line_data['a_fg_color'][idx+1] = color[edges[e][2]]
  413 + self.line_data['a_linewidth'][idx+1] = size[edges[e][1]]
  414 +
  415 + self.line_data['a_position'][idx+2] = p0
  416 + self.line_data['a_normal'][idx+2] = -norm
  417 + self.line_data['a_fg_color'][idx+2] = color[edges[e][2]]
  418 + self.line_data['a_linewidth'][idx+2] = size[edges[e][0]]
  419 +
  420 + self.line_data['a_position'][idx+3] = p1
  421 + self.line_data['a_normal'][idx+3] = -norm
  422 + self.line_data['a_fg_color'][idx+3] = color[edges[e][2]]
  423 + self.line_data['a_linewidth'][idx+3] = size[edges[e][1]]
  424 +
  425 + self.edges[e*2] = [idx, idx+1, idx+3]
  426 + self.edges[e*2+1] = [idx, idx+2, idx+3]
  427 +
  428 + #Set the buffer object and update the shader programs.
  429 + self.program_e = gloo.Program(vs, fs)
  430 + #self.program_e['l_color'] = self.linecolor.astype(np.float32)
  431 + self.vbo_line = gloo.VertexBuffer(self.line_data)
  432 + self.index = gloo.IndexBuffer(self.edges)
  433 + self.program_e['u_size'] = 1
  434 + self.program_e['u_model'] = self.model
  435 + self.program_e['u_view'] = self.view
  436 + self.program_e['u_projection'] = self.projection
  437 + self.program_e.bind(self.vbo_line)
  438 +
  439 +
  440 + """
  441 + Helper function that generates the edges between the cluster in the layout.
  442 + Color is based on the cluster source/target color and transitions between the
  443 + two.
  444 + """
  445 + def gen_cluster_line_vbo(self, G):
  446 + #create a graph that stores the edges of between the clusters
  447 + self.G_cluster = nwt.gt.Graph(directed=False)
  448 + self.G_cluster.vertex_properties["pos"] = self.G_cluster.new_vertex_property("vector<double>", val=np.zeros((3,1), dtype=np.float32))
  449 + self.G_cluster.vertex_properties["RGBA"] = self.G_cluster.new_vertex_property("vector<double>", val=np.zeros((4,1), dtype=np.float32))
  450 + for v in range(len(self.cluster_pos)):
  451 + self.G_cluster.add_vertex()
  452 + self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(v)] = np.asarray(self.cluster_pos[v], dtype=np.float32)
  453 + self.G_cluster.edge_properties["weight"] = self.G_cluster.new_edge_property("int", val = 0)
  454 + #for each edge in the original graph, generate appropriate subgraph edges without repretiions
  455 + #i.e. controls the thichness of the edges in the subgraph view.
  456 + for e in G.edges():
  457 + #if the source and target cluster is not equal to each other
  458 + #add an inter subgraph edge.
  459 + if(G.vertex_properties["clusters"][e.source()] != G.vertex_properties["clusters"][e.target()]):
  460 + #temp_e.append([G.vertex_properties["clusters"][e.source()], G.vertex_properties["clusters"][e.target()]])
  461 + self.G_cluster.add_edge(self.G_cluster.vertex(G.vertex_properties["clusters"][e.source()]), \
  462 + self.G_cluster.vertex(G.vertex_properties["clusters"][e.target()]))
  463 + self.G_cluster.edge_properties["weight"][self.G_cluster.edge(self.G_cluster.vertex(G.vertex_properties["clusters"][e.source()]), \
  464 + self.G_cluster.vertex(G.vertex_properties["clusters"][e.target()]))] += 1
  465 + self.G_cluster.vertex_properties["RGBA"][self.G_cluster.vertex(G.vertex_properties["clusters"][e.source()])] \
  466 + = G.vertex_properties["RGBA"][e.source()]
  467 + self.G_cluster.vertex_properties["RGBA"][self.G_cluster.vertex(G.vertex_properties["clusters"][e.target()])] \
  468 + = G.vertex_properties["RGBA"][e.target()]
  469 +
  470 + self.cluster_line_data = np.zeros(self.G_cluster.num_edges()*4, dtype=[('a_position', np.float32, 3),
  471 + ('a_normal', np.float32, 2),
  472 + ('a_fg_color', np.float32, 4),
  473 + ('a_linewidth', np.float32, 1),
  474 + ])
  475 + self.cluster_edges = np.random.randint(size=(self.G_cluster.num_edges()*2, 3), low=0,
  476 + high=(G.num_edges()-1)).astype(np.uint32)
  477 +
  478 + edges = self.G_cluster.get_edges()
  479 + #size need to be changed to the size based on the current property map
  480 + size = nwt.Network.map_edges_to_range(self.G_cluster, [1.0, 0.5], 'weight').get_array()
  481 + color = self.G_cluster.vertex_properties["RGBA"].get_2d_array(range(4)).T
  482 + #generate the vertex buffer and the connections buffer.
  483 + for e in range(edges.shape[0]):
  484 + idx = int(4*edges[e][2])
  485 + p0 = self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(edges[e][0])]
  486 + p1 = self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(edges[e][1])]
  487 + d = np.subtract(p1, p0)
  488 + #d_norm = np.multiply(d, 1/np.sqrt(np.power(d[0],2) + np.power(d[1],2)))
  489 + d_norm = d[0:2]
  490 + d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2))
  491 + norm = np.zeros((2,), dtype=np.float32)
  492 + norm[0] = d_norm[1]
  493 + norm[1] = d_norm[0]*-1
  494 + #print(np.sqrt(norm[0]*norm[0] + norm[1]*norm[1]))
  495 + #thickness = G.edge_properties["thickness"][e]
  496 + self.cluster_dict[int(edges[e][0]), int(edges[e][1])] = int(edges[e][2])
  497 + self.cluster_line_data['a_position'][idx] = p0
  498 + self.cluster_line_data['a_normal'][idx] = norm
  499 + self.cluster_line_data['a_fg_color'][idx] = color[edges[e][0]]
  500 + self.cluster_line_data['a_linewidth'][idx] = size[e]
  501 +
  502 + self.cluster_line_data['a_position'][idx+1] = p1
  503 + self.cluster_line_data['a_normal'][idx+1] = norm
  504 + self.cluster_line_data['a_fg_color'][idx+1] = color[edges[e][1]]
  505 + self.cluster_line_data['a_linewidth'][idx+1] = size[e]
  506 +
  507 + self.cluster_line_data['a_position'][idx+2] = p0
  508 + self.cluster_line_data['a_normal'][idx+2] = -norm
  509 + self.cluster_line_data['a_fg_color'][idx+2] = color[edges[e][0]]
  510 + self.cluster_line_data['a_linewidth'][idx+2] = size[e]
  511 +
  512 + self.cluster_line_data['a_position'][idx+3] = p1
  513 + self.cluster_line_data['a_normal'][idx+3] = -norm
  514 + self.cluster_line_data['a_fg_color'][idx+3] = color[edges[e][1]]
  515 + self.cluster_line_data['a_linewidth'][idx+3] = size[e]
  516 +
  517 + self.cluster_edges[e*2] = [idx, idx+1, idx+3]
  518 + self.cluster_edges[e*2+1] = [idx, idx+2, idx+3]
  519 +
  520 +
  521 + self.program_e_s = gloo.Program(vs_s, fs_s)
  522 + self.index_clusters_s = gloo.IndexBuffer(self.cluster_edges)
  523 + self.vbo_cluster_lines = gloo.VertexBuffer(self.cluster_line_data)
  524 +
  525 + self.program_e_s['u_size'] = 1
  526 + self.program_e_s['u_model'] = self.model
  527 + self.program_e_s['u_view'] = self.view
  528 + self.program_e_s['u_projection'] = self.projection
  529 + self.program_e_s.bind(self.vbo_cluster_lines)
  530 +
  531 +
  532 + """
  533 + Updates the vertex buffers based on the current position of the cluster.
  534 + Updates it's position.
  535 + """
  536 + def update_cluster_line_vbo(self):
  537 +
  538 + for v in range(len(self.cluster_pos)):
  539 + self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(v)] = np.asarray(self.cluster_pos[v], dtype=np.float32)
  540 + #OPTIMIZE HERE to update only one cluster at a time.
  541 + edges = self.G_cluster.get_edges()
  542 + #size need to be changed to the size based on the current property map
  543 + size = nwt.Network.map_edges_to_range(self.G_cluster, [1.0, 0.5], 'weight').get_array()
  544 + color = self.G_cluster.vertex_properties["RGBA"].get_2d_array(range(4)).T
  545 + for e in range(edges.shape[0]):
  546 + idx = int(4*edges[e][2])
  547 + p0 = self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(edges[e][0])]
  548 + p1 = self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(edges[e][1])]
  549 + d = np.subtract(p1, p0)
  550 + #d_norm = np.multiply(d, 1/np.sqrt(np.power(d[0],2) + np.power(d[1],2)))
  551 + d_norm = d[0:2]
  552 + d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2))
  553 + norm = np.zeros((2,), dtype=np.float32)
  554 + norm[0] = d_norm[1]
  555 + norm[1] = d_norm[0]*-1
  556 + #print(np.sqrt(norm[0]*norm[0] + norm[1]*norm[1]))
  557 + #thickness = G.edge_properties["thickness"][e]
  558 + self.cluster_dict[int(edges[e][0]), int(edges[e][1])] = int(edges[e][2])
  559 + self.cluster_line_data['a_position'][idx] = p0
  560 + self.cluster_line_data['a_normal'][idx] = norm
  561 + self.cluster_line_data['a_fg_color'][idx] = color[edges[e][0]]
  562 + self.cluster_line_data['a_linewidth'][idx] = size[e]
  563 +
  564 + self.cluster_line_data['a_position'][idx+1] = p1
  565 + self.cluster_line_data['a_normal'][idx+1] = norm
  566 + self.cluster_line_data['a_fg_color'][idx+1] = color[edges[e][1]]
  567 + self.cluster_line_data['a_linewidth'][idx+1] = size[e]
  568 +
  569 + self.cluster_line_data['a_position'][idx+2] = p0
  570 + self.cluster_line_data['a_normal'][idx+2] = -norm
  571 + self.cluster_line_data['a_fg_color'][idx+2] = color[edges[e][0]]
  572 + self.cluster_line_data['a_linewidth'][idx+2] = size[e]
  573 +
  574 + self.cluster_line_data['a_position'][idx+3] = p1
  575 + self.cluster_line_data['a_normal'][idx+3] = -norm
  576 + self.cluster_line_data['a_fg_color'][idx+3] = color[edges[e][1]]
  577 + self.cluster_line_data['a_linewidth'][idx+3] = size[e]
  578 +
  579 + self.program_e_s = gloo.Program(vs_s, fs_s)
  580 + self.index_clusters_s = gloo.IndexBuffer(self.cluster_edges)
  581 + self.vbo_cluster_lines = gloo.VertexBuffer(self.cluster_line_data)
  582 +
  583 + self.program_e_s['u_size'] = 1
  584 + self.program_e_s['u_model'] = self.model
  585 + self.program_e_s['u_view'] = self.view
  586 + self.program_e_s['u_projection'] = self.projection
  587 + self.program_e_s.bind(self.vbo_cluster_lines)
  588 +
  589 + """
  590 + Genererates a unique index for every vertex.
  591 + """
  592 + def gen_vertex_id(self, G):
  593 + self.color_dict = {}
  594 + base = [0, 0, 0, 255]
  595 + idx = 0
  596 + #colors = cm.get_cmap('Wistia', G.num_vertices()*2)
  597 + v_id = np.zeros((G.num_vertices(), 4), dtype=np.float32)
  598 + for v in G.vertices():
  599 + color = np.multiply(base, 1/255.0)
  600 + v_id[int(v)] = color
  601 + self.color_dict[tuple(color)] = int(v)
  602 + idx += 1
  603 + base = [int(idx/(255*255)), int((idx/255)%255), int(idx%255), 255]
  604 +
  605 + return(v_id)
  606 +
  607 + """
  608 + Generates a unique index for every cluster.
  609 + """
  610 + def gen_cluster_id(self, G):
  611 + self.cluster_dict = {}
  612 + base = [0, 0, 0, 255]
  613 + idx = 0
  614 + #colors = cm.get_cmap('Wistia', G.num_vertices()*2)
  615 + v_id = np.zeros((self.n_c, 4), dtype=np.float32)
  616 + for v in range(self.n_c):
  617 + color = np.multiply(base, 1/255.0)
  618 + v_id[int(v)] = color
  619 + self.cluster_dict[tuple(color)] = int(v)
  620 + idx += 1
  621 + base = [int(idx/(255*255)), int((idx/255)%255), int(idx%255), 255]
  622 +
  623 + return(v_id)
  624 +
  625 +
  626 + """
  627 + Generates the bounding box of the radial glyph.
  628 + """
  629 + def gen_cluster_coords(self, center, diameter):
  630 + radius = diameter/2.0
  631 + top = center[1]+radius
  632 + bottom = center[1]-radius
  633 + left = center[0]-radius
  634 + right = center[0]+radius
  635 +
  636 + positions = [[right, bottom, center[2]],
  637 + [right, top, center[2]],
  638 + [left, top, center[2]],
  639 + [left, bottom, center[2]]]
  640 +
  641 +
  642 + values = [[1.0, -1.0],
  643 + [1.0, 1.0,],
  644 + [-1.0, 1.0],
  645 + [-1.0, -1.0]]
  646 + return positions, values
  647 +
  648 +
  649 + """
  650 + Layout algorithm that expands the cluster based on the location of the of the clusters
  651 + """
  652 + def expand_based_on_clusters(self, G, n):
  653 + pos = G.vertex_properties["pos"].get_2d_array(range(3)).T
  654 + for p in range(pos.shape[0]):
  655 + pos[p][0] = pos[p][0] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][0]
  656 + pos[p][1] = pos[p][1] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][1]
  657 + pos[p][2] = pos[p][2] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][2]
  658 + G.vertex_properties["pos"] = G.new_vertex_property("vector<double>", vals = pos)
  659 + for i in range(n):
  660 + index = 4*i
  661 + #generate the vertex filter for this cluster
  662 + num_v_in_cluster = len(np.argwhere(self.labels == i))
  663 + vfilt = np.zeros([G.num_vertices(), 1], dtype="bool")
  664 + vfilt[np.argwhere(self.labels == i)] = 1
  665 + vfilt_prop = G.new_vertex_property("bool", vals = vfilt)
  666 + G.set_vertex_filter(vfilt_prop)
  667 +
  668 + #get the filtered properties
  669 + g = nwt.gt.Graph(G, prune=True, directed=False)
  670 + positions = g.vertex_properties["pos"].get_2d_array(range(3)).T
  671 + position = np.sum(positions, 0)/num_v_in_cluster
  672 + p, v = self.gen_cluster_coords(position, np.sum(g.vertex_properties['degree'].get_array()))
  673 + self.clusters['a_position'][index:index+4] = np.asarray(p, dtype=np.float32)
  674 + self.clusters['a_value'][index:index+4] = np.asarray(v, dtype=np.float32)
  675 + G.clear_filters()
  676 + self.cluster_pos[i] = position
  677 + color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T
  678 + size = nwt.Network.map_vertices_to_range(G, [30*self.pixel_scale, 8*self.pixel_scale], 'degree').get_array()
  679 +
  680 + position = G.vertex_properties["pos"].get_2d_array(range(3)).T
  681 + #for p in range(position.shape[0]):
  682 + # position[p][0] = position[p][0] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][0]
  683 + # position[p][1] = position[p][1] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][1]
  684 + # position[p][2] = position[p][2] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][2]
  685 + #G.vertex_properties["pos"] = G.new_vertex_property("vector<double>", vals = position)
  686 + edges = G.get_edges();
  687 + edges = edges[:, 0:2]
  688 + #width = nwt.Network.map_edges_to_range(G, [1*self.pixel_scale, 5*self.pixel_scale], 'volume').get_array()
  689 + #ecolor = G.edge_properties["RGBA"].get_2d_array(range(4)).T
  690 +
  691 + self.data = np.zeros(G.num_vertices(), dtype=[('a_position', np.float32, 3),
  692 + ('a_fg_color', np.float32, 4),
  693 + ('a_bg_color', np.float32, 4),
  694 + ('a_size', np.float32, 1),
  695 + ('a_linewidth', np.float32, 1),
  696 + ('a_unique_id', np.float32, 4),
  697 + ('a_selection', np.float32, 1),
  698 + ])
  699 +
  700 + #self.edges = edges.astype(np.uint32)
  701 + self.data['a_position'] = position
  702 + #fg color is the color of the ring
  703 + self.data['a_fg_color'] = 0, 0, 0, 1
  704 + self.data['a_bg_color'] = color
  705 + self.data['a_size'] = size
  706 + self.data['a_linewidth'] = 4.*self.pixel_scale
  707 + self.data['a_unique_id'] = self.gen_vertex_id(G)
  708 + self.data['a_selection'] = G.vertex_properties["selection"].get_array()
  709 + #self.data['a_graph_size'] = [bbu-bbl]
  710 +
  711 + self.program['u_graph_size'] = [bbu-bbl]
  712 + self.vbo = gloo.VertexBuffer(self.data)
  713 + self.gen_line_vbo(G)
  714 + if(self.subgraphs):
  715 + self.vbo_s = gloo.VertexBuffer(self.clusters)
  716 + self.index_s = gloo.IndexBuffer(self.edges_s)
  717 + #self.index = gloo.IndexBuffer(self.edges)
  718 + self.program_e.bind(self.vbo_line)
  719 + self.program.bind(self.vbo)
  720 + if(self.subgraphs):
  721 + #self.program_e_s.bind(self.vbo_s)
  722 + self.program_s.bind(self.vbo_s)
  723 + print(self.view)
  724 + self.update()
  725 +
  726 +
  727 + """
  728 + Function that generates the clusters for an unclustered graph
  729 + These are to be represented by the arcs
  730 + """
  731 + def gen_clusters(self, G, bbl, bbu, n_c = None, edge_metric = 'volume', vertex_metric = 'degree'):
  732 +
  733 + #Generate the clusters
  734 + self.labels = nwt.Network.spectral_clustering(G,'length', n_clusters = n_c)
  735 + #self.labels = nwt.Network.spectral_clustering(G,'length')
  736 +
  737 + #Add clusters as a vertex property
  738 + G.vertex_properties["clusters"] = G.new_vertex_property("int", vals=self.labels)
  739 + num_clusters = len(np.unique(self.labels))
  740 + self.n_c = n_c
  741 +
  742 + #add colormap
  743 + G.vertex_properties["RGBA"] = nwt.Network.map_property_to_color(G, G.vertex_properties["clusters"])
  744 +
  745 + #generate an empty property set for the clusters.
  746 + self.clusters = np.zeros(num_clusters*4, dtype=[('a_position', np.float32, 3),
  747 + ('a_value', np.float32, 2),
  748 + ('a_bg_color', np.float32, 4),
  749 + ('a_cluster_color', np.float32, 4),
  750 + ('a_arc_length', np.float32, 1),
  751 + ('a_outer_arc_length', np.float32, 4),
  752 + ('a_unique_id', np.float32, 4),
  753 + ])
  754 + self.edges_s = np.random.randint(size=(num_clusters*2, 3), low=0,
  755 + high=4).astype(np.uint32)
  756 + #fill the foreground color as halo
  757 + #self.clusters['a_fg_color'] = 1., 1., 1., 0.0
  758 + #self.clusters['a_linewidth'] = 4.*self.pixel_scale
  759 +
  760 + G.vertex_properties["pos"] = nwt.gt.sfdp_layout(G, groups = G.vertex_properties["clusters"], pos = G.vertex_properties["pos"])
  761 + temp = [];
  762 + temp_pos = [];
  763 + #Find the global total of the metric.
  764 + global_metric = np.sum(G.edge_properties[edge_metric].get_array(), 0)
  765 + unique_color = self.gen_cluster_id(G)
  766 +
  767 + #generate the property values for every cluster
  768 + for i in range(num_clusters):
  769 + idx = 4*i
  770 + #generate the vertex filter for this cluster
  771 + num_v_in_cluster = len(np.argwhere(self.labels == i))
  772 + vfilt = np.zeros([G.num_vertices(), 1], dtype="bool")
  773 + vfilt[np.argwhere(self.labels == i)] = 1
  774 + vfilt_prop = G.new_vertex_property("bool", vals = vfilt)
  775 + G.set_vertex_filter(vfilt_prop)
  776 +
  777 + #get the filtered properties
  778 + g = nwt.gt.Graph(G, prune=True, directed=False)
  779 + positions = g.vertex_properties["pos"].get_2d_array(range(3)).T
  780 + position = np.sum(positions, 0)/num_v_in_cluster
  781 +
  782 + #calculate the arclength for the global statistic
  783 + arc_length = np.sum(g.edge_properties[edge_metric].get_array(), 0)/global_metric*np.pi*2
  784 + arc_length_vertex = np.ones((4,1), dtype = np.float32)
  785 + array = g.vertex_properties[vertex_metric].get_array()
  786 +
  787 + #calculate metric distribution and turn it into arc_lengths
  788 + t_vertex_metric = np.sum(array)
  789 + arc_length_vertex[0] = np.sum(array < 2)/t_vertex_metric
  790 + arc_length_vertex[1] = np.sum(array == 2)/t_vertex_metric
  791 + arc_length_vertex[2] = np.sum(array == 3)/t_vertex_metric
  792 + arc_length_vertex[3] = np.sum(array > 3)/t_vertex_metric
  793 +
  794 + #arc_length_vertex = np.asarray(arc_length_vertex, dtype = np.float32)
  795 + #arc_length_vertex = (max(arc_length_vertex) - min(arc_length_vertex)) \
  796 + #* (arc_length_vertex- min(arc_length_vertex))
  797 + for j in range(len(arc_length_vertex)):
  798 + if j != 0:
  799 + arc_length_vertex[j] += arc_length_vertex[j-1]
  800 + print("arc_length before ", arc_length_vertex, " and sum to ", sum(arc_length_vertex))
  801 + arc_length_vertex = np.asarray(arc_length_vertex, dtype = np.float32)
  802 + arc_length_vertex = (np.pi - -np.pi)/(max(arc_length_vertex) - min(arc_length_vertex)) \
  803 + * (arc_length_vertex- min(arc_length_vertex)) + (-np.pi)
  804 + print(arc_length_vertex)
  805 + #print(arc_length)
  806 +
  807 +
  808 + temp_pos.append(position)
  809 +
  810 + #generate the color for every vertex,
  811 + #since all vertices belong to the same cluster we can check only
  812 + #one vertex for the cluster color.
  813 +
  814 + self.clusters['a_cluster_color'][idx:idx+4] = g.vertex_properties["RGBA"][g.vertex(0)]
  815 + self.clusters['a_bg_color'][idx:idx+4] = [0.1, 0.1, 0.1, 1.0]
  816 + self.clusters['a_unique_id'][idx:idx+4] = unique_color[i]
  817 +
  818 + #The arc-length representing one global metric.
  819 + self.clusters['a_arc_length'][idx:idx+4] = arc_length
  820 + self.clusters['a_outer_arc_length'][idx:idx+4] = arc_length_vertex[:].T
  821 +
  822 + temp.append(np.sum(g.vertex_properties['degree'].get_array()))
  823 + G.clear_filters()
  824 + print(self.clusters['a_outer_arc_length'])
  825 + maximum = max(temp)
  826 + minimum = min(temp)
  827 + if len(temp) > 1:
  828 + temp = ((temp-minimum)/(maximum-minimum)*(60*self.pixel_scale)+20*self.pixel_scale)
  829 + else:
  830 + temp = [60*self.pixel_scale]
  831 + for i in range(num_clusters):
  832 + index = i*4
  833 + index_t = i*2
  834 + p, v = self.gen_cluster_coords(temp_pos[i], temp[i]*2.0)
  835 + self.clusters['a_position'][index:index+4] = np.asarray(p, dtype=np.float32)
  836 + self.clusters['a_value'][index:index+4] = np.asarray(v, dtype=np.float32)
  837 +
  838 + self.edges_s[index_t] = [index, index+1, index+2]
  839 + self.edges_s[index_t+1] = [index, index+2, index+3]
  840 + #self.edges_s[i][0:4] = np.asarray(range(index, index+4), dtype=np.uint32)
  841 + #self.edges_s[i]
  842 + self.cluster_pos = temp_pos
  843 + #self.expand_based_on_clusters(G, self.n_c)
  844 + G.clear_filters()
  845 +# self.edges_s[1][0:4] = np.asarray(range(0, 0+4), dtype=np.uint32)
  846 +# self.edges_s[1][4] = 0
  847 +# self.edges_s[1][5] = 0+2
  848 +#
  849 +# self.edges_s[0][0:4] = np.asarray(range(index, index+4), dtype=np.uint32)
  850 +# self.edges_s[0][4] = index
  851 +# self.edges_s[0][5] = index+2
  852 + #self.clusters['a_size'] = temp
  853 + self.gen_cluster_line_vbo(G)
  854 + self.program_s['u_graph_size'] = [bbu-bbl]
  855 + #if len(temp_e) > 0:
  856 + # self.edges_s = np.unique(np.asarray(temp_e, np.uint32), axis=0)
  857 + #else:
  858 + # self.edges_s = []
  859 + #print(self.edges_s)
  860 +
  861 +
  862 + """
  863 + Function that expands that generates the layout and updates the buffer
  864 +
  865 + """
  866 + def expand_clusters(self, G, n_c):
  867 + self.expand_based_on_clusters(G, n_c)
  868 + self.gen_cluster_line_vbo(G)
  869 + if(self.subgraphs):
  870 + self.vbo_s = gloo.VertexBuffer(self.clusters)
  871 + self.index_s = gloo.IndexBuffer(self.edges_s)
  872 + self.program_e.bind(self.vbo_line)
  873 + self.program.bind(self.vbo)
  874 + if(self.subgraphs):
  875 + self.program_s.bind(self.vbo_s)
  876 + print(self.view)
  877 + self.update()
  878 +
  879 + """
  880 + Loads the data G and generates all the buffers necessary as well as performs
  881 + spectral clustering on the graph passed if the subgraph is set to true.
  882 + """
  883 + def set_data(self, G, bbl, bbu, subgraph=True):
  884 + print("Setting data")
  885 + self.G = G
  886 + self.bbl = bbl
  887 + self.bbu = bbu
  888 + clear(color=True, depth=True)
  889 + self.subgraphs = True
  890 + self.current_color = "clusters"
  891 + if(subgraph==True):
  892 + self.gen_clusters(G, bbl, bbu, n_c=19)
  893 +
  894 + #color based on clusters
  895 + self.color_edges(G)
  896 +
  897 + color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T
  898 + size = nwt.Network.map_vertices_to_range(G, [30*self.pixel_scale, 8*self.pixel_scale], 'degree').get_array()
  899 +
  900 + position = G.vertex_properties["pos"].get_2d_array(range(3)).T
  901 + #for p in range(position.shape[0]):
  902 + # position[p][0] = position[p][0] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][0]
  903 + # position[p][1] = position[p][1] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][1]
  904 + # position[p][2] = position[p][2] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][2]
  905 + #G.vertex_properties["pos"] = G.new_vertex_property("vector<double>", vals = position)
  906 + edges = G.get_edges();
  907 + edges = edges[:, 0:2]
  908 + #width = nwt.Network.map_edges_to_range(G, [1*self.pixel_scale, 5*self.pixel_scale], 'volume').get_array()
  909 + #ecolor = G.edge_properties["RGBA"].get_2d_array(range(4)).T
  910 +
  911 + self.data = np.zeros(G.num_vertices(), dtype=[('a_position', np.float32, 3),
  912 + ('a_fg_color', np.float32, 4),
  913 + ('a_bg_color', np.float32, 4),
  914 + ('a_size', np.float32, 1),
  915 + ('a_linewidth', np.float32, 1),
  916 + ('a_unique_id', np.float32, 4),
  917 + ('a_selection', np.float32, 1),
  918 + ])
  919 +
  920 + #self.edges = edges.astype(np.uint32)
  921 + self.data['a_position'] = position
  922 + #fg color is the color of the ring
  923 + self.data['a_fg_color'] = 0, 0, 0, 1
  924 + self.data['a_bg_color'] = color
  925 + self.data['a_size'] = size
  926 + self.data['a_linewidth'] = 4.*self.pixel_scale
  927 + self.data['a_unique_id'] = self.gen_vertex_id(G)
  928 + self.data['a_selection'] = G.vertex_properties["selection"].get_array()
  929 + #self.data['a_graph_size'] = [bbu-bbl]
  930 +
  931 + self.program['u_graph_size'] = [bbu-bbl]
  932 +
  933 + self.vbo = gloo.VertexBuffer(self.data)
  934 + self.gen_line_vbo(G)
  935 + #self.gen_cylinder_vbo(G)
  936 + if(self.subgraphs):
  937 + self.vbo_s = gloo.VertexBuffer(self.clusters)
  938 + self.index_s = gloo.IndexBuffer(self.edges_s)
  939 + #self.index = gloo.IndexBuffer(self.edges)
  940 + self.program_e.bind(self.vbo_line)
  941 + self.program.bind(self.vbo)
  942 + if(self.subgraphs):
  943 + #self.program_e_s.bind(self.vbo_s)
  944 + self.program_s.bind(self.vbo_s)
  945 + print(self.view)
  946 + self.update()
  947 +
  948 + """
  949 + Function that changes and redraws the buffer during a resize event.
  950 + """
  951 + def on_resize(self, event):
  952 + set_viewport(0, 0, *event.physical_size)
  953 + self.fbo = gloo.FrameBuffer(color=gloo.RenderBuffer(self.size[::-1]), depth=gloo.RenderBuffer(self.size[::-1]))
  954 +
  955 +
  956 + """
  957 + Overloaded function that is called during every self.update() call
  958 + """
  959 + def on_draw(self, event):
  960 + clear(color='white', depth=True)
  961 + self.program_e.draw('triangles', indices=self.index)
  962 + self.program.draw('points')
  963 + #self.program_e.draw('lines')
  964 + if(self.subgraphs):
  965 + self.program_e_s.draw('triangles', indices=self.index_clusters_s)
  966 + self.program_s.draw('triangles', indices=self.index_s)
  967 +
  968 + """
  969 + Function performed during a mouse click (either right or left)
  970 + gets the unique id of the object drawn underneath the cursor
  971 + handles the cases depending on whether the click happened to a cluster
  972 + or a vertex. Edges are not interactable (yet)
  973 + """
  974 + def get_clicked_id(self, event, clusters = False):
  975 + #Get the framebuffer coordinates of the click
  976 + coord = self.transforms.get_transform('canvas', 'framebuffer').map(event.pos)
  977 +
  978 + #get the framebuffer where each element is rendered as a unique color
  979 + size = self.size;
  980 + self.fbo = gloo.FrameBuffer(color=gloo.RenderBuffer(size[::-1]), depth=gloo.RenderBuffer(size[::-1]))
  981 + buff = gloo.read_pixels((0,0,self.physical_size[0], self.physical_size[1]))
  982 + #imsave("test_ori.png", buff)
  983 + self.fbo.activate()
  984 + if clusters == False:
  985 + self.program['u_picking'] = True
  986 + clear(color='white', depth=True)
  987 + self.program.draw('points')
  988 + buff = gloo.read_pixels((0,0,self.physical_size[0], self.physical_size[1]))
  989 + #imsave("test.png", buff)
  990 +
  991 + #return to the original state
  992 + self.fbo.deactivate()
  993 + self.program['u_picking'] = False
  994 + else:
  995 + self.program_s['u_picking'] = True
  996 + clear(color='white', depth=True)
  997 + self.program_s.draw('triangles', indices=self.index_s)
  998 + buff = gloo.read_pixels((0,0,self.physical_size[0], self.physical_size[1]))
  999 + #imsave("test.png", buff)
  1000 +
  1001 + #return to the original state
  1002 + self.fbo.deactivate()
  1003 + self.program_s['u_picking'] = False
  1004 +
  1005 + #print(buff[self.physical_size[1]-int(coord[1]), int(coord[0])])
  1006 +
  1007 + #Get the color under the click.
  1008 + #Keep in mind that the buff is y, x
  1009 + #And 0,0 is in the top RIGHT corner.
  1010 + #IGNORE THE DOCUMENTATION
  1011 + color = np.multiply(buff[self.physical_size[1]-int(coord[1]), int(coord[0])], 1/255.0)
  1012 + #if (tuple(color) not in self.color_dict):
  1013 + # print("clicked on nothing")
  1014 + #else:
  1015 + # print(self.color_dict[tuple(color)])
  1016 +
  1017 + #reset the original buffer
  1018 + self.update()
  1019 +
  1020 + #Return the element under the click.
  1021 + if clusters == False:
  1022 + if(tuple(color) not in self.color_dict):
  1023 + return None
  1024 + else:
  1025 + return self.color_dict[tuple(color)]
  1026 + else:
  1027 + if(tuple(color) not in self.cluster_dict):
  1028 + return None
  1029 + else:
  1030 + return self.cluster_dict[tuple(color)]
  1031 +
  1032 +
  1033 + """
  1034 + Top level handle-mouse presee event for either left or right click
  1035 + """
  1036 + def on_mouse_press(self, event):
  1037 +
  1038 + def update_view():
  1039 + self.location = event.pos
  1040 + self.program['u_view'] = self.view
  1041 + self.program_e['u_view'] = self.view
  1042 + self.program_s['u_view'] = self.view
  1043 + self.program_e_s['u_view'] = self.view
  1044 + self.down = True
  1045 +
  1046 +
  1047 +# if(event.button == 2):
  1048 +## menu = QtWidgets.QMenu(self.parent)
  1049 +## NS = menu.addAction('Node Size')
  1050 +## NC = menu.addAction('Node Color')
  1051 +## action = menu.exec_(self.parent.globalPos())
  1052 +## if action == NS:
  1053 +# print("right_click")
  1054 +# #if menu.exec_(event.globalPos()):
  1055 +# # print(item.text())
  1056 + if(event.button == 1):
  1057 + if(self.view[0][0] > 0.0010):
  1058 + c_id = self.get_clicked_id(event)
  1059 + if(c_id != None):
  1060 + self.original_point = G.vertex_properties["pos"][G.vertex(c_id)]
  1061 + self.location = event.pos
  1062 + self.moving = True
  1063 + self.down = True
  1064 + self.c_id = [c_id]
  1065 + else:
  1066 + update_view()
  1067 + #print("Clicked on:", event.pos)
  1068 + else:
  1069 + c_id = self.get_clicked_id(event, True)
  1070 + print(c_id)
  1071 + if(c_id != None):
  1072 + self.original_point = self.cluster_pos[c_id]
  1073 + self.location = event.pos
  1074 + self.moving = True
  1075 + self.down = True
  1076 + self.c_id = [c_id]
  1077 + self.moving_cluster = True
  1078 + else:
  1079 + update_view()
  1080 +
  1081 +
  1082 + """
  1083 + Handles the double click event that it responsible for path selection.
  1084 + Generates paths our of consecutive paths out of the selected vertices.
  1085 + """
  1086 + def on_mouse_double_click(self, event):
  1087 + def update_vbo(self):
  1088 + self.vbo = gloo.VertexBuffer(self.data)
  1089 + self.program.bind(self.vbo)
  1090 + self.update()
  1091 +
  1092 + def add_to_path(self, source, target):
  1093 + vl, el = nwt.gt.graph_tool.topology.shortest_path(G, G.vertex(source), G.vertex(target), weights=G.edge_properties["av_radius"])
  1094 + for v in vl:
  1095 + if(int(v) not in self.path):
  1096 + G.vertex_properties["selection"][v] = 2.0
  1097 + self.data['a_selection'][int(v)] = 2.0
  1098 + if(int(v) not in self.full_path):
  1099 + self.full_path.append(int(v))
  1100 +
  1101 + if (event.button == 1):
  1102 + if(self.view[0][0] > 0.0010):
  1103 + c_id = self.get_clicked_id(event)
  1104 + if(c_id != None):
  1105 + #check whether this is the first node to be selected
  1106 + if(self.pathing == False):
  1107 + #if it is, select that node and turn the pathing variable on.
  1108 + G.vertex_properties["selection"][G.vertex(c_id)] = 1.0
  1109 + self.pathing = True
  1110 + if(c_id not in self.path):
  1111 + self.path.append(c_id)
  1112 + self.data['a_selection'][c_id] = 1.0
  1113 + update_vbo(self)
  1114 + print("I turned on the first node")
  1115 + else:
  1116 + if(G.vertex_properties["selection"][G.vertex(c_id)] == 1.0):
  1117 + G.vertex_properties["selection"][G.vertex(c_id)] = 0.0
  1118 + self.path.remove(c_id)
  1119 + self.data['a_selection'][c_id] = 0.0
  1120 + update_vbo(self)
  1121 + print("I turned off a node")
  1122 + elif(G.vertex_properties["selection"][G.vertex(c_id)] == 0.0):
  1123 + G.vertex_properties["selection"][G.vertex(c_id)] = 1.0
  1124 + if(c_id not in self.path):
  1125 + self.path.append(c_id)
  1126 + self.data['a_selection'][c_id] = 1.0
  1127 + update_vbo(self)
  1128 + print("I turned on a node")
  1129 + if(len(self.path) >= 2):
  1130 + for i in range(len(self.path)-1):
  1131 + add_to_path(self, self.path[i], self.path[i+1])
  1132 + update_vbo(self)
  1133 + #THIS IS WHERE I LEFT IT OFF.
  1134 + if(np.sum(G.vertex_properties["selection"].get_array()) == 0):
  1135 + self.pathing = False
  1136 +
  1137 +
  1138 +
  1139 +# elif(np.sum(self.G.vertex_properties["selection"].get_array()) :
  1140 +# self.G.vertex_properties["selection"][self.G.vertex(c_id)] == False
  1141 + print("clicked on: ", c_id, " ", self.path)
  1142 +
  1143 + """
  1144 + Resets the variables that are used during the pressdown and move events
  1145 + """
  1146 + def on_mouse_release(self, event):
  1147 + self.down = False
  1148 + self.moving = False
  1149 + self.moving_cluster = False
  1150 + self.c_id = []
  1151 + #self.location = event.pos
  1152 + #print("Clicked off:", event.pos)
  1153 +
  1154 + """
  1155 + used during the drag evern to update the position of the clusters
  1156 + """
  1157 + def update_cluster_position(self, G, pos, offset, c_id):
  1158 + v_pos = G.vertex_properties["pos"].get_2d_array(range(3)).T
  1159 + vertices = np.argwhere(self.labels == c_id)
  1160 + for v in range(vertices.shape[0]):
  1161 + idx = vertices[v][0]
  1162 + v_pos[idx][0] = v_pos[idx][0] + offset[0]
  1163 + v_pos[idx][1] = v_pos[idx][1] + offset[1]
  1164 + v_pos[idx][2] = v_pos[idx][2] + offset[2]
  1165 + self.data['a_position'][idx] = np.asarray([v_pos[idx][0], v_pos[idx][1], v_pos[idx][2]], dtype = np.float32)
  1166 + #update the edge data by finding all edges connected to the vertex
  1167 + vtx = self.G.vertex(idx)
  1168 + for e in vtx.all_edges():
  1169 + d = np.subtract(G.vertex_properties["pos"][e.source()], G.vertex_properties["pos"][e.target()])
  1170 + d_norm = d[0:2]
  1171 + d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2))
  1172 + norm = np.zeros((2,), dtype=np.float32)
  1173 + norm[0] = d_norm[1]
  1174 + norm[1] = d_norm[0]*-1
  1175 + if (int(e.source()), int(e.target())) in self.edge_dict.keys():
  1176 + index = int(self.edge_dict[int(e.source()), int(e.target())])
  1177 + if vtx == int(e.source()):
  1178 + self.line_data['a_position'][index*4] = v_pos[idx]
  1179 + self.line_data['a_position'][index*4+2] = v_pos[idx]
  1180 + self.line_data['a_normal'][index*4] = norm
  1181 + self.line_data['a_normal'][index*4+2] = -norm
  1182 + self.line_data['a_normal'][index*4+1] = norm
  1183 + self.line_data['a_normal'][index*4+3] = -norm
  1184 + elif vtx == int(e.target()):
  1185 + self.line_data['a_position'][index*4+1] = v_pos[idx]
  1186 + self.line_data['a_position'][index*4+3] = v_pos[idx]
  1187 + self.line_data['a_normal'][index*4] = norm
  1188 + self.line_data['a_normal'][index*4+2] = -norm
  1189 + self.line_data['a_normal'][index*4+1] = norm
  1190 + self.line_data['a_normal'][index*4+3] = -norm
  1191 + else:
  1192 + index = int(self.edge_dict[int(e.target()), int(e.source())])
  1193 + if vtx == int(e.target()):
  1194 + self.line_data['a_position'][index*4] = v_pos[idx]
  1195 + self.line_data['a_position'][index*4+2] = v_pos[idx]
  1196 + self.line_data['a_normal'][index*4] = norm
  1197 + self.line_data['a_normal'][index*4+2] = -norm
  1198 + self.line_data['a_normal'][index*4+1] = norm
  1199 + self.line_data['a_normal'][index*4+3] = -norm
  1200 + elif vtx == int(e.source()):
  1201 + self.line_data['a_position'][index*4+1] = v_pos[idx]
  1202 + self.line_data['a_position'][index*4+3] = v_pos[idx]
  1203 + self.line_data['a_normal'][index*4] = norm
  1204 + self.line_data['a_normal'][index*4+2] = -norm
  1205 + self.line_data['a_normal'][index*4+1] = norm
  1206 + self.line_data['a_normal'][index*4+3] = -norm
  1207 +
  1208 +
  1209 + G.vertex_properties["pos"] = G.new_vertex_property("vector<double>", vals = v_pos)
  1210 + index = 4*c_id
  1211 + #generate the vertex filter for this cluster
  1212 + vfilt = np.zeros([G.num_vertices(), 1], dtype="bool")
  1213 + vfilt[np.argwhere(self.labels == c_id)] = 1
  1214 + vfilt_prop = G.new_vertex_property("bool", vals = vfilt)
  1215 + G.set_vertex_filter(vfilt_prop)
  1216 +
  1217 + #get the filtered properties
  1218 + g = nwt.gt.Graph(G, prune=True, directed=False)
  1219 + p, v = self.gen_cluster_coords(pos, np.sum(g.vertex_properties['degree'].get_array()))
  1220 + self.clusters['a_position'][index:index+4] = np.asarray(p, dtype=np.float32)
  1221 + self.clusters['a_value'][index:index+4] = np.asarray(v, dtype=np.float32)
  1222 + G.clear_filters()
  1223 + self.cluster_pos[c_id] = pos
  1224 + self.original_point = pos
  1225 +
  1226 +
  1227 + """
  1228 + function that handles the mouse move event in a way that depends on a set
  1229 + of variables: state of the mouse button, the type of object selected and
  1230 + the number of objects.
  1231 + """
  1232 + def on_mouse_move(self, event):
  1233 + if(self.down == True):
  1234 + if(self.moving == True and self.moving_cluster == False):
  1235 + if(len(self.c_id) < 2):
  1236 + #Project into GLSpace and get before and after move coordinates
  1237 + coord = self.transforms.get_transform('canvas', 'render').map(self.location)[:2]
  1238 + coord2 = self.transforms.get_transform('canvas', 'render').map(event.pos)[:2]
  1239 + cur_pos = G.vertex_properties["pos"][G.vertex(self.c_id[0])]
  1240 + #print(cur_pos, " Before")
  1241 +
  1242 + #Adjust the position of the node based on the current view matrix.
  1243 + cur_pos[0] = cur_pos[0] - (coord[0]-coord2[0])/self.view[0][0]
  1244 + cur_pos[1] = cur_pos[1] - (coord[1]-coord2[1])/self.view[0][0]
  1245 +
  1246 + #print(cur_pos, " After")
  1247 + #Upload the changed data.
  1248 + G.vertex_properties["pos"][G.vertex(self.c_id[0])] = cur_pos
  1249 + self.data['a_position'][self.c_id[0]] = cur_pos
  1250 +
  1251 + #update the edge data by finding all edges connected to the vertex
  1252 + v = self.G.vertex(self.c_id[0])
  1253 + for e in v.all_edges():
  1254 + d = np.subtract(G.vertex_properties["pos"][e.source()], G.vertex_properties["pos"][e.target()])
  1255 + d_norm = d[0:2]
  1256 + d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2))
  1257 + norm = np.zeros((2,), dtype=np.float32)
  1258 + norm[0] = d_norm[1]
  1259 + norm[1] = d_norm[0]*-1
  1260 + if (int(e.source()), int(e.target())) in self.edge_dict.keys():
  1261 + idx = int(self.edge_dict[int(e.source()), int(e.target())])
  1262 + if self.c_id[0] == int(e.source()):
  1263 + self.line_data['a_position'][idx*4] = cur_pos
  1264 + self.line_data['a_position'][idx*4+2] = cur_pos
  1265 + self.line_data['a_normal'][idx*4] = norm
  1266 + self.line_data['a_normal'][idx*4+2] = -norm
  1267 + self.line_data['a_normal'][idx*4+1] = norm
  1268 + self.line_data['a_normal'][idx*4+3] = -norm
  1269 + elif self.c_id[0] == int(e.target()):
  1270 + self.line_data['a_position'][idx*4+1] = cur_pos
  1271 + self.line_data['a_position'][idx*4+3] = cur_pos
  1272 + self.line_data['a_normal'][idx*4] = norm
  1273 + self.line_data['a_normal'][idx*4+2] = -norm
  1274 + self.line_data['a_normal'][idx*4+1] = norm
  1275 + self.line_data['a_normal'][idx*4+3] = -norm
  1276 + else:
  1277 + idx = int(self.edge_dict[int(e.target()), int(e.source())])
  1278 + if self.c_id[0] == int(e.target()):
  1279 + self.line_data['a_position'][idx*4] = cur_pos
  1280 + self.line_data['a_position'][idx*4+2] = cur_pos
  1281 + self.line_data['a_normal'][idx*4] = norm
  1282 + self.line_data['a_normal'][idx*4+2] = -norm
  1283 + self.line_data['a_normal'][idx*4+1] = norm
  1284 + self.line_data['a_normal'][idx*4+3] = -norm
  1285 + elif self.c_id[0] == int(e.source()):
  1286 + self.line_data['a_position'][idx*4+1] = cur_pos
  1287 + self.line_data['a_position'][idx*4+3] = cur_pos
  1288 + self.line_data['a_normal'][idx*4] = norm
  1289 + self.line_data['a_normal'][idx*4+2] = -norm
  1290 + self.line_data['a_normal'][idx*4+1] = norm
  1291 + self.line_data['a_normal'][idx*4+3] = -norm
  1292 + #self.line_data['a_position'][self.c_id[0]] =
  1293 + self.vbo = gloo.VertexBuffer(self.data)
  1294 + self.vbo_line = gloo.VertexBuffer(self.line_data)
  1295 + #Bind the buffer and redraw.
  1296 + self.program.bind(self.vbo)
  1297 + self.program_e.bind(self.vbo_line)
  1298 + #self.program.draw('points')
  1299 + self.location = event.pos
  1300 + self.update()
  1301 + elif(self.moving == True and self.moving_cluster == True):
  1302 + if(len(self.c_id) < 2):
  1303 + #Project into GLSpace and get before and after move coordinates
  1304 + coord = self.transforms.get_transform('canvas', 'render').map(self.location)[:2]
  1305 + coord2 = self.transforms.get_transform('canvas', 'render').map(event.pos)[:2]
  1306 + cur_pos = np.zeros(self.cluster_pos[self.c_id[0]].shape, dtype = np.float32)
  1307 + offset = np.zeros(self.cluster_pos[self.c_id[0]].shape, dtype = np.float32)
  1308 + cur_pos[0] = self.cluster_pos[self.c_id[0]][0]
  1309 + cur_pos[1] = self.cluster_pos[self.c_id[0]][1]
  1310 + cur_pos[2] = self.cluster_pos[self.c_id[0]][2]
  1311 + offset[0] = self.cluster_pos[self.c_id[0]][0]
  1312 + offset[1] = self.cluster_pos[self.c_id[0]][1]
  1313 + offset[2] = self.cluster_pos[self.c_id[0]][2]
  1314 +# ofset = self.cluster_pos[self.c_id[0]]
  1315 +
  1316 + #Adjust the position of the node based on the current view matrix.
  1317 + offset[0] = self.original_point[0] - cur_pos[0] - (coord[0]-coord2[0])/self.view[0][0]
  1318 + offset[1] = self.original_point[1] - cur_pos[1] - (coord[1]-coord2[1])/self.view[0][0]
  1319 + cur_pos[0] = cur_pos[0] - (coord[0]-coord2[0])/self.view[0][0]
  1320 + cur_pos[1] = cur_pos[1] - (coord[1]-coord2[1])/self.view[0][0]
  1321 +
  1322 + self.update_cluster_position(G, cur_pos, offset, self.c_id[0])
  1323 + #self.original_point = cur_pos
  1324 + self.vbo = gloo.VertexBuffer(self.data)
  1325 + self.vbo_line = gloo.VertexBuffer(self.line_data)
  1326 + #Bind the buffer and redraw.
  1327 + self.program.bind(self.vbo)
  1328 + self.program_e.bind(self.vbo_line)
  1329 + #self.program.draw('points')
  1330 + self.location = event.pos
  1331 + if(self.subgraphs):
  1332 + self.vbo_s = gloo.VertexBuffer(self.clusters)
  1333 + self.program_s.bind(self.vbo_s)
  1334 + self.update_cluster_line_vbo()
  1335 + self.update()
  1336 +
  1337 +
  1338 + else:
  1339 + #print("Mouse at:", event.pos)
  1340 + #new_model = np.eye(4, dtype=np.float32)
  1341 + coord = self.transforms.get_transform('canvas', 'render').map(self.location)[:2]
  1342 + coord2 = self.transforms.get_transform('canvas', 'render').map(event.pos)[:2]
  1343 + self.translate[0] += (coord[0]-coord2[0])/self.view[0][0]
  1344 + self.translate[1] += (coord[1]-coord2[1])/self.view[1][1]
  1345 + #self.view[3][0] = self.view[3][0]-(self.location[0]-event.pos[0])/10000.0
  1346 + #self.view[3][1] = self.view[3][1]+(self.location[1]-event.pos[1])/10000.0
  1347 +
  1348 + self.view = np.matmul(translate((self.translate[0], self.translate[1], 0)), scale((self.scale[0], self.scale[1], 0)))
  1349 +
  1350 + self.program['u_view'] = self.view
  1351 + self.program_e['u_view'] = self.view
  1352 + self.program_s['u_view'] = self.view
  1353 + self.program_e_s['u_view'] = self.view
  1354 + self.location = event.pos
  1355 + self.update()
  1356 +
  1357 + """
  1358 + Handles the mouse wheel zoom event.
  1359 + """
  1360 + def on_mouse_wheel(self, event):
  1361 +
  1362 + #print(self.view)
  1363 + #TO_DO IMPLEMENT ZOOM TO CURSOR
  1364 + #self.view[3][0] = self.view[3][0]-event.pos[0]/10000.0
  1365 + #self.view[3][1] = self.view[3][1]-event.pos[1]/10000.0
  1366 + #print(self.scale[0] , self.scale[0]*event.delta[1]*0.05)
  1367 + self.scale[0] = self.scale[0] + self.scale[0]*event.delta[1]*0.05
  1368 + self.scale[1] = self.scale[1] + self.scale[1]*event.delta[1]*0.05
  1369 +
  1370 + self.view = np.matmul(translate((self.translate[0], self.translate[1], 0)),
  1371 + scale((self.scale[0], self.scale[1], 0)))
  1372 +
  1373 + #self.view[0][0] = self.view[0][0]+self.view[0][0]*event.delta[1]*0.05
  1374 + #self.view[1][1] = self.view[1][1]+self.view[1][1]*event.delta[1]*0.05
  1375 + #print(self.view[0][0], " ",self.view[1][1])
  1376 + #print(self.view)
  1377 + self.program['u_view'] = self.view
  1378 + self.program_e['u_view'] = self.view
  1379 + self.program_s['u_view'] = self.view
  1380 + self.program_e_s['u_view'] = self.view
  1381 + #print(event.delta[1])
  1382 + self.update()
... ...
GraphWidget.py 0 → 100644
  1 +++ a/GraphWidget.py
  1 +#!/usr/bin/env python3
  2 +# -*- coding: utf-8 -*-
  3 +"""
  4 +Created on Mon Aug 5 15:42:19 2019
  5 +
  6 +@author: pavel
  7 +"""
  8 +
  9 +from GraphCanvas import GraphCanvas
  10 +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
  11 +import network_dep as nwt
  12 +
  13 +
  14 +"""
  15 +Initializes the entire QTlayout and sets the mouse press events.
  16 +These are connected to the slots such that each is processes by this class
  17 +and the class it wraps.
  18 +"""
  19 +class GraphWidget(QtGui.QWidget):
  20 + def __init__(self):
  21 + super(GraphWidget, self).__init__()
  22 + box = QtGui.QVBoxLayout(self)
  23 + self.resize(500,500)
  24 + self.setLayout(box)
  25 + self.canvas = GraphCanvas()
  26 + #self.canvas.create_native()
  27 + box.addWidget(self.canvas.native)
  28 + self.color = True
  29 + self.use_3D = False
  30 + self.camera = [0,0,0]
  31 +
  32 + self.canvas.events.mouse_press.connect(self.on_mouse_press)
  33 + self.canvas.events.mouse_release.connect(self.on_mouse_release)
  34 + self.canvas.events.mouse_move.connect(self.on_mouse_move)
  35 + self.canvas.events.mouse_double_click.connect(self.on_mouse_double_click)
  36 +
  37 + #self.show()
  38 +
  39 + """
  40 + Handles the right click mouse event at the QWidget level.
  41 + Depending on where the mouse button in clicked and the current zoom level,
  42 + the function gets the unique id of the node, cluster or background under the
  43 + cursor and opens a context menu based on the ID.
  44 +
  45 + The context menu level actions are also coded in this section of the code.
  46 + """
  47 + def on_mouse_press(self, event):
  48 + if event.button == 2:
  49 + if self.canvas.view[0][0] >= 0.0010:
  50 + c_id = self.canvas.get_clicked_id(event)
  51 + else:
  52 + c_id = self.canvas.get_clicked_id(event, clusters=True)
  53 + #right click
  54 + if(c_id == None):
  55 + menu = QtWidgets.QMenu(self)
  56 + NS = menu.addAction('Node Size')
  57 +
  58 + #tmp = menu.addAction('temp')
  59 + tmp = menu.addMenu('Node Color')
  60 + vertex_props = self.canvas.G.vertex_properties.keys()
  61 + vertex_actions = []
  62 + for i in range(len(vertex_props)):
  63 + if(vertex_props[i] != "map" or vertex_props[i] != "RGBA"):
  64 + vertex_actions.append(tmp.addAction(vertex_props[i]))
  65 + #tmp.addAction("Cluster")
  66 + #NC = menu.addAction('Node Color')
  67 + #EXP = menu.addAction('Export VTK')
  68 + #EXP_adv = menu.addAction('Export VTK Advanced')
  69 + NL = menu.addAction('New Layout')
  70 + action = menu.exec_(event.native.globalPos())
  71 + print(action)
  72 + for i in range(len(vertex_actions)):
  73 + if action == vertex_actions[i]:
  74 + if vertex_props[i] == "clusters":
  75 + self.canvas.color_vertices(self.canvas.G, vertex_props[i], dtype=True)
  76 + else:
  77 + self.canvas.color_vertices(self.canvas.G, vertex_props[i])
  78 + print(vertex_props[i])
  79 + if action == NS:
  80 + if self.use_3D == False:
  81 + self.use_3D = True
  82 + else:
  83 + self.use_3D = False
  84 + self.canvas.size_vertices(self.canvas.G, 'degree' )
  85 + self.canvas.color_vertices(self.canvas.G)
  86 + #if action == NC:
  87 + # self.canvas.color_vertices(self.canvas.G, not self.color)
  88 + # self.color = not self.color
  89 +# if action == EXP:
  90 +# nwt.Network.write_vtk(self.canvas.G, "./nwt.vtk")
  91 +# if action == EXP_adv:
  92 +# nwt.Network.write_vtk(self.canvas.G, "./nwt_median_binned.vtk")
  93 +# nwt.Network.write_vtk(self.canvas.G, "./nwt_median_non_binned.vtk", binning=False)
  94 +# nwt.Network.write_vtk(self.canvas.G, "./nwt_points_wise_binned.vtk", camera=self.camera, binning = True)
  95 +# nwt.Network.write_vtk(self.canvas.G, "./nwt_points_wise_non_binned.vtk", camera=self.camera, binning = False)
  96 + if action == tmp:
  97 + print("Stuff")
  98 + #self.cb = QtWidgets.QComboBox()
  99 + #vertex_props = self.canvas.G.vertex_properties.keys()
  100 + #for i in range(len(vertex_props)):
  101 + # self.cb.addItem(vertex_props[i])
  102 + #self.cb.currentIndexChanged.connect(self.selection_change)
  103 + #self.cb.show()
  104 + if action == NL:
  105 + self.canvas.G = nwt.Network.gen_new_fd_layout(self.canvas.G)
  106 + self.canvas.gen_vertex_vbo(self.canvas.G)
  107 + #self.canvas.set_data(self.canvas.G, self.canvas.bbl, self.canvas.bbu)
  108 + self.canvas.expand_clusters(self.canvas.G, self.canvas.n_c)
  109 +
  110 + #self.canvas.size_vertices(self.canvas.G, 'degree_volume')
  111 + else:
  112 + if self.canvas.view[0][0] >= 0.0010:
  113 + menu = QtWidgets.QMenu(self)
  114 + NS = menu.addAction('Display Node Info')
  115 + action = menu.exec_(event.native.globalPos())
  116 + if action == NS:
  117 + print("Display Node Info")
  118 + else:
  119 + menu = QtWidgets.QMenu(self)
  120 + MnOC = menu.addAction('Minimize Other Clusters')
  121 + RA = menu.addAction('Reset Alpha')
  122 + action = menu.exec_(event.native.globalPos())
  123 + if action == MnOC:
  124 + new_Graph = self.canvas.focus_on_cluster(self.canvas.G, c_id)
  125 + self.test.draw_new(new_Graph)
  126 + if action == RA:
  127 + self.canvas.reset_alpha(c_id[0])
  128 +
  129 +
  130 +# def selection_change(self, i):
  131 +# self.canvas.size_vertices(self.canvas.G, self.cb.currentText())
  132 +
  133 +
  134 + """
  135 + Handles the mouse double click event.
  136 + Pass-through function.
  137 + """
  138 + def on_mouse_double_click(self, event):
  139 + #print("in applet")
  140 + n = 0
  141 +
  142 + """
  143 + Handles the mouse release event.
  144 + Pass-through function.
  145 + """
  146 + def on_mouse_release(self, event):
  147 + n = 1
  148 +
  149 + """
  150 + Handles the mouse move event.
  151 + Pass-through function.
  152 + """
  153 + def on_mouse_move(self, event):
  154 + n = 2
  155 +
  156 + """
  157 + Internal function that interacts with the tube visualization.
  158 + """
  159 + def set_g_view(self, test):
  160 + self.test = test
  161 +
  162 + """
  163 + Function to handle the pyqt Slot that receives the interacted-with signal
  164 + from the Tube visualization. The signal sends the camera position in 3D coordinates
  165 + every time the camera is moved.
  166 + """
  167 + @QtCore.pyqtSlot(float, float, float)
  168 + def sigUpdate(self, x, y, z):
  169 + self.camera = [x,y,z]
  170 + if(self.use_3D == True):
  171 + self.camera = [x,y,z]
  172 + self.canvas.vertexSizeFromDistance(self.canvas.G, [x,y,z])
  173 + self.canvas.vertexAlphaFromDistance(self.canvas.G, [x,y,z])
  174 + #print("got signal", x, y, z)
  175 +
  176 + """
  177 + Initialization method that connects the slot to the function that handles
  178 + the information being sent.
  179 + """
  180 + def connect(self, signal_object):
  181 + signal_object.sigUpdate.connect(self.sigUpdate)
  182 +
  183 +
  184 +
... ...
GuiVisPy_tube.py 0 → 100644
  1 +++ a/GuiVisPy_tube.py
  1 +#!/usr/bin/env python3
  2 +# -*- coding: utf-8 -*-
  3 +"""
  4 +Created on Thu Jan 31 15:29:40 2019
  5 +
  6 +@author: pavel
  7 +`"""
  8 +
  9 +from vispy import app
  10 +
  11 +
  12 +import network_dep as nwt
  13 +
  14 +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
  15 +import pyqtgraph as pg
  16 +import numpy as np
  17 +import math
  18 +
  19 +from mpl_toolkits.mplot3d import Axes3D
  20 +import matplotlib
  21 +import matplotlib.pyplot as plt
  22 +matplotlib.use('Qt5Agg')
  23 +
  24 +from GraphWidget import GraphWidget
  25 +from TubeWidget import TubeWidget
  26 +
  27 +
  28 +#set the backend. for different versions of PyQt
  29 +app.use_app(backend_name='PyQt5')
  30 +
  31 +#Define a top level application
  32 +appMain = QtGui.QApplication([])
  33 +
  34 +##Define a toplevel Widget
  35 +top = QtGui.QWidget()
  36 +top.resize(900, 900)
  37 +
  38 +
  39 +hist = pg.PlotWidget()
  40 +#fibers = FiberView()
  41 +fibers = TubeWidget()
  42 +fibers.canvas.create_native()
  43 +#fibers = gl.GLViewWidget()
  44 +
  45 +#plt = hist.addPlot()
  46 +
  47 +layout = QtGui.QGridLayout()
  48 +graph = GraphWidget()
  49 +graph.canvas.create_native()
  50 +
  51 +graph.connect(fibers)
  52 +
  53 +#initialize the layout
  54 +layout.addWidget(graph, 0, 0, 2, 3)
  55 +layout.addWidget(fibers, 0, 2, 2, 3)
  56 +layout.setColumnStretch(0, 2)
  57 +layout.setRowStretch(0, 2)
  58 +layout.setColumnStretch(2, 1)
  59 +layout.setRowStretch(0, 1)
  60 +
  61 +top.setLayout(layout)
  62 +top.show()
  63 +
  64 +
  65 +def draw_histogram(G, value):
  66 + vals = G.edge_properties[value].get_array()
  67 + y, x = np.histogram(vals,40)
  68 + hist.plot(x,y, stepMode=True, fillLevel=0, brush=(0,0,255,150))
  69 +
  70 +def load_nwt(filepath):
  71 + net = nwt.Network(filepath)
  72 + G = net.createFullGraph_gt()
  73 + G = net.filterDisconnected(G)
  74 + #G = net.filterFullGraph_gt(G, erode=True)
  75 + #G = net.filterFullGraph_gt(G, erode=True)
  76 + #G = net.gen_new_fd_layout(G)
  77 + #G = net.filterBorder(G)
  78 + #nwt.Network.saveGraph_gt(nwt, G, "FilteredNetwork_JACK_3.nwt")
  79 + color = np.zeros(4, dtype = np.double)
  80 + color = [0.0, 1.0, 0.0, 1.0]
  81 + G.edge_properties["RGBA"] = G.new_edge_property("vector<double>", val=color)
  82 + color = [1.0, 0.0, 0.0, 0.9]
  83 + G.vertex_properties["RGBA"] = G.new_vertex_property("vector<double>", val=color)
  84 + bbl, bbu = net.aabb()
  85 +
  86 +
  87 + return G, bbl, bbu
  88 +
  89 +G, bbl, bbu = load_nwt("/home/pavel/Documents/Python/GraphGuiQt/network_4.nwt")
  90 +#nwt.Network.write_vtk(G)
  91 +
  92 +#G, bbl, bbu = load_nwt("/home/pavel/Documents/Python/GraphGuiQt/network_test_251_1kx3_short_NEW.nwt")
  93 +#ret = nwt.Network.get_affinity_matrix(G, "length")
  94 +node_image = QtGui.QImage("/home/pavel/Documents/Python/GraphGuiQt/node_tex.jpg")
  95 +node_tex = QtGui.QBitmap.fromImage(node_image)
  96 +print(node_tex.depth())
  97 +center = (bbu-bbl)/2.0
  98 +#fibers.opts['distance'] = 5
  99 +# item = NodeItem(G)
  100 +# graph.addItem(item)
  101 +draw_histogram(G, "length")
  102 +graph.canvas.set_data(G, bbl, bbu)
  103 +fibers.canvas.set_data(G, bbl, bbu, 16)
  104 +#fibers.draw_all(G, center, fibers, graph, node_tex)
  105 +graph.set_g_view(fibers)
  106 +## Start Qt event loop unless running in interactive mode.
  107 +if __name__ == '__main__':
  108 + import sys
  109 + if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
  110 + QtGui.QApplication.instance().exec_()
  111 +
  112 +
... ...
TubeCanvas.py 0 → 100644
  1 +++ a/TubeCanvas.py
  1 +#!/usr/bin/env python3
  2 +# -*- coding: utf-8 -*-
  3 +"""
  4 +Created on Mon Aug 5 15:56:47 2019
  5 +
  6 +@author: pavel
  7 +"""
  8 +
  9 +"""
  10 + Class that extends the vispy SceneCanvas to draw 3D tubes
  11 +"""
  12 +
  13 +from vispy import gloo, scene
  14 +from vispy.gloo import set_viewport, set_state, clear, set_blend_color
  15 +from vispy.util.transforms import perspective, translate, rotate, scale
  16 +import vispy.gloo.gl as glcore
  17 +from vispy.util.quaternion import Quaternion
  18 +
  19 +import numpy as np
  20 +import math
  21 +import network_dep as nwt
  22 +
  23