Search code examples
canvasgraphresolution

How to increase resolution of circular graph in canvas


I am using a circular graph in a canvas following the process shown in codepen As you can see, the circle is blurred out when I zoom in page. I want to get the line high resolution. How can I do it in a way that It does not blur after zooming in the page. Js code I am using is given below:

$(document).ready(function(e) {

var el = document.getElementById('graph-1'); // get canvas
var options = {
percent:  el.getAttribute('data-percent')  ,
size: el.getAttribute('data-size') , /*|| 177*/
lineWidth: el.getAttribute('data-line') /*|| 4*/,
rotate: el.getAttribute('data-rotate') || 0
}

var canvas = document.createElement('canvas');
var paragrph = document.createElement('p');
var span = document.createElement('span');
span.textContent = options.percent + '%';


if (typeof(G_vmlCanvasManager) !== 'undefined') {
 G_vmlCanvasManager.initElement(canvas);
}

  var ctx = canvas.getContext('2d');
  canvas.width = canvas.height = options.size;

    el.appendChild(span);

    el.appendChild(canvas);

    ctx.translate(options.size / 2, options.size / 2); // change center
    ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg

    //imd = ctx.getImageData(0, 0, 240, 240);
    var radius = (options.size - options.lineWidth) / 2;

var drawCircle = function(color, lineWidth, percent) {
        percent = Math.min(Math.max(0, percent || 1), 1);
        ctx.beginPath();
        ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
        ctx.strokeStyle = color;
        ctx.lineCap = 'round'; // butt, round or square
        ctx.lineWidth = lineWidth
        ctx.stroke();
};

drawCircle('#aeb4bb', options.lineWidth, 100 / 100);
drawCircle('#006bcf', options.lineWidth, options.percent / 100);

    });

HTML code:

<div  id="graph-1" data-percent="85.25" data-size="177" data-line="7"> </div>

Solution

  • devicePixelRatio

    You can use window.devicePixelRatio to get the amount of zoom and use the window.onresize event to redraw when there is a zoom (zoom changes window size)

    See MDN devicePixelRatio for browser support.

    Snippet

    Copy of some of your code with modifications to demonstrate how to scale to different pixel ratios via the window.onresize event.

    Note ES6 code requires transpiler for legacy browsers.

    // helper functions unrelated to answer
    const getElementDataSet = (el, data = {}) => {Object.keys(el.dataset).forEach(key => {data[key] = el.dataset[key]}); return data }
    const assignProps = (obj, props) => { Object.keys(props).forEach(key => obj[key] = props[key]);}       
    const _$ = (name,props) => { const el = document.createElement(name); if (props) { assignProps(el, props) } return el }
    const _A$ = (container,el) => {container.appendChild(el); return el }
    // end of helper functions unrelated to answer
    
    // Get graph container
    const el = graph1;  
    // defaults and get options
    const options = getElementDataSet(el, {
            percent: 0, size: 100, lineWidth : 5, rotate: 0
        } 
    );
    // create elements and add to graph container
    const elements = {
      canvas : _A$(el, _$('canvas',{width : options.size, height : options.size})),
      display : _A$(el, _$('div',{textContent : options.percent + '%'})),
    }
    const ctx = elements.canvas.getContext('2d');
    function resizeCanvas(){
        elements.canvas.style.height = elements.canvas.style.width = options.startSize + "px";
        elements.canvas.height = elements.canvas.width = options.size;
    }
    function drawGraph(){
        function drawCircle(color, percent = options.percent / 100) {
          ctx.setTransform(1,0,0,1,options.size / 2, options.size / 2);
          ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg
          var radius = (options.size - options.lineWidth) / 2;
    
          percent = Math.min(Math.max(0, percent || 1), 1);
          ctx.beginPath();
          ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent);
          ctx.strokeStyle = color;
          ctx.lineCap = 'round'; // butt, round or square
          ctx.lineWidth = options.lineWidth
          ctx.stroke();
        };
        const scale = devicePixelRatio;
        options.size = Math.floor(options.startSize * scale);
        options.lineWidth = Math.floor(options.startLineWidth * scale);
        resizeCanvas();    
        drawCircle('#aeb4bb',100);
        drawCircle('#006bcf');
    };
    
    // remember the start scalable values
    options.startSize = options.size;
    options.startLineWidth = options.lineWidth;
    drawGraph(); 
    // listen to resize events and scale as needed
    addEventListener("resize", drawGraph );
    canvas {
      border: 2px solid black;
    }
     <div  id="graph1" data-percent="85.25" data-size="177" data-line-width="7"> </div>