2
\$\begingroup\$

I need to draw a flow dynamically based on some user choices. In that flow I want to draw the choices (blue circles with number) and the directions of that choices (line and arrow). For example: node 1 to node 2.

JSFiddle Example

To draw the direction I draw a arrow in the end of the line but I can't make the arrow rotate around itself.

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 drawArrow() method in the lines

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

canvasContext.rotate(angleInDegrees);

I already tried every possible values for the rotation and the translation but arrow still don't rotate properly around itself. Can anyone help me?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

You got it wrong in your draw code : once you translated the context to a given point, you shouldn't use any more the point coordinates : the 'zero' is now at the translation point.
So to draw your arrow after you translated to its start point, just translate by the size of the arrow.

http://jsfiddle.net/gamealchemist/jxrdtLxx/2/

enter image description here

function drawArrow(canvasContext, arrow, ptArrow, endPt) {    
    var angleInDegrees = getAngleBetweenPoints(ptArrow, endPt);

    // 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*Math.PI/180);

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

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

Rq : i changed your css, since it was scaling the canvas. Change the size of the canvas directly in the canvas tag to avoid stretching artifacts.

\$\endgroup\$
2
  • \$\begingroup\$ Fantastic! Great job! Thanks a lot. Can you explain me why circle 2 is with border? That isn't my intent.. \$\endgroup\$
    – Ninita
    Commented Jan 16, 2015 at 12:19
  • 1
    \$\begingroup\$ You're welcome. For the circle border, it's the common mistake : you forgot to beginPath() before drawing (stroke()) the line. (jsfiddle.net/gamealchemist/jxrdtLxx/4). Yo ! \$\endgroup\$ Commented Jan 16, 2015 at 12:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .