Search code examples
javascriptjqueryangularjsd3.jsdonut-chart

Add border-radius property to D3js Donut Chart


I have this donut chart currently working in an AngularJS app:

enter image description here

But the design mockup says we would like this, note the border-radius property on the green portion of the arc:

enter image description here

How do I add a border-radius to the SVG that d3js outputs, the code I'm currently using looks like this:

let data = [
    {
        label: 'Data',
        count: scope.data
    },
    {
        label: 'Fill',
        count: 100 - scope.data
    }
];

let width = 60;
let height = 60;
let radius = Math.min(width, height) / 2;
let color = d3.scale
    .ordinal()
    .range(['#3CC692', '#F3F3F4']);
let selector = '#donut-asset-' + scope.chartId;

d3
    .select(selector)
    .selectAll('*')
    .remove();

let svg = d3
    .selectAll(selector)
    .append('svg')
    .attr('width', width)
    .attr('height', height)
    .append('g')
    .attr(
        'transform',
        'translate(' + width / 2 + ',' + height / 2 + ')'
    );
let arc = d3.svg
    .arc()
    .innerRadius(23)
    .outerRadius(radius);
let pie = d3.layout
    .pie()
    .value(function(d) {
        return d.count;
    })
    .sort(null);
let path = svg
    .selectAll('path')
    .data(pie(data))
    .enter()
    .append('path')
    .attr('d', arc)
    .attr('fill', function(d, i) {
        return color(d.data.label);
    });
let legend = svg
    .selectAll('.legend')
    .data(data)
    .enter()
    .append('g')
    .attr('class', 'legend')
    .attr('transform', function(d, i) {
        return 'translate(' + 0 + ',' + 0 + ')';
    });
legend
    .append('text')
    .attr('x', 1)
    .attr('y', 1)
    .attr('text-anchor', 'middle')
    .attr('dominant-baseline', 'central')
    .text(function(d) {
        return d.count + '%';
    });

};

I know to use cornerRadius but when I do it sets a radius for both arcs, it just needs to exist on the colored one. Any ideas? Thanks in advance for any help!


Solution

  • You can apply a corner radius to a d3 arc which allows rounding on the corners:

    let arc = d3.svg
        .arc()
        .innerRadius(23)
        .outerRadius(radius)
        .cornerRadius(10);
    

    But, the downside is that all arcs' borders are rounded:

    enter image description here

    If you apply the cornerRadius to only the darkened arc - the other arc won't fill in the background behind the rounded corners. Instead, we could append a circular arc (full donut) and place the darkened arc on top with rounding (my example doesn't adapt your code, just shows how that it can be done, also with d3v4 which uses d3.arc() rather than d3.svg.arc() ):

    var backgroundArc = d3.arc()
      .innerRadius(30)
      .outerRadius(50)
      .startAngle(0)
      .endAngle(Math.PI*2);
      
    var mainArc = d3.arc()
      .innerRadius(30)
      .outerRadius(50)
      .cornerRadius(10)
      .startAngle(0)
      .endAngle(function(d) { return d/100*Math.PI* 2 });
      
    var data = [10,20,30,40,50] // percents.
      
    var svg = d3.select("body").append("svg")
      .attr("width", 600)
      .attr("height", 200);
      
    var charts = svg.selectAll("g")
      .data(data)
      .enter()
      .append("g")
      .attr("transform",function(d,i) { 
         return "translate("+(i*100+50)+",100)";
      });
      
    charts.append("path")
      .attr("d", backgroundArc)
      .attr("fill","#ccc")
    
    charts.append("path")
      .attr("d", mainArc) 
      .attr("fill","orange")
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>