require 'silicium' require 'chunky_png' require 'ruby2d' require 'ruby2d/window' module Silicium module GraphVisualizer include Silicium::Graphs include Ruby2D ## # Changes window size def change_window_size(w, h) (Window.get :window).set width: w, height: h end ## # Changes width of edges def change_edge_width(w) @@line_width = w end ## # Changes radius of vertices def change_vertices_radius(r) @@vert_radius = r end ## # Changes labels size def change_label_size(s) @@label_size = s end ## # Changes labels color def change_label_color(c) @@label_color = Color.new(c) end ## # Changes vertices color def change_vertex_color(c) @@vertex_color = Color.new(c) end ## # Set the graph for visualization def set_graph(graph) if graph.class != OrientedGraph and graph.class != UnorientedGraph raise ArgumentError, "Invalid graph type!" end clear_window set_vertices(graph) set_edges(graph) set_labels(graph) end ## # clear screen def clear_window Window.clear end ## # close screen def close_window Window.close end ## # show graph on the screen def show_window Window.show end ## # radius of vertices circles @@vert_radius = 15 ## # width of the edges @@line_width = 5 # size of labels @@label_size = 15 ## # color of labels @@label_color = Color.new('red') ## # color of vertices @@vertex_color = Color.new('white') private ## # creates labels of edges and vertices def set_labels(graph) @v_labels = {} graph.vertex_labels.keys.each do |v| @v_labels[v] = draw_vertex_label(v, graph.vertex_labels[v]) end @e_labels = {} graph.edge_labels.keys.each do |pair| @e_labels[pair] = draw_edge_label(pair, graph.edge_labels[pair]) end end ## # draws label on vertex def draw_vertex_label(v,label) if (label.class != String and label.class != Integer) return end x = @vertices[v].x - Math.sqrt(2)/2*@@vert_radius y = @vertices[v].y - Math.sqrt(2)/2*@@vert_radius return Text.new(label,x: x, y: y, size: @@label_size, color: @@label_color) end ## # draws label on edge def draw_edge_label(pair,label) if (label.class != String and label.class != Integer) return end x1 = @vertices[pair[:first]].x y1 = @vertices[pair[:first]].y x2 = @vertices[pair[:second]].x y2 = @vertices[pair[:second]].y x = (x1+x2)/2 y = (y1+y2)/2 if x1 == x2 and y1 == y2 x = @edges[pair][:line].x y = @edges[pair][:line].y end return Text.new(label,x: x, y: y, size: @@label_size, color: @@label_color) end ## # set all edges of the graph def set_edges(graph) @edges = {} @is_oriented = graph.class == OrientedGraph graph.vertices.keys.each do |from_vert| graph.vertices[from_vert].each do |to_vert| push_edge(from_vert,to_vert) end end end ## # creates edge and push it to the @edges def push_edge(from_vert, to_vert) col = get_random_color if @is_oriented and has_edge?(to_vert,from_vert) col = @edges[Pair.new(to_vert,from_vert)][:line].color end arrow = @is_oriented ? draw_oriented_edge(from_vert,to_vert,col):draw_edge(from_vert,to_vert,col) @edges[Pair.new(from_vert,to_vert)] = arrow end ## # return true if graph contains current edge def has_edge?(from_vert, to_vert) if @is_oriented return @edges.has_key?(Pair.new(from_vert,to_vert)) end return (@edges.has_key?(Pair.new(to_vert,from_vert)) or @edges.has_key?(Pair.new(from_vert,to_vert))) end ## # returns random color def get_random_color col = Color.new('random') while (col == @@label_color) or (col == @@vertex_color) col = Color.new('random') end return col end ## # draws all vertices of the graph def set_vertices(graph) @vertices = {} w = Window.get :width h = Window.get :height radius= [w,h].min*1.0 / 2 - @@vert_radius*4 vert_step = (360.0 / graph.vertex_number)*(Math::PI/180) position = 0 graph.vertices.keys.each do |vert| x = w/2 + Math.cos(position)*radius y = h/2 + Math.sin(position)*radius @vertices[vert] = draw_vertex(x,y) position += vert_step end end ## # creates circle for vertex def draw_vertex(x, y) circle = Circle.new(x: x, y: y, radius: @@vert_radius, sectors: 128, color: @@vertex_color) return circle end ## # creates arrow of edge between vertices def draw_oriented_edge(v1,v2,col) line = draw_edge(v1,v2,col) x1 = @vertices[v1].x y1 = @vertices[v1].y x2 = @vertices[v2].x y2 = @vertices[v2].y x_len = x2-x1 y_len = y2-y1 len = Math.sqrt(x_len*x_len+y_len*y_len) sin = y_len/len cos = x_len/len pos_x1 = x2 - @@vert_radius*cos pos_y1 = y2 - @@vert_radius*sin height_x= pos_x1 - @@line_width*4*cos height_y= pos_y1 - @@line_width*4*sin sin, cos = cos, sin pos_x2 = height_x + @@line_width*2*cos pos_y3 = height_y + @@line_width*2*sin pos_x3 = height_x - @@line_width*2*cos pos_y2 = height_y - @@line_width*2*sin #triangle = Circle.new(x: pos_x2, y: pos_y3,radius: 4, color: col) #Circle.new(x: pos_x3, y: pos_y2,radius: 4, color: col) triangle = Triangle.new(x1: pos_x1, y1: pos_y1, x2: pos_x2, y2: pos_y2, x3: pos_x3, y3: pos_y3, color: col) return {line: line, triangle: triangle} end ## # creates edge between vertices def draw_edge(v1,v2,col) x1 = @vertices[v1].x y1 = @vertices[v1].y x2 = @vertices[v2].x y2 = @vertices[v2].y x_len = x1-x2 y_len = y1-y2 len = Math.sqrt(x_len*x_len+y_len*y_len) if len == 0 return draw_loop(v1,col) end sin = y_len/len cos = x_len/len pos_x0 = x1 - @@vert_radius*cos pos_y0 = y1 - @@vert_radius*sin x_len = x2-x1 y_len = y2-y1 sin = y_len/len cos = x_len/len pos_x1 = x2 - @@vert_radius*cos pos_y1 = y2 - @@vert_radius*sin return Line.new(x1: pos_x0, y1: pos_y0, x2: pos_x1, y2: pos_y1, width: @@line_width, color: col) end ## # create loop edge def draw_loop(v,col) x = @vertices[v].x y = @vertices[v].y center_x = (Window.get :width) / 2 center_y = (Window.get :height) / 2 x_len = center_x-x y_len = center_y-y len = Math.sqrt(x_len*x_len+y_len*y_len) sin = y_len/len cos = x_len/len pos_x1 = x - @@vert_radius*cos*2 pos_y1 = y - @@vert_radius*sin*2 circle = Circle.new(x: pos_x1, y: pos_y1, radius: @@vert_radius*2, color: col) Circle.new(x: pos_x1, y: pos_y1, radius: @@vert_radius*2-@@line_width, color: Window.get( :background)) @vertices[v] = Circle.new(x: x, y: y, radius: @@vert_radius+1, color: @@vertex_color) return circle end end end