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.
7 imports = typeof(imports) == 'undefined' ? false : imports;
8 Raphael = typeof(Raphael) != 'undefined' ? Raphael : (imports ? imports.seed.Raphael.Raphael : {});
9 Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {});
16 function finger(x, y, width, height, dir, ending, isPath, paper) {
18 ends = { round: 'round', sharp: 'sharp', soft: 'soft', square: 'square' };
20 // dir 0 for horizontal and 1 for vertical
21 if ((dir && !height) || (!dir && !width)) {
22 return isPath ? "" : paper.path();
25 ending = ends[ending] || "square";
26 height = Math.round(height);
27 width = Math.round(width);
34 var r = ~~(height / 2);
39 "M", x + .5, y + .5 - ~~(height / 2),
41 "a", r, ~~(height / 2), 0, 0, 1, 0, height,
47 "M", x + .5, y + .5 - r,
49 "a", r, r, 0, 1, 1, 0, height,
60 "M", x - ~~(width / 2), y,
62 "a", ~~(width / 2), r, 0, 0, 1, width, 0,
70 "a", r, r, 0, 1, 1, width, 0,
79 var half = ~~(height / 2);
83 "l", 0, -height, mmax(width - half, 0), 0, mmin(half, width), half, -mmin(half, width), half + (half * 2 < height),
90 "l", -width, 0, 0, -mmax(height - half, 0), half, -mmin(half, height), half, mmin(half, height), half,
98 "M", x, y + ~~(height / 2),
99 "l", 0, -height, width, 0, 0, height,
104 "M", x + ~~(width / 2), y,
105 "l", 1 - width, 0, 0, -height, width - 1, 0,
112 r = mmin(width, Math.round(height / 5));
114 "M", x + .5, y + .5 - ~~(height / 2),
116 "a", r, r, 0, 0, 1, r, r,
117 "l", 0, height - r * 2,
118 "a", r, r, 0, 0, 1, -r, r,
123 r = mmin(Math.round(width / 5), height);
125 "M", x - ~~(width / 2), y,
127 "a", r, r, 0, 0, 1, r, -r,
128 "l", width - 2 * r, 0,
129 "a", r, r, 0, 0, 1, r, r,
137 return path.join(",");
139 return paper.path(path);
145 * @param {Raphael} paper The paper to render the graph on
146 * @param {int} x left coord
147 * @param {int} y right coord
148 * @param {int} width width of graph
149 * @param {int} height heigt of graph
150 * @param {Array} values - single = [1,2,3] or multiple [ [1,2,3] , [1,2,3]]
151 * @param {Object} opts - options
152 * type : {String} square | ???
153 * gutter: {Number} percentage width default 20%
154 * vgutter : {Number} vertical gutter?
155 * colors : {Array} list of colours - default chartinst.colors
156 * xvalues : {Array} not sure - this looks like can be single or multi dimensional array
157 * yvalues : {Array} not sure - this looks like can be single or multi dimensional array
159 * axis: {String} eg. "top right bottom left" - which sides to show an axis eg. "0 0 1 1"
162 * axisxlabels: {Array} - note - first element appears left of first bar..
166 * labels : {Array} labels to go above the chart..
169 function VBarchart(paper, x, y, width, height, values, opts) {
172 var chartinst = this,
173 type = opts.type || "square",
174 gutter = parseFloat(opts.gutter || "20%"),
177 covers = paper.set(),
178 covers2 = paper.set(),
179 total = Math.max.apply(Math, values),
182 colors = opts.colors || chartinst.colors,
183 len = values.length + 2,
186 opts.xvalues = opts.xvalues|| [];
187 if (!paper.raphael.is(opts.xvalues[0], "array")) {
188 opts.xvalues = [opts.xvalues];
190 opts.yvalues = opts.yvalues|| [];
191 if (!paper.raphael.is(opts.yvalues[0], "array")) {
192 opts.yvalues = [opts.yvalues];
196 var allx = Array.prototype.concat.apply([], opts.xvalues),
197 ally = Array.prototype.concat.apply([], opts.yvalues),
199 xdim = chartinst.snapEnds(
200 Math.min.apply(Math, allx),
201 Math.max.apply(Math, allx),
202 opts.xvalues[0].length - 1
206 ydim = chartinst.snapEnds(
207 Math.min.apply(Math, ally),
208 Math.max.apply(Math, ally),
209 opts.yvalues[0].length - 1
211 miny = typeof(opts.ymin) == 'undefined' ? ydim.from : opts.ymin,
213 kx = (width - gutter * 2) / ((maxx - minx) || 1),
214 ky = (height - gutter * 2) / ((maxy - miny) || 1);
216 //Roo.log({ally : ally});
218 if (Raphael.is(values[0], "array")) { // && values.length > 1) {
223 for (var i = values.length; i--;) {
224 bars.push(paper.set());
225 total.push(Math.max.apply(Math, values[i]));
226 len = Math.max(len, values[i].length);
230 for (var i = len; i--;) {
233 for (var j = values.length; j--;) {
234 tot +=+ values[j][i] || 0;
237 stacktotal.push(tot);
242 for (var i = values.length; i--;) {
243 if (values[i].length < len) {
244 for (var j = len; j--;) {
250 total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
253 for (var i = 0; i < len; i++) {
255 values[i] = { value: values[i], order: i, valueOf: function () { return this.value; } };
258 total = (opts.to) || total;
259 //Roo.log([width, len, gutter]);
260 var barwidth = width / (len * (90 + gutter) + gutter) * 100,
261 barhgutter = barwidth * (gutter) / 100,
262 barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
265 Y = (height - 2 * barvgutter) / total;
266 //Roo.log([barwidth, X, barhgutter]);
268 barhgutter = Math.round(barhgutter);
269 barwidth = Math.floor(barwidth);
273 var axis = paper.set();
277 // Roo.log(opts.axis);
279 // Value - "top right bottom left". If
280 var ax = (opts.axis + "").split(/[,\s]+/);
290 opts.axisxstep || Math.floor((width - 2 * gutter) / 20),
300 opts.axisystep || Math.floor((height - 2 * gutter) / 20),
305 opts.axisxlabels = opts.axisxlabels || [];
306 // add elements at beginning and end of array...
307 opts.axisxlabels.unshift(' ');
308 opts.axisxlabels.push(' ');
309 opts.axisxstep = opts.axisxstep || (opts.axisxlabels ? opts.axisxlabels.length -1 : false ) || len;
314 // x, y, length, from, to, steps, orientation, labels, type, dashsize, paper
318 width - (gutter + barhgutter), // total width
323 opts.axisxlabels || false, // labels
330 // x, y, length, from, to, steps, orientation, labels, type, dashsize, paper
332 // vertical steps (eg. number of Lables) - should be related to the range.
334 opts.axisystep = opts.axisystep || Math.floor((height - 2 * gutter) / 20);
335 var yrangedivs = Math.ceil((maxy - miny) / opts.axisystep)
336 var yrange = yrangedivs * opts.axisystep;
337 if (yrange < (maxy - miny) ) {
339 maxy = miny + (yrangedivs * opts.axisystep);
341 //Roo.log(opts.axisystep);
360 !opts.stacked && (barwidth /= multi || 1);
362 for (var i = 0; i < len; i++) {
369 for (var j = 0; j < (multi || 1); j++) {
370 var h = Math.round((multi ? values[j][i] : values[i]) * Y),
371 top = y + height - barvgutter - h,
372 bar = finger(Math.round(X + barwidth / 2), top + h, barwidth, h, true, type, null, paper).attr({
374 fill: colors[multi ? j : i]
380 if (multi) { // bars[j] did not appear to exist?
387 bar.x = Math.round(X + barwidth / 2);
390 bar.value = multi ? values[j][i] : values[i];
392 if(!isNaN(bar.y) && opts.labels && opts.labels[i]){
393 bar.labels = paper.set();
394 var qtyLbl = paper.text(
397 opts.labels[i]).attr({
399 'text-anchor' : 'start',
403 bar.labels.push(qtyLbl);
413 //X += barhgutter + 25; -- why add 25??
418 covers2.push(cvr = paper.rect(stack[0].x - stack[0].w / 2, y, barwidth, height).attr(chartinst.shim));
419 cvr.bars = paper.set();
423 for (var s = stack.length; s--;) {
427 for (var s = 0, ss = stack.length; s < ss; s++) {
430 h = (size + bar.value) * Y,
431 path = finger(bar.x, y + height - barvgutter - !!size * .5, barwidth, h, true, type, 1, paper);
434 size && bar.attr({path: path});
436 bar.y = y + height - barvgutter - !!size * .5 - h;
437 covers.push(cover = paper.rect(bar.x - bar.w / 2, bar.y, barwidth, bar.value * Y).attr(chartinst.shim));
439 cover.value = bar.value;
452 * create the legend for Vbarchart
456 // var legend = function (labels, otherslabel, mark, dir) {
459 // labels = labels || [];
460 //// dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "south";
461 // dir = "east"; // default set to east, do not support other position first...
462 // mark = paper[mark && mark.toLowerCase()] || "circle";
463 // chart.labels = paper.set();
465 // for (var i = 0; i < len; i++) {
468 // values[i].others && (labels[i] = otherslabel || "Others");
470 // labels[i] = chartinst.labelise(labels[i], values[i], sum);
471 // chart.labels.push(paper.set());
472 // chart.labels[i].push(paper[mark](x + 5, h, 5).attr({ stroke: "none", fill: colors[i] }));
473 // 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"}));
474 // covers[i].label = chart.labels[i];
475 // h += txt.getBBox().height * 1.2;
479 // east: [width + 15, - Y +30]
480 //// west: [-bb.width - 2 * r - 20, -bb.height / 2],
481 //// north: [-r - bb.width / 2, -r - bb.height - 10],
482 //// south: [ x - 5, 10]
485 // chart.labels.translate.apply(chart.labels, tr);
486 // chart.push(chart.labels);
489 // if (opts.legend) {
490 // legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
495 // chart.hover = function (fin, fout) {
498 // covers.mouseover(fin).mouseout(fout);
502 // chart.hoverColumn = function (fin, fout) {
505 // fout = fout || function () {};
506 // covers2.mouseover(fin).mouseout(fout);
510 // chart.click = function (f) {
517 // chart.each = function (f) {
518 // if (!Raphael.is(f, "function")) {
521 // for (var i = covers.length; i--;) {
522 // f.call(covers[i]);
527 // chart.eachColumn = function (f) {
528 // if (!Raphael.is(f, "function")) {
531 // for (var i = covers2.length; i--;) {
532 // f.call(covers2[i]);
537 // chart.clickColumn = function (f) {
544 // chart.push(bars, covers, covers2);+
546 for (var i = 0; i < len; i++) {
547 for (var j = 0; j < (multi || 1); j++) {
550 covers.push(cover = paper.rect(Math.round(X), y + barvgutter, barwidth, height - barvgutter).attr(chartinst.shim));
551 cover.bar = multi ? bars[j][i] : bars[i];
552 cover.value = cover.bar.value;
561 // chart.covers = covers;
566 * Horizontal Barchart
568 function HBarchart(paper, x, y, width, height, values, opts) {
571 var chartinst = this,
572 type = opts.type || "square",
573 gutter = parseFloat(opts.gutter || "20%"),
576 covers = paper.set(),
577 covers2 = paper.set(),
578 total = Math.max.apply(Math, values),
581 colors = opts.colors || chartinst.colors,
586 if (Raphael.is(values[0], "array")) {
591 for (var i = values.length; i--;) {
592 bars.push(paper.set());
593 total.push(Math.max.apply(Math, values[i]));
594 len = Math.max(len, values[i].length);
598 for (var i = len; i--;) {
600 for (var j = values.length; j--;) {
601 tot +=+ values[j][i] || 0;
603 stacktotal.push(tot);
607 for (var i = values.length; i--;) {
608 if (values[i].length < len) {
609 for (var j = len; j--;) {
615 total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
618 for (var i = 0; i < len; i++) {
620 values[i] = { value: values[i], order: i, valueOf: function () { return this.value; } };
624 for (i = 0; i < len; i++) {
626 values[opts.cut].value += values[i];
627 values[opts.cut].others = true;
628 others = values[opts.cut].value;
632 len = Math.min(opts.cut + 1, values.length);
634 others && values.splice(len) && (values[opts.cut].others = true);
637 total = (opts.to) || Math.max.apply(Math, values);
639 var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
640 bargutter = Math.floor(barheight * gutter / 100),
643 X = (width - 1) / total;
645 !opts.stacked && (barheight /= multi || 1);
647 for (var i = 0; i < len; i++) {
650 for (var j = 0; j < (multi || 1); j++) {
651 var val = multi ? values[j][i] : values[i],
652 bar = finger(x, Y + barheight / 2, Math.round(val * X), barheight - 1, false, type, null, paper).attr({stroke: "none", fill: colors[multi ? j : i]});
660 bar.x = x + Math.round(val * X);
661 bar.y = Y + barheight / 2;
662 bar.w = Math.round(val * X);
674 var cvr = paper.rect(x, stack[0].y - stack[0].h / 2, width, barheight).attr(chartinst.shim);
677 cvr.bars = paper.set();
681 for (var s = stack.length; s--;) {
685 for (var s = 0, ss = stack.length; s < ss; s++) {
688 val = Math.round((size + bar.value) * X),
689 path = finger(x, bar.y, val, barheight - 1, false, type, 1, paper);
692 size && bar.attr({ path: path });
695 covers.push(cover = paper.rect(x + size * X, bar.y - bar.h / 2, bar.value * X, barheight).attr(chartinst.shim));
710 for (var i = 0; i < len; i++) {
711 for (var j = 0; j < (multi || 1); j++) {
712 var cover = paper.rect(x, Y, width, barheight).attr(chartinst.shim);
715 cover.bar = multi ? bars[j][i] : bars[i];
716 cover.value = cover.bar.value;
724 chart.label = function (labels, isRight) {
725 labels = labels || [];
726 this.labels = paper.set();
728 for (var i = 0; i < len; i++) {
729 for (var j = 0; j < (multi || 1); j++) {
730 //var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total),
731 var label = Raphael.g.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total),
733 bars[i * (multi || 1) + j].x + 5 : // - barheight / 2 + 3 :
735 A = !isRight ? "end" : "start",
738 this.labels.push(L = paper.text(X, bars[i * (multi || 1) + j].y, label).attr(txtattr).attr({ "text-anchor": A }).insertBefore(covers[0]));
740 if (L.getBBox().x < x + 5) {
741 L.attr({x: x + 5, "text-anchor": "start"});
743 bars[i * (multi || 1) + j].label = L;
751 var axis = paper.set();
754 // Roo.log(opts.axis);
756 // Value - "top right bottom left". If
757 var ax = (opts.axis + "").split(/[,\s]+/);
760 +ax[0] && axis.push(// not in used at the moment
767 opts.axisxstep || Math.floor((width - 2 * gutter) / 20),
771 +ax[1] && axis.push(// not in used at the moment
778 opts.axisystep || Math.floor((height - 2 * gutter) / 20),
783 +ax[2] && axis.push(// not in used at the moment
785 // x, y, length, from, to, steps, orientation, labels, type, dashsize, paper
789 // width - 2 * gutter,
792 width - 20, // total width
797 opts.axisxstep || Math.floor((width - 2 * gutter) / 20), // data length
799 opts.axisxlabels || false,
805 +ax[3] && axis.push(// just hardcode this
816 // opts.axisystep || Math.floor((height - 2 * gutter) / 20),
824 chart.hover = function (fin, fout) {
827 fout = fout || function () {};
828 covers.mouseover(fin).mouseout(fout);
832 chart.hoverColumn = function (fin, fout) {
835 fout = fout || function () {};
836 covers2.mouseover(fin).mouseout(fout);
840 chart.each = function (f) {
841 if (!Raphael.is(f, "function")) {
844 for (var i = covers.length; i--;) {
850 chart.eachColumn = function (f) {
851 if (!Raphael.is(f, "function")) {
854 for (var i = covers2.length; i--;) {
860 chart.click = function (f) {
867 chart.clickColumn = function (f) {
875 * create the legend for Hbarchart
879 var legend = function (labels, otherslabel, mark, dir) {
882 labels = labels || [];
883 // dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "south";
884 dir = "east"; // default set to east, do not support other position first...
885 mark = paper[mark && mark.toLowerCase()] || "circle";
886 chart.labels = paper.set();
888 for (var i = 0; i < len; i++) {
891 values[i].others && (labels[i] = otherslabel || "Others");
892 // Roo.log(values[i]);
893 labels[i] = chartinst.labelise(labels[i], values[i], sum);
894 chart.labels.push(paper.set());
895 chart.labels[i].push(paper[mark](x + 5, h, 5).attr({ stroke: "none", fill: colors[i] }));
896 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"}));
897 covers[i].label = chart.labels[i];
898 h += txt.getBBox().height * 1.2;
902 east: [width + 25, - Y +30]
903 // west: [-bb.width - 2 * r - 20, -bb.height / 2],
904 // north: [-r - bb.width / 2, -r - bb.height - 10],
905 // south: [ x - 5, 10]
908 chart.labels.translate.apply(chart.labels, tr);
909 chart.push(chart.labels);
913 legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
916 chart.push(bars, covers, covers2);
918 chart.covers = covers;
923 var F = function() {};
924 F.prototype = Raphael.g;
925 HBarchart.prototype = VBarchart.prototype = new F;
927 Raphael.fn.hbarchart = function(x, y, width, height, values, opts) {
928 return new HBarchart(this, x, y, width, height, values, opts);
931 Raphael.fn.barchart = function(x, y, width, height, values, opts) {
932 return new VBarchart(this, x, y, width, height, values, opts);