Search code examples
javascriptangulartypescriptd3.jsangular12

D3 with Angular - Half Pie chart Chart colors sequence


Could anybody please help me regarding chart colors ?

I am using d3.js in angular to create half pie chart. I would like to have 3 portions where I should have 3 colors. I want 3 colors, each color for some range.

I have data as { a: 50, b: 50, c: 50} and colors as ['green','yellow','red']. For this, chart is splitted equally and chart colors are in correct order (starts with green and ends with red). This is correct as attached image.

Correct Chart

But when I provide values as { a: 50, b: 30, c: 70}, I am getting colors in different order. It looks like it renders the highest value (70) in first place as attached below.

Incorrect Chart

My code:

 private theData = { a: 50, b: 30, c: 70};
 private svg:any;    
 private svgWidth = 200;
 private svgHeight = 125;  
 private colorRanges:any;
 private pie:any;
     
this.svg = d3
              .select('div#pie')
              .append('svg')
              .attr('width',this.svgWidth)
              .attr('height',this.svgHeight)
              .attr('id','pie-svg') 
              .append('g')
              .attr('class','ps')
              .attr(
                'transform',                
                'translate('+this.svgWidth/2+',115)');
      
  this.colorRanges = d3.scaleOrdinal().range(['green','yellow','red']); 

 this.pie = d3
.pie()
.startAngle(-90 * (Math.PI / 180))
.endAngle(90 * (Math.PI / 180))
.value((d: any) => d[1]);

this.svg.selectAll('rect')  
.data(this.pie(Object.entries(this.theData)))
.join('path')
.attr('d',
d3.arc().innerRadius(75).outerRadius(35))
.attr('fill', (d:any) => this.colorRanges(d.data[0]))
.attr('stroke','none')
.style('stroke-width','1px')
.style('opacity',1);

Solution

  • You can specify how the slices of the pie chart should be sorted:

    d3.pie()
        .startAngle(-90 * (Math.PI / 180))
        .endAngle(90 * (Math.PI / 180))
        .value(d => d[1])
        .sort((a, b) => d3.ascending(a[0], b[0]))
    

    This puts the slice for "a" first, then "b", then "c".

    Also, I would recommend setting a domain for your color scale so that you're not relying on the order returned by Object.entries():

    d3.scaleOrdinal()
        .domain(['a', 'b', 'c'])
        .range(['green','yellow','red'])