#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Mon Aug 5 15:43:59 2019 @author: pavel The GraphCanvas class that extends the scene class in vispy in order to draw the graph object. This class is wrapped in a QTcanvas. """ from vispy import gloo, scene from vispy.gloo import set_viewport, set_state, clear, set_blend_color, context from vispy.util.transforms import perspective, translate, rotate, scale import vispy.gloo.gl as glcore import numpy as np import math import network_dep as nwt #import the graph shaders. from graph_shaders import vert, frag, vs, fs from subgraph_shaders import vert_s, frag_s, vs_s, fs_s DEBUG = False #class storing the path. a vertex in the path is defined as a vertex idx #and a list of all vertices required to reach the next point in the path class path_point: #Init an empty vertex with no path def __init__(self, idx): self.idx = idx self.v_path = [] self.e_path = [] #Remove all future path vertices attached to this vertex def clear_path(self): self.v_path = [] self.e_path = [] # == comparison operator def __eq__(self, other): if self.idx == other.idx: return True else: return False # != comparison operator def __ne__(self, other): if self.idx != other.idx: return True else: return False def __str__(self): print("PathPoint[", self.idx, "], Chain = [", self.v_path, "]") #The graph canvas class that class GraphCanvas(scene.SceneCanvas): """ Initialization method. Generates the 512x512 canvas, makes it available for drawing the fills all the GLSL shaders with dummy data. """ def __init__(self, **kwargs): # Initialize the canvas for real scene.SceneCanvas.__init__(self, size=(512, 512), **kwargs) #Unfreeze the canvas to make dynamic interaction possible self.unfreeze() #initialize all the boolean and dictionary variables. ps = self.pixel_scale self.subgraphs = False self.position = 50, 50 self.down=False; #Dictionaries to store the unique color ID, cluster ID, and edge-to-ID #dictionaries. self.color_dict = {} self.cluster_dict = {} self.edge_dict = {} #Booleans the storage for the current "Path", i.e. edges the user selected. self.pathing = False self.path = [] self.full_path = [] #utility variables used for storing the cluster being moved and all the #nodes and edges that belong to that cluster and move along with it. self.moving = False self.moving_cluster = False self.selection = False n = 10 ne = 10 #Init dummy structures self.uniforms = [('u_graph_size', np.float32, 3)] self.data = np.zeros(n, dtype=[('a_position', np.float32, 3), ('a_fg_color', np.float32, 4), ('a_bg_color', np.float32, 4), ('a_size', np.float32, 1), ('a_linewidth', np.float32, 1), ('a_unique_id', np.float32, 4), ]) self.clusters = np.zeros(n, dtype=[('a_position', np.float32, 3), ('a_bg_color', np.float32, 4), ('a_value', np.float32, 2), ('a_unique_id', np.float32, 4), ]) self.edges = np.random.randint(size=(ne, 2), low=0, high=n-1).astype(np.uint32) self.edges_s = np.random.randint(size=(ne, 4), low=0, high=n-1).astype(np.uint32) self.data['a_position'] = np.hstack((0.25 * np.random.randn(n, 2), np.zeros((n, 1)))) self.data['a_fg_color'] = 0, 0, 0, 1.0 color = np.random.uniform(0.5, 1., (n, 3)) self.data['a_bg_color'] = np.hstack((color, np.zeros((n, 1)))) self.data['a_size'] = np.random.randint(size=n, low=8*ps, high=20*ps) self.data['a_linewidth'] = 8.*ps self.data['a_unique_id'] = np.hstack((color, np.ones((n, 1)))) #self.uniforms['u_graph_size'] = [1.0, 1.0, 1.0] self.translate = [0,0,0] self.scale = [1,1,1] #color = np.random.uniform(0.5, 1., (ne, 3)) #self.linecolor = np.hstack((color, np.ones((ne, 1)))) #color = np.random.uniform(0.5, 1., (ne, 3)) #self.linecolor = np.hstack((color, np.ones((ne, 1)))) self.u_antialias = 1 #init dummy vertex and index buffers. self.vbo = gloo.VertexBuffer(self.data) self.vbo_s = gloo.VertexBuffer(self.clusters) #Need to initialize thick lines. self.index = gloo.IndexBuffer(self.edges) self.index_s = gloo.IndexBuffer(self.edges_s) #Set the view matrices. self.view = np.eye(4, dtype=np.float32) self.model = np.eye(4, dtype=np.float32) self.projection = np.eye(4, dtype=np.float32) #init shaders used for vertices of the full graph. self.program = gloo.Program(vert, frag) self.program.bind(self.vbo) self.program['u_size'] = 1 self.program['u_antialias'] = self.u_antialias self.program['u_model'] = self.model self.program['u_view'] = self.view self.program['u_projection'] = self.projection self.program['u_graph_size'] = [1.0, 1.0, 1.0] self.program['u_picking'] = False #init shades used for the edges in the graph self.program_e = gloo.Program(vs, fs) self.program_e['u_size'] = 1 self.program_e['u_model'] = self.model self.program_e['u_view'] = self.view self.program_e['u_projection'] = self.projection #self.program_e['l_color'] = self.linecolor.astype(np.float32) self.program_e.bind(self.vbo) #init shaders used to the vertices in the subgraph graph. self.program_s = gloo.Program(vert_s, frag_s) self.program_s.bind(self.vbo_s) self.program_s['u_model'] = self.model self.program_s['u_view'] = self.view self.program_s['u_projection'] = self.projection self.program_s['u_graph_size'] = [1.0, 1.0, 1.0] self.program_s['u_picking'] = False #init shaders used for the subgraph-edges self.program_e_s = gloo.Program(vs_s, fs_s) self.program_e_s['u_size'] = 1 self.program_e_s['u_model'] = self.model self.program_e_s['u_view'] = self.view self.program_e_s['u_projection'] = self.projection #self.program_e['l_color'] = self.linecolor.astype(np.float32) self.program_e_s.bind(self.vbo_s) #set up the viewport and the gl state. set_viewport(0, 0, *self.physical_size) set_state(clear_color='white', depth_test=True, blend=True, blend_func=('src_alpha', 'one_minus_src_alpha'), depth_func = ('lequal')) """ Function that recolors vertices based on the selected statistic Maps the statisic stored in G to a colormap passed to the function Then updates the necessary color array. """ def color_vertices(self, G, vertex_property, dtype = False, cm = 'plasma'): #if we are visualing the clusters we should use a discrete colormap #otherwise use the passed colormap if dtype == True: G.vertex_properties["RGBA"] = nwt.Network.map_property_to_color(G, G.vertex_properties["clusters"]) else: G.vertex_properties["RGBA"] = nwt.Network.map_property_to_color(G, G.vertex_properties[vertex_property], colormap=cm) #set the color and update the Vertices. self.current_color = vertex_property color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T self.data['a_bg_color'] = color self.vbo = gloo.VertexBuffer(self.data) self.program.bind(self.vbo) #self.program_e.bind(self.vbo) self.update() def update_color_buffers(self): color = self.G.vertex_properties["RGBA"].get_2d_array(range(4)).T self.data['a_bg_color'] = color edges = self.G.get_edges() for e in range(edges.shape[0]): idx = int(4*edges[e][2]) self.line_data['a_fg_color'][idx] = color[edges[e][0]] self.line_data['a_fg_color'][idx+1] = color[edges[e][1]] self.line_data['a_fg_color'][idx+2] = color[edges[e][0]] self.line_data['a_fg_color'][idx+3] = color[edges[e][1]] self.vbo = gloo.VertexBuffer(self.data) self.vbo_line = gloo.VertexBuffer(self.line_data) self.program.bind(self.vbo) self.program_e.bind(self.vbo_line) """ Function takes a graph and a state and sets all vertices and edges to the transparency defined by state """ def make_all_transparent(self, state): for v in self.G.vertices(): temp = self.G.vertex_properties["RGBA"][v] temp[3] = state self.G.vertex_properties["RGBA"][v] = temp for e in self.G.edges(): temp = self.G.edge_properties["RGBA"][e] temp[3] = state self.G.edge_properties["RGBA"][e] = temp self.update_color_buffers() """ Maps a statistic of the vertices based on the size of the canvas to size of the drawn object. """ def size_vertices(self, G, propertymap): size = nwt.Network.map_vertices_to_range(G, [30*self.pixel_scale, 8*self.pixel_scale], propertymap).get_array() self.data['a_size'] = size self.vbo = gloo.VertexBuffer(self.data) self.program.bind(self.vbo) #self.program_e.bind(self.vbo) self.update() """ Function to dim all nodes and edges that do not belong to a cluster chosen in the graph view. Returns a copy of the graph with the alpha channel saved. OPTMIZE HERE: could just return an alpha array to reduce memory usage. """ def focus_on_cluster(self, G, c_id): G_copy = nwt.gt.Graph(G, directed=False) e_color = G_copy.edge_properties["RGBA"].get_2d_array(range(4)).T vertices = np.argwhere(self.labels != c_id) for v in range(vertices.shape[0]): idx = vertices[v][0] vtx = G_copy.vertex(idx) for e in vtx.all_edges(): if (int(e.source()), int(e.target())) in self.edge_dict.keys(): index = int(self.edge_dict[int(e.source()), int(e.target())]) if vtx == int(e.source()): e_color[index][3] = 0.05 elif vtx == int(e.target()): e_color[index][3] = 0.05 else: index = int(self.edge_dict[int(e.target()), int(e.source())]) if vtx == int(e.target()): e_color[index][3] = 0.05 elif vtx == int(e.source()): e_color[index][3] = 0.05 G_copy.edge_properties["RGBA"] = G_copy.new_edge_property("vector", vals = e_color) return G_copy """ Function that sets the size of the vertices based on the distance from the camera. """ def vertexSizeFromDistance(self, G, camera_pos): location = G.vertex_properties["p"].get_2d_array(range(3)).T cam_array = np.zeros(location.shape, dtype=np.float32) len_array = np.zeros(location.shape[0]) offset_array = np.zeros(location.shape, dtype=np.float32) cam_array[:][0:3] = camera_pos offset = [(self.bbu[0]-self.bbl[0])/2, (self.bbu[1]-self.bbl[1])/2, (self.bbu[2]-self.bbl[2])/2] location = location - offset location = location - camera_pos for i in range(location.shape[0]): len_array[i] = np.sqrt(np.power(location[i][0],2) + np.power(location[i][1],2) + np.power(location[i][2],2)) G.vertex_properties['dist_from_camera'] = G.new_vertex_property('float', vals=len_array) self.data['a_size'] = nwt.Network.map_vertices_to_range(G, [1*self.pixel_scale, 60*self.pixel_scale], 'dist_from_camera').get_array() size = nwt.Network.map_vertices_to_range(G, [1.0, 0.5], 'dist_from_camera').get_array() edges = G.get_edges() for e in range(edges.shape[0]): idx = int(4*edges[e][2]) self.line_data['a_linewidth'][idx] = size[edges[e][0]] self.line_data['a_linewidth'][idx+1] = size[edges[e][1]] self.line_data['a_linewidth'][idx+2] = size[edges[e][0]] self.line_data['a_linewidth'][idx+3] = size[edges[e][1]] #self.vbo = gloo.VertexBuffer(self.data) #self.vbo_line = gloo.VertexBuffer(self.line_data) #self.program.bind(self.vbo) #self.program_e.bind(self.vbo_line) #self.update() """ Function that scales the alpha channel of each vertex in the graph based on The distance from the camera. Sometimes needs to be done separetly. """ def vertexAlphaFromDistance(self, G, camera_pos): location = G.vertex_properties["p"].get_2d_array(range(3)).T cam_array = np.zeros(location.shape, dtype=np.float32) len_array = np.zeros(location.shape[0]) #offset_array = np.zeros(location.shape, dtype=np.float32) cam_array[:][0:3] = camera_pos offset = [(self.bbu[0]-self.bbl[0])/2, (self.bbu[1]-self.bbl[1])/2, (self.bbu[2]-self.bbl[2])/2] location = location - offset location = location - camera_pos for i in range(location.shape[0]): len_array[i] = np.sqrt(np.power(location[i][0],2) + np.power(location[i][1],2) + np.power(location[i][2],2)) test = nwt.Network.map_vertices_to_range(G, [0.0, 1.0], 'dist_from_camera').get_array() color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T for i in range(location.shape[0]): color[i][3] = test[i] G.vertex_properties["RGBA"] = G.new_vertex_property("vector", vals = color) self.data['a_bg_color'] = color edges = G.get_edges() for e in range(edges.shape[0]): idx = int(4*edges[e][2]) self.line_data['a_fg_color'][idx] = color[edges[e][0]] self.line_data['a_fg_color'][idx+1] = color[edges[e][1]] self.line_data['a_fg_color'][idx+2] = color[edges[e][0]] self.line_data['a_fg_color'][idx+3] = color[edges[e][1]] self.vbo = gloo.VertexBuffer(self.data) self.vbo_line = gloo.VertexBuffer(self.line_data) self.program.bind(self.vbo) self.program_e.bind(self.vbo_line) self.update() """ Sets the edge color based on the the cluster the vertices belongs to Propertymap is a VERTEXPROPERTYMAP since the color of the edges is based on the clusters the edges belong to. """ def color_edges(self, G, propertymap="clusters"): if propertymap == "clusters": for e in G.edges(): if G.vertex_properties[propertymap][e.source()] == G.vertex_properties[propertymap][e.target()]: G.edge_properties["RGBA"][e] = G.vertex_properties["RGBA"][e.source()] else: G.edge_properties["RGBA"][e] = [0.0, 0.0, 0.0, 0.8] """ Helper function that generates the framebuffer object that stores the vertices Generates the vertex buffer based on the graph G that is passed to the function Sets the color, generates the graph and subgraph color if necessary. """ def gen_vertex_vbo(self, G): color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T size = nwt.Network.map_vertices_to_range(G, [30*self.pixel_scale, 8*self.pixel_scale], 'degree').get_array() position = G.vertex_properties["pos"].get_2d_array(range(3)).T #for p in range(position.shape[0]): # position[p][0] = position[p][0] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][0] # position[p][1] = position[p][1] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][1] # position[p][2] = position[p][2] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][2] #G.vertex_properties["pos"] = G.new_vertex_property("vector", vals = position) edges = G.get_edges(); edges = edges[:, 0:2] #width = nwt.Network.map_edges_to_range(G, [1*self.pixel_scale, 5*self.pixel_scale], 'volume').get_array() #ecolor = G.edge_properties["RGBA"].get_2d_array(range(4)).T self.data = np.zeros(G.num_vertices(), dtype=[('a_position', np.float32, 3), ('a_fg_color', np.float32, 4), ('a_bg_color', np.float32, 4), ('a_size', np.float32, 1), ('a_linewidth', np.float32, 1), ('a_unique_id', np.float32, 4), ('a_selection', np.float32, 1), ]) #self.edges = edges.astype(np.uint32) self.data['a_position'] = position #fg color is the color of the ring self.data['a_fg_color'] = 0, 0, 0, 1 self.data['a_bg_color'] = color self.data['a_size'] = size self.data['a_linewidth'] = 4.*self.pixel_scale self.data['a_unique_id'] = self.gen_vertex_id(G) self.data['a_selection'] = G.vertex_properties["selection"].get_array() #self.data['a_graph_size'] = [bbu-bbl] self.program['u_graph_size'] = [self.bbu-self.bbl] self.vbo = gloo.VertexBuffer(self.data) self.gen_line_vbo(G) if(self.subgraphs): self.vbo_s = gloo.VertexBuffer(self.clusters) self.index_s = gloo.IndexBuffer(self.edges_s) #self.index = gloo.IndexBuffer(self.edges) self.program_e.bind(self.vbo_line) self.program.bind(self.vbo) if(self.subgraphs): #self.program_e_s.bind(self.vbo_s) self.program_s.bind(self.vbo_s) if DEBUG: print(self.view) self.update() """ Helper function that creates colored "block" lines based on the edges in the graph. Generates the framebuffer object and fills it with the relavant data. Note that each line segment is saved as a two triangles that share the same two points on the centerline, but are offset according to the normal of the line segmente to control thickness dynamically. """ def gen_line_vbo(self, G): #Set the data. self.line_data = np.zeros(G.num_edges()*4, dtype=[('a_position', np.float32, 3), ('a_normal', np.float32, 2), ('a_fg_color', np.float32, 4), ('a_linewidth', np.float32, 1), ]) self.edges = np.random.randint(size=(G.num_edges()*2, 3), low=0, high=(G.num_edges()-1)).astype(np.uint32) color = G.edge_properties["RGBA"].get_2d_array(range(4)).T edges = G.get_edges() #size need to be changed to the size based on the current property map size = nwt.Network.map_vertices_to_range(G, [1.0, 0.5], 'degree').get_array() for e in range(edges.shape[0]): idx = int(4*edges[e][2]) p0 = G.vertex_properties["pos"][G.vertex(edges[e][0])] p1 = G.vertex_properties["pos"][G.vertex(edges[e][1])] d = np.subtract(p1, p0) #d_norm = np.multiply(d, 1/np.sqrt(np.power(d[0],2) + np.power(d[1],2))) d_norm = d[0:2] d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2)) norm = np.zeros((2,), dtype=np.float32) norm[0] = d_norm[1] norm[1] = d_norm[0]*-1 #print(np.sqrt(norm[0]*norm[0] + norm[1]*norm[1])) #thickness = G.edge_properties["thickness"][e] thickness = 1.0 self.edge_dict[int(edges[e][0]), int(edges[e][1])] = int(edges[e][2]) self.line_data['a_position'][idx] = p0 self.line_data['a_normal'][idx] = norm self.line_data['a_fg_color'][idx] = color[edges[e][2]] #a_linewidth is a vector. self.line_data['a_linewidth'][idx] = size[edges[e][0]] self.line_data['a_position'][idx+1] = p1 self.line_data['a_normal'][idx+1] = norm self.line_data['a_fg_color'][idx+1] = color[edges[e][2]] self.line_data['a_linewidth'][idx+1] = size[edges[e][1]] self.line_data['a_position'][idx+2] = p0 self.line_data['a_normal'][idx+2] = -norm self.line_data['a_fg_color'][idx+2] = color[edges[e][2]] self.line_data['a_linewidth'][idx+2] = size[edges[e][0]] self.line_data['a_position'][idx+3] = p1 self.line_data['a_normal'][idx+3] = -norm self.line_data['a_fg_color'][idx+3] = color[edges[e][2]] self.line_data['a_linewidth'][idx+3] = size[edges[e][1]] self.edges[e*2] = [idx, idx+1, idx+3] self.edges[e*2+1] = [idx, idx+2, idx+3] #Set the buffer object and update the shader programs. self.program_e = gloo.Program(vs, fs) #self.program_e['l_color'] = self.linecolor.astype(np.float32) self.vbo_line = gloo.VertexBuffer(self.line_data) self.index = gloo.IndexBuffer(self.edges) self.program_e['u_size'] = 1 self.program_e['u_model'] = self.model self.program_e['u_view'] = self.view self.program_e['u_projection'] = self.projection self.program_e.bind(self.vbo_line) """ Helper function that generates the edges between the cluster in the layout. Color is based on the cluster source/target color and transitions between the two. """ def gen_cluster_line_vbo(self, G): #create a graph that stores the edges of between the clusters self.G_cluster = nwt.gt.Graph(directed=False) self.G_cluster.vertex_properties["pos"] = self.G_cluster.new_vertex_property("vector", val=np.zeros((3,1), dtype=np.float32)) self.G_cluster.vertex_properties["RGBA"] = self.G_cluster.new_vertex_property("vector", val=np.zeros((4,1), dtype=np.float32)) for v in range(len(self.cluster_pos)): self.G_cluster.add_vertex() self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(v)] = np.asarray(self.cluster_pos[v], dtype=np.float32) self.G_cluster.edge_properties["weight"] = self.G_cluster.new_edge_property("int", val = 0) #for each edge in the original graph, generate appropriate subgraph edges without repretiions #i.e. controls the thichness of the edges in the subgraph view. for e in G.edges(): #if the source and target cluster is not equal to each other #add an inter subgraph edge. if(G.vertex_properties["clusters"][e.source()] != G.vertex_properties["clusters"][e.target()]): #temp_e.append([G.vertex_properties["clusters"][e.source()], G.vertex_properties["clusters"][e.target()]]) self.G_cluster.add_edge(self.G_cluster.vertex(G.vertex_properties["clusters"][e.source()]), \ self.G_cluster.vertex(G.vertex_properties["clusters"][e.target()])) self.G_cluster.edge_properties["weight"][self.G_cluster.edge(self.G_cluster.vertex(G.vertex_properties["clusters"][e.source()]), \ self.G_cluster.vertex(G.vertex_properties["clusters"][e.target()]))] += 1 self.G_cluster.vertex_properties["RGBA"][self.G_cluster.vertex(G.vertex_properties["clusters"][e.source()])] \ = G.vertex_properties["RGBA"][e.source()] self.G_cluster.vertex_properties["RGBA"][self.G_cluster.vertex(G.vertex_properties["clusters"][e.target()])] \ = G.vertex_properties["RGBA"][e.target()] self.cluster_line_data = np.zeros(self.G_cluster.num_edges()*4, dtype=[('a_position', np.float32, 3), ('a_normal', np.float32, 2), ('a_fg_color', np.float32, 4), ('a_linewidth', np.float32, 1), ]) self.cluster_edges = np.random.randint(size=(self.G_cluster.num_edges()*2, 3), low=0, high=(G.num_edges()-1)).astype(np.uint32) edges = self.G_cluster.get_edges() #size need to be changed to the size based on the current property map size = nwt.Network.map_edges_to_range(self.G_cluster, [1.0, 0.5], 'weight').get_array() color = self.G_cluster.vertex_properties["RGBA"].get_2d_array(range(4)).T #generate the vertex buffer and the connections buffer. for e in range(edges.shape[0]): idx = int(4*edges[e][2]) p0 = self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(edges[e][0])] p1 = self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(edges[e][1])] d = np.subtract(p1, p0) #d_norm = np.multiply(d, 1/np.sqrt(np.power(d[0],2) + np.power(d[1],2))) d_norm = d[0:2] d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2)) norm = np.zeros((2,), dtype=np.float32) norm[0] = d_norm[1] norm[1] = d_norm[0]*-1 #print(np.sqrt(norm[0]*norm[0] + norm[1]*norm[1])) #thickness = G.edge_properties["thickness"][e] self.cluster_dict[int(edges[e][0]), int(edges[e][1])] = int(edges[e][2]) self.cluster_line_data['a_position'][idx] = p0 self.cluster_line_data['a_normal'][idx] = norm self.cluster_line_data['a_fg_color'][idx] = color[edges[e][0]] self.cluster_line_data['a_linewidth'][idx] = size[e] self.cluster_line_data['a_position'][idx+1] = p1 self.cluster_line_data['a_normal'][idx+1] = norm self.cluster_line_data['a_fg_color'][idx+1] = color[edges[e][1]] self.cluster_line_data['a_linewidth'][idx+1] = size[e] self.cluster_line_data['a_position'][idx+2] = p0 self.cluster_line_data['a_normal'][idx+2] = -norm self.cluster_line_data['a_fg_color'][idx+2] = color[edges[e][0]] self.cluster_line_data['a_linewidth'][idx+2] = size[e] self.cluster_line_data['a_position'][idx+3] = p1 self.cluster_line_data['a_normal'][idx+3] = -norm self.cluster_line_data['a_fg_color'][idx+3] = color[edges[e][1]] self.cluster_line_data['a_linewidth'][idx+3] = size[e] self.cluster_edges[e*2] = [idx, idx+1, idx+3] self.cluster_edges[e*2+1] = [idx, idx+2, idx+3] self.program_e_s = gloo.Program(vs_s, fs_s) self.index_clusters_s = gloo.IndexBuffer(self.cluster_edges) self.vbo_cluster_lines = gloo.VertexBuffer(self.cluster_line_data) self.program_e_s['u_size'] = 1 self.program_e_s['u_model'] = self.model self.program_e_s['u_view'] = self.view self.program_e_s['u_projection'] = self.projection self.program_e_s.bind(self.vbo_cluster_lines) """ Updates the vertex buffers based on the current position of the cluster. Updates it's position. """ def update_cluster_line_vbo(self): for v in range(len(self.cluster_pos)): self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(v)] = np.asarray(self.cluster_pos[v], dtype=np.float32) #OPTIMIZE HERE to update only one cluster at a time. edges = self.G_cluster.get_edges() #size need to be changed to the size based on the current property map size = nwt.Network.map_edges_to_range(self.G_cluster, [1.0, 0.5], 'weight').get_array() color = self.G_cluster.vertex_properties["RGBA"].get_2d_array(range(4)).T for e in range(edges.shape[0]): idx = int(4*edges[e][2]) p0 = self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(edges[e][0])] p1 = self.G_cluster.vertex_properties["pos"][self.G_cluster.vertex(edges[e][1])] d = np.subtract(p1, p0) #d_norm = np.multiply(d, 1/np.sqrt(np.power(d[0],2) + np.power(d[1],2))) d_norm = d[0:2] d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2)) norm = np.zeros((2,), dtype=np.float32) norm[0] = d_norm[1] norm[1] = d_norm[0]*-1 #print(np.sqrt(norm[0]*norm[0] + norm[1]*norm[1])) #thickness = G.edge_properties["thickness"][e] self.cluster_dict[int(edges[e][0]), int(edges[e][1])] = int(edges[e][2]) self.cluster_line_data['a_position'][idx] = p0 self.cluster_line_data['a_normal'][idx] = norm self.cluster_line_data['a_fg_color'][idx] = color[edges[e][0]] self.cluster_line_data['a_linewidth'][idx] = size[e] self.cluster_line_data['a_position'][idx+1] = p1 self.cluster_line_data['a_normal'][idx+1] = norm self.cluster_line_data['a_fg_color'][idx+1] = color[edges[e][1]] self.cluster_line_data['a_linewidth'][idx+1] = size[e] self.cluster_line_data['a_position'][idx+2] = p0 self.cluster_line_data['a_normal'][idx+2] = -norm self.cluster_line_data['a_fg_color'][idx+2] = color[edges[e][0]] self.cluster_line_data['a_linewidth'][idx+2] = size[e] self.cluster_line_data['a_position'][idx+3] = p1 self.cluster_line_data['a_normal'][idx+3] = -norm self.cluster_line_data['a_fg_color'][idx+3] = color[edges[e][1]] self.cluster_line_data['a_linewidth'][idx+3] = size[e] self.program_e_s = gloo.Program(vs_s, fs_s) self.index_clusters_s = gloo.IndexBuffer(self.cluster_edges) self.vbo_cluster_lines = gloo.VertexBuffer(self.cluster_line_data) self.program_e_s['u_size'] = 1 self.program_e_s['u_model'] = self.model self.program_e_s['u_view'] = self.view self.program_e_s['u_projection'] = self.projection self.program_e_s.bind(self.vbo_cluster_lines) """ Genererates a unique index for every vertex. """ def gen_vertex_id(self, G): self.color_dict = {} base = [0, 0, 0, 255] idx = 0 #colors = cm.get_cmap('Wistia', G.num_vertices()*2) v_id = np.zeros((G.num_vertices(), 4), dtype=np.float32) for v in G.vertices(): color = np.multiply(base, 1/255.0) v_id[int(v)] = color self.color_dict[tuple(color)] = int(v) idx += 1 base = [int(idx/(255*255)), int((idx/255)%255), int(idx%255), 255] return(v_id) """ Generates a unique index for every cluster. """ def gen_cluster_id(self, G): self.cluster_dict = {} base = [0, 0, 0, 255] idx = 0 #colors = cm.get_cmap('Wistia', G.num_vertices()*2) v_id = np.zeros((self.n_c, 4), dtype=np.float32) for v in range(self.n_c): color = np.multiply(base, 1/255.0) v_id[int(v)] = color self.cluster_dict[tuple(color)] = int(v) idx += 1 base = [int(idx/(255*255)), int((idx/255)%255), int(idx%255), 255] return(v_id) """ Generates the bounding box of the radial glyph. """ def gen_cluster_coords(self, center, diameter): radius = diameter/2.0 top = center[1]+radius bottom = center[1]-radius left = center[0]-radius right = center[0]+radius positions = [[right, bottom, center[2]], [right, top, center[2]], [left, top, center[2]], [left, bottom, center[2]]] values = [[1.0, -1.0], [1.0, 1.0,], [-1.0, 1.0], [-1.0, -1.0]] return positions, values """ Layout algorithm that expands the cluster based on the location of the of the clusters """ def expand_based_on_clusters(self, G, n): pos = G.vertex_properties["pos"].get_2d_array(range(3)).T for p in range(pos.shape[0]): pos[p][0] = pos[p][0] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][0] pos[p][1] = pos[p][1] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][1] pos[p][2] = pos[p][2] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][2] G.vertex_properties["pos"] = G.new_vertex_property("vector", vals = pos) for i in range(n): index = 4*i #generate the vertex filter for this cluster num_v_in_cluster = len(np.argwhere(self.labels == i)) vfilt = np.zeros([G.num_vertices(), 1], dtype="bool") vfilt[np.argwhere(self.labels == i)] = 1 vfilt_prop = G.new_vertex_property("bool", vals = vfilt) G.set_vertex_filter(vfilt_prop) #get the filtered properties g = nwt.gt.Graph(G, prune=True, directed=False) positions = g.vertex_properties["pos"].get_2d_array(range(3)).T position = np.sum(positions, 0)/num_v_in_cluster p, v = self.gen_cluster_coords(position, np.sum(g.vertex_properties['degree'].get_array())) self.clusters['a_position'][index:index+4] = np.asarray(p, dtype=np.float32) self.clusters['a_value'][index:index+4] = np.asarray(v, dtype=np.float32) G.clear_filters() self.cluster_pos[i] = position color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T size = nwt.Network.map_vertices_to_range(G, [30*self.pixel_scale, 8*self.pixel_scale], 'degree').get_array() position = G.vertex_properties["pos"].get_2d_array(range(3)).T #for p in range(position.shape[0]): # position[p][0] = position[p][0] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][0] # position[p][1] = position[p][1] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][1] # position[p][2] = position[p][2] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][2] #G.vertex_properties["pos"] = G.new_vertex_property("vector", vals = position) edges = G.get_edges(); edges = edges[:, 0:2] #width = nwt.Network.map_edges_to_range(G, [1*self.pixel_scale, 5*self.pixel_scale], 'volume').get_array() #ecolor = G.edge_properties["RGBA"].get_2d_array(range(4)).T self.data = np.zeros(G.num_vertices(), dtype=[('a_position', np.float32, 3), ('a_fg_color', np.float32, 4), ('a_bg_color', np.float32, 4), ('a_size', np.float32, 1), ('a_linewidth', np.float32, 1), ('a_unique_id', np.float32, 4), ('a_selection', np.float32, 1), ]) #self.edges = edges.astype(np.uint32) self.data['a_position'] = position #fg color is the color of the ring self.data['a_fg_color'] = 0, 0, 0, 1 self.data['a_bg_color'] = color self.data['a_size'] = size self.data['a_linewidth'] = 4.*self.pixel_scale self.data['a_unique_id'] = self.gen_vertex_id(G) self.data['a_selection'] = G.vertex_properties["selection"].get_array() #self.data['a_graph_size'] = [bbu-bbl] self.program['u_graph_size'] = [bbu-bbl] self.vbo = gloo.VertexBuffer(self.data) self.gen_line_vbo(G) if(self.subgraphs): self.vbo_s = gloo.VertexBuffer(self.clusters) self.index_s = gloo.IndexBuffer(self.edges_s) #self.index = gloo.IndexBuffer(self.edges) self.program_e.bind(self.vbo_line) self.program.bind(self.vbo) if(self.subgraphs): #self.program_e_s.bind(self.vbo_s) self.program_s.bind(self.vbo_s) if DEBUG: print(self.view) self.update() """ Function that generates the clusters for an unclustered graph These are to be represented by the arcs """ def gen_clusters(self, G, bbl, bbu, n_c = None, edge_metric = 'volume', vertex_metric = 'degree'): #Generate the clusters self.labels = nwt.Network.spectral_clustering(G,'length', n_clusters = n_c) #self.labels = nwt.Network.spectral_clustering(G,'length') #Add clusters as a vertex property G.vertex_properties["clusters"] = G.new_vertex_property("int", vals=self.labels) num_clusters = len(np.unique(self.labels)) self.n_c = n_c #add colormap G.vertex_properties["RGBA"] = nwt.Network.map_property_to_color(G, G.vertex_properties["clusters"]) #generate an empty property set for the clusters. self.clusters = np.zeros(num_clusters*4, dtype=[('a_position', np.float32, 3), ('a_value', np.float32, 2), ('a_bg_color', np.float32, 4), ('a_cluster_color', np.float32, 4), ('a_arc_length', np.float32, 1), ('a_outer_arc_length', np.float32, 4), ('a_unique_id', np.float32, 4), ]) self.edges_s = np.random.randint(size=(num_clusters*2, 3), low=0, high=4).astype(np.uint32) #fill the foreground color as halo #self.clusters['a_fg_color'] = 1., 1., 1., 0.0 #self.clusters['a_linewidth'] = 4.*self.pixel_scale G.vertex_properties["pos"] = nwt.gt.sfdp_layout(G, groups = G.vertex_properties["clusters"], pos = G.vertex_properties["pos"]) temp = [] temp_pos = [] #Find the global total of the metric. global_metric = np.sum(G.edge_properties[edge_metric].get_array(), 0) unique_color = self.gen_cluster_id(G) #generate the property values for every cluster for i in range(num_clusters): idx = 4*i #generate the vertex filter for this cluster num_v_in_cluster = len(np.argwhere(self.labels == i)) vfilt = np.zeros([G.num_vertices(), 1], dtype="bool") vfilt[np.argwhere(self.labels == i)] = 1 vfilt_prop = G.new_vertex_property("bool", vals = vfilt) G.set_vertex_filter(vfilt_prop) #get the filtered properties g = nwt.gt.Graph(G, prune=True, directed=False) positions = g.vertex_properties["pos"].get_2d_array(range(3)).T position = np.sum(positions, 0)/num_v_in_cluster #calculate the arclength for the global statistic arc_length = np.sum(g.edge_properties[edge_metric].get_array(), 0)/global_metric*np.pi*2 arc_length_vertex = np.ones((4,1), dtype = np.float32) array = g.vertex_properties[vertex_metric].get_array() #calculate metric distribution and turn it into arc_lengths t_vertex_metric = np.sum(array) arc_length_vertex[0] = np.sum(array < 2)/t_vertex_metric arc_length_vertex[1] = np.sum(array == 2)/t_vertex_metric arc_length_vertex[2] = np.sum(array == 3)/t_vertex_metric arc_length_vertex[3] = np.sum(array > 3)/t_vertex_metric #arc_length_vertex = np.asarray(arc_length_vertex, dtype = np.float32) #arc_length_vertex = (max(arc_length_vertex) - min(arc_length_vertex)) \ #* (arc_length_vertex- min(arc_length_vertex)) for j in range(len(arc_length_vertex)): if j != 0: arc_length_vertex[j] += arc_length_vertex[j-1] if DEBUG: print("arc_length before ", arc_length_vertex, " and sum to ", sum(arc_length_vertex)) arc_length_vertex = np.asarray(arc_length_vertex, dtype = np.float32) arc_length_vertex = (np.pi - -np.pi)/(max(arc_length_vertex) - min(arc_length_vertex)) \ * (arc_length_vertex- min(arc_length_vertex)) + (-np.pi) if DEBUG: print(arc_length_vertex) #print(arc_length) temp_pos.append(position) #generate the color for every vertex, #since all vertices belong to the same cluster we can check only #one vertex for the cluster color. self.clusters['a_cluster_color'][idx:idx+4] = g.vertex_properties["RGBA"][g.vertex(0)] self.clusters['a_bg_color'][idx:idx+4] = [0.1, 0.1, 0.1, 1.0] self.clusters['a_unique_id'][idx:idx+4] = unique_color[i] #The arc-length representing one global metric. self.clusters['a_arc_length'][idx:idx+4] = arc_length self.clusters['a_outer_arc_length'][idx:idx+4] = arc_length_vertex[:].T temp.append(np.sum(g.vertex_properties['degree'].get_array())) G.clear_filters() if DEBUG: print(self.clusters['a_outer_arc_length']) maximum = max(temp) minimum = min(temp) if len(temp) > 1: temp = ((temp-minimum)/(maximum-minimum)*(60*self.pixel_scale)+20*self.pixel_scale) else: temp = [60*self.pixel_scale] for i in range(num_clusters): index = i*4 index_t = i*2 p, v = self.gen_cluster_coords(temp_pos[i], temp[i]*2.0) self.clusters['a_position'][index:index+4] = np.asarray(p, dtype=np.float32) self.clusters['a_value'][index:index+4] = np.asarray(v, dtype=np.float32) self.edges_s[index_t] = [index, index+1, index+2] self.edges_s[index_t+1] = [index, index+2, index+3] #self.edges_s[i][0:4] = np.asarray(range(index, index+4), dtype=np.uint32) #self.edges_s[i] self.cluster_pos = temp_pos #self.expand_based_on_clusters(G, self.n_c) G.clear_filters() # self.edges_s[1][0:4] = np.asarray(range(0, 0+4), dtype=np.uint32) # self.edges_s[1][4] = 0 # self.edges_s[1][5] = 0+2 # # self.edges_s[0][0:4] = np.asarray(range(index, index+4), dtype=np.uint32) # self.edges_s[0][4] = index # self.edges_s[0][5] = index+2 #self.clusters['a_size'] = temp self.gen_cluster_line_vbo(G) self.program_s['u_graph_size'] = [bbu-bbl] #if len(temp_e) > 0: # self.edges_s = np.unique(np.asarray(temp_e, np.uint32), axis=0) #else: # self.edges_s = [] #print(self.edges_s) """ Function that expands that generates the layout and updates the buffer """ def expand_clusters(self, G, n_c): self.expand_based_on_clusters(G, n_c) self.gen_cluster_line_vbo(G) if(self.subgraphs): self.vbo_s = gloo.VertexBuffer(self.clusters) self.index_s = gloo.IndexBuffer(self.edges_s) self.program_e.bind(self.vbo_line) self.program.bind(self.vbo) if(self.subgraphs): self.program_s.bind(self.vbo_s) if DEBUG: print(self.view) self.update() """ Loads the data G and generates all the buffers necessary as well as performs spectral clustering on the graph passed if the subgraph is set to true. """ def set_data(self, G, bbl, bbu, subgraph=True): if DEBUG: print("Setting data") self.G = G self.bbl = bbl self.bbu = bbu clear(color=True, depth=True) self.subgraphs = True self.current_color = "clusters" if(subgraph==True): self.gen_clusters(G, bbl, bbu, n_c=19) #color based on clusters self.color_edges(G) color = G.vertex_properties["RGBA"].get_2d_array(range(4)).T size = nwt.Network.map_vertices_to_range(G, [30*self.pixel_scale, 8*self.pixel_scale], 'degree').get_array() position = G.vertex_properties["pos"].get_2d_array(range(3)).T #for p in range(position.shape[0]): # position[p][0] = position[p][0] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][0] # position[p][1] = position[p][1] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][1] # position[p][2] = position[p][2] + self.clusters["a_position"][G.vertex_properties["clusters"][G.vertex(p)]][2] #G.vertex_properties["pos"] = G.new_vertex_property("vector", vals = position) edges = G.get_edges(); edges = edges[:, 0:2] #width = nwt.Network.map_edges_to_range(G, [1*self.pixel_scale, 5*self.pixel_scale], 'volume').get_array() #ecolor = G.edge_properties["RGBA"].get_2d_array(range(4)).T self.data = np.zeros(G.num_vertices(), dtype=[('a_position', np.float32, 3), ('a_fg_color', np.float32, 4), ('a_bg_color', np.float32, 4), ('a_size', np.float32, 1), ('a_linewidth', np.float32, 1), ('a_unique_id', np.float32, 4), ('a_selection', np.float32, 1), ]) #self.edges = edges.astype(np.uint32) self.data['a_position'] = position #fg color is the color of the ring self.data['a_fg_color'] = 0, 0, 0, 1 self.data['a_bg_color'] = color self.data['a_size'] = size self.data['a_linewidth'] = 4.*self.pixel_scale self.data['a_unique_id'] = self.gen_vertex_id(G) self.data['a_selection'] = G.vertex_properties["selection"].get_array() #self.data['a_graph_size'] = [bbu-bbl] self.program['u_graph_size'] = [bbu-bbl] self.vbo = gloo.VertexBuffer(self.data) self.gen_line_vbo(G) #self.gen_cylinder_vbo(G) if(self.subgraphs): self.vbo_s = gloo.VertexBuffer(self.clusters) self.index_s = gloo.IndexBuffer(self.edges_s) #self.index = gloo.IndexBuffer(self.edges) self.program_e.bind(self.vbo_line) self.program.bind(self.vbo) if(self.subgraphs): #self.program_e_s.bind(self.vbo_s) self.program_s.bind(self.vbo_s) if DEBUG: print(self.view) self.update() """ Function that changes and redraws the buffer during a resize event. """ def on_resize(self, event): set_viewport(0, 0, *event.physical_size) self.fbo = gloo.FrameBuffer(color=gloo.RenderBuffer(self.size[::-1]), depth=gloo.RenderBuffer(self.size[::-1])) """ Overloaded function that is called during every self.update() call """ def on_draw(self, event): clear(color='white', depth=True) self.program_e.draw('triangles', indices=self.index) self.program.draw('points') #self.program_e.draw('lines') if(self.subgraphs): self.program_e_s.draw('triangles', indices=self.index_clusters_s) self.program_s.draw('triangles', indices=self.index_s) """ Function performed during a mouse click (either right or left) gets the unique id of the object drawn underneath the cursor handles the cases depending on whether the click happened to a cluster or a vertex. Edges are not interactable (yet) """ def get_clicked_id(self, event, clusters = False): #Get the framebuffer coordinates of the click coord = self.transforms.get_transform('canvas', 'framebuffer').map(event.pos) #get the framebuffer where each element is rendered as a unique color size = self.size; self.fbo = gloo.FrameBuffer(color=gloo.RenderBuffer(size[::-1]), depth=gloo.RenderBuffer(size[::-1])) buff = gloo.read_pixels((0,0,self.physical_size[0], self.physical_size[1])) #imsave("test_ori.png", buff) self.fbo.activate() if clusters == False: self.program['u_picking'] = True clear(color='white', depth=True) self.program.draw('points') buff = gloo.read_pixels((0,0,self.physical_size[0], self.physical_size[1])) #imsave("test.png", buff) #return to the original state self.fbo.deactivate() self.program['u_picking'] = False else: self.program_s['u_picking'] = True clear(color='white', depth=True) self.program_s.draw('triangles', indices=self.index_s) buff = gloo.read_pixels((0,0,self.physical_size[0], self.physical_size[1])) #imsave("test.png", buff) #return to the original state self.fbo.deactivate() self.program_s['u_picking'] = False #print(buff[self.physical_size[1]-int(coord[1]), int(coord[0])]) #Get the color under the click. #Keep in mind that the buff is y, x #And 0,0 is in the top RIGHT corner. #IGNORE THE DOCUMENTATION color = np.multiply(buff[self.physical_size[1]-int(coord[1]), int(coord[0])], 1/255.0) #if (tuple(color) not in self.color_dict): # print("clicked on nothing") #else: # print(self.color_dict[tuple(color)]) #reset the original buffer self.update() #Return the element under the click. if clusters == False: if(tuple(color) not in self.color_dict): return None else: return self.color_dict[tuple(color)] else: if(tuple(color) not in self.cluster_dict): return None else: return self.cluster_dict[tuple(color)] """ Top level handle-mouse presee event for either left or right click """ def on_mouse_press(self, event): def update_view(): self.location = event.pos self.program['u_view'] = self.view self.program_e['u_view'] = self.view self.program_s['u_view'] = self.view self.program_e_s['u_view'] = self.view self.down = True # if(event.button == 2): ## menu = QtWidgets.QMenu(self.parent) ## NS = menu.addAction('Node Size') ## NC = menu.addAction('Node Color') ## action = menu.exec_(self.parent.globalPos()) ## if action == NS: # print("right_click") # #if menu.exec_(event.globalPos()): # # print(item.text()) if(event.button == 1): if(self.view[0][0] > 0.0010): c_id = self.get_clicked_id(event) if(c_id != None): self.original_point = self.G.vertex_properties["pos"][self.G.vertex(c_id)] self.location = event.pos self.moving = True self.down = True self.c_id = [c_id] else: update_view() #print("Clicked on:", event.pos) else: c_id = self.get_clicked_id(event, True) if DEBUG: print(c_id) if(c_id != None): self.original_point = self.cluster_pos[c_id] self.location = event.pos self.moving = True self.down = True self.c_id = [c_id] self.moving_cluster = True else: update_view() """ Gets the path and formats it in terms of vertex-to-vertex instead of source(obj)-to-source(obj) """ def get_path(self): p = [] for s in self.path: for e in s.e_path: temp = (int(e.source()), int(e.target())) if (temp not in p): p.append(temp) return p def update_path(self, event): #Method to update the vertex buffer of the nodes in the graph view def update_vbo(self): self.vbo = gloo.VertexBuffer(self.data) self.program.bind(self.vbo) self.update() def update_vertex_alpha(self, vertex, alpha): temp = self.G.vertex_properties["RGBA"][vertex] temp[3] = alpha self.G.vertex_properties["RGBA"][vertex] = temp #updates the path structure of the class #source and target are of type "source" defined in this class. def add_to_path(self, source, target): vl, el = nwt.gt.graph_tool.topology.shortest_path(self.G, self.G.vertex(source.idx), self.G.vertex(target.idx), weights=self.G.edge_properties["av_radius"]) for v in range(1, len(vl)-1): if (self.G.vertex_properties["selection"][vl[v]] != 1.0): self.G.vertex_properties["selection"][vl[v]] = 2.0 update_vertex_alpha(self, self.G.vertex(vl[v]), 1.0) self.data['a_selection'][int(vl[v])] = 2.0 source.v_path.append(int(vl[v])) for e in el: source.e_path.append(e) temp = self.G.edge_properties["RGBA"][e] temp[3] = 1.0 self.G.edge_properties["RGBA"][e] = temp def remove_from_path(self, source): for v in source.v_path: self.G.vertex_properties["selection"][self.G.vertex(v)] = 0.0 update_vertex_alpha(self,self.G.vertex(v), 0.5) self.data['a_selection'][v] = 0.0 for e in source.e_path: temp = self.G.edge_properties["RGBA"][e] temp[3] = 0.0 self.G.edge_properties["RGBA"][e] = temp source.clear_path() if (event.button == 1): if(self.view[0][0] > 0.0010): c_id = self.get_clicked_id(event) if(c_id != None): #check whether this is the first node to be selected if(self.pathing == False): #if it is, select that node and turn the pathing variable on. self.G.vertex_properties["selection"][self.G.vertex(c_id)] = 1.0 self.pathing = True self.path.append(path_point(c_id)) self.data['a_selection'][c_id] = 1.0 self.make_all_transparent(0.5) update_vertex_alpha(self, self.G.vertex(c_id), 1.0) self.update_color_buffers() print("I turned on the first node") else: #If the node is selected already, unselect it and remove from path the last occurance in the path if(self.G.vertex_properties["selection"][self.G.vertex(c_id)] == 1.0): self.G.vertex_properties["selection"][self.G.vertex(c_id)] = 0.0 update_vertex_alpha(self, self.G.vertex(c_id), 1.0) self.data['a_selection'][c_id] = 0.0 s_id = self.path.index(path_point(c_id)) if(s_id == 0): remove_from_path(self, self.path[s_id]) else: remove_from_path(self, self.path[s_id-1]) remove_from_path(self, self.path[s_id]) self.path.remove(path_point(c_id)) #self.data['a_selection'][c_id] = 0.0 #update_vbo(self) print("I turned off a node") elif(self.G.vertex_properties["selection"][self.G.vertex(c_id)] == 0.0): self.G.vertex_properties["selection"][self.G.vertex(c_id)] = 1.0 #if the source is not in the path add it if(path_point(c_id) not in self.path): self.path.append(path_point(c_id)) self.G.vertex_properties["selection"][self.G.vertex(c_id)] = 1.0 update_vertex_alpha(self, self.G.vertex(c_id), 1.0) self.data['a_selection'][c_id] = 1.0 #if the source is not LAST in the path, add it. elif(self.path[len(self.path)-1] != path_point(c_id)): self.path.append(path_point(c_id)) self.G.vertex_properties["selection"][self.G.vertex(c_id)] = 1.0 update_vertex_alpha(self, self.G.vertex(c_id), 1.0) self.data['a_selection'][c_id] = 1.0 print("I turned on a node") if(len(self.path) >= 1): for i in range(len(self.path)-1): add_to_path(self, self.path[i], self.path[i+1]) self.update_color_buffers() #THIS IS WHERE I LEFT IT OFF. if(np.sum(self.G.vertex_properties["selection"].get_array()) == 0): self.pathing = False self.make_all_transparent(1.0) self.update_color_buffers() # elif(np.sum(self.G.vertex_properties["selection"].get_array()) : # self.G.vertex_properties["selection"][self.G.vertex(c_id)] == False print("clicked on: ", c_id, " ", self.path) """ Handles the double click event that it responsible for path selection. Generates paths our of consecutive paths out of the selected vertices. """ def on_mouse_double_click(self, event): n=1 """ Resets the variables that are used during the pressdown and move events """ def on_mouse_release(self, event): self.down = False self.moving = False self.moving_cluster = False self.c_id = [] #self.location = event.pos #print("Clicked off:", event.pos) """ used during the drag evern to update the position of the clusters """ def update_cluster_position(self, G, pos, offset, c_id): v_pos = G.vertex_properties["pos"].get_2d_array(range(3)).T vertices = np.argwhere(self.labels == c_id) for v in range(vertices.shape[0]): idx = vertices[v][0] v_pos[idx][0] = v_pos[idx][0] + offset[0] v_pos[idx][1] = v_pos[idx][1] + offset[1] v_pos[idx][2] = v_pos[idx][2] + offset[2] self.data['a_position'][idx] = np.asarray([v_pos[idx][0], v_pos[idx][1], v_pos[idx][2]], dtype = np.float32) #update the edge data by finding all edges connected to the vertex vtx = self.G.vertex(idx) for e in vtx.all_edges(): d = np.subtract(G.vertex_properties["pos"][e.source()], G.vertex_properties["pos"][e.target()]) d_norm = d[0:2] d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2)) norm = np.zeros((2,), dtype=np.float32) norm[0] = d_norm[1] norm[1] = d_norm[0]*-1 if (int(e.source()), int(e.target())) in self.edge_dict.keys(): index = int(self.edge_dict[int(e.source()), int(e.target())]) if vtx == int(e.source()): self.line_data['a_position'][index*4] = v_pos[idx] self.line_data['a_position'][index*4+2] = v_pos[idx] self.line_data['a_normal'][index*4] = norm self.line_data['a_normal'][index*4+2] = -norm self.line_data['a_normal'][index*4+1] = norm self.line_data['a_normal'][index*4+3] = -norm elif vtx == int(e.target()): self.line_data['a_position'][index*4+1] = v_pos[idx] self.line_data['a_position'][index*4+3] = v_pos[idx] self.line_data['a_normal'][index*4] = norm self.line_data['a_normal'][index*4+2] = -norm self.line_data['a_normal'][index*4+1] = norm self.line_data['a_normal'][index*4+3] = -norm else: index = int(self.edge_dict[int(e.target()), int(e.source())]) if vtx == int(e.target()): self.line_data['a_position'][index*4] = v_pos[idx] self.line_data['a_position'][index*4+2] = v_pos[idx] self.line_data['a_normal'][index*4] = norm self.line_data['a_normal'][index*4+2] = -norm self.line_data['a_normal'][index*4+1] = norm self.line_data['a_normal'][index*4+3] = -norm elif vtx == int(e.source()): self.line_data['a_position'][index*4+1] = v_pos[idx] self.line_data['a_position'][index*4+3] = v_pos[idx] self.line_data['a_normal'][index*4] = norm self.line_data['a_normal'][index*4+2] = -norm self.line_data['a_normal'][index*4+1] = norm self.line_data['a_normal'][index*4+3] = -norm G.vertex_properties["pos"] = G.new_vertex_property("vector", vals = v_pos) index = 4*c_id #generate the vertex filter for this cluster vfilt = np.zeros([G.num_vertices(), 1], dtype="bool") vfilt[np.argwhere(self.labels == c_id)] = 1 vfilt_prop = G.new_vertex_property("bool", vals = vfilt) G.set_vertex_filter(vfilt_prop) #get the filtered properties g = nwt.gt.Graph(G, prune=True, directed=False) p, v = self.gen_cluster_coords(pos, np.sum(g.vertex_properties['degree'].get_array())) self.clusters['a_position'][index:index+4] = np.asarray(p, dtype=np.float32) self.clusters['a_value'][index:index+4] = np.asarray(v, dtype=np.float32) G.clear_filters() self.cluster_pos[c_id] = pos self.original_point = pos """ function that handles the mouse move event in a way that depends on a set of variables: state of the mouse button, the type of object selected and the number of objects. """ def on_mouse_move(self, event): if(self.down == True): if(self.moving == True and self.moving_cluster == False): if(len(self.c_id) < 2): #Project into GLSpace and get before and after move coordinates coord = self.transforms.get_transform('canvas', 'render').map(self.location)[:2] coord2 = self.transforms.get_transform('canvas', 'render').map(event.pos)[:2] cur_pos = self.G.vertex_properties["pos"][self.G.vertex(self.c_id[0])] #print(cur_pos, " Before") #Adjust the position of the node based on the current view matrix. cur_pos[0] = cur_pos[0] - (coord[0]-coord2[0])/self.view[0][0] cur_pos[1] = cur_pos[1] - (coord[1]-coord2[1])/self.view[0][0] #print(cur_pos, " After") #Upload the changed data. self.G.vertex_properties["pos"][self.G.vertex(self.c_id[0])] = cur_pos self.data['a_position'][self.c_id[0]] = cur_pos #update the edge data by finding all edges connected to the vertex v = self.G.vertex(self.c_id[0]) for e in v.all_edges(): d = np.subtract(self.G.vertex_properties["pos"][e.source()], self.G.vertex_properties["pos"][e.target()]) d_norm = d[0:2] d_norm = d_norm / np.sqrt(np.power(d_norm[0],2) + np.power(d_norm[1],2)) norm = np.zeros((2,), dtype=np.float32) norm[0] = d_norm[1] norm[1] = d_norm[0]*-1 if (int(e.source()), int(e.target())) in self.edge_dict.keys(): idx = int(self.edge_dict[int(e.source()), int(e.target())]) if self.c_id[0] == int(e.source()): self.line_data['a_position'][idx*4] = cur_pos self.line_data['a_position'][idx*4+2] = cur_pos self.line_data['a_normal'][idx*4] = norm self.line_data['a_normal'][idx*4+2] = -norm self.line_data['a_normal'][idx*4+1] = norm self.line_data['a_normal'][idx*4+3] = -norm elif self.c_id[0] == int(e.target()): self.line_data['a_position'][idx*4+1] = cur_pos self.line_data['a_position'][idx*4+3] = cur_pos self.line_data['a_normal'][idx*4] = norm self.line_data['a_normal'][idx*4+2] = -norm self.line_data['a_normal'][idx*4+1] = norm self.line_data['a_normal'][idx*4+3] = -norm else: idx = int(self.edge_dict[int(e.target()), int(e.source())]) if self.c_id[0] == int(e.target()): self.line_data['a_position'][idx*4] = cur_pos self.line_data['a_position'][idx*4+2] = cur_pos self.line_data['a_normal'][idx*4] = norm self.line_data['a_normal'][idx*4+2] = -norm self.line_data['a_normal'][idx*4+1] = norm self.line_data['a_normal'][idx*4+3] = -norm elif self.c_id[0] == int(e.source()): self.line_data['a_position'][idx*4+1] = cur_pos self.line_data['a_position'][idx*4+3] = cur_pos self.line_data['a_normal'][idx*4] = norm self.line_data['a_normal'][idx*4+2] = -norm self.line_data['a_normal'][idx*4+1] = norm self.line_data['a_normal'][idx*4+3] = -norm #self.line_data['a_position'][self.c_id[0]] = self.vbo = gloo.VertexBuffer(self.data) self.vbo_line = gloo.VertexBuffer(self.line_data) #Bind the buffer and redraw. self.program.bind(self.vbo) self.program_e.bind(self.vbo_line) #self.program.draw('points') self.location = event.pos self.update() elif(self.moving == True and self.moving_cluster == True): if(len(self.c_id) < 2): #Project into GLSpace and get before and after move coordinates coord = self.transforms.get_transform('canvas', 'render').map(self.location)[:2] coord2 = self.transforms.get_transform('canvas', 'render').map(event.pos)[:2] cur_pos = np.zeros(self.cluster_pos[self.c_id[0]].shape, dtype = np.float32) offset = np.zeros(self.cluster_pos[self.c_id[0]].shape, dtype = np.float32) cur_pos[0] = self.cluster_pos[self.c_id[0]][0] cur_pos[1] = self.cluster_pos[self.c_id[0]][1] cur_pos[2] = self.cluster_pos[self.c_id[0]][2] offset[0] = self.cluster_pos[self.c_id[0]][0] offset[1] = self.cluster_pos[self.c_id[0]][1] offset[2] = self.cluster_pos[self.c_id[0]][2] # ofset = self.cluster_pos[self.c_id[0]] #Adjust the position of the node based on the current view matrix. offset[0] = self.original_point[0] - cur_pos[0] - (coord[0]-coord2[0])/self.view[0][0] offset[1] = self.original_point[1] - cur_pos[1] - (coord[1]-coord2[1])/self.view[0][0] cur_pos[0] = cur_pos[0] - (coord[0]-coord2[0])/self.view[0][0] cur_pos[1] = cur_pos[1] - (coord[1]-coord2[1])/self.view[0][0] self.update_cluster_position(self.G, cur_pos, offset, self.c_id[0]) #self.original_point = cur_pos self.vbo = gloo.VertexBuffer(self.data) self.vbo_line = gloo.VertexBuffer(self.line_data) #Bind the buffer and redraw. self.program.bind(self.vbo) self.program_e.bind(self.vbo_line) #self.program.draw('points') self.location = event.pos if(self.subgraphs): self.vbo_s = gloo.VertexBuffer(self.clusters) self.program_s.bind(self.vbo_s) self.update_cluster_line_vbo() self.update() else: #print("Mouse at:", event.pos) #new_model = np.eye(4, dtype=np.float32) coord = self.transforms.get_transform('canvas', 'render').map(self.location)[:2] coord2 = self.transforms.get_transform('canvas', 'render').map(event.pos)[:2] self.translate[0] += (coord[0]-coord2[0])/self.view[0][0] self.translate[1] += (coord[1]-coord2[1])/self.view[1][1] #self.view[3][0] = self.view[3][0]-(self.location[0]-event.pos[0])/10000.0 #self.view[3][1] = self.view[3][1]+(self.location[1]-event.pos[1])/10000.0 self.view = np.matmul(translate((self.translate[0], self.translate[1], 0)), scale((self.scale[0], self.scale[1], 0))) self.program['u_view'] = self.view self.program_e['u_view'] = self.view self.program_s['u_view'] = self.view self.program_e_s['u_view'] = self.view self.location = event.pos self.update() """ Handles the mouse wheel zoom event. """ def on_mouse_wheel(self, event): #print(self.view) #TO_DO IMPLEMENT ZOOM TO CURSOR #self.view[3][0] = self.view[3][0]-event.pos[0]/10000.0 #self.view[3][1] = self.view[3][1]-event.pos[1]/10000.0 #print(self.scale[0] , self.scale[0]*event.delta[1]*0.05) self.scale[0] = self.scale[0] + self.scale[0]*event.delta[1]*0.05 self.scale[1] = self.scale[1] + self.scale[1]*event.delta[1]*0.05 self.view = np.matmul(translate((self.translate[0], self.translate[1], 0)), scale((self.scale[0], self.scale[1], 0))) #self.view[0][0] = self.view[0][0]+self.view[0][0]*event.delta[1]*0.05 #self.view[1][1] = self.view[1][1]+self.view[1][1]*event.delta[1]*0.05 #print(self.view[0][0], " ",self.view[1][1]) #print(self.view) self.program['u_view'] = self.view self.program_e['u_view'] = self.view self.program_s['u_view'] = self.view self.program_e_s['u_view'] = self.view #print(event.delta[1]) self.update()