This note generates the graph that you see on graph page. The script requires 2 variables to be set correctly:
roam_notes_dir
: This is the location of org roam notes.graph_dir
: Location where the hugo post containing output graph would be stored.
roam_notes_dir="/home/harshads/base/sites/digital_garden/posts"
graph_dir="/home/harshads/base/sites/digital_garden"
Some Getter Functions
These functions return ID and URL from the filename. All the notes are assumed to have ID in first 3 lines (that is how org roam works).
def get_id(path):
with open(path) as f:
lines = f.readlines()
for line in lines:
return line.split(":")[2].strip()
def get_url(path):
return os.path.splitext(os.path.basename(path))[0]
Setting up dictorionaries
Next, we will set up a few dictionaries for generating graphs:
id_to_url
: This dictionary maps org roam ID to URL in the websiteid_incoming_links
: This dictionary stores all the incoming links to a given note.
import os
import re
id_to_url = {}
id_incoming_links = {}
for filename in os.scandir(roam_notes_dir):
if not filename.is_file():
continue
if not filename.path.endswith(".org"):
continue
current_id = get_id(filename.path)
id_to_url[current_id] = get_url(filename.path)
id_incoming_links[current_id] = []
for filename in os.scandir(roam_notes_dir):
if not filename.is_file():
continue
if not filename.path.endswith(".org"):
continue
regex = re.compile("\[id:([A-Za-z0-9]+(-[A-Za-z0-9]+)+)\]")
current_id = get_id(filename.path)
with open(filename.path) as f:
content = f.read()
refs = re.findall(regex, content)
for ref in refs:
print(ref[0])
if ref[0] in id_incoming_links:
id_incoming_links[ref[0]].append(current_id)
Build Nodes and Edges
Now that we have dictionaries, time to represent the graph in a way that our eventual vis.js script is going to like.
nodes = set()
edges = set()
node_link_count = {}
max_links = 0
for id in id_to_urhttp://
nodes.add(id_to_url[id])
node_link_count[id_to_url[id]] = len(id_incoming_links[id])
max_links = max(max_links, len(id_incoming_links[id]))
for id in id_incoming_links:
for link in id_incoming_links[id]:
edge = (id_to_url[id], id_to_url[link])
edges.add(edge)
print(nodes)
print(edges)
if max_links > 0:
for node in node_link_count:
node_link_count[node] = node_link_count[node] * 5 / max_links
Elements of graph.org
Alright, now we have all the stuff necessary to create the final graph file. Okay some bits about the final graph.org file. The final graph.org file is still an org-mode file. It however contains raw html and javascript code that is responsible for producing graph. Here's how th vis.js script looks like:
vis_js_script_start
<!--
In the following URLs you may want to replace @latest by @version
to prevent unexpected potentionally breaking updates.
For example vis-data@1.0.0 instead of vis-data@latest.
-->
<script type="text/javascript" src="https://unpkg.com/vis-data@latest/peer/umd/vis-data.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/vis-network@latest/peer/umd/vis-network.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/vis-network/styles/vis-network.min.css" />
<!-- You may include other packages like Vis Timeline or Vis Graph3D here. -->
<style>
body {
color: #d3d3d3;
}
#mynetwork {
height: 800px;
border: 1px solid #444444;
background-color: #242730;
}
</style>
<div id="mynetwork"></div>
<script type="text/javascript">
Sample dataset.
var nodes = new vis.DataSet([
{ id: 1, labehttp:// "google", urhttp:// "www.google.com", color: "#ff665c" },
{ id: 2, labehttp:// "Node 2" },
{ id: 3, labehttp:// "Node 3" },
{ id: 4, labehttp:// "Node 4" },
{ id: 5, labehttp:// "Node 5" }
]);
// create an array with edges
var edges = new vis.DataSet([
{ from: "str1", to: 3 },
{ from: "str1", to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 },
{ from: 3, to: 3 }
]);
vis_js_script_end
// create a network
var container = document.getElementById("mynetwork");
var data = {
nodes: nodes,
edges: edges
};
var options = {
physics: false,
nodes: {
shape: "dot",
size: 13,
font: {
size: 15,
color: "white",
},
borderWidth: 0,
},
edges: {
color: "#62686E",
width: 2,
},
}
var network = new vis.Network(container, data, options);
network.on("selectNode", function (params) {
var node = nodes.get(params.nodes[0]);
window.open(node.url, '_blank');
});
</script>
Finally generate the graph.org file
emacs org-mode's babel module enables literate programming. It supports noweb syntax as well. And quite wonderfully, it allows me to reuse sample examples that I shared in the previous section to actually generate the final graph.org file.
import random
colors = [ "#ff665c", "#5CEFFF", "#A991F1", "#FCCE7B" ]
with open(graph_dir + "/graph.org", "w") as f:
f.write("""
#+title: Graph
""")
f.write("{{" + "< rawhtml >" + "}}")
f.write("""
<<vis_js_script_start>>
""")
f.write("var nodes = new vis.DataSet([")
for node in nodes:
f.write(f"""{{ id: \"{node}\",
labehttp:// \"{node}\",
urhttp:// \"/posts/{node}/\",
color: \"{random.choice(colors)}\",
value: \"{node_link_count[node]}\",
}},\n""")
f.write("""].map((node, index, arr) => {
const angle = 2 * Math.PI * (index / arr.length + 0.75);
node.x = 300 * Math.cos(angle);
node.y = 300 * Math.sin(angle);
return node; }));
""")
f.write("var edges = [")
for edge in edges:
f.write(f"{{ from: \"{edge[0]}\", to: \"{edge[1]}\" }},\n")
f.write("""];
<<vis_js_script_end>>
""")
f.write("{{" + "< /rawhtml >" + "}}")