选择性连线与悬停标签.onNodeHover、.onNodeClick在 Vasturiano的 3d-force-graph中
我在使用Vasco Asturiano的惊人作品 3d-force-graph,在为下面的问题设计一个合适的开关命令(或if/else语句)时遇到了困难:
在下面的最小可复现示例(MRE)中,你会看到.onNodeHover会对每个节点输出文本,即使文本没有意义(我只是想要对人名显示悬停文本,而不是标签或学科)。我希望鼠标悬停文本仅在.json文件中存在链接时才出现。
https://jsfiddle.net/oan6zwcm/
我接着想保持相机重新定位 .onNodeClick,这简直酷到爆,但想用右键单击来打开链接。可以做到吗?目前我只能选择一个函数,或另一个:
.onNodeClick(node => window.open(node.link))
.onNodeClick(node => {
// Aim at node from outside it
const distance = 80;
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}; // special case if node is in (0,0,0)
Graph.cameraPosition(
newPos, // new position
node, // lookAt ({ x, y, z })
3000 // ms transition duration
);
})
右键点击函数在别处定义,但我还没想清楚如何创建正确的条件,将右键点击与 window.open(node.link)) 连接起来。
function doSomething(e) {
var rightclick;
if (!e) var e = window.event;
if (e.which) rightclick = (e.which == 3);
else if (e.button) rightclick = (e.button == 2);
alert('Rightclick: ' + rightclick); // true or false
}
我的当前构建在这里: https://github.com/greg-gan/JTC_Oak-visualization/blob/main/index.html
以及可视化:https://greg-gan.github.io/JTC_Oak-visualization/public/
我最近在这里发了我的第一个问题,它帮了我非常多!@kikon给出的if/else语句很可能就是破解难题的钥匙。无论如何,非常感谢你的帮助。
解决方案
我理解你的意思是在悬停时你只想要带有链接的节点,在右键时你想从某个特定节点打开链接……希望理解正确。
试试这个思路
.nodeLabel(node => {
if (node.link) {
return `${node.label}<br>${node.link}`;
}
return '';
})
逐个检查每个节点是否存在链接:若存在,就返回tooltip;若不存在,就返回一个空字符串。
至于第二个问题,为什么不使用.onNodeRightClick?
https://github.com/vasturiano/3d-force-graph#interaction
.onNodeRightClick(node => {
if (node.link) {
window.open(node.link, '_blank', 'noopener,noreferrer');
}
})
<div id="3d-graph"></div>
<script type="importmap">
{
"imports": {
"three": "https://esm.sh/[email protected]",
"three-spritetext": "https://esm.sh/[email protected]",
"3d-force-graph": "https://esm.sh/[email protected]"
}
}
</script>
<script type="module">
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}<br>${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/JTC_Oak_visualization_trial/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_Oak_visualization_trial/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);
</script>
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。