fix #8056 - more refinements to checking data
[g.raphael] / g.bar.overlay.js
1 /*!
2  * g.Raphael 0.51 - Charting library, based on RaphaĆ«l
3  *
4  * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
5  * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6  */
7
8 if (typeof(Raphael) == 'undefined') {
9     // support for seed/simple browser usage
10     importz = imports['seed/importz.js'].importz;
11
12     Raphael = importz('Raphael');
13     Roo = importz('Roo');
14 }
15
16 (function () {
17     
18     function clearAr(ar) {
19         var ret = new Array();
20         //Roo.log(JSON.stringify(ar));
21         for (var i = 0; i < ar.length; i++) {
22             //print(typeof(ar[i]));
23             if (Raphael.is(ar[i], "object")) {
24                 ret.push(clearAr(ar[i]));
25                 continue;
26             }
27             ret.push(parseInt(ar[i]));
28         }
29         return ret;
30     }
31  
32
33 /*\
34  **
35  * Creates a vertical bar chart
36  **
37  > Parameters
38  **
39  - x (number) x coordinate of the chart
40  - y (number) y coordinate of the chart
41  - width (number) width of the chart (respected by all elements in the set)
42  - height (number) height of the chart (respected by all elements in the set)
43  - values (array) values
44  - opts (object) options for the chart
45  o {
46     background (string) : background color
47     gutter (string) (number) : indicate width of gutters with repsect to the bars in terms of percentage (e.g. '20%' means that the width ratio of gutter to bar is 10:2)
48     barsteps (number) : number of bars shown
49     barfill (string) : color to fill the empty bar (not filling if not provided)
50     baroffset (number) : distance between the rightmost bar and y axis;
51     colors (array) : colors of the bars 
52     axis (string) : composed of 4 '0' / '1' separated by space (indicated whether top / right / bottom / left axis should be shown) 
53     (e.g. '0 1 1 0' indicates that the right and the bottom axis should be shown)
54     asixxheight (number) : height of x axis area
55     axisxlabels (string) : labels in x axis
56     asixywidth (number) : width of y axis area
57     axisystep (number) : number of steps in y axis
58     axislinecolor (string) : color of lines in axes
59     axisstrokewidth (number) : stroke width of lines in axes
60     axisfont (string) : font family of labels in axes
61     axisfontsize (number) : font size of labels in axes
62     axisfontweight (string) (number) : font weight of labels in axes
63     axisfontcolor (string) : font color of labels in axes
64     labelfont (string) : font family of labels
65     labelsize (number) : font size of labels
66     labelweight (string)(number) : font weight of labels
67     labelcolors (array) : font colors of the labels
68     labelborderradius (number) : border radius of the label boxes
69     labelmaxheight (number) : max height for the label boxes
70     labeloffsety (number) : horizontal distance between the label boxes and the bars below
71     legends (array) : legend
72     legendheight : height of the legend
73     legendkeyshape (string) : shape of the legend keys ('circle' / 'rect')
74     legendkeysize (number) : size of the legend keys (diameter for 'circle' and width for 'rect')
75     legendkeyoffsetx (number) : horizontal distance between the left border of the chart and the first top left legend key
76     legendkeyoffsety (number) : vertical distance between the top border of the chart and the first top left legend key
77     legendfont (string) : font family of the legend labels
78     legendfontsize (number) : font size of the legend labels
79     legendfontweight (string)(number) : font weight of the legend labels
80     legendfontcolor (string) : font color of the legend colors
81  o }
82  **
83  \*/
84
85  
86     function MVBarchart(paper, x, y, width, height, values, opts) {
87         opts = opts || {};
88         values = clearAr(values);
89        
90         var chartinst = this,
91             chart = paper.set(),
92             bars = paper.set(),
93             covers = paper.set(),
94             len = values.length,
95             gutter = parseFloat(opts.gutter || "0%"), 
96             barsteps = opts.barsteps || 20,
97             barFill = opts.barfill || false, // default no fill
98             colors = opts.colors || chartinst.colors,
99             axisLineColor = opts.axislinecolor || '#BABABA',
100             axisStrokeWidth = opts.axisstrokewidth || 2,
101             axisFont = opts.axisfont || "'Fontin Sans', Fontin-Sans, sans-serif",
102             axisFontSize = opts.axisfontsize || ((barsteps > 10) ? 12 : 14),
103             axisFontWeight = opts.axisfontweight || 'bold',
104             axisFontColor = opts.axisfontcolor || '#BABABA',
105             labelFont = opts.labelfont || "'Fontin Sans', Fontin-Sans, sans-serif",
106             labelSize = opts.labelsize || ((barsteps > 10) ? 10 : 12),
107             labelWeight = opts.labelweight || 'bold',
108             labelColors = opts.labelcolors || [],
109             labelBorderRadius = opts.labelborderradius || 4,
110             labelMaxHeight = opts.labelmaxheight || 30,
111             labelOffsetY = opts.labeloffsety || 20,
112             legendKeyShape = opts.legendkeyshape || 'circle',
113             legendKeySize = opts.legendkeysize || 16,
114             legendKeyOffsetX = typeof(opts.legendkeyoffsetx) != 'undefined' ? opts.legendkeyoffsetx : 10,
115             legendKeyOffsetY = typeof(opts.legendkeyoffsety) != 'undefined' ? opts.legendkeyoffsety : 10,
116             legendFont = opts.legendfont || "'Fontin Sans', Fontin-Sans, sans-serif",
117             legendFontSize = opts.legendfontsize || 14,
118             legendFontWeight = opts.legendfontweight || 'bold',
119             legendFontColor = opts.legendfontcolor || '#0C014D';
120
121         for(var i = 0; i < len; i++) {
122             labelColors.push('#FFFFFF');
123         }
124             
125         
126         opts.axis = opts.axis || "";
127         opts.baroffset = opts.baroffset || 10;
128         axisXHeight = opts.asixxheight || 100;
129         axisYWidth = opts.asixywidth || 100;
130         opts.axisxlabels = opts.axisxlabels || [];
131         opts.legendheight = opts.legendheight || 80;
132         opts.legends = opts.legends || 100;
133         
134         // background
135         paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") });
136 //        background.toBack();
137         
138         // bar border && bar gutter
139         // total 'barsteps' of bars and 'barsteps - 1' of gutters
140         var barWidth = (width - x - axisYWidth - opts.baroffset) / (barsteps * (100 + gutter) - gutter) * 100;
141         var barGutter = barWidth * gutter / 100;
142         var barHeight = height - y - axisXHeight - opts.legendheight || 100;
143         var path = [];
144         
145         for (var i = 0; i < barsteps; i++) {
146             path = path.concat(["M", x + (i * (barWidth + barGutter)), height - axisXHeight, "l", 0, -1 * barHeight, "l", barWidth, 0, "l", 0, barHeight]);
147
148             if(barFill) {
149                 paper.rect(x + (i * (barWidth + barGutter)) + 1 , y + opts.legendheight + 1, barWidth - 2, barHeight - 2).attr({ stroke: "none", fill: barFill });
150             }
151         }
152         
153         paper.path(path).attr({ stroke: "#FFF", "stroke-width": 2});
154         
155         var max = 0;
156         
157         values.forEach(function(value,i) {
158             if (!Raphael.is(values[0], "array")) {
159                 value = [value];
160             }
161     
162             value.forEach(function(v,k)  {
163                 if(k >= barsteps) {
164                     return;
165                 }
166                 max = (v > max) ? v : max;
167             });
168             
169         });
170         
171         // legends
172         values.forEach(function(value,i) {
173             if (!Raphael.is(value, "array")) {
174                 value = [value];
175             }
176
177             if(legendKeyShape == 'rect') {
178                 // pass top left position for 'rect'
179                 paper.rect(x + (i * (width - x) / len) + legendKeyOffsetX, y + legendKeyOffsetY, legendKeySize, legendKeySize, 0).attr({ fill: colors[i%colors.length], "stroke": "#fff" });
180             }
181             else {
182                 // pass center position for 'circle'
183                 paper.circle(x + (i * (width - x) / len) + legendKeyOffsetX + (legendKeySize / 2), y + legendKeyOffsetY + (legendKeySize / 2), legendKeySize / 2).attr({ fill: colors[i%colors.length], "stroke": "#fff" });
184             }
185             
186             // 10 pixels away from right of the legend key
187             //  align legend key and text horizontally
188             paper.text(x + (i * (width - x) / len) + legendKeyOffsetX + legendKeySize + 10, y + legendKeyOffsetY + (legendKeySize / 2) - legendFontSize / 10, opts.legends[i%opts.legends.length]).attr({ 
189                 "font-size": legendFontSize,
190                 "font-family": legendFont,
191                 "font-weight": legendFontWeight,
192                 "text-anchor": "start",
193                 fill : legendFontColor
194             });
195         });
196         
197         //bars
198         var unit = barHeight / max;
199         
200         values.forEach(function(value,i) {
201             if (!Raphael.is(values, "array")) {
202                 value = [value];
203             }
204             
205             value.forEach(function(v,k)  {
206                 if(v == 0 || k >= barsteps) {
207                     return;
208                 }
209                 
210                 paper.rect(x + (k * (barWidth + barGutter)) + 1 , y + opts.legendheight + 1 + (max - v) * unit, barWidth - 2, v * unit - 2).attr({ stroke: "none", fill: colors[i%colors.length] });
211
212                 // width and height of indicator
213                 var iw = barWidth * 0.8;
214                 var ih = Math.min(labelMaxHeight, iw * 0.5);
215
216                 // position of indicator
217                 var ix = x + (k * (barWidth + barGutter)) + 1 + (barWidth - iw) / 2;
218                 var iy = y + opts.legendheight + 1 + (max - v) * unit - ih - labelOffsetY;
219                 
220                 if(i == 0) {
221                     iy = y + opts.legendheight + 1 - ih - labelOffsetY;
222                 }
223                 
224                 paper.rect(ix , iy, iw, ih, labelBorderRadius).attr({ stroke: "none", fill: colors[i%colors.length] });
225                 paper.path(["M", ix + (iw - 10) / 2, iy + ih, "l", 5, 10, "l", 5, -10]).attr({ stroke: "none", fill: colors[i%colors.length] });
226                 // align labels center
227                 paper.text(ix + iw / 2, iy + ih / 2 - labelSize / 10, Roo.util.Format.number(v, 0)).attr({ 
228                     "font-size": labelSize,
229                     "font-family": labelFont,
230                     "font-weight": labelWeight,
231                     fill : labelColors[i]
232                 });
233             });
234         });
235         
236         var ax = (opts.axis + "").split(/[,\s]+/),
237             axis = paper.set();
238         
239         // right axis
240         +ax[1] && axis.push(
241             chartinst.rightAxis(
242                 width - axisYWidth,
243                 height - axisXHeight,
244                 height - y - axisXHeight - opts.legendheight,
245                 max,
246                 opts.axisystep || 10,
247                 axisYWidth,
248                 paper,
249                 axisLineColor,
250                 axisStrokeWidth,
251                 axisFont,
252                 axisFontSize,
253                 axisFontWeight,
254                 axisFontColor
255             )
256         );
257         
258         // bottom axis
259         +ax[2] && axis.push(
260             chartinst.bottomAxis(
261                 x ,
262                 height - axisXHeight,
263                 width - x - axisYWidth / 2,
264                 opts.axisxlabels,
265                 barsteps,
266                 barWidth,
267                 barGutter,
268                 axisXHeight,
269                 paper,
270                 axisLineColor,
271                 axisStrokeWidth,
272                 axisFont,
273                 axisFontSize,
274                 axisFontWeight,
275                 axisFontColor
276             )
277         );
278         
279         chart.push(bars, covers);
280         chart.bars = bars;
281         chart.covers = covers;
282         return chart;
283     };
284     
285     //inheritance
286     var F = function() {};
287     F.prototype = Raphael.g;
288     MVBarchart.prototype = new F; //prototype reused by hbarchart
289     
290     Raphael.fn.mbarchart = function(x, y, width, height, values, opts) {
291         return new MVBarchart(this, x, y, width, height, values, opts);
292     };
293     
294     MVBarchart.prototype.rightAxis = function (x, y, length, max, steps, axisYWidth, paper, axisLineColor, axisStrokeWidth, axisFont, axisFontSize, axisFontWeight, axisFontColor)
295     {
296 //        Roo.log('Right Axis');
297 //        Roo.log([x, y, length, max, steps, axisYWidth]);
298         
299         var path = [],
300             text = paper.set(),
301             d = Math.ceil(max / steps);
302         
303         var label = 0,
304             dx = length / steps;
305         
306         var Y = y;
307         
308         for(var i = 0; i <= steps; i++) {
309             
310             if(i != 0) {
311                 // 6 pixels away from bottom of the line
312                 paper.text(x + (axisYWidth / 2), Y + axisFontSize / 2 + 6, Roo.util.Format.number(label, 0)).attr({ 
313                     "font-size": axisFontSize,
314                     "font-family": axisFont,
315                     "font-weight": axisFontWeight,
316                     "text-anchor": "end",
317                     fill : axisFontColor
318                 });
319                 path = path.concat(["M", x , Y, "l", axisYWidth / 2, 0]);
320                 
321             }
322             
323             label += d;
324             Y -= dx;
325         }
326         
327         var res = paper.path(path).attr({ stroke: axisLineColor, "stroke-width": axisStrokeWidth });
328
329         res.text = text;
330         res.all = paper.set([res, text]);
331         res.remove = function () {
332             this.text.remove();
333             this.constructor.prototype.remove.call(this);
334         };
335
336         return res;
337     }
338     
339     MVBarchart.prototype.bottomAxis = function (x, y, length, labels, barsteps, barWidth, barGutter, height, paper, axisLineColor, axisStrokeWidth, axisFont, axisFontSize, axisFontWeight, axisFontColor)
340     {
341 //        Roo.log('Bottom Axis');
342 //        Roo.log([x, y, length, steps, labels, barWidth]);
343         
344         var path = ["M", x, y, "l", length, 0],
345             text = paper.set(),
346             offset = Math.round(barWidth / 2);
347     
348         labels.forEach(function(v,k)  {
349             if(k >= barsteps) {
350                 return;
351             }
352             paper.text(x + (k * (barWidth + barGutter)) + offset, y + (height / 2), v).attr({ 
353                 "font-size": axisFontSize,
354                 "font-family": axisFont,
355                 "font-weight": axisFontWeight,
356                 fill : axisFontColor
357             });
358         });
359         
360         var res = paper.path(path).attr({ stroke: axisLineColor, "stroke-width": axisStrokeWidth });
361
362         res.text = text;
363         res.all = paper.set([res, text]);
364         res.remove = function () {
365             this.text.remove();
366             this.constructor.prototype.remove.call(this);
367         };
368
369         return res;
370     }
371     
372 })();