Mercurial > public > think_complexity
comparison GraphWorld.py @ 2:e87731ed81fc
Adding GraphWorld.py from the website.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Thu, 29 Nov 2012 20:18:30 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1:de3ae15ebbfa | 2:e87731ed81fc |
---|---|
1 """ Code example from Complexity and Computation, a book about | |
2 exploring complexity science with Python. Available free from | |
3 | |
4 http://greenteapress.com/complexity | |
5 | |
6 Copyright 2011 Allen B. Downey. | |
7 Distributed under the GNU General Public License at gnu.org/licenses/gpl.html. | |
8 """ | |
9 | |
10 import string | |
11 import random | |
12 import math | |
13 | |
14 from itertools import chain | |
15 | |
16 try: | |
17 from Gui import Gui, GuiCanvas | |
18 except ImportError: | |
19 from swampy.Gui import Gui, GuiCanvas | |
20 | |
21 from Graph import Vertex | |
22 from Graph import Edge | |
23 from Graph import Graph | |
24 | |
25 | |
26 class GraphCanvas(GuiCanvas): | |
27 """a GraphCanvas is a canvas that knows how to draw Vertices | |
28 and Edges""" | |
29 | |
30 def draw_vertex(self, v, r=0.45): | |
31 """draw a Vertex as a yellow circle with radius (r) | |
32 and text (v.label)""" | |
33 tag = 'v%d' % id(self) | |
34 | |
35 try: | |
36 color = v.color | |
37 except: | |
38 color = 'yellow' | |
39 | |
40 self.circle(v.pos, r, color, tags=tag) | |
41 self.text(v.pos, v.label, 'black', tags=tag) | |
42 return tag | |
43 | |
44 def draw_edge(self, e): | |
45 """draw an Edge as a line between the positions of the | |
46 Vertices it connects""" | |
47 v, w = e | |
48 tag = self.line([v.pos, w.pos]) | |
49 return tag | |
50 | |
51 | |
52 class GraphWorld(Gui): | |
53 """GraphWorld is a Gui that has a Graph Canvas and control buttons.""" | |
54 | |
55 def __init__(self): | |
56 Gui.__init__(self) | |
57 self.title('GraphWorld') | |
58 self.setup() | |
59 | |
60 def setup(self): | |
61 """Create the widgets.""" | |
62 self.ca_width = 400 | |
63 self.ca_height = 400 | |
64 xscale = self.ca_width / 20 | |
65 yscale = self.ca_height / 20 | |
66 | |
67 # canvas | |
68 self.col() | |
69 self.canvas = self.widget(GraphCanvas, scale=[xscale, yscale], | |
70 width=self.ca_width, height=self.ca_height, | |
71 bg='white') | |
72 | |
73 # buttons | |
74 self.row() | |
75 self.bu(text='Clear', command=self.clear) | |
76 self.bu(text='Quit', command=self.quit) | |
77 self.endrow() | |
78 | |
79 def show_graph(self, g, layout): | |
80 """Draws the Vertices and Edges of Graph (g) using the | |
81 positions in Layout (layout). | |
82 """ | |
83 | |
84 # copy the positions from the layout into the Vertex objects | |
85 for v in g.vertices(): | |
86 v.pos = layout.pos(v) | |
87 | |
88 # draw the edges and store the tags in self.etags, which maps | |
89 # from Edges to their tags | |
90 c = self.canvas | |
91 self.etags = {} | |
92 for v in g: | |
93 self.etags[v] = [c.draw_edge(e) for e in g.out_edges(v)] | |
94 | |
95 # draw the vertices and store their tags in a list | |
96 self.vtags = [c.draw_vertex(v) for v in g] | |
97 | |
98 def clear(self): | |
99 """Delete all canvas items.""" | |
100 tags = chain(self.vtags, *self.etags.itervalues()) | |
101 for tag in tags: | |
102 self.canvas.delete(tag) | |
103 | |
104 | |
105 class Layout(dict): | |
106 """A Layout is a mapping from vertices to positions in 2-D space.""" | |
107 | |
108 def __init__(self, g): | |
109 for v in g.vertices(): | |
110 self[v] = (0, 0) | |
111 | |
112 def pos(self, v): | |
113 """Returns the position of this Vertex as a tuple.""" | |
114 return self[v] | |
115 | |
116 def distance_between(self, v1, v2): | |
117 """Computes the Euclidean distance between two vertices.""" | |
118 x1, y1 = self.pos(v1) | |
119 x2, y2 = self.pos(v2) | |
120 dx = x1 - x2 | |
121 dy = y1 - y2 | |
122 return math.sqrt(dx**2 + dy**2) | |
123 | |
124 def sort_by_distance(self, v, others): | |
125 """Returns a list of the vertices in others sorted in | |
126 increasing order by their distance from v.""" | |
127 t = [(self.distance_between(v, w), w) for w in others] | |
128 t.sort() | |
129 return [w for (d, w) in t] | |
130 | |
131 | |
132 class CircleLayout(Layout): | |
133 """Creates a layout for a graph with the vertices equally | |
134 spaced around the perimeter of a circle.""" | |
135 | |
136 def __init__(self, g, radius=9): | |
137 """Creates a layout for Graph (g)""" | |
138 vs = g.vertices() | |
139 theta = math.pi * 2 / len(vs) | |
140 for i, v in enumerate(vs): | |
141 x = radius * math.cos(i * theta) | |
142 y = radius * math.sin(i * theta) | |
143 self[v] = (x, y) | |
144 | |
145 | |
146 class RandomLayout(Layout): | |
147 """Create a layout with each Vertex at a random position in | |
148 [[-max, -max], [max, max]].""" | |
149 | |
150 def __init__(self, g, max=10): | |
151 """Creates a layout for Graph (g)""" | |
152 self.max = max | |
153 for v in g.vertices(): | |
154 self[v] = self.random_pos() | |
155 | |
156 def random_pos(self): | |
157 """choose a random position and return it as a tuple""" | |
158 x = random.uniform(-self.max, self.max) | |
159 y = random.uniform(-self.max, self.max) | |
160 return x, y | |
161 | |
162 def spread_vertex(self, v, others, min_dist=1.0): | |
163 """Keep choosing random positions for v until it is at least | |
164 min_dist units from the vertices in others. | |
165 | |
166 Each time it fails, it relaxes min_dist by 10%. | |
167 """ | |
168 while True: | |
169 t = [(self.distance_between(v, w), w) for w in others] | |
170 d, w = min(t) | |
171 if d > min_dist: | |
172 break | |
173 min_dist *= 0.9 | |
174 self[v] = self.random_pos() | |
175 | |
176 def spread_vertices(self): | |
177 """Moves the vertices around until no two are closer together | |
178 than a minimum distance.""" | |
179 vs = self.keys() | |
180 others = vs[:] | |
181 for v in vs: | |
182 others.remove(v) | |
183 self.spread_vertex(v, others) | |
184 others.append(v) | |
185 | |
186 | |
187 | |
188 def main(script, n='10', *args): | |
189 | |
190 # create n Vertices | |
191 n = int(n) | |
192 labels = string.ascii_lowercase + string.ascii_uppercase | |
193 vs = [Vertex(c) for c in labels[:n]] | |
194 | |
195 # create a graph and a layout | |
196 g = Graph(vs) | |
197 g.add_all_edges() | |
198 layout = CircleLayout(g) | |
199 | |
200 # draw the graph | |
201 gw = GraphWorld() | |
202 gw.show_graph(g, layout) | |
203 gw.mainloop() | |
204 | |
205 | |
206 if __name__ == '__main__': | |
207 import sys | |
208 main(*sys.argv) | |
209 | |
210 |