You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于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" and type: "discipline", but your code checks for "keywords" (plural) and misses "discipline" entirely.
  • Duplicate variable definition: You overwrite the img variable 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 SpriteText to nodes with no type, 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 to true means 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

  1. Fix node type matching: Align your node.type checks with the values in your data ("keyword", "scholar", "discipline").
  2. Load textures directly via URL: Use THREE.TextureLoader to load your PNGs directly from their URLs (or local paths if hosted with your project).
  3. Combine sprite + text for all node types: For each node type, create a parent THREE.Object3D that holds both the sprite (either custom PNG or default sphere) and the text label.
  4. Configure SpriteMaterial for color tinting: Set the color property of the SpriteMaterial to your node's color, and enable transparent: true to preserve PNG transparency.
  5. Disable nodeThreeObjectExtend: Set this to false so 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.y or text.position.y if you need to move the label closer/farther from the sprite.
  • Node Scaling: The nodeSize method ensures your node.size values control the overall node scale (you can adjust the sprite scale values in each type case for finer control).

内容的提问来源于stack exchange,提问作者greg.gan

火山引擎 最新活动