Skip to content

Commit

Permalink
feat(Vis View): ✨ Functional Vis Modal!
Browse files Browse the repository at this point in the history
  • Loading branch information
SkepticMystic committed Aug 2, 2021
1 parent 5658473 commit ac59f6f
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 165 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Grid View",
"Juggl",
"List/Matrix View",
"Stats View"
"Stats View",
"Vis View"
]
}
142 changes: 1 addition & 141 deletions src/MatrixView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
BreadcrumbsSettings,
internalLinkObj,
SquareProps,
d3Graph,
} from "src/interfaces";
import type BreadcrumbsPlugin from "src/main";
import { closeImpliedLinks, debug } from "src/sharedFunctions";
Expand Down Expand Up @@ -183,26 +184,6 @@ export default class MatrixView extends ItemView {
return pathsArr;
}

dfsAdjList(g: Graph, startNode: string): AdjListItem[] {
const queue: string[] = [startNode];
const adjList: AdjListItem[] = [];

let i = 0;
while (queue.length && i < 1000) {
i++;

const currNode = queue.shift();
const newNodes = (g.successors(currNode) ?? []) as string[];

newNodes.forEach((succ) => {
const next: AdjListItem = { name: currNode, parentId: succ };
queue.unshift(succ);
adjList.push(next);
});
}
return adjList;
}

