drawing centered arcs in raphael js
Asked Answered
P

7

25

I need to draw concentric arcs of various sizes using raphael.js. I tried to understand the code behind http://raphaeljs.com/polar-clock.html, which is very similar to what I want, but, whithout comments, it is quite difficult to fathom.

Ideally, I would need a function that creates a path that is at a given distance from some center point, starts at some angle and ends at some other angle.

Pleasantry answered 21/2, 2011 at 0:55 Comment(0)
S
53

That answer is ok, but cant be animated. I ripped the important stuff out of polar-clock for you. Here is a red arc that animates growing. enjoy.

// Custom Arc Attribute, position x&y, value portion of total, total value, Radius
var archtype = Raphael("canvas", 200, 100);
archtype.customAttributes.arc = function (xloc, yloc, value, total, R) {
    var alpha = 360 / total * value,
        a = (90 - alpha) * Math.PI / 180,
        x = xloc + R * Math.cos(a),
        y = yloc - R * Math.sin(a),
        path;
    if (total == value) {
        path = [
            ["M", xloc, yloc - R],
            ["A", R, R, 0, 1, 1, xloc - 0.01, yloc - R]
        ];
    } else {
        path = [
            ["M", xloc, yloc - R],
            ["A", R, R, 0, +(alpha > 180), 1, x, y]
        ];
    }
    return {
        path: path
    };
};

//make an arc at 50,50 with a radius of 30 that grows from 0 to 40 of 100 with a bounce
var my_arc = archtype.path().attr({
    "stroke": "#f00",
    "stroke-width": 14,
    arc: [50, 50, 0, 100, 30]
});

my_arc.animate({
    arc: [50, 50, 40, 100, 30]
}, 1500, "bounce");
Sedulity answered 29/3, 2011 at 22:4 Comment(7)
For convenience, here's this answer as a jsfiddle where you can input how much of the circle to draw: jsfiddle.net/Bzdnm/2Glycerin
As with this and the Polar Clock example, does anybody know how to modify this so that the start point isn't always at 12 0'clock? I guessed that the start value out of 100 would do that, but it doesn't seem to work.Voluntaryism
Use the rotate method. CODE{ my_arc.rotate(-90, 50, 50).animate({ arc: [50, 50, amount, 100, 30] }, 1500, "bounce"); } You can find more on it in the Raphael docs.Sedulity
Is it possible to specify a different starting point for an arc than the top?Penury
@Penury - If you look at the comment two lines above yours, you can see that daxiang28 asked the exact same question. Even more interestingly, if you look to the following line, the one just above your question, not more than 1cm away, you can find the answer to the very question you asked.Sedulity
Haha, I saw that, but what I meant (and badly worded) was rotation clockwise, rather than rotate per-say, for animation purposes.Penury
Hi- thanks for this demo. I'm finding the Raphael.js docs a little unclear. Where are the size parameters given here- ie the size of the canvas, and the center of the canvas? Here's my forked jsfiddle: jsfiddle.net/urjJa/1 I've thickened the line to create a pie-chart style countdown, but I now overflow the scope of the canvas.Paraboloid
A
10

Here's how I have done it. The following code allows you to specify a start and end angle as well as an inner and outer radius (useful for doing those trendy donut style pie charts). The solution doesn't rely on approximating a curve with line segments and can be animated as per the clock example mentioned in the original question.

First create your Raphael drawing area; the following assumes a div with id "raphael_paper" in your HTML file:

var paper = Raphael("raphael_paper", 800, 800);

to this Raphael object we add a custom arc attribute, a function which takes the center of a circle (x and y coords), a start angle, an end angle, an inner radius and an outer radius:

