4

I'm drawing a line chart with canvas. The chart is responsive, but the line has to have a fixed width.

I made it responsive with css

    #myCanvas{
        width: 80%;
    }

,so the stroke is scaled.

The only solution I have found is to get the value of the lineWidth with the proportion between the width attribute of the canvas and its real width. To apply it, I clear and draw the canvas on resize.

    <canvas id="myCanvas" width="510" height="210"></canvas>

<script type="text/javascript">
    var c = document.getElementById("myCanvas");
    var ctx = c.getContext("2d");   
    function draw(){
        var canvasattrwidth = $('#myCanvas').attr('width');
        var canvasrealwidth = $('#myCanvas').width();
        // n sets the line width
        var n = 4;
        var widthStroke = n * (canvasattrwidth / canvasrealwidth) ;
        ctx.lineWidth = widthStroke;
        ctx.beginPath();
        ctx.moveTo( 0 , 10 );
        ctx.lineTo( 200 , 100 );
        ctx.stroke();
    }
    $(window).on('resize', function(){
        ctx.clearRect(0, 0, c.width, c.height);
        draw();
    });
    draw();
</script>

This is my first canvas and I think there is an easier way to made the lineWidth fixed (not to clear and draw everytime on resize), but I cannot find it.

There is a question with the similar problem

html5 canvas prevent linewidth scaling but with the method scale(), so I cannot use that solution.

Community
  • 1
  • 1
Natalia.js
  • 41
  • 4
  • 1
    No, redrawing is the way to go. If you are changing what is drawn on the canvas in any way (and don't want to draw on top of what is already on it), you will need to redraw. You want the line to stay the same pixel width as you resize, and this cannot be done properly without a redraw. Redraws aren't very taxing for things like charts anyway. – Matt Feb 23 '16 at 11:58

1 Answers1

5

There is no way to get a real world dimension of details for the canvas such as millimeters or inches so you will have to do it in pixels.

As the canvas resolution decreases the pixel width of a line needs to decrease as well. The limiting property of line width is a pixel. Rendering a line narrower than a pixel will only approximate the appearance of a narrower line by reducing the opacity (this is done automatically)

You need to define the line width in terms of the lowest resolution you will expect, within reason of course and adjust that width as the canvas resolution changes in relation to this selected ideal resolution.

If you are scaling the chart by different amounts in the x and y directions you will have to use the ctx.scale or ctx.setTransform methods. As you say you do not want to do this I will assume that your scaling is always with a square aspect.

So we can pick the lowest acceptable resolution. Say 512 pixels for either width or height of the canvas and select the lineWidth in pixels for that resolution.

Thus we can create two constants

const NATIVE_RES = 512; // the minimum resolution we reasonably expect
const LINE_WIDTH = 1;   // pixel width of the line at that resolution
                        // Note I Capitalize constants, This is non standard in Javascript

Then to calculate the actual line width is simply the actual canvas.width divided by the NATIVE_RES then multiply that result by the LINE_WIDTH.

var actualLineWidth = LINE_WIDTH * (canvas.width / NATIVE_RES);
ctx.lineWidth = actualLineWidth;

You may want to limit that size to the smallest canvas dimension. You can do that with Math.min or you can limit it in the largest dimension with Math.max

For min dimention.

var actualLineWidth = LINE_WIDTH * (Math.min(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;

For max dimension

var actualLineWidth = LINE_WIDTH * (Math.max(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;

You could also consider the diagonal as the adjusting factor that would incorporate the best of both x and y dimensions.

// get the diagonal resolution
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = LINE_WIDTH * (diagonalRes / NATIVE_RES);
ctx.lineWidth = actualLineWidth;

And finally you may wish to limit the lower range of the line to stop strange artifacts when the line gets smaller than 1 pixel.

Set lower limit using the diagonal

var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = Math.max(1, LINE_WIDTH * (diagonalRes / NATIVE_RES));
ctx.lineWidth = actualLineWidth;

This will create a responsive line width that will not go under 1 pixel if the canvas diagonal resolution goes under 512.

The method you use is up to you. Try them out a see what you like best. The NATIVE_RES I picked "512" is also arbitrary and can be what ever you wish. You will just have to experiment with the values and method to see which you like best.

If your scaling aspect is changing then there is a completely different technique to solve that problem which I will leave for another question.

Hope this has helped.

Blindman67
  • 51,134
  • 11
  • 73
  • 136