mirror of
https://github.com/boostorg/graph.git
synced 2025-05-11 05:14:01 +00:00
- Remove use of connected components, so we need only one color map running around - Provide simple overloads, requiring only the graph and the output iterator. boost/graph/detail/sparse_ordering.hpp: - Move out_degree_property_map here boost/graph/fruchterman_reingold.hpp: - Fix enumeration of pairs in grid_force_pairs to repulse vertices in adjacent grid cells (in addition to the current cell), so we don't get vertices forming crosses on grid lines. - Round up the number of rows/columns. libs/graph/doc/cuthill_mckee_ordering.html, libs/graph/doc/king_ordering.html: - Document new overloads libs/graph/doc/gursoy_atun_layout.html: - Add missing semicolon libs/graph/src/python/cuthill_mckee_ordering.cpp, libs/graph/test/cuthill_mckee_ordering.cpp: - Test and use the new overloads libs/graph/build/python/Jamfile, libs/graph/src/python/module.cpp, libs/graph/src/python/king_ordering.cpp: - Support King's ordering algorithm from Python libs/graph/example/python/vis.py: - By default, do a "progressive" Fruchterman-Reingold layout libs/graph/test/king_ordering.cpp: - Test new overload [SVN r28306]
707 lines
27 KiB
Python
707 lines
27 KiB
Python
import bgl
|
|
import wx
|
|
import wx.lib.ogl as ogl
|
|
# import wx.lib.masked
|
|
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("__stored_shape")
|
|
self.edge_to_shape = graph.get_edge_object_map("__stored_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)
|
|
|
|
# Other algorithms...
|
|
algorithmsMenu.Append(221, "Sequential vertex coloring")
|
|
|
|
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)
|
|
self.Bind(wx.EVT_MENU, self.SequentialVertexColoring, id=221)
|
|
|
|
# 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()
|
|
try:
|
|
graph = bgl.Graph(path, bgl.file_kind.graphviz)
|
|
except bgl.directed_graph_error:
|
|
graph = bgl.Digraph(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 SequentialVertexColoring(self, event):
|
|
graph = self.canvas.graph
|
|
color_map = graph.get_vertex_int_map("color")
|
|
bgl.sequential_vertex_coloring(graph, color_map)
|
|
self.canvas.set_vertex_colors_from_components(color_map)
|
|
|
|
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,
|
|
progressive=True)
|
|
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()
|