diff options
Diffstat (limited to 'src/index.js')
| -rw-r--r-- | src/index.js | 144 |
1 files changed, 126 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"); |
