Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canvas ArcTo confusion

So I am once again dealing with annular sectors which is not my forte. I can use the .arc method on canvas just fine, the problem comes from needing my arc to be part of a path.

So for example:

 ctx.save();
 ctx.arc(centerX, centerY, radius, startAngle, endAngle, true);
 ctx.stroke();
 ctx.restore();

Works fine. But now I need it as part of a path, so I have something like this:

 var pointArray = [...]; //this contains all four corner points of the annular sector
 ctx.save();
 ctx.moveTo(pointArray[0].x, pointArray[0].y);
 ctx.lineTo(pointArray[1].x, pointArray[1].y); //so that draws one of the flat ends
 ctx.arcTo(?, ?, pointArray[2].x pointArray[2].y, radius);

That tangent of tangent coordinate is driving me mad. Plus I have a serious concern: http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html Makes it sound like an arc drawn with arcTo could never cover 180degrees or more of a circle and there will be times that my annular sector will be greater than 180degrees.

Thanks for the help superior geometric minds of stackoverflow!

UPDATE Ok, so I am having to do svg canvas inter-polarity here, and using coffee-script, actual production code follows!

 annularSector : (startAngle,endAngle,innerRadius,outerRadius) ->

    startAngle  = degreesToRadians startAngle+180
    endAngle    = degreesToRadians endAngle+180
    p           = [ 
        [ @centerX+innerRadius*Math.cos(startAngle),    @centerY+innerRadius*Math.sin(startAngle) ]
        [ @centerX+outerRadius*Math.cos(startAngle),    @centerY+outerRadius*Math.sin(startAngle) ]
        [ @centerX+outerRadius*Math.cos(endAngle),      @centerY+outerRadius*Math.sin(endAngle) ]
        [ @centerX+innerRadius*Math.cos(endAngle),      @centerY+innerRadius*Math.sin(endAngle) ] 
    ]
    angleDiff   = endAngle - startAngle
    largeArc    = (if (angleDiff % (Math.PI * 2)) > Math.PI then 1 else 0)

    if @isSVG

        commands    = []

        commands.push "M" + p[0].join()
        commands.push "L" + p[1].join()
        commands.push "A" + [ outerRadius, outerRadius ].join() + " 0 " + largeArc + " 1 " + p[2].join()
        commands.push "L" + p[3].join()
        commands.push "A" + [ innerRadius, innerRadius ].join() + " 0 " + largeArc + " 0 " + p[0].join()
        commands.push "z"

        return commands.join(" ")

    else

        @gaugeCTX.moveTo p[0][0], p[0][1]
        @gaugeCTX.lineTo p[1][0], p[1][1]
        #@gaugeCTX.arcTo 
        @gaugeCTX.arc @centerX, @centerY, outerRadius, startAngle, endAngle, false
        #@gaugeCTX.moveTo p[2][0], p[2][1]
        @gaugeCTX.lineTo p[3][0], p[3][1]
        @gaugeCTX.arc @centerX, @centerY, innerRadius, startAngle, endAngle, false          

enter image description here

THE SOLUTION

        @gaugeCTX.moveTo p[0][0], p[0][1]
        @gaugeCTX.lineTo p[1][0], p[1][1]
        @gaugeCTX.arc @centerX, @centerY, outerRadius, startAngle, endAngle, false
        @gaugeCTX.lineTo p[3][0], p[3][1]
        @gaugeCTX.arc @centerX, @centerY, innerRadius, endAngle, startAngle, true #note that this arc is set to true and endAngle and startAngle are reversed!
like image 974
Fresheyeball Avatar asked Jul 24 '12 01:07

Fresheyeball


2 Answers

While your question/code is not 100% clear to me,

arcTo() still has browser/rendering problems, so use arc() for now.
(Please forgive me, I can't give detailed link right now since I to became a victim of new forced firefox 12 crap and lost years of notes in my ff3.6 powered system, which it simply deleted during it's unapproved update).

