Merge pull request #17 from maximevaillancourt/update-graph
Update graph based on markdown-links
This commit is contained in:
commit
4bac4852fe
@ -6,7 +6,7 @@
|
||||
|
||||
.nodes circle {
|
||||
cursor: pointer;
|
||||
fill: blue;
|
||||
fill: #8b88e6;
|
||||
transition: all 0.15s ease-out;
|
||||
}
|
||||
|
||||
@ -19,15 +19,16 @@
|
||||
.nodes [active],
|
||||
.text [active] {
|
||||
cursor: pointer;
|
||||
fill: red;
|
||||
fill: black;
|
||||
}
|
||||
|
||||
.nodes circle[active] {
|
||||
r: 6;
|
||||
.inactive {
|
||||
opacity: 0.1;
|
||||
transition: all 0.15s ease-out;
|
||||
}
|
||||
|
||||
#graph-wrapper {
|
||||
background: #fafafa;
|
||||
background: #fcfcfc;
|
||||
border-radius: 4px;
|
||||
height: auto;
|
||||
}
|
||||
@ -39,38 +40,134 @@
|
||||
|
||||
<div id="graph-wrapper">
|
||||
<script>
|
||||
const RADIUS = 4;
|
||||
const MINIMAL_NODE_SIZE = 8;
|
||||
const MAX_NODE_SIZE = 12;
|
||||
const ACTIVE_RADIUS_FACTOR = 1.5;
|
||||
const STROKE = 1;
|
||||
const FONT_SIZE = 15;
|
||||
const TICKS = 100;
|
||||
const FONT_BASELINE = 15;
|
||||
const FONT_SIZE = 16;
|
||||
const TICKS = 200;
|
||||
const FONT_BASELINE = 40;
|
||||
const MAX_LABEL_LENGTH = 50;
|
||||
|
||||
const graphData = {% include notes_graph.json %}
|
||||
|
||||
let nodesData = graphData.nodes;
|
||||
let linksData = graphData.edges;
|
||||
|
||||
const nodeSize = {};
|
||||
|
||||
const updateNodeSize = () => {
|
||||
nodesData.forEach((el) => {
|
||||
let weight =
|
||||
3 *
|
||||
Math.sqrt(
|
||||
linksData.filter((l) => l.source === el.id || l.target === el.id)
|
||||
.length + 1
|
||||
);
|
||||
if (weight < MINIMAL_NODE_SIZE) {
|
||||
weight = MINIMAL_NODE_SIZE;
|
||||
} else if (weight > MAX_NODE_SIZE) {
|
||||
weight = MAX_NODE_SIZE;
|
||||
}
|
||||
nodeSize[el.id] = weight;
|
||||
});
|
||||
};
|
||||
|
||||
const onClick = (d) => {
|
||||
window.location = d.path
|
||||
};
|
||||
|
||||
const element = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"svg"
|
||||
);
|
||||
const onMouseover = function (d) {
|
||||
const relatedNodesSet = new Set();
|
||||
linksData
|
||||
.filter((n) => n.target.id == d.id || n.source.id == d.id)
|
||||
.forEach((n) => {
|
||||
relatedNodesSet.add(n.target.id);
|
||||
relatedNodesSet.add(n.source.id);
|
||||
});
|
||||
|
||||
node.attr("class", (node_d) => {
|
||||
if (node_d.id !== d.id && !relatedNodesSet.has(node_d.id)) {
|
||||
return "inactive";
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
link.attr("class", (link_d) => {
|
||||
if (link_d.source.id !== d.id && link_d.target.id !== d.id) {
|
||||
return "inactive";
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
link.attr("stroke-width", (link_d) => {
|
||||
if (link_d.source.id === d.id || link_d.target.id === d.id) {
|
||||
return STROKE * 4;
|
||||
}
|
||||
return STROKE;
|
||||
});
|
||||
text.attr("class", (text_d) => {
|
||||
if (text_d.id !== d.id && !relatedNodesSet.has(text_d.id)) {
|
||||
return "inactive";
|
||||
}
|
||||
return "";
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseout = function (d) {
|
||||
node.attr("class", "");
|
||||
link.attr("class", "");
|
||||
text.attr("class", "");
|
||||
link.attr("stroke-width", STROKE);
|
||||
};
|
||||
|
||||
const sameNodes = (previous, next) => {
|
||||
if (next.length !== previous.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const map = new Map();
|
||||
for (const node of previous) {
|
||||
map.set(node.id, node.label);
|
||||
}
|
||||
|
||||
for (const node of next) {
|
||||
const found = map.get(node.id);
|
||||
if (!found || found !== node.title) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const sameEdges = (previous, next) => {
|
||||
if (next.length !== previous.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const set = new Set();
|
||||
for (const edge of previous) {
|
||||
set.add(`${edge.source.id}-${edge.target.id}`);
|
||||
}
|
||||
|
||||
for (const edge of next) {
|
||||
if (!set.has(`${edge.source}-${edge.target}`)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const graphWrapper = document.getElementById('graph-wrapper')
|
||||
|
||||
const element = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
element.setAttribute("width", graphWrapper.getBoundingClientRect().width);
|
||||
graphWrapper.setAttribute("height", window.innerHeight * 0.8);
|
||||
element.setAttribute("height", window.innerHeight * 0.8);
|
||||
|
||||
graphWrapper.appendChild(element);
|
||||
|
||||
const reportWindowSize = () => {
|
||||
element.setAttribute("width", graphWrapper.getBoundingClientRect().width);
|
||||
graphWrapper.setAttribute("height", window.innerHeight * 0.8);
|
||||
element.setAttribute("height", window.innerHeight * 0.8);
|
||||
element.setAttribute("width", window.innerWidth);
|
||||
element.setAttribute("height", window.innerHeight);
|
||||
};
|
||||
|
||||
window.onresize = reportWindowSize;
|
||||
@ -82,18 +179,18 @@
|
||||
|
||||
const simulation = d3
|
||||
.forceSimulation(nodesData)
|
||||
.force("charge", d3
|
||||
.forceManyBody()
|
||||
.strength(-4000)
|
||||
)
|
||||
.force("forceX", d3.forceX().x(width / 2))
|
||||
.force("forceY", d3.forceY().y(height / 2))
|
||||
.force("charge", d3.forceManyBody())
|
||||
.force(
|
||||
"link",
|
||||
d3
|
||||
.forceLink(linksData)
|
||||
.id((d) => d.id)
|
||||
.distance(150)
|
||||
.distance(70)
|
||||
)
|
||||
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||
.force("collision", d3.forceCollide().radius(80))
|
||||
.stop();
|
||||
|
||||
const g = svg.append("g");
|
||||
@ -101,26 +198,36 @@
|
||||
let node = g.append("g").attr("class", "nodes").selectAll(".node");
|
||||
let text = g.append("g").attr("class", "text").selectAll(".text");
|
||||
|
||||
const zoomActions = () => {
|
||||
const scale = d3.event.transform;
|
||||
zoomLevel = scale.k;
|
||||
g.attr("transform", scale);
|
||||
const resize = () => {
|
||||
if (d3.event) {
|
||||
const scale = d3.event.transform;
|
||||
zoomLevel = scale.k;
|
||||
g.attr("transform", scale);
|
||||
}
|
||||
|
||||
const zoomOrKeep = (value) => (scale.k >= 1 ? value / scale.k : value);
|
||||
const zoomOrKeep = (value) => (zoomLevel >= 1 ? value / zoomLevel : value);
|
||||
|
||||
const font = Math.max(Math.round(zoomOrKeep(FONT_SIZE)), 1);
|
||||
|
||||
text.attr("font-size", `${font}px`);
|
||||
text.attr("y", (d) => d.y - zoomOrKeep(FONT_BASELINE));
|
||||
text.attr("font-size", (d) => font);
|
||||
text.attr("y", (d) => d.y - zoomOrKeep(FONT_BASELINE) + 8);
|
||||
link.attr("stroke-width", zoomOrKeep(STROKE));
|
||||
node.attr("r", zoomOrKeep(RADIUS));
|
||||
node.attr("r", (d) => {
|
||||
return zoomOrKeep(nodeSize[d.id]);
|
||||
});
|
||||
svg
|
||||
.selectAll("circle")
|
||||
.filter((_d, i, nodes) => d3.select(nodes[i]).attr("active"))
|
||||
.attr("r", (d) => zoomOrKeep(ACTIVE_RADIUS_FACTOR * nodeSize[d.id]));
|
||||
|
||||
document.getElementById("zoom").innerHTML = zoomLevel.toFixed(2);
|
||||
};
|
||||
|
||||
const ticked = () => {
|
||||
node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
|
||||
text
|
||||
.attr("x", (d) => d.x)
|
||||
.attr("y", (d) => d.y - FONT_BASELINE / zoomLevel);
|
||||
.attr("y", (d) => d.y - (FONT_BASELINE - nodeSize[d.id]) / zoomLevel);
|
||||
link
|
||||
.attr("x1", (d) => d.source.x)
|
||||
.attr("y1", (d) => d.source.y)
|
||||
@ -129,26 +236,23 @@
|
||||
};
|
||||
|
||||
const restart = () => {
|
||||
updateNodeSize();
|
||||
node = node.data(nodesData, (d) => d.id);
|
||||
node.exit().remove();
|
||||
node = node
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("r", RADIUS)
|
||||
// .attr("fill", (d) => getNodeColor(d))
|
||||
.attr("r", (d) => {
|
||||
return nodeSize[d.id];
|
||||
})
|
||||
.on("click", onClick)
|
||||
.on("mouseover", onMouseover)
|
||||
.on("mouseout", onMouseout)
|
||||
.merge(node);
|
||||
|
||||
link = link.data(linksData, (d) => `${d.source.id}-${d.target.id}`);
|
||||
link.exit().remove();
|
||||
link = link
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("stroke-width", STROKE)
|
||||
.merge(link);
|
||||
|
||||
node.attr("active", (d) => isCurrentPath(d.path) ? true : null);
|
||||
text.attr("active", (d) => isCurrentPath(d.path) ? true : null);
|
||||
link = link.enter().append("line").attr("stroke-width", STROKE).merge(link);
|
||||
|
||||
text = text.data(nodesData, (d) => d.label);
|
||||
text.exit().remove();
|
||||
@ -160,8 +264,13 @@
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("alignment-baseline", "central")
|
||||
.on("click", onClick)
|
||||
.on("mouseover", onMouseover)
|
||||
.on("mouseout", onMouseout)
|
||||
.merge(text);
|
||||
|
||||
node.attr("active", (d) => isCurrentPath(d.path) ? true : null);
|
||||
text.attr("active", (d) => isCurrentPath(d.path) ? true : null);
|
||||
|
||||
simulation.nodes(nodesData);
|
||||
simulation.force("link").links(linksData);
|
||||
simulation.alpha(1).restart();
|
||||
@ -174,12 +283,7 @@
|
||||
ticked();
|
||||
};
|
||||
|
||||
const zoomHandler = d3
|
||||
.zoom()
|
||||
.scaleExtent([0.2, 3])
|
||||
//.translateExtent([[0,0], [width, height]])
|
||||
//.extent([[0, 0], [width, height]])
|
||||
.on("zoom", zoomActions);
|
||||
const zoomHandler = d3.zoom().scaleExtent([0.2, 3]).on("zoom", resize);
|
||||
|
||||
zoomHandler(svg);
|
||||
restart();
|
||||
|
Loading…
x
Reference in New Issue
Block a user