From 077391919da06f05cfda722d8612df1bd43f4f3c Mon Sep 17 00:00:00 2001 From: Ross Keenan Date: Sun, 15 Aug 2021 18:23:12 +0200 Subject: [PATCH] feat(Vis View): :sparkles: Force Directed Graph: Highlight path from current note to hovered node --- src/Visualisations/ForceDirectedG.ts | 63 ++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/src/Visualisations/ForceDirectedG.ts b/src/Visualisations/ForceDirectedG.ts index ae14f73d..dedea21a 100644 --- a/src/Visualisations/ForceDirectedG.ts +++ b/src/Visualisations/ForceDirectedG.ts @@ -1,5 +1,6 @@ import * as d3 from "d3"; import type { Graph } from "graphlib"; +import * as graphlib from "graphlib"; import { openOrSwitch } from "src/sharedFunctions"; import type { d3Node } from "src/interfaces"; import { graphlibToD3, VisModal } from "src/VisModal"; @@ -13,6 +14,11 @@ export const forceDirectedG = ( width: number, height: number ) => { + const pathsFromCurrNode = graphlib.alg.dijkstra(graph, currFile.basename); + + const nodeColour = getComputedStyle(document.body).getPropertyValue( + "--text-accent" + ); const colourChange = d3 .select(".d3-graph") .append("input") @@ -41,11 +47,14 @@ export const forceDirectedG = ( const links: { index: number; - source: { index: number }; - target: { index: number }; + source: { index: number; x: number; y: number }; + target: { index: number; x: number; y: number }; }[] = data.links.map((d) => Object.create(d)); const nodes = data.nodes.map((d) => Object.create(d)); + const currNodeIndex = data.nodes.find( + (node) => node.name === currFile.basename + ).id; const simulation = d3 .forceSimulation(nodes) @@ -126,9 +135,11 @@ export const forceDirectedG = ( .append("svg:path") .attr("d", "M0,-5L10,0L0,5"); - const nodeColour = getComputedStyle(document.body).getPropertyValue( - "--text-accent" - ); + const nameFromIndex = (d: { index: number }) => + data.nodes.find((node) => node.id === d.index).name; + + const indexFromName = (name: string): number => + data.nodes.find((node) => node.name === name).id; const node: d3.Selection< d3.BaseType | SVGCircleElement, @@ -137,7 +148,6 @@ export const forceDirectedG = ( unknown > = svg .append("g") - .attr("stroke", nodeColour) .attr("stroke-width", 1.5) .selectAll("circle") .data(nodes) @@ -168,6 +178,29 @@ export const forceDirectedG = ( return !!linkedArr; } + function walkDijkstraPaths( + paths: { [node: string]: graphlib.Path }, + startNode: string + ) { + if ( + startNode === currFile.basename || + paths[startNode].distance === Infinity + ) + return []; + let step = startNode; + + const path: string[] = [startNode]; + let i = 0; + while (paths[step].distance > 1 && i < 200) { + i++; + step = paths[startNode].predecessor; + path.push(step); + } + if (i >= 200) return []; + path.push(currFile.basename); + return path; + } + node .on("mouseover", (event: MouseEvent, d: { index: number }) => { node.style("opacity", (o) => { @@ -178,6 +211,22 @@ export const forceDirectedG = ( ? 1 : 0.1; }); + + // Highlight path from hovered node to currNode + const hoveredNode = nameFromIndex(d); + const path = walkDijkstraPaths(pathsFromCurrNode, hoveredNode); + if (path.length) { + link + .transition() + .duration(150) + .style("stroke", function (link) { + if ( + path.includes(nameFromIndex(link.source)) && + path.includes(nameFromIndex(link.target)) + ) + return nodeColour; + }); + } }) .on("mouseout", unfocus); @@ -186,7 +235,7 @@ export const forceDirectedG = ( function unfocus() { // labelNode.attr("display", "block"); node.style("opacity", 1); - link.style("opacity", 1); + link.style("opacity", 1).style("stroke", "#868282"); } simulation.on("tick", () => {