2 * g.Raphael 0.5 - Charting library, based on Raphaƫl
4 * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
5 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
11 function finger(x, y, width, height, dir, ending, isPath, paper) {
13 ends = { round: 'round', sharp: 'sharp', soft: 'soft', square: 'square' };
15 // dir 0 for horizontal and 1 for vertical
16 if ((dir && !height) || (!dir && !width)) {
17 return isPath ? "" : paper.path();
20 ending = ends[ending] || "square";
21 height = Math.round(height);
22 width = Math.round(width);
29 var r = ~~(height / 2);
34 "M", x + .5, y + .5 - ~~(height / 2),
36 "a", r, ~~(height / 2), 0, 0, 1, 0, height,
42 "M", x + .5, y + .5 - r,
44 "a", r, r, 0, 1, 1, 0, height,
55 "M", x - ~~(width / 2), y,
57 "a", ~~(width / 2), r, 0, 0, 1, width, 0,
65 "a", r, r, 0, 1, 1, width, 0,
74 var half = ~~(height / 2);
78 "l", 0, -height, mmax(width - half, 0), 0, mmin(half, width), half, -mmin(half, width), half + (half * 2 < height),
85 "l", -width, 0, 0, -mmax(height - half, 0), half, -mmin(half, height), half, mmin(half, height), half,
93 "M", x, y + ~~(height / 2),
94 "l", 0, -height, width, 0, 0, height,
99 "M", x + ~~(width / 2), y,
100 "l", 1 - width, 0, 0, -height, width - 1, 0,
107 r = mmin(width, Math.round(height / 5));
109 "M", x + .5, y + .5 - ~~(height / 2),
111 "a", r, r, 0, 0, 1, r, r,
112 "l", 0, height - r * 2,
113 "a", r, r, 0, 0, 1, -r, r,
118 r = mmin(Math.round(width / 5), height);
120 "M", x - ~~(width / 2), y,
122 "a", r, r, 0, 0, 1, r, -r,
123 "l", width - 2 * r, 0,
124 "a", r, r, 0, 0, 1, r, r,
132 return path.join(",");
134 return paper.path(path);
140 * @param {Raphael} paper The paper to render the graph on
141 * @param {int} x left coord
142 * @param {int} y right coord
143 * @param {int} width width of graph
144 * @param {int} height heigt of graph
145 * @param {Array} values - single = [1,2,3] or multiple [ [1,2,3] , [1,2,3]]
146 * @param {Object} opts - options
147 * type : {String} square | ???
148 * gutter: {Number} percentage width default 20%
149 * vgutter : {Number} vertical gutter?
150 * colors : {Array} list of colours - default chartinst.colors
151 * xvalues : {Array} not sure - this looks like can be single or multi dimensional array
152 * yvalues : {Array} not sure - this looks like can be single or multi dimensional array
154 * axis: {String} eg. "top right bottom left" - which sides to show an axis eg. "0 0 1 1"
161 * labels : {Array} labels to go above the chart..
164 function VBarchart(paper, x, y, width, height, values, opts) {
168 var chartinst = this,
169 type = opts.type || "square",
170 gutter = parseFloat(opts.gutter || "20%"),
173 // covers = paper.set(),
174 // covers2 = paper.set(),
175 total = Math.max.apply(Math, values),
178 colors = opts.colors || chartinst.colors,
179 len = values.length + 2,
182 opts.xvalues = opts.xvalues|| [];
183 if (!paper.raphael.is(opts.xvalues[0], "array")) {
184 opts.xvalues = [opts.xvalues];
186 opts.yvalues = opts.yvalues|| [];
187 if (!paper.raphael.is(opts.yvalues[0], "array")) {
188 opts.yvalues = [opts.yvalues];
192 var allx = Array.prototype.concat.apply([], opts.xvalues),
193 ally = Array.prototype.concat.apply([], opts.yvalues),
194 xdim = chartinst.snapEnds(
195 Math.min.apply(Math, allx),
196 Math.max.apply(Math, allx),
197 opts.xvalues[0].length - 1
201 ydim = chartinst.snapEnds(
202 Math.min.apply(Math, ally),
203 Math.max.apply(Math, ally),
204 opts.yvalues[0].length - 1
206 miny = typeof(opts.ymin) == 'undefined' ? ydim.from : opts.ymin,
208 kx = (width - gutter * 2) / ((maxx - minx) || 1),
209 ky = (height - gutter * 2) / ((maxy - miny) || 1);
213 if (Raphael.is(values[0], "array") && values.length > 1) {
218 for (var i = values.length; i--;) {
219 bars.push(paper.set());
220 total.push(Math.max.apply(Math, values[i]));
221 len = Math.max(len, values[i].length);
224 for (var i = values.length; i--;) {
225 if (values[i].length < len) {
226 for (var j = len; j--;) {
232 total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
235 for (var i = 0; i < len; i++) {
237 values[i] = { value: values[i], order: i, valueOf: function () { return this.value; } };
240 total = (opts.to) || total;
241 Roo.log([width, len, gutter]);
242 var barwidth = width / (len * (90 + gutter) + gutter) * 100,
243 barhgutter = barwidth * (gutter) / 100,
244 barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
247 Y = (height - 2 * barvgutter) / total;
248 Roo.log([barwidth, X, barhgutter]);
250 barhgutter = Math.round(barhgutter);
251 barwidth = Math.floor(barwidth);
255 var axis = paper.set();
259 // Roo.log(opts.axis);
261 // Value - "top right bottom left". If
262 var ax = (opts.axis + "").split(/[,\s]+/);
272 opts.axisxstep || Math.floor((width - 2 * gutter) / 20),
282 opts.axisystep || Math.floor((height - 2 * gutter) / 20),
289 // x, y, length, from, to, steps, orientation, labels, type, dashsize, paper
291 x + gutter + barwidth,
293 width - (gutter + barwidth), // total width
296 opts.axisxstep || len - 3, // steps
298 opts.axisxlabels || false, // labels
311 // opts.axisystep || Math.floor((height - 2 * gutter) / 20),
319 !opts.stacked && (barwidth /= multi || 1);
321 for (var i = 0; i < len; i++) {
327 for (var j = 0; j < (multi || 1); j++) {
328 var h = Math.round((multi ? values[j][i] : values[i]) * Y),
329 top = y + height - barvgutter - h,
330 bar = finger(Math.round(X + barwidth / 2), top + h, barwidth, h, true, type, null, paper).attr({ stroke: "none", fill: colors[multi ? j : i] });
332 // bar = finger(x, Y + barwidth / 2, Math.round(8 * X), barwidth - 1, false, type, null, paper).attr({stroke: "none", fill: colors[multi ? j : i]});
335 if (multi && bars[j]) { // bars[j] did not appear to exist?
342 bar.x = Math.round(X + barwidth / 2);
345 bar.value = multi ? values[j][i] : values[i];
347 if(!isNaN(bar.y) && opts.labels && opts.labels[i]){
348 bar.labels = paper.set();
349 var qtyLbl = paper.text(
355 'text-anchor' : 'start',
359 bar.labels.push(qtyLbl);
369 //X += barhgutter + 25; -- why add 25??
376 * create the legend for Vbarchart
380 // var legend = function (labels, otherslabel, mark, dir) {
383 // labels = labels || [];
384 //// dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "south";
385 // dir = "east"; // default set to east, do not support other position first...
386 // mark = paper[mark && mark.toLowerCase()] || "circle";
387 // chart.labels = paper.set();
389 // for (var i = 0; i < len; i++) {
392 // values[i].others && (labels[i] = otherslabel || "Others");
394 // labels[i] = chartinst.labelise(labels[i], values[i], sum);
395 // chart.labels.push(paper.set());
396 // chart.labels[i].push(paper[mark](x + 5, h, 5).attr({ stroke: "none", fill: colors[i] }));
397 // chart.labels[i].push(txt = paper.text(x + 20, h, labels[i] || values[i]).attr(chartinst.txtattr).attr({ fill: opts.legendcolor || "#000", "text-anchor": "start"}));
398 // covers[i].label = chart.labels[i];
399 // h += txt.getBBox().height * 1.2;
403 // east: [width + 15, - Y +30]
404 //// west: [-bb.width - 2 * r - 20, -bb.height / 2],
405 //// north: [-r - bb.width / 2, -r - bb.height - 10],
406 //// south: [ x - 5, 10]
409 // chart.labels.translate.apply(chart.labels, tr);
410 // chart.push(chart.labels);
413 // if (opts.legend) {
414 // legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
419 // chart.hover = function (fin, fout) {
422 // covers.mouseover(fin).mouseout(fout);
426 // chart.hoverColumn = function (fin, fout) {
429 // fout = fout || function () {};
430 // covers2.mouseover(fin).mouseout(fout);
434 // chart.click = function (f) {
441 // chart.each = function (f) {
442 // if (!Raphael.is(f, "function")) {
445 // for (var i = covers.length; i--;) {
446 // f.call(covers[i]);
451 // chart.eachColumn = function (f) {
452 // if (!Raphael.is(f, "function")) {
455 // for (var i = covers2.length; i--;) {
456 // f.call(covers2[i]);
461 // chart.clickColumn = function (f) {
468 // chart.push(bars, covers, covers2);
470 // chart.covers = covers;
475 * Horizontal Barchart
477 function HBarchart(paper, x, y, width, height, values, opts) {
480 var chartinst = this,
481 type = opts.type || "square",
482 gutter = parseFloat(opts.gutter || "20%"),
485 covers = paper.set(),
486 covers2 = paper.set(),
487 total = Math.max.apply(Math, values),
490 colors = opts.colors || chartinst.colors,
495 if (Raphael.is(values[0], "array")) {
500 for (var i = values.length; i--;) {
501 bars.push(paper.set());
502 total.push(Math.max.apply(Math, values[i]));
503 len = Math.max(len, values[i].length);
507 for (var i = len; i--;) {
509 for (var j = values.length; j--;) {
510 tot +=+ values[j][i] || 0;
512 stacktotal.push(tot);
516 for (var i = values.length; i--;) {
517 if (values[i].length < len) {
518 for (var j = len; j--;) {
524 total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
527 for (var i = 0; i < len; i++) {
529 values[i] = { value: values[i], order: i, valueOf: function () { return this.value; } };
533 for (i = 0; i < len; i++) {
535 values[opts.cut].value += values[i];
536 values[opts.cut].others = true;
537 others = values[opts.cut].value;
541 len = Math.min(opts.cut + 1, values.length);
543 others && values.splice(len) && (values[opts.cut].others = true);
546 total = (opts.to) || Math.max.apply(Math, values);
548 var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
549 bargutter = Math.floor(barheight * gutter / 100),
552 X = (width - 1) / total;
554 !opts.stacked && (barheight /= multi || 1);
556 for (var i = 0; i < len; i++) {
559 for (var j = 0; j < (multi || 1); j++) {
560 var val = multi ? values[j][i] : values[i],
561 bar = finger(x, Y + barheight / 2, Math.round(val * X), barheight - 1, false, type, null, paper).attr({stroke: "none", fill: colors[multi ? j : i]});
569 bar.x = x + Math.round(val * X);
570 bar.y = Y + barheight / 2;
571 bar.w = Math.round(val * X);
583 var cvr = paper.rect(x, stack[0].y - stack[0].h / 2, width, barheight).attr(chartinst.shim);
586 cvr.bars = paper.set();
590 for (var s = stack.length; s--;) {
594 for (var s = 0, ss = stack.length; s < ss; s++) {
597 val = Math.round((size + bar.value) * X),
598 path = finger(x, bar.y, val, barheight - 1, false, type, 1, paper);
601 size && bar.attr({ path: path });
604 covers.push(cover = paper.rect(x + size * X, bar.y - bar.h / 2, bar.value * X, barheight).attr(chartinst.shim));
619 for (var i = 0; i < len; i++) {
620 for (var j = 0; j < (multi || 1); j++) {
621 var cover = paper.rect(x, Y, width, barheight).attr(chartinst.shim);
624 cover.bar = multi ? bars[j][i] : bars[i];
625 cover.value = cover.bar.value;
633 chart.label = function (labels, isRight) {
634 labels = labels || [];
635 this.labels = paper.set();
637 for (var i = 0; i < len; i++) {
638 for (var j = 0; j < (multi || 1); j++) {
639 //var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total),
640 var label = Raphael.g.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total),
642 bars[i * (multi || 1) + j].x + 5 : // - barheight / 2 + 3 :
644 A = !isRight ? "end" : "start",
647 this.labels.push(L = paper.text(X, bars[i * (multi || 1) + j].y, label).attr(txtattr).attr({ "text-anchor": A }).insertBefore(covers[0]));
649 if (L.getBBox().x < x + 5) {
650 L.attr({x: x + 5, "text-anchor": "start"});
652 bars[i * (multi || 1) + j].label = L;
660 var axis = paper.set();
663 // Roo.log(opts.axis);
665 // Value - "top right bottom left". If
666 var ax = (opts.axis + "").split(/[,\s]+/);
669 +ax[0] && axis.push(// not in used at the moment
676 opts.axisxstep || Math.floor((width - 2 * gutter) / 20),
680 +ax[1] && axis.push(// not in used at the moment
687 opts.axisystep || Math.floor((height - 2 * gutter) / 20),
692 +ax[2] && axis.push(// not in used at the moment
694 // x, y, length, from, to, steps, orientation, labels, type, dashsize, paper
698 // width - 2 * gutter,
701 width - 20, // total width
706 opts.axisxstep || Math.floor((width - 2 * gutter) / 20), // data length
708 opts.axisxlabels || false,
714 +ax[3] && axis.push(// just hardcode this
725 // opts.axisystep || Math.floor((height - 2 * gutter) / 20),
733 chart.hover = function (fin, fout) {
736 fout = fout || function () {};
737 covers.mouseover(fin).mouseout(fout);
741 chart.hoverColumn = function (fin, fout) {
744 fout = fout || function () {};
745 covers2.mouseover(fin).mouseout(fout);
749 chart.each = function (f) {
750 if (!Raphael.is(f, "function")) {
753 for (var i = covers.length; i--;) {
759 chart.eachColumn = function (f) {
760 if (!Raphael.is(f, "function")) {
763 for (var i = covers2.length; i--;) {
769 chart.click = function (f) {
776 chart.clickColumn = function (f) {
784 * create the legend for Hbarchart
788 var legend = function (labels, otherslabel, mark, dir) {
791 labels = labels || [];
792 // dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "south";
793 dir = "east"; // default set to east, do not support other position first...
794 mark = paper[mark && mark.toLowerCase()] || "circle";
795 chart.labels = paper.set();
797 for (var i = 0; i < len; i++) {
800 values[i].others && (labels[i] = otherslabel || "Others");
801 // Roo.log(values[i]);
802 labels[i] = chartinst.labelise(labels[i], values[i], sum);
803 chart.labels.push(paper.set());
804 chart.labels[i].push(paper[mark](x + 5, h, 5).attr({ stroke: "none", fill: colors[i] }));
805 chart.labels[i].push(txt = paper.text(x + 20, h, labels[i] || values[i]).attr(chartinst.txtattr).attr({ fill: opts.legendcolor || "#000", "text-anchor": "start"}));
806 covers[i].label = chart.labels[i];
807 h += txt.getBBox().height * 1.2;
811 east: [width + 25, - Y +30]
812 // west: [-bb.width - 2 * r - 20, -bb.height / 2],
813 // north: [-r - bb.width / 2, -r - bb.height - 10],
814 // south: [ x - 5, 10]
817 chart.labels.translate.apply(chart.labels, tr);
818 chart.push(chart.labels);
822 legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
825 chart.push(bars, covers, covers2);
827 chart.covers = covers;
832 var F = function() {};
833 F.prototype = Raphael.g;
834 HBarchart.prototype = VBarchart.prototype = new F;
836 Raphael.fn.hbarchart = function(x, y, width, height, values, opts) {
837 return new HBarchart(this, x, y, width, height, values, opts);
840 Raphael.fn.barchart = function(x, y, width, height, values, opts) {
841 return new VBarchart(this, x, y, width, height, values, opts);