lbrutti
lbrutti

Reputation: 1183

d3js : how to put circles at the end of an arc

I'm trying to create a donut chart in d3js where each arc has a circle at its end. Circle's edge must fit on arc's one.

I tried both by appending a circle and a circle wrapped in marker but with no succes.

Trying to append a marker seems to be the closest solution to the desired one but I can't help the marker oveflowing the arc edges.

Code:

var data = [
  {
    name: "punti",
    count: 3,
    color: "#fff000"
  },
  {
    name: "max",
    count: 7,
    color: "#f8b70a"
  }
];
var totalCount = data.reduce((acc, el) => el.count + acc, 0);
var image_width = 32;
var image_height = 32;
var width = 540,
  height = 540,
  radius = 200,
  outerRadius = radius - 10,
  innerRadius = 100;
var cornerRadius = innerRadius;
var markerRadius = (outerRadius - innerRadius) / 2;
var arc = d3
  .arc()
  .outerRadius(outerRadius)
  .innerRadius(innerRadius)
  .cornerRadius(cornerRadius);

var pie = d3
  .pie()
  .sort(null)
  .value(function(d) {
    return d.count;
  });

var svg = d3
  .select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pieData = pie(data);
var g = svg
  .selectAll(".arc")
  .data(pieData)
  .enter()
  .append("g");

var path = g
  .append("path")
  .attr("d", arc)
  .style("fill", function(d, i) {
    return d.data.color;
  });

var marker = svg
  .append("defs")
  .append("marker")
  .attr("id", "endmarker")
  .attr("overflow", "visible")
  .append("circle")
  .attr("cy", 0)
  .attr("cx", 0)
  .attr("r", markerRadius)
  .attr("fill", "red");
g.attr("marker-end", "url(#endmarker)");

g
  .append("circle")
  .attr("cx", function(d) {
    let path = d3.select(this.parentNode);
    var x = arc.centroid(d)[0];
    return x;
  })
  .attr("cy", function(d) {
    var y = arc.centroid(d)[1];
    console.log(d3.select(this).attr("cx"));
    return y;
  })
  .attr("fill", d => d.data.color)
  .attr("stroke", "black")
  .attr("r", (outerRadius - innerRadius) / 2);

codepen here

Thanks to anyone who will help!

Upvotes: 3

Views: 1113

Answers (1)

Coola
Coola

Reputation: 3142

Assuming that you want your output like: enter image description here

I found some code from Mike Bostock's Block here which shows how to add circles to rounded Arc Corners.

I adapted the following code for you which performs quite a bit of complex mathematics.

var cornerRadius = (outerRadius - innerRadius)/2;
svg.append("g")
    .style("stroke", "#555")
    .style("fill", "none")
    .attr("class", "corner")
  .selectAll("circle")
    .data(d3.merge(pieData.map(function(d) {
      return [
        {angle: d.startAngle + d.padAngle / 2, radius: outerRadius - cornerRadius, start: +1},
        {angle:   d.endAngle - d.padAngle / 2, radius: outerRadius - cornerRadius, start: -1},
      ];
    })))
  .enter().append("circle")
    .attr("cx", function(d) { return d.start * cornerRadius * Math.cos(d.angle) + Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.sin(d.angle); })
    .attr("cy", function(d) { return d.start * cornerRadius * Math.sin(d.angle) - Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.cos(d.angle); })
    .attr("r", cornerRadius);

Full snippet showing the output:

<div id="chart"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js"></script>
<script>
var data = [
  {
    name: "punti",
    count: 3,
    color: "#fff000"
  },
  {
    name: "max",
    count: 7,
    color: "#f8b70a"
  },
];
var totalCount = data.reduce((acc, el) => el.count + acc, 0);
var image_width = 32;
var image_height = 32;
var width = 540,
  height = 540,
  radius = 200,
  outerRadius = radius - 10,
  innerRadius = 100;
var cornerRadius = innerRadius;
var markerRadius = (outerRadius - innerRadius) / 2;
var arc = d3
  .arc()
  .outerRadius(outerRadius)
  .innerRadius(innerRadius)
  .cornerRadius(cornerRadius);

var pie = d3
  .pie()
  .sort(null)
  .value(function(d) {
    return d.count;
  });

var svg = d3
  .select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pieData = pie(data);
var g = svg
  .selectAll(".arc")
  .data(pieData)
  .enter()
  .append("g");

var path = g
  .append("path")
  .attr("d", arc)
  .style("fill", function(d, i) {
    return d.data.color;
  });

var cornerRadius = (outerRadius - innerRadius)/2;
svg.append("g")
    .style("stroke", "#555")
    .style("fill", "none")
    .attr("class", "corner")
  .selectAll("circle")
    .data(d3.merge(pieData.map(function(d) {
      return [
        {angle: d.startAngle + d.padAngle / 2, radius: outerRadius - cornerRadius, start: +1},
        {angle:   d.endAngle - d.padAngle / 2, radius: outerRadius - cornerRadius, start: -1},
      ];
    })))
  .enter().append("circle")
    .attr("cx", function(d) { return d.start * cornerRadius * Math.cos(d.angle) + Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.sin(d.angle); })
    .attr("cy", function(d) { return d.start * cornerRadius * Math.sin(d.angle) - Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.cos(d.angle); })
    .attr("r", cornerRadius);
  
  
</script>

Upvotes: 3

Related Questions