1
\$\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\$
2
\$\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\$
  • \$\begingroup\$ Fantastic! Great job! Thanks a lot. Can you explain me why circle 2 is with border? That isn't my intent.. \$\endgroup\$ – Ninita Jan 16 '15 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\$ – GameAlchemist Jan 16 '15 at 12:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.