diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2021-08-23 21:33:42 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2021-08-23 21:33:42 +0200 |
| commit | a05d52a7b13607181ce0443b17769bb02532dfc1 (patch) | |
| tree | f5c4722363f29a610d1a4f95efbd9351999083f6 /src | |
| parent | 5ddde1cbb70bf4bc2df127fced5afb966069d299 (diff) | |
importing rtf
Diffstat (limited to 'src')
| -rw-r--r-- | src/graph.js | 120 | ||||
| -rw-r--r-- | src/index.js | 150 | ||||
| -rw-r--r-- | src/views/Detail.js | 31 |
3 files changed, 156 insertions, 145 deletions
diff --git a/src/graph.js b/src/graph.js new file mode 100644 index 0000000..fdac979 --- /dev/null +++ b/src/graph.js @@ -0,0 +1,120 @@ +import * as THREE from "three"; +import ForceGraph3D from "3d-force-graph"; +import SpriteText from "three-spritetext"; +import { union } from "./utils/set_utils.js"; + +const IMG_SCALE = 20; + +export default function buildGraph(db, handlers) { + const linkable = {}; + const groups = {}; + const data = { nodes: [], links: [] }; + + // console.log(db); + + /** + * load nodes and links + */ + + db.page.forEach((item, index) => { + const node = { + title: item.title, + id: index, + data: item, + groups: [], + }; + data.nodes.push(node); + for (let tagIndex = 0; tagIndex < 8; tagIndex += 1) { + 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 { + 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) + .showNavInfo(false) + .nodeLabel((node) => node.title) + .nodeThreeObject((node) => { + let sprite; + if (node.data.thumbnail?.uri) { + const imgTexture = new THREE.TextureLoader().load( + node.data.thumbnail.uri + ); + // console.log(imgTexture); + const aspect = node.data.thumbnail.width / node.data.thumbnail.height; + const material = new THREE.SpriteMaterial({ map: imgTexture }); + sprite = new THREE.Sprite(material); + sprite.scale.set(IMG_SCALE, IMG_SCALE / aspect); + return sprite; + } else { + sprite = new SpriteText( + node.title.split(/[ :]+/).slice(0, 3).join(" ") + ); + sprite.material.depthWrite = false; // make sprite background transparent + sprite.color = "#888888"; // colors[node.groups[0]]; // node.groups.length - 1]]; + sprite.textHeight = 4; + return sprite; + } + }) + .onNodeClick(handlers.click); + + // 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); diff --git a/src/index.js b/src/index.js index e23db17..c51ecd1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,161 +1,21 @@ /** * No.6092 site for Charles Stankievech - * - load db.json - * - map the nodes to a graph - * - click the nodes to open them - * - some of the nodes are images, others are 3D */ -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)", -]; +import buildGraph from "./graph.js"; async function main() { const db = await loadDB(); - const groups = {}; - const linkable = {}; - const data = { nodes: [], links: [] }; - - console.log(db); - - /** - * load nodes and links - */ - - db.page.forEach((item, index) => { - const node = { - title: item.title, - id: index, - groups: [], - }; - data.nodes.push(node); - for (let tagIndex = 0; tagIndex < 8; tagIndex += 1) { - 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 { - 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) - .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()); + function handleClick(node) { + console.log(node); } - 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), + buildGraph(db, { + click: handleClick, }); - - 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("/assets/db.json"); return await request.json(); diff --git a/src/views/Detail.js b/src/views/Detail.js new file mode 100644 index 0000000..51598c1 --- /dev/null +++ b/src/views/Detail.js @@ -0,0 +1,31 @@ +/** + * Detail view, displaying text plus media + */ + +export default function Detail({ node }) { + const index = id + 1; + const { id, data } = node; + return ( + <div className="detail"> + <div className="content"> + <div className="title"> + {index} + <br /> + {data.author} + <br /> + {data.title} + <br /> + </div> + <div + className="caption" + dangerouslySetInnerHTML={{ __html: data.caption }} + /> + <div + className="description" + dangerouslySetInnerHTML={{ __html: data.description }} + /> + </div> + <div className="media"></div> + </div> + ); +} |