paper.customAttributes.arc = function (centerX, centerY, startAngle, endAngle, innerR, outerR) {
    var radians = Math.PI / 180,
        largeArc = +(endAngle - startAngle > 180);
        // calculate the start and end points for both inner and outer edges of the arc segment
        // the -90s are about starting the angle measurement from the top get rid of these if this doesn't suit your needs
        outerX1 = centerX + outerR * Math.cos((startAngle-90) * radians),
        outerY1 = centerY + outerR * Math.sin((startAngle-90) * radians),
        outerX2 = centerX + outerR * Math.cos((endAngle-90) * radians),
        outerY2 = centerY + outerR * Math.sin((endAngle-90) * radians),
        innerX1 = centerX + innerR * Math.cos((endAngle-90) * radians),
        innerY1 = centerY + innerR * Math.sin((endAngle-90) * radians),
        innerX2 = centerX + innerR * Math.cos((startAngle-90) * radians),
        innerY2 = centerY + innerR * Math.sin((startAngle-90) * radians);

    // build the path array
    var path = [
        ["M", outerX1, outerY1], //move to the start point
        ["A", outerR, outerR, 0, largeArc, 1, outerX2, outerY2], //draw the outer edge of the arc
        ["L", innerX1, innerY1], //draw a line inwards to the start of the inner edge of the arc
        ["A", innerR, innerR, 0, largeArc, 0, innerX2, innerY2], //draw the inner arc
        ["z"] //close the path
    ];                   
    return {path: path};
};

now we can use this to draw arcs of a specified thickness, starting and ending wherever we want them to eg.

var redParams = {stroke: "#f00", "stroke-width": 1, fill:"#eee"},
    greenParams = {stroke: "#0f0", "stroke-width": 1, fill:"#eee"},
    blueParams = {stroke: "#00f", "stroke-width": 1, fill:"#eee"},
    cx = 300, cy = 300, innerRadius = 100, outerRadius = 250,

var red = paper.path().attr(redParams).attr({arc: [cx, cy, 0, 90, innerRadius, outerRadius]}); 
var green = paper.path().attr(greenParams).attr({arc: [cx, cy, 270, 320, innerRadius, outerRadius]}); 
var blue = paper.path().attr(blueParams).attr({arc: [cx, cy, 95, 220, innerRadius, outerRadius]});

This should result in three grey arc segments with red, blue and green 1px borders.

Argentous answered 17/2, 2012 at 15:15 Comment(1)
I was trying to spike creating donut charts in Raphael and this was exactly what I needed!Nuremberg
P
7

Actually found the answer myself. I first thought of something fancy involving bezier curves, but this just works.

-> creates a path using SVG path syntax, which works as is with raphael

function arc(center, radius, startAngle, endAngle) {
    angle = startAngle;
    coords = toCoords(center, radius, angle);
    path = "M " + coords[0] + " " + coords[1];
    while(angle<=endAngle) {
        coords = toCoords(center, radius, angle);
        path += " L " + coords[0] + " " + coords[1];
        angle += 1;
    }
    return path;
}

function toCoords(center, radius, angle) {
    var radians = (angle/180) * Math.PI;
    var x = center[0] + Math.cos(radians) * radius;
    var y = center[1] + Math.sin(radians) * radius;
    return [x, y];
}
Pleasantry answered 23/2, 2011 at 13:8 Comment(2)
I thought I would make a php version in case anyone is interested.Darb
No need to approximate a circle yourself. SVG already offers elliptical pathsWrought
P
7

Just to remove some guesswork from user592699's answer, this is the complete code that works:

<script src="raphael.js"></script>
<script>

  var paper = Raphael(20, 20, 320, 320);

  function arc(center, radius, startAngle, endAngle) {
      angle = startAngle;
      coords = toCoords(center, radius, angle);
      path = "M " + coords[0] + " " + coords[1];
      while(angle<=endAngle) {
          coords = toCoords(center, radius, angle);
          path += " L " + coords[0] + " " + coords[1];
          angle += 1;
      }
      return path;
  }

  function toCoords(center, radius, angle) {
      var radians = (angle/180) * Math.PI;
      var x = center[0] + Math.cos(radians) * radius;
      var y = center[1] + Math.sin(radians) * radius;
      return [x, y];
  }

  paper.path(arc([100, 100], 80, 0, 270));  // draw an arc 
                                            // centered at (100, 100),
                                            // radius 80, starting at degree 0,
                                            // beginning at coordinate (80, 0)
                                            //   which is relative to the center
                                            //   of the circle,
                                            // going clockwise, until 270 degree

</script>
Proulx answered 13/4, 2011 at 8:29 Comment(0)
G
2

For those who want the arc to be made with closed path and not stroke, I have extended genkilabs answer to make a solution. In cases when you need to give outer stroke to your arc, this might help.

