Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/graphs/graph_plot.py
4045 views
1
"""
2
Graph Plotting
3
"""
4
5
#*****************************************************************************
6
# Copyright (C) 2009 Emily Kirkman
7
# 2009 Robert L. Miller <[email protected]>
8
#
9
# Distributed under the terms of the GNU General Public License (GPL)
10
#
11
# This code is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
# General Public License for more details.
15
#
16
# The full text of the GPL is available at:
17
#
18
# http://www.gnu.org/licenses/
19
#*****************************************************************************
20
from sage.structure.sage_object import SageObject
21
from sage.plot.all import Graphics, scatter_plot, bezier_path, line, arrow, text, circle
22
from sage.misc.decorators import options
23
from math import sqrt, cos, sin, atan, pi
24
25
layout_options = {
26
'layout': 'A layout algorithm -- one of "acyclic", "circular", "ranked", "graphviz", "planar", "spring", or "tree".',
27
'iterations': 'The number of times to execute the spring layout algorithm.',
28
'heights': 'A dictionary mapping heights to the list of vertices at this height.',
29
'spring': 'Use spring layout to finalize the current layout.',
30
'tree_root': 'A vertex designation for drawing trees.',
31
'tree_orientation': 'The direction of tree branches -- "up" or "down".',
32
'save_pos': 'Whether or not to save the computed position for the graph.',
33
'dim': 'The dimension of the layout -- 2 or 3.',
34
'prog': 'Which graphviz layout program to use -- one of "circo", "dot", "fdp", "neato", or "twopi".',
35
'by_component': 'Whether to do the spring layout by connected component -- a boolean.',
36
}
37
38
graphplot_options = layout_options.copy()
39
graphplot_options.update(
40
{'pos': 'The position dictionary of vertices',
41
'vertex_labels': 'Whether or not to draw vertex labels.',
42
'vertex_colors': 'Dictionary of vertex coloring.',
43
'vertex_size': 'The size to draw the vertices.',
44
'vertex_shape': 'The shape to draw the vertices, Currently unavailable for Multi-edged DiGraphs.',
45
'edge_labels': 'Whether or not to draw edge labels.',
46
'edge_style': 'The linestyle of the edges-- one of "solid", "dashed", "dotted", dashdot".',
47
'edge_color': 'The default color for edges.',
48
'edge_colors': 'Dictionary of edge coloring.',
49
'color_by_label': 'Whether or not to color the edges by their label values.',
50
'partition': 'A partition of the vertex set. (Draws each cell of vertices in a different color).',
51
'loop_size': 'The radius of the smallest loop.',
52
'dist': 'The distance between multiedges.',
53
'max_dist': 'The max distance range to allow multiedges.',
54
'talk': 'Whether to display the vertices in talk mode (larger and white)',
55
'graph_border': 'Whether or not to draw a frame around the graph.'})
56
57
class GraphPlot(SageObject):
58
def __init__(self, graph, options):
59
"""
60
Returns a ``GraphPlot`` object, which stores all the parameters needed for
61
plotting (Di)Graphs. A ``GraphPlot`` has a plot and show function, as well
62
as some functions to set parameters for vertices and edges. This constructor
63
assumes default options are set. Defaults are shown in the example below.
64
65
EXAMPLE::
66
67
sage: from sage.graphs.graph_plot import GraphPlot
68
sage: options = {
69
... 'vertex_size':200,
70
... 'vertex_labels':True,
71
... 'layout':None,
72
... 'edge_style':'solid',
73
... 'edge_color':'black',
74
... 'edge_colors':None,
75
... 'edge_labels':False,
76
... 'iterations':50,
77
... 'tree_orientation':'down',
78
... 'heights':None,
79
... 'graph_border':False,
80
... 'talk':False,
81
... 'color_by_label':False,
82
... 'partition':None,
83
... 'dist':.075,
84
... 'max_dist':1.5,
85
... 'loop_size':.075}
86
sage: g = Graph({0:[1,2], 2:[3], 4:[0,1]})
87
sage: GP = GraphPlot(g, options)
88
89
TESTS::
90
91
sage: g = graphs.CompleteGraph(2); g.show()
92
93
"""
94
self._plot_components = {}
95
self._nodelist = graph.vertices()
96
self._graph = graph
97
self._options = options
98
self.set_pos()
99
self._arcs = self._graph.has_multiple_edges(to_undirected=True)
100
self._loops = self._graph.has_loops()
101
if self._graph.is_directed() and self._arcs:
102
self._arcdigraph = True
103
else:
104
self._arcdigraph = False
105
self.set_vertices()
106
self.set_edges()
107
108
def _repr_(self):
109
"""
110
Returns a string representation of a ``GraphPlot`` object.
111
112
EXAMPLE:
113
114
This function is called implicitly by the code below::
115
116
sage: g = Graph({0:[1,2], 2:[3], 4:[0,1]})
117
sage: g.graphplot() # indirect doctest
118
GraphPlot object for Graph on 5 vertices
119
"""
120
return "GraphPlot object for %s"%self._graph
121
122
def set_pos(self):
123
"""
124
Sets the position plotting parameters for this GraphPlot.
125
126
EXAMPLES:
127
128
This function is called implicitly by the code below::
129
130
sage: g = Graph({0:[1,2], 2:[3], 4:[0,1]})
131
sage: g.graphplot(save_pos=True, layout='circular') # indirect doctest
132
GraphPlot object for Graph on 5 vertices
133
134
The following illustrates the format of a position dictionary,
135
but due to numerical noise we do not check the values themselves::
136
137
sage: g.get_pos()
138
{0: [...e-17, 1.0],
139
1: [-0.951..., 0.309...],
140
2: [-0.587..., -0.809...],
141
3: [0.587..., -0.809...],
142
4: [0.951..., 0.309...]}
143
144
::
145
146
sage: T = list(graphs.trees(7))
147
sage: t = T[3]
148
sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]})
149
150
TESTS:
151
152
Make sure that vertex locations are floats. Not being floats
153
isn't a bug in itself but makes it too easy to accidentally
154
introduce a bug elsewhere, such as in :meth:`set_edges` (:trac:`10124`),
155
via silent truncating division of integers::
156
157
sage: g = graphs.FruchtGraph()
158
sage: gp = g.graphplot()
159
sage: set(map(type, flatten(gp._pos.values())))
160
set([<type 'float'>])
161
sage: g = graphs.BullGraph()
162
sage: gp = g.graphplot(save_pos=True)
163
sage: set(map(type, flatten(gp._pos.values())))
164
set([<type 'float'>])
165
166
"""
167
self._pos = self._graph.layout(**self._options)
168
# make sure the positions are floats (trac #10124)
169
self._pos = dict((k,(float(v[0]), float(v[1]))) for k,v in self._pos.iteritems())
170
171
def set_vertices(self, **vertex_options):
172
"""
173
Sets the vertex plotting parameters for this ``GraphPlot``. This function
174
is called by the constructor but can also be called to make updates to
175
the vertex options of an existing ``GraphPlot`` object. Note that the
176
changes are cumulative.
177
178
EXAMPLES::
179
180
sage: g = Graph({}, loops=True, multiedges=True, sparse=True)
181
sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
182
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
183
sage: GP = g.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, edge_style='dashed')
184
sage: GP.set_vertices(talk=True)
185
sage: GP.plot()
186
sage: GP.set_vertices(vertex_colors='pink', vertex_shape='^')
187
sage: GP.plot()
188
"""
189
# Handle base vertex options
190
voptions = {}
191
192
for arg in vertex_options:
193
self._options[arg] = vertex_options[arg]
194
195
# First set defaults for styles
196
vertex_colors = None
197
if self._options['talk']:
198
voptions['markersize'] = 500
199
if self._options['partition'] is None:
200
vertex_colors = '#ffffff'
201
else:
202
voptions['markersize'] = self._options['vertex_size']
203
204
if 'vertex_colors' not in self._options or self._options['vertex_colors'] is None:
205
if self._options['partition'] is not None:
206
from sage.plot.colors import rainbow,rgbcolor
207
partition = self._options['partition']
208
l = len(partition)
209
R = rainbow(l)
210
vertex_colors = {}
211
for i in range(l):
212
vertex_colors[R[i]] = partition[i]
213
elif len(self._graph._boundary) != 0:
214
vertex_colors = {}
215
bdy_verts = []
216
int_verts = []
217
for v in self._graph.vertex_iterator():
218
if v in self._graph._boundary:
219
bdy_verts.append(v)
220
else:
221
int_verts.append(v)
222
vertex_colors['#fec7b8'] = int_verts
223
vertex_colors['#b3e8ff'] = bdy_verts
224
elif not vertex_colors:
225
vertex_colors='#fec7b8'
226
else:
227
vertex_colors = self._options['vertex_colors']
228
229
if 'vertex_shape' in self._options:
230
voptions['marker'] = self._options['vertex_shape']
231
232
if self._graph.is_directed():
233
self._vertex_radius = sqrt(voptions['markersize']/pi)
234
self._arrowshorten = 2*self._vertex_radius
235
if self._arcdigraph:
236
self._vertex_radius = sqrt(voptions['markersize']/(20500*pi))
237
238
voptions['zorder'] = 7
239
240
if not isinstance(vertex_colors, dict):
241
voptions['facecolor'] = vertex_colors
242
if self._arcdigraph:
243
self._plot_components['vertices'] = [circle(center,
244
self._vertex_radius, fill=True, facecolor=vertex_colors, edgecolor='black', clip=False)
245
for center in self._pos.values()]
246
else:
247
self._plot_components['vertices'] = scatter_plot(
248
self._pos.values(), clip=False, **voptions)
249
else:
250
# Color list must be ordered:
251
pos = []
252
colors = []
253
for i in vertex_colors:
254
pos += [self._pos[j] for j in vertex_colors[i]]
255
colors += [i]*len(vertex_colors[i])
256
257
# If all the vertices have not been assigned a color
258
if len(self._pos)!=len(pos):
259
from sage.plot.colors import rainbow,rgbcolor
260
vertex_colors_rgb=[rgbcolor(c) for c in vertex_colors]
261
for c in rainbow(len(vertex_colors)+1):
262
if rgbcolor(c) not in vertex_colors_rgb:
263
break
264
leftovers=[j for j in self._pos.values() if j not in pos]
265
pos+=leftovers
266
colors+=[c]*len(leftovers)
267
268
if self._arcdigraph:
269
self._plot_components['vertices'] = [circle(pos[i],
270
self._vertex_radius, fill=True, facecolor=colors[i], edgecolor='black', clip=False)
271
for i in range(len(pos))]
272
else:
273
self._plot_components['vertices'] = scatter_plot(pos,
274
facecolor=colors, clip=False, **voptions)
275
276
if self._options['vertex_labels']:
277
self._plot_components['vertex_labels'] = []
278
# TODO: allow text options
279
for v in self._nodelist:
280
self._plot_components['vertex_labels'].append(text(str(v),
281
self._pos[v], rgbcolor=(0,0,0), zorder=8))
282
283
def set_edges(self, **edge_options):
284
"""
285
Sets the edge (or arrow) plotting parameters for the ``GraphPlot`` object.
286
This function is called by the constructor but can also be called to make
287
updates to the vertex options of an existing ``GraphPlot`` object. Note
288
that the changes are cumulative.
289
290
EXAMPLES::
291
292
sage: g = Graph({}, loops=True, multiedges=True, sparse=True)
293
sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
294
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
295
sage: GP = g.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, edge_style='dashed')
296
sage: GP.set_edges(edge_style='solid')
297
sage: GP.plot()
298
sage: GP.set_edges(edge_color='black')
299
sage: GP.plot()
300
301
sage: d = DiGraph({}, loops=True, multiedges=True, sparse=True)
302
sage: d.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
303
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
304
sage: GP = d.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, edge_style='dashed')
305
sage: GP.set_edges(edge_style='solid')
306
sage: GP.plot()
307
sage: GP.set_edges(edge_color='black')
308
sage: GP.plot()
309
310
TESTS::
311
312
sage: G = Graph("Fooba")
313
sage: G.show(edge_colors={'red':[(3,6),(2,5)]})
314
315
Verify that default edge labels are pretty close to being between the vertices
316
in some cases where they weren't due to truncating division (:trac:`10124`)::
317
318
sage: test_graphs = graphs.FruchtGraph(), graphs.BullGraph()
319
sage: tol = 0.001
320
sage: for G in test_graphs:
321
... E=G.edges()
322
... for e0, e1, elab in E:
323
... G.set_edge_label(e0, e1, '%d %d' % (e0, e1))
324
... gp = G.graphplot(save_pos=True,edge_labels=True)
325
... vx = gp._plot_components['vertices'][0].xdata
326
... vy = gp._plot_components['vertices'][0].ydata
327
... for elab in gp._plot_components['edge_labels']:
328
... textobj = elab[0]
329
... x, y, s = textobj.x, textobj.y, textobj.string
330
... v0, v1 = map(int, s.split())
331
... vn = vector(((x-(vx[v0]+vx[v1])/2.),y-(vy[v0]+vy[v1])/2.)).norm()
332
... assert vn < tol
333
334
335
"""
336
for arg in edge_options:
337
self._options[arg] = edge_options[arg]
338
if 'edge_colors' in edge_options: self._options['color_by_label'] = False
339
340
# Handle base edge options: thickness, linestyle
341
eoptions={}
342
if 'edge_style' in self._options:
343
eoptions['linestyle'] = self._options['edge_style']
344
if 'thickness' in self._options:
345
eoptions['thickness'] = self._options['thickness']
346
347
# Set labels param to add labels on the fly
348
labels = False
349
if self._options['edge_labels']:
350
labels = True
351
self._plot_components['edge_labels'] = []
352
353
# Make dict collection of all edges (keep label and edge color)
354
edges_to_draw = {}
355
if self._options['color_by_label'] or isinstance(self._options['edge_colors'], dict):
356
if self._options['color_by_label']: edge_colors = self._graph._color_by_label()
357
else: edge_colors = self._options['edge_colors']
358
for color in edge_colors:
359
for edge in edge_colors[color]:
360
key = tuple(sorted([edge[0],edge[1]]))
361
if key == (edge[0],edge[1]): head = 1
362
else: head = 0
363
364
if len(edge) < 3:
365
label = self._graph.edge_label(edge[0],edge[1])
366
if isinstance(label, list):
367
if key in edges_to_draw:
368
edges_to_draw[key].append((label[-1], color, head))
369
else:
370
edges_to_draw[key] = [(label[-1], color, head)]
371
for i in range(len(label)-1):
372
edges_to_draw[key].append((label[-1], color, head))
373
else:
374
label = edge[2]
375
376
if key in edges_to_draw:
377
edges_to_draw[key].append((label, color, head))
378
else:
379
edges_to_draw[key] = [(label, color, head)]
380
# add unspecified edges in (default color black)
381
for edge in self._graph.edge_iterator():
382
key = tuple(sorted([edge[0],edge[1]]))
383
label = edge[2]
384
specified = False
385
if key in edges_to_draw:
386
for old_label, old_color, old_head in edges_to_draw[key]:
387
if label == old_label:
388
specified = True
389
break
390
if not specified:
391
if key == (edge[0],edge[1]): head = 1
392
else: head = 0
393
edges_to_draw[key] = [(label, 'black', head)]
394
else:
395
for edge in self._graph.edges(sort=True):
396
key = tuple(sorted([edge[0],edge[1]]))
397
if key == (edge[0],edge[1]): head = 1
398
else: head = 0
399
if key in edges_to_draw:
400
edges_to_draw[key].append((edge[2], self._options['edge_color'], head))
401
else:
402
edges_to_draw[key] = [(edge[2], self._options['edge_color'], head)]
403
404
if edges_to_draw:
405
self._plot_components['edges'] = []
406
else:
407
return
408
409
# Check for multi-edges or loops
410
if self._arcs or self._loops:
411
tmp = edges_to_draw.copy()
412
dist = self._options['dist']*2.
413
loop_size = self._options['loop_size']
414
max_dist = self._options['max_dist']
415
from sage.functions.all import sqrt
416
for (a,b) in tmp:
417
if a == b:
418
# Loops
419
distance = dist
420
local_labels = edges_to_draw.pop((a,b))
421
if len(local_labels)*dist > max_dist:
422
distance = float(max_dist)/len(local_labels)
423
curr_loop_size = loop_size
424
for i in range(len(local_labels)):
425
self._plot_components['edges'].append(circle((self._pos[a][0],self._pos[a][1]-curr_loop_size), curr_loop_size, rgbcolor=local_labels[i][1], **eoptions))
426
if labels:
427
self._plot_components['edge_labels'].append(text(local_labels[i][0], (self._pos[a][0], self._pos[a][1]-2*curr_loop_size)))
428
curr_loop_size += distance/4
429
elif len(edges_to_draw[(a,b)]) > 1:
430
# Multi-edge
431
local_labels = edges_to_draw.pop((a,b))
432
433
# Compute perpendicular bisector
434
p1 = self._pos[a]
435
p2 = self._pos[b]
436
M = ((p1[0]+p2[0])/2., (p1[1]+p2[1])/2.) # midpoint
437
if not p1[1] == p2[1]:
438
S = float(p1[0]-p2[0])/(p2[1]-p1[1]) # perp slope
439
y = lambda x : S*x-S*M[0]+M[1] # perp bisector line
440
441
# f,g are functions of distance d to determine x values
442
# on line y at d from point M
443
f = lambda d : sqrt(d**2/(1.+S**2)) + M[0]
444
g = lambda d : -sqrt(d**2/(1.+S**2)) + M[0]
445
446
odd_x = f
447
even_x = g
448
if p1[0] == p2[0]:
449
odd_y = lambda d : M[1]
450
even_y = odd_y
451
else:
452
odd_y = lambda x : y(f(x))
453
even_y = lambda x : y(g(x))
454
else:
455
odd_x = lambda d : M[0]
456
even_x = odd_x
457
odd_y = lambda d : M[1] + d
458
even_y = lambda d : M[1] - d
459
460
# We now have the control points for each bezier curve
461
# in terms of distance parameter d.
462
# Also note that the label for each edge should be drawn at d/2.
463
# (This is because we're using the perp bisectors).
464
distance = dist
465
if len(local_labels)*dist > max_dist:
466
distance = float(max_dist)/len(local_labels)
467
for i in range(len(local_labels)/2):
468
k = (i+1.0)*distance
469
if self._arcdigraph:
470
odd_start = self._polar_hack_for_multidigraph(p1, [odd_x(k),odd_y(k)], self._vertex_radius)[0]
471
odd_end = self._polar_hack_for_multidigraph([odd_x(k),odd_y(k)], p2, self._vertex_radius)[1]
472
even_start = self._polar_hack_for_multidigraph(p1, [even_x(k),even_y(k)], self._vertex_radius)[0]
473
even_end = self._polar_hack_for_multidigraph([even_x(k),even_y(k)], p2, self._vertex_radius)[1]
474
self._plot_components['edges'].append(arrow(path=[[odd_start,[odd_x(k),odd_y(k)],odd_end]], head=local_labels[2*i][2], zorder=1, rgbcolor=local_labels[2*i][1], **eoptions))
475
self._plot_components['edges'].append(arrow(path=[[even_start,[even_x(k),even_y(k)],even_end]], head=local_labels[2*i+1][2], zorder=1, rgbcolor=local_labels[2*i+1][1], **eoptions))
476
else:
477
self._plot_components['edges'].append(bezier_path([[p1,[odd_x(k),odd_y(k)],p2]],zorder=1, rgbcolor=local_labels[2*i][1], **eoptions))
478
self._plot_components['edges'].append(bezier_path([[p1,[even_x(k),even_y(k)],p2]],zorder=1, rgbcolor=local_labels[2*i+1][1], **eoptions))
479
if labels:
480
j = k/2.0
481
self._plot_components['edge_labels'].append(text(local_labels[2*i][0],[odd_x(j),odd_y(j)]))
482
self._plot_components['edge_labels'].append(text(local_labels[2*i+1][0],[even_x(j),even_y(j)]))
483
if len(local_labels)%2 == 1:
484
edges_to_draw[(a,b)] = [local_labels[-1]] # draw line for last odd
485
486
dir = self._graph.is_directed()
487
for (a,b) in edges_to_draw:
488
if self._arcdigraph:
489
C,D = self._polar_hack_for_multidigraph(self._pos[a], self._pos[b], self._vertex_radius)
490
self._plot_components['edges'].append(arrow(C,D, rgbcolor=edges_to_draw[(a,b)][0][1], head=edges_to_draw[(a,b)][0][2], **eoptions))
491
if labels:
492
self._plot_components['edge_labels'].append(text(str(edges_to_draw[(a,b)][0][0]),[(C[0]+D[0])/2., (C[1]+D[1])/2.]))
493
elif dir:
494
self._plot_components['edges'].append(arrow(self._pos[a],self._pos[b], rgbcolor=edges_to_draw[(a,b)][0][1], arrowshorten=self._arrowshorten, head=edges_to_draw[(a,b)][0][2], **eoptions))
495
else:
496
self._plot_components['edges'].append(line([self._pos[a],self._pos[b]], rgbcolor=edges_to_draw[(a,b)][0][1], **eoptions))
497
if labels and not self._arcdigraph:
498
self._plot_components['edge_labels'].append(text(str(edges_to_draw[(a,b)][0][0]),[(self._pos[a][0]+self._pos[b][0])/2., (self._pos[a][1]+self._pos[b][1])/2.]))
499
500
def _polar_hack_for_multidigraph(self, A, B, VR):
501
"""
502
Helper function to quickly compute the two points of intersection of a line
503
segment from A to B (xy tuples) and circles centered at A and B, both with
504
radius VR. Returns a tuple of xy tuples representing the two points.
505
506
EXAMPLE::
507
508
sage: d = DiGraph({}, loops=True, multiedges=True, sparse=True)
509
sage: d.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
510
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
511
sage: GP = d.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, edge_style='dashed')
512
sage: GP._polar_hack_for_multidigraph((0,1),(1,1),.1)
513
([0.10..., 1.00...], [0.90..., 1.00...])
514
515
TESTS:
516
517
Make sure that Python ints are acceptable arguments (:trac:`10124`)::
518
519
sage: GP = DiGraph().graphplot()
520
sage: GP._polar_hack_for_multidigraph((0, 1), (2, 2), .1)
521
([0.08..., 1.04...], [1.91..., 1.95...])
522
sage: GP._polar_hack_for_multidigraph((int(0),int(1)),(int(2),int(2)),.1)
523
([0.08..., 1.04...], [1.91..., 1.95...])
524
525
"""
526
D = [float(B[i]-A[i]) for i in range(2)]
527
R = sqrt(D[0]**2+D[1]**2)
528
theta = 3*pi/2
529
if D[0] > 0:
530
theta = atan(D[1]/D[0])
531
if D[1] < 0:
532
theta += 2*pi
533
elif D[0] < 0:
534
theta = atan(D[1]/D[0]) + pi
535
elif D[1] > 0:
536
theta = pi/2
537
return ([VR*cos(theta)+A[0], VR*sin(theta)+A[1]], [(R-VR)*cos(theta)+A[0], (R-VR)*sin(theta)+A[1]])
538
539
def show(self, **kwds):
540
"""
541
Shows the (Di)Graph associated with this ``GraphPlot`` object.
542
543
For syntax and lengthy documentation, see :meth:`GraphPlot.plot`.
544
Any options not used by plot will be passed on to the
545
:meth:`~sage.plot.plot.Graphics.show` method.
546
547
EXAMPLE::
548
549
sage: C = graphs.CubeGraph(8)
550
sage: P = C.graphplot(vertex_labels=False, vertex_size=0, graph_border=True)
551
sage: P.show()
552
"""
553
self.plot().show(**kwds)
554
555
def plot(self, **kwds):
556
"""
557
Returns a graphics object representing the (di)graph.
558
559
INPUT:
560
561
- ``pos`` -- an optional positioning dictionar
562
563
- ``layout`` -- what kind of layout to use, takes precedence over ``pos``
564
565
- 'circular' -- plots the graph with vertices evenly distributed
566
on a circle
567
568
- 'spring' -- uses the traditional spring layout, using the
569
graph's current positions as initial positions
570
571
- 'tree' -- the (di)graph must be a tree. One can specify the root
572
of the tree using the keyword ``tree_root``, otherwise a root
573
will be selected at random. Then the tree will be plotted in
574
levels, depending on minimum distance for the root.
575
576
- ``vertex_labels`` -- whether to print vertex labels
577
578
- ``edge_labels`` -- whether to print edge labels. By default, ``False``,
579
but if ``True``, the result of ``str(l)`` is printed on the edge for
580
each label l. Labels equal to None are not printed (to set edge
581
labels, see :meth:`~sage.graphs.generic_graph.GenericGraph.set_edge_label`).
582
583
- ``vertex_size`` -- size of vertices displayed
584
585
- ``vertex_shape`` -- the shape to draw the vertices (Not available for
586
multiedge digraphs.
587
588
- ``graph_border`` -- whether to include a box around the graph
589
590
- ``vertex_colors`` -- optional dictionary to specify vertex colors: each
591
key is a color recognizable by matplotlib, and each corresponding
592
entry is a list of vertices. If a vertex is not listed, it looks
593
invisible on the resulting plot (it doesn't get drawn).
594
595
- ``edge_colors`` -- a dictionary specifying edge colors: each key is a
596
color recognized by matplotlib, and each entry is a list of edges.
597
598
- ``partition`` -- a partition of the vertex set. if specified, plot will
599
show each cell in a different color. vertex_colors takes precedence.
600
601
- ``talk`` -- if ``True``, prints large vertices with white backgrounds
602
so that labels are legible on slides
603
604
- ``iterations`` -- how many iterations of the spring layout algorithm to
605
go through, if applicable
606
607
- ``color_by_label`` -- if ``True``, color edges by their labels
608
609
- ``heights`` -- if specified, this is a dictionary from a set of
610
floating point heights to a set of vertices
611
612
- ``edge_style`` -- keyword arguments passed into the
613
edge-drawing routine. This currently only works for
614
directed graphs, since we pass off the undirected graph to
615
networkx
616
617
- ``tree_root`` -- a vertex of the tree to be used as the root for
618
the ``layout="tree"`` option. If no root is specified, then one
619
is chosen at random. Ignored unless ``layout='tree'``.
620
621
- ``tree_orientation`` -- "up" or "down" (default is "down").
622
If "up" (resp., "down"), then the root of the tree will
623
appear on the bottom (resp., top) and the tree will grow
624
upwards (resp. downwards). Ignored unless ``layout='tree'``.
625
626
- ``save_pos`` -- save position computed during plotting
627
628
EXAMPLES:
629
630
Let's list all possible options::
631
632
sage: from sage.graphs.graph_plot import graphplot_options
633
sage: list(sorted(graphplot_options.iteritems()))
634
[('by_component', 'Whether to do the spring layout by connected component -- a boolean.'),
635
('color_by_label', 'Whether or not to color the edges by their label values.'),
636
('dim', 'The dimension of the layout -- 2 or 3.'),
637
('dist', 'The distance between multiedges.'),
638
('edge_color', 'The default color for edges.'),
639
('edge_colors', 'Dictionary of edge coloring.'),
640
('edge_labels', 'Whether or not to draw edge labels.'),
641
('edge_style', 'The linestyle of the edges-- one of "solid", "dashed", "dotted", dashdot".'),
642
('graph_border', 'Whether or not to draw a frame around the graph.'),
643
('heights', 'A dictionary mapping heights to the list of vertices at this height.'),
644
('iterations', 'The number of times to execute the spring layout algorithm.'),
645
('layout', 'A layout algorithm -- one of "acyclic", "circular", "ranked", "graphviz", "planar", "spring", or "tree".'),
646
('loop_size', 'The radius of the smallest loop.'),
647
('max_dist', 'The max distance range to allow multiedges.'),
648
('partition', 'A partition of the vertex set. (Draws each cell of vertices in a different color).'),
649
('pos', 'The position dictionary of vertices'),
650
('prog', 'Which graphviz layout program to use -- one of "circo", "dot", "fdp", "neato", or "twopi".'),
651
('save_pos', 'Whether or not to save the computed position for the graph.'),
652
('spring', 'Use spring layout to finalize the current layout.'),
653
('talk', 'Whether to display the vertices in talk mode (larger and white)'),
654
('tree_orientation', 'The direction of tree branches -- "up" or "down".'),
655
('tree_root', 'A vertex designation for drawing trees.'),
656
('vertex_colors', 'Dictionary of vertex coloring.'),
657
('vertex_labels', 'Whether or not to draw vertex labels.'),
658
('vertex_shape', 'The shape to draw the vertices, Currently unavailable for Multi-edged DiGraphs.'),
659
('vertex_size', 'The size to draw the vertices.')]
660
661
662
We can specify some pretty precise plotting of familiar graphs::
663
664
sage: from math import sin, cos, pi
665
sage: P = graphs.PetersenGraph()
666
sage: d = {'#FF0000':[0,5], '#FF9900':[1,6], '#FFFF00':[2,7], '#00FF00':[3,8], '#0000FF':[4,9]}
667
sage: pos_dict = {}
668
sage: for i in range(5):
669
... x = float(cos(pi/2 + ((2*pi)/5)*i))
670
... y = float(sin(pi/2 + ((2*pi)/5)*i))
671
... pos_dict[i] = [x,y]
672
...
673
sage: for i in range(10)[5:]:
674
... x = float(0.5*cos(pi/2 + ((2*pi)/5)*i))
675
... y = float(0.5*sin(pi/2 + ((2*pi)/5)*i))
676
... pos_dict[i] = [x,y]
677
...
678
sage: pl = P.graphplot(pos=pos_dict, vertex_colors=d)
679
sage: pl.show()
680
681
Here are some more common graphs with typical options::
682
683
sage: C = graphs.CubeGraph(8)
684
sage: P = C.graphplot(vertex_labels=False, vertex_size=0, graph_border=True)
685
sage: P.show()
686
687
sage: G = graphs.HeawoodGraph().copy(sparse=True)
688
sage: for u,v,l in G.edges():
689
... G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')')
690
sage: G.graphplot(edge_labels=True).show()
691
692
The options for plotting also work with directed graphs::
693
694
sage: D = DiGraph( { 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13], 13: [14], 14: [15], 15: [16], 16: [17], 17: [18], 18: [19], 19: []}, implementation='networkx' )
695
sage: for u,v,l in D.edges():
696
... D.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')')
697
sage: D.graphplot(edge_labels=True, layout='circular').show()
698
699
700
This example shows off the coloring of edges::
701
702
sage: from sage.plot.colors import rainbow
703
sage: C = graphs.CubeGraph(5)
704
sage: R = rainbow(5)
705
sage: edge_colors = {}
706
sage: for i in range(5):
707
... edge_colors[R[i]] = []
708
sage: for u,v,l in C.edges():
709
... for i in range(5):
710
... if u[i] != v[i]:
711
... edge_colors[R[i]].append((u,v,l))
712
sage: C.graphplot(vertex_labels=False, vertex_size=0, edge_colors=edge_colors).show()
713
714
715
With the ``partition`` option, we can separate out same-color groups
716
of vertices::
717
718
sage: D = graphs.DodecahedralGraph()
719
sage: Pi = [[6,5,15,14,7],[16,13,8,2,4],[12,17,9,3,1],[0,19,18,10,11]]
720
sage: D.show(partition=Pi)
721
722
Loops are also plotted correctly::
723
724
sage: G = graphs.PetersenGraph()
725
sage: G.allow_loops(True)
726
sage: G.add_edge(0,0)
727
sage: G.show()
728
729
::
730
731
sage: D = DiGraph({0:[0,1], 1:[2], 2:[3]}, loops=True)
732
sage: D.show()
733
sage: D.show(edge_colors={(0,1,0):[(0,1,None),(1,2,None)],(0,0,0):[(2,3,None)]})
734
735
More options::
736
737
sage: pos = {0:[0.0, 1.5], 1:[-0.8, 0.3], 2:[-0.6, -0.8], 3:[0.6, -0.8], 4:[0.8, 0.3]}
738
sage: g = Graph({0:[1], 1:[2], 2:[3], 3:[4], 4:[0]})
739
sage: g.graphplot(pos=pos, layout='spring', iterations=0).plot()
740
741
sage: G = Graph()
742
sage: P = G.graphplot().plot()
743
sage: P.axes()
744
False
745
sage: G = DiGraph()
746
sage: P = G.graphplot().plot()
747
sage: P.axes()
748
False
749
750
We can plot multiple graphs::
751
752
sage: T = list(graphs.trees(7))
753
sage: t = T[3]
754
sage: t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}).plot()
755
756
::
757
758
sage: T = list(graphs.trees(7))
759
sage: t = T[3]
760
sage: t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}).plot()
761
sage: t.set_edge_label(0,1,-7)
762
sage: t.set_edge_label(0,5,3)
763
sage: t.set_edge_label(0,5,99)
764
sage: t.set_edge_label(1,2,1000)
765
sage: t.set_edge_label(3,2,'spam')
766
sage: t.set_edge_label(2,6,3/2)
767
sage: t.set_edge_label(0,4,66)
768
sage: t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}, edge_labels=True).plot()
769
770
::
771
772
sage: T = list(graphs.trees(7))
773
sage: t = T[3]
774
sage: t.graphplot(layout='tree').show()
775
776
The tree layout is also useful::
777
778
sage: t = DiGraph('JCC???@A??GO??CO??GO??')
779
sage: t.graphplot(layout='tree', tree_root=0, tree_orientation="up").show()
780
781
More examples::
782
783
sage: D = DiGraph({0:[1,2,3], 2:[1,4], 3:[0]})
784
sage: D.graphplot().show()
785
786
sage: D = DiGraph(multiedges=True, sparse=True)
787
sage: for i in range(5):
788
... D.add_edge((i,i+1,'a'))
789
... D.add_edge((i,i-1,'b'))
790
sage: D.graphplot(edge_labels=True,edge_colors=D._color_by_label()).plot()
791
792
sage: g = Graph({}, loops=True, multiedges=True, sparse=True)
793
sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
794
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
795
sage: g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed').plot()
796
"""
797
G = Graphics()
798
for comp in self._plot_components.values():
799
if not isinstance(comp, list):
800
G += comp
801
else:
802
for item in comp:
803
G += item
804
G.set_axes_range(*(self._graph._layout_bounding_box(self._pos)))
805
if self._options['graph_border']:
806
xmin = G.xmin()
807
xmax = G.xmax()
808
ymin = G.ymin()
809
ymax = G.ymax()
810
dx = (xmax-xmin)/10.0
811
dy = (ymax-ymin)/10.0
812
border = (line([( xmin - dx, ymin - dy), ( xmin - dx, ymax + dy ), ( xmax + dx, ymax + dy ), ( xmax + dx, ymin - dy ), ( xmin - dx, ymin - dy )], thickness=1.3))
813
border.axes_range(xmin = (xmin - dx), xmax = (xmax + dx), ymin = (ymin - dy), ymax = (ymax + dy))
814
G += border
815
G.set_aspect_ratio(1)
816
G.axes(False)
817
G._extra_kwds['axes_pad']=.05
818
return G
819
820
def layout_tree(self,root,orientation):
821
"""
822
Compute a nice layout of a tree.
823
824
INPUT:
825
826
- ``root`` -- the root vertex.
827
828
- ``orientation`` -- Whether to place the root
829
at the top or at the bottom :
830
831
- ``orientation="down"`` -- children are placed below
832
their parent
833
- ``orientation="top"`` -- children are placed above
834
their parent
835
836
837
EXAMPLES::
838
839
sage: T = graphs.RandomLobster(25,0.3,0.3)
840
sage: T.show(layout='tree',tree_orientation='up') # indirect doctest
841
842
sage: from sage.graphs.graph_plot import GraphPlot
843
sage: G = graphs.HoffmanSingletonGraph()
844
sage: T = Graph()
845
sage: T.add_edges(G.min_spanning_tree(starting_vertex=0))
846
sage: T.show(layout='tree',tree_root=0) # indirect doctest
847
848
"""
849
850
T = self._graph
851
852
if not self._graph.is_tree():
853
raise RuntimeError("Cannot use tree layout on this graph: self.is_tree() returns False.")
854
855
children = {root:T.neighbors(root)}
856
857
#always make a copy of the children because they get eaten
858
stack = [[u for u in children[root]]]
859
stick = [root]
860
parent = dict([(u,root) for u in children[root]])
861
pos = {}
862
obstruction = [0.0]*T.num_verts()
863
if orientation == 'down':
864
o = -1
865
else:
866
o = 1
867
868
def slide(v,dx):
869
"""
870
871
shift the vertex v and its descendants to the right by dx
872
873
Precondition: v and its descendents have already had their
874
positions computed.
875
876
"""
877
878
level = [v]
879
while level:
880
nextlevel = []
881
for u in level:
882
x,y = pos[u]
883
x+= dx
884
obstruction[y] = max(x+1, obstruction[y])
885
pos[u] = x,y
886
nextlevel += children[u]
887
888
level = nextlevel
889
890
while stack:
891
C = stack[-1]
892
if len(C) == 0:
893
p = stick.pop()
894
stack.pop()
895
cp = children[p]
896
y = o*len(stack)
897
if len(cp) == 0:
898
x = obstruction[y]
899
pos[p] = x,y
900
else:
901
x = sum([pos[c][0] for c in cp])/(float(len(cp)))
902
pos[p] = x,y
903
ox = obstruction[y]
904
if x < ox:
905
slide(p,ox-x)
906
x = ox
907
obstruction[y] = x+1
908
continue
909
910
t = C.pop()
911
pt = parent[t]
912
913
ct = [u for u in T.neighbors(t) if u != pt]
914
for c in ct:
915
parent[c] = t
916
children[t] = ct
917
918
stack.append([c for c in ct])
919
stick.append(t)
920
921
return pos
922
923