Home > database >  JavaScript canvas pie chart warping issue
JavaScript canvas pie chart warping issue

Time:04-08

I am trying to create a custom HTML element that allows me to create pie charts easily. I have been very successful so far except for the fact that I cant seem to get the actual canvas drawing to be proportioned correctly.

class PieChart extends HTMLElement {
    constructor(){
        super();
    }

    connectedCallback(){
        this.classList.add('pie-chart')
        this.innerHTML = `<canvas style="width:100%; height:100%;"></canvas>`
        this.style.display = 'block'
        this.ctx = this.getElementsByTagName('canvas')[0].getContext('2d')

        const results = [
            {mood: "Angry", total: 1599, shade: "#0a9627"},
            {mood: "Happy", total: 478, shade: "#960A2C"},
            {mood: "Melancholic", total:332, shade: "#332E2E"},
            {mood: "Gloomy", total: 195, shade: "#F73809"},
            {mood: "Eh", total: 195, shade: "#000000"}
        ];

            this.chartify(results);
        

    }

    chartify(results){
        var el_width = this.getBoundingClientRect().width;
        var half = (el_width / 2);
        let sum = 0;
        let portion = results.reduce((sum, {total}) => sum   total, 0);
        let currentAngle = 0;
    
        for (let moodValue of results) {
            //calculating the angle the slice (portion) will take in the chart
            let portionAngle = (moodValue.total / portion) * 2 * Math.PI;
            //drawing an arc and a line to the center to differentiate the slice from the rest
            this.ctx.beginPath();
            this.ctx.arc(half, half, half, currentAngle, currentAngle   portionAngle);
            currentAngle  = portionAngle;
            this.ctx.lineTo(half, half);
            //filling the slices with the corresponding mood's color
            this.ctx.fillStyle = moodValue.shade;
            this.ctx.fill();
        }
    }
}

window.customElements.define('pie-chart', PieChart);
export{PieChart};

this is the returned result

enter image description here

The custom element is designed to have dynamic size. So to draw the arcs I was dividing the width by 2 in order to get the midpoint. I figured this would also be the radius. then all I need is to calculate the angle which I have no problem with. Why is it that the pie chart is oblong and not taking up the full space? What am I doing wrong in my canvas draw code?.

CodePudding user response:

The canvas element has default values for its height and width attributes of 150 and 300, respectively. If you need your canvas to be square, you need to specify these sizes to be the same.

This example tweaks your el_width variable to read the width off the canvas directly, instead of using getBoundingClientRect. I've also tweaked the way the canvas element is created so it has width and height attributes, and its 100% width and height styles are set via CSS:

class PieChart extends HTMLElement {
    constructor(){
        super();
    }

    connectedCallback(){
        this.classList.add('pie-chart')
        this.innerHTML = `<canvas width="300" height="300"></canvas>`
        this.style.display = 'block'
        this.ctx = this.getElementsByTagName('canvas')[0].getContext('2d')

        const results = [
            {mood: "Angry", total: 1599, shade: "#0a9627"},
            {mood: "Happy", total: 478, shade: "#960A2C"},
            {mood: "Melancholic", total:332, shade: "#332E2E"},
            {mood: "Gloomy", total: 195, shade: "#F73809"},
            {mood: "Eh", total: 195, shade: "#000000"}
        ];

            this.chartify(results);
        

    }

    chartify(results){
        var el_width = this.ctx.canvas.width;
        var half = (el_width / 2);
        let sum = 0;
        let portion = results.reduce((sum, {total}) => sum   total, 0);
        let currentAngle = 0;
    
        for (let moodValue of results) {
            //calculating the angle the slice (portion) will take in the chart
            let portionAngle = (moodValue.total / portion) * 2 * Math.PI;
            //drawing an arc and a line to the center to differentiate the slice from the rest
            this.ctx.beginPath();
            this.ctx.arc(half, half, half, currentAngle, currentAngle   portionAngle);
            currentAngle  = portionAngle;
            this.ctx.lineTo(half, half);
            //filling the slices with the corresponding mood's color
            this.ctx.fillStyle = moodValue.shade;
            this.ctx.fill();
        }
    }
}

window.customElements.define('pie-chart', PieChart);
pie-chart {
  width: 150px;
  aspect-ratio: 1 / 1;
}

pie-chart canvas {
  width: 100%;
  height: 100%;
}
<pie-chart></pie-chart>

You'll also notice that if you use width and height values that are smaller than the actual size at which the canvas is displayed, it will be noticeably scaled up since it's producing a raster image rather than a vector one.

  • Related