test_latex.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import pytest
  2. import networkx as nx
  3. def test_tikz_attributes():
  4. G = nx.path_graph(4, create_using=nx.DiGraph)
  5. pos = {n: (n, n) for n in G}
  6. G.add_edge(0, 0)
  7. G.edges[(0, 0)]["label"] = "Loop"
  8. G.edges[(0, 0)]["label_options"] = "midway"
  9. G.nodes[0]["style"] = "blue"
  10. G.nodes[1]["style"] = "line width=3,draw"
  11. G.nodes[2]["style"] = "circle,draw,blue!50"
  12. G.nodes[3]["label"] = "Stop"
  13. G.edges[(0, 1)]["label"] = "1st Step"
  14. G.edges[(0, 1)]["label_options"] = "near end"
  15. G.edges[(2, 3)]["label"] = "3rd Step"
  16. G.edges[(2, 3)]["label_options"] = "near start"
  17. G.edges[(2, 3)]["style"] = "bend left,green"
  18. G.edges[(1, 2)]["label"] = "2nd"
  19. G.edges[(1, 2)]["label_options"] = "pos=0.5"
  20. G.edges[(1, 2)]["style"] = ">->,bend right,line width=3,green!90"
  21. output_tex = nx.to_latex(
  22. G,
  23. pos=pos,
  24. as_document=False,
  25. tikz_options="[scale=3]",
  26. node_options="style",
  27. edge_options="style",
  28. node_label="label",
  29. edge_label="label",
  30. edge_label_options="label_options",
  31. )
  32. expected_tex = r"""\begin{figure}
  33. \begin{tikzpicture}[scale=3]
  34. \draw
  35. (0, 0) node[blue] (0){0}
  36. (1, 1) node[line width=3,draw] (1){1}
  37. (2, 2) node[circle,draw,blue!50] (2){2}
  38. (3, 3) node (3){Stop};
  39. \begin{scope}[->]
  40. \draw (0) to node[near end] {1st Step} (1);
  41. \draw[loop,] (0) to node[midway] {Loop} (0);
  42. \draw[>->,bend right,line width=3,green!90] (1) to node[pos=0.5] {2nd} (2);
  43. \draw[bend left,green] (2) to node[near start] {3rd Step} (3);
  44. \end{scope}
  45. \end{tikzpicture}
  46. \end{figure}"""
  47. # First, check for consistency line-by-line - if this fails, the mismatched
  48. # line will be shown explicitly in the failure summary
  49. for expected, actual in zip(expected_tex.split("\n"), output_tex.split("\n")):
  50. assert expected == actual
  51. assert output_tex == expected_tex
  52. def test_basic_multiple_graphs():
  53. H1 = nx.path_graph(4)
  54. H2 = nx.complete_graph(4)
  55. H3 = nx.path_graph(8)
  56. H4 = nx.complete_graph(8)
  57. captions = [
  58. "Path on 4 nodes",
  59. "Complete graph on 4 nodes",
  60. "Path on 8 nodes",
  61. "Complete graph on 8 nodes",
  62. ]
  63. labels = ["fig2a", "fig2b", "fig2c", "fig2d"]
  64. latex_code = nx.to_latex(
  65. [H1, H2, H3, H4],
  66. n_rows=2,
  67. sub_captions=captions,
  68. sub_labels=labels,
  69. )
  70. assert "begin{document}" in latex_code
  71. assert "begin{figure}" in latex_code
  72. assert latex_code.count("begin{subfigure}") == 4
  73. assert latex_code.count("tikzpicture") == 8
  74. assert latex_code.count("[-]") == 4
  75. def test_basic_tikz():
  76. expected_tex = r"""\documentclass{report}
  77. \usepackage{tikz}
  78. \usepackage{subcaption}
  79. \begin{document}
  80. \begin{figure}
  81. \begin{subfigure}{0.5\textwidth}
  82. \begin{tikzpicture}[scale=2]
  83. \draw[gray!90]
  84. (0.749, 0.702) node[red!90] (0){0}
  85. (1.0, -0.014) node[red!90] (1){1}
  86. (-0.777, -0.705) node (2){2}
  87. (-0.984, 0.042) node (3){3}
  88. (-0.028, 0.375) node[cyan!90] (4){4}
  89. (-0.412, 0.888) node (5){5}
  90. (0.448, -0.856) node (6){6}
  91. (0.003, -0.431) node[cyan!90] (7){7};
  92. \begin{scope}[->,gray!90]
  93. \draw (0) to (4);
  94. \draw (0) to (5);
  95. \draw (0) to (6);
  96. \draw (0) to (7);
  97. \draw (1) to (4);
  98. \draw (1) to (5);
  99. \draw (1) to (6);
  100. \draw (1) to (7);
  101. \draw (2) to (4);
  102. \draw (2) to (5);
  103. \draw (2) to (6);
  104. \draw (2) to (7);
  105. \draw (3) to (4);
  106. \draw (3) to (5);
  107. \draw (3) to (6);
  108. \draw (3) to (7);
  109. \end{scope}
  110. \end{tikzpicture}
  111. \caption{My tikz number 1 of 2}\label{tikz_1_2}
  112. \end{subfigure}
  113. \begin{subfigure}{0.5\textwidth}
  114. \begin{tikzpicture}[scale=2]
  115. \draw[gray!90]
  116. (0.749, 0.702) node[green!90] (0){0}
  117. (1.0, -0.014) node[green!90] (1){1}
  118. (-0.777, -0.705) node (2){2}
  119. (-0.984, 0.042) node (3){3}
  120. (-0.028, 0.375) node[purple!90] (4){4}
  121. (-0.412, 0.888) node (5){5}
  122. (0.448, -0.856) node (6){6}
  123. (0.003, -0.431) node[purple!90] (7){7};
  124. \begin{scope}[->,gray!90]
  125. \draw (0) to (4);
  126. \draw (0) to (5);
  127. \draw (0) to (6);
  128. \draw (0) to (7);
  129. \draw (1) to (4);
  130. \draw (1) to (5);
  131. \draw (1) to (6);
  132. \draw (1) to (7);
  133. \draw (2) to (4);
  134. \draw (2) to (5);
  135. \draw (2) to (6);
  136. \draw (2) to (7);
  137. \draw (3) to (4);
  138. \draw (3) to (5);
  139. \draw (3) to (6);
  140. \draw (3) to (7);
  141. \end{scope}
  142. \end{tikzpicture}
  143. \caption{My tikz number 2 of 2}\label{tikz_2_2}
  144. \end{subfigure}
  145. \caption{A graph generated with python and latex.}
  146. \end{figure}
  147. \end{document}"""
  148. edges = [
  149. (0, 4),
  150. (0, 5),
  151. (0, 6),
  152. (0, 7),
  153. (1, 4),
  154. (1, 5),
  155. (1, 6),
  156. (1, 7),
  157. (2, 4),
  158. (2, 5),
  159. (2, 6),
  160. (2, 7),
  161. (3, 4),
  162. (3, 5),
  163. (3, 6),
  164. (3, 7),
  165. ]
  166. G = nx.DiGraph()
  167. G.add_nodes_from(range(8))
  168. G.add_edges_from(edges)
  169. pos = {
  170. 0: (0.7490296171687696, 0.702353520257394),
  171. 1: (1.0, -0.014221357723796535),
  172. 2: (-0.7765783344161441, -0.7054170966808919),
  173. 3: (-0.9842690223417624, 0.04177547602465483),
  174. 4: (-0.02768523817180917, 0.3745724439551441),
  175. 5: (-0.41154855146767433, 0.8880106515525136),
  176. 6: (0.44780153389148264, -0.8561492709269164),
  177. 7: (0.0032499953371383505, -0.43092436645809945),
  178. }
  179. rc_node_color = {0: "red!90", 1: "red!90", 4: "cyan!90", 7: "cyan!90"}
  180. gp_node_color = {0: "green!90", 1: "green!90", 4: "purple!90", 7: "purple!90"}
  181. H = G.copy()
  182. nx.set_node_attributes(G, rc_node_color, "color")
  183. nx.set_node_attributes(H, gp_node_color, "color")
  184. sub_captions = ["My tikz number 1 of 2", "My tikz number 2 of 2"]
  185. sub_labels = ["tikz_1_2", "tikz_2_2"]
  186. output_tex = nx.to_latex(
  187. [G, H],
  188. [pos, pos],
  189. tikz_options="[scale=2]",
  190. default_node_options="gray!90",
  191. default_edge_options="gray!90",
  192. node_options="color",
  193. sub_captions=sub_captions,
  194. sub_labels=sub_labels,
  195. caption="A graph generated with python and latex.",
  196. n_rows=2,
  197. as_document=True,
  198. )
  199. # First, check for consistency line-by-line - if this fails, the mismatched
  200. # line will be shown explicitly in the failure summary
  201. for expected, actual in zip(expected_tex.split("\n"), output_tex.split("\n")):
  202. assert expected == actual
  203. # Double-check for document-level consistency
  204. assert output_tex == expected_tex
  205. def test_exception_pos_single_graph(to_latex=nx.to_latex):
  206. # smoke test that pos can be a string
  207. G = nx.path_graph(4)
  208. to_latex(G, pos="pos")
  209. # must include all nodes
  210. pos = {0: (1, 2), 1: (0, 1), 2: (2, 1)}
  211. with pytest.raises(nx.NetworkXError):
  212. to_latex(G, pos)
  213. # must have 2 values
  214. pos[3] = (1, 2, 3)
  215. with pytest.raises(nx.NetworkXError):
  216. to_latex(G, pos)
  217. pos[3] = 2
  218. with pytest.raises(nx.NetworkXError):
  219. to_latex(G, pos)
  220. # check that passes with 2 values
  221. pos[3] = (3, 2)
  222. to_latex(G, pos)
  223. def test_exception_multiple_graphs(to_latex=nx.to_latex):
  224. G = nx.path_graph(3)
  225. pos_bad = {0: (1, 2), 1: (0, 1)}
  226. pos_OK = {0: (1, 2), 1: (0, 1), 2: (2, 1)}
  227. fourG = [G, G, G, G]
  228. fourpos = [pos_OK, pos_OK, pos_OK, pos_OK]
  229. # input single dict to use for all graphs
  230. to_latex(fourG, pos_OK)
  231. with pytest.raises(nx.NetworkXError):
  232. to_latex(fourG, pos_bad)
  233. # input list of dicts to use for all graphs
  234. to_latex(fourG, fourpos)
  235. with pytest.raises(nx.NetworkXError):
  236. to_latex(fourG, [pos_bad, pos_bad, pos_bad, pos_bad])
  237. # every pos dict must include all nodes
  238. with pytest.raises(nx.NetworkXError):
  239. to_latex(fourG, [pos_OK, pos_OK, pos_bad, pos_OK])
  240. # test sub_captions and sub_labels (len must match Gbunch)
  241. with pytest.raises(nx.NetworkXError):
  242. to_latex(fourG, fourpos, sub_captions=["hi", "hi"])
  243. with pytest.raises(nx.NetworkXError):
  244. to_latex(fourG, fourpos, sub_labels=["hi", "hi"])
  245. # all pass
  246. to_latex(fourG, fourpos, sub_captions=["hi"] * 4, sub_labels=["lbl"] * 4)
  247. def test_exception_multigraph():
  248. G = nx.path_graph(4, create_using=nx.MultiGraph)
  249. G.add_edge(1, 2)
  250. with pytest.raises(nx.NetworkXNotImplemented):
  251. nx.to_latex(G)