Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

radar chart #95

Closed
wants to merge 11 commits into from
237 changes: 237 additions & 0 deletions g.radar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/*
* Radar charts for g.Raphael
*
* Copyright (c) 2009 Silvan T. Golega
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*
* Developed for:
* g.Raphael 0.4 - Charting library, based on Raphaël
*
* Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*/
Raphael.fn.g.radar = function (cx, cy, r, values, opts) {
opts = opts || {};
var paper = this,
arms = [], // holds the values, their positions, paths, arms, discs
chart = this.set(), // the chart that will be constructed and returned
covers = this.set(), // holds the areas for event handling
series = this.set(),
middle_point, // raphael disc for background mesh
mesh = this.set(), // the background mesh
total = 0,
max = 0, // the maximum of the values
len = values.length, // number of values
web = {pointarray: [], path: null}; // connecting lines between values


// overwrite default values for options with opts
var default_opts = {
meshwidth: 1,
strokewidth: 2,
stroke: "#f90",
meshcolor: "#999",
helplines: 5,
discradius: 10,
numbers: true,
numberscolor: "#fff"
};
for (var property in opts)
default_opts[property] = opts[property];
opts = default_opts;
delete default_opts;

// help function for drawing an arm
var arm = function (sx, sy, r, angle, m) {
var rad = Math.PI / 180,
cos = Math.cos(-angle * rad),
sin = Math.sin(-angle * rad),
x = sx + r * cos,
y = sy + r * sin,
ex = sx + m * cos,
ey = sy + m * sin,
res = {
x: x,
y: y,
//start: {x: sx, y: sy},
//end: {x: ex, y: ey},
path: ["M", cx, cy, "L", x, y].join(','),
rest: ["M", x, y, "L", ex, ey].join(','),
};
return res;
}

// determine the max value
for (var i = 0; i < len; i++) {
total += values[i];
max = max>values[i] ? max : values[i];
}

// draw middle point and mesh circles
middle_point = this.g.disc(cx, cy, 5).attr({stroke: opts.meshcolor, fill: opts.meshcolor, "stroke-width": opts.meshwidth});
if (opts.helplines){
var helpradius = r / opts.helplines;
for (var i = 0; i < opts.helplines; i++) {
mesh.push(this.circle(cx, cy, helpradius*(i+1)).attr({stroke: opts.meshcolor, "stroke-width": opts.meshwidth}));
}
}

// calculate the arms
for (var i = 0; i < len; i++) {
arms[i] = arm(cx, cy, r * values[i] / max, i * 360 / len, r);
}

// draw a poligon through the value points
web.pointarray.push("M");
for (var i = 0; i < len; i++) {
web.pointarray.push(arms[i].x, arms[i].y, "L");
}
web.pointarray.push(arms[0].x, arms[0].y);
web.path = this.path(web.pointarray.join(',')).attr({stroke: opts.stroke, "stroke-width": opts.meshwidth, fill: opts.stroke, "fill-opacity": 0.4});

// draw the value points (and arms) as latest to make sure they are the topmost
for (var i = 0; i < len; i++) {
arms[i].path = this.path(arms[i].path)
.attr({stroke: opts.stroke, "stroke-width": opts.strokewidth});
arms[i].rest = this.path(arms[i].rest)
.attr({stroke: opts.meshcolor, "stroke-width": opts.meshwidth});
arms[i].point = this.g.disc(arms[i].x, arms[i].y, opts.discradius)
.attr({stroke: opts.stroke, fill: opts.stroke });
if(opts.numbers){
arms[i].number = this.text(arms[i].x, arms[i].y+1, i+1).attr(this.g.txtattr).attr({fill: opts.numberscolor, "text-anchor": "middle"});
}
var cover = this.set();
cover.push(arms[i].path, arms[i].rest, arms[i].point);
if (arms[i].number)
cover.push(arms[i].number);
covers.push(cover);
series.push(arms[i].point);
}


chart.hover = function (fin, fout) {
fout = fout || function () {};
var that = this;
for (var i = 0; i < len; i++) {
(function (arm, cover, j) {
var o = {
arm: arm.point,
number: arm.number,
cover: cover,
cx: cx,
cy: cy,
mx: arm.x,
my: arm.y,
value: values[j],
max: max,
label: that.labels && that.labels[j]
};
o.cover.mouseover(function () {
fin.call(o);
}).mouseout(function () {
fout.call(o);
});
if (o.label){
o.label.mouseover(function () {
fin.call(o);
}).mouseout(function () {
fout.call(o);
});
}
})(arms[i], covers[i], i);
}
return this;
};
// x: where label could be put
// y: where label could be put
// value: value to show
// total: total number to count %
chart.each = function (f) {
var that = this;
for (var i = 0; i < len; i++) {
(function (arm, cover, j) {
var o = {
arm: arm.point,
number: arm.number,
cover: cover,
cx: cx,
cy: cy,
x: arm.x,
y: arm.y,
value: values[j],
max: max,
label: that.labels && that.labels[j]
};
f.call(o);
})(arms[i], covers[i], i);
}
return this;
};

chart.click = function (f) {
var that = this;
for (var i = 0; i < len; i++) {
(function (arm, cover, j) {
var o = {
arm: arm.point,
number: arm.number,
cover: cover,
cx: cx,
cy: cy,
mx: arm.x,
my: arm.y,
value: values[j],
max: max,
label: that.labels && that.labels[j]
};
cover.click(function () { f.call(o); });
if (o.label){
o.label.click(function () {
f.call(o);
});
}
})(arms[i], covers[i], i);
}
return this;
};

var legend = function (labels, otherslabel, mark, dir) {
var x = cx + r + r / 5,
y = cy,
h = y + 10;
labels = labels || [];
dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "east";
mark = paper.g.markers[mark && mark.toLowerCase()] || "disc";
chart.labels = paper.set();
for (var i = 0; i < len; i++) {
var clr = series[i].attr("fill"),
txt;
values[i].others && (labels[j] = otherslabel || "Others");
labels[i] = paper.g.labelise(labels[i], values[i], total);
chart.labels.push(paper.set());
chart.labels[i].push(paper.g[mark](x + 5, h, 8).attr({fill: clr, stroke: "none"}));
chart.labels[i].push(txt = paper.text(x + 20, h, labels[i] || values[i]).attr(paper.g.txtattr).attr({fill: opts.legendcolor || "#000", "text-anchor": "start"}));
if(opts.numbers){
chart.labels[i].push(paper.text(x+5, h+1, i+1).attr(paper.g.txtattr).attr({fill: opts.numberscolor, "text-anchor": "middle"}));
}
covers[i].label = chart.labels[i];
h += txt.getBBox().height * 1.2;
}
var bb = chart.labels.getBBox(),
tr = {
east: [0, -bb.height / 2],
west: [-bb.width - 2 * r - 20, -bb.height / 2],
north: [-r - bb.width / 2, -r - bb.height - 10],
south: [-r - bb.width / 2, r + 10]
}[dir];
chart.labels.translate.apply(chart.labels, tr);
chart.push(chart.labels);
};
if (opts.legend) {
legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
}
chart.push(series, covers, middle_point, mesh);
chart.series = series;
chart.covers = covers;
return chart;
};
67 changes: 67 additions & 0 deletions test-radar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>gRaphaël Static Radar Chart</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="../raphael.js" type="text/javascript" charset="utf-8"></script>
<script src="g.raphael.js" type="text/javascript" charset="utf-8"></script>
<script src="g.radar.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
window.onload = function () {
var r = Raphael("holder");
r.g.txtattr.font = "12px 'Fontin Sans', Fontin-Sans, sans-serif";

r.g.text(320, 20, "Static Radar Chart").attr({"font-size": 20});

static_radar = r.g.radar(
320, 240, 200,
[35, 20, 13, 32, 25, 40, 37, 16, 42],
{discradius: 8, numbers: false}
);

r.g.text(960, 20, "Animated Radar Chart").attr({"font-size": 20});
anim_radar = r.g.radar(
960, 240, 200,
[35, 37, 16, 20, 40, 13, 32, 25, 42, 20 ],
{ stroke: "#09f",
helplines: 4,
discradius: 10,
legend: ["Value number one", "A second value", "And a third one",
"Always give meaningful titles", "Don't you think?", "This legend rocks!"],
href: ["http://raphaeljs.com", "http://g.raphaeljs.com"]
});

anim_radar.hover(function () {
this.arm.stop();
this.arm.animate({scale: [2, 2, this.mx, this.my]}, 500);
this.number.animate({"font-size": 20}, 500);
if (this.label) {
this.label[0].stop();
this.label[0].scale(2);
this.label[1].attr({"font-weight": "bold"});
this.label[2].attr({"font-size": 20});
}
}, function () {
anim_radar.each(function () {
this.arm.stop();
this.arm.animate({scale: [1, 1, this.x, this.y]}, 500);
this.number.animate({"font-size": 12}, 500);
});
if (this.label) {
this.label[0].animate({scale: 1}, 500, "bounce");
this.label[1].attr({"font-weight": "normal"});
this.label[2].attr({"font-size": 12});
}
});

anim_radar.click(function () {
this.arm.stop();
window.location.href = "";
});
};
</script>
</head>
<body>
<div id="holder" style="width:13000px; height:480px;"></div>
</body>
</html>