mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-13 19:07:10 +00:00
feat: GraphML export (--graphml flag) for Gephi and yEd
This commit is contained in:
@@ -107,6 +107,7 @@ All commands are typed inside Claude Code:
|
||||
|
||||
/graphify ./raw --html # also export graph.html (browser, no Obsidian needed)
|
||||
/graphify ./raw --svg # also export graph.svg (embeds in Notion, GitHub)
|
||||
/graphify ./raw --graphml # also export graph.graphml (Gephi, yEd, any GraphML tool)
|
||||
/graphify ./raw --neo4j # generate cypher.txt for Neo4j import
|
||||
/graphify ./raw --mcp # start MCP stdio server for agent access
|
||||
```
|
||||
@@ -218,7 +219,7 @@ graphify/
|
||||
├── cluster.py Leiden community detection, cohesion scoring
|
||||
├── analyze.py god nodes, bridge nodes, surprising connections, suggested questions, graph diff
|
||||
├── report.py render GRAPH_REPORT.md
|
||||
├── export.py Obsidian vault, graph.json, graph.html, graph.svg, Neo4j Cypher, Canvas
|
||||
├── export.py Obsidian vault, graph.json, graph.html, graph.svg, graph.graphml, Neo4j Cypher, Canvas
|
||||
├── ingest.py fetch URLs (arXiv, Twitter/X, PDF, any webpage); save Q&A to .graphify/memory/
|
||||
├── cache.py SHA256-based per-file extraction cache; check_semantic_cache / save_semantic_cache
|
||||
├── security.py URL validation (http/https only), safe fetch with size cap, path guards, label sanitisation
|
||||
|
||||
+18
-1
@@ -1,4 +1,4 @@
|
||||
# write graph to HTML, JSON, SVG, Obsidian vault, and Neo4j Cypher
|
||||
# write graph to HTML, JSON, SVG, GraphML, Obsidian vault, and Neo4j Cypher
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import math
|
||||
@@ -586,6 +586,23 @@ def push_to_neo4j(
|
||||
return {"nodes": nodes_pushed, "edges": edges_pushed}
|
||||
|
||||
|
||||
def to_graphml(
|
||||
G: nx.Graph,
|
||||
communities: dict[int, list[str]],
|
||||
output_path: str,
|
||||
) -> None:
|
||||
"""Export graph as GraphML — opens in Gephi, yEd, and any GraphML-compatible tool.
|
||||
|
||||
Community IDs are written as a node attribute so Gephi can colour by community.
|
||||
Edge confidence (EXTRACTED/INFERRED/AMBIGUOUS) is preserved as an edge attribute.
|
||||
"""
|
||||
H = G.copy()
|
||||
node_community = {n: cid for cid, nodes in communities.items() for n in nodes}
|
||||
for node_id in H.nodes():
|
||||
H.nodes[node_id]["community"] = node_community.get(node_id, -1)
|
||||
nx.write_graphml(H, output_path)
|
||||
|
||||
|
||||
def to_svg(
|
||||
G: nx.Graph,
|
||||
communities: dict[int, list[str]],
|
||||
|
||||
+28
-1
@@ -3,7 +3,7 @@ import tempfile
|
||||
from pathlib import Path
|
||||
from graphify.build import build_from_json
|
||||
from graphify.cluster import cluster
|
||||
from graphify.export import to_json, to_cypher
|
||||
from graphify.export import to_json, to_cypher, to_graphml
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
@@ -52,3 +52,30 @@ def test_to_cypher_contains_merge_statements():
|
||||
to_cypher(G, str(out))
|
||||
content = out.read_text()
|
||||
assert "MERGE" in content
|
||||
|
||||
def test_to_graphml_creates_file():
|
||||
G = make_graph()
|
||||
communities = cluster(G)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
out = Path(tmp) / "graph.graphml"
|
||||
to_graphml(G, communities, str(out))
|
||||
assert out.exists()
|
||||
|
||||
def test_to_graphml_valid_xml():
|
||||
G = make_graph()
|
||||
communities = cluster(G)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
out = Path(tmp) / "graph.graphml"
|
||||
to_graphml(G, communities, str(out))
|
||||
content = out.read_text()
|
||||
assert "<graphml" in content
|
||||
assert "<node" in content
|
||||
|
||||
def test_to_graphml_has_community_attribute():
|
||||
G = make_graph()
|
||||
communities = cluster(G)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
out = Path(tmp) / "graph.graphml"
|
||||
to_graphml(G, communities, str(out))
|
||||
content = out.read_text()
|
||||
assert "community" in content
|
||||
|
||||
Reference in New Issue
Block a user