107 lines
3.3 KiB
Python
107 lines
3.3 KiB
Python
|
|
"""
|
||
|
|
Graph visualization export for dependency graphs.
|
||
|
|
|
||
|
|
Exports DependencyGraph to DOT (Graphviz) and Mermaid diagram formats.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from markitect.prompts.dependencies.models import DependencyGraph, EdgeType
|
||
|
|
|
||
|
|
|
||
|
|
# Edge type to style mappings
|
||
|
|
_DOT_EDGE_STYLES = {
|
||
|
|
EdgeType.REQUIRES: 'style="solid"',
|
||
|
|
EdgeType.GENERATES: 'style="dashed"',
|
||
|
|
EdgeType.INCLUDES: 'style="dotted"',
|
||
|
|
}
|
||
|
|
|
||
|
|
_MERMAID_EDGE_STYLES = {
|
||
|
|
EdgeType.REQUIRES: "-->",
|
||
|
|
EdgeType.GENERATES: "-.->",
|
||
|
|
EdgeType.INCLUDES: "==>",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class GraphExporter:
|
||
|
|
"""Export DependencyGraph to DOT and Mermaid formats."""
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def to_dot(graph: DependencyGraph, title: str = "Dependencies") -> str:
|
||
|
|
"""
|
||
|
|
Export dependency graph to Graphviz DOT format.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
graph: DependencyGraph to export
|
||
|
|
title: Graph title
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
DOT format string
|
||
|
|
"""
|
||
|
|
lines = [
|
||
|
|
f'digraph "{title}" {{',
|
||
|
|
" rankdir=LR;",
|
||
|
|
f' label="{title}";',
|
||
|
|
" node [shape=box];",
|
||
|
|
]
|
||
|
|
|
||
|
|
# Add nodes
|
||
|
|
for node in sorted(graph.nodes):
|
||
|
|
safe_id = node.replace("-", "_")
|
||
|
|
lines.append(f' {safe_id} [label="{node}"];')
|
||
|
|
|
||
|
|
# Add edges
|
||
|
|
for source in sorted(graph.nodes):
|
||
|
|
for target in sorted(graph.get_successors(source)):
|
||
|
|
edge_type = graph.get_edge_type(source, target)
|
||
|
|
style = _DOT_EDGE_STYLES.get(edge_type, 'style="solid"')
|
||
|
|
safe_source = source.replace("-", "_")
|
||
|
|
safe_target = target.replace("-", "_")
|
||
|
|
label = edge_type.value if edge_type else "requires"
|
||
|
|
lines.append(
|
||
|
|
f' {safe_source} -> {safe_target} [{style} label="{label}"];'
|
||
|
|
)
|
||
|
|
|
||
|
|
lines.append("}")
|
||
|
|
return "\n".join(lines)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def to_mermaid(graph: DependencyGraph, title: str = "Dependencies") -> str:
|
||
|
|
"""
|
||
|
|
Export dependency graph to Mermaid diagram format.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
graph: DependencyGraph to export
|
||
|
|
title: Graph title (used as comment)
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Mermaid format string
|
||
|
|
"""
|
||
|
|
lines = [
|
||
|
|
f"%%{{ title: {title} }}%%",
|
||
|
|
"graph LR",
|
||
|
|
]
|
||
|
|
|
||
|
|
# Add edges
|
||
|
|
edges_added = False
|
||
|
|
for source in sorted(graph.nodes):
|
||
|
|
for target in sorted(graph.get_successors(source)):
|
||
|
|
edge_type = graph.get_edge_type(source, target)
|
||
|
|
arrow = _MERMAID_EDGE_STYLES.get(edge_type, "-->")
|
||
|
|
label = edge_type.value if edge_type else "requires"
|
||
|
|
lines.append(f" {source}{arrow}|{label}|{target}")
|
||
|
|
edges_added = True
|
||
|
|
|
||
|
|
# Add isolated nodes (no edges)
|
||
|
|
if not edges_added:
|
||
|
|
for node in sorted(graph.nodes):
|
||
|
|
lines.append(f" {node}")
|
||
|
|
else:
|
||
|
|
# Add any isolated nodes that have no edges
|
||
|
|
connected = set()
|
||
|
|
for source in graph.nodes:
|
||
|
|
if graph.get_successors(source) or graph.get_predecessors(source):
|
||
|
|
connected.add(source)
|
||
|
|
for node in sorted(graph.nodes - connected):
|
||
|
|
lines.append(f" {node}")
|
||
|
|
|
||
|
|
return "\n".join(lines)
|