summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2021-08-16 18:02:43 +0200
committerJules Laplace <julescarbon@gmail.com>2021-08-16 18:02:43 +0200
commit9ffba33ec3d0e1bc2340f10afba9af39cac095b9 (patch)
tree6af61f342ab20e4327d012f0612abe3fcf06f7d3 /src
parent6f29c59991925e8685a1911ce1b37c6097d7c517 (diff)
better viz
Diffstat (limited to 'src')
-rw-r--r--src/index.js144
-rw-r--r--src/utils/set_utils.js81
-rw-r--r--src/utils/stars.js53
3 files changed, 260 insertions, 18 deletions
diff --git a/src/index.js b/src/index.js
index 39b3c7d..4c09857 100644
--- a/src/index.js
+++ b/src/index.js
@@ -7,46 +7,154 @@
*/
import ForceGraph3D from "3d-force-graph";
+import SpriteText from "three-spritetext";
+import { union } from "./utils/set_utils.js";
+import stars from "./utils/stars.js";
+
+const colors = [
+ "rgba(111,53,158,1.0)",
+ "rgba(220,188,253,1.0)",
+ "rgba(30,177,237,1.0)",
+ "rgba(148,206,88,1.0)",
+ "rgba(252,42,28,1.0)",
+ "rgba(255,253,56,1.0)",
+ "rgba(43,253,183,1.0)",
+ "rgba(252,76,252,1.0)",
+ "rgba(205,254,170,1.0)",
+ "rgba(254,205,195,1.0)",
+ "rgba(199,227,254,1.0)",
+ "rgba(253,191,45,1.0)",
+ "rgba(253,191,45,1.0)",
+];
async function main() {
const db = await loadDB();
- const trees = {};
+ const groups = {};
+ const linkable = {};
const data = { nodes: [], links: [] };
console.log(db);
+ /**
+ * load nodes and links
+ */
+
db.page.forEach((item, index) => {
- data.nodes.push({
+ const node = {
+ title: item.title,
id: index,
- });
+ groups: [],
+ };
+ data.nodes.push(node);
for (let tagIndex = 0; tagIndex < 8; tagIndex += 1) {
- const value = item["tag_" + tagIndex];
- if (value) {
- if (value in trees) {
- data.links.push({
- source: choice(trees[value]),
- target: index,
- });
- // option: don't link to the root node more than once
- if (window.location.hash === "#dense" && trees[value][0]) {
- trees[value].push(index);
- } else {
- trees[value] = [index];
- }
+ const group = item["tag_" + tagIndex];
+ if (!group) continue;
+ group -= 1;
+ node.groups.push(group);
+ if (group in groups) {
+ groups[group].push(index);
+ } else {
+ groups[group] = [index];
+ }
+ if (group in linkable) {
+ data.links.push({
+ // source: choice(linkable[group]),
+ source: linkable[group][0],
+ target: index,
+ });
+ // option: don't link to the root node more than once
+ // if (window.location.hash === "#dense" && linkable[group][0]) {
+ if (linkable[group][0]) {
+ linkable[group].push(index);
} else {
- trees[value] = [index];
+ linkable[group] = [index];
}
+ } else {
+ linkable[group] = [index];
}
}
});
+ /**
+ * find common links
+ */
+
+ data.links.forEach((link) => {
+ const a = data.nodes[link.source];
+ const b = data.nodes[link.target];
+
+ !a.links && (a.links = []);
+ !b.links && (b.links = []);
+ a.links.push(link);
+ b.links.push(link);
+ });
+
+ const highlightNodes = new Set();
+ const highlightLinks = new Set();
+ let selectedNode = null;
+
+ /** build group */
+
let graph = ForceGraph3D();
- graph(document.querySelector("#graph")).graphData(data);
+ graph(document.querySelector("#graph"))
+ .graphData(data)
+ .nodeLabel((node) => node.title)
+ .nodeThreeObject((node) => {
+ const sprite = new SpriteText(
+ node.title.split(/[ :]+/).slice(0, 3).join(" ")
+ );
+ sprite.material.depthWrite = false; // make sprite background transparent
+ sprite.color = colors[node.groups[0]]; // node.groups.length - 1]];
+ sprite.textHeight = 4;
+ return sprite;
+ })
+ // .nodeColor((node) =>
+ // highlightNodes.has(node.id)
+ // ? node === selectedNode
+ // ? colors[commonGroups(selectedNode, node)[0]]
+ // : colors[commonGroups(selectedNode, node)[0]].replace("1.0", "0.8")
+ // : colors[commonGroups(selectedNode, node)[0]].replace("1.0", "0.6")
+ // )
+ .linkWidth((link) => (highlightLinks.has(link) ? 4 : 1))
+ .onNodeClick((node) => {
+ // no state change
+ if (!node && !highlightNodes.size) return;
+
+ selectedNode = selectedNode === node ? null : node;
+
+ highlightNodes.clear();
+ highlightLinks.clear();
+ if (node) {
+ node.groups.forEach((group) =>
+ groups[group].forEach((neighbor) => highlightNodes.add(neighbor))
+ );
+ node.links.forEach((link) => highlightLinks.add(link));
+ }
+
+ updateHighlight();
+ });
+
+ function updateHighlight() {
+ graph.nodeColor(graph.nodeColor()).linkWidth(graph.linkWidth());
+ }
+
+ graph.d3Force("charge").strength(-150);
+
+ // camera orbit
+ const distance = 250;
+ let angle = 0;
+ graph.cameraPosition({
+ x: distance * Math.sin(angle),
+ z: distance * Math.cos(angle),
+ });
+
+ stars();
}
const randint = (limit) => Math.floor(Math.random() * limit);
const choice = (list) => list[randint(list.length)];
+const commonGroups = (a, b) => union(a.groups, b.groups);
async function loadDB() {
const request = await fetch("/db.json");
diff --git a/src/utils/set_utils.js b/src/utils/set_utils.js
new file mode 100644
index 0000000..88e8fea
--- /dev/null
+++ b/src/utils/set_utils.js
@@ -0,0 +1,81 @@
+/**
+ * Operations on sets.
+ * @module app/utils/set_utils
+ */
+
+/**
+ * Determine if `set` contains `subset`
+ * @param {Set} set the superset
+ * @param {Set} subset the subset
+ * @return {Boolean} true if set contains subset
+ */
+export function isSuperset(set, subset) {
+ for (let elem of subset) {
+ if (!set.has(elem)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Return the union (A or B) of two sets
+ * @param {Set} setA a set
+ * @param {Set} setB a set
+ * @return {Boolean} the union of the sets
+ */
+export function union(setA, setB) {
+ let _union = new Set(setA);
+ for (let elem of setB) {
+ _union.add(elem);
+ }
+ return _union;
+}
+
+/**
+ * Return the intersection (A and B) of two sets
+ * @param {Set} setA a set
+ * @param {Set} setB a set
+ * @return {Boolean} the intersection of the sets
+ */
+export function intersection(setA, setB) {
+ let _intersection = new Set();
+ for (let elem of setB) {
+ if (setA.has(elem)) {
+ _intersection.add(elem);
+ }
+ }
+ return _intersection;
+}
+
+/**
+ * Return the symmetric difference (A xor B) of two sets
+ * @param {Set} setA a set
+ * @param {Set} setB a set
+ * @return {Boolean} the symmetric difference of the sets
+ */
+export function symmetricDifference(setA, setB) {
+ let _difference = new Set(setA);
+ for (let elem of setB) {
+ if (_difference.has(elem)) {
+ _difference.delete(elem);
+ } else {
+ _difference.add(elem);
+ }
+ }
+ return _difference;
+}
+
+/**
+ * Return the difference (A not B) of two sets
+ * @param {Set} setA a set
+ * @param {Set} setB a set
+ * @return {Boolean} the difference of the sets
+ */
+export function difference(setA, setB) {
+ let _difference = new Set(setA);
+ for (let elem of setB) {
+ _difference.delete(elem);
+ }
+ return _difference;
+}
diff --git a/src/utils/stars.js b/src/utils/stars.js
new file mode 100644
index 0000000..516a359
--- /dev/null
+++ b/src/utils/stars.js
@@ -0,0 +1,53 @@
+export default function stars() {
+ var canvas = document.createElement("canvas"),
+ ctx = canvas.getContext("2d");
+ document.body.appendChild(canvas);
+ canvas.style.width = "100%";
+ canvas.style.height = "100%";
+ canvas.style.position = "absolute";
+ canvas.style.top = "0px";
+ canvas.style.left = "0px";
+ canvas.style.zIndex = 1;
+ canvas.style.opacity = 0.6;
+ canvas.style.pointerEvents = "none";
+ document.body.addEventListener("resize", go);
+ document.body.parentNode.style.backgroundColor = "black";
+ ctx.strokeStyle = "white";
+ var s = Math.sin,
+ c = Math.cos;
+ go();
+ function ri(n) {
+ return Math.random() * n;
+ }
+ function rr(a, b) {
+ return (b - a) * Math.random() + a;
+ }
+ function go() {
+ var w = (canvas.width = window.innerWidth);
+ var h = (canvas.height = window.innerHeight);
+ ctx.clearRect(0, 0, w, h);
+ var n = Math.sqrt(w * h) | 0;
+ while (n--) {
+ var x = ri(w);
+ var y = ri(h);
+ var r0 = rr(0, 1);
+ var r1 = rr(0, 1);
+ var r2 = rr(0, 1);
+ var t0 = ri(2 * Math.PI);
+ var t1 = ri(2 * Math.PI);
+ var t2 = ri(2 * Math.PI);
+ var x0 = x + c(t0) * r0;
+ var y0 = y + s(t0) * r0;
+ var x1 = x + c(t1) * r1;
+ var y1 = y + s(t1) * r1;
+ var x2 = x + c(t2) * r2;
+ var y2 = y + s(t2) * r2;
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.bezierCurveTo(x0, y0, x1, y1, x2, y2);
+ var color = rr(0, 255) | 0;
+ ctx.strokeStyle = "rgb(" + color + "," + color + "," + color + ")";
+ ctx.stroke();
+ }
+ }
+}