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

Vasturiano 3d-force-graph多类型节点显示整合问题:自定义PNG、默认精灵与文本标签适配及着色解决方案问询

解决3d-force-graph自定义Sprite/图片节点的显示与着色问题

我仔细看了你的代码和需求,发现几个关键的bug和逻辑问题,导致自定义图片被覆盖、标签不显示,以及无法着色的问题。下面是完整的修复方案,完美匹配你的4个需求:

先说说代码里的核心问题

  • 重复声明const img变量,导致discipline图片覆盖了hashtag的引用
  • 类型判断不匹配:你的节点数据里是type: "keyword"/"discipline"/"scholar",但代码里写的是node.type == "keywords"(多了个s
  • Scholar分支错误加载了discipline的图片,逻辑完全搞反了
  • 自定义节点时没有手动添加文本标签,且nodeThreeObjectExtend的用法需要调整
  • 没有开启SpriteMaterial的着色功能,导致无法用node.color给PNG上色

修复后的完整代码

HTML

<div id="3d-graph"></div>
<!-- 不需要提前加载img标签,我们直接在Three.js中加载纹理 -->

CSS

body { margin: 0; }

JavaScript

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 textureLoader = new THREE.TextureLoader();
  const hashtagTexture = textureLoader.load('https://ibb.co/JWP3VSv');
  hashtagTexture.colorSpace = THREE.SRGBColorSpace;
  const disciplineTexture = textureLoader.load('https://ibb.co/5g6FdHQ7');
  disciplineTexture.colorSpace = THREE.SRGBColorSpace;

  const Graph = new ForceGraph3D(elem)
    .graphData(data)
    // 关闭默认标签,我们手动添加自定义标签
    .nodeLabel(null)
    .nodeThreeObjectExtend(false) // 不需要扩展默认节点,完全自定义
    .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 => {
      const group = new THREE.Group();

      // 1. 创建节点主体
      let nodeSprite;
      if (node.type === "keyword") {
        // 关键词节点:加载hashtag图片
        const material = new THREE.SpriteMaterial({
          map: hashtagTexture,
          color: new THREE.Color(node.color), // 用node.color着色
          transparent: true, // 保留PNG透明度
          blending: THREE.AdditiveBlending // 优化透明效果
        });
        nodeSprite = new THREE.Sprite(material);
        nodeSprite.scale.set(14, 14);
      } else if (node.type === "discipline") {
        // 学科节点:加载discipline图片
        const material = new THREE.SpriteMaterial({
          map: disciplineTexture,
          color: new THREE.Color(node.color),
          transparent: true,
          blending: THREE.AdditiveBlending
        });
        nodeSprite = new THREE.Sprite(material);
        nodeSprite.scale.set(14, 14);
      } else if (node.type === "scholar") {
        // 学者节点:默认彩色球形精灵
        const geometry = new THREE.SphereGeometry(7, 16, 16);
        const material = new THREE.MeshBasicMaterial({ color: new THREE.Color(node.color) });
        nodeSprite = new THREE.Mesh(geometry, material);
      }

      group.add(nodeSprite);

      // 2. 添加文本标签(所有节点都添加)
      const textSprite = new SpriteText(node.label);
      textSprite.material.depthWrite = false;
      textSprite.color = node.color;
      textSprite.textHeight = 4;
      textSprite.center.y = -1.2; // 标签在节点上方
      textSprite.position.y = 10; // 调整标签与节点的间距
      group.add(textSprite);

      return group;
    })
    // 节点大小控制
    .nodeSize(node => node.size / 2); // 根据你的size值调整缩放比例

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

const data = {
  "nodes": [{
    "label": "Interdisciplinary research",
    "x": 1072,
    "y": -498,
    "id": "Interdisciplinary research",
    "type": "keyword", // 修正为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": "#coding-trouble",
    "id": "555",
    "color": "rgb(192,192,192)",
    "size": 1.0
  }, {
    "source": "Prof. Dr. J. H.",
    "target": "Computer science",
    "id": "1555",
    "color": "rgb(120,29,248)",
    "size": 1.0
  }]
};

main(data);

关键改动说明

  1. 预加载纹理:用同一个TextureLoader预加载两张图片,避免重复加载,提升性能
  2. 修正类型判断:将keywords改为keyword,匹配数据里的type值,同时修正scholar和discipline的逻辑错误
  3. 自定义节点组:用THREE.Group将节点主体和文本标签组合在一起,确保两者一起显示
  4. 图片着色实现:在SpriteMaterial中设置color: new THREE.Color(node.color),同时开启transparent: true保留PNG的透明区域
  5. 学者节点处理:用Three.js的SphereMesh实现默认彩色球形,匹配你的需求
  6. 文本标签统一添加:所有节点都手动添加SpriteText标签,设置颜色和位置,解决标签缺失问题
  7. 关闭默认标签:设置.nodeLabel(null)避免默认标签和自定义标签冲突
  8. 节点大小调整:通过.nodeSize()根据数据里的size值缩放节点,保证显示比例合理

这样调整后,你的三个节点类型都会按照需求正确显示,PNG图片也能被node.color着色,文本标签正常显示在节点上方。

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

火山引擎 最新活动