var data = [ {v: 350, t: 90, c:[50,25,25]}, {v: 250, t: 50, c:[10,20]}, {v: 220, t: 17.5, c:[10,10]}, {v: 200, t: 30, c:[75,25]}, {v: 100, t: 55, c:[5,15]}, {v: 80, t: 50, c:[10,20]}, {v: 50, t: 67.5} ]; //Is it necessary for data to have values beyond first one..because just subtract // -> cannot visualise magical unaccounted values //Data is structured in val of outerRadius + width of slice (time) //What if the data was represented as a circular linked list.... generateDonut(data); function generateDonut(data){ const h = 1000; const w = 1000; const smallestDim = Math.min(h,w) const innerRadius = smallestDim/10; const startAngle = -1*Math.PI; const rScale = d3.scale.linear() .domain([0,d3.max(data, function(d) { return d.v; })]) .range([0, smallestDim/2-innerRadius]); var pie = d3.layout.pie() .value(function(d){ return d.t || 10; }) .startAngle(startAngle) .endAngle(startAngle + 2*Math.PI) .sort(function(a,b) { a.v < b.v; }) //setting the order of the slices var arc = d3.svg.arc() //If set inner + outer radius it is not contained in the dom object.... var svg = d3.select("#chartArea").append("svg") .attr("width", w) .attr("height", h) .append("g") .attr("transform", "translate(" + w / 2 + "," + h / 2 + ")"); const colour = d3.scale.category20b(); var pieData = pie(data) svg.selectAll("path") .data(pieData) .enter().append("path") .each(function(d, i) { d.outerRadius = innerRadius + rScale(d.data.v) //might want to scale d.data.h... d.innerRadius = innerRadius d.startAngle = startAngle //This makes all arcs have 1 origin -> looks nice }) .attr("d", arc) .style('fill', function(d) { return colour(d.outerRadius); }) .each(function(d,i) { createOffShoot(d, i); }) //Bind data to this instead, have 1 g element for each slice + offshoot? function createOffShoot(d, i) { const nextSlice = i === pieData.length - 1 ? pieData[0] : pieData[i+1] const h = rScale(200) const angle = d.endAngle * 180 / Math.PI - 90; const offshoots = d.data.c || []; offshoots.forEach(function(offshoot, i2) { const offset = rScale(d3.sum(offshoots.slice(0,i2))) const width = rScale(offshoot) const x = (d.outerRadius - offset) * Math.sin(d.endAngle); const y = -1 * (d.outerRadius - offset) * Math.cos(d.endAngle); const points = x+','+y+' '+(x-width)+','+y+' '+ (x-width)+','+(y+h)+' '+(x-width/2)+','+(y+Math.sin(Math.PI/3)*width+h)+' '+ x+','+(y+h) //Since all the coordinate of the rect + triangle is known.... -> make polygon? var outflow = svg.append('g') .attr('transform', 'rotate(' + angle + ',' + x + ',' + y +')') .attr('fill', colour(d.outerRadius)) .attr('stroke', "orange") outflow.append('polygon') .attr('points', points) }) } }