createIndex(
// Gotta give it a starting index. This allows it to work for the global index feat
index: string,
Expand Down Expand Up @@ -268,127 +249,6 @@ export default class MatrixView extends ItemView {
const currFile = this.app.workspace.getActiveFile();
const settings = this.plugin.settings;

const closedParents = closeImpliedLinks(gParents, gChildren);
// const dijkstraPaths = graphlib.alg.dijkstra(
// closedParents,
// currFile.basename
// );

const adjList: AdjListItem[] = this.dfsAdjList(
closedParents,
currFile.basename
);
console.log({ adjList });

const noDoubles = adjList.filter(
(thing, index, self) =>
index ===
self.findIndex(
(t) => t.name === thing.name && t?.parentId === thing?.parentId
)
);
console.log({ noDoubles });
console.time("tree");
const hierarchy = createTreeHierarchy(noDoubles, { excludeParent: true });
console.timeEnd("tree");
console.log({ hierarchy });

const d3GraphDiv = this.contentEl.createDiv({ cls: "d3-graph" });

const width = 450;
const height = 450;

const tree = (data) => {
const root = d3
.hierarchy(data)
.sort(
(a, b) =>
d3.descending(a.height, b.height) ||
d3.ascending(a.data.name, b.data.name)
);
root.dx = 10;
root.dy = width / (root.height + 1);
return d3.cluster().nodeSize([root.dx, root.dy])(root);
};

const pack = (data) =>
d3
.pack()
.size([width - 2, height - 2])
.padding(3)(
d3
.hierarchy(data)
.sum((d) => d.value)
.sort((a, b) => b.value - a.value)
);

const root = pack(adjList);

const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("font", "10px sans-serif")
.attr("text-anchor", "middle");

svg
.append("filter")
.append("feDropShadow")
.attr("flood-opacity", 0.3)
.attr("dx", 0)
.attr("dy", 1);

const node = svg
.selectAll("g")
.data(d3.group(root.descendants(), (d) => d.height))
.join("g")
.selectAll("g")
.data((d) => d[1])
.join("g")
.attr("transform", (d) => `translate(${d.x + 1},${d.y + 1})`);

node
.append("circle")
.attr("r", (d) => d.r)
.attr("fill", (d) => color(d.height));

const leaf = node.filter((d) => !d.children);

// leaf.select("circle").attr("id", (d) => (d.leafUid = DOM.uid("leaf")).id);

leaf
.append("clipPath")
// .attr("id", (d) => (d.clipUid = DOM.uid("clip")).id)
.append("use")
.attr("xlink:href", (d) => d.leafUid.href);

leaf
.append("text")
.attr("clip-path", (d) => d.clipUid)
.selectAll("tspan")
.data((d) => d.data.name.split(/(?=[A-Z][a-z])|\s+/g))
.join("tspan")
.attr("x", 0)
.attr("y", (d, i, nodes) => `${i - nodes.length / 2 + 0.8}em`)
.text((d) => d);

node.append("title").text(
(d) =>
`${d
.ancestors()
.map((d) => d.data.name)
.reverse()
.join("/")}\n${format(d.value)}`
);

// for (const [node, path] of Object.entries(dijkstraPaths)) {
// const item: AdjListItem = { id: node };
// const predecessor: string | undefined = path.predecessor;
// if (predecessor) {
// item.parentId = predecessor;
// }
// adjList.push(item);
// }

const viewToggleButton = this.contentEl.createEl("button", {
text: this.matrixQ ? "List" : "Matrix",
});
Expand Down
149 changes: 149 additions & 0 deletions src/VisModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import * as d3 from "d3";
import type { Graph } from "graphlib";
import { createTreeHierarchy } from "hierarchy-js";
import { App, Modal } from "obsidian";
import type { AdjListItem, d3Graph } from "src/interfaces";
import type BreadcrumbsPlugin from "src/main";
import { closeImpliedLinks } from "src/sharedFunctions";

export class VisModal extends Modal {
plugin: BreadcrumbsPlugin;

constructor(app: App, plugin: BreadcrumbsPlugin) {
super(app);
this.plugin = plugin;
}

onOpen() {
// ANCHOR Setup Modal layout
let { contentEl } = this;
contentEl.createEl("h1", { text: "Hey" });

function dfsAdjList(g: Graph, startNode: string): AdjListItem[] {
const queue: string[] = [startNode];
const adjList: AdjListItem[] = [];

let i = 0;
while (queue.length && i < 1000) {
i++;

const currNode = queue.shift();
const newNodes = (g.successors(currNode) ?? []) as string[];

newNodes.forEach((succ) => {
const next: AdjListItem = { name: currNode, parentId: succ };
queue.unshift(succ);
adjList.push(next);
});
}
return adjList;
}

function graphlibToD3(g: Graph): d3Graph {
const d3Graph: d3Graph = { nodes: [], links: [] };
const edgeIDs = {};

g.nodes().forEach((node, i) => {
d3Graph.nodes.push({ id: i, name: node });
edgeIDs[node] = i;
});
g.edges().forEach((edge) => {
d3Graph.links.push({
source: edgeIDs[edge.v],
target: edgeIDs[edge.w],
});
});
return d3Graph;
}

contentEl.empty();
const { gParents, gSiblings, gChildren } = this.plugin.currGraphs;
const currFile = this.app.workspace.getActiveFile();

const closedParents = closeImpliedLinks(gParents, gChildren);

const adjList: AdjListItem[] = dfsAdjList(closedParents, currFile.basename);
console.log({ adjList });

const noDoubles = adjList.filter(
(thing, index, self) =>
index ===
self.findIndex(
(t) => t.name === thing.name && t?.parentId === thing?.parentId
)
);
console.log({ noDoubles });
console.time("tree");
const hierarchy = createTreeHierarchy(noDoubles, {
id: "name",
excludeParent: true,
});
console.timeEnd("tree");
console.log({ hierarchy });

const data = graphlibToD3(closedParents);
const d3GraphDiv = contentEl.createDiv({
cls: "d3-graph",
});

const width = 1000;
const height = 1000;

contentEl.style.width = `${Math.round(screen.width / 1.5)}px`;
contentEl.style.height = `${Math.round(screen.height / 1.3)}px`;

const links = data.links.map((d) => Object.create(d));
const nodes = data.nodes.map((d) => Object.create(d));

const simulation = d3
.forceSimulation(nodes)
.force(
"link",
d3.forceLink(links).id((d) => d.id)
)
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));

const svg = d3
.select(".d3-graph")
.append("svg")
.attr("height", Math.round(screen.height / 1.3))
.attr("width", Math.round(screen.width / 1.3));

const link = svg
.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
.attr("stroke-width", (d) => Math.sqrt(d.value));

const node = svg
.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", 5);
// .attr("fill", color)
// .call(drag(simulation));

node.append("title").text((d) => d.id);

simulation.on("tick", () => {
link
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);

node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
});
}

onClose() {
this.contentEl.empty();
}
}
Loading

0 comments on commit ac59f6f

Please sign in to comment.