Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Showing labels front of node #669

Open
longlp opened this issue Feb 24, 2024 · 8 comments
Open

Showing labels front of node #669

longlp opened this issue Feb 24, 2024 · 8 comments

Comments

@longlp
Copy link

longlp commented Feb 24, 2024

I have try two options for showing labels with node

  • Sprite text
  • Html

619DE843-3942-44C2-856B-C0C5C71766BE

8EF7BCAD-8888-44C4-A537-8F9AA7D441C0

My requirement is showing the text front of node ( for sprite text the position is center of node)

  • The Sprite text is give me the best UX when controlling the graph (zooming, rotating..)
  • The Html is solved my requirement is showing label front of node but it’s not zooming with node.

I’m working around with calculating distance between node and camera and set font size of text but still meet the UX requirements (it’s delayed due to event stop of control trigger with debound function).

I try to set potions of Sprite but the methods to calculate of Sprite text after rotating is complexity and still delay due to debound when rotating)

Can your guy have any ideas to solve this problem or have any others ideas for this requirement plz give me some instructions.
Thank you all.!

@EncompassingResidential
Copy link

EncompassingResidential commented Mar 5, 2024

Hi @longlp , I got sprites to work in my React app. Here is my code. down below that I'll show the HTML code that I can not get to work if you can help.

Sprites / canvas code : TreeForceGraphComponent.js

import React, { useEffect } from 'react';
import ForceGraph3D from 'react-force-graph-3d';
import { Group, Mesh, MeshBasicMaterial, SphereGeometry, Sprite, SpriteMaterial, Texture } from 'three';

-function TreeForceGraphComponent({ tree_18_GraphData, targetBAGName, areNodeLabelsOn }) {

function createTextSprite(text) {
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');

  canvas.width = 512; 
  canvas.height = 256;
  context.font = '24px Arial';
  context.fillStyle = 'rgba(255, 255, 255, 1.0)';

  context.textAlign = 'left';
  context.textBaseline = 'middle';
  
  context.fillText(text, canvas.width / 2, canvas.height / 2);

  const texture = new Texture(canvas);
  texture.needsUpdate = true;

  const spriteMaterial = new SpriteMaterial({ map: texture });
  const sprite = new Sprite(spriteMaterial);

  sprite.scale.set(75, 75, 1);

  return sprite;
}

const nodeThreeObject = areNodeLabelsOn ? node => {

const group = new Group();

const nodeGeometry = new SphereGeometry(7);
const nodeMaterial = new MeshBasicMaterial({ color: node.color });
const mesh = new Mesh(nodeGeometry, nodeMaterial);
group.add(mesh);

const sprite = createTextSprite(node.id);
sprite.color = node.color;
sprite.textHeight = 1;
sprite.position.x = 0;
sprite.position.y = 0;
sprite.position.z = 8;
return sprite;

} : null;

 return (
    <div>
    <ForceGraph3D
        graphData={tree_18_GraphData}
        width={1000}
        height={500}
        backgroundColor="#B4B5C5"
        nodeThreeObject={ areNodeLabelsOn ? nodeThreeObject : undefined }
        nodeThreeObjectExtend={true}
        nodeLabel={nodes => `${nodes.id || 'SomE ThinG'} :: ${nodes.description || 'Empty Description'}`}
        nodeAutoColorBy="group"
        linkDirectionalParticles={6}
        linkWidth={3}
        linkOpacity={0.5}
        />
    </div>
);

}

export default TreeForceGraphComponent;

Here is React code trying to add HTML on each node, but not working: TreeForceGraphComponent.js

import React, { useCallback, useEffect, useRef } from 'react';

import ForceGraph3D from 'react-force-graph-3d';
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
import { logEntry } from './Utilities';

function TreeForceGraphComponent({ tree_18_GraphData, targetBAGName, areNodeLabelsOn, focusNodeId }) {

const fgRef = useRef();

const nodeThreeObject = areNodeLabelsOn ? node => {

const nodeEl = document.createElement('div');
nodeEl.textContent = node.id;
nodeEl.style.color = node.color;
nodeEl.className = 'node-label';
const label = new CSS2DObject(nodeEl);

label.position.setY(5);

return label;

};

useEffect(() => {
const css2DRenderer = new CSS2DRenderer();

document.body.appendChild(css2DRenderer.domElement);

// Set the size of the CSS2DRenderer to match your graph container
// This might be dynamic depending on your setup
css2DRenderer.setSize(window.innerWidth, window.innerHeight);

// Update ForceGraph3D to use the CSS2DRenderer
if (fgRef.current) {
  fgRef.current.renderer([css2DRenderer]);
}

}, []);

const nodeAutoColorBy = (node) => {
if (node.id === targetBAGName) {
return 12;
}
return node.group;
};

const linkColorDetermine = link => {
if (link.type === 'Past Member') {
return 'green';
} else if (link.type === 'Current Member') {
return 'darkgreen';
} else if (link.type === 'Genre') {
return 'lightgreen';
} else if (link.type === 'Label') {
return 'blue';
} else {
return 'gray';
}
}

return (


<ForceGraph3D
graphData={tree_18_GraphData}
ref={fgRef}
onNodeDragStart={node => logEntry('TreeForceGraphComponent', onNodeDragStart: ${node.id}) }
onNodeDragEnd={node => logEntry('TreeForceGraphComponent', onNodeDragEnd: ${node.id}) }
onNodeClick={handleNodeClick}
width={1000}
height={500}
backgroundColor="#B4B5C5"
nodeAutoColorBy={nodeAutoColorBy}
nodeThreeObject={areNodeLabelsOn ? nodeThreeObject : undefined }
nodeThreeObjectExtend={true} // false turns off nodes with nodeThreeObject
linkWidth={areNodeLabelsOn ? 2 : 1}
linkOpacity={areNodeLabelsOn ? 0.5 : 0.4}
linkColor={linkColorDetermine}
linkDirectionalParticles={6}
nodeLabel={nodes => ${nodes.id || 'SomE ThinG'} :: ${nodes.description || 'Empty Description'}}
/>

);
}

export default TreeForceGraphComponent;

@longlp-sosene
Copy link

longlp-sosene commented Mar 6, 2024

Just work around and solve my problem with inject the callback function to handle increase fontsize (base on distance between Node and Camera) with control change event

Graph.controls()
    .addEventListener('change', ev => {
        handleChangeTextFormat()
    });

function getNodeDistanceWithCamera(node) {
    // get the position of the node
    // @ts-ignore
    const obj = node;
    if (!obj.x || !obj.y || !obj.z) return 0;
    const nodePosition = new THREE.Vector3(obj.x, obj.y, obj.z);
    // then get the distance between the node and camera , console.log it
    return nodePosition.distanceTo(Graph.cameraPosition())
}

function handleChangeTextFormat() {
    Graph.nodeThreeObject(node => {
        // console.log('handling text format');
        let label = node.label
        let distance = getNodeDistanceWithCamera(node) ?? 1;
        let fontSize = controls['Font size'] + highlight;
        let labelFontSize= fontSize + (2000 / distance)
        const nodeEl = document.createElement('div');
        nodeEl.textContent = label;
        nodeEl.className = 'node-label';
        nodeEl.style.fontSize = labelFontSize + 'px;
        return new CSS2DObject(nodeEl);
    })
}

@EncompassingResidential
Copy link

Thank you Le Phi for replying. I got some un-referenced variables compiler errors:

import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';

// Is your Graph variable this:
const Graph = useRef();
// If not this then I need your Graph declaration / definition.

// I have this three library import, but you are referencing a THREE.Vector3()
// what is your THREE definition?
import { Group, Mesh, MeshBasicMaterial, SphereGeometry, Sprite, SpriteMaterial, Texture } from 'three';

// What is your highlight variable defined as ?
let fontSize = controls['Font size'] + highlight;

@EncompassingResidential
Copy link

Le, I figured it out how to show labels all the time. I don't need this HTML specific solution. Since I got this to work I'm not sure what advantage the HTML path is?

The clue was in https://github.com/vasturiano/3d-force-graph/blob/master/example/text-nodes/index.html

I needed to
1 - npm install three-spritetext
2 - import
import SpriteText from 'three-spritetext';

so all I needed was:

import React, { useCallback, useEffect, useRef } from 'react';
import ForceGraph3D from 'react-force-graph-3d';
import SpriteText from 'three-spritetext';

const fgRef = useRef();

const nodeThreeObject = areNodeLabelsOn ? node => {

const sprite = new SpriteText(node.id);
sprite.color = node.color;
sprite.textHeight = 8;
sprite.position.x = 0;
sprite.position.y = 0;
sprite.position.z = 8;
return sprite;

} : null;

return (


<ForceGraph3D
graphData={tree_18_GraphData}
ref={fgRef}
onNodeDragStart={node => logEntry('TreeForceGraphComponent', onNodeDragStart: ${node.id}) }
onNodeDragEnd={node => logEntry('TreeForceGraphComponent', onNodeDragEnd: ${node.id}) }
onNodeClick={handleNodeClick}
width={1000}
height={500}
backgroundColor="#B4B5C5"
nodeAutoColorBy={nodeAutoColorBy}
nodeThreeObject={areNodeLabelsOn ? nodeThreeObject : undefined }
nodeThreeObjectExtend={true} // false turns off nodes with nodeThreeObject
linkWidth={areNodeLabelsOn ? 2 : 1}
linkOpacity={areNodeLabelsOn ? 0.5 : 0.4}
linkColor={linkColorDetermine}
linkDirectionalParticles={6}
nodeLabel={areNodeLabelsOn ? undefined : nodes => ${nodes.id || 'SomE ThinG'} :: ${nodes.description || 'Empty Description'}}
/>

);

@longlp-sosene
Copy link

sprite.position.x = 0; sprite.position.y = 0; sprite.position.z = 8;
Yeah, but the problem is when you rotate, the label will be behind node :D

@longlp-sosene
Copy link

Thank you Le Phi for replying. I got some un-referenced variables compiler errors:

import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';

// Is your Graph variable this: const Graph = useRef(); // If not this then I need your Graph declaration / definition.

// I have this three library import, but you are referencing a THREE.Vector3() // what is your THREE definition? import { Group, Mesh, MeshBasicMaterial, SphereGeometry, Sprite, SpriteMaterial, Texture } from 'three';

// What is your highlight variable defined as ? let fontSize = controls['Font size'] + highlight;

const Graph = ForceGraph3D({ extraRenderers: [new CSS2DRenderer()] }) (container3DGraph) .backgroundColor('#1C1C1E') .enableNodeDrag(true) .enableNavigationControls(true) ....

import * as THREE from 'three';
just remove highlight

@dhilst
Copy link

dhilst commented Apr 18, 2024

By default, when I hover the mouse over a node I get the label displayed, is there any way to use the same mechanism to toggle the label visibility? I would like to toggle the node label visibility on click

Do I need SpriteText for that?

Great project BTW 🎉 !!

@longlp-sosene
Copy link

By default, when I hover the mouse over a node I get the label displayed, is there any way to use the same mechanism to toggle the label visibility? I would like to toggle the node label visibility on click

Do I need SpriteText for that?

Great project BTW 🎉 !!

You can handle it by using SpriteText or CSS2DObject with default opacity of text color = 0,
and using highlight when click on node onNodeClick or onNodeHove (see example for more detail) to set opacity = 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants