Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graph tikz method fixup #38847

Merged
merged 11 commits into from
Nov 3, 2024
185 changes: 185 additions & 0 deletions src/sage/graphs/generic_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@
:meth:`~GenericGraph.show3d` | Plot the graph using :class:`~sage.plot.plot3d.tachyon.Tachyon`, and shows the resulting plot.
:meth:`~GenericGraph.graphviz_string` | Return a representation in the ``dot`` language.
:meth:`~GenericGraph.graphviz_to_file_named` | Write a representation in the ``dot`` language in a file.
:meth:`~GenericGraph.tikz` | Return a :class:`~sage.misc.latex_standalone.TikzPicture` object representing the (di)graph.

**Algorithmically hard stuff:**

Expand Down Expand Up @@ -938,6 +939,190 @@

return self.latex_options().latex()

def tikz(self, format=None, edge_labels=None,
color_by_label=False, prog='dot', rankdir='down',
standalone_config=None, usepackage=None,
usetikzlibrary=None, macros=None,
use_sage_preamble=None, **kwds):
r"""
Return a TikzPicture of the graph.

If graphviz and dot2tex are available, it uses these packages for
placements of vertices and edges.

INPUT:

- ``format`` -- string (default: ``None``), ``'dot2tex'`` or
``'tkz_graph'``. If ``None``, it is set to ``'dot2tex'`` if
dot2tex is present, otherwise it is set to ``'tkz_graph'``.
- ``edge_labels`` -- bool (default: ``None``), if ``None``
it is set to ``True`` if and only if format is ``'dot2tex'``
- ``color_by_label`` -- boolean or dictionary or function (default:
``False``); whether to color each edge with a different color
according to its label; the colors are chosen along a rainbow, unless
they are specified by a function or dictionary mapping labels to
colors;

When using format ``'dot2tex'``, the following inputs are considered:

- ``prog`` -- string (default: ``'dot'``) the program used for the
layout corresponding to one of the software of the graphviz
suite: 'dot', 'neato', 'twopi', 'circo' or 'fdp'.
- ``rankdir`` -- string (default: ``'down'``), direction of graph layout
when prog is ``'dot'``, possible values are ``'down'``,
``'up'``, ``'right'`` and ``'left'``.
- ``subgraph_clusters`` -- (default: ``[]``) a list of lists of
vertices, if supported by the layout engine, nodes belonging to
the same cluster subgraph are drawn together, with the entire
drawing of the cluster contained within a bounding rectangle.

Additionnal keywords arguments are forwarded to
:meth:`sage.graphs.graph_latex.GraphLatex.set_option`.

The following inputs define the preamble of the latex standalone
document class file containing the tikzpicture:

- ``standalone_config`` -- list of strings (default: ``["border=4mm"]``);
latex document class standalone configuration options
- ``usepackage`` -- list of strings (default: ``[]``); latex
packages
- ``usetikzlibrary`` -- list of strings (default: ``[]``); tikz
libraries to use
- ``macros`` -- list of strings (default: ``[]``); list of
newcommands needed for the picture
- ``use_sage_preamble`` -- bool (default: ``None``), if ``None``
it is set to ``True`` if and only if format is ``'tkz_graph'``

OUTPUT:

An instance of :mod:`sage.misc.latex_standalone.TikzPicture`.

.. NOTE::

Prerequisite: dot2tex optional Sage package and graphviz must be
installed when using format ``'dot2tex'``.

EXAMPLES::

sage: g = graphs.PetersenGraph()
sage: tikz = g.tikz() # optional - dot2tex graphviz # long time
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time

::

sage: tikz = g.tikz(format='tkz_graph')
sage: _ = tikz.pdf(view=False) # optional - latex

Using another value for ``prog``::

sage: tikz = g.tikz(prog='neato') # optional - dot2tex graphviz # long time
sage: _ = tikz.pdf() # optional - dot2tex graphviz latex # long time

Using ``color_by_label`` with default rainbow colors::

sage: G = DiGraph({0: {1: 333, 2: 444}, 1: {0: 444}, 2: {0: 555}})
sage: t = G.tikz(color_by_label=True) # optional - dot2tex graphviz # long time
sage: _ = t.pdf(view=False) # optional - dot2tex graphviz latex # long time

Using ``color_by_label`` with colors given as a dictionary::

sage: G = DiGraph({0: {1: 333, 2: 444}, 1: {0: 444}, 2: {0: 555}})
sage: cbl = {333:'orange', 444: 'yellow', 555: 'purple'}
sage: t = G.tikz(color_by_label=cbl) # optional - dot2tex graphviz # long time
sage: _ = t.pdf(view=False) # optional - dot2tex graphviz latex # long time

Using ``color_by_label`` with colors given as a function::

sage: G = DiGraph({0: {1: -333, 2: -444}, 1: {0: 444}, 2: {0: 555}})
sage: cbl = lambda label:'green' if label >= 0 else 'orange'
sage: t = G.tikz(color_by_label=cbl) # optional - dot2tex graphviz # long time
sage: _ = t.pdf(view=False) # optional - dot2tex graphviz latex # long time

Using another value for ``rankdir``::

sage: tikz = g.tikz(rankdir='right') # optional - dot2tex graphviz # long time
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time

Using subgraphs clusters (broken when using labels, see
:issue:`22070`)::

sage: S = FiniteSetMaps(5)
sage: I = S((0,1,2,3,4))
sage: a = S((0,1,3,0,0))
sage: b = S((0,2,4,1,0))
sage: roots = [I]
sage: succ = lambda v: [v*a,v*b,a*v,b*v]
sage: R = RecursivelyEnumeratedSet(roots, succ)
sage: G = R.to_digraph()
sage: G
Looped multi-digraph on 27 vertices
sage: C = G.strongly_connected_components()
sage: tikz = G.tikz(subgraph_clusters=C)# optional - dot2tex graphviz # long time
sage: tikz.add_usepackage('amstext') # optional - dot2tex graphviz # long time
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time

An example coming from ``graphviz_string`` documentation in SageMath::

sage: # needs sage.symbolic
sage: f(x) = -1 / x
sage: g(x) = 1 / (x + 1)
sage: G = DiGraph()
sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4))
sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4))
sage: tikz = G.tikz(format='dot2tex') # optional - dot2tex graphviz # long time
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time
sage: def edge_options(data):
....: u, v, label = data
....: options = {"color": {f: "red", g: "blue"}[label]}
....: if (u,v) == (1/2, -2): options["label"] = "coucou"; options["label_style"] = "string"
....: if (u,v) == (1/2,2/3): options["dot"] = "x=1,y=2"
....: if (u,v) == (1, -1): options["label_style"] = "latex"
....: if (u,v) == (1, 1/2): options["dir"] = "back"
....: return options
sage: tikz = G.tikz(format='dot2tex', # optional - dot2tex graphviz # long time
....: edge_options=edge_options)
sage: _ = tikz.pdf(view=False) # optional - dot2tex graphviz latex # long time
"""
# use format dot2tex by default
if format is None:
from sage.features import PythonModule
if PythonModule("dot2tex").is_present():
format = 'dot2tex'
else:

Check warning on line 1091 in src/sage/graphs/generic_graph.py

View check run for this annotation

Codecov / codecov/patch

src/sage/graphs/generic_graph.py#L1089-L1091

Added lines #L1089 - L1091 were not covered by tests
format = 'tkz_graph'

Check warning on line 1093 in src/sage/graphs/generic_graph.py

View check run for this annotation

Codecov / codecov/patch

src/sage/graphs/generic_graph.py#L1093

Added line #L1093 was not covered by tests
# by default draw edge_labels for dot2tex but not for tkz_graph
# (because tkz_graph draws None everywhere which is ugly, whereas
# dot2tex ignores the labels when they are ``None``)
if edge_labels is None:
if format == 'tkz_graph':
edge_labels = False
elif format == 'dot2tex':
edge_labels = True

Check warning on line 1102 in src/sage/graphs/generic_graph.py

View check run for this annotation

Codecov / codecov/patch

src/sage/graphs/generic_graph.py#L1101-L1102

Added lines #L1101 - L1102 were not covered by tests
self.latex_options().set_options(format=format,
edge_labels=edge_labels, color_by_label=color_by_label,
prog=prog, rankdir=rankdir, **kwds)

# by default use sage preamble only for format tkz_graph
# because content generated by tkz_graph depends on it
if use_sage_preamble is None:
if format == 'tkz_graph':
use_sage_preamble = True
elif format == 'dot2tex':
use_sage_preamble = False

Check warning on line 1114 in src/sage/graphs/generic_graph.py

View check run for this annotation

Codecov / codecov/patch

src/sage/graphs/generic_graph.py#L1113-L1114

Added lines #L1113 - L1114 were not covered by tests
if standalone_config is None:
standalone_config = ["border=4mm"]

from sage.misc.latex_standalone import TikzPicture
return TikzPicture(self._latex_(),
standalone_config=standalone_config,
usepackage=usepackage,
usetikzlibrary=usetikzlibrary,
macros=macros,
use_sage_preamble=use_sage_preamble)

def _matrix_(self, R=None, vertices=None):
"""
Return the adjacency matrix of the graph over the specified ring.
Expand Down
Loading