I’m trying to fill the border of a circle, depending on a variable, like so:
I found this example but it’s not what I want. I want the outer border to fill from bottom-up, on both sides of circle.
Here’s the code that I have:
var completion = '5%';
drawcircle("js-raphael-completion", completion);
function drawcircle(div, rate) {
var archtype = Raphael(document.getElementsByClassName(div)[0], "90%", "90%");
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
};
};
// inner ring
var circle = archtype.circle("50%", "50%", "40%").attr({
"stroke": "#2787d3",
"stroke-width": 1
});
// text
var text = archtype.text("95%", "50%", rate).attr({
'font-size': 14, 'font-family': 'Avenir',
"fill": "#2787d3"
});
// outer ring (filling)
var my_arc = archtype.path().attr({
"stroke": "#2787d3",
"stroke-width": 10,
arc: [100, 100, 0, 100, 50]
});
// animation of outer ring
my_arc.animate({
arc: [100, 100, 100, 100, 50]
}, 1000);
}
See the demo: http://jsfiddle.net/SUBn6/
Currently, because I’m not using percentages for the outer ring (filling), it’s not centred with the border circle (inner).
Another issue I have is with the number “5%”. It needs to follow the outer ring (filling) as it fills up from bottom to top…
Any ideas? Thanks.
It looks like Raphael doesn't have such a settings for stroke ending. So another approach to this would be to have two arcs and fill the space between them to simulate the stroke.
That involves a little more geometry :) but this should work:
var completion = '5%';
drawcircle("js-raphael-completion", completion);
// JSFiddle needs not onload
// window.onload = function () {
// drawcircle("js-raphael-completion", completion);
// };
function drawcircle(div, rate) {
var archtype = Raphael(document.getElementsByClassName(div)[0], "90%", "90%");
archtype.customAttributes.arc = function (xloc, yloc, value, total, R,lineWidth) {
var alpha = 360 / total * value,
a = value / total * Math.PI,
w = R * Math.sin(a),
h = R * Math.cos(a),
gamma = Math.asin(R / (R + lineWidth) * Math.sin(Math.PI / 2 + a)),
beta = Math.PI/2 - a - gamma,
xOffset = (R + lineWidth) * Math.sin(beta) / Math.sin(Math.PI / 2 + a),
x = xloc - R * Math.sin(a),
y = yloc + R * Math.cos(a),
path;
if (Math.abs(a - Math.PI/2) < 0.0001) {
xOffset = lineWidth;
}
if (total == value) {
path = [
["M", x, y],
["A", R , R , 1, 1, 1, x - 0.01, y],
["L", x - 0.01,y-lineWidth],
["A", R + lineWidth, R + lineWidth, 1, 1, 0, x, y - lineWidth],
["Z"]
/*
["M", xloc, yloc - R],
["A", R, R, 0, 1, 1, xloc - 0.01, yloc - R]
*/
];
} else {
path = [
["M", x, y],
["A", R , R , 0, +(alpha > 180), 0, x + 2 * w, y],
["L", x+ 2 * w + xOffset,y],
["A", R + lineWidth, R + lineWidth, 0 , +(alpha > 180) , 1, x - xOffset, y],
["Z"]
];
}
return {
path: path
};
};
// inner ring
var circle = archtype.circle(100, 100, 50).attr({
"stroke": "#2787d3",
"stroke-width": 3
});
// text
var text = archtype.text("95%", "50%", rate).attr({
'font-size': 14,
'font-family': 'Avenir',
"fill": "#2787d3"
});
// outer ring (filling)
var my_arc = archtype.path().attr({
"stroke": "#FF0000",
"fill": "#2787d3",
"stroke-width" : 0,
arc: [100, 100, 0, 100, 51,10]
});
// animation of outer ring
my_arc.animate({
arc: [100, 100, 100, 100, 51,10]
}, 1000);
}
See http://jsfiddle.net/Xp77x/2/
I think you messed around the cos and sin stuff :). Basically the starting point of the arc is variable depending on the ratio (xloc - R * sin(a), yloc + R * cos(a)), and the width is also variable (2 * R *sin(a)).
I modified a little bit your stuff:
var completion = '5%';
drawcircle("js-raphael-completion", completion);
// JSFiddle needs not onload
// window.onload = function () {
// drawcircle("js-raphael-completion", completion);
// };
function drawcircle(div, rate) {
var archtype = Raphael(document.getElementsByClassName(div)[0], "90%", "90%");
archtype.customAttributes.arc = function (xloc, yloc, value, total, R) {
var alpha = 360 / total * value,
a = value / total * Math.PI,
w = R * Math.sin(a),
h = R * Math.cos(a),
x = xloc - R * Math.sin(a),
y = yloc + R * Math.cos(a),
path;
if (total == value) {
path = [
["M", xloc, yloc - R],
["A", R, R, 0, 1, 1, xloc - 0.01, yloc - R]
];
} else {
path = [
["M", x, y],
["A", R, R, 0, +(alpha > 180), 0, x + 2 * w, y]
];
}
return {
path: path
};
};
// inner ring
var circle = archtype.circle(100, 100, 50).attr({
"stroke": "#2787d3",
"stroke-width": 1
});
// text
var text = archtype.text("95%", "50%", rate).attr({
'font-size': 14,
'font-family': 'Avenir',
"fill": "#2787d3"
});
// outer ring (filling)
var my_arc = archtype.path().attr({
"stroke": "#2787d3",
"stroke-width": 10,
arc: [100, 100, 0, 100, 50]
});
// animation of outer ring
my_arc.animate({
arc: [100, 100, 100, 100, 55]
}, 1000);
}
see: http://jsfiddle.net/Xp77x/1/
However I used absolute coordinates. To have relative coordinates you need to parse a little bit the input for % and translate this to a 0-100 canvas.
Related
So I looked at the answer here: Draw arrow head in canvas using Angle
But it didn't seem to do it the way I wanted. I definitely do not want to use rotate, what I would like, is based on an Angle in degrees (0 being up on the screen, 180 being down, etc) is draw an arrow pointing in that direction.
Now I slept through trig in highschool so the correct usage of Rads, Sin and Cos are... well, they elude me :(.
Anyways, I have the angle already computed, and based on that I want to draw like the following:
The top one is at 0 degrees in my computation, the lower one 90 degrees.
I'm using a 2d canvas as my draw surface.
Inspired by the trigonometry of this answer I made a line + two smaller lines for the arrow part.
const size = 200;
var canvas = document.querySelector("canvas")
canvas.width = size;
canvas.height = size;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, size, size);
ctx.strokeStyle = "red"
function lineToAngle(ctx, x1, y1, length, angle) {
angle = (angle - 90) * Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.fill();
return {
x: x2,
y: y2
};
}
function draw_arrow(ctx, x1, y1, length, angle) {
var pos = lineToAngle(ctx, x1, y1, length, angle);
lineToAngle(ctx, pos.x, pos.y, 10, angle - 135);
lineToAngle(ctx, pos.x, pos.y, 10, angle + 135);
}
var pos = draw_arrow(ctx, 50, 50, 50, 30);
ctx.strokeStyle = "blue"
for (var angle = 0; angle <= 360; angle += 30) {
draw_arrow(ctx, 100, 100, 60, angle);
}
<canvas></canvas>
Overkill maybe?
This may help or may not.
Rather than create the arrow (shape) with a set of draw calls you can create a
Path2D to hold the shape with the helper function Path (in demo code below) that converts a set of arrays (each array a sub path) to a Path2D object. For example. const arrow = Path([[0, 0, 180, 0], [160, -10, 180, 0, 160, 10]]); a simple line arrow. To draw the path call ctx.stroke(arrow) or ctx.fill(arrow);
Define a style using Style eg const red = Style(...["#F00", , 2, "round"])
Position, rotate, and scale
Then the function drawPath(ctx, path, style, centerX, centerY, deg, scale) where...
ctx context to draw on.
path path to draw
style style to use,
centerX, centerY point to rotate around
deg angle in degrees from 0 deg pointing up, and positive values moving clockwize
scale lets you set the scale and defaults to 1.
Example use
// draw red arrow pointing up
const arrow = Path([[0, 0, 180, 0], [160, -10, 180, 0, 160, 10]]);
const red = Style(...["#F00", , 2, "round"]);
drawPath(ctx, arrow, red, 200, 200, 0);
Demo
Working example draw 6 arrows using 3 styles rotating around center of canvas.
const Path = (paths) => {
var j = 0, xx, yy, path = new Path2D();;
for (const subPath of paths) {
j = 0;
while (j < subPath.length) {
const [x, y] = [subPath[j++], subPath[j++]];
j === 2 ?
path.moveTo(...([xx, yy] = [x, y])) :
xx === x && yy === y ? path.closePath() : path.lineTo(x, y);
}
}
return path;
}
const Style = (strokeStyle, fillStyle, lineWidth, lineJoin = "bevel") => ({strokeStyle, fillStyle, lineWidth, lineJoin});
const styles = {
redLine: Style(...["#F00", , 6, "round"]),
greenFill: Style(...[ , "#0A0"]),
fillAndLine: Style(...["#000", "#0AF", 2, "round"]),
};
const paths = {
lineArrow: Path([ [10, 0, 180, 0], [160, -10, 180, 0, 160, 10] ]),
fatArrow: Path([ [10, -5, 180, -5, 170, -15, 200, 0, 170, 15, 180, 5, 10, 5, 10, -5] ]),
diamondArrow: Path([ [60, 0, 100, -15, 150, 0, 100, 15, 60, 0] ]),
};
requestAnimationFrame(mainLoop);
const [W, H, ctx] = [can.width, can.height, can.getContext("2d")];
const DEG2RAD = Math.PI / 180, DEG_0_OFFSET = -90;
function drawPath(ctx, path, style, centerX, centerY, deg, scale = 1) {
const rad = (deg + DEG_0_OFFSET) * DEG2RAD;
const [ax, ay] = [Math.cos(rad) * scale, Math.sin(rad) * scale];
ctx.setTransform(ax, ay, -ay, ax, centerX, centerY);
Object.assign(ctx, style);
style.fillStyle && ctx.fill(path);
style.strokeStyle && ctx.stroke(path);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
function mainLoop(time) {
ctx.clearRect(0, 0, W, H);
drawPath(ctx, paths.fatArrow, styles.fillAndLine, W * 0.5, H * 0.5, (time / 15000) * 360, 0.5);
drawPath(ctx, paths.fatArrow, styles.greenFill, W * 0.5, H * 0.5, (time / 20000) * 360);
drawPath(ctx, paths.diamondArrow, styles.fillAndLine, W * 0.5, H * 0.5, (time / 30000) * 360, 0.5);
drawPath(ctx, paths.diamondArrow, styles.greenFill, W * 0.5, H * 0.5, (time / 60000) * 360);
drawPath(ctx, paths.lineArrow, styles.redLine, W * 0.5, H * 0.5, (time / 5000) * 360, 0.9);
drawPath(ctx, paths.lineArrow, styles.redLine, W * 0.5, H * 0.5, (time / 10000) * 360);
requestAnimationFrame(mainLoop);
}
<canvas id="can" width="400" height="400"></canvas>
I am trying to draw an elliptical arc which can animate(bounce) using raphael. So far I have been following http://jsfiddle.net/jonhartmann/vm0etvz9/light/ to draw the arc. This is the output which I am getting from the link above.
But I need to get an output like an shell(below Image) starting the arc from 2nd quadrant with x=-30 and y=0(x can be anything on negative x axis depends on RADIUS and y should be 0).
I have attached my version of code to achieve the result.
http://jsfiddle.net/vssb7n25/
var archtype = Raphael('graph', 100, 100);
archtype.customAttributes.arc = function (value) {
var xloc = 50,
yloc = 50,
total = 100,
R = 30,
alpha = 180 / total * value,
a = (180 - 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, +(alpha > 180), 1, x, y]
];
}else{
path = [
["M", xloc, yloc - R],
["A", R, R, 0, +(alpha > 180), 1, x, y]
];
}
return {
path: path
};
};
It works fine but adds an extra arc on the bottom.
Could anyone help me out in drawing raphael elliptical arc.
Thank you
From the original sample you need to change starting and ending points of the arc.
The starting point (-1,0) becomes
["M", xloc - R, yloc],
And the ending point is
x = xloc - R * Math.sin(a),
y = yloc - R * Math.cos(a),
My circle starts from 12 o clock. But I need to start from 7 and end at 5 . Always starts horizontally but I need to start vertically . Also how to add Effects like shadow etc.
var amount = ("80");
var archtype = Raphael("canvas", 600, 400);
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: [200, 200, 0, 100, 60]
});
my_arc.animate({
arc: [200, 200, amount, 100, 60]
}, 1500, "bounce");
i finally found it :D just added my_arc.rotate(215, 100 ,100)
<script >
var amount = ("50");
var archtype = Raphael("canvas", 300, 200);
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: [100, 100, 0, 100, 60]
});
my_arc.rotate(215, 100 ,100).animate({
arc: [100, 100, 80, 100, 60]
}, 2000, "bounce");
Tiny update to make this work on IE8.
You need to remove the extra comma at the end of the path array else IE8 will give a JS error.
var arcseg = function (cx, cy, radius, start_r, finish_r) {
start_r = Raphael.rad(start_r);
finish_r = Raphael.rad(finish_r);
var start_x = cx + Math.cos( start_r ) * radius,
start_y = cy + Math.sin( start_r ) * radius,
finish_x = cx + Math.cos( finish_r ) * radius,
finish_y = cy + Math.sin( finish_r ) * radius,
path;
path = [
[ "M", start_x, start_y ],
[ "A", radius, radius, finish_r - start_r,
(finish_r - start_r > Raphael.rad( 180 )) ? 1 : 0,
(finish_r > start_r) ? 1 : 0,
finish_x, finish_y ]
];
return { path: path };
};
I'm attempting to integrate some charts into a project and I've got this one working just like the example, but properly displaying my data, but What I'd like to be able to do is change the baseline from 0 to another value as most of my data is points > 700, making the line relatively flat, as they are all high values.
Can anyone tell me where the baseline value is set in this? I'm not much of a javascript guy, so I'm having trouble.
Edit: I'd also like to be able to set a Fixed Top Line as well, ideally, if anyone sees an easy way to do both?
Edit2: Found the method of fixing the Topline: There's a "max" variable. Just alter that.
The Example is here. http://raphaeljs.com/analytics.html
The Javascript in Question is this:
Raphael.fn.drawGrid = function (x, y, w, h, wv, hv, color) {
color = color || "#000";
var path = ["M", Math.round(x) + .5, Math.round(y) + .5, "L", Math.round(x + w) + .5, Math.round(y) + .5, Math.round(x + w) + .5, Math.round(y + h) + .5, Math.round(x) + .5, Math.round(y + h) + .5, Math.round(x) + .5, Math.round(y) + .5],
rowHeight = h / hv,
columnWidth = w / wv;
for (var i = 1; i < hv; i++) {
path = path.concat(["M", Math.round(x) + .5, Math.round(y + i * rowHeight) + .5, "H", Math.round(x + w) + .5]);
}
for (i = 1; i < wv; i++) {
path = path.concat(["M", Math.round(x + i * columnWidth) + .5, Math.round(y) + .5, "V", Math.round(y + h) + .5]);
}
return this.path(path.join(",")).attr({stroke: color});
};
$(function () {
$("#data").css({
position: "absolute",
left: "-9999em",
top: "-9999em"
});
});
window.onload = function () {
function getAnchors(p1x, p1y, p2x, p2y, p3x, p3y) {
var l1 = (p2x - p1x) / 2,
l2 = (p3x - p2x) / 2,
a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y));
a = p1y < p2y ? Math.PI - a : a;
b = p3y < p2y ? Math.PI - b : b;
var alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2,
dx1 = l1 * Math.sin(alpha + a),
dy1 = l1 * Math.cos(alpha + a),
dx2 = l2 * Math.sin(alpha + b),
dy2 = l2 * Math.cos(alpha + b);
return {
x1: p2x - dx1,
y1: p2y + dy1,
x2: p2x + dx2,
y2: p2y + dy2
};
}
// Grab the data
var labels = [],
data = [];
$("#data tfoot th").each(function () {
labels.push($(this).html());
});
$("#data tbody td").each(function () {
data.push($(this).html());
});
// Draw
var width = 800,
height = 250,
leftgutter = 30,
bottomgutter = 20,
topgutter = 20,
colorhue = .6 || Math.random(),
color = "hsl(" + [colorhue, .5, .5] + ")",
r = Raphael("holder", width, height),
txt = {font: '12px Helvetica, Arial', fill: "#fff"},
txt1 = {font: '10px Helvetica, Arial', fill: "#fff"},
txt2 = {font: '12px Helvetica, Arial', fill: "#000"},
X = (width - leftgutter) / labels.length,
max = Math.max.apply(Math, data),
Y = (height - bottomgutter - topgutter) / max;
r.drawGrid(leftgutter + X * .5 + .5, topgutter + .5, width - leftgutter - X, height - topgutter - bottomgutter, 10, 10, "#000");
var path = r.path().attr({stroke: color, "stroke-width": 4, "stroke-linejoin": "round"}),
bgp = r.path().attr({stroke: "none", opacity: .3, fill: color}),
label = r.set(),
lx = 0, ly = 0,
is_label_visible = false,
leave_timer,
blanket = r.set();
label.push(r.text(60, 12, "24 hits").attr(txt));
label.push(r.text(60, 27, "22 September 2008").attr(txt1).attr({fill: color}));
label.hide();
var frame = r.popup(100, 100, label, "right").attr({fill: "#000", stroke: "#666", "stroke-width": 2, "fill-opacity": .7}).hide();
var p, bgpp;
for (var i = 0, ii = labels.length; i < ii; i++) {
var y = Math.round(height - bottomgutter - Y * data[i]),
x = Math.round(leftgutter + X * (i + .5)),
t = r.text(x, height - 6, labels[i]).attr(txt).toBack();
if (!i) {
p = ["M", x, y, "C", x, y];
bgpp = ["M", leftgutter + X * .5, height - bottomgutter, "L", x, y, "C", x, y];
}
if (i && i < ii - 1) {
var Y0 = Math.round(height - bottomgutter - Y * data[i - 1]),
X0 = Math.round(leftgutter + X * (i - .5)),
Y2 = Math.round(height - bottomgutter - Y * data[i + 1]),
X2 = Math.round(leftgutter + X * (i + 1.5));
var a = getAnchors(X0, Y0, x, y, X2, Y2);
p = p.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
bgpp = bgpp.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
}
var dot = r.circle(x, y, 4).attr({fill: "#333", stroke: color, "stroke-width": 2});
blanket.push(r.rect(leftgutter + X * i, 0, X, height - bottomgutter).attr({stroke: "none", fill: "#fff", opacity: 0}));
var rect = blanket[blanket.length - 1];
(function (x, y, data, lbl, dot) {
var timer, i = 0;
rect.hover(function () {
clearTimeout(leave_timer);
var side = "right";
if (x + frame.getBBox().width > width) {
side = "left";
}
var ppp = r.popup(x, y, label, side, 1),
anim = Raphael.animation({
path: ppp.path,
transform: ["t", ppp.dx, ppp.dy]
}, 200 * is_label_visible);
lx = label[0].transform()[0][1] + ppp.dx;
ly = label[0].transform()[0][2] + ppp.dy;
frame.show().stop().animate(anim);
label[0].attr({text: data + " hit" + (data == 1 ? "" : "s")}).show().stop().animateWith(frame, anim, {transform: ["t", lx, ly]}, 200 * is_label_visible);
label[1].attr({text: lbl + " September 2008"}).show().stop().animateWith(frame, anim, {transform: ["t", lx, ly]}, 200 * is_label_visible);
dot.attr("r", 6);
is_label_visible = true;
}, function () {
dot.attr("r", 4);
leave_timer = setTimeout(function () {
frame.hide();
label[0].hide();
label[1].hide();
is_label_visible = false;
}, 1);
});
})(x, y, data[i], labels[i], dot);
}
p = p.concat([x, y, x, y]);
bgpp = bgpp.concat([x, y, x, y, "L", x, height - bottomgutter, "z"]);
path.attr({path: p});
bgp.attr({path: bgpp});
frame.toFront();
label[0].toFront();
label[1].toFront();
blanket.toFront();
};
It's been a while since you posted the question, but maybe the answer is still helpful for someone else:
You need to add some extra variables to get the minimum data and to set a new baseline:
The maximum value is already defined here.
max = Math.max.apply(Math, data), //e.g. 800
Now you just do the same to get the minimum value:
min = Math.min.apply(Math, data), //e.g. 700
From these two variables you can get the range of the values. Use 'range' instead of 'max' to calculate Y.
range = max - min, //e.g 100
Y = (height - bottomgutter - topgutter ) / range;
Now jump to the Loop where y, Y0 and Y2 are defined and change
y = Math.round(height - bottomgutter - Y * data[i]),
Y0 = Math.round(height - bottomgutter - Y * data[i - 1]),
Y2 = Math.round(height - bottomgutter - Y * data[i + 1]),
to:
y = Math.round(height - bottomgutter - Y * data[i] + min * Y),
Y0 = Math.round(height - bottomgutter - Y * data[i - 1] + min * Y),
Y2 = Math.round(height - bottomgutter - Y * data[i + 1] + min * Y),
And if you prefer copy&paste, that is the complete edited window.onload function() with a new baseline:
window.onload = function () {
function getAnchors(p1x, p1y, p2x, p2y, p3x, p3y) {
var l1 = (p2x - p1x) / 2,
l2 = (p3x - p2x) / 2,
a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y));
a = p1y < p2y ? Math.PI - a : a;
b = p3y < p2y ? Math.PI - b : b;
var alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2,
dx1 = l1 * Math.sin(alpha + a),
dy1 = l1 * Math.cos(alpha + a),
dx2 = l2 * Math.sin(alpha + b),
dy2 = l2 * Math.cos(alpha + b);
return {
x1: p2x - dx1,
y1: p2y + dy1,
x2: p2x + dx2,
y2: p2y + dy2
};
}
// Grab the data
var labels = [],
data = [];
$("#data tfoot th").each(function () {
labels.push($(this).html());
});
$("#data tbody td").each(function () {
data.push($(this).html());
});
// Draw
var width = 800,
height = 250,
leftgutter = 30,
bottomgutter = 20,
topgutter = 20,
colorhue = .6 || Math.random(),
color = "hsl(" + [colorhue, .5, .5] + ")",
r = Raphael("holder", width, height),
txt = {font: '12px Helvetica, Arial', fill: "#fff"},
txt1 = {font: '10px Helvetica, Arial', fill: "#fff"},
txt2 = {font: '12px Helvetica, Arial', fill: "#000"},
X = (width - leftgutter) / labels.length,
max = Math.max.apply(Math, data),
min = Math.min.apply(Math, data),
range = max - min,
Y = (height - bottomgutter - topgutter ) / range;
r.drawGrid(leftgutter + X * .5 + .5, topgutter + .5, width - leftgutter - X, height - topgutter - bottomgutter, 10, 10, "#000");
var path = r.path().attr({stroke: color, "stroke-width": 4, "stroke-linejoin": "round"}),
bgp = r.path().attr({stroke: "none", opacity: .3, fill: color}),
label = r.set(),
lx = 0, ly = 0,
is_label_visible = false,
leave_timer,
blanket = r.set();
label.push(r.text(60, 12, "24 hits").attr(txt));
label.push(r.text(60, 27, "22 September 2008").attr(txt1).attr({fill: color}));
label.hide();
var frame = r.popup(100, 100, label, "right").attr({fill: "#000", stroke: "#666", "stroke-width": 2, "fill-opacity": .7}).hide();
var p, bgpp;
for (var i = 0, ii = labels.length; i < ii; i++) {
var y = Math.round(height - bottomgutter - Y * data[i] + min * Y),
x = Math.round(leftgutter + X * (i + .5)),
t = r.text(x, height - 6, labels[i]).attr(txt).toBack();
if (!i) {
p = ["M", x, y, "C", x, y];
bgpp = ["M", leftgutter + X * .5, height - bottomgutter, "L", x, y, "C", x, y];
}
if (i && i < ii - 1) {
var Y0 = Math.round(height - bottomgutter - Y * data[i - 1] + min * Y),
X0 = Math.round(leftgutter + X * (i - .5)),
Y2 = Math.round(height - bottomgutter - Y * data[i + 1] + min * Y),
X2 = Math.round(leftgutter + X * (i + 1.5));
var a = getAnchors(X0, Y0, x, y, X2, Y2);
p = p.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
bgpp = bgpp.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
}
var dot = r.circle(x, y, 4).attr({fill: "#333", stroke: color, "stroke-width": 2});
blanket.push(r.rect(leftgutter + X * i, 0, X, height - bottomgutter).attr({stroke: "none", fill: "#fff", opacity: 0}));
var rect = blanket[blanket.length - 1];
(function (x, y, data, lbl, dot) {
var timer, i = 0;
rect.hover(function () {
clearTimeout(leave_timer);
var side = "right";
if (x + frame.getBBox().width > width) {
side = "left";
}
var ppp = r.popup(x, y, label, side, 1),
anim = Raphael.animation({
path: ppp.path,
transform: ["t", ppp.dx, ppp.dy]
}, 200 * is_label_visible);
lx = label[0].transform()[0][1] + ppp.dx;
ly = label[0].transform()[0][2] + ppp.dy;
frame.show().stop().animate(anim);
label[0].attr({text: data + " hit" + (data == 1 ? "" : "s")}).show().stop().animateWith(frame, anim, {transform: ["t", lx, ly]}, 200 * is_label_visible);
label[1].attr({text: lbl + " September 2008"}).show().stop().animateWith(frame, anim, {transform: ["t", lx, ly]}, 200 * is_label_visible);
dot.attr("r", 6);
is_label_visible = true;
}, function () {
dot.attr("r", 4);
leave_timer = setTimeout(function () {
frame.hide();
label[0].hide();
label[1].hide();
is_label_visible = false;
}, 1);
});
})(x, y, data[i], labels[i], dot);
}
p = p.concat([x, y, x, y]);
bgpp = bgpp.concat([x, y, x, y, "L", x, height - bottomgutter, "z"]);
path.attr({path: p});
bgp.attr({path: bgpp});
frame.toFront();
label[0].toFront();
label[1].toFront();
blanket.toFront();
};
First off, fairly new to JS but getting better :-)
This question is similar to drawing centered arcs in raphael js but a little different due to some specifics.
I am using jQuery Countdown to handle a countdown.
The 'seconds' on the timer need to be animated similar to the Polar Clock Example on the Raphaël demo page.
There is a callback 'onTick' which I think needs to be somehow 'plugged into' Raphaël.
Markup:
<div id="timer"></div>
<div id="pane"></div>
JS:
$(document).ready(function() {
$('#timer').countdown({
until: new Date(2011, 11, 11),
timezone: -5,
layout: '<ul>{d<}<li><em>{dn}</em> {dl}</li>{d>}{h<}<li><em>{hn}</em> {hl}</li>{h>}' +
'{m<}<li><em>{mn}</em> {ml}</li>{m>}{s<}<li><em>{sn}</em> {sl}</li>{s>}</ul>',
onTick: get_seconds
});
var archtype = Raphael("pane", 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 - .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": 6, arc: [50, 50, 0, 100, 30]});
my_arc.animate({arc: [50, 50, timer_radius, 100, 30]}, 1500, "bounce");
function get_seconds () {
var counter_seconds = $('#timer ul li:last em').text();
timer_radius = 100 - (counter_seconds * 1.66666667);
}});
Any help would be appreciated. Thank you!
Here was is final use (splash page will be active until Dec 11, 2011.): http://tomwahlin.com/
And the JavaScript I ended up with:
;(function() {
// ON DOCUMENT.ready fire the primary function
$(function(){ timer.init() })
var timer = {
//
// `create_arc` is used to assign an arc to the Raphael pane created in .init
//
create_arc: function(arch) {
arch.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 - .01, yloc - R]];
} else {
path = [["M", xloc, yloc - R], ["A", R, R, 0, +(alpha > 180), 1, x, y]];
}
return {path: path};
};
},
//
// `get_arc_radius` returns the correct arc radius based on something
//
get_arc_radius: function() {
return ((($('#timer ul li:last em').text()) * 1.66666667));
},
//
// `update_based_on(type increment)
//
update_based_on: function(type, obj) {
var types = {
'seconds': 1000,
'minutes': 60000,
'hours': 3600000
}
var update = setInterval(function() {
obj.animate({arc: [100, 100, timer.get_arc_radius(), 100, 90]}, types[type]);
}, types[type])
},
//
// `create_pieces`
//
create_pieces: function() {
var archtype = Raphael("pane", 200, 200)
timer.create_arc(archtype)
//http://stackoverflow.com/questions/5061318/drawing-centered-arcs-in-raphael-js
//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": "#5ab4fc", "stroke-width": 18, arc: [100, 100, timer.get_arc_radius(), 100, 90]});
timer.update_based_on('seconds', my_arc)
},
//
// `init` kicks of the main behavior
//
init: function() {
$('#timer').countdown({
until: new Date(2011, 11, 11),
timezone: -5,
layout: '<ul>{d<}<li class="days"><em>{dn}</em> {dl}</li>{d>}{h<}<li class="hours"><em>{hn}</em> {hl}</li>{h>}' +
'{m<}<li class="minutes"><em>{mn}</em> {ml}</li>{m>}{s<}<li class="seconds"><em>{sn}</em> {sl}</li>{s>}</ul>',
onTick: function() {}
});
timer.create_pieces()
} // eo .init
}})();