// Custom Arc Attribute, position x&y, value portion of total, total value, Radius, width
var archtype = Raphael("canvas", 200, 100);
archtype.customAttributes.arc = function (xloc, yloc, value, total, R, width) {
    if(!width) width = R * 0.4;
    var alpha = 360 / total * value,
        a = (90 - alpha) * Math.PI / 180,
        w = width / 2,
        r1 = R + w,
        r2 = R - w,
        x1 = xloc + r1 * Math.cos(a),
        y1 = yloc - r1 * Math.sin(a),
        x2 = xloc + r2 * Math.cos(a),
        y2 = yloc - r2 * Math.sin(a),
        path;
    if (total == value) {
        path = [
            ["M", xloc, yloc - r1],
            ["A", r1, r1, 0, 1, 1, xloc - 0.01, yloc - r1],
            ["Z"],
            ["M", xloc - 0.01, yloc - r2],
            ["A", r2, r2, 0, 1, 0, xloc, yloc - r2],
            ["Z"]
        ];
    } else {
        path = [
            ["M", xloc, yloc - r1],
            ["A", r1, r1, 0, +(alpha > 180), 1, x1, y1],
            ["L", x2, y2],
            ["A", r2, r2, 0, +(alpha > 180), 0,  xloc, yloc - r2],
            ["L", xloc, yloc - r1],
            ["Z"]
        ];
    }
    return {
        path: path
    };
};

//make an arc at 50,50 with a radius of 30 that grows from 0 to 40 of 100 with a bounce
var my_arc = archtype.path().attr({
    "fill": "#00f",
    "stroke": "#f00",
    "stroke-width": 5,
    arc: [50, 50, 0, 100, 30]
});

my_arc.animate({
    arc: [50, 50, 40, 100, 30]
}, 1500, "bounce");

JSFiddle

Golgi answered 18/12, 2013 at 9:55 Comment(0)
M
1

You can also do this without having to use loops. The following achieves this and works with negative angles as well.

Pass in a Raphael object as r. The angles start with 0 degrees, which is the top of the circle rather than the right as was listed in a couple of other solutions.

        function drawArc(r, centerX, centerY, radius, startAngle, endAngle) {
            var startX = centerX+radius*Math.cos((90-startAngle)*Math.PI/180); 
            var startY = centerY-radius*Math.sin((90-startAngle)*Math.PI/180);
            var endX = centerX+radius*Math.cos((90-endAngle)*Math.PI/180); 
            var endY = centerY-radius*Math.sin((90-endAngle)*Math.PI/180);
            var flg1 = 0;

            if (startAngle>endAngle)
                flg1 = 1;
            else if (startAngle<180 && endAngle<180)
                flg1 = 0;
            else if (startAngle>180 && endAngle>180)
                flg1 = 0;
            else if (startAngle<180 && endAngle>180)
                flg1 = 0; // edited for bugfix here, previously this was 1
            else if (startAngle>180 && endAngle<180)
                flg1 = 1;

            return r.path([['M',startX, startY],['A',radius,radius,0,flg1,1,endX,endY]]);
        };
Mittel answered 24/12, 2012 at 15:29 Comment(0)
S
1

I have adapted genkilabs answer to include rotation and inversion abilities. Also, how much of the ring is filled was changed to a single-number percent. (The inversion was adapted from this post). Hope it's helpful!

paper.customAttributes.arc = function (xloc, yloc, percent, rad, rot, invert) {
    var alpha = 3.6 * percent,
    a = (90 - alpha) * Math.PI / 180,
    x = xloc + rad * Math.cos(a),
    y = yloc - rad * Math.sin(a),
    path;

    if (invert) {
        x = xloc - rad * Math.cos(a);
    }

    if (percent >= 100) {
        path = [
            ["M", xloc, yloc - rad],
            ["A", rad, rad, 0, 1, 1, xloc - 0.01, yloc - rad]
        ];
    } else {
        path = [
            ["M", xloc, yloc - rad],
            ["A", rad, rad, 0, +(alpha > 180), +(!invert), x, y]
        ];
        }
    return {
        path: path,
        transform: "r"+rot+","+xloc+","+yloc,
    };
};
Scot answered 10/12, 2014 at 4:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.