arc() works with radians, so do a quick read-up on wiki how Math.PI relates to radians, then whip up some formula to convert your degrees (or what ever you might wish) to radians.
You'll be doing something like: (((2 * Math.PI) / 360) * 270)    (=3/4 circle)
By the way: I did not ran into noticeable drawing problems with Radian/Unit conversions and ECMAscript's floating point behavior!

Also don't forget beginPath() and closePath() (and stroke() where needed): don't make the canvas guess!! This is usually key to drawing (closed) paths!!

You might also want to look at bezierCurveTo().

UPDATE (on TS' update): Looking at your picture (which I guess is the rendering of your problem), I think I see what you want: pieces of pie-charts.

This is simple, they are 2 arcs and two lines between a beginPath() and closePath() (and a fill-color).
What you want to do is to center your origin (point 0,0) with translate(). Before you do this, read-up on getting crisp lines: the trick is to translate to half pixels: (x.5,y.5).

Then make one 'main-canvas' and one 'temp-canvas'. For each piece-of-pie, draw a on clean temp-canvas (just set it's width and height instead of some clear mumbo jumbo) and put this temp-canvas on your main-canvas. Lastly render/output your main-canvas. Done.

The 'magic' (plain math) in your script that does the translation between your existing svg-path I cannot help you with, since I('m ashamed to admit) don't recognize any javascript in your updated source.

Hope this helps!

Update 2: Actually.. if you would tell us the format of your points/coordinates array.. that would really help! Then we would know from where to where you are drawing.
The best solution MIGHT indeed be to whip-up a javascript function that accepts your points-array's..
This way your coffeescript could simply spit-out your known values (data-format) to the javascript that was needed anyways to render canvas (in html).

Which makes me think.. there must be existing svg-path to canvas translation-scripts .. right? Maybe some-one knows of a tried and tested one and can link/copy it here (for future reference)..

Update 3: HINT: Don't forget: you can rotate the canvas in drawing-mode, but also when layering canvas'.
When you rotate (which works with the same radian-principle mentioned above) the canvas will rotate around it's origin-point (0,0) which is why translating (to the center of the canvas + 0.5px) is so useful for drawing these kind circle-based shapes!!!

like image 93
GitaarLAB Avatar answered Sep 29 '22 15:09

GitaarLAB


I recently found myself disappointed by the arcTo() method (which really should have been called roundedCorner() ). I decided to come up with a general-purpose work-around for people who want to use the cx,cy,r,theta1,theta2 expression as well:

http://www.purplefrog.com/~thoth/art/paisley/arcTo.html

With the important bit of code copied in here:

/**
   if code is "move" then we will do a moveTo x0,y0
   if code is "line" then we will do a lineTo x0,y0
   if code is anything else, we'll assume the cursor is already at x0,y0
*/
function otherArcTo(ctx, cx, cy, r, theta1, theta2, code)
{
    console.log([cx,cy,r,theta1, theta2, code])
    var x0 = cx + r*Math.cos(theta1)
    var y0 = cy + r*Math.sin(theta1)
    if (code=="move") {
        ctx.moveTo(x0,y0)
    } else if (code=="line") {
        ctx.lineTo(x0,y0)
    }

    var dTheta = theta2-theta1
    var nChunks = Math.ceil( Math.abs(dTheta) / (0.67*Math.PI) )
    if (nChunks <=1) {
        var theta3 = theta1 + dTheta/2
        var r3 = r/Math.cos(dTheta/2)
        var x1 = cx + r3*Math.cos(theta3)
        var y1 = cy + r3*Math.sin(theta3)
        var x2 = cx + r*Math.cos(theta2)
        var y2 = cy + r*Math.sin(theta2)
        ctx.arcTo(x1,y1,x2,y2, r)
    } else {
        for (var i=0; i<nChunks; i++) {
            var code2 = null
            if (i==0)
                code2 = code
            otherArcTo(ctx, cx, cy, r,
                       theta1 + dTheta*i/nChunks,
                       theta1 + dTheta*(i+1)/nChunks, code2)
        }
    }
}
like image 38
Mutant Bob Avatar answered Sep 29 '22 17:09

Mutant Bob