Rotate an arrow on a canvas

Asked

Viewed 534 times

8

I need to draw a flow dynamically based on a few user choices. In this flow I want to draw the chosen hypotheses (blue circles with numbers) and the direction between the choices (lines with arrows). For example: node 1 to node 2.

Jsfiddle example

To draw the direction I draw the arrow at the end of the line but I can’t get the arrow to turn just around its center, following the direction of the line.

JS code

$(document).ready(function () {
    drawOnCanvas();
});

function drawOnCanvas() {
    var canvas = document.getElementById('myCanvas');

    if (canvas.getContext) {
        var ctx = canvas.getContext("2d");

        var circle1 = {
            x: 75,
            y: 75,
            r: 15
        };

        var circle2 = {
            x: 225,
            y: 50,
            r: 15
        };

        var arrow = 
            {
                h: 5,
                w: 10
            };

        drawCircle(ctx, circle1, "1");
        drawCircle(ctx, circle2, "2");

        var ptCircle1 = getPointOnCircle(circle1.r, circle1, circle2);
        var ptCircle2 = getPointOnCircle(circle2.r, circle2, circle1);
        var ptArrow = getPointOnCircle(circle2.r + arrow.w, circle2, circle1);

        drawLine(ctx, ptCircle1, ptCircle2);
        drawArrow(ctx, arrow, ptArrow, ptCircle2);
    }
}

function drawArrow(canvasContext, arrow, ptArrow, endPt) {

    var angleInDegrees = getAngleBetweenPoints(ptArrow, endPt);

    canvasContext.beginPath();
    // first save the untranslated/unrotated context
    canvasContext.save();        

    // move the rotation point to the center of the rect    
    canvasContext.translate(ptArrow.x, ptArrow.y);        
    // rotate the rect
    canvasContext.rotate(angleInDegrees);

    canvasContext.moveTo(endPt.x, endPt.y);
    canvasContext.lineTo(endPt.x - arrow.w, endPt.y + arrow.h);
    canvasContext.lineTo(endPt.x - arrow.w, endPt.y - arrow.h);
    canvasContext.closePath();
    canvasContext.fillStyle = "rgb(72,72,72)";
    canvasContext.stroke();
    canvasContext.fill();

    // restore the context to its untranslated/unrotated state
    canvasContext.restore();
}

function drawCircle(canvasContext, circle, text) {
    canvasContext.beginPath(); //começa ou reinicia o desenho de algo       
    canvasContext.fillStyle = "rgb(43,166,203)";
    canvasContext.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI, false); //cria arcos     
    canvasContext.fill(); //atribui estilos

    drawText(canvasContext, circle, text);
}

function drawText(canvasContext, circle, text) {
    canvasContext.font = '8pt Calibri';
    canvasContext.fillStyle = 'white';
    canvasContext.textAlign = 'center';
    canvasContext.fillText(text, circle.x, circle.y + 3);
}

function drawLine(canvasContext, startPt, endPt) {
    canvasContext.moveTo(startPt.x, startPt.y);
    canvasContext.lineTo(endPt.x, endPt.y);
    canvasContext.stroke();
}

function getPointOnCircle(radius, originPt, endPt) {
    var angleInDegrees = getAngleBetweenPoints(originPt, endPt);


    // Convert from degrees to radians via multiplication by PI/180        
    var x = radius * Math.cos(angleInDegrees * Math.PI / 180) + originPt.x;
    var y = radius * Math.sin(angleInDegrees * Math.PI / 180) + originPt.y;

    return { x: x, y: y };
}

function getAngleBetweenPoints(originPt, endPt) {
    var interPt = { x: endPt.x - originPt.x,
        y: endPt.y - originPt.y
    };

    return Math.atan2(interPt.y, interPt.x) * 180 / Math.PI;
}

I think the problem is in the method drawArrow() in:

canvasContext.translate(ptArrow.x, ptArrow.y);   

canvasContext.rotate(angleInDegrees);

I have tried all possible values for rotation and translation but the arrow still does not rotate properly around itself. Can someone help me?

Update

I’ve been watching and maybe the best way to draw the triangle is Jsfiddle Example

canvasContext.moveTo(ptArrow.x, ptArrow.y);
canvasContext.lineTo(ptArrow.x, ptArrow.y - arrow.h);
canvasContext.lineTo(ptArrow.x + arrow.w, ptArrow.y);
canvasContext.lineTo(ptArrow.x + arrow.w, ptArrow.y);
canvasContext.lineTo(ptArrow.x, ptArrow.y + arrow.h);

Yet I still have the same problem..

  • Using canvasContext.translate(-ptArrow.x/20, ptArrow.y/3.5); and canvasContext.rotate(angleInDegrees/100); I managed to get the arrow right for Fiddle’s coordinates, but I played with the coordinates of circle 2 and that was just a trick of mine.

  • @Can Mutley show me in the example that working please? I applied this to my example but the arrow goes out of place and is supposed to be at the end of the line pointing to circle 2. http://jsfiddle.net/msmini5/gbyt2zuq/

  • Link to jsfiddle http://jsfiddle.net/zfoyf8bg/

  • @Mutley Because it really really works for the current circles. The problem is that the construction of this flow must be dynamic. If we change the circle coordinates, the solution no longer works. The values you placed for rotation and translation correspond to some logic or were just a result of trial-error?

  • Trial and error, unfortunately. Changing circle coordinates will not work. D:

1 answer

6


Problem solved! After defining the translation point, the coordinates used to draw the arrow should be based on point 0 which actually becomes the translation point.

       // move the rotation point to the center of the rect    
       canvasContext.translate(ptArrow.x, ptArrow.y);        
       // rotate the rect
       canvasContext.rotate(angleInDegrees*Math.PI/180);

        canvasContext.beginPath();
        canvasContext.moveTo(0,0);
        canvasContext.lineTo( 0, -arrow.h);
        canvasContext.lineTo( arrow.w, 0);
        canvasContext.lineTo( 0, +arrow.h);    

Browser other questions tagged

You are not signed in. Login or sign up in order to post.