1

I am new to D3 and was playing with D3 network diagram. I can successfully create a network diagram and make it draggable but I was not able to add labels to nodes. I searched for answers and I think my code should work. Here is the code.

Note: The layout of the network did not properly render here (but the nodes can be dragged to see the proper layout )but it renders properly (without the label) when it ran on full web page.

Please point out where did I do wrong.

Thanks a lot,

Alex

var data = {
  "name": "A1",
  "children": [{
      "name": "B1",
      "children": [{
          "name": "C1",
          "value": 100
        },
        {
          "name": "C2",
          "value": 300
        },
        {
          "name": "C3",
          "value": 200
        }
      ]
    },
    {
      "name": "B2",
      "value": 200
    }
  ]
};

var root = d3.hierarchy(data);

function dragged(d) {
  d.x = d3.event.x, d.y = d3.event.y;
  d3.select(this).attr("cx", d.x).attr("cy", d.y);
  link.filter(function(l) {
    return l.source === d;
  }).attr("x1", d.x).attr("y1", d.y);
  link.filter(function(l) {
    return l.target === d;
  }).attr("x2", d.x).attr("y2", d.y);
}

// Nodes
var node = d3.select('#network g.nodes')
  .selectAll('circle.node')
  .data(root.descendants())
  .enter()
  .append('circle')
  .classed('node', true)
  .attr('cx', function(d) {
    return d.x;
  })
  .attr('cy', function(d) {
    return d.y;
  })
  .attr('r', 4)
  .each(function(d) {
    console.log(d);
    d3.select(this)
      .append('text')
      .text(d.data.name);
  })
  .call(d3.drag().on("drag", dragged));


// Links
var link = d3.select('#network g.links')
  .selectAll('line.link')
  .data(root.links())
  .enter()
  .append('line')
  .classed('link', true)
  .attr('x1', function(d) {
    return d.source.x;
  })
  .attr('y1', function(d) {
    return d.source.y;
  })
  .attr('x2', function(d) {
    return d.target.x;
  })
  .attr('y2', function(d) {
    return d.target.y;
  });

/* 
  node.append("text")
     .attr("dx", 12)
     .attr("dy", ".35em")
     .text(function(d) { 
      console.log(d); 
      return d.data.name;
});
*/
circle {
  cursor: pointer;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1px;
}

svg text {
  color: #000;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.2/d3.min.js"></script>
<svg id="network" width="400" height="220">
  <g transform="translate(20, 5)">
    <g class="links"></g>
    <g class="nodes"></g>
  </g>
</svg>
user1941319
  • 375
  • 3
  • 19
  • You are appending text to circles, this won't work. You need to append the text to some other parent, as in [here](https://stackoverflow.com/a/37640083/7106086), [here](https://stackoverflow.com/a/13369282/7106086) or [here](https://stackoverflow.com/a/18175103/7106086). Those require positioning of text and circle every tick, I prefer to enter one `g` for each node, position that each tick, and add a circle and text to that `g` as in [here](https://stackoverflow.com/a/49884100/7106086). – Andrew Reid May 24 '20 at 17:45
  • @AndrewReid Thanks very much for the reply. I understand now! Now I need to make the text label drags together with the label. May need helps later :) – user1941319 May 24 '20 at 18:47

2 Answers2

2

Here is sample of what @AndrewReid suggested. The text and circle are grouped in and <g> element. The function dragged updates the transform attribute of the group.

var data = {
  "name": "A1",
  "children": [
    {
      "name": "B1",
      "children": [
        {
          "name": "C1",
          "value": 100
        },
        {
          "name": "C2",
          "value": 300
        },
        {
          "name": "C3",
          "value": 200
        }
      ]
    },
    {
      "name": "B2",
      "value": 200
    }
  ]
};

var root = d3.hierarchy(data);

function dragged(d) {
  // Get coords relative to svg
  [d.x, d.y] = d3.mouse(this.parentNode.parentNode);
  d3.select(this.parentNode).attr("transform", 'translate(' + d.x + ',' + d.y + ')');

  link.filter(function(l) {
    return l.source === d;
  }).attr("x1", d.x).attr("y1", d.y);
  link.filter(function(l) {
    return l.target === d;
  }).attr("x2", d.x).attr("y2", d.y);
}

// Nodes
var nodeg = d3.select('#network g.nodes')
  .selectAll('circle.node')
  .data(root.descendants())
  .enter()
  .append('g');

nodeg.append('circle')
  .classed('node', true)
  .attr('cx', function(d) {
    return d.x;
  })
  .attr('cy', function(d) {
    return d.y;
  })
  .attr('r', 4)
  .each(function(d) {
    d3.select(this)
      .append('text')
      .text(d.data.name);
  })
  .call(d3.drag().on("drag", dragged));

// Links
var link = d3.select('#network g.links')
  .selectAll('line.link')
  .data(root.links())
  .enter()
  .append('line')
  .classed('link', true)
  .attr('x1', function(d) {
    return d.source.x;
  })
  .attr('y1', function(d) {
    return d.source.y;
  })
  .attr('x2', function(d) {
    return d.target.x;
  })
  .attr('y2', function(d) {
    return d.target.y;
  });

var texts = nodeg.append("text")
  .attr("dx", 12)
  .attr("dy", ".35em")
  .text(function(d) {
    //console.log(d); 
    return d.data.name;
  });
circle {
  cursor: pointer;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1px;
}

svg text {
  color: #000;
  cursor: pointer;
}
<svg id="network" width="400" height="220">
  <g transform="translate(20, 5)">
    <g class="links"></g>
    <g class="nodes"></g>
  </g>
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.2/d3.min.js"></script>
Marcelo
  • 4,234
  • 1
  • 18
  • 18
  • Thanks for the reply. I will try this code and compare the one that I came up with. Good to know additional way to achieve the same thing. – user1941319 May 24 '20 at 19:41
0

Following the comments from @AndrewReid, I modified the code as follows and it worked fine. Basically I need to break the chaining so that different elements append to proper parent.

var node = d3.select('#network g.nodes')
  .selectAll('circle.node')
  .data(root.descendants())
  .enter()
  .append('g');

var circle = node.append('circle')
.classed('node', true)
.attr('cx', function(d) {return d.x;})
.attr('cy', function(d) {return d.y;})
.attr('r', 6)
.call(d3.drag().on("drag", dragged))
;


// Links
var link = d3.select('#network g.links')
.selectAll('line.link')
.data(root.links())
.enter()
.append('line')
.classed('link', true)
.attr('x1', function(d) {return d.source.x;})
.attr('y1', function(d) {return d.source.y;})
.attr('x2', function(d) {return d.target.x;})
.attr('y2', function(d) {return d.target.y;});


var text= node.append("text")
.attr('x', function(d) {return d.x;})
.attr('y', function(d) {return d.y;})
.attr("dx", 12)
.attr("dy", ".25em")
.text(function(d) {   
    return d.data.name;
});

function dragged(d) {
    d.x = d3.event.x, d.y = d3.event.y;
    d3.select(this).attr("cx", d.x).attr("cy", d.y);
    text
     .attr('x', function(d) {return d.x;})
     .attr('y', function(d) {return d.y;})
    link.filter(function(l) {return l.source === d; }).attr("x1", 
      d.x).attr("y1", d.y);
    link.filter(function(l) { return l.target === d; }).attr("x2", 
      d.x).attr("y2", d.y);
}
user1941319
  • 375
  • 3
  • 19