lucky1928
lucky1928

Reputation: 8905

d3.js - chain box with arrow lines

I would like to draw few text box and chain it with arrow lines. I use below code to draw the text box few issues there:

  1. text box is black and no text show there.
  2. One box is missing, it should be 5 box but only 4 can be seen.
  3. how can I add a arrow line to connect each other!

test()
function test() {
  var data = ["a","b","c","d","e"]
  
  width = 800
  height = 600
  margin = 10
  
  //var svg = d3.select("svg");
  var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom);
  
  svg.style("border","5px solid red");

  svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  
  var group = svg.selectAll('g')
      .data(data).enter()
      .append('g')
      .attr('transform',function(d,i) {
        //console.log(i,d);
        return 'translate('+(100*i)+',0)';
      });
  
  var box = group.selectAll('rect')
      .data(function(d) {
        return d;
      });
  
  box.enter()
    .append("rect")
    .attr("width", 30)
    .attr("height", 30)
    .attr('font-size',2)
    .attr("x", function(d, i) { 
    //console.log(d);
    return 60 + 2*d; 
  })  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

Upvotes: 1

Views: 265

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38211

  1. text box is black and no text shown.

You aren't appending any text. Text also can't be appended to a rectangle, so there is no need to apply font properties to a rectangle. Text can be appended to a g though. So we can use a parent g to hold both rectangle and text. Something like:

group.append("rect")...

group.append("text")...

The boxes are black because you haven't applied a fill. The default fill is black.

  1. One box is missing, it should be 5 box but only 4 can be seen.

This is because when you enter the parent g elements, you select all g elements. This includes the one you've already appended (svg.append("g")). The enter selection is intended to create elements such that every item in the data array is paired with an element in the DOM. Since you already have a g in your selection, the enter selection will only create 4 new ones (representing data array items with indexes 1-4 but not 0).

Instead of selectAll("g") you could specify a class name or, in the event you simply want to enter everything and there isn't a need to ever update a selection: selectAll(null). The latter option will always return an empty selection, which will result in the enter selection containing one element per item in the data array.

Note, that the parent's datum is passed to appended children automagically, there is no need to use the .data method to pass this onward unless you are handling nested data.

Here's a snippet addressing issues in one and two:

test()
function test() {
  var data = ["a","b","c","d","e"]
  
  width = 800
  height = 600
  margin = 10

  var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
    .style("border","5px solid red");
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  
  var group = svg.selectAll(null)
      .data(data).enter()
      .append('g')
      .attr('transform',function(d,i) {
        return 'translate('+(40*i)+',0)';
      });
  
  group
    .append("rect")
    .attr("width", 30)
    .attr("height", 30)
    .attr("fill","yellow")

  group.append("text")
    .text(function(d) { return d; })
    .attr("y", 20)
    .attr("x", 15)
    .attr("text-anchor","middle");

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

I also changed svg to refer to the parent g, the one with the margin applied. Before the g with the margin remained unused, along with the margin. I also modified the spacing to keep everything in view.

  1. how can I add a arrow line to connect each other!

This can be done in many ways and really is a separate issue from the others, so I'll only quickly demonstrate one of many options. I'll modify your data structure a bit so that each datum has positional data and then add arrows using SVG markers:

test()
function test() {
  var data = [{name:"a"},{name:"b"},{name:"c"},{name:"d"},{name:"e"}]
  
  width = 800
  height = 600
  margin = 10

  var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .style("border","5px solid red")
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
    
  svg.append("defs")
    .append("marker")
    .attr("id","pointer")
    .attr("markerWidth", 10)
    .attr("markerHeight", 10)
    .attr("orient","auto")
    .attr("refY", 5)
    .append("path")
    .attr("d", "M 0 0 L 10 5 L 0 10 z")
    
  
  var group = svg.selectAll(null)
      .data(data).enter()
      .append('g')
      .attr('transform',function(d,i) {
        d.x = 40*i+15, d.y=30;
        return 'translate('+(40*i)+',0)';
      });
  
  group
    .append("rect")
    .attr("width", 30)
    .attr("height", 30)
    .attr("fill","yellow")

  group.append("text")
    .text(function(d) { return d.name; })
    .attr("y", 20)
    .attr("x", 15)
    .attr("text-anchor","middle");
    
    
   links = [
     {source: data[0], target: data[1]},
     {source: data[0], target: data[2]}
   ]
   
   svg.selectAll(null)
     .data(links)
     .enter()
     .append("path")
     .attr("d", function(d) {
        var midX = (d.source.x+d.target.x)/2;
        return "M"+d.source.x+" "+d.source.y+"Q"+midX+" "+200+" "+d.target.x+" "+(d.target.y+6);
     })
     .attr("fill","none")
     .attr("stroke","black")
     .attr("stroke-width",1)
     .attr("marker-end","url(#pointer)");
  
  

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

Upvotes: 2

Related Questions