Kyle Underhill
Kyle Underhill

Reputation: 109

d3.js center image in bubble chart

I am trying to center my append images to the circle but I cannot figure out how to set up my x and y since the size of the circle is dynamic and changes with the width of the svg.

What is the formula for finding the x and y coordinates given the current radius of the circle?

  vis
    .enter()
    .append("svg:image")
    .attr("transform", d => "translate(" + d.x + "," + d.y + ")")
    .attr("xlink:href", function(d) {
      return d.img;
    })
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", d => d.r / 1.5);

(function() {
  var json = {
    call_data: [
      [
        "Lifestyle",
        1,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/5bb3ce2f801fbc657f83dd57_pp-lifestyle(white).svg"
      ],
      [
        "Sports",
        10,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/5c9131911ad86f445cb5abc7_pp-sport(white).svg"
      ],
      [
        "Environment",
        8,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f2a4bef42fff000159ba7a_pp-environ(white).svg"
      ],
      [
        "Medical",
        6,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f2a4dc831e8500015fda53_pp-health(white).svg"
      ],
      [
        "Food",
        4,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f8c2cc78cc2d0001fd4a7e_pp-food(white).svg"
      ]
    ]
  };

  var svg = d3
    .select(".bubble_chart")
    .append("svg")
    /*
    .attr("width", diameter)
    .attr("height", diameter);
*/
    .attr("preserveAspectRatio", "xMinYMin meet")
    .attr("viewBox", "0 0 600 400")
    //class to make it responsive
    .classed("svg-content-responsive", true);

  var bubble = d3.layout
    .pack()
    .size([600, 400])
    .value(function(d) {
      return d.size;
    })
    .padding(2);

  // generate data with calculated layout values
  var nodes = bubble.nodes(processData(json)).filter(function(d) {
    return !d.children;
  }); // filter out the outer bubble

  var vis = svg.selectAll("circle").data(nodes, function(d, i) {
    return d.name + i;
  });

  vis
    .enter()
    .append("circle")
    .attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    })
    .attr("class", function(d) {
      return d.className;
    })
    .attr("r", 0)
    .transition()
    .duration(1000)
    .attr("r", function(d) {
      return d.r;
    });

  vis
    .enter()
    .append("svg:image")
    .attr("transform", d => "translate(" + d.x + "," + d.y + ")")
    .attr("xlink:href", function(d) {
      return d.img;
    })
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", d => d.r / 1.5);

  function processData(data) {
    var obj = data.call_data;

    var newDataSet = [];

    for (var prop in obj) {
      newDataSet.push({
        name: obj[prop][0],
        className: obj[prop][0].toLowerCase(),
        size: obj[prop][1],
        img: obj[prop][2]
      });
    }
    return {
      children: newDataSet
    };
  }
})();
.lifestyle {
  fill: #89BED3;
}

.sports {
  fill: #2A83D4;
}

.environment {
  fill: #6CC070;
}

.food {
  fill: #665C9E;
}

.medical {
  fill: #C13E40;
}

.bubble_chart {
  border: 2px solid red;
  display: inline-block;
  position: absolute;
  width: 100%;
  padding-bottom: 100%;
  /* aspect ratio */
  vertical-align: top;
  overflow: hidden;
}

.svg-content-responsive {
  border: 3px solid green;
  display: inline-block;
  position: absolute;
  top: 0;
  left: 0;
}
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.5/d3.js"></script>
<div class="container">
  <div class="bubble_chart"></div>
</div>

Upvotes: 0

Views: 940

Answers (1)

Shashank
Shashank

Reputation: 5670

As you're already transforming the images using translate(" + d.x + "," + d.y + ") which results in the images having the start point as the center of the corresponding circles, you just have to offset the images by their respective height and widths.

i.e. applying the following x, y attributes:

.attr('x', d => -(d.r/1.5)/2)
.attr('y', d => -(d.r/1.5)/2)

will center the images where (d.r/1.5) is the width/height of the image.

Code snippet:

(function() {
  var json = {
    call_data: [
      [
        "Lifestyle",
        1,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/5bb3ce2f801fbc657f83dd57_pp-lifestyle(white).svg"
      ],
      [
        "Sports",
        10,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/5c9131911ad86f445cb5abc7_pp-sport(white).svg"
      ],
      [
        "Environment",
        8,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f2a4bef42fff000159ba7a_pp-environ(white).svg"
      ],
      [
        "Medical",
        6,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f2a4dc831e8500015fda53_pp-health(white).svg"
      ],
      [
        "Food",
        4,
        "https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f8c2cc78cc2d0001fd4a7e_pp-food(white).svg"
      ]
    ]
  };

  var svg = d3
    .select(".bubble_chart")
    .append("svg")
    /*
    .attr("width", diameter)
    .attr("height", diameter);
*/
    .attr("preserveAspectRatio", "xMinYMin meet")
    .attr("viewBox", "0 0 600 400")
    //class to make it responsive
    .classed("svg-content-responsive", true);

  var bubble = d3.layout
    .pack()
    .size([600, 400])
    .value(function(d) {
      return d.size;
    })
    .padding(2);

  // generate data with calculated layout values
  var nodes = bubble.nodes(processData(json)).filter(function(d) {
    return !d.children;
  }); // filter out the outer bubble

  var vis = svg.selectAll("circle").data(nodes, function(d, i) {
    return d.name + i;
  });

  vis
    .enter()
    .append("circle")
    .attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    })
    .attr("class", function(d) {
      return d.className;
    })
    .attr("r", 0)
    .transition()
    .duration(1000)
    .attr("r", function(d) {
      return d.r;
    });

  vis
    .enter()
    .append("svg:image").style('opacity', 0)
    .attr("transform", d => "translate(" + d.x + "," + d.y + ")")
    .attr('x', d => -(d.r/1.5)/2)
    .attr('y', d => -(d.r/1.5)/2)
    .attr("xlink:href", function(d) {
      return d.img;
    })
    .attr("width", d => d.r / 1.5).transition().duration(1000).style('opacity', 1);

  function processData(data) {
    var obj = data.call_data;

    var newDataSet = [];

    for (var prop in obj) {
      newDataSet.push({
        name: obj[prop][0],
        className: obj[prop][0].toLowerCase(),
        size: obj[prop][1],
        img: obj[prop][2]
      });
    }
    return {
      children: newDataSet
    };
  }
})();
.lifestyle {
  fill: #89BED3;
}

.sports {
  fill: #2A83D4;
}

.environment {
  fill: #6CC070;
}

.food {
  fill: #665C9E;
}

.medical {
  fill: #C13E40;
}

.bubble_chart {
  border: 2px solid red;
  display: inline-block;
  position: absolute;
  width: 100%;
  padding-bottom: 100%;
  /* aspect ratio */
  vertical-align: top;
  overflow: hidden;
}

.svg-content-responsive {
  border: 3px solid green;
  display: inline-block;
  position: absolute;
  top: 0;
  left: 0;
}
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.5/d3.js"></script>
<div class="container">
  <div class="bubble_chart"></div>
</div>

Also, I've added an additional transition to the images' opacity to match with the circles visibility - .transition().duration(1000).style('opacity', 1). Hope this helps.

Upvotes: 1

Related Questions