Coverage for src / graphable / views / tikz.py: 100%

45 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-16 21:32 +0000

1from dataclasses import dataclass 

2from logging import getLogger 

3from pathlib import Path 

4from typing import Any, Callable 

5 

6from ..graph import Graph 

7from ..graphable import Graphable 

8from ..registry import register_view 

9 

10logger = getLogger(__name__) 

11 

12 

13@dataclass 

14class TikzStylingConfig: 

15 """ 

16 Configuration for customizing TikZ diagram generation. 

17 

18 Attributes: 

19 node_ref_fnc: Function to generate the node identifier. 

20 node_label_fnc: Function to generate the node label. 

21 node_options: Global TikZ options for nodes. 

22 edge_options: Global TikZ options for edges. 

23 use_graphs_lib: Whether to use the TikZ 'graphs' library syntax. 

24 """ 

25 

26 node_ref_fnc: Callable[[Graphable[Any]], str] = lambda n: f"node_{id(n)}" 

27 node_label_fnc: Callable[[Graphable[Any]], str] = lambda n: str(n.reference) 

28 node_options: str = "draw, circle" 

29 edge_options: str = "->" 

30 use_graphs_lib: bool = True 

31 

32 

33def create_topology_tikz(graph: Graph, config: TikzStylingConfig | None = None) -> str: 

34 """ 

35 Generate TikZ LaTeX code from a Graph. 

36 

37 Args: 

38 graph (Graph): The graph to convert. 

39 config (TikzStylingConfig | None): Styling configuration. 

40 

41 Returns: 

42 str: The TikZ LaTeX code string. 

43 """ 

44 config = config or TikzStylingConfig() 

45 lines: list[str] = [r"\begin{tikzpicture}"] 

46 

47 if config.use_graphs_lib: 

48 lines.append(r" \usetikzlibrary{graphs}") 

49 lines.append(" \\graph [nodes={" + config.node_options + "}] {") 

50 

51 # Define nodes and edges in graph syntax 

52 for node in graph.topological_order(): 

53 node_ref = config.node_ref_fnc(node) 

54 node_label = config.node_label_fnc(node) 

55 # TikZ graph syntax: alias/Label 

56 lines.append(f' {node_ref} ["{node_label}"];') 

57 

58 for dependent, _ in graph.internal_dependents(node): 

59 dep_ref = config.node_ref_fnc(dependent) 

60 lines.append(f" {node_ref} -> {dep_ref};") 

61 

62 lines.append(" };") 

63 else: 

64 # Standard TikZ syntax (simplified placement) 

65 for i, node in enumerate(graph.topological_order()): 

66 node_ref = config.node_ref_fnc(node) 

67 node_label = config.node_label_fnc(node) 

68 lines.append( 

69 f" \\node[{config.node_options}] ({node_ref}) at (0,{-i * 1.5}) {{{node_label}}};" 

70 ) 

71 

72 for node in graph.topological_order(): 

73 node_ref = config.node_ref_fnc(node) 

74 for dependent, _ in graph.internal_dependents(node): 

75 dep_ref = config.node_ref_fnc(dependent) 

76 lines.append( 

77 f" \\draw[{config.edge_options}] ({node_ref}) -- ({dep_ref});" 

78 ) 

79 

80 lines.append(r"\end{tikzpicture}") 

81 return "\n".join(lines) 

82 

83 

84@register_view(".tex", creator_fnc=create_topology_tikz) 

85def export_topology_tikz( 

86 graph: Graph, output: Path, config: TikzStylingConfig | None = None 

87) -> None: 

88 """ 

89 Export the graph to a TikZ (.tex) file. 

90 

91 Args: 

92 graph (Graph): The graph to export. 

93 output (Path): The output file path. 

94 config (TikZStylingConfig | None): Styling configuration. 

95 """ 

96 logger.info(f"Exporting TikZ definition to: {output}") 

97 with open(output, "w+") as f: 

98 f.write(create_topology_tikz(graph, config))