From e2649f122fdc1d386aeae436a773589e00ddac7f Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Wed, 16 Mar 2005 14:19:09 +0000 Subject: [PATCH] Vis stuff [SVN r27704] --- example/python/biconnected_components.dot | 13 + example/python/disconnected.dot | 10 + example/python/mst.dot | 10 + example/python/vis.py | 692 ++++++++++++++++++++++ 4 files changed, 725 insertions(+) create mode 100644 example/python/biconnected_components.dot create mode 100644 example/python/disconnected.dot create mode 100644 example/python/mst.dot create mode 100644 example/python/vis.py diff --git a/example/python/biconnected_components.dot b/example/python/biconnected_components.dot new file mode 100644 index 00000000..ef4ba592 --- /dev/null +++ b/example/python/biconnected_components.dot @@ -0,0 +1,13 @@ +graph G { + A -- B + A -- F + A -- G + B -- C + B -- D + B -- E + C -- D + E -- F + G -- H + G -- I + H -- I +} diff --git a/example/python/disconnected.dot b/example/python/disconnected.dot new file mode 100644 index 00000000..e7685ad8 --- /dev/null +++ b/example/python/disconnected.dot @@ -0,0 +1,10 @@ +graph G { + A -- F + B -- C + B -- D + C -- D + E -- F + G -- H + G -- I + H -- I +} diff --git a/example/python/mst.dot b/example/python/mst.dot new file mode 100644 index 00000000..508b4c77 --- /dev/null +++ b/example/python/mst.dot @@ -0,0 +1,10 @@ +graph G { + A -- C [weight="1.0"] + B -- D [weight="1.0"] + B -- E [weight="2.0"] + C -- B [weight="7.0"] + C -- D [weight="3.0"] + D -- E [weight="1.0"] + E -- A [weight="1.0"] + E -- B [weight="1.0"] +} \ No newline at end of file diff --git a/example/python/vis.py b/example/python/vis.py new file mode 100644 index 00000000..2fbb7c20 --- /dev/null +++ b/example/python/vis.py @@ -0,0 +1,692 @@ +#!/usr/bin/pythonw +import wx +import wx.lib.ogl as ogl +# import wx.lib.masked +import bgl +import os +import random +import sys + +wildcard = "GraphViz (*.dot)|*.dot|" \ + "All files (*.*)|*.*" + +def path_to_title(path): + title = path + dot_idx = title.rfind('.') + if dot_idx != -1: title = title[:dot_idx] + slash_idx = title.rfind('/') + if slash_idx != -1: title = title[slash_idx+1:] + slash_idx = title.rfind('\\') + if slash_idx != -1: title = title[slash_idx+1:] + return title + +class GraphCanvas(ogl.ShapeCanvas): + def __init__(self, parent): + ogl.ShapeCanvas.__init__(self, parent) + + maxWidth = 400 + maxHeight = 400 + self.SetScrollbars(20, 20, maxWidth/20, maxHeight/20) + + def set_graph(self, graph, position_map, property_maps): + self.diagram = ogl.Diagram() + self.SetDiagram(self.diagram) + self.diagram.SetCanvas(self) + + self.graph = graph + self.position_map = position_map + self.property_maps = property_maps + self.vertex_position_rect = self.compute_rect() + self.vertex_to_shape = graph.get_vertex_object_map("shape") + self.edge_to_shape = graph.get_edge_object_map("shape") + self.shape_to_vertex = {} + + for v in graph.vertices: + self.add_vertex(v) + + for e in graph.edges: + self.add_edge(e) + + self.Refresh() + + def compute_rect(self): + pos = self.position_map + if self.graph.num_vertices() == 0: + return (-50, -50, 50, 50) + else: + left, top, right, bottom = 0, 0, 0, 0 + for v in self.graph.vertices: + if pos[v].x < left: left = pos[v].x + if pos[v].y < top: top = pos[v].y + if pos[v].x > right: right = pos[v].x + if pos[v].y > bottom: bottom = pos[v].y + return (left, top, right, bottom) + + def update_layout(self): + self.vertex_position_rect = self.compute_rect() + dc = wx.ClientDC(self) + self.PrepareDC(dc) + for v in self.graph.vertices: + shape = self.vertex_to_shape[v] + x, y = self.vertex_position_to_shape_position(v) + shape.Move(dc, x, y, False) + self.Refresh() + + def vertex_position_to_shape_position(self, vertex): + (width, height) = self.GetVirtualSize() + width = width - 20 + height = height - 20 + (pos_width, pos_height) = (self.vertex_position_rect[2] + - self.vertex_position_rect[0], + self.vertex_position_rect[3] + - self.vertex_position_rect[1]) + return ((self.position_map[vertex].x - self.vertex_position_rect[0]) + / pos_width * width + 10, + (self.position_map[vertex].y - self.vertex_position_rect[1]) + / pos_height * height + 10); + + def translate_color(self, color): + if color=="black": return wx.BLACK + elif color=="blue": return wx.BLUE + elif color=="red": return wx.RED + elif color=="green": return wx.GREEN + else: return wx.BLACK + + def add_vertex(self, vertex): + shape = self.CreateVertex(vertex) + shape.SetDraggable(True, True) + shape.SetCanvas(self) + x, y = self.vertex_position_to_shape_position(vertex) + shape.SetX(x) + shape.SetY(y) + shape.SetPen(self.VertexPen(vertex)) + shape.SetBrush(self.VertexBrush(vertex)) + s = self.VertexLabel(vertex) + if s != "": shape.AddText(s) + self.diagram.AddShape(shape) + shape.Show(True) + self.vertex_to_shape[vertex] = shape + self.shape_to_vertex[shape] = vertex + + evthandler = VertexEventHandler(self) + evthandler.SetShape(shape) + evthandler.SetPreviousHandler(shape.GetEventHandler()) + shape.SetEventHandler(evthandler) + + return shape; + + def CreateVertex(self, vertex): + return ogl.CircleShape(20) + + def VertexPen(self, vertex): + thickness = 1 + color = wx.BLACK + if "vertex.border.thickness" in self.property_maps: + thickness = self.property_maps["vertex.border.thickness"][vertex] + if "vertex.border.color" in self.property_maps: + color_text = self.property_maps["vertex.border.color"][vertex] + color = translate_color(color_text) + return wx.Pen(color, thickness) + + def VertexBrush(self, vertex): + return wx.GREEN_BRUSH + + def VertexLabel(self, vertex): + if "vertex.label" in self.property_maps: + return self.property_maps["vertex.label"][vertex] + else: + return "" + + def VertexShape(self, vertex): + return self.vertex_to_shape[vertex] + + def add_edge(self, edge): + (u, v) = (self.graph.source(edge), self.graph.target(edge)) + line = ogl.LineShape() + line.SetCanvas(self) + line.SetPen(wx.BLACK_PEN) + line.SetBrush(wx.BLACK_BRUSH) + if self.graph.is_directed(): line.AddArrow(ogl.ARROW_ARROW) + if "edge.label" in self.property_maps: + label = str(self.property_maps["edge.label"][edge]) + line.AddText(label) + line.MakeLineControlPoints(2) + self.vertex_to_shape[u].AddLine(line, self.vertex_to_shape[v]) + self.diagram.AddShape(line) + line.Show(True) + self.edge_to_shape[edge] = line + return line + + def EdgeShape(self, edge): + return self.edge_to_shape[edge] + + def set_vertex_colors_from_components(self, component_map): + brushes = {} + for v in self.graph.vertices: + shape = self.vertex_to_shape[v] + comp = component_map[v] + if not comp in brushes: + brushes[comp] = wx.Brush(wx.Color(random.randint(0, 200), + random.randint(0, 200), + random.randint(0, 200))) + shape.SetBrush(brushes[comp]) + self.Refresh() + + def set_edge_colors_from_components(self, component_map): + pens = {} + for e in self.graph.edges: + shape = self.edge_to_shape[e] + comp = component_map[e] + if not comp in pens: + pens[comp] = wx.Pen(wx.Color(random.randint(0, 200), + random.randint(0, 200), + random.randint(0, 200)), + 1) + shape.SetPen(pens[comp]) + self.Refresh() + + def default_property_maps(self, graph, node_id = "node_id"): + maps = {} + if graph.has_vertex_map("label"): + maps["vertex.label"] = graph.get_vertex_string_map("label") + elif graph.has_vertex_map(node_id): + maps["vertex.label"] = graph.get_vertex_string_map(node_id) + if graph.has_edge_map("label"): + maps["edge.label"] = graph.get_edge_string_map("label") + elif graph.has_edge_map("weight"): + maps["edge.label"] = graph.get_edge_double_map("weight") + return maps + +class VertexEventHandler(ogl.ShapeEvtHandler): + def __init__(self, graphwin): + ogl.ShapeEvtHandler.__init__(self) + self.graphwin = graphwin + + def OnEndDragLeft(self, x, y, keys=0, attachment=0): + shape = self.GetShape() + if shape.Selected(): + vertex = self.graphwin.shape_to_vertex[shape] + self.graphwin.position_map[vertex].x = x + self.graphwin.position_map[vertex].y = y + ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment) + +class ErdosRenyiDialog(wx.Dialog): + def __init__( + self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition, + style=wx.DEFAULT_DIALOG_STYLE + ): + wx.Dialog.__init__(self, parent, ID, title, pos, size, style, + "Erdos-Renyi Generator") + + sizer = wx.BoxSizer(wx.VERTICAL) + + box = wx.BoxSizer(wx.HORIZONTAL) + + box.Add(wx.StaticText(self, -1, "Number of vertices (n)"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + num_vertices_spin = wx.SpinCtrl(self, -1, "", (30, 50)) + num_vertices_spin.SetRange(0,10000000) + num_vertices_spin.SetValue(10) + box.Add(num_vertices_spin, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + self.num_vertices_spin = num_vertices_spin + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + + box.Add(wx.StaticText(self, -1, "Edge probability (%)"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + # This is better than what we're currently doing with a SpinCtrl, but + # it makes the program unstable (?) +# probability_ctrl = wx.lib.masked.numctrl.NumCtrl(self, value = 0.2, +# integerWidth = 1, +# fractionWidth = 3, +# allowNegative = False, +# min = 0.0, max = 1.0) + + probability_ctrl = wx.SpinCtrl(self, -1, "", (30, 50)) + probability_ctrl.SetRange(0,100) + probability_ctrl.SetValue(20) + self.probability_ctrl = probability_ctrl + box.Add(probability_ctrl, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(wx.StaticText(self, -1, "Random seed"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + seed_ctrl = wx.SpinCtrl(self, -1, "", (30, 50)) + seed_ctrl.SetRange(1, sys.maxint) + seed_ctrl.SetValue(random.randint(1, sys.maxint)) + self.seed_ctrl = seed_ctrl + box.Add(seed_ctrl, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL) + sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + + btn = wx.Button(self, wx.ID_OK, " Generate! ") + btn.SetDefault() + box.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + + btn = wx.Button(self, wx.ID_CANCEL, " Cancel ") + box.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + + sizer.Add(box, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + self.SetSizer(sizer) + self.SetAutoLayout(True) + sizer.Fit(self) + + def GetNumVertices(self): + return self.num_vertices_spin.GetValue() + + def GetProbability(self): + return float(self.probability_ctrl.GetValue()) + + def GetRandomSeed(self): + return int(self.seed_ctrl.GetValue()) + +class PLODDialog(wx.Dialog): + def __init__( + self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition, + style=wx.DEFAULT_DIALOG_STYLE + ): + wx.Dialog.__init__(self, parent, ID, title, pos, size, style) + + sizer = wx.BoxSizer(wx.VERTICAL) + + box = wx.BoxSizer(wx.HORIZONTAL) + + box.Add(wx.StaticText(self, -1, "Number of vertices (n)"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + num_vertices_spin = wx.SpinCtrl(self, -1, "", (30, 50)) + num_vertices_spin.SetRange(0,10000000) + num_vertices_spin.SetValue(10) + box.Add(num_vertices_spin, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + self.num_vertices_spin = num_vertices_spin + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + # Alpha + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(wx.StaticText(self, -1, "Alpha"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + alpha_ctrl = wx.TextCtrl(self, -1, "2.75", (30, 50)) + self.alpha_ctrl = alpha_ctrl + box.Add(alpha_ctrl, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + + # Beta + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(wx.StaticText(self, -1, "Beta"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + beta_ctrl = wx.SpinCtrl(self, -1, "", (30, 50)) + beta_ctrl.SetRange(1, sys.maxint) + beta_ctrl.SetValue(300) + self.beta_ctrl = beta_ctrl + box.Add(beta_ctrl, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(wx.StaticText(self, -1, "Random seed"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + seed_ctrl = wx.SpinCtrl(self, -1, "", (30, 50)) + seed_ctrl.SetRange(1, sys.maxint) + seed_ctrl.SetValue(random.randint(1, sys.maxint)) + self.seed_ctrl = seed_ctrl + box.Add(seed_ctrl, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL) + sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + + btn = wx.Button(self, wx.ID_OK, " Generate! ") + btn.SetDefault() + box.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + + btn = wx.Button(self, wx.ID_CANCEL, " Cancel ") + box.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + + sizer.Add(box, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + self.SetSizer(sizer) + self.SetAutoLayout(True) + sizer.Fit(self) + + def GetNumVertices(self): + return self.num_vertices_spin.GetValue() + + def GetAlpha(self): + return float(self.alpha_ctrl.GetValue()) + + def GetBeta(self): + return float(self.beta_ctrl.GetValue()) + + def GetRandomSeed(self): + return int(self.seed_ctrl.GetValue()) + +class SmallWorldDialog(wx.Dialog): + def __init__( + self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition, + style=wx.DEFAULT_DIALOG_STYLE + ): + wx.Dialog.__init__(self, parent, ID, title, pos, size, style) + + sizer = wx.BoxSizer(wx.VERTICAL) + + box = wx.BoxSizer(wx.HORIZONTAL) + + box.Add(wx.StaticText(self, -1, "Number of vertices (n)"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + num_vertices_spin = wx.SpinCtrl(self, -1, "", (30, 50)) + num_vertices_spin.SetRange(0,10000000) + num_vertices_spin.SetValue(10) + box.Add(num_vertices_spin, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + self.num_vertices_spin = num_vertices_spin + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + # Number of neighbors + box = wx.BoxSizer(wx.HORIZONTAL) + + box.Add(wx.StaticText(self, -1, "Number of neighbors (k)"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + num_neighbors_spin = wx.SpinCtrl(self, -1, "", (30, 50)) + num_neighbors_spin.SetRange(0,10000000) + num_neighbors_spin.SetValue(4) + box.Add(num_neighbors_spin, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + self.num_neighbors_spin = num_neighbors_spin + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + + box.Add(wx.StaticText(self, -1, "Rewiring probability (%)"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + # This is better than what we're currently doing with a SpinCtrl, but + # it makes the program unstable (?) +# probability_ctrl = wx.lib.masked.numctrl.NumCtrl(self, value = 0.2, +# integerWidth = 1, +# fractionWidth = 3, +# allowNegative = False, +# min = 0.0, max = 1.0) + + probability_ctrl = wx.SpinCtrl(self, -1, "", (30, 50)) + probability_ctrl.SetRange(0,100) + probability_ctrl.SetValue(20) + self.probability_ctrl = probability_ctrl + box.Add(probability_ctrl, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(wx.StaticText(self, -1, "Random seed"), 0, + wx.ALIGN_CENTRE|wx.ALL, 5) + + seed_ctrl = wx.SpinCtrl(self, -1, "", (30, 50)) + seed_ctrl.SetRange(1, sys.maxint) + seed_ctrl.SetValue(random.randint(1, sys.maxint)) + self.seed_ctrl = seed_ctrl + box.Add(seed_ctrl, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL) + sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + + btn = wx.Button(self, wx.ID_OK, " Generate! ") + btn.SetDefault() + box.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + + btn = wx.Button(self, wx.ID_CANCEL, " Cancel ") + box.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + + sizer.Add(box, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + self.SetSizer(sizer) + self.SetAutoLayout(True) + sizer.Fit(self) + + def GetNumVertices(self): + return self.num_vertices_spin.GetValue() + + def GetNumNeighbors(self): + return self.num_neighbors_spin.GetValue() + + def GetProbability(self): + return float(self.probability_ctrl.GetValue()) + + def GetRandomSeed(self): + return int(self.seed_ctrl.GetValue()) + +class GraphEditorWindow(wx.Frame): + def __init__(self, parent, title='BGL Graph Viewer'): + wx.Frame.__init__(self, parent, -1, title); + self.canvas = GraphCanvas(self) + self.CreateMenuBar() + self.erdos_renyi_dlg = None + self.plod_dlg = None + self.small_world_dlg = None + self.NewGraph(None) + + def CreateMenuBar(self): + menuBar = wx.MenuBar() + + # File menu + fileMenu = wx.Menu() + + # New graph menu + newGraphMenu = wx.Menu() + newGraphMenu.Append(111, "Empty graph") + newGraphMenu.Append(112, "Erdos-Renyi graph...") + newGraphMenu.Append(113, "Power Law Out Degree graph...") + newGraphMenu.Append(114, "Small-world graph...") + fileMenu.AppendMenu(110, "&New graph", newGraphMenu) + + fileMenu.Append(120, "&Open graph") + fileMenu.Append(130, "&Save graph") + menuBar.Append(fileMenu, "&File") + + # Algorithms menu + algorithmsMenu = wx.Menu() + # - Connected components menu + ccMenu = wx.Menu(); + ccMenu.Append(201, "Connected Components") + ccMenu.Append(202, "Strongly-Connected Components") + ccMenu.Append(203, "Biconnected Components") + algorithmsMenu.AppendMenu(200, "Connected Components", ccMenu) + + # - Minimum Spanning Tree menu + mstMenu = wx.Menu(); + mstMenu.Append(212, "Kruskal") + algorithmsMenu.AppendMenu(210, "Minimum Spanning Tree", mstMenu) + + menuBar.Append(algorithmsMenu, "&Algorithms") + + # Layout menu + layoutMenu = wx.Menu() + layoutMenu.Append(301, "&Circle layout") + layoutMenu.Append(302, "&Fruchterman-Reingold layout") + layoutMenu.Append(303, "&Kamada-Kawai layout") + menuBar.Append(layoutMenu, "&Layout") + + # File menu events + self.Bind(wx.EVT_MENU, self.NewGraph, id=111) + self.Bind(wx.EVT_MENU, self.ErdosRenyiGraph, id=112) + self.Bind(wx.EVT_MENU, self.PLODGraph, id=113) + self.Bind(wx.EVT_MENU, self.SmallWorldGraph, id=114) + self.Bind(wx.EVT_MENU, self.OpenGraph, id=120) + self.Bind(wx.EVT_MENU, self.SaveGraph, id=130) + + # Algorithms menu events + self.Bind(wx.EVT_MENU, self.ConnectedComponents, id=201) + self.Bind(wx.EVT_MENU, self.StrongComponents, id=202) + self.Bind(wx.EVT_MENU, self.BiconnectedComponents, id=203) + self.Bind(wx.EVT_MENU, self.KruskalMST, id=212) + + # Layout menu events + self.Bind(wx.EVT_MENU, self.CircleLayout, id=301) + self.Bind(wx.EVT_MENU, self.FruchtermanReingoldLayout, id=302) + self.Bind(wx.EVT_MENU, self.KamadaKawaiLayout, id=303) + + self.SetMenuBar(menuBar) + + def NewGraph(self, event): + graph = bgl.Graph() + position_map = graph.get_vertex_point2d_map("position") + self.canvas.set_graph(graph, position_map, {}) + self.SetTitle("Graph") + + def ErdosRenyiGraph(self, event): + if not self.erdos_renyi_dlg: + self.erdos_renyi_dlg = ErdosRenyiDialog(self, -1, + "Erdos-Renyi Generator") + dlg = self.erdos_renyi_dlg + if dlg.ShowModal() == wx.ID_OK: + graph = bgl.Graph(bgl.ErdosRenyi(dlg.GetNumVertices(), + dlg.GetProbability() / 100), + dlg.GetRandomSeed()) + position_map = graph.get_vertex_point2d_map("position") + bgl.circle_graph_layout(graph, position_map, 50) + self.canvas.set_graph(graph, position_map, {}) + self.SetTitle("Erdos-Renyi Graph (" + + str(dlg.GetNumVertices()) + ", " + + str(dlg.GetProbability() / 100) + ")") + def PLODGraph(self, event): + if not self.plod_dlg: + self.plod_dlg = PLODDialog(self, -1, + "Power Law Out Degree Generator") + dlg = self.plod_dlg + if dlg.ShowModal() == wx.ID_OK: + graph = bgl.Graph(bgl.PowerLawOutDegree(dlg.GetNumVertices(), + dlg.GetAlpha(), + dlg.GetBeta()), + dlg.GetRandomSeed()) + position_map = graph.get_vertex_point2d_map("position") + bgl.circle_graph_layout(graph, position_map, 50) + self.canvas.set_graph(graph, position_map, {}) + self.SetTitle("Power Law Out Degree Graph (" + + str(dlg.GetNumVertices()) + ", " + + str(dlg.GetAlpha()) + ", " + + str(dlg.GetBeta()) + ")") + + def SmallWorldGraph(self, event): + if not self.small_world_dlg: + self.small_world_dlg = SmallWorldDialog(self, -1, + "Small-World Generator") + dlg = self.small_world_dlg + if dlg.ShowModal() == wx.ID_OK: + graph = bgl.Graph(bgl.SmallWorld(dlg.GetNumVertices(), + dlg.GetNumNeighbors(), + dlg.GetProbability() / 100), + dlg.GetRandomSeed()) + position_map = graph.get_vertex_point2d_map("position") + bgl.circle_graph_layout(graph, position_map, 50) + self.canvas.set_graph(graph, position_map, {}) + self.SetTitle("Small-World Graph (" + + str(dlg.GetNumVertices()) + ", " + + str(dlg.GetNumNeighbors()) + ", " + + str(dlg.GetProbability() / 100) + ")") + + def OpenGraph(self, event): + dlg = wx.FileDialog( + self, message="Choose a file", defaultDir=os.getcwd(), + defaultFile="", wildcard=wildcard, style=wx.OPEN | wx.CHANGE_DIR) + if dlg.ShowModal() == wx.ID_OK: + path = dlg.GetPath() + graph = bgl.Graph(path, bgl.file_kind.graphviz) + needs_layout = not graph.has_vertex_map("position") + position_map = graph.get_vertex_point2d_map("position") + if needs_layout: + bgl.circle_graph_layout(graph, position_map, 50) + + self.canvas.set_graph(graph, position_map, + self.canvas.default_property_maps(graph)) + + self.SetTitle(path_to_title(path)) + + dlg.Destroy() + + def SaveGraph(self, event): + dlg = wx.FileDialog( + self, message="Choose a file", defaultDir=os.getcwd(), + defaultFile="", wildcard=wildcard, style=wx.SAVE | wx.CHANGE_DIR) + if dlg.ShowModal() == wx.ID_OK: + path = dlg.GetPath() + # TBD: This fails because we can't serialize Python + # objects. Need to be able to mark some property maps as + # internal (i.e., don't serialize them). + self.canvas.graph.write_graphviz(path) + dlg.Destroy() + + def ConnectedComponents(self, event): + graph = self.canvas.graph + component_map = graph.get_vertex_int_map("component") + bgl.connected_components(graph, component_map) + self.canvas.set_vertex_colors_from_components(component_map) + + def StrongComponents(self, event): + graph = self.canvas.graph + component_map = graph.get_vertex_int_map("component") + bgl.strong_components(graph, component_map) + self.canvas.set_vertex_colors_from_components(component_map) + + def BiconnectedComponents(self, event): + graph = self.canvas.graph + component_map = graph.get_edge_int_map("component") + art_points = bgl.biconnected_components(graph, component_map) + for v in art_points: + self.canvas.VertexShape(v).SetBrush(wx.RED_BRUSH) + self.canvas.set_edge_colors_from_components(component_map) + + def KruskalMST(self, event): + graph = self.canvas.graph + weight_map = graph.get_edge_double_map("weight") + mst_edges = bgl.kruskal_minimum_spanning_tree(graph, weight_map) + for e in mst_edges: + shape = self.canvas.EdgeShape(e) + shape.SetPen(wx.Pen(shape.GetPen().GetColour(), 3)) + self.canvas.Refresh() + + def CircleLayout(self, event): + bgl.circle_graph_layout(self.canvas.graph, self.canvas.position_map, 50) + self.canvas.update_layout() + + def FruchtermanReingoldLayout(self, event): + bgl.fruchterman_reingold_force_directed_layout(self.canvas.graph, + self.canvas.position_map, + width=100, height=100) + self.canvas.update_layout() + + def KamadaKawaiLayout(self, event): + bgl.kamada_kawai_spring_layout(self.canvas.graph, + self.canvas.position_map, side_length=90) + self.canvas.update_layout() + + +class GraphDrawApp(wx.App): + def OnInit(self): + # This creates some pens and brushes that the OGL library uses. + # It should be called after the app object has been created, but + # before OGL is used. + ogl.OGLInitialize() + + self.editor = GraphEditorWindow(None) + self.editor.Show(True) + return True + +app = GraphDrawApp(0) +app.MainLoop()