passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1756 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# sage.doctest: needs sage.plot
|
3
|
+
r"""
|
4
|
+
Graph plotting
|
5
|
+
|
6
|
+
*(For LaTeX drawings of graphs, see the* :mod:`~sage.graphs.graph_latex` *module.)*
|
7
|
+
|
8
|
+
All graphs have an associated Sage graphics object, which you can display::
|
9
|
+
|
10
|
+
sage: G = graphs.WheelGraph(15)
|
11
|
+
sage: P = G.plot()
|
12
|
+
sage: P.show() # long time
|
13
|
+
|
14
|
+
.. PLOT::
|
15
|
+
|
16
|
+
sphinx_plot(graphs.WheelGraph(15))
|
17
|
+
|
18
|
+
When plotting a graph created using Sage's ``Graph`` command,
|
19
|
+
node positions are determined using the spring-layout algorithm.
|
20
|
+
Special graphs available from ``graphs.*`` have preset positions.
|
21
|
+
For example, compare the two plots of the Petersen graph,
|
22
|
+
as obtained using ``Graph`` or as obtained from that database::
|
23
|
+
|
24
|
+
sage: petersen_spring = Graph(':I`ES@obGkqegW~')
|
25
|
+
sage: petersen_spring.show() # long time
|
26
|
+
|
27
|
+
.. PLOT::
|
28
|
+
|
29
|
+
petersen_spring = Graph(':I`ES@obGkqegW~')
|
30
|
+
sphinx_plot(petersen_spring)
|
31
|
+
|
32
|
+
::
|
33
|
+
|
34
|
+
sage: petersen_database = graphs.PetersenGraph()
|
35
|
+
sage: petersen_database.show() # long time
|
36
|
+
|
37
|
+
.. PLOT::
|
38
|
+
|
39
|
+
petersen_database = graphs.PetersenGraph()
|
40
|
+
sphinx_plot(petersen_database)
|
41
|
+
|
42
|
+
All constructors in this database (except some random graphs) prefill
|
43
|
+
the position dictionary, bypassing the spring-layout positioning algorithm.
|
44
|
+
|
45
|
+
**Plot options**
|
46
|
+
|
47
|
+
Here is the list of options accepted by
|
48
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.plot` and the constructor of
|
49
|
+
:class:`GraphPlot`. Those two functions also accept all options of
|
50
|
+
:meth:`sage.plot.graphics.Graphics.show`.
|
51
|
+
|
52
|
+
.. csv-table::
|
53
|
+
:class: contentstable
|
54
|
+
:widths: 30, 70
|
55
|
+
:delim: |
|
56
|
+
|
57
|
+
{PLOT_OPTIONS_TABLE}
|
58
|
+
|
59
|
+
**Default options**
|
60
|
+
|
61
|
+
This module defines two dictionaries containing default options for the
|
62
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.plot` and
|
63
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.show` methods. These two
|
64
|
+
dictionaries are ``sage.graphs.graph_plot.DEFAULT_PLOT_OPTIONS`` and
|
65
|
+
``sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS``, respectively.
|
66
|
+
|
67
|
+
Obviously, these values are overruled when arguments are given explicitly.
|
68
|
+
|
69
|
+
Here is how to define the default size of a graph drawing to be ``(6, 6)``.
|
70
|
+
The first two calls to :meth:`~sage.graphs.generic_graph.GenericGraph.show`
|
71
|
+
use this option, while the third does not (a value for ``figsize``
|
72
|
+
is explicitly given)::
|
73
|
+
|
74
|
+
sage: import sage.graphs.graph_plot
|
75
|
+
sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = (6, 6)
|
76
|
+
sage: graphs.PetersenGraph().show() # long time
|
77
|
+
sage: graphs.ChvatalGraph().show() # long time
|
78
|
+
sage: graphs.PetersenGraph().show(figsize=(4, 4)) # long time
|
79
|
+
|
80
|
+
We can now reset the default to its initial value, and now display graphs as
|
81
|
+
previously::
|
82
|
+
|
83
|
+
sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = (4, 4)
|
84
|
+
sage: graphs.PetersenGraph().show() # long time
|
85
|
+
sage: graphs.ChvatalGraph().show() # long time
|
86
|
+
|
87
|
+
.. NOTE::
|
88
|
+
|
89
|
+
* While ``DEFAULT_PLOT_OPTIONS`` affects both ``G.show()`` and ``G.plot()``,
|
90
|
+
settings from ``DEFAULT_SHOW_OPTIONS`` only affects ``G.show()``.
|
91
|
+
|
92
|
+
* In order to define a default value permanently, you can add a couple of
|
93
|
+
lines to `Sage's startup scripts <../../../repl/startup.html>`_. Example::
|
94
|
+
|
95
|
+
sage: import sage.graphs.graph_plot
|
96
|
+
sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = (4, 4)
|
97
|
+
|
98
|
+
**Index of methods and functions**
|
99
|
+
|
100
|
+
.. csv-table::
|
101
|
+
:class: contentstable
|
102
|
+
:widths: 30, 70
|
103
|
+
:delim: |
|
104
|
+
|
105
|
+
:meth:`GraphPlot.set_pos` | Set the position plotting parameters for this GraphPlot.
|
106
|
+
:meth:`GraphPlot.set_vertices` | Set the vertex plotting parameters for this GraphPlot.
|
107
|
+
:meth:`GraphPlot.set_edges` | Set the edge (or arrow) plotting parameters for the GraphPlot object.
|
108
|
+
:meth:`GraphPlot.show` | Show the (Di)Graph associated with this GraphPlot object.
|
109
|
+
:meth:`GraphPlot.plot` | Return a graphics object representing the (di)graph.
|
110
|
+
:meth:`GraphPlot.layout_tree` | Compute a nice layout of a tree.
|
111
|
+
"""
|
112
|
+
|
113
|
+
# ****************************************************************************
|
114
|
+
# Copyright (C) 2009 Emily Kirkman
|
115
|
+
# 2009 Robert L. Miller <rlmillster@gmail.com>
|
116
|
+
#
|
117
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
118
|
+
#
|
119
|
+
# This code is distributed in the hope that it will be useful,
|
120
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
121
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
122
|
+
# General Public License for more details.
|
123
|
+
#
|
124
|
+
# The full text of the GPL is available at:
|
125
|
+
#
|
126
|
+
# https://www.gnu.org/licenses/
|
127
|
+
# ****************************************************************************
|
128
|
+
|
129
|
+
from collections import defaultdict
|
130
|
+
from math import sqrt, cos, sin, atan, pi
|
131
|
+
from sage.structure.sage_object import SageObject
|
132
|
+
from sage.misc.lazy_import import lazy_import
|
133
|
+
lazy_import("sage.plot.all", [
|
134
|
+
"Graphics", "scatter_plot", "bezier_path", "line", "arrow", "text", "circle"])
|
135
|
+
|
136
|
+
|
137
|
+
layout_options = {
|
138
|
+
'layout':
|
139
|
+
'A layout algorithm -- one of : "acyclic", "circular" (plots the '
|
140
|
+
'graph with vertices evenly distributed on a circle), "ranked", '
|
141
|
+
'"graphviz", "planar", "spring" (traditional spring layout, using '
|
142
|
+
'the graph\'s current positions as initial positions), or "tree" '
|
143
|
+
'(the tree will be plotted in levels, depending on minimum distance '
|
144
|
+
'for the root).',
|
145
|
+
'iterations':
|
146
|
+
'The number of times to execute the spring layout algorithm.',
|
147
|
+
'heights':
|
148
|
+
'A dictionary mapping heights to the list of vertices at this height.',
|
149
|
+
'spring':
|
150
|
+
'Use spring layout to finalize the current layout.',
|
151
|
+
'tree_root':
|
152
|
+
'A vertex designation for drawing trees. A vertex of the tree to '
|
153
|
+
'be used as the root for the ``layout=\'tree\'`` option. If no root '
|
154
|
+
'is specified, then one is chosen close to the center of the tree. '
|
155
|
+
'Ignored unless ``layout=\'tree\'``.',
|
156
|
+
'forest_roots':
|
157
|
+
'An iterable specifying which vertices to use as roots for the '
|
158
|
+
'``layout=\'forest\'`` option. If no root is specified for a tree, '
|
159
|
+
'then one is chosen close to the center of the tree. '
|
160
|
+
'Ignored unless ``layout=\'forest\'``.',
|
161
|
+
'tree_orientation':
|
162
|
+
'The direction of tree branches -- \'up\', \'down\', '
|
163
|
+
'\'left\' or \'right\'.',
|
164
|
+
'external_face':
|
165
|
+
'A list of the vertices of the external face of the graph, '
|
166
|
+
'used for Tutte embedding layout.',
|
167
|
+
'external_face_pos':
|
168
|
+
'A dictionary specifying the positions of the external face of the '
|
169
|
+
'graph, used for Tutte embedding layout. If none specified, the'
|
170
|
+
'external face is a regular polygon.',
|
171
|
+
'save_pos':
|
172
|
+
'Whether or not to save the computed position for the graph.',
|
173
|
+
'dim':
|
174
|
+
'The dimension of the layout -- 2 or 3.',
|
175
|
+
'prog':
|
176
|
+
'Which graphviz layout program to use -- one of '
|
177
|
+
'"circo", "dot", "fdp", "neato", or "twopi".',
|
178
|
+
'by_component':
|
179
|
+
'Whether to do the spring layout by connected component -- boolean.'}
|
180
|
+
|
181
|
+
graphplot_options = layout_options.copy()
|
182
|
+
|
183
|
+
graphplot_options.update({
|
184
|
+
'pos':
|
185
|
+
'The position dictionary of vertices.',
|
186
|
+
'vertex_labels':
|
187
|
+
'Vertex labels to draw. This can be ``True``/``False`` to indicate '
|
188
|
+
'whether to print the vertex string representation of not, '
|
189
|
+
'a dictionary keyed by vertices and associating to each vertex '
|
190
|
+
'a label string, or a function taking as input a vertex and returning '
|
191
|
+
'a label string.',
|
192
|
+
'vertex_label_shift':
|
193
|
+
'If layout is circular and we have vertex labels, will shift vertices '
|
194
|
+
'away from center of circle in coordinate fashion `(x, y)`.',
|
195
|
+
'vertex_color':
|
196
|
+
'Default color for vertices not listed '
|
197
|
+
'in vertex_colors dictionary.',
|
198
|
+
'vertex_colors':
|
199
|
+
'A dictionary specifying vertex colors: '
|
200
|
+
'each key is a color recognizable by matplotlib, '
|
201
|
+
'and each corresponding value is a list of vertices.',
|
202
|
+
'vertex_size':
|
203
|
+
'The size to draw the vertices.',
|
204
|
+
'vertex_shape':
|
205
|
+
'The shape to draw the vertices. '
|
206
|
+
'Currently unavailable for Multi-edged DiGraphs.',
|
207
|
+
'edge_labels':
|
208
|
+
'Whether or not to draw edge labels.',
|
209
|
+
'edge_style':
|
210
|
+
'The linestyle of the edges. It should be '
|
211
|
+
'one of "solid", "dashed", "dotted", "dashdot", '
|
212
|
+
'or "-", "--", ":", "-.", respectively. ',
|
213
|
+
'edge_styles':
|
214
|
+
'A dictionary specifying edge styles: '
|
215
|
+
'each key is an edge or a label (all same) and value is the linestyle '
|
216
|
+
'of the edge. It should be one of "solid", "dashed", "dotted", '
|
217
|
+
'"dashdot", or "-", "--", ":", "-.", respectively.',
|
218
|
+
'edge_thickness':
|
219
|
+
'The thickness of the edges.',
|
220
|
+
'edge_thicknesses':
|
221
|
+
'A dictionary specifying edge thicknesses: '
|
222
|
+
'each key is an edge or a label (all same) and thickness of the '
|
223
|
+
'corresponding edge.',
|
224
|
+
'edge_color':
|
225
|
+
'The default color for edges not listed in edge_colors.',
|
226
|
+
'edge_colors':
|
227
|
+
'A dictionary specifying edge colors: '
|
228
|
+
'each key is a color recognized by matplotlib, '
|
229
|
+
'and each corresponding value is a list of edges.',
|
230
|
+
'color_by_label':
|
231
|
+
'Whether to color the edges according to their labels. This also '
|
232
|
+
'accepts a function or dictionary mapping labels to colors.',
|
233
|
+
'partition':
|
234
|
+
'A partition of the vertex set. If specified, plot will show each '
|
235
|
+
'cell in a different color; vertex_colors takes precedence.',
|
236
|
+
'loop_size':
|
237
|
+
'The radius of the smallest loop.',
|
238
|
+
'arrowsize':
|
239
|
+
'Size of arrows.',
|
240
|
+
'dist':
|
241
|
+
'The distance between multiedges.',
|
242
|
+
'max_dist':
|
243
|
+
'The max distance range to allow multiedges.',
|
244
|
+
'talk':
|
245
|
+
'Whether to display the vertices in talk mode (larger and white).',
|
246
|
+
'label_fontsize':
|
247
|
+
'font size of all labels',
|
248
|
+
'graph_border':
|
249
|
+
'Whether or not to draw a frame around the graph.',
|
250
|
+
'edge_labels_background':
|
251
|
+
'The color of the background of the edge labels.'})
|
252
|
+
|
253
|
+
_PLOT_OPTIONS_TABLE = ""
|
254
|
+
|
255
|
+
for key, value in graphplot_options.items():
|
256
|
+
_PLOT_OPTIONS_TABLE += f" ``{key}`` | {value}\n"
|
257
|
+
|
258
|
+
__doc__ = __doc__.format(PLOT_OPTIONS_TABLE=_PLOT_OPTIONS_TABLE)
|
259
|
+
|
260
|
+
DEFAULT_SHOW_OPTIONS = {'figsize': (4, 4)}
|
261
|
+
|
262
|
+
DEFAULT_PLOT_OPTIONS = {
|
263
|
+
'vertex_size' : 200,
|
264
|
+
'vertex_labels' : True,
|
265
|
+
'vertex_label_shift' : None,
|
266
|
+
'layout' : None,
|
267
|
+
'edge_style' : 'solid',
|
268
|
+
'edge_styles' : None,
|
269
|
+
'edge_thickness' : 1,
|
270
|
+
'edge_thicknesses' : None,
|
271
|
+
'edge_color' : 'black',
|
272
|
+
'edge_colors' : None,
|
273
|
+
'edge_labels' : False,
|
274
|
+
'iterations' : 50,
|
275
|
+
'tree_orientation' : 'down',
|
276
|
+
'heights' : None,
|
277
|
+
'graph_border' : False,
|
278
|
+
'talk' : False,
|
279
|
+
'color_by_label' : False,
|
280
|
+
'partition' : None,
|
281
|
+
'dist' : .075,
|
282
|
+
'max_dist' : 1.5,
|
283
|
+
'label_fontsize' : 10,
|
284
|
+
'loop_size' : .075,
|
285
|
+
'edge_labels_background' : 'white'}
|
286
|
+
|
287
|
+
|
288
|
+
class GraphPlot(SageObject):
|
289
|
+
def __init__(self, graph, options):
|
290
|
+
"""
|
291
|
+
Return a ``GraphPlot`` object, which stores all the parameters needed
|
292
|
+
for plotting (Di)Graphs.
|
293
|
+
|
294
|
+
A ``GraphPlot`` has a plot and show function, as well as some functions
|
295
|
+
to set parameters for vertices and edges. This constructor assumes
|
296
|
+
default options are set. Defaults are shown in the example below.
|
297
|
+
|
298
|
+
EXAMPLES::
|
299
|
+
|
300
|
+
sage: from sage.graphs.graph_plot import GraphPlot
|
301
|
+
sage: options = {
|
302
|
+
....: 'vertex_size': 200,
|
303
|
+
....: 'vertex_labels': True,
|
304
|
+
....: 'layout': None,
|
305
|
+
....: 'edge_style': 'solid',
|
306
|
+
....: 'edge_color': 'black',
|
307
|
+
....: 'edge_colors': None,
|
308
|
+
....: 'edge_labels': False,
|
309
|
+
....: 'iterations': 50,
|
310
|
+
....: 'tree_orientation': 'down',
|
311
|
+
....: 'heights': None,
|
312
|
+
....: 'graph_border': False,
|
313
|
+
....: 'talk': False,
|
314
|
+
....: 'color_by_label': False,
|
315
|
+
....: 'partition': None,
|
316
|
+
....: 'dist': .075,
|
317
|
+
....: 'max_dist': 1.5,
|
318
|
+
....: 'loop_size': .075,
|
319
|
+
....: 'edge_labels_background': 'transparent'}
|
320
|
+
sage: g = Graph({0: [1, 2], 2: [3], 4: [0, 1]})
|
321
|
+
sage: GP = GraphPlot(g, options)
|
322
|
+
"""
|
323
|
+
# Setting the default values if needed
|
324
|
+
for k, value in DEFAULT_PLOT_OPTIONS.items():
|
325
|
+
if k not in options:
|
326
|
+
options[k] = value
|
327
|
+
self._plot_components = {}
|
328
|
+
self._nodelist = list(graph)
|
329
|
+
self._graph = graph
|
330
|
+
self._options = options # contains both plot and show options
|
331
|
+
self._arcs = self._graph.has_multiple_edges(to_undirected=True)
|
332
|
+
self._loops = self._graph.has_loops()
|
333
|
+
self._arcdigraph = self._graph.is_directed() and self._arcs
|
334
|
+
|
335
|
+
self.set_pos()
|
336
|
+
self.set_vertices()
|
337
|
+
self.set_edges()
|
338
|
+
|
339
|
+
def _repr_(self):
|
340
|
+
"""
|
341
|
+
Return a string representation of a ``GraphPlot`` object.
|
342
|
+
|
343
|
+
EXAMPLES:
|
344
|
+
|
345
|
+
This function is called implicitly by the code below::
|
346
|
+
|
347
|
+
sage: g = Graph({0: [1, 2], 2: [3], 4: [0, 1]})
|
348
|
+
sage: g.graphplot() # indirect doctest
|
349
|
+
GraphPlot object for Graph on 5 vertices
|
350
|
+
"""
|
351
|
+
return f"GraphPlot object for {self._graph}"
|
352
|
+
|
353
|
+
def set_pos(self):
|
354
|
+
"""
|
355
|
+
Set the position plotting parameters for this GraphPlot.
|
356
|
+
|
357
|
+
EXAMPLES:
|
358
|
+
|
359
|
+
This function is called implicitly by the code below::
|
360
|
+
|
361
|
+
sage: g = Graph({0: [1, 2], 2: [3], 4: [0, 1]})
|
362
|
+
sage: g.graphplot(save_pos=True, layout='circular') # indirect doctest
|
363
|
+
GraphPlot object for Graph on 5 vertices
|
364
|
+
|
365
|
+
The following illustrates the format of a position dictionary, but due
|
366
|
+
to numerical noise we do not check the values themselves::
|
367
|
+
|
368
|
+
sage: g.get_pos()
|
369
|
+
{0: (0.0, 1.0),
|
370
|
+
1: (-0.951..., 0.309...),
|
371
|
+
2: (-0.587..., -0.809...),
|
372
|
+
3: (0.587..., -0.809...),
|
373
|
+
4: (0.951..., 0.309...)}
|
374
|
+
|
375
|
+
::
|
376
|
+
|
377
|
+
sage: T = list(graphs.trees(7))
|
378
|
+
sage: t = T[3]
|
379
|
+
sage: t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]})
|
380
|
+
Graphics object consisting of 14 graphics primitives
|
381
|
+
|
382
|
+
.. PLOT::
|
383
|
+
|
384
|
+
g = Graph({0: [1, 2], 2: [3], 4: [0, 1]})
|
385
|
+
g.graphplot(save_pos=True, layout='circular') # indirect doctest
|
386
|
+
T = list(graphs.trees(7))
|
387
|
+
t = T[3]
|
388
|
+
P = t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]})
|
389
|
+
sphinx_plot(P)
|
390
|
+
|
391
|
+
TESTS:
|
392
|
+
|
393
|
+
Make sure that vertex locations are floats. Not being floats isn't
|
394
|
+
a bug in itself but made it too easy to accidentally introduce a bug
|
395
|
+
elsewhere, such as in :meth:`set_edges` (:issue:`10124`), via silent
|
396
|
+
truncating division of Python 2 integers::
|
397
|
+
|
398
|
+
sage: g = graphs.FruchtGraph()
|
399
|
+
sage: gp = g.graphplot()
|
400
|
+
sage: set(map(type, flatten(gp._pos.values())))
|
401
|
+
{<... 'float'>}
|
402
|
+
sage: g = graphs.BullGraph()
|
403
|
+
sage: gp = g.graphplot(save_pos=True)
|
404
|
+
sage: set(map(type, flatten(gp._pos.values())))
|
405
|
+
{<... 'float'>}
|
406
|
+
|
407
|
+
Non-ascii labels are also possible using unicode (:issue:`21008`)::
|
408
|
+
|
409
|
+
sage: Graph({u'où': [u'là', u'ici']}).plot()
|
410
|
+
Graphics object consisting of 6 graphics primitives
|
411
|
+
"""
|
412
|
+
self._pos = self._graph.layout(**self._options)
|
413
|
+
# Make sure the positions are floats (trac #10124)
|
414
|
+
self._pos = {k: (float(v[0]), float(v[1]))
|
415
|
+
for k, v in self._pos.items()}
|
416
|
+
|
417
|
+
def set_vertices(self, **vertex_options):
|
418
|
+
"""
|
419
|
+
Set the vertex plotting parameters for this ``GraphPlot``.
|
420
|
+
|
421
|
+
This function is called by the constructor but can also be
|
422
|
+
called to make updates to the vertex options of an existing
|
423
|
+
``GraphPlot`` object. Note that the changes are cumulative.
|
424
|
+
|
425
|
+
EXAMPLES::
|
426
|
+
|
427
|
+
sage: g = Graph({}, loops=True, multiedges=True, sparse=True)
|
428
|
+
sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
429
|
+
....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
430
|
+
....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
431
|
+
sage: GP = g.graphplot(vertex_size=100, edge_labels=True,
|
432
|
+
....: color_by_label=True, edge_style='dashed')
|
433
|
+
sage: GP.set_vertices(talk=True)
|
434
|
+
sage: GP.plot()
|
435
|
+
Graphics object consisting of 22 graphics primitives
|
436
|
+
sage: GP.set_vertices(vertex_color='green', vertex_shape='^')
|
437
|
+
sage: GP.plot()
|
438
|
+
Graphics object consisting of 22 graphics primitives
|
439
|
+
|
440
|
+
.. PLOT::
|
441
|
+
|
442
|
+
g = Graph({}, loops=True, multiedges=True, sparse=True)
|
443
|
+
g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
444
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
445
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
446
|
+
GP = g.graphplot(vertex_size=100, edge_labels=True,
|
447
|
+
color_by_label=True, edge_style='dashed')
|
448
|
+
GP.set_vertices(talk=True)
|
449
|
+
sphinx_plot(GP)
|
450
|
+
|
451
|
+
.. PLOT::
|
452
|
+
|
453
|
+
g = Graph({}, loops=True, multiedges=True, sparse=True)
|
454
|
+
g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
455
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
456
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
457
|
+
GP = g.graphplot(vertex_size=100, edge_labels=True,
|
458
|
+
color_by_label=True, edge_style='dashed')
|
459
|
+
GP.set_vertices(talk=True)
|
460
|
+
GP.set_vertices(vertex_color='green', vertex_shape='^')
|
461
|
+
sphinx_plot(GP)
|
462
|
+
|
463
|
+
Vertex labels are flexible::
|
464
|
+
|
465
|
+
sage: g = graphs.PathGraph(4)
|
466
|
+
sage: g.plot(vertex_labels=False)
|
467
|
+
Graphics object consisting of 4 graphics primitives
|
468
|
+
|
469
|
+
.. PLOT::
|
470
|
+
|
471
|
+
g = graphs.PathGraph(4)
|
472
|
+
P = g.graphplot(vertex_labels=False)
|
473
|
+
sphinx_plot(P)
|
474
|
+
|
475
|
+
::
|
476
|
+
|
477
|
+
sage: g = graphs.PathGraph(4)
|
478
|
+
sage: g.plot(vertex_labels=True)
|
479
|
+
Graphics object consisting of 8 graphics primitives
|
480
|
+
|
481
|
+
.. PLOT::
|
482
|
+
|
483
|
+
g = graphs.PathGraph(4)
|
484
|
+
P = g.graphplot(vertex_labels=True)
|
485
|
+
sphinx_plot(P)
|
486
|
+
|
487
|
+
::
|
488
|
+
|
489
|
+
sage: g = graphs.PathGraph(4)
|
490
|
+
sage: g.plot(vertex_labels=dict(zip(g, ['+', '-', '/', '*'])))
|
491
|
+
Graphics object consisting of 8 graphics primitives
|
492
|
+
|
493
|
+
.. PLOT::
|
494
|
+
|
495
|
+
g = graphs.PathGraph(4)
|
496
|
+
P = g.graphplot(vertex_labels=dict(zip(g, ['+', '-', '/', '*'])))
|
497
|
+
sphinx_plot(P)
|
498
|
+
|
499
|
+
::
|
500
|
+
|
501
|
+
sage: g = graphs.PathGraph(4)
|
502
|
+
sage: g.plot(vertex_labels=lambda x: str(x % 2))
|
503
|
+
Graphics object consisting of 8 graphics primitives
|
504
|
+
|
505
|
+
.. PLOT::
|
506
|
+
|
507
|
+
g = graphs.PathGraph(4)
|
508
|
+
P = g.graphplot(vertex_labels=lambda x: str(x % 2))
|
509
|
+
sphinx_plot(P)
|
510
|
+
"""
|
511
|
+
# Handle base vertex options
|
512
|
+
voptions = {}
|
513
|
+
|
514
|
+
for arg in vertex_options:
|
515
|
+
self._options[arg] = vertex_options[arg]
|
516
|
+
|
517
|
+
# First set defaults for styles
|
518
|
+
vertex_colors = None
|
519
|
+
if self._options['talk']:
|
520
|
+
voptions['markersize'] = 500
|
521
|
+
if self._options['partition'] is None:
|
522
|
+
vertex_colors = '#ffffff'
|
523
|
+
else:
|
524
|
+
voptions['markersize'] = self._options['vertex_size']
|
525
|
+
|
526
|
+
if ('vertex_color' not in self._options
|
527
|
+
or self._options['vertex_color'] is None):
|
528
|
+
vertex_color = '#fec7b8'
|
529
|
+
else:
|
530
|
+
vertex_color = self._options['vertex_color']
|
531
|
+
|
532
|
+
if ('vertex_colors' not in self._options
|
533
|
+
or self._options['vertex_colors'] is None):
|
534
|
+
if self._options['partition'] is not None:
|
535
|
+
from sage.plot.colors import rainbow
|
536
|
+
partition = self._options['partition']
|
537
|
+
length = len(partition)
|
538
|
+
R = rainbow(length)
|
539
|
+
vertex_colors = {R[i]: partition[i] for i in range(length)}
|
540
|
+
elif not vertex_colors:
|
541
|
+
vertex_colors = vertex_color
|
542
|
+
else:
|
543
|
+
vertex_colors = self._options['vertex_colors']
|
544
|
+
|
545
|
+
if 'vertex_shape' in self._options:
|
546
|
+
voptions['marker'] = self._options['vertex_shape']
|
547
|
+
|
548
|
+
if self._graph.is_directed():
|
549
|
+
self._vertex_radius = sqrt(voptions['markersize'] / pi)
|
550
|
+
self._arrowshorten = 2 * self._vertex_radius
|
551
|
+
if self._arcdigraph:
|
552
|
+
self._vertex_radius = sqrt(voptions['markersize'] / (20500 * pi))
|
553
|
+
|
554
|
+
voptions['zorder'] = 7
|
555
|
+
|
556
|
+
if not isinstance(vertex_colors, dict):
|
557
|
+
voptions['facecolor'] = vertex_colors
|
558
|
+
pos = list(self._pos.values())
|
559
|
+
if self._arcdigraph:
|
560
|
+
self._plot_components['vertices'] = [
|
561
|
+
circle(p, self._vertex_radius, fill=True, clip=False,
|
562
|
+
edgecolor='black', facecolor=vertex_colors)
|
563
|
+
for p in pos]
|
564
|
+
else:
|
565
|
+
self._plot_components['vertices'] = (
|
566
|
+
scatter_plot(pos, clip=False, **voptions))
|
567
|
+
else:
|
568
|
+
# Color list must be ordered:
|
569
|
+
pos = []
|
570
|
+
colors = []
|
571
|
+
for i in vertex_colors:
|
572
|
+
pos.extend([self._pos[j] for j in vertex_colors[i]])
|
573
|
+
colors.extend([i] * len(vertex_colors[i]))
|
574
|
+
|
575
|
+
# If all the vertices have not been assigned a color
|
576
|
+
if len(self._pos) != len(pos):
|
577
|
+
leftovers = [j for j in self._pos.values() if j not in pos]
|
578
|
+
pos.extend(leftovers)
|
579
|
+
colors.extend([vertex_color] * len(leftovers))
|
580
|
+
|
581
|
+
if self._arcdigraph:
|
582
|
+
self._plot_components['vertices'] = [
|
583
|
+
circle(p, self._vertex_radius, fill=True, clip=False,
|
584
|
+
facecolor=colors[i], edgecolor='black')
|
585
|
+
for i, p in enumerate(pos)]
|
586
|
+
else:
|
587
|
+
self._plot_components['vertices'] = scatter_plot(
|
588
|
+
pos, facecolor=colors, clip=False, **voptions)
|
589
|
+
|
590
|
+
vlabels = self._options['vertex_labels']
|
591
|
+
if vlabels:
|
592
|
+
if vlabels is True:
|
593
|
+
vfun = str
|
594
|
+
elif isinstance(vlabels, dict):
|
595
|
+
def vfun(x):
|
596
|
+
return vlabels.get(x, "")
|
597
|
+
else:
|
598
|
+
vfun = vlabels
|
599
|
+
|
600
|
+
# TODO: allow text options
|
601
|
+
if self._options['layout'] == 'circular' and self._options['vertex_label_shift'] is not None:
|
602
|
+
def pos_shift(v, shift):
|
603
|
+
return (v[0] + (v[0] * shift[0])/100, v[1] + (v[1] * shift[1])/100)
|
604
|
+
self._plot_components['vertex_labels'] = [
|
605
|
+
text(
|
606
|
+
vfun(v),
|
607
|
+
pos_shift(self._pos[v], self._options['vertex_label_shift']),
|
608
|
+
fontsize=self._options['label_fontsize'],
|
609
|
+
color='black',
|
610
|
+
zorder=8
|
611
|
+
)
|
612
|
+
for v in self._nodelist
|
613
|
+
]
|
614
|
+
else:
|
615
|
+
self._plot_components['vertex_labels'] = [
|
616
|
+
text(vfun(v), self._pos[v], color='black', zorder=8, fontsize=self._options['label_fontsize'])
|
617
|
+
for v in self._nodelist
|
618
|
+
]
|
619
|
+
|
620
|
+
def set_edges(self, **edge_options):
|
621
|
+
"""
|
622
|
+
Set edge plotting parameters for the ``GraphPlot`` object.
|
623
|
+
|
624
|
+
This function is called by the constructor but can also be called to
|
625
|
+
update the edge options of an existing ``GraphPlot`` object.
|
626
|
+
Note that the changes are cumulative.
|
627
|
+
|
628
|
+
EXAMPLES::
|
629
|
+
|
630
|
+
sage: g = Graph(loops=True, multiedges=True, sparse=True)
|
631
|
+
sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
632
|
+
....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
633
|
+
....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
634
|
+
sage: GP = g.graphplot(vertex_size=100, edge_labels=True,
|
635
|
+
....: color_by_label=True, edge_style='dashed')
|
636
|
+
sage: GP.set_edges(edge_style='solid')
|
637
|
+
sage: GP.plot()
|
638
|
+
Graphics object consisting of 22 graphics primitives
|
639
|
+
|
640
|
+
.. PLOT::
|
641
|
+
|
642
|
+
g = Graph(loops=True, multiedges=True, sparse=True)
|
643
|
+
g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
644
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
645
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
646
|
+
GP = g.graphplot(vertex_size=100, edge_labels=True,
|
647
|
+
color_by_label=True, edge_style='dashed')
|
648
|
+
GP.set_edges(edge_style='solid')
|
649
|
+
sphinx_plot(GP)
|
650
|
+
|
651
|
+
::
|
652
|
+
|
653
|
+
sage: GP.set_edges(edge_color='black')
|
654
|
+
sage: GP.plot()
|
655
|
+
Graphics object consisting of 22 graphics primitives
|
656
|
+
|
657
|
+
.. PLOT::
|
658
|
+
|
659
|
+
g = Graph(loops=True, multiedges=True, sparse=True)
|
660
|
+
g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
661
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
662
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
663
|
+
GP = g.graphplot(vertex_size=100, edge_labels=True,
|
664
|
+
color_by_label=True, edge_style='dashed')
|
665
|
+
GP.set_edges(edge_style='solid')
|
666
|
+
GP.set_edges(edge_color='black')
|
667
|
+
sphinx_plot(GP)
|
668
|
+
|
669
|
+
::
|
670
|
+
|
671
|
+
sage: d = DiGraph(loops=True, multiedges=True, sparse=True)
|
672
|
+
sage: d.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
673
|
+
....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
674
|
+
....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
675
|
+
sage: GP = d.graphplot(vertex_size=100, edge_labels=True,
|
676
|
+
....: color_by_label=True, edge_style='dashed')
|
677
|
+
sage: GP.set_edges(edge_style='solid')
|
678
|
+
sage: GP.plot()
|
679
|
+
Graphics object consisting of 24 graphics primitives
|
680
|
+
|
681
|
+
.. PLOT::
|
682
|
+
|
683
|
+
d = DiGraph(loops=True, multiedges=True, sparse=True)
|
684
|
+
d.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
685
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
686
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
687
|
+
GP = d.graphplot(vertex_size=100, edge_labels=True,
|
688
|
+
color_by_label=True, edge_style='dashed')
|
689
|
+
GP.set_edges(edge_style='solid')
|
690
|
+
sphinx_plot(GP)
|
691
|
+
|
692
|
+
::
|
693
|
+
|
694
|
+
sage: GP.set_edges(edge_color='black')
|
695
|
+
sage: GP.plot()
|
696
|
+
Graphics object consisting of 24 graphics primitives
|
697
|
+
|
698
|
+
.. PLOT::
|
699
|
+
|
700
|
+
d = DiGraph(loops=True, multiedges=True, sparse=True)
|
701
|
+
d.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
702
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
703
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
704
|
+
GP = d.graphplot(vertex_size=100, edge_labels=True,
|
705
|
+
color_by_label=True, edge_style='dashed')
|
706
|
+
GP.set_edges(edge_style='solid')
|
707
|
+
GP.set_edges(edge_color='black')
|
708
|
+
sphinx_plot(GP)
|
709
|
+
|
710
|
+
TESTS::
|
711
|
+
|
712
|
+
sage: G = Graph("Fooba")
|
713
|
+
sage: G.show(edge_colors={'red':[(3, 6), (2, 5)]})
|
714
|
+
|
715
|
+
Check default edge labels are pretty close to halfway between
|
716
|
+
the vertices in some cases where they weren't due to Python 2
|
717
|
+
truncating division (:issue:`10124`)::
|
718
|
+
|
719
|
+
sage: test_graphs = graphs.FruchtGraph(), graphs.BullGraph()
|
720
|
+
sage: tol = 0.001
|
721
|
+
sage: for G in test_graphs:
|
722
|
+
....: E = G.edges(sort=True)
|
723
|
+
....: for e0, e1, elab in E:
|
724
|
+
....: G.set_edge_label(e0, e1, '%d %d' % (e0, e1))
|
725
|
+
....: gp = G.graphplot(save_pos=True, edge_labels=True)
|
726
|
+
....: vx = gp._plot_components['vertices'][0].xdata
|
727
|
+
....: vy = gp._plot_components['vertices'][0].ydata
|
728
|
+
....: for elab in gp._plot_components['edge_labels']:
|
729
|
+
....: textobj = elab[0]
|
730
|
+
....: x, y, s = textobj.x, textobj.y, textobj.string
|
731
|
+
....: v0, v1 = map(int, s.split())
|
732
|
+
....: m = sum(vector((vx[v], vy[v])) for v in (v0, v1))/2
|
733
|
+
....: assert (vector((x, y)) - m).norm() < tol
|
734
|
+
|
735
|
+
Issue :issue:`24051` is fixed::
|
736
|
+
|
737
|
+
sage: G = Graph([(0, 1), (0, 1)], multiedges=True)
|
738
|
+
sage: G.plot(edge_colors={"red": [(1, 0)]})
|
739
|
+
Graphics object consisting of 5 graphics primitives
|
740
|
+
|
741
|
+
Issue :issue:`31542` is fixed::
|
742
|
+
|
743
|
+
sage: s = 'ABCCCCDABCDABCDA'
|
744
|
+
sage: g = DiGraph({}, loops=True, multiedges=True)
|
745
|
+
sage: for a, b in [(s[i], u) for i, u in enumerate(s[1:])]:
|
746
|
+
....: g.add_edge(a, b, b)
|
747
|
+
sage: g.plot(color_by_label=True, layout='circular')
|
748
|
+
Graphics object consisting of 23 graphics primitives
|
749
|
+
"""
|
750
|
+
for arg in edge_options:
|
751
|
+
self._options[arg] = edge_options[arg]
|
752
|
+
if 'edge_colors' in edge_options:
|
753
|
+
self._options['color_by_label'] = False
|
754
|
+
if self._options['edge_labels_background'] == "transparent":
|
755
|
+
self._options['edge_labels_background'] = "None"
|
756
|
+
|
757
|
+
# Whether a key is an edge or not:
|
758
|
+
# None => edge_x is not set
|
759
|
+
# True => keys are edges
|
760
|
+
# False => keys are labels
|
761
|
+
style_key_edges = None
|
762
|
+
thickness_key_edges = None
|
763
|
+
if isinstance(self._options['edge_styles'], dict):
|
764
|
+
style_key_edges = next(iter(self._options['edge_styles'])) in self._graph.edges()
|
765
|
+
if isinstance(self._options['edge_thicknesses'], dict):
|
766
|
+
thickness_key_edges = next(iter(self._options['edge_thicknesses'])) in self._graph.edges()
|
767
|
+
|
768
|
+
eoptions = {}
|
769
|
+
if 'arrowsize' in self._options:
|
770
|
+
eoptions['arrowsize'] = self._options['arrowsize']
|
771
|
+
|
772
|
+
# Set labels param to add labels on the fly
|
773
|
+
labels = False
|
774
|
+
if self._options['edge_labels']:
|
775
|
+
labels = True
|
776
|
+
self._plot_components['edge_labels'] = []
|
777
|
+
|
778
|
+
# Make dict collection of all edges (keep label and edge color)
|
779
|
+
edges_to_draw = defaultdict(list)
|
780
|
+
|
781
|
+
v_to_int = {v: i for i, v in enumerate(self._graph)}
|
782
|
+
|
783
|
+
if (self._options['color_by_label']
|
784
|
+
or isinstance(self._options['edge_colors'], dict)):
|
785
|
+
if self._options['color_by_label']:
|
786
|
+
edge_colors = self._graph._color_by_label(
|
787
|
+
format=self._options['color_by_label'])
|
788
|
+
else:
|
789
|
+
edge_colors = self._options['edge_colors']
|
790
|
+
edges_drawn = []
|
791
|
+
for color in edge_colors:
|
792
|
+
for edge in edge_colors[color]:
|
793
|
+
a, b = edge[0], edge[1]
|
794
|
+
if v_to_int[a] < v_to_int[b]:
|
795
|
+
key = (a, b)
|
796
|
+
head = 1
|
797
|
+
else:
|
798
|
+
key = (b, a)
|
799
|
+
head = 0
|
800
|
+
if len(edge) < 3:
|
801
|
+
label = self._graph.edge_label(a, b)
|
802
|
+
if isinstance(label, list):
|
803
|
+
edges_to_draw[key].append((label[-1], color, head))
|
804
|
+
edges_drawn.append((a, b, label[-1]))
|
805
|
+
for lab in label[:-1]:
|
806
|
+
edges_to_draw[key].append((lab, color, head))
|
807
|
+
edges_drawn.append((a, b, lab))
|
808
|
+
else:
|
809
|
+
edges_to_draw[key].append((label, color, head))
|
810
|
+
edges_drawn.append((a, b, label))
|
811
|
+
else:
|
812
|
+
label = edge[2]
|
813
|
+
edges_to_draw[key].append((label, color, head))
|
814
|
+
edges_drawn.append((a, b, label))
|
815
|
+
|
816
|
+
# Add unspecified edges (default color black set in DEFAULT_PLOT_OPTIONS)
|
817
|
+
for a, b, c in self._graph.edge_iterator():
|
818
|
+
if ((a, b, c) not in edges_drawn
|
819
|
+
and (self._graph.is_directed()
|
820
|
+
or (b, a, c) not in edges_drawn)):
|
821
|
+
if v_to_int[a] < v_to_int[b]:
|
822
|
+
key = (a, b)
|
823
|
+
head = 1
|
824
|
+
else:
|
825
|
+
key = (b, a)
|
826
|
+
head = 0
|
827
|
+
edges_to_draw[key].append((c, self._options['edge_color'], head))
|
828
|
+
|
829
|
+
else:
|
830
|
+
for a, b, c in self._graph.edge_iterator():
|
831
|
+
if v_to_int[a] < v_to_int[b]:
|
832
|
+
key = (a, b)
|
833
|
+
head = 1
|
834
|
+
else:
|
835
|
+
key = (b, a)
|
836
|
+
head = 0
|
837
|
+
edges_to_draw[key].append((c, self._options['edge_color'], head))
|
838
|
+
|
839
|
+
if edges_to_draw:
|
840
|
+
self._plot_components['edges'] = []
|
841
|
+
else:
|
842
|
+
return
|
843
|
+
|
844
|
+
# Check for multi-edges or loops
|
845
|
+
if self._arcs or self._loops:
|
846
|
+
tmp = edges_to_draw.copy()
|
847
|
+
dist = self._options['dist'] * 2
|
848
|
+
min_loop_size = self._options['loop_size']
|
849
|
+
max_dist = self._options['max_dist']
|
850
|
+
from sage.misc.functional import sqrt
|
851
|
+
for a, b in tmp:
|
852
|
+
if a == b:
|
853
|
+
# Multiple loops need varying loop radius starting at
|
854
|
+
# minimum loop size and respecting other distances
|
855
|
+
loop_size = min_loop_size # current loop radius
|
856
|
+
distance = dist
|
857
|
+
local_labels = edges_to_draw.pop((a, b))
|
858
|
+
len_local_labels = len(local_labels)
|
859
|
+
if len_local_labels * dist > max_dist:
|
860
|
+
distance = float(max_dist) / len_local_labels
|
861
|
+
loop_size_increment = distance / 4
|
862
|
+
# Now add all the loops at this vertex, varying their size
|
863
|
+
for lab, col, _ in local_labels:
|
864
|
+
x, y = self._pos[a][0], self._pos[a][1] - loop_size
|
865
|
+
|
866
|
+
estyle = self._options['edge_style']
|
867
|
+
ethickness = self._options['edge_thickness']
|
868
|
+
if (style_key_edges is not None
|
869
|
+
and ((style_key_edges and (x, y) in self._options['edge_styles'])
|
870
|
+
or (not style_key_edges and lab in self._options['edge_styles']))):
|
871
|
+
estyle = style_key_edges and self._options['edge_styles'][(x, y)] or self._options['edge_styles'][lab]
|
872
|
+
if (thickness_key_edges is not None
|
873
|
+
and ((thickness_key_edges and (x, y) in self._options['edge_thicknesses'])
|
874
|
+
or (not thickness_key_edges and lab in self._options['edge_thicknesses']))):
|
875
|
+
ethickness = thickness_key_edges and self._options['edge_thicknesses'][(x, y)] or self._options['edge_thicknesses'][lab]
|
876
|
+
|
877
|
+
c = circle((x, y), loop_size, rgbcolor=col, linestyle=estyle, thickness=ethickness)
|
878
|
+
self._plot_components['edges'].append(c)
|
879
|
+
if labels:
|
880
|
+
bg = self._options['edge_labels_background']
|
881
|
+
y -= loop_size # place label at bottom of loop
|
882
|
+
t = text(lab, (x, y), background_color=bg, fontsize=self._options['label_fontsize'])
|
883
|
+
self._plot_components['edge_labels'].append(t)
|
884
|
+
loop_size += loop_size_increment
|
885
|
+
elif len(edges_to_draw[a, b]) > 1:
|
886
|
+
# Multi-edge
|
887
|
+
local_labels = edges_to_draw.pop((a, b))
|
888
|
+
|
889
|
+
# Compute perpendicular bisector
|
890
|
+
p1 = self._pos[a]
|
891
|
+
p2 = self._pos[b]
|
892
|
+
m = ((p1[0] + p2[0]) / 2., (p1[1] + p2[1]) / 2.) # midpoint
|
893
|
+
if not p1[1] == p2[1]:
|
894
|
+
s = (p1[0] - p2[0]) / (p2[1] - p1[1]) # perp slope
|
895
|
+
|
896
|
+
def y(x):
|
897
|
+
return s * (x - m[0]) + m[1] # perp bisector line
|
898
|
+
|
899
|
+
# f, g are functions to determine x-values of point
|
900
|
+
# on line y at distance d from point m (on each side)
|
901
|
+
def f(d):
|
902
|
+
return sqrt(d**2 / (1. + s**2)) + m[0]
|
903
|
+
|
904
|
+
def g(d):
|
905
|
+
return -sqrt(d**2 / (1. + s**2)) + m[0]
|
906
|
+
|
907
|
+
odd_x = f
|
908
|
+
even_x = g
|
909
|
+
if p1[0] == p2[0]:
|
910
|
+
def odd_y(d):
|
911
|
+
return m[1]
|
912
|
+
|
913
|
+
even_y = odd_y
|
914
|
+
else:
|
915
|
+
def odd_y(x):
|
916
|
+
return y(f(x))
|
917
|
+
|
918
|
+
def even_y(x):
|
919
|
+
return y(g(x))
|
920
|
+
else:
|
921
|
+
def odd_x(d):
|
922
|
+
return m[0]
|
923
|
+
|
924
|
+
even_x = odd_x
|
925
|
+
|
926
|
+
def odd_y(d):
|
927
|
+
return m[1] + d
|
928
|
+
|
929
|
+
def even_y(d):
|
930
|
+
return m[1] - d
|
931
|
+
|
932
|
+
def odd_xy(d):
|
933
|
+
return (odd_x(d), odd_y(d))
|
934
|
+
|
935
|
+
def even_xy(d):
|
936
|
+
return (even_x(d), even_y(d))
|
937
|
+
|
938
|
+
# We now have the control points for each Bezier curve
|
939
|
+
# in terms of distance parameter d.
|
940
|
+
# Also note each edge label should be drawn at d/2.
|
941
|
+
# (This is because we're using the perp bisectors).
|
942
|
+
distance = dist
|
943
|
+
len_local_labels = len(local_labels)
|
944
|
+
if len_local_labels * dist > max_dist:
|
945
|
+
distance = float(max_dist) / len_local_labels
|
946
|
+
for i in range(len_local_labels // 2):
|
947
|
+
k = (i + 1.0) * distance
|
948
|
+
estyle = self._options['edge_style']
|
949
|
+
ethickness = self._options['edge_thickness']
|
950
|
+
|
951
|
+
if self._arcdigraph:
|
952
|
+
vr = self._vertex_radius
|
953
|
+
ph = self._polar_hack_for_multidigraph
|
954
|
+
odd_start = ph(p1, odd_xy(k), vr)[0]
|
955
|
+
odd_end = ph(odd_xy(k), p2, vr)[1]
|
956
|
+
even_start = ph(p1, even_xy(k), vr)[0]
|
957
|
+
even_end = ph(even_xy(k), p2, vr)[1]
|
958
|
+
|
959
|
+
self._plot_components['edges'].append(
|
960
|
+
arrow(path=[[odd_start, odd_xy(k), odd_end]],
|
961
|
+
head=local_labels[2 * i][2], zorder=1,
|
962
|
+
rgbcolor=local_labels[2 * i][1],
|
963
|
+
linestyle=estyle,
|
964
|
+
width=ethickness,
|
965
|
+
**eoptions
|
966
|
+
))
|
967
|
+
self._plot_components['edges'].append(
|
968
|
+
arrow(path=[[even_start, even_xy(k), even_end]],
|
969
|
+
head=local_labels[2 * i + 1][2], zorder=1,
|
970
|
+
rgbcolor=local_labels[2 * i + 1][1],
|
971
|
+
linestyle=estyle,
|
972
|
+
width=ethickness,
|
973
|
+
**eoptions
|
974
|
+
))
|
975
|
+
else:
|
976
|
+
self._plot_components['edges'].append(
|
977
|
+
bezier_path([[p1, odd_xy(k), p2]], zorder=1,
|
978
|
+
rgbcolor=local_labels[2 * i][1],
|
979
|
+
linestyle=estyle,
|
980
|
+
thickness=ethickness
|
981
|
+
))
|
982
|
+
self._plot_components['edges'].append(
|
983
|
+
bezier_path([[p1, even_xy(k), p2]], zorder=1,
|
984
|
+
rgbcolor=local_labels[2 * i + 1][1],
|
985
|
+
linestyle=estyle,
|
986
|
+
thickness=ethickness
|
987
|
+
))
|
988
|
+
if labels:
|
989
|
+
j = k / 2.0
|
990
|
+
bg = self._options['edge_labels_background']
|
991
|
+
self._plot_components['edge_labels'].append(
|
992
|
+
text(local_labels[2 * i][0], odd_xy(j),
|
993
|
+
background_color=bg, fontsize=self._options['label_fontsize']))
|
994
|
+
self._plot_components['edge_labels'].append(
|
995
|
+
text(local_labels[2 * i + 1][0], even_xy(j),
|
996
|
+
background_color=bg, fontsize=self._options['label_fontsize']))
|
997
|
+
if len_local_labels % 2:
|
998
|
+
# draw line for last odd
|
999
|
+
edges_to_draw[a, b] = [local_labels[-1]]
|
1000
|
+
|
1001
|
+
is_directed = self._graph.is_directed()
|
1002
|
+
for a, b in edges_to_draw:
|
1003
|
+
elabel = edges_to_draw[a, b][0][0]
|
1004
|
+
ecolor = edges_to_draw[a, b][0][1]
|
1005
|
+
ehead = edges_to_draw[a, b][0][2]
|
1006
|
+
e = (a, b, elabel)
|
1007
|
+
|
1008
|
+
estyle = self._options['edge_style']
|
1009
|
+
ethickness = self._options['edge_thickness']
|
1010
|
+
if (style_key_edges is not None
|
1011
|
+
and ((style_key_edges and e in self._options['edge_styles'])
|
1012
|
+
or (not style_key_edges and elabel in self._options['edge_styles']))):
|
1013
|
+
estyle = style_key_edges and self._options['edge_styles'][e] or self._options['edge_styles'][elabel]
|
1014
|
+
if (thickness_key_edges is not None
|
1015
|
+
and ((thickness_key_edges and e in self._options['edge_thicknesses'])
|
1016
|
+
or (not thickness_key_edges and elabel in self._options['edge_thicknesses']))):
|
1017
|
+
ethickness = thickness_key_edges and self._options['edge_thicknesses'][e] or self._options['edge_thicknesses'][elabel]
|
1018
|
+
|
1019
|
+
if self._arcdigraph:
|
1020
|
+
ph = self._polar_hack_for_multidigraph
|
1021
|
+
C, D = ph(self._pos[a], self._pos[b], self._vertex_radius)
|
1022
|
+
self._plot_components['edges'].append(
|
1023
|
+
arrow(C, D,
|
1024
|
+
rgbcolor=ecolor,
|
1025
|
+
head=ehead,
|
1026
|
+
linestyle=estyle,
|
1027
|
+
width=ethickness,
|
1028
|
+
**eoptions
|
1029
|
+
))
|
1030
|
+
if labels:
|
1031
|
+
bg = self._options['edge_labels_background']
|
1032
|
+
self._plot_components['edge_labels'].append(
|
1033
|
+
text(str(elabel),
|
1034
|
+
[(C[0] + D[0]) / 2., (C[1] + D[1]) / 2.],
|
1035
|
+
background_color=bg,
|
1036
|
+
fontsize=self._options['label_fontsize']
|
1037
|
+
))
|
1038
|
+
elif is_directed:
|
1039
|
+
self._plot_components['edges'].append(
|
1040
|
+
arrow(self._pos[a], self._pos[b],
|
1041
|
+
rgbcolor=ecolor,
|
1042
|
+
arrowshorten=self._arrowshorten,
|
1043
|
+
head=ehead,
|
1044
|
+
linestyle=estyle,
|
1045
|
+
width=ethickness,
|
1046
|
+
**eoptions
|
1047
|
+
))
|
1048
|
+
else:
|
1049
|
+
self._plot_components['edges'].append(
|
1050
|
+
line([self._pos[a], self._pos[b]],
|
1051
|
+
rgbcolor=ecolor,
|
1052
|
+
linestyle=estyle,
|
1053
|
+
thickness=ethickness
|
1054
|
+
))
|
1055
|
+
if labels and not self._arcdigraph:
|
1056
|
+
bg = self._options['edge_labels_background']
|
1057
|
+
self._plot_components['edge_labels'].append(
|
1058
|
+
text(str(edges_to_draw[a, b][0][0]),
|
1059
|
+
[(self._pos[a][0] + self._pos[b][0]) / 2.,
|
1060
|
+
(self._pos[a][1] + self._pos[b][1]) / 2.],
|
1061
|
+
background_color=bg,
|
1062
|
+
fontsize=self._options['label_fontsize']
|
1063
|
+
))
|
1064
|
+
|
1065
|
+
def _polar_hack_for_multidigraph(self, A, B, VR):
|
1066
|
+
"""
|
1067
|
+
Helper function to quickly compute the two points of intersection
|
1068
|
+
of a line segment from ``A`` to ``B`` (provided as xy pairs) and
|
1069
|
+
circles centered at ``A`` and ``B``, both with radius ``VR``.
|
1070
|
+
Returns a pair of xy pairs representing the two points.
|
1071
|
+
|
1072
|
+
EXAMPLES::
|
1073
|
+
|
1074
|
+
sage: d = DiGraph(loops=True, multiedges=True, sparse=True)
|
1075
|
+
sage: d.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
1076
|
+
....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
1077
|
+
....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
1078
|
+
sage: GP = d.graphplot(vertex_size=100, edge_labels=True,
|
1079
|
+
....: color_by_label=True, edge_style='dashed')
|
1080
|
+
sage: GP._polar_hack_for_multidigraph((0, 1), (1, 1), .1)
|
1081
|
+
([0.10..., 1.00...], [0.90..., 1.00...])
|
1082
|
+
|
1083
|
+
TESTS:
|
1084
|
+
|
1085
|
+
Make sure that Python ints are acceptable arguments (:issue:`10124`)::
|
1086
|
+
|
1087
|
+
sage: GP = DiGraph().graphplot()
|
1088
|
+
sage: GP._polar_hack_for_multidigraph((0, 1), (2, 2), .1)
|
1089
|
+
([0.08..., 1.04...], [1.91..., 1.95...])
|
1090
|
+
sage: GP._polar_hack_for_multidigraph((int(0), int(1)),
|
1091
|
+
....: (int(2), int(2)), .1)
|
1092
|
+
([0.08..., 1.04...], [1.91..., 1.95...])
|
1093
|
+
"""
|
1094
|
+
D = [float(B[i] - A[i]) for i in range(2)]
|
1095
|
+
R = sqrt(D[0]**2 + D[1]**2)
|
1096
|
+
theta = 3 * pi / 2
|
1097
|
+
if D[0] > 0:
|
1098
|
+
theta = atan(D[1] / D[0])
|
1099
|
+
if D[1] < 0:
|
1100
|
+
theta += 2 * pi
|
1101
|
+
elif D[0] < 0:
|
1102
|
+
theta = atan(D[1] / D[0]) + pi
|
1103
|
+
elif D[1] > 0:
|
1104
|
+
theta = pi / 2
|
1105
|
+
cos_theta = cos(theta)
|
1106
|
+
sin_theta = sin(theta)
|
1107
|
+
return ([VR * cos_theta + A[0], VR * sin_theta + A[1]],
|
1108
|
+
[(R - VR) * cos_theta + A[0], (R - VR) * sin_theta + A[1]])
|
1109
|
+
|
1110
|
+
def show(self, **kwds):
|
1111
|
+
"""
|
1112
|
+
Show the (di)graph associated with this ``GraphPlot`` object.
|
1113
|
+
|
1114
|
+
INPUT:
|
1115
|
+
|
1116
|
+
This method accepts all parameters of
|
1117
|
+
:meth:`sage.plot.graphics.Graphics.show`.
|
1118
|
+
|
1119
|
+
.. NOTE::
|
1120
|
+
|
1121
|
+
- See :mod:`the module's documentation <sage.graphs.graph_plot>`
|
1122
|
+
for information on default values of this method.
|
1123
|
+
|
1124
|
+
- Any options not used by plot will be passed on to the
|
1125
|
+
:meth:`~sage.plot.graphics.Graphics.show` method.
|
1126
|
+
|
1127
|
+
EXAMPLES::
|
1128
|
+
|
1129
|
+
sage: C = graphs.CubeGraph(8)
|
1130
|
+
sage: P = C.graphplot(vertex_labels=False, vertex_size=0,
|
1131
|
+
....: graph_border=True)
|
1132
|
+
sage: P.show()
|
1133
|
+
|
1134
|
+
.. PLOT::
|
1135
|
+
|
1136
|
+
C = graphs.CubeGraph(8)
|
1137
|
+
P = C.graphplot(vertex_labels=False, vertex_size=0,
|
1138
|
+
graph_border=True)
|
1139
|
+
sphinx_plot(P)
|
1140
|
+
"""
|
1141
|
+
# Setting the default values if needed
|
1142
|
+
for k, value in DEFAULT_SHOW_OPTIONS.items():
|
1143
|
+
if k not in kwds:
|
1144
|
+
kwds[k] = value
|
1145
|
+
|
1146
|
+
self.plot().show(**kwds)
|
1147
|
+
|
1148
|
+
def plot(self, **kwds):
|
1149
|
+
"""
|
1150
|
+
Return a graphics object representing the (di)graph.
|
1151
|
+
|
1152
|
+
INPUT:
|
1153
|
+
|
1154
|
+
The options accepted by this method are to be found in the
|
1155
|
+
documentation of the :mod:`sage.graphs.graph_plot` module,
|
1156
|
+
and the :meth:`~sage.plot.graphics.Graphics.show` method.
|
1157
|
+
|
1158
|
+
.. NOTE::
|
1159
|
+
|
1160
|
+
See :mod:`the module's documentation <sage.graphs.graph_plot>` for
|
1161
|
+
information on default values of this method.
|
1162
|
+
|
1163
|
+
We can specify some pretty precise plotting of familiar graphs::
|
1164
|
+
|
1165
|
+
sage: from math import sin, cos, pi
|
1166
|
+
sage: P = graphs.PetersenGraph()
|
1167
|
+
sage: d = {'#FF0000': [0, 5], '#FF9900': [1, 6], '#FFFF00': [2, 7],
|
1168
|
+
....: '#00FF00': [3, 8], '#0000FF': [4,9]}
|
1169
|
+
sage: pos_dict = {}
|
1170
|
+
sage: for i in range(5):
|
1171
|
+
....: x = float(cos(pi/2 + ((2*pi)/5)*i))
|
1172
|
+
....: y = float(sin(pi/2 + ((2*pi)/5)*i))
|
1173
|
+
....: pos_dict[i] = [x,y]
|
1174
|
+
...
|
1175
|
+
sage: for i in range(5, 10):
|
1176
|
+
....: x = float(0.5*cos(pi/2 + ((2*pi)/5)*i))
|
1177
|
+
....: y = float(0.5*sin(pi/2 + ((2*pi)/5)*i))
|
1178
|
+
....: pos_dict[i] = [x,y]
|
1179
|
+
...
|
1180
|
+
sage: pl = P.graphplot(pos=pos_dict, vertex_colors=d)
|
1181
|
+
sage: pl.show()
|
1182
|
+
|
1183
|
+
.. PLOT::
|
1184
|
+
|
1185
|
+
from math import sin, cos, pi
|
1186
|
+
P = graphs.PetersenGraph()
|
1187
|
+
d = {'#FF0000': [0, 5], '#FF9900': [1, 6], '#FFFF00': [2, 7],
|
1188
|
+
'#00FF00': [3, 8], '#0000FF': [4,9]}
|
1189
|
+
pos_dict = {}
|
1190
|
+
for i in range(5):
|
1191
|
+
x = float(cos(pi/2 + ((2*pi)/5)*i))
|
1192
|
+
y = float(sin(pi/2 + ((2*pi)/5)*i))
|
1193
|
+
pos_dict[i] = [x,y]
|
1194
|
+
|
1195
|
+
for i in range(5, 10):
|
1196
|
+
x = float(0.5*cos(pi/2 + ((2*pi)/5)*i))
|
1197
|
+
y = float(0.5*sin(pi/2 + ((2*pi)/5)*i))
|
1198
|
+
pos_dict[i] = [x,y]
|
1199
|
+
|
1200
|
+
pl = P.graphplot(pos=pos_dict, vertex_colors=d)
|
1201
|
+
sphinx_plot(pl)
|
1202
|
+
|
1203
|
+
Here are some more common graphs with typical options::
|
1204
|
+
|
1205
|
+
sage: C = graphs.CubeGraph(8)
|
1206
|
+
sage: P = C.graphplot(vertex_labels=False, vertex_size=0,
|
1207
|
+
....: graph_border=True)
|
1208
|
+
sage: P.show()
|
1209
|
+
|
1210
|
+
.. PLOT::
|
1211
|
+
|
1212
|
+
C = graphs.CubeGraph(8)
|
1213
|
+
P = C.graphplot(vertex_labels=False, vertex_size=0,
|
1214
|
+
graph_border=True)
|
1215
|
+
sphinx_plot(P)
|
1216
|
+
|
1217
|
+
::
|
1218
|
+
|
1219
|
+
sage: G = graphs.HeawoodGraph().copy(sparse=True)
|
1220
|
+
sage: for u, v, l in G.edges(sort=True):
|
1221
|
+
....: G.set_edge_label(u, v, f'({u},{v})')
|
1222
|
+
sage: G.graphplot(edge_labels=True).show()
|
1223
|
+
|
1224
|
+
.. PLOT::
|
1225
|
+
|
1226
|
+
G = graphs.HeawoodGraph().copy(sparse=True)
|
1227
|
+
for u, v, l in G.edges(sort=True):
|
1228
|
+
G.set_edge_label(u, v, f'({u},{v})')
|
1229
|
+
sphinx_plot(G.graphplot(edge_labels=True))
|
1230
|
+
|
1231
|
+
The options for plotting also work with directed graphs::
|
1232
|
+
|
1233
|
+
sage: D = DiGraph({
|
1234
|
+
....: 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4],
|
1235
|
+
....: 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9],
|
1236
|
+
....: 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13],
|
1237
|
+
....: 13: [14], 14: [15], 15: [16], 16: [17], 17: [18],
|
1238
|
+
....: 18: [19], 19: []})
|
1239
|
+
sage: for u, v, l in D.edges(sort=True):
|
1240
|
+
....: D.set_edge_label(u, v, f'({u},{v})')
|
1241
|
+
sage: D.graphplot(edge_labels=True, layout='circular').show()
|
1242
|
+
|
1243
|
+
.. PLOT::
|
1244
|
+
|
1245
|
+
D = DiGraph({
|
1246
|
+
0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4],
|
1247
|
+
4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9],
|
1248
|
+
9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13],
|
1249
|
+
13: [14], 14: [15], 15: [16], 16: [17], 17: [18],
|
1250
|
+
18: [19], 19: []})
|
1251
|
+
for u, v, l in D.edges(sort=True):
|
1252
|
+
D.set_edge_label(u, v, f'({u},{v})')
|
1253
|
+
sphinx_plot(D.graphplot(edge_labels=True, layout='circular'))
|
1254
|
+
|
1255
|
+
For graphs with ``circular`` layouts, one may shift the vertex labels by
|
1256
|
+
specifying coordinates to shift by::
|
1257
|
+
|
1258
|
+
sage: D = DiGraph({
|
1259
|
+
....: 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4],
|
1260
|
+
....: 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9],
|
1261
|
+
....: 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13],
|
1262
|
+
....: 13: [14], 14: [15], 15: [16], 16: [17], 17: [18],
|
1263
|
+
....: 18: [19], 19: []})
|
1264
|
+
sage: for u, v, l in D.edges(sort=True):
|
1265
|
+
....: D.set_edge_label(u, v, f'({u},{v})')
|
1266
|
+
sage: D.graphplot(edge_labels=True, layout='circular', vertex_label_shift=(15,10)).show()
|
1267
|
+
|
1268
|
+
.. PLOT::
|
1269
|
+
|
1270
|
+
D = DiGraph({
|
1271
|
+
0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4],
|
1272
|
+
4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9],
|
1273
|
+
9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13],
|
1274
|
+
13: [14], 14: [15], 15: [16], 16: [17], 17: [18],
|
1275
|
+
18: [19], 19: []})
|
1276
|
+
for u, v, l in D.edges(sort=True):
|
1277
|
+
D.set_edge_label(u, v, f'({u},{v})')
|
1278
|
+
sphinx_plot(D.graphplot(edge_labels=True, layout='circular', vertex_label_shift=(15,10)))
|
1279
|
+
|
1280
|
+
This example shows off the coloring of edges::
|
1281
|
+
|
1282
|
+
sage: from sage.plot.colors import rainbow
|
1283
|
+
sage: C = graphs.CubeGraph(5)
|
1284
|
+
sage: R = rainbow(5)
|
1285
|
+
sage: edge_colors = {}
|
1286
|
+
sage: for i in range(5):
|
1287
|
+
....: edge_colors[R[i]] = []
|
1288
|
+
sage: for u, v, l in C.edges(sort=True):
|
1289
|
+
....: for i in range(5):
|
1290
|
+
....: if u[i] != v[i]:
|
1291
|
+
....: edge_colors[R[i]].append((u, v, l))
|
1292
|
+
sage: C.graphplot(vertex_labels=False, vertex_size=0,
|
1293
|
+
....: edge_colors=edge_colors).show()
|
1294
|
+
|
1295
|
+
.. PLOT::
|
1296
|
+
|
1297
|
+
from sage.plot.colors import rainbow
|
1298
|
+
C = graphs.CubeGraph(5)
|
1299
|
+
R = rainbow(5)
|
1300
|
+
edge_colors = {}
|
1301
|
+
for i in range(5):
|
1302
|
+
edge_colors[R[i]] = []
|
1303
|
+
for u, v, l in C.edges(sort=True):
|
1304
|
+
for i in range(5):
|
1305
|
+
if u[i] != v[i]:
|
1306
|
+
edge_colors[R[i]].append((u, v, l))
|
1307
|
+
sphinx_plot(C.graphplot(vertex_labels=False, vertex_size=0,
|
1308
|
+
edge_colors=edge_colors))
|
1309
|
+
|
1310
|
+
With the ``partition`` option, we can separate out same-color groups
|
1311
|
+
of vertices::
|
1312
|
+
|
1313
|
+
sage: D = graphs.DodecahedralGraph()
|
1314
|
+
sage: Pi = [[6, 5, 15, 14, 7], [16, 13, 8, 2, 4],
|
1315
|
+
....: [12, 17, 9, 3, 1], [0, 19, 18, 10, 11]]
|
1316
|
+
sage: D.show(partition=Pi)
|
1317
|
+
|
1318
|
+
.. PLOT::
|
1319
|
+
|
1320
|
+
D = graphs.DodecahedralGraph()
|
1321
|
+
Pi = [[6, 5, 15, 14, 7], [16, 13, 8, 2, 4],
|
1322
|
+
[12, 17, 9, 3, 1], [0, 19, 18, 10, 11]]
|
1323
|
+
sphinx_plot(D.plot(partition=Pi))
|
1324
|
+
|
1325
|
+
Loops are also plotted correctly::
|
1326
|
+
|
1327
|
+
sage: G = graphs.PetersenGraph()
|
1328
|
+
sage: G.allow_loops(True)
|
1329
|
+
sage: G.add_edge(0,0)
|
1330
|
+
sage: G.show()
|
1331
|
+
|
1332
|
+
.. PLOT::
|
1333
|
+
|
1334
|
+
G = graphs.PetersenGraph()
|
1335
|
+
G.allow_loops(True)
|
1336
|
+
G.add_edge(0,0)
|
1337
|
+
sphinx_plot(G)
|
1338
|
+
|
1339
|
+
::
|
1340
|
+
|
1341
|
+
sage: D = DiGraph({0:[0,1], 1:[2], 2:[3]}, loops=True)
|
1342
|
+
sage: D.show()
|
1343
|
+
sage: D.show(edge_colors={(0, 1, 0): [(0, 1, None), (1, 2, None)],
|
1344
|
+
....: (0, 0, 0): [(2, 3, None)]})
|
1345
|
+
|
1346
|
+
.. PLOT::
|
1347
|
+
|
1348
|
+
D = DiGraph({0:[0,1], 1:[2], 2:[3]}, loops=True)
|
1349
|
+
P = D.plot(edge_colors={(0, 1, 0): [(0, 1, None), (1, 2, None)],
|
1350
|
+
(0, 0, 0): [(2, 3, None)]})
|
1351
|
+
sphinx_plot(P)
|
1352
|
+
|
1353
|
+
More options::
|
1354
|
+
|
1355
|
+
sage: pos = {0: [0.0, 1.5], 1: [-0.8, 0.3], 2: [-0.6, -0.8],
|
1356
|
+
....: 3:[0.6, -0.8], 4:[0.8, 0.3]}
|
1357
|
+
sage: g = Graph({0: [1], 1: [2], 2: [3], 3: [4], 4: [0]})
|
1358
|
+
sage: g.graphplot(pos=pos, layout='spring', iterations=0).plot()
|
1359
|
+
Graphics object consisting of 11 graphics primitives
|
1360
|
+
|
1361
|
+
.. PLOT::
|
1362
|
+
|
1363
|
+
pos = {0: [0.0, 1.5], 1: [-0.8, 0.3], 2: [-0.6, -0.8],
|
1364
|
+
3: [0.6, -0.8], 4:[0.8, 0.3]}
|
1365
|
+
g = Graph({0: [1], 1: [2], 2: [3], 3: [4], 4: [0]})
|
1366
|
+
P = g.graphplot(pos=pos, layout='spring', iterations=0).plot()
|
1367
|
+
sphinx_plot(P)
|
1368
|
+
|
1369
|
+
::
|
1370
|
+
|
1371
|
+
sage: D = graphs.CubeGraph(3)
|
1372
|
+
sage: D.graphplot(layout='planar').plot() # needs planarity
|
1373
|
+
Graphics object consisting of 21 graphics primitives
|
1374
|
+
|
1375
|
+
.. PLOT::
|
1376
|
+
|
1377
|
+
D = graphs.CubeGraph(3)
|
1378
|
+
sphinx_plot(D.graphplot(layout='planar'))
|
1379
|
+
|
1380
|
+
::
|
1381
|
+
|
1382
|
+
sage: G = Graph()
|
1383
|
+
sage: P = G.graphplot().plot()
|
1384
|
+
sage: P.axes()
|
1385
|
+
False
|
1386
|
+
sage: G = DiGraph()
|
1387
|
+
sage: P = G.graphplot().plot()
|
1388
|
+
sage: P.axes()
|
1389
|
+
False
|
1390
|
+
|
1391
|
+
We can plot multiple graphs::
|
1392
|
+
|
1393
|
+
sage: T = list(graphs.trees(7))
|
1394
|
+
sage: t = T[3]
|
1395
|
+
sage: t.graphplot(heights={0: [0], 1: [4, 5, 1],
|
1396
|
+
....: 2: [2], 3: [3, 6]}
|
1397
|
+
....: ).plot()
|
1398
|
+
Graphics object consisting of 14 graphics primitives
|
1399
|
+
|
1400
|
+
.. PLOT::
|
1401
|
+
|
1402
|
+
T = list(graphs.trees(7))
|
1403
|
+
t = T[3]
|
1404
|
+
sphinx_plot(t.graphplot(heights={0: [0], 1: [4, 5, 1],
|
1405
|
+
2: [2], 3: [3, 6]}))
|
1406
|
+
|
1407
|
+
::
|
1408
|
+
|
1409
|
+
sage: T = list(graphs.trees(7))
|
1410
|
+
sage: t = T[3]
|
1411
|
+
sage: t.graphplot(heights={0: [0], 1: [4, 5, 1],
|
1412
|
+
....: 2: [2], 3: [3, 6]}
|
1413
|
+
....: ).plot()
|
1414
|
+
Graphics object consisting of 14 graphics primitives
|
1415
|
+
|
1416
|
+
.. PLOT::
|
1417
|
+
|
1418
|
+
T = list(graphs.trees(7))
|
1419
|
+
t = T[3]
|
1420
|
+
sphinx_plot(t.graphplot(heights={0: [0], 1: [4, 5, 1],
|
1421
|
+
2: [2], 3: [3, 6]}))
|
1422
|
+
|
1423
|
+
::
|
1424
|
+
|
1425
|
+
sage: t.set_edge_label(0, 1, -7)
|
1426
|
+
sage: t.set_edge_label(0, 5, 3)
|
1427
|
+
sage: t.set_edge_label(0, 5, 99)
|
1428
|
+
sage: t.set_edge_label(1, 2, 1000)
|
1429
|
+
sage: t.set_edge_label(3, 2, 'spam')
|
1430
|
+
sage: t.set_edge_label(2, 6, 3/2)
|
1431
|
+
sage: t.set_edge_label(0, 4, 66)
|
1432
|
+
sage: t.graphplot(heights={0: [0], 1: [4, 5, 1],
|
1433
|
+
....: 2: [2], 3: [3, 6]},
|
1434
|
+
....: edge_labels=True
|
1435
|
+
....: ).plot()
|
1436
|
+
Graphics object consisting of 20 graphics primitives
|
1437
|
+
|
1438
|
+
.. PLOT::
|
1439
|
+
|
1440
|
+
T = list(graphs.trees(7))
|
1441
|
+
t = T[3]
|
1442
|
+
t.set_edge_label(0, 1, -7)
|
1443
|
+
t.set_edge_label(0, 5, 3)
|
1444
|
+
t.set_edge_label(0, 5, 99)
|
1445
|
+
t.set_edge_label(1, 2, 1000)
|
1446
|
+
t.set_edge_label(3, 2, 'spam')
|
1447
|
+
t.set_edge_label(2, 6, 3/2)
|
1448
|
+
t.set_edge_label(0, 4, 66)
|
1449
|
+
sphinx_plot(t.graphplot(heights={0: [0], 1: [4, 5, 1],
|
1450
|
+
2: [2], 3: [3, 6]},
|
1451
|
+
edge_labels=True))
|
1452
|
+
|
1453
|
+
::
|
1454
|
+
|
1455
|
+
sage: T = list(graphs.trees(7))
|
1456
|
+
sage: t = T[3]
|
1457
|
+
sage: t.graphplot(layout='tree').show()
|
1458
|
+
|
1459
|
+
.. PLOT::
|
1460
|
+
|
1461
|
+
T = list(graphs.trees(7))
|
1462
|
+
t = T[3]
|
1463
|
+
sphinx_plot(t.graphplot(layout='tree'))
|
1464
|
+
|
1465
|
+
The tree layout is also useful::
|
1466
|
+
|
1467
|
+
sage: t = DiGraph('JCC???@A??GO??CO??GO??')
|
1468
|
+
sage: t.graphplot(layout='tree', tree_root=0,
|
1469
|
+
....: tree_orientation="up"
|
1470
|
+
....: ).show()
|
1471
|
+
|
1472
|
+
.. PLOT::
|
1473
|
+
|
1474
|
+
t = DiGraph('JCC???@A??GO??CO??GO??')
|
1475
|
+
sphinx_plot(t.graphplot(layout='tree', tree_root=0,
|
1476
|
+
tree_orientation='up'))
|
1477
|
+
|
1478
|
+
More examples::
|
1479
|
+
|
1480
|
+
sage: D = DiGraph({0:[1,2,3], 2:[1,4], 3:[0]})
|
1481
|
+
sage: D.graphplot().show()
|
1482
|
+
|
1483
|
+
.. PLOT::
|
1484
|
+
|
1485
|
+
D = DiGraph({0:[1,2,3], 2:[1,4], 3:[0]})
|
1486
|
+
sphinx_plot(D.graphplot())
|
1487
|
+
|
1488
|
+
::
|
1489
|
+
|
1490
|
+
sage: D = DiGraph({0:[1,2,3], 2:[1,4], 3:[0]})
|
1491
|
+
sage: D.graphplot(label_fontsize=20, arrowsize=10).show()
|
1492
|
+
|
1493
|
+
.. PLOT::
|
1494
|
+
|
1495
|
+
D = DiGraph({0:[1,2,3], 2:[1,4], 3:[0]})
|
1496
|
+
sphinx_plot(D.graphplot(label_fontsize=20, arrowsize=10))
|
1497
|
+
|
1498
|
+
|
1499
|
+
::
|
1500
|
+
|
1501
|
+
sage: D = DiGraph(multiedges=True, sparse=True)
|
1502
|
+
sage: for i in range(5):
|
1503
|
+
....: D.add_edge((i, i + 1, 'a'))
|
1504
|
+
....: D.add_edge((i, i - 1, 'b'))
|
1505
|
+
sage: D.graphplot(edge_labels=True,
|
1506
|
+
....: edge_colors=D._color_by_label()
|
1507
|
+
....: ).plot()
|
1508
|
+
Graphics object consisting of 34 graphics primitives
|
1509
|
+
|
1510
|
+
.. PLOT::
|
1511
|
+
|
1512
|
+
D = DiGraph(multiedges=True, sparse=True)
|
1513
|
+
for i in range(5):
|
1514
|
+
D.add_edge((i, i + 1, 'a'))
|
1515
|
+
D.add_edge((i, i - 1, 'b'))
|
1516
|
+
sphinx_plot(D.graphplot(edge_labels=True,
|
1517
|
+
edge_colors=D._color_by_label()))
|
1518
|
+
|
1519
|
+
::
|
1520
|
+
|
1521
|
+
sage: g = Graph({}, loops=True, multiedges=True, sparse=True)
|
1522
|
+
sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
1523
|
+
....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
1524
|
+
....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
1525
|
+
sage: g.graphplot(edge_labels=True,
|
1526
|
+
....: color_by_label=True,
|
1527
|
+
....: edge_style='dashed'
|
1528
|
+
....: ).plot()
|
1529
|
+
Graphics object consisting of 22 graphics primitives
|
1530
|
+
|
1531
|
+
.. PLOT::
|
1532
|
+
|
1533
|
+
g = Graph({}, loops=True, multiedges=True, sparse=True)
|
1534
|
+
g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
1535
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
1536
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
1537
|
+
sphinx_plot(g.graphplot(edge_labels=True,
|
1538
|
+
color_by_label=True,
|
1539
|
+
edge_style='dashed'))
|
1540
|
+
|
1541
|
+
The ``edge_style`` option may be provided in the short format too::
|
1542
|
+
|
1543
|
+
|
1544
|
+
sage: g.graphplot(edge_labels=True,
|
1545
|
+
....: color_by_label=True,
|
1546
|
+
....: edge_style='--'
|
1547
|
+
....: ).plot()
|
1548
|
+
Graphics object consisting of 22 graphics primitives
|
1549
|
+
|
1550
|
+
The ``edge_styles`` option may be provided if you need only certain edges
|
1551
|
+
to have certain styles::
|
1552
|
+
|
1553
|
+
sage: g = Graph(loops=True, multiedges=True, sparse=True)
|
1554
|
+
sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
1555
|
+
....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
1556
|
+
....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
1557
|
+
sage: GP = g.graphplot(vertex_size=100, edge_labels=True,
|
1558
|
+
....: color_by_label=True, edge_style='dashed')
|
1559
|
+
sage: GP.set_edges(edge_styles={'a':'dashed', 'g':'dotted'})
|
1560
|
+
sage: GP.plot()
|
1561
|
+
Graphics object consisting of 22 graphics primitives
|
1562
|
+
|
1563
|
+
.. PLOT::
|
1564
|
+
|
1565
|
+
g = Graph(loops=True, multiedges=True, sparse=True)
|
1566
|
+
g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
1567
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
1568
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
1569
|
+
GP = g.graphplot(vertex_size=100, edge_labels=True,
|
1570
|
+
color_by_label=True, edge_style='dashed')
|
1571
|
+
GP.set_edges(edge_style='solid')
|
1572
|
+
GP.set_edges(edge_color='black')
|
1573
|
+
GP.set_edges(edge_styles={'a':'dashed', 'g':'dotted'})
|
1574
|
+
sphinx_plot(GP)
|
1575
|
+
|
1576
|
+
::
|
1577
|
+
|
1578
|
+
sage: g = Graph(loops=True, multiedges=True, sparse=True)
|
1579
|
+
sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
1580
|
+
....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
1581
|
+
....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
1582
|
+
sage: GP = g.graphplot(vertex_size=100, edge_labels=True,
|
1583
|
+
....: color_by_label=True, edge_thickness=3)
|
1584
|
+
sage: GP.set_edges(edge_thicknesses={'a':1, 'g':5})
|
1585
|
+
sage: GP.plot()
|
1586
|
+
Graphics object consisting of 22 graphics primitives
|
1587
|
+
|
1588
|
+
.. PLOT::
|
1589
|
+
|
1590
|
+
g = Graph(loops=True, multiedges=True, sparse=True)
|
1591
|
+
g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'),
|
1592
|
+
(0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'),
|
1593
|
+
(0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')])
|
1594
|
+
GP = g.graphplot(vertex_size=100, edge_labels=True,
|
1595
|
+
color_by_label=True, edge_thickness=3)
|
1596
|
+
GP.set_edges(edge_style='solid')
|
1597
|
+
GP.set_edges(edge_color='black')
|
1598
|
+
GP.set_edges(edge_thicknesses={'a':1, 'g':5})
|
1599
|
+
sphinx_plot(GP)
|
1600
|
+
|
1601
|
+
TESTS:
|
1602
|
+
|
1603
|
+
Make sure that show options work with plot also::
|
1604
|
+
|
1605
|
+
sage: g = Graph({})
|
1606
|
+
sage: g.plot(title='empty graph', axes=True)
|
1607
|
+
Graphics object consisting of 0 graphics primitives
|
1608
|
+
|
1609
|
+
Check for invalid inputs::
|
1610
|
+
|
1611
|
+
sage: p = graphs.PetersenGraph().plot(egabrag='garbage')
|
1612
|
+
Traceback (most recent call last):
|
1613
|
+
...
|
1614
|
+
ValueError: invalid input 'egabrag=garbage'
|
1615
|
+
|
1616
|
+
Make sure that no graphics primitive is clipped::
|
1617
|
+
|
1618
|
+
sage: tadpole = Graph({0: [0, 1]}).plot()
|
1619
|
+
sage: bbox = tadpole.get_minmax_data()
|
1620
|
+
sage: for part in tadpole:
|
1621
|
+
....: part_bbox = part.get_minmax_data()
|
1622
|
+
....: assert (bbox['xmin'] <= part_bbox['xmin']
|
1623
|
+
....: <= part_bbox['xmax'] <= bbox['xmax'])
|
1624
|
+
....: assert (bbox['ymin'] <= part_bbox['ymin']
|
1625
|
+
....: <= part_bbox['ymax'] <= bbox['ymax'])
|
1626
|
+
|
1627
|
+
Check that one can plot immutable graphs (:issue:`17340`)::
|
1628
|
+
|
1629
|
+
sage: Graph({0: [0]}, immutable=True).plot()
|
1630
|
+
Graphics object consisting of 3 graphics primitives
|
1631
|
+
"""
|
1632
|
+
G = Graphics()
|
1633
|
+
options = self._options.copy()
|
1634
|
+
options.update(kwds)
|
1635
|
+
G._set_extra_kwds(Graphics._extract_kwds_for_show(options))
|
1636
|
+
|
1637
|
+
# Check the arguments
|
1638
|
+
for o in options:
|
1639
|
+
if o not in graphplot_options and o not in G._extra_kwds:
|
1640
|
+
raise ValueError("invalid input '{}={}'".format(o, options[o]))
|
1641
|
+
|
1642
|
+
for comp in self._plot_components.values():
|
1643
|
+
if not isinstance(comp, list):
|
1644
|
+
G += comp
|
1645
|
+
else:
|
1646
|
+
for item in comp:
|
1647
|
+
G += item
|
1648
|
+
|
1649
|
+
if self._options['graph_border']:
|
1650
|
+
xmin = G.xmin()
|
1651
|
+
xmax = G.xmax()
|
1652
|
+
ymin = G.ymin()
|
1653
|
+
ymax = G.ymax()
|
1654
|
+
dx = (xmax - xmin) / 10.0
|
1655
|
+
dy = (ymax - ymin) / 10.0
|
1656
|
+
border = (line([(xmin - dx, ymin - dy), (xmin - dx, ymax + dy),
|
1657
|
+
(xmax + dx, ymax + dy), (xmax + dx, ymin - dy),
|
1658
|
+
(xmin - dx, ymin - dy)], thickness=1.3))
|
1659
|
+
border.axes_range(xmin=(xmin - dx), xmax=(xmax + dx),
|
1660
|
+
ymin=(ymin - dy), ymax=(ymax + dy))
|
1661
|
+
G += border
|
1662
|
+
G.set_aspect_ratio(1)
|
1663
|
+
G.axes(False)
|
1664
|
+
return G
|
1665
|
+
|
1666
|
+
def layout_tree(self, root, orientation):
|
1667
|
+
"""
|
1668
|
+
Compute a nice layout of a tree.
|
1669
|
+
|
1670
|
+
INPUT:
|
1671
|
+
|
1672
|
+
- ``root`` -- the root vertex
|
1673
|
+
|
1674
|
+
- ``orientation`` -- whether to place the root at the top or at the
|
1675
|
+
bottom:
|
1676
|
+
|
1677
|
+
* ``orientation="down"`` -- children are placed below their parent
|
1678
|
+
* ``orientation="top"`` -- children are placed above their parent
|
1679
|
+
|
1680
|
+
EXAMPLES::
|
1681
|
+
|
1682
|
+
sage: from sage.graphs.graph_plot import GraphPlot
|
1683
|
+
sage: G = graphs.HoffmanSingletonGraph()
|
1684
|
+
sage: T = Graph()
|
1685
|
+
sage: T.add_edges(G.min_spanning_tree(starting_vertex=0))
|
1686
|
+
sage: T.show(layout='tree', tree_root=0) # indirect doctest
|
1687
|
+
"""
|
1688
|
+
T = self._graph
|
1689
|
+
|
1690
|
+
if not self._graph.is_tree():
|
1691
|
+
raise RuntimeError("cannot use tree layout on this graph: "
|
1692
|
+
"self.is_tree() returns False")
|
1693
|
+
|
1694
|
+
children = {root: T.neighbors(root)}
|
1695
|
+
|
1696
|
+
# Always make a copy of the children because they get eaten
|
1697
|
+
stack = [list(children[root])]
|
1698
|
+
stick = [root]
|
1699
|
+
parent = {u: root for u in children[root]}
|
1700
|
+
pos = {}
|
1701
|
+
obstruction = [0.0] * T.num_verts()
|
1702
|
+
if orientation == 'down':
|
1703
|
+
o = -1
|
1704
|
+
else:
|
1705
|
+
o = 1
|
1706
|
+
|
1707
|
+
def slide(v, dx):
|
1708
|
+
"""
|
1709
|
+
Shift the vertex v and its descendants to the right by dx.
|
1710
|
+
|
1711
|
+
Precondition: v and its descendents have already had their
|
1712
|
+
positions computed.
|
1713
|
+
"""
|
1714
|
+
level = [v]
|
1715
|
+
while level:
|
1716
|
+
nextlevel = []
|
1717
|
+
for u in level:
|
1718
|
+
x, y = pos[u]
|
1719
|
+
x += dx
|
1720
|
+
obstruction[y] = max(x + 1, obstruction[y])
|
1721
|
+
pos[u] = x, y
|
1722
|
+
nextlevel += children[u]
|
1723
|
+
level = nextlevel
|
1724
|
+
|
1725
|
+
while stack:
|
1726
|
+
C = stack[-1]
|
1727
|
+
if not C:
|
1728
|
+
p = stick.pop()
|
1729
|
+
stack.pop()
|
1730
|
+
cp = children[p]
|
1731
|
+
y = o * len(stack)
|
1732
|
+
if not cp:
|
1733
|
+
x = obstruction[y]
|
1734
|
+
pos[p] = x, y
|
1735
|
+
else:
|
1736
|
+
x = sum([pos[c][0] for c in cp]) / float(len(cp))
|
1737
|
+
pos[p] = x, y
|
1738
|
+
ox = obstruction[y]
|
1739
|
+
if x < ox:
|
1740
|
+
slide(p, ox - x)
|
1741
|
+
x = ox
|
1742
|
+
obstruction[y] = x + 1
|
1743
|
+
continue
|
1744
|
+
|
1745
|
+
t = C.pop()
|
1746
|
+
pt = parent[t]
|
1747
|
+
|
1748
|
+
ct = [u for u in T.neighbors(t) if u != pt]
|
1749
|
+
for c in ct:
|
1750
|
+
parent[c] = t
|
1751
|
+
children[t] = ct
|
1752
|
+
|
1753
|
+
stack.append(ct)
|
1754
|
+
stick.append(t)
|
1755
|
+
|
1756
|
+
return pos
|