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