From 14ee9d074c7457c46ee4c02ecb25a4ee73689b34 Mon Sep 17 00:00:00 2001 From: Ross Keenan Date: Fri, 6 Aug 2021 08:45:20 +0200 Subject: [PATCH] feat(Vis View): :sparkles: Better Circle Packing --- src/Visualisations/CirclePacking.ts | 106 ++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/src/Visualisations/CirclePacking.ts b/src/Visualisations/CirclePacking.ts index 042bc659..23669226 100644 --- a/src/Visualisations/CirclePacking.ts +++ b/src/Visualisations/CirclePacking.ts @@ -1,16 +1,9 @@ import * as d3 from "d3"; import type { Graph } from "graphlib"; -import { createTreeHierarchy } from "hierarchy-js"; import type { App, TFile } from "obsidian"; -import type { AdjListItem, d3Node, d3Tree } from "src/interfaces"; +import type { AdjListItem, d3Node } from "src/interfaces"; import { openOrSwitch } from "src/sharedFunctions"; -import { - bfsAdjList, - bfsFromAllSinks, - reverseDepth, - stratify, - VisModal, -} from "src/VisModal"; +import { bfsAdjList, VisModal } from "src/VisModal"; export const circlePacking = ( graph: Graph, @@ -23,27 +16,31 @@ export const circlePacking = ( const adjList: AdjListItem[] = bfsAdjList(graph, currFile.basename); console.log({ adjList }); - const root = stratify(adjList); - console.log(root); - - console.log(reverseDepth(adjList)); + const noDoubles = [...adjList]; + noDoubles.forEach((a, i) => { + if (noDoubles.some((b, j) => i !== j && a.name === b.name)) { + const index = noDoubles.findIndex((b, j) => i !== j && a.name === b.name); + noDoubles.splice(index, 1); + } + }); - const noDoubles = adjList.filter( - (thing, index, self) => - index === - self.findIndex( - // This version only check if the name is the same, not the parent - (t) => t.name === thing.name - ) - ); + // const noDoubles = adjList.filter((a) => { + // !adjList.some((b) => { + // console.log({ a, b }); + // return a.name !== b.name && a.parentId === b.parentId; + // }); + // }); console.log({ noDoubles }); - const hierarchy: d3Tree = createTreeHierarchy(noDoubles, { - id: "name", - excludeParent: true, - }); + // const root = stratify(noDoubles); + // console.log(root); - console.log({ hierarchy }); + // const hierarchy: d3Tree = createTreeHierarchy(noDoubles, { + // id: "name", + // excludeParent: true, + // }); + + // console.log({ hierarchy }); const svg = d3 .select(".d3-graph") @@ -51,18 +48,22 @@ export const circlePacking = ( .attr("height", height) .attr("width", width); + const nodeColour = getComputedStyle(document.body).getPropertyValue( + "--text-accent" + ); + // Initialize the circle: all located at the center of the svg area const node = svg .append("g") .selectAll("circle") .data(noDoubles) .join("circle") - .attr("r", 25) + .attr("r", (d) => Math.round(d.height / 10) + 10) .attr("cx", width / 2) .attr("cy", height / 2) - .style("fill", "#69b3a2") - .style("fill-opacity", 0.3) - .attr("stroke", "#69a2b2") + .style("fill", nodeColour) + .style("fill-opacity", 0.6) + .attr("stroke", nodeColour) .style("stroke-width", 4); node.attr("aria-label", (d: AdjListItem) => d.name); @@ -89,7 +90,7 @@ export const circlePacking = ( .force("charge", d3.forceManyBody().strength(0.5)) // Nodes are attracted one each other of value is > 0 .force( "collide", - d3.forceCollide().strength(0.01).radius(30).iterations(1) + d3.forceCollide().strength(0.025).radius(30).iterations(1) ); // Force that avoids circle overlapping // Apply these forces to the nodes and update their positions. @@ -97,4 +98,47 @@ export const circlePacking = ( simulation.nodes(noDoubles).on("tick", function (d) { node.attr("cx", (d) => d.x).attr("cy", (d) => d.y); }); + + function zoomed({ transform }) { + node.attr("transform", transform); + } + svg.call( + d3 + .zoom() + .extent([ + [0, 0], + [width, height], + ]) + .scaleExtent([0.5, 8]) + .on("zoom", zoomed) + ); + + const drag = ( + simulation: d3.Simulation + ) => { + function dragstarted(event, d) { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + } + + function dragged(event, d) { + d.fx = event.x; + d.fy = event.y; + } + + function dragended(event, d) { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + } + + return d3 + .drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended); + }; + + node.call(drag(simulation)); };