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