Skip to content

Commit 05c92eb

Browse files
author
Release Manager
committed
gh-38798: adding method tikz to class Graph Currently, we can create a TikzPicture from a graph as follows, but is raises an experimental feature warning: ``` sage: from sage.misc.latex_standalone import TikzPicture sage: g = graphs.PetersenGraph() sage: t = TikzPicture.from_graph(g) <ipython-input-20-c4b6306d5e76>:1: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See #20343 for details. t = TikzPicture.from_graph(g) sage: t ``` It was declared experimental during the review of #20343 because it should rather be a method of the class Graph. This is what we do in this PR: we add a tikz method to the class Graph. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation - [ ] and checked the documentation preview. ### ⌛ Dependencies None ### Screenshots As a consequence, the following now works in Jupyter. By default, it uses the dot2tex format if dot2tex is available: ![image](https://github.com/user-attachments/assets/ff3c1ffb-46ae-46bf- bf64-f26cc3f93941) ### Works well with sagetex For example: ``` \begin{sagesilent} g = graphs.PetersenGraph() tikz = g.tikz() \end{sagesilent} \begin{center} \sageplot[scale=.5][pdf]{tikz} \end{center} ``` URL: #38798 Reported by: Sébastien Labbé Reviewer(s): David Coudert, Frédéric Chapoton, Sébastien Labbé, Xavier Caruso
2 parents bd5df79 + 2a5b4d7 commit 05c92eb

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed

src/sage/graphs/generic_graph.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@
297297
:meth:`~GenericGraph.show3d` | Plot the graph using :class:`~sage.plot.plot3d.tachyon.Tachyon`, and shows the resulting plot.
298298
:meth:`~GenericGraph.graphviz_string` | Return a representation in the ``dot`` language.
299299
:meth:`~GenericGraph.graphviz_to_file_named` | Write a representation in the ``dot`` language in a file.
300+
:meth:`~GenericGraph.tikz` | Return a :class:`~sage.misc.latex_standalone.TikzPicture` object representing the (di)graph.
300301

301302
**Algorithmically hard stuff:**
302303

@@ -939,6 +940,190 @@ def _latex_(self):
939940

940941
return self.latex_options().latex()
941942

943+
def tikz(self, format='dot2tex', edge_labels=None,
944+
color_by_label=False, prog='dot', rankdir='down',
945+
standalone_config=None, usepackage=None,
946+
usetikzlibrary=None, macros=None,
947+
use_sage_preamble=None, **kwds):
948+
r"""
949+
Return a TikzPicture of the graph.
950+
951+
If graphviz and dot2tex are available, it uses these packages for
952+
placements of vertices and edges.
953+
954+
INPUT:
955+
956+
- ``format`` -- string (default: ``None``), ``'dot2tex'`` or
957+
``'tkz_graph'``. If ``None``, it is set to ``'dot2tex'`` if
958+
dot2tex is present, otherwise it is set to ``'tkz_graph'``.
959+
- ``edge_labels`` -- bool (default: ``None``), if ``None``
960+
it is set to ``True`` if and only if format is ``'dot2tex'``
961+
- ``color_by_label`` -- boolean or dictionary or function (default:
962+
``False``); whether to color each edge with a different color
963+
according to its label; the colors are chosen along a rainbow, unless
964+
they are specified by a function or dictionary mapping labels to
965+
colors;
966+
967+
When using format ``'dot2tex'``, the following inputs are considered:
968+
969+
- ``prog`` -- string (default: ``'dot'``) the program used for the
970+
layout corresponding to one of the software of the graphviz
971+
suite: 'dot', 'neato', 'twopi', 'circo' or 'fdp'.
972+
- ``rankdir`` -- string (default: ``'down'``), direction of graph layout
973+
when prog is ``'dot'``, possible values are ``'down'``,
974+
``'up'``, ``'right'`` and ``'left'``.
975+
- ``subgraph_clusters`` -- (default: ``[]``) a list of lists of
976+
vertices, if supported by the layout engine, nodes belonging to
977+
the same cluster subgraph are drawn together, with the entire
978+
drawing of the cluster contained within a bounding rectangle.
979+
980+
Additionnal keywords arguments are forwarded to
981+
:meth:`sage.graphs.graph_latex.GraphLatex.set_option`.
982+
983+
The following inputs define the preamble of the latex standalone
984+
document class file containing the tikzpicture:
985+
986+
- ``standalone_config`` -- list of strings (default: ``["border=4mm"]``);
987+
latex document class standalone configuration options
988+
- ``usepackage`` -- list of strings (default: ``[]``); latex
989+
packages
990+
- ``usetikzlibrary`` -- list of strings (default: ``[]``); tikz
991+
libraries to use
992+
- ``macros`` -- list of strings (default: ``[]``); list of
993+
newcommands needed for the picture
994+
- ``use_sage_preamble`` -- bool (default: ``None``), if ``None``
995+
it is set to ``True`` if and only if format is ``'tkz_graph'``
996+
997+
OUTPUT:
998+
999+
An instance of :mod:`sage.misc.latex_standalone.TikzPicture`.
1000+
1001+
.. NOTE::
1002+
1003+
Prerequisite: dot2tex optional Sage package and graphviz must be
1004+
installed when using format ``'dot2tex'``.
1005+
1006+
EXAMPLES::
1007+
1008+
sage: g = graphs.PetersenGraph()
1009+
sage: tikz = g.tikz() # optional - dot2tex graphviz # long time
1010+
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time
1011+
1012+
::
1013+
1014+
sage: tikz = g.tikz(format='tkz_graph')
1015+
sage: _ = tikz.pdf(view=False) # optional - latex
1016+
1017+
Using another value for ``prog``::
1018+
1019+
sage: tikz = g.tikz(prog='neato') # optional - dot2tex graphviz # long time
1020+
sage: _ = tikz.pdf() # optional - dot2tex graphviz latex # long time
1021+
1022+
Using ``color_by_label`` with default rainbow colors::
1023+
1024+
sage: G = DiGraph({0: {1: 333, 2: 444}, 1: {0: 444}, 2: {0: 555}})
1025+
sage: t = G.tikz(color_by_label=True) # optional - dot2tex graphviz # long time
1026+
sage: _ = t.pdf(view=False) # optional - dot2tex graphviz latex # long time
1027+
1028+
Using ``color_by_label`` with colors given as a dictionary::
1029+
1030+
sage: G = DiGraph({0: {1: 333, 2: 444}, 1: {0: 444}, 2: {0: 555}})
1031+
sage: cbl = {333:'orange', 444: 'yellow', 555: 'purple'}
1032+
sage: t = G.tikz(color_by_label=cbl) # optional - dot2tex graphviz # long time
1033+
sage: _ = t.pdf(view=False) # optional - dot2tex graphviz latex # long time
1034+
1035+
Using ``color_by_label`` with colors given as a function::
1036+
1037+
sage: G = DiGraph({0: {1: -333, 2: -444}, 1: {0: 444}, 2: {0: 555}})
1038+
sage: cbl = lambda label:'green' if label >= 0 else 'orange'
1039+
sage: t = G.tikz(color_by_label=cbl) # optional - dot2tex graphviz # long time
1040+
sage: _ = t.pdf(view=False) # optional - dot2tex graphviz latex # long time
1041+
1042+
Using another value for ``rankdir``::
1043+
1044+
sage: tikz = g.tikz(rankdir='right') # optional - dot2tex graphviz # long time
1045+
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time
1046+
1047+
Using subgraphs clusters (broken when using labels, see
1048+
:issue:`22070`)::
1049+
1050+
sage: S = FiniteSetMaps(5)
1051+
sage: I = S((0,1,2,3,4))
1052+
sage: a = S((0,1,3,0,0))
1053+
sage: b = S((0,2,4,1,0))
1054+
sage: roots = [I]
1055+
sage: succ = lambda v: [v*a,v*b,a*v,b*v]
1056+
sage: R = RecursivelyEnumeratedSet(roots, succ)
1057+
sage: G = R.to_digraph()
1058+
sage: G
1059+
Looped multi-digraph on 27 vertices
1060+
sage: C = G.strongly_connected_components()
1061+
sage: tikz = G.tikz(subgraph_clusters=C)# optional - dot2tex graphviz # long time
1062+
sage: tikz.add_usepackage('amstext') # optional - dot2tex graphviz # long time
1063+
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time
1064+
1065+
An example coming from ``graphviz_string`` documentation in SageMath::
1066+
1067+
sage: # needs sage.symbolic
1068+
sage: f(x) = -1 / x
1069+
sage: g(x) = 1 / (x + 1)
1070+
sage: G = DiGraph()
1071+
sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4))
1072+
sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4))
1073+
sage: tikz = G.tikz(format='dot2tex') # optional - dot2tex graphviz # long time
1074+
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time
1075+
sage: def edge_options(data):
1076+
....: u, v, label = data
1077+
....: options = {"color": {f: "red", g: "blue"}[label]}
1078+
....: if (u,v) == (1/2, -2): options["label"] = "coucou"; options["label_style"] = "string"
1079+
....: if (u,v) == (1/2,2/3): options["dot"] = "x=1,y=2"
1080+
....: if (u,v) == (1, -1): options["label_style"] = "latex"
1081+
....: if (u,v) == (1, 1/2): options["dir"] = "back"
1082+
....: return options
1083+
sage: tikz = G.tikz(format='dot2tex', # optional - dot2tex graphviz # long time
1084+
....: edge_options=edge_options)
1085+
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time
1086+
"""
1087+
# use format dot2tex by default
1088+
if format is None:
1089+
from sage.features import PythonModule
1090+
if PythonModule("dot2tex").is_present():
1091+
format = 'dot2tex'
1092+
else:
1093+
format = 'tkz_graph'
1094+
1095+
# by default draw edge_labels for dot2tex but not for tkz_graph
1096+
# (because tkz_graph draws None everywhere which is ugly, whereas
1097+
# dot2tex ignores the labels when they are ``None``)
1098+
if edge_labels is None:
1099+
if format == 'tkz_graph':
1100+
edge_labels = False
1101+
elif format == 'dot2tex':
1102+
edge_labels = True
1103+
1104+
self.latex_options().set_options(format=format,
1105+
edge_labels=edge_labels, color_by_label=color_by_label,
1106+
prog=prog, rankdir=rankdir, **kwds)
1107+
1108+
# by default use sage preamble only for format tkz_graph
1109+
# because content generated by tkz_graph depends on it
1110+
if use_sage_preamble is None:
1111+
if format == 'tkz_graph':
1112+
use_sage_preamble = True
1113+
elif format == 'dot2tex':
1114+
use_sage_preamble = False
1115+
1116+
if standalone_config is None:
1117+
standalone_config = ["border=4mm"]
1118+
1119+
from sage.misc.latex_standalone import TikzPicture
1120+
return TikzPicture(self._latex_(),
1121+
standalone_config=standalone_config,
1122+
usepackage=usepackage,
1123+
usetikzlibrary=usetikzlibrary,
1124+
macros=macros,
1125+
use_sage_preamble=use_sage_preamble)
1126+
9421127
def _matrix_(self, R=None, vertices=None):
9431128
"""
9441129
Return the adjacency matrix of the graph over the specified ring.

0 commit comments

Comments
 (0)