vasturiano / react-force-graph

React component for 2D, 3D, VR and AR force directed graphs
https://vasturiano.github.io/react-force-graph/example/large-graph/
MIT License
2.21k stars 284 forks source link

How to have custom svg's in place of nodes for 2D graph #288

Open Chinmay-k96 opened 3 years ago

Chinmay-k96 commented 3 years ago

I want to have custom svg's for 2D graph. having images can be done in 3D using node three object. Can i do the same with nodeCanvas object ?.... how can i do it

vasturiano commented 3 years ago

https://github.com/vasturiano/force-graph/blob/6c3f645d913d33e47cdc4a0737ba2b1263c6ccf7/example/img-nodes/index.html#L32-L40

From this force-graph example.

Chinmay-k96 commented 3 years ago

Below is my entire code........ now i want to import a local svg file and then replace it with the existing nodes based on some condition like if (node.value > 10) then svg = high.svg so final ly when i render, the nodes should have svg' s according to what is assigned to them.......This usecase is different from what your example shows.........also this is a complete react app and lot of external data is used and to be displayed......................how can i achive this

`import React, {useEffect, useState, useRef, useCallback} from 'react'; import './App.css'; import ForceGraph2D from 'react-force-graph-2d'; import neo4j from 'neo4j-driver'

// Usage:

const CypherViz =()=> { const [query, setQuery] = useState( MATCH (n)-[:INTERACTS1]->(m) RETURN n.name as source, m.name as target LIMIT ${neo4j.int(50)})

    const [data, setData] = useState({nodes:[],links:[]})
    const [pause, setPause] = useState(false)

  function getRandomColor() {
      var letters = '0123456789ABCDEF';
      var color = '#';
      for (var i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
      }
      return color;
    }

    function hexToRgbA(hex){
      //console.log(hex)
      var c;
      if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){
          c= hex.substring(1).split('');
          if(c.length== 3){
              c= [c[0], c[0], c[1], c[1], c[2], c[2]];
          }
          c= '0x'+c.join('');
          return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+',0.6)';
      }
      throw new Error('Bad Hex');
  }

const loadData = async () => {
  let driver = neo4j.driver("bolt://demo.neo4jlabs.com", neo4j.auth.basic("gameofthrones", "gameofthrones"),{encrypted: true});
  let session = await driver.session({database:"gameofthrones"});
  let res = await session.run(query);
 // console.log(res);
  let nodes = new Set();
  let links = res.records.map(r => {
    let source = r.get("source");
    let target = r.get("target");
    nodes.add(source);
    nodes.add(target);
    return {source, target}});
    session.close();
  nodes = Array.from(nodes).map(name => {return {name}});
  nodes.map(n=>{
    n.color = getRandomColor();
    n.size = Math.floor((Math.random() * 10) + 1);
  })

  links.map(l =>{
    l.width = Math.floor((Math.random() * 10) + 1);
    l.color = hexToRgbA(getRandomColor())
  })
  setData({nodes:nodes, links:links});
}

useEffect(()=>{
  loadData();
},[])

  return (
    <div className='back'>
      <button onClick={()=> setPause(prev =>!prev)}>Pause</button>
      <ForceGraph2D 
        graphData={data}
        nodeId="name"
        pauseAnimation={pause ? true : false}
        linkDirectionalArrowRelPos={1} 
        linkDirectionalArrowLength={7}
        nodeVal={(node)=> node.size}
        linkWidth= {(a)=> pause ? a.width : 3}
        linkColor={(l) => l.color}
        nodeColor={(node)=>{ return node.color}}
        onNodeDragEnd={node => {
          node.fx = node.x;
          node.fy = node.y;
          node.fz = node.z;
        }}
        nodeCanvasObjectMode={() => 'after'}
        nodeCanvasObject={(node, ctx, globalScale) => {
          console.log('**ctx',ctx)
          const label = node.name;
          const fontSize = 12 / globalScale;
          ctx.font = `${fontSize}px Sans-Serif`;
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.fillStyle = 'white'; //node.color;
          ctx.fillText(label, node.x, node.y + 15);
        }}
        //onNodeClick={handleClick}
      />
    </div>
  );  

}

export default CypherViz;`

vasturiano commented 3 years ago

You should just need to update the functionality in your nodeCanvasObject prop.

Chinmay-k96 commented 3 years ago

i am not getting where will i replace the node with an svg......Is there somthing like ctxImage = high.svg.... what is the command ?

Chinmay-k96 commented 3 years ago

also how to import an svg file in a local folder in the javascript(react) file and store it in a variable to use it in the nodeCanvasObject

Chinmay-k96 commented 3 years ago

Can you please use the above code and help me with the code to replace the default node with attached svg file and reproduce the same graph. I have attached a text document because svg file is not allowed to upload here. please change the format to svg and help me with this.......Please please help here it will be a big help. I will be grateful to you forever

healthy.txt

vasturiano commented 3 years ago

@Chinmay-k96 I would recommend making a reduced example on https://codesandbox.io/ that focuses just on the specific issue that you're having and excludes all the other non-relevant logic from the rest of your app. The more focused the example is, the easier you make for someone to help with debugging the issue.

Chinmay-k96 commented 3 years ago

https://codesandbox.io/s/lopxe?file=/src/App.js ----> codesandbox link,

healthy.txt Please help me with the code to replace the default node with attached svg file and reproduce the same graph.

I have attached a text document because svg file is not allowed to upload here. please change the format to svg and help me with this

feznyng commented 3 years ago

Hi @Chinmay-k96, I was able to get a custom SVG icon rendered as my nodes. nodeCanvasObject={(node, ctx, globalScale) => { var img = new Image(); img.src = node.src ctx.drawImage(img, (node.x - fontSize) - 5, (node.y - fontSize) - 5, node.size / 2, node.size / 2); }}

I set each node.src to the url that I wanted for that node, then in my nodeCanvasObject object I'm creating an image using that src and then finally calling drawImage with the requisite coordinates to draw it. As shown in this example (https://github.com/vasturiano/react-force-graph/blob/master/example/img-nodes/index.html), you can perform these processing steps and store the image directly within the node before passing to the graph.

skhaide1 commented 3 years ago

@vasturiano any update on this