Question Details

No question body available.

Tags

javascript graph three.js 3d

Answers (1)

Accepted Answer Available
Accepted Answer
May 6, 2026 Score: 2 Rep: 2,686 Quality: High Completeness: 80%

I understood you that on hover you only want nodes with a link, and on the right click you want to open the link from a given node... I hope that's correct.

Try this approach

.nodeLabel(node => {
  if (node.link) {
    return ${node.label}
${node.link}
; } return ''; })

Check for each node whether the link exists, if it exists, you return tooltip, if not, you get an empty string.

As for the second question, why don't you use .onNodeRightClick?

https://github.com/vasturiano/3d-force-graph#interaction

.onNodeRightClick(node => {
    if (node.link) {
    window.open(node.link, 'blank', 'noopener,noreferrer');
    }
})

{ "imports": { "three": "https://esm.sh/three@0.184.0", "three-spritetext": "https://esm.sh/three-spritetext?deps=three@0.184.0", "3d-force-graph": "https://esm.sh/3d-force-graph?deps=three@0.184.0" } }

import SpriteText from "three-spritetext"; import * as THREE from "three"; import ForceGraph3D from "3d-force-graph";

function main(data) { const elem = document.getElementById("3d-graph");

const Graph = new ForceGraph3D(elem) .graphData(data) //v .nodeLabel(node => { if (node.link) { return ${node.label}
${node.link}
; } return ''; }) //^hover .onNodeClick(node => { const distance = 40; const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z); const newPos = node.x || node.y || node.z ? { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio } : { x: 0, y: 0, z: distance };

Graph.cameraPosition(newPos, node, 3000); }) //v .onNodeRightClick(node => { if (node.link) { window.open(node.link, '
blank', 'noopener,noreferrer'); } }) //^rc .nodeThreeObjectExtend(true) .nodeThreeObject(node => { const textSprite = new SpriteText(node.label ?? node.id); textSprite.material.depthWrite = false; textSprite.color = node.color; textSprite.center.y = -1.2;

if (node.type === "keywords") { const imgTexture = new THREE.TextureLoader().load( "https://raw.githubusercontent.com/greg-gan/JTCOakvisualizationtrial/refs/heads/main/imgs/hashtag.png" ); imgTexture.colorSpace = THREE.SRGBColorSpace; const material = new THREE.SpriteMaterial({ map: imgTexture }); const sprite = new THREE.Sprite(material); sprite.scale.set(8, 8); textSprite.textHeight = 4 / 8; sprite.add(textSprite); node.color = "rgba(0,0,0,0)"; return sprite; }

if (node.type === "discipline") { const imgTexture = new THREE.TextureLoader().load( "https://raw.githubusercontent.com/greg-gan/JTC
Oakvisualizationtrial/refs/heads/main/imgs/discipline.png" ); imgTexture.colorSpace = THREE.SRGBColorSpace; const material = new THREE.SpriteMaterial({ map: imgTexture }); const sprite = new THREE.Sprite(material); sprite.scale.set(14, 14); textSprite.textHeight = 4 / 14; sprite.add(textSprite); node.color = "rgba(0,0,0,0)"; return sprite; }

textSprite.textHeight = 4; return textSprite; });

Graph.d3Force("charge").strength(-120); }

const data = { nodes: [ { label: "Interdisciplinary", x: 1072, y: -498, id: "Interdisciplinary research", type: "keywords", color: "rgb(0,189,148)", size: 33 }, { label: "Prof. Dr. J. H.", link: "https://www.archive.org", x: -1448, y: -4560, id: "Prof. Dr. J. H.", type: "scholar", color: "rgb(248,189,0)", size: 30 }, { label: "Benjamin St.", link: "https://www.wikipedia.org", x: -2228, y: 13, id: "Benjamin St.", color: "rgb(190,129,248)", size: 30.0 }, { label: "#journalism", x: -15396, y: 16305, id: "#journalism", type: "discipline", color: "rgb(192,192,192)", size: 31 } ], links: [ { source: "Prof. Dr. J. H.", target: "Interdisciplinary research", id: "988", color: "rgb(0,189,148)", size: 1.0 }, { source: "Prof. Dr. J. H.", target: "#journalism", id: "555", color: "rgb(192,192,192)", size: 1.0 }, { source: "Benjamin St.", target: "#journalism", id: "1555", color: "rgb(92,192,192)", size: 1.0 } ] };

main(data);