d3.js

D3JS: Unable to place a group of text elements over several rectangles


I'm trying to create a set of text elements and place them above various rect elements so that it looks as if they were inside. the thing is that I haven't been able to accomplish this simple task.

The text elements I need inside the column of rect's are the elements of the array: var dataDnt4 = [42,31,16,4,3,2,1];

I'll leave a running snippet so that you can see my progress so far.

var icon2 = '<g><path class="st0" d="M23.1,34.9c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5 C10.6,29.3,16.2,34.9,23.1,34.9L23.1,34.9z"/><path class="st0" d="M39.2,54.6c0.2,0,0.4,0,0.7,0c-3.7-3-6-7.5-6-12.6c0-1.2,0.1-2.4,0.4-3.6c-0.1,0-0.3,0-0.4,0H12.4 C5.5,38.5-0.1,44.1-0.1,51v17.9h23.3C24.1,60.8,30.9,54.6,39.2,54.6L39.2,54.6z"/><path class="st0" d="M76.8,34.9c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5  C64.2,29.3,69.9,34.9,76.8,34.9L76.8,34.9z"/><path class="st0" d="M87.5,38.5H66c-0.1,0-0.3,0-0.4,0c0.3,1.1,0.4,2.3,0.4,3.6c0,5.1-2.4,9.6-6,12.6c0.2,0,0.4,0,0.7,0    c8.3,0,15.1,6.3,16,14.3H100V51C100,44.1,94.4,38.5,87.5,38.5L87.5,38.5z"/><path class="st0" d="M49.9,54.6c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5    C37.4,49,43,54.6,49.9,54.6L49.9,54.6z"/><path class="st0" d="M60.7,58.1H39.2c-6.9,0-12.5,5.6-12.5,12.5v17.9h46.5V70.7C73.2,63.7,67.6,58.1,60.7,58.1L60.7,58.1z"/></g>'

var dataDnt4 = [42, 31, 16, 4, 3, 2, 1];

var distanciaRect = [25, 50, 75, 100, 125, 150, 175]

var width = 512,

  height = 600

radius = (Math.min(width, height) / 2.5) - 60;

var sym = "%"

var legendTextArr = ["alpha", "beta", "Gamma", "vvv", "www", "xxx", "yyy", "zzz"]


var color_rect = ["#00338D", "#BC204B", "#0091DA", "#eaaa00", "#005eb8", "#f68d2e", "#009444", "#470a68"]

var pie = d3.pie()
  .value(function(d) {
    return d
  })(dataDnt4);

var arc = d3.arc()
  .outerRadius(radius - 10)
  .innerRadius(radius - (radius / 2.4));

var labelArc = d3.arc()
  .outerRadius(radius - 35)
  .innerRadius(radius - 35);

var svg = d3.select("#chartdiv")
  .append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform", "translate(" + width / 2 + "," + height / 2.4 + ")");



var title = svg.append("text")
  .attr("font-weight", "bold")
  .attr("class", "title1")
  .html("title 1")
  .attr("transform", function() {
    return "translate(" + (-184) + "," + (-180) + ")"
  })

var title = svg.append("text")
  .attr("font-weight", "bold")
  .attr("class", "title2")
  .html("title 2")
  .attr("transform", function() {
    return "translate(" + (-184) + "," + (-160) + ")"
  })

var legendG = svg.append("g")
  .attr("class", "legendG")
  .attr("transform", function() {
    return "translate(" + (-60) + "," + (155) + ")"
  })

var legendG = svg.append("g")
  .attr("class", "legendG")
  .attr("transform", function() {
    return "translate(" + (-60) + "," + (155) + ")"
  })



var legendText = legendG.selectAll("text")
  .data(distanciaRect)
  .enter()
  .append("text")
  .attr("x", -80)
  .attr("y", function(d, i) {
    return d + 10
  })
  .data(legendTextArr)
  .html(function(d) {
    return d
  })


var legends = legendG.selectAll(".rect")
  .data(distanciaRect)
  .enter()
  .append("rect")
  .attr("x", -120)
  .attr("y", function(d, i) {
    return d
  })
  .attr("width", 25)
  .attr("height", 17)
  .attr("class", "icon1")
  .data(color_rect)
  .attr("fill", function(d, i) {
    return d
  })


var g = svg.selectAll("arc")
  .data(pie)
  .enter().append("g")
  .attr("class", "arc");

function easeInverse(ease) {
  return function(e) {
    var min = 0,
      max = 1;
    while (max - min > 1e-3) {
      var mid = (max + min) * 0.5;
      emid = ease(mid);
      if (emid > e) {
        max = mid;
      } else {
        min = mid;
      }
    }
    return max;
  }
}

var inverseCubic = easeInverse(d3.easeCubic);
var oneOver2Pi = 1.0 / (2 * Math.PI);
var total_msec = 2000;



g.append("path")
  .attr("d", arc)
  .attr("transform", function() {
    return "translate(" + (-16) + "," + (0) + ")"
  })
  .style("fill", function(d, i) {
    return color_rect[i];
  })
  .transition()
  .ease(d3.easeLinear)
  .delay(function(d) {
    return total_msec * inverseCubic(d.startAngle * oneOver2Pi);
  })
  .duration(function(d) {
    return total_msec * (inverseCubic(d.endAngle * oneOver2Pi) - inverseCubic(d.startAngle * oneOver2Pi));
  })
  .attrTween("d", arcTween);

function arcTween(d) {
  var i = d3.interpolate(inverseCubic(d.startAngle * oneOver2Pi), inverseCubic(d.endAngle * oneOver2Pi));
  return function(t) {
    d.endAngle = 2 * Math.PI * d3.easeCubic(i(t));
    return arc(d);
  }
}

svg.append("g")
  .attr("class", "icon2")
  .html(icon2);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="chartdiv"></div>


Solution

  • There are numerous ways to do this, so here is one possible way. I would group together the three pieces of the legend--the rectangle, the key text, and the text over the rectangle--in a g element and bind dataDnt4 to each item. The rectangle colour and the legend text can be retrieved by position, i.e. the first dataDnt4 item corresponds to color_rect[0] and legendTextArr[0], the second to color_rect[1] and legendTextArr[1], etc.

    I've cut out the code that is not relevant to the positioning of the legend items -- you can restore that in your script.

    var width = 512,
    
    height = 600,
    
    radius = (Math.min(width, height) / 2.5) - 60;
    
    var sym = "%"
    
    var legendTextArr = ["alpha", "beta", "Gamma", "vvv", "www", "xxx", "yyy", "zzz"]
    var dataDnt4 =      [42, 31, 16, 4, 3, 2, 1];
    
    var color_rect =    ["#00338D", "#BC204B", "#0091DA", "#eaaa00", "#005eb8", "#f68d2e", "#009444", "#470a68"]
    
    var svg = d3.select("#chartdiv")
      .append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
     .attr("transform", "translate(" + width / 2 + "," + height / 2.4 + ")");
    
    var title = svg.append("text")
      .attr("font-weight", "bold")
      .attr("class", "title1")
      .html("scroll down!")
      .attr("transform", function() {
        return "translate(" + -184 + "," + -180 + ")"
      })
    
    var legendG = svg.append("g")
      .attr("class", "legendG")
      .attr("transform", function() {
      // this moves the whole legend box
      // you can change this to whatever transformation is appropriate for your chart
        return "translate(" + -((width / 2)-40) + "," + 120 + ")"
      })
    
    // group each legend item in a `g` element
    var legendText = legendG.selectAll("g")
      .data(dataDnt4)
      .enter()
      .append('g')
        .attr('transform', function(d, i) {
       // instead of having a hard-coded list of multiples of 25, you can multiply
       // the array index, `i`, by 25 to get the correct position
          return 'translate(0,' + (i*25) + ')';
        });
    
     legendText.append("rect")
      .attr("width", 25)
      .attr("height", 17)
      .attr("class", "icon1")
      .attr("fill", function(d, i) {
        return color_rect[i];
      })
    
     // the text "in" the rectangle
     // use 'text-anchor: middle' and an x offset of 12.5 (rectangle width / 2)
     // to centre the labels
     // change the `y` attribute to alter the vertical positioning
     legendText.append("text")
      .attr("x", 12.5)
      .attr("y", 13)
      .attr('text-anchor', 'middle')
      .attr('fill', 'white')
      // d is the items in dataDnt4
      .text(function(d) {
        return d;
      })
    
     // legend text items
     legendText.append("text")
      .attr("x", 40)
      .attr("y", 13)
      // take legendTextArr item in position i
      .text(function(d,i) {
        return legendTextArr[i];
      })
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <div id="chartdiv"></div>

    You have some errors in your code (e.g. you declare the variables legendG and title twice), and it would probably be helpful for you to run your code through a code linter so you can see the problems that you might not pick up by eye.