d3.js

d3 - old topic - custom image on node


It is an old topic and read many questions regarding this issue but I still can't figure it out what the error can be. My only excuse is that I am pretty new with d3.

So I'd like a custom image or svg on nodes but all I can achieve is the built-in symbols (circle).

enter image description here

The code is

            const simulation = d3.forceSimulation(nodes)
                        .force('link', d3.forceLink(links).id((l: any) => l.id))
                        .force('charge', d3.forceManyBody())
                        .force('center', d3.forceCenter(width / 2, height / 2))
                        .on('tick', ticked)

            const svg = d3
                        .select(graphRef.current)
                        .attr("preserveAspectRatio", "xMinYMin meet")
                        .attr("viewBox", "0 0 960 500")

            const link = svg
                        .append('g')
                        .attr('stroke', '#999')
                        .attr('stroke-opacity', 0.6)                        
                        .selectAll()
                        .data(links)
                        .join('line')
                        .attr('stroke-width', (d: any) => Math.sqrt(d.value))    

                        
            const node = svg
                        .append('g')
                        .attr('stroke', '#fff')
                        .attr('stroke-width', 1.5)
                        .selectAll()
                        .data(nodes)
                        .join('circle')
                        .attr('r', 5)
                        .attr('fill', 'green')

            node.call(d3.drag()
                        .on('start', dragStarted)
                        .on('drag', dragged)
                        .on('end', dragEnded))

I tried to mimic the code from https://gist.github.com/mbostock/950642 but it doesn't show anything, the nodes disappear, only the links can be seen (well I think the icon is displayed on the top left corner though)

enter image description here

            const node = svg
                        .append('g')
                        .attr('stroke', '#fff')
                        .attr('stroke-width', 1.5)
                        .selectAll()
                        .data(nodes)

                        .enter()
                        .append("g")
                        .append("image")
                        .attr("xlink:href", "https://github.com/favicon.ico")
                        .attr("width", 16)
                        .attr("height", 16);

                        // .join('circle')
                        // .attr('r', 5)
                        // .attr('fill', 'green')

I'd appreciate any hint please.
Thank you!

//////////////// UPDATE //////////////////

            const ticked = (): void => {
                link.attr("x1", (d: any) => d.source.x)
                    .attr("y1", (d: any) => d.source.y)
                    .attr("x2", (d: any) => d.target.x)
                    .attr("y2", (d: any) => d.target.y);
    
                node.attr("cx", (d: any) => d.x)
                    .attr("cy", (d: any) => d.y);
            }

Solution

  • You didn't post your ticked function which is most relevant bit to your question. So, without seeing it, I'm guessing that you probably failed to update it to position the g holding your image and instead it is still trying to position a circle with cx and cy properties that don't exist on a g/image. The update would look something like this:

    function ticked() {
       link
        .attr('x1', (d) => d.source.x)
        .attr('y1', (d) => d.source.y)
        .attr('x2', (d) => d.target.x)
        .attr('y2', (d) => d.target.y);
    
      node.attr('transform', (d) => `translate(${d.x - 8},${d.y - 8})`);
    }
    

    Notice how it is now being positioned with a transform.

    Running code:

    <!DOCTYPE html>
    
    <html>
      <head>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js"></script>
      </head>
    
      <body>
        <svg id="networkGraph" width="960" height="500"></svg>
        <script>
          const width = 300,
            height = 150;
    
          const nodes = [{ id: 'Alice' }, { id: 'Bob' }, { id: 'Carol' }];
    
          const links = [
            { source: 'Alice', target: 'Bob' },
            { source: 'Bob', target: 'Carol' },
          ];
    
          const simulation = d3
            .forceSimulation(nodes)
            .force(
              'link',
              d3.forceLink(links).id((l) => l.id)
            )
            .force('charge', d3.forceManyBody())
            .force('center', d3.forceCenter(width / 2, height / 2))
            .on('tick', ticked);
    
          const svg = d3
            //.select(graphRef.current)
            .select('#networkGraph')
            .attr('preserveAspectRatio', 'xMinYMin meet')
            .attr('viewBox', '0 0 960 500');
    
          const link = svg
            .append('g')
            .attr('stroke', '#999')
            .attr('stroke-opacity', 0.6)
            .selectAll()
            .data(links)
            .join('line')
            .attr('stroke-width', (d) => Math.sqrt(d.value));
    
          const node = svg
            .append('g')
            .selectAll()
            .data(nodes)
            .enter()
            .append('g');
            
          node
            .append('image')
            .attr('xlink:href', 'https://github.com/favicon.ico')
            .attr('width', 16)
            .attr('height', 16);
    
          /*
          node.call(
            d3
              .drag()
              .on('start', dragStarted)
              .on('drag', dragged)
              .on('end', dragEnded)
          );
          */
    
          function ticked() {
            link
              .attr('x1', (d) => d.source.x)
              .attr('y1', (d) => d.source.y)
              .attr('x2', (d) => d.target.x)
              .attr('y2', (d) => d.target.y);
    
            node.attr('transform', (d) => `translate(${d.x - 8},${d.y - 8})`);
          }
        </script>
      </body>
    </html>