1r"""
2*****
3LaTeX
4*****
5
6Export NetworkX graphs in LaTeX format using the TikZ library within TeX/LaTeX.
7Usually, you will want the drawing to appear in a figure environment so
8you use ``to_latex(G, caption="A caption")``. If you want the raw
9drawing commands without a figure environment use :func:`to_latex_raw`.
10And if you want to write to a file instead of just returning the latex
11code as a string, use ``write_latex(G, "filename.tex", caption="A caption")``.
12
13To construct a figure with subfigures for each graph to be shown, provide
14``to_latex`` or ``write_latex`` a list of graphs, a list of subcaptions,
15and a number of rows of subfigures inside the figure.
16
17To be able to refer to the figures or subfigures in latex using ``\\ref``,
18the keyword ``latex_label`` is available for figures and `sub_labels` for
19a list of labels, one for each subfigure.
20
21We intend to eventually provide an interface to the TikZ Graph
22features which include e.g. layout algorithms.
23
24Let us know via github what you'd like to see available, or better yet
25give us some code to do it, or even better make a github pull request
26to add the feature.
27
28The TikZ approach
29=================
30Drawing options can be stored on the graph as node/edge attributes, or
31can be provided as dicts keyed by node/edge to a string of the options
32for that node/edge. Similarly a label can be shown for each node/edge
33by specifying the labels as graph node/edge attributes or by providing
34a dict keyed by node/edge to the text to be written for that node/edge.
35
36Options for the tikzpicture environment (e.g. "[scale=2]") can be provided
37via a keyword argument. Similarly default node and edge options can be
38provided through keywords arguments. The default node options are applied
39to the single TikZ "path" that draws all nodes (and no edges). The default edge
40options are applied to a TikZ "scope" which contains a path for each edge.
41
42Examples
43========
44>>> G = nx.path_graph(3)
45>>> nx.write_latex(G, "just_my_figure.tex", as_document=True)
46>>> nx.write_latex(G, "my_figure.tex", caption="A path graph", latex_label="fig1")
47>>> latex_code = nx.to_latex(G) # a string rather than a file
48
49You can change many features of the nodes and edges.
50
51>>> G = nx.path_graph(4, create_using=nx.DiGraph)
52>>> pos = {n: (n, n) for n in G} # nodes set on a line
53
54>>> G.nodes[0]["style"] = "blue"
55>>> G.nodes[2]["style"] = "line width=3,draw"
56>>> G.nodes[3]["label"] = "Stop"
57>>> G.edges[(0, 1)]["label"] = "1st Step"
58>>> G.edges[(0, 1)]["label_opts"] = "near start"
59>>> G.edges[(1, 2)]["style"] = "line width=3"
60>>> G.edges[(1, 2)]["label"] = "2nd Step"
61>>> G.edges[(2, 3)]["style"] = "green"
62>>> G.edges[(2, 3)]["label"] = "3rd Step"
63>>> G.edges[(2, 3)]["label_opts"] = "near end"
64
65>>> nx.write_latex(G, "latex_graph.tex", pos=pos, as_document=True)
66
67Then compile the LaTeX using something like ``pdflatex latex_graph.tex``
68and view the pdf file created: ``latex_graph.pdf``.
69
70If you want **subfigures** each containing one graph, you can input a list of graphs.
71
72>>> H1 = nx.path_graph(4)
73>>> H2 = nx.complete_graph(4)
74>>> H3 = nx.path_graph(8)
75>>> H4 = nx.complete_graph(8)
76>>> graphs = [H1, H2, H3, H4]
77>>> caps = ["Path 4", "Complete graph 4", "Path 8", "Complete graph 8"]
78>>> lbls = ["fig2a", "fig2b", "fig2c", "fig2d"]
79>>> nx.write_latex(graphs, "subfigs.tex", n_rows=2, sub_captions=caps, sub_labels=lbls)
80>>> latex_code = nx.to_latex(graphs, n_rows=2, sub_captions=caps, sub_labels=lbls)
81
82>>> node_color = {0: "red", 1: "orange", 2: "blue", 3: "gray!90"}
83>>> edge_width = {e: "line width=1.5" for e in H3.edges}
84>>> pos = nx.circular_layout(H3)
85>>> latex_code = nx.to_latex(H3, pos, node_options=node_color, edge_options=edge_width)
86>>> print(latex_code)
87\documentclass{report}
88\usepackage{tikz}
89\usepackage{subcaption}
90<BLANKLINE>
91\begin{document}
92\begin{figure}
93 \begin{tikzpicture}
94 \draw
95 (1.0, 0.0) node[red] (0){0}
96 (0.707, 0.707) node[orange] (1){1}
97 (-0.0, 1.0) node[blue] (2){2}
98 (-0.707, 0.707) node[gray!90] (3){3}
99 (-1.0, -0.0) node (4){4}
100 (-0.707, -0.707) node (5){5}
101 (0.0, -1.0) node (6){6}
102 (0.707, -0.707) node (7){7};
103 \begin{scope}[-]
104 \draw[line width=1.5] (0) to (1);
105 \draw[line width=1.5] (1) to (2);
106 \draw[line width=1.5] (2) to (3);
107 \draw[line width=1.5] (3) to (4);
108 \draw[line width=1.5] (4) to (5);
109 \draw[line width=1.5] (5) to (6);
110 \draw[line width=1.5] (6) to (7);
111 \end{scope}
112 \end{tikzpicture}
113\end{figure}
114\end{document}
115
116Notes
117-----
118If you want to change the preamble/postamble of the figure/document/subfigure
119environment, use the keyword arguments: `figure_wrapper`, `document_wrapper`,
120`subfigure_wrapper`. The default values are stored in private variables
121e.g. ``nx.nx_layout._DOCUMENT_WRAPPER``
122
123References
124----------
125TikZ: https://tikz.dev/
126
127TikZ options details: https://tikz.dev/tikz-actions
128"""
129
130import networkx as nx
131
132__all__ = [
133 "to_latex_raw",
134 "to_latex",
135 "write_latex",
136]
137
138
139@nx.utils.not_implemented_for("multigraph")
140def to_latex_raw(
141 G,
142 pos="pos",
143 tikz_options="",
144 default_node_options="",
145 node_options="node_options",
146 node_label="label",
147 default_edge_options="",
148 edge_options="edge_options",
149 edge_label="label",
150 edge_label_options="edge_label_options",
151):
152 """Return a string of the LaTeX/TikZ code to draw `G`
153
154 This function produces just the code for the tikzpicture
155 without any enclosing environment.
156
157 Parameters
158 ==========
159 G : NetworkX graph
160 The NetworkX graph to be drawn
161 pos : string or dict (default "pos")
162 The name of the node attribute on `G` that holds the position of each node.
163 Positions can be sequences of length 2 with numbers for (x,y) coordinates.
164 They can also be strings to denote positions in TikZ style, such as (x, y)
165 or (angle:radius).
166 If a dict, it should be keyed by node to a position.
167 If an empty dict, a circular layout is computed by TikZ.
168 tikz_options : string
169 The tikzpicture options description defining the options for the picture.
170 Often large scale options like `[scale=2]`.
171 default_node_options : string
172 The draw options for a path of nodes. Individual node options override these.
173 node_options : string or dict
174 The name of the node attribute on `G` that holds the options for each node.
175 Or a dict keyed by node to a string holding the options for that node.
176 node_label : string or dict
177 The name of the node attribute on `G` that holds the node label (text)
178 displayed for each node. If the attribute is "" or not present, the node
179 itself is drawn as a string. LaTeX processing such as ``"$A_1$"`` is allowed.
180 Or a dict keyed by node to a string holding the label for that node.
181 default_edge_options : string
182 The options for the scope drawing all edges. The default is "[-]" for
183 undirected graphs and "[->]" for directed graphs.
184 edge_options : string or dict
185 The name of the edge attribute on `G` that holds the options for each edge.
186 If the edge is a self-loop and ``"loop" not in edge_options`` the option
187 "loop," is added to the options for the self-loop edge. Hence you can
188 use "[loop above]" explicitly, but the default is "[loop]".
189 Or a dict keyed by edge to a string holding the options for that edge.
190 edge_label : string or dict
191 The name of the edge attribute on `G` that holds the edge label (text)
192 displayed for each edge. If the attribute is "" or not present, no edge
193 label is drawn.
194 Or a dict keyed by edge to a string holding the label for that edge.
195 edge_label_options : string or dict
196 The name of the edge attribute on `G` that holds the label options for
197 each edge. For example, "[sloped,above,blue]". The default is no options.
198 Or a dict keyed by edge to a string holding the label options for that edge.
199
200 Returns
201 =======
202 latex_code : string
203 The text string which draws the desired graph(s) when compiled by LaTeX.
204
205 See Also
206 ========
207 to_latex
208 write_latex
209 """
210 i4 = "\n "
211 i8 = "\n "
212
213 # set up position dict
214 # TODO allow pos to be None and use a nice TikZ default
215 if not isinstance(pos, dict):
216 pos = nx.get_node_attributes(G, pos)
217 if not pos:
218 # circular layout with radius 2
219 pos = {n: f"({round(360.0 * i / len(G), 3)}:2)" for i, n in enumerate(G)}
220 for node in G:
221 if node not in pos:
222 raise nx.NetworkXError(f"node {node} has no specified pos {pos}")
223 posnode = pos[node]
224 if not isinstance(posnode, str):
225 try:
226 posx, posy = posnode
227 pos[node] = f"({round(posx, 3)}, {round(posy, 3)})"
228 except (TypeError, ValueError):
229 msg = f"position pos[{node}] is not 2-tuple or a string: {posnode}"
230 raise nx.NetworkXError(msg)
231
232 # set up all the dicts
233 if not isinstance(node_options, dict):
234 node_options = nx.get_node_attributes(G, node_options)
235 if not isinstance(node_label, dict):
236 node_label = nx.get_node_attributes(G, node_label)
237 if not isinstance(edge_options, dict):
238 edge_options = nx.get_edge_attributes(G, edge_options)
239 if not isinstance(edge_label, dict):
240 edge_label = nx.get_edge_attributes(G, edge_label)
241 if not isinstance(edge_label_options, dict):
242 edge_label_options = nx.get_edge_attributes(G, edge_label_options)
243
244 # process default options (add brackets or not)
245 topts = "" if tikz_options == "" else f"[{tikz_options.strip('[]')}]"
246 defn = "" if default_node_options == "" else f"[{default_node_options.strip('[]')}]"
247 linestyle = f"{'->' if G.is_directed() else '-'}"
248 if default_edge_options == "":
249 defe = "[" + linestyle + "]"
250 elif "-" in default_edge_options:
251 defe = default_edge_options
252 else:
253 defe = f"[{linestyle},{default_edge_options.strip('[]')}]"
254
255 # Construct the string line by line
256 result = " \\begin{tikzpicture}" + topts
257 result += i4 + " \\draw" + defn
258 # load the nodes
259 for n in G:
260 # node options goes inside square brackets
261 nopts = f"[{node_options[n].strip('[]')}]" if n in node_options else ""
262 # node text goes inside curly brackets {}
263 ntext = f"{{{node_label[n]}}}" if n in node_label else f"{{{n}}}"
264
265 result += i8 + f"{pos[n]} node{nopts} ({n}){ntext}"
266 result += ";\n"
267
268 # load the edges
269 result += " \\begin{scope}" + defe
270 for edge in G.edges:
271 u, v = edge[:2]
272 e_opts = f"{edge_options[edge]}".strip("[]") if edge in edge_options else ""
273 # add loop options for selfloops if not present
274 if u == v and "loop" not in e_opts:
275 e_opts = "loop," + e_opts
276 e_opts = f"[{e_opts}]" if e_opts != "" else ""
277 # TODO -- handle bending of multiedges
278
279 els = edge_label_options[edge] if edge in edge_label_options else ""
280 # edge label options goes inside square brackets []
281 els = f"[{els.strip('[]')}]"
282 # edge text is drawn using the TikZ node command inside curly brackets {}
283 e_label = f" node{els} {{{edge_label[edge]}}}" if edge in edge_label else ""
284
285 result += i8 + f"\\draw{e_opts} ({u}) to{e_label} ({v});"
286
287 result += "\n \\end{scope}\n \\end{tikzpicture}\n"
288 return result
289
290
291_DOC_WRAPPER_TIKZ = r"""\documentclass{{report}}
292\usepackage{{tikz}}
293\usepackage{{subcaption}}
294
295\begin{{document}}
296{content}
297\end{{document}}"""
298
299
300_FIG_WRAPPER = r"""\begin{{figure}}
301{content}{caption}{label}
302\end{{figure}}"""
303
304
305_SUBFIG_WRAPPER = r""" \begin{{subfigure}}{{{size}\textwidth}}
306{content}{caption}{label}
307 \end{{subfigure}}"""
308
309
310def to_latex(
311 Gbunch,
312 pos="pos",
313 tikz_options="",
314 default_node_options="",
315 node_options="node_options",
316 node_label="node_label",
317 default_edge_options="",
318 edge_options="edge_options",
319 edge_label="edge_label",
320 edge_label_options="edge_label_options",
321 caption="",
322 latex_label="",
323 sub_captions=None,
324 sub_labels=None,
325 n_rows=1,
326 as_document=True,
327 document_wrapper=_DOC_WRAPPER_TIKZ,
328 figure_wrapper=_FIG_WRAPPER,
329 subfigure_wrapper=_SUBFIG_WRAPPER,
330):
331 """Return latex code to draw the graph(s) in `Gbunch`
332
333 The TikZ drawing utility in LaTeX is used to draw the graph(s).
334 If `Gbunch` is a graph, it is drawn in a figure environment.
335 If `Gbunch` is an iterable of graphs, each is drawn in a subfigure environment
336 within a single figure environment.
337
338 If `as_document` is True, the figure is wrapped inside a document environment
339 so that the resulting string is ready to be compiled by LaTeX. Otherwise,
340 the string is ready for inclusion in a larger tex document using ``\\include``
341 or ``\\input`` statements.
342
343 Parameters
344 ==========
345 Gbunch : NetworkX graph or iterable of NetworkX graphs
346 The NetworkX graph to be drawn or an iterable of graphs
347 to be drawn inside subfigures of a single figure.
348 pos : string or list of strings
349 The name of the node attribute on `G` that holds the position of each node.
350 Positions can be sequences of length 2 with numbers for (x,y) coordinates.
351 They can also be strings to denote positions in TikZ style, such as (x, y)
352 or (angle:radius).
353 If a dict, it should be keyed by node to a position.
354 If an empty dict, a circular layout is computed by TikZ.
355 If you are drawing many graphs in subfigures, use a list of position dicts.
356 tikz_options : string
357 The tikzpicture options description defining the options for the picture.
358 Often large scale options like `[scale=2]`.
359 default_node_options : string
360 The draw options for a path of nodes. Individual node options override these.
361 node_options : string or dict
362 The name of the node attribute on `G` that holds the options for each node.
363 Or a dict keyed by node to a string holding the options for that node.
364 node_label : string or dict
365 The name of the node attribute on `G` that holds the node label (text)
366 displayed for each node. If the attribute is "" or not present, the node
367 itself is drawn as a string. LaTeX processing such as ``"$A_1$"`` is allowed.
368 Or a dict keyed by node to a string holding the label for that node.
369 default_edge_options : string
370 The options for the scope drawing all edges. The default is "[-]" for
371 undirected graphs and "[->]" for directed graphs.
372 edge_options : string or dict
373 The name of the edge attribute on `G` that holds the options for each edge.
374 If the edge is a self-loop and ``"loop" not in edge_options`` the option
375 "loop," is added to the options for the self-loop edge. Hence you can
376 use "[loop above]" explicitly, but the default is "[loop]".
377 Or a dict keyed by edge to a string holding the options for that edge.
378 edge_label : string or dict
379 The name of the edge attribute on `G` that holds the edge label (text)
380 displayed for each edge. If the attribute is "" or not present, no edge
381 label is drawn.
382 Or a dict keyed by edge to a string holding the label for that edge.
383 edge_label_options : string or dict
384 The name of the edge attribute on `G` that holds the label options for
385 each edge. For example, "[sloped,above,blue]". The default is no options.
386 Or a dict keyed by edge to a string holding the label options for that edge.
387 caption : string
388 The caption string for the figure environment
389 latex_label : string
390 The latex label used for the figure for easy referral from the main text
391 sub_captions : list of strings
392 The sub_caption string for each subfigure in the figure
393 sub_latex_labels : list of strings
394 The latex label for each subfigure in the figure
395 n_rows : int
396 The number of rows of subfigures to arrange for multiple graphs
397 as_document : bool
398 Whether to wrap the latex code in a document environment for compiling
399 document_wrapper : formatted text string with variable ``content``.
400 This text is called to evaluate the content embedded in a document
401 environment with a preamble setting up TikZ.
402 figure_wrapper : formatted text string
403 This text is evaluated with variables ``content``, ``caption`` and ``label``.
404 It wraps the content and if a caption is provided, adds the latex code for
405 that caption, and if a label is provided, adds the latex code for a label.
406 subfigure_wrapper : formatted text string
407 This text evaluate variables ``size``, ``content``, ``caption`` and ``label``.
408 It wraps the content and if a caption is provided, adds the latex code for
409 that caption, and if a label is provided, adds the latex code for a label.
410 The size is the vertical size of each row of subfigures as a fraction.
411
412 Returns
413 =======
414 latex_code : string
415 The text string which draws the desired graph(s) when compiled by LaTeX.
416
417 See Also
418 ========
419 write_latex
420 to_latex_raw
421 """
422 if hasattr(Gbunch, "adj"):
423 raw = to_latex_raw(
424 Gbunch,
425 pos,
426 tikz_options,
427 default_node_options,
428 node_options,
429 node_label,
430 default_edge_options,
431 edge_options,
432 edge_label,
433 edge_label_options,
434 )
435 else: # iterator of graphs
436 sbf = subfigure_wrapper
437 size = 1 / n_rows
438
439 N = len(Gbunch)
440 if isinstance(pos, str | dict):
441 pos = [pos] * N
442 if sub_captions is None:
443 sub_captions = [""] * N
444 if sub_labels is None:
445 sub_labels = [""] * N
446 if not (len(Gbunch) == len(pos) == len(sub_captions) == len(sub_labels)):
447 raise nx.NetworkXError(
448 "length of Gbunch, sub_captions and sub_figures must agree"
449 )
450
451 raw = ""
452 for G, pos, subcap, sublbl in zip(Gbunch, pos, sub_captions, sub_labels):
453 subraw = to_latex_raw(
454 G,
455 pos,
456 tikz_options,
457 default_node_options,
458 node_options,
459 node_label,
460 default_edge_options,
461 edge_options,
462 edge_label,
463 edge_label_options,
464 )
465 cap = f" \\caption{{{subcap}}}" if subcap else ""
466 lbl = f"\\label{{{sublbl}}}" if sublbl else ""
467 raw += sbf.format(size=size, content=subraw, caption=cap, label=lbl)
468 raw += "\n"
469
470 # put raw latex code into a figure environment and optionally into a document
471 raw = raw[:-1]
472 cap = f"\n \\caption{{{caption}}}" if caption else ""
473 lbl = f"\\label{{{latex_label}}}" if latex_label else ""
474 fig = figure_wrapper.format(content=raw, caption=cap, label=lbl)
475 if as_document:
476 return document_wrapper.format(content=fig)
477 return fig
478
479
480@nx.utils.open_file(1, mode="w")
481def write_latex(Gbunch, path, **options):
482 """Write the latex code to draw the graph(s) onto `path`.
483
484 This convenience function creates the latex drawing code as a string
485 and writes that to a file ready to be compiled when `as_document` is True
486 or ready to be ``import`` ed or ``include`` ed into your main LaTeX document.
487
488 The `path` argument can be a string filename or a file handle to write to.
489
490 Parameters
491 ----------
492 Gbunch : NetworkX graph or iterable of NetworkX graphs
493 If Gbunch is a graph, it is drawn in a figure environment.
494 If Gbunch is an iterable of graphs, each is drawn in a subfigure
495 environment within a single figure environment.
496 path : string or file
497 Filename or file handle to write to.
498 Filenames ending in .gz or .bz2 will be compressed.
499 options : dict
500 By default, TikZ is used with options: (others are ignored)::
501
502 pos : string or dict or list
503 The name of the node attribute on `G` that holds the position of each node.
504 Positions can be sequences of length 2 with numbers for (x,y) coordinates.
505 They can also be strings to denote positions in TikZ style, such as (x, y)
506 or (angle:radius).
507 If a dict, it should be keyed by node to a position.
508 If an empty dict, a circular layout is computed by TikZ.
509 If you are drawing many graphs in subfigures, use a list of position dicts.
510 tikz_options : string
511 The tikzpicture options description defining the options for the picture.
512 Often large scale options like `[scale=2]`.
513 default_node_options : string
514 The draw options for a path of nodes. Individual node options override these.
515 node_options : string or dict
516 The name of the node attribute on `G` that holds the options for each node.
517 Or a dict keyed by node to a string holding the options for that node.
518 node_label : string or dict
519 The name of the node attribute on `G` that holds the node label (text)
520 displayed for each node. If the attribute is "" or not present, the node
521 itself is drawn as a string. LaTeX processing such as ``"$A_1$"`` is allowed.
522 Or a dict keyed by node to a string holding the label for that node.
523 default_edge_options : string
524 The options for the scope drawing all edges. The default is "[-]" for
525 undirected graphs and "[->]" for directed graphs.
526 edge_options : string or dict
527 The name of the edge attribute on `G` that holds the options for each edge.
528 If the edge is a self-loop and ``"loop" not in edge_options`` the option
529 "loop," is added to the options for the self-loop edge. Hence you can
530 use "[loop above]" explicitly, but the default is "[loop]".
531 Or a dict keyed by edge to a string holding the options for that edge.
532 edge_label : string or dict
533 The name of the edge attribute on `G` that holds the edge label (text)
534 displayed for each edge. If the attribute is "" or not present, no edge
535 label is drawn.
536 Or a dict keyed by edge to a string holding the label for that edge.
537 edge_label_options : string or dict
538 The name of the edge attribute on `G` that holds the label options for
539 each edge. For example, "[sloped,above,blue]". The default is no options.
540 Or a dict keyed by edge to a string holding the label options for that edge.
541 caption : string
542 The caption string for the figure environment
543 latex_label : string
544 The latex label used for the figure for easy referral from the main text
545 sub_captions : list of strings
546 The sub_caption string for each subfigure in the figure
547 sub_latex_labels : list of strings
548 The latex label for each subfigure in the figure
549 n_rows : int
550 The number of rows of subfigures to arrange for multiple graphs
551 as_document : bool
552 Whether to wrap the latex code in a document environment for compiling
553 document_wrapper : formatted text string with variable ``content``.
554 This text is called to evaluate the content embedded in a document
555 environment with a preamble setting up the TikZ syntax.
556 figure_wrapper : formatted text string
557 This text is evaluated with variables ``content``, ``caption`` and ``label``.
558 It wraps the content and if a caption is provided, adds the latex code for
559 that caption, and if a label is provided, adds the latex code for a label.
560 subfigure_wrapper : formatted text string
561 This text evaluate variables ``size``, ``content``, ``caption`` and ``label``.
562 It wraps the content and if a caption is provided, adds the latex code for
563 that caption, and if a label is provided, adds the latex code for a label.
564 The size is the vertical size of each row of subfigures as a fraction.
565
566 See Also
567 ========
568 to_latex
569 """
570 path.write(to_latex(Gbunch, **options))