Skip to content

Commit 3300582

Browse files
committed
Add visual graph visualizer tool in scripts
1 parent 01991cd commit 3300582

2 files changed

Lines changed: 131 additions & 20 deletions

File tree

scripts/export_graph_mermaid.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,22 @@ def generate_mermaid():
3939

4040
lines.append(f'{nid}["{name}"]{style}')
4141

42-
# Edges
43-
# 1. Requirements (Quest -> Quest)
42+
# 2. Location Unlocks (Quest -> Location)
43+
if "unlocks_location" in node["out_edges"]:
44+
for edge in node["out_edges"]["unlocks_location"]:
45+
target_id = edge["targetId"]
46+
if target_id in sig_nodes:
47+
lines.append(f"{nid} ==>|Unlocks| {target_id}")
48+
49+
# 3. Requirements (Location -> Quest)
4450
if "requires_quest" in node["in_edges"]:
4551
for req_id in node["in_edges"]["requires_quest"]:
4652
if req_id in sig_nodes:
47-
lines.append(f"{req_id} -->|Requires| {nid}")
48-
49-
# 2. Location Unlocks (Quest -> Location) - Actually Location requires Quest in gating
50-
# In our graph, Location has in_edge requires_quest.
51-
# Mermaid: Quest --> Location
52-
# Wait, Location -> Quest means Location requires Quest to enter.
53-
# So Quest -> Location flow is correct for progression.
54-
55-
# 3. Quest containment (Location -> Quest)
56-
# Location contains Quest
57-
if "contains" in node["out_edges"]:
58-
for edge in node["out_edges"]["contains"]:
59-
target_id = edge["targetId"]
60-
if target_id in sig_nodes:
61-
# lines.append(f"{nid} -.->|Contains| {target_id}") # Optional: Can be noisy
62-
pass
53+
# Avoid duplicate edges with 'requires_quest' which is also on Quest nodes.
54+
if node["type"] == "Location":
55+
lines.append(f"{req_id} -->|Enables| {nid}")
56+
elif node["type"] == "Quest":
57+
lines.append(f"{req_id} -->|Requires| {nid}")
6358

6459
# 4. Cadence Unlocks (Quest -> Cadence)
6560
if "unlocks_cadence" in node["out_edges"]:
@@ -69,7 +64,7 @@ def generate_mermaid():
6964
lines.append(f"{nid} ==>|Unlocks| {target_id}")
7065

7166
# Output
72-
print("\n".join(lines))
67+
return "\n".join(lines)
7368

7469
if __name__ == "__main__":
75-
generate_mermaid()
70+
print(generate_mermaid())

scripts/visualize_graph.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import os
2+
import webbrowser
3+
import tempfile
4+
from export_graph_mermaid import generate_mermaid
5+
6+
def generate_html(mermaid_code):
7+
html_template = f"""
8+
<!DOCTYPE html>
9+
<html>
10+
<head>
11+
<meta charset="UTF-8">
12+
<title>Mythril Content Graph Visualizer</title>
13+
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
14+
<style>
15+
body {{
16+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17+
background-color: #0d1117;
18+
color: #c9d1d9;
19+
margin: 0;
20+
padding: 0;
21+
overflow: hidden;
22+
}}
23+
.header {{
24+
background-color: #161b22;
25+
padding: 10px 20px;
26+
border-bottom: 1px solid #30363d;
27+
display: flex;
28+
justify-content: space-between;
29+
align-items: center;
30+
}}
31+
.legend {{
32+
display: flex;
33+
gap: 15px;
34+
font-size: 0.8em;
35+
}}
36+
.legend-item {{ display: flex; align-items: center; gap: 5px; }}
37+
.legend-box {{ width: 12px; height: 12px; border-radius: 2px; }}
38+
.quest-box {{ background-color: #f9f; border: 1px solid #333; }}
39+
.location-box {{ background-color: #ccf; border: 1px solid #333; }}
40+
.cadence-box {{ background-color: #cfc; border: 1px solid #333; }}
41+
42+
#graph-container {{
43+
width: 100vw;
44+
height: calc(100vh - 60px);
45+
overflow: auto;
46+
padding: 20px;
47+
box-sizing: border-box;
48+
display: flex;
49+
justify-content: center;
50+
}}
51+
52+
.mermaid {{
53+
background: #0d1117;
54+
}}
55+
</style>
56+
</head>
57+
<body>
58+
<div class="header">
59+
<div>
60+
<h2 style="margin:0; font-size: 1.2em;">Mythril Content Graph</h2>
61+
</div>
62+
<div class="legend">
63+
<div class="legend-item"><div class="legend-box quest-box"></div> Quest</div>
64+
<div class="legend-item"><div class="legend-box location-box"></div> Location</div>
65+
<div class="legend-item"><div class="legend-box cadence-box"></div> Cadence</div>
66+
</div>
67+
</div>
68+
<div id="graph-container">
69+
<pre class="mermaid">
70+
{mermaid_code}
71+
</pre>
72+
</div>
73+
<script>
74+
mermaid.initialize({{
75+
startOnLoad: true,
76+
theme: 'dark',
77+
securityLevel: 'loose',
78+
flowchart: {{
79+
useMaxWidth: false,
80+
htmlLabels: true,
81+
curve: 'basis'
82+
}}
83+
}});
84+
</script>
85+
</body>
86+
</html>
87+
"""
88+
return html_template
89+
90+
def main():
91+
print("Generating Mermaid graph content...")
92+
try:
93+
mermaid_code = generate_mermaid()
94+
html = generate_html(mermaid_code)
95+
96+
# Ensure output directory exists
97+
output_dir = "output"
98+
if not os.path.exists(output_dir):
99+
os.makedirs(output_dir)
100+
101+
file_path = os.path.join(output_dir, "graph_visualizer.html")
102+
with open(file_path, "w", encoding="utf-8") as f:
103+
f.write(html)
104+
105+
abs_path = os.path.abspath(file_path)
106+
print(f"Graph generated: {abs_path}")
107+
108+
# Open in browser
109+
print("Opening browser...")
110+
webbrowser.open(f"file://{abs_path}")
111+
112+
except Exception as e:
113+
print(f"Error: {e}")
114+
115+
if __name__ == "__main__":
116+
main()

0 commit comments

Comments
 (0)