Valdemar
Valdemar

Reputation: 1060

Drawing lines with continuously varying line width on HTML canvas

I'm trying to draw a line that starts as a thin line and then gadually widens until the end. I need to draw semi-smooth curves (composite out of several straight lines) and I'm having problems finding a way to solve this task.

This fiddle shows my problem:

http://jsfiddle.net/ZvuQG/1/

When you call stroke(), the currently set lineWidth is used to stroke the entire line. My first thought was to draw each line piece individually, but of course, this leaves noticeable gaps in the line at the corners.

What is my best option here? Should I resort to drawing polygons (trapezoids) to get the corners right?

Is there an easier way?

(Edit: Note that I am not trying to actually draw ellipses or any other basic shapes; I'm trying to plot mathematical functions, using line thickness to represent velocity)

Upvotes: 16

Views: 6686

Answers (4)

whocares
whocares

Reputation: 1

Just add more points and it will become a lot smoother.

https://jsfiddle.net/enmvfarc/

for (var i=0; i<200; i++)
{
    var k = 2*Math.PI/200
    var width = 0.5 + 12*i/200
    
    var m = 200
    
    var x1 = Math.cos((i-1)*k) * 180
    var y1 = Math.sin((i-1)*k) * 140
    
    var x2 = Math.cos(i*k) * 180
    var y2 = Math.sin(i*k) * 140
    
    ctx.beginPath()
    ctx.lineWidth = width
    ctx.strokeStyle = "black"
    ctx.moveTo(m+x1,m+y1)
    ctx.lineTo(m+x2,m+y2)
    ctx.stroke()
}

Upvotes: 0

Valdemar
Valdemar

Reputation: 1060

For those interested, I have come up with two solutions to my problem.

The first idea was to actually draw each point as a corner, using canvas to draw a neat angle. A demo can be seen at:

http://jsfiddle.net/7BkyK/2/

var ctx = document.getElementById('canvas1').getContext('2d');
var points = [null, null, null];

for(var i=0; i<24; i++)
{
    var width = 0.5 + i/2;

    var m = 200;

    var x = Math.cos(i/4) * 180;
    var y = Math.sin(i/4) * 140;

    points[0] = points[1];
    points[1] = points[2];
    points[2] = { X:x, Y:y};

    if(points[0] == null)
        continue;

    var px0 = (points[0].X + points[1].X) / 2;
    var py0 = (points[0].Y + points[1].Y) / 2;

    var px1 = (points[1].X + points[2].X) / 2;
    var py1 = (points[1].Y + points[2].Y) / 2;

    ctx.beginPath();
    ctx.lineWidth = width;
    ctx.strokeStyle = "rgba(0,0,0,0.5)";
    ctx.moveTo(m+px0,m+py0);
    ctx.lineTo(m+points[1].X,m+points[1].Y);
    ctx.lineTo(m+px1,m+py1);
    ctx.stroke();
}
​

The second and much prettier solution, as suggested by Shmiddty, is to use bezier curves. This proved to be a great solution:

http://jsfiddle.net/Ssrv9/1/

// 1.
// Varying line width, stroking each piece of line separately
var ctx = document.getElementById('canvas1').getContext('2d');
var points = [null, null, null, null];

for(var i=-1; i<25; i = i +1)
{
    var width = 0.5 + i/2;

    var m = 200;


    var x = Math.cos(i/4) * 180;
    var y = Math.sin(i/4) * 140;

    points[0] = points[1];
    points[1] = points[2];
    points[2] = { X:x, Y:y};

    if(points[0] == null)
        continue;


    var p0 = points[0];
    var p1 = points[1];
    var p2 = points[2];

    var x0 = (p0.X + p1.X) / 2;
    var y0 = (p0.Y + p1.Y) / 2;

    var x1 = (p1.X + p2.X) / 2;
    var y1 = (p1.Y + p2.Y) / 2;

    ctx.beginPath();
    ctx.lineWidth = width;
    ctx.strokeStyle = "black";

    ctx.moveTo(m+x0, m+y0);
    ctx.quadraticCurveTo(m+p1.X, m+p1.Y, m+x1, m+y1);
    ctx.stroke();
}

Upvotes: 8

Rob Hardy
Rob Hardy

Reputation: 1821

Another way you can solve this is to consider each of you plot points to be a circle of a radius determined by the velocity.

plot paths joining the profile-edges of these circles (straight or curved, your choice), first over the top, round the last and back to the start on the underside. Then fill the path at the end.

This should give you a smooth line expanding and contracting as it approaches you plot point 'circles'.

Upvotes: 0

Rob Hardy
Rob Hardy

Reputation: 1821

Adding rounded line caps and a quadratic curve makes the whole thing look a lot tidier.

See here for example.

Upvotes: 2

Related Questions