基于Vasturiano 3d-force-graph的多类型节点显示组合问题求助
Fixing Your 3D Force Graph Node Display Issues
Hey there, let's work through these issues step by step. I've reviewed your code and identified several key fixes to get your nodes displaying exactly as you intended:
Key Issues in Your Current Code
- Mismatched node type checks: Your data uses
type: "keyword"andtype: "discipline", but your code checks for"keywords"(plural) and misses"discipline"entirely. - Duplicate variable definition: You overwrite the
imgvariable twice when trying to reference DOM image elements (you don't need these for texture loading anyway). - Missing text labels for custom nodes: You only add
SpriteTextto nodes with notype, but your keyword/discipline/scholar nodes all need text labels. - Incorrect texture loading paths: You're trying to load textures from a DOM element ID instead of the actual image URL.
- PNG color tinting not configured: SpriteMaterial requires explicit color setting and transparency enabled to tint your custom PNGs.
- Misused
nodeThreeObjectExtend: Setting this totruemeans your custom objects extend the default sphere sprite, which is why your PNGs are being overwritten. We need to fully replace the node objects instead.
Step-by-Step Fixes
- Fix node type matching: Align your
node.typechecks with the values in your data ("keyword","scholar","discipline"). - Load textures directly via URL: Use
THREE.TextureLoaderto load your PNGs directly from their URLs (or local paths if hosted with your project). - Combine sprite + text for all node types: For each node type, create a parent
THREE.Object3Dthat holds both the sprite (either custom PNG or default sphere) and the text label. - Configure SpriteMaterial for color tinting: Set the
colorproperty of the SpriteMaterial to your node's color, and enabletransparent: trueto preserve PNG transparency. - Disable
nodeThreeObjectExtend: Set this tofalseso your custom objects fully replace the default nodes.
Corrected Full Code
import SpriteText from "https://esm.sh/three-spritetext"; import * as THREE from "https://esm.sh/three"; import ForceGraph3D from "https://cdn.jsdelivr.net/npm/3d-force-graph"; function main(data) { const elem = document.getElementById('3d-graph'); const Graph = new ForceGraph3D(elem) .graphData(data) .nodeLabel(node => `${node.user}: ${node.description}`) .nodeThreeObjectExtend(false) // Disable extension to fully replace nodes .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); }) .nodeAutoColorBy('group') .nodeThreeObject(node => { // Create a parent container to hold both sprite and text const nodeContainer = new THREE.Object3D(); let sprite; // Handle each node type if (node.type === "keyword") { // Load keyword PNG const imgTexture = new THREE.TextureLoader().load('https://i.ibb.co/JWP3VSv/hashtag.png'); imgTexture.colorSpace = THREE.SRGBColorSpace; const material = new THREE.SpriteMaterial({ map: imgTexture, color: new THREE.Color(node.color), // Apply node color tint transparent: true // Preserve PNG transparency }); sprite = new THREE.Sprite(material); sprite.scale.set(14, 14); } else if (node.type === "discipline") { // Load discipline PNG const imgTexture = new THREE.TextureLoader().load('https://i.ibb.co/5g6FdHQ/discipline.png'); imgTexture.colorSpace = THREE.SRGBColorSpace; const material = new THREE.SpriteMaterial({ map: imgTexture, color: new THREE.Color(node.color), transparent: true }); sprite = new THREE.Sprite(material); sprite.scale.set(14, 14); } else if (node.type === "scholar") { // Default colored sphere sprite const material = new THREE.SpriteMaterial({ color: new THREE.Color(node.color) }); sprite = new THREE.Sprite(material); sprite.scale.set(12, 12); // Adjust size as needed } else { // Fallback for unknown types (optional) const material = new THREE.SpriteMaterial({ color: new THREE.Color(node.color) }); sprite = new THREE.Sprite(material); sprite.scale.set(10, 10); } // Add text label to all nodes const text = new SpriteText(node.label); text.material.depthWrite = false; text.color = node.color; text.textHeight = 4; text.center.y = -1.2; // Position above the sprite nodeContainer.add(sprite); nodeContainer.add(text); return nodeContainer; }) .nodeSize(node => node.size); // Use node.size to control node scale Graph.d3Force('charge').strength(-120); } const data = { "nodes": [{ "label": "Interdisciplinary research", "x": 1072, "y": -498, "id": "Interdisciplinary research", "type": "keyword", "color": "rgb(0,189,148)", "size": 33 }, { "label": "Prof. Dr. J. H.", "x": -1448, "y": -4560, "id": "Prof. Dr. J. H.", "type": "scholar", "color": "rgb(0,189,148)", "size": 30 }, { "label": "Computer science", "x": -2228, "y": 13, "id": "Computer science", "type": "discipline", "color": "rgb(120,29,248)", "size": 30.0 }, { "label": "#coding-trouble", "x": -15396, "y": 16305, "id": "#coding-trouble", "type": "keyword", "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);
body { margin: 0; } #3d-graph { width: 100vw; height: 100vh; }
<div id="3d-graph"></div>
Additional Notes
- Texture Loading: Make sure your PNG URLs are accessible and CORS-enabled (the ones you provided should work). If hosting locally, use relative paths like
./imgs/hashtag.png. - Text Positioning: Adjust
text.center.yortext.position.yif you need to move the label closer/farther from the sprite. - Node Scaling: The
nodeSizemethod ensures yournode.sizevalues control the overall node scale (you can adjust the sprite scale values in each type case for finer control).
内容的提问来源于stack exchange,提问作者greg.gan




