fix #8056 - more refinements to checking data
[g.raphael] / g.line.js
1 /*!
2  * g.Raphael 0.5 - Charting library, based on RaphaĆ«l
3  *
4  * Copyright (c) 2009 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 shrink(values, dim) {
19         var k = values.length / dim,
20             j = 0,
21             l = k,
22             sum = 0,
23             res = [];
24
25         while (j < values.length) {
26             l--;
27
28             if (l < 0) {
29                 sum += values[j] * (1 + l);
30                 res.push(sum / k);
31                 sum = values[j++] * -l;
32                 l += k;
33             } else {
34                 sum += values[j++] * 1;
35             }
36         }
37         return res;
38     }
39
40     function getAnchors(p1x, p1y, p2x, p2y, p3x, p3y) {
41         var l1 = (p2x - p1x) / 2,
42             l2 = (p3x - p2x) / 2,
43             a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
44             b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y));
45
46         a = p1y < p2y ? Math.PI - a : a;
47         b = p3y < p2y ? Math.PI - b : b;
48
49         var alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2,
50             dx1 = l1 * Math.sin(alpha + a),
51             dy1 = l1 * Math.cos(alpha + a),
52             dx2 = l2 * Math.sin(alpha + b),
53             dy2 = l2 * Math.cos(alpha + b);
54
55         return {
56             x1: p2x - dx1,
57             y1: p2y + dy1,
58             x2: p2x + dx2,
59             y2: p2y + dy2
60         };
61     }
62
63     function Linechart(paper, x, y, width, height, valuesx, valuesy, opts) {
64         
65         var chartinst = this;
66         
67         opts = opts || {};
68         
69         if (!paper.raphael.is(valuesx[0], "array")) {
70             valuesx = [valuesx];
71         }
72
73         if (!paper.raphael.is(valuesy[0], "array")) {
74             valuesy = [valuesy];
75         }
76 //        Roo.log(valuesx);
77 //        Roo.log(valuesy);
78         var gutter = opts.gutter || 10,
79             len = Math.max(valuesx[0].length, valuesy[0].length),
80             symbol = opts.symbol || "",
81             colors = opts.colors || chartinst.colors,
82             columns = null,
83             dots = null,
84             chart = paper.set(),
85             path = [];
86
87         for (var i = 0, ii = valuesy.length; i < ii; i++) {
88             len = Math.max(len, valuesy[i].length);
89         }
90
91         var shades = paper.set();
92
93         for (i = 0, ii = valuesy.length; i < ii; i++) {
94             if (opts.shade) {
95                 shades.push(paper.path().attr({ stroke: "none", fill: colors[i], opacity: opts.nostroke ? 1 : .3 }));
96             }
97
98             if (valuesy[i].length > width - 2 * gutter) {
99                 valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
100                 len = width - 2 * gutter;
101             }
102
103             if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
104                 valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
105             }
106         }
107
108         var allx = Array.prototype.concat.apply([], valuesx),
109             ally = Array.prototype.concat.apply([], valuesy),
110             xdim = chartinst.snapEnds(
111                 Math.min.apply(Math, allx),
112                 Math.max.apply(Math, allx),
113                 valuesx[0].length - 1
114             ),
115             minx = xdim.from,
116             maxx = xdim.to,
117             ydim = chartinst.snapEnds(
118                     Math.min.apply(Math, ally),
119                     Math.max.apply(Math, ally),
120                     valuesy[0].length - 1
121             ),
122             miny = typeof(opts.ymin) == 'undefined' ? ydim.from : opts.ymin,
123             maxy = typeof(opts.maxy) == 'undefined' ? ydim.to : opts.maxy,
124             kx = (width - gutter * 2) / ((maxx - minx) || 1),
125             ky = (height - gutter * 2) / ((maxy - miny) || 1);
126
127         var axis = paper.set();
128         
129        // Roo.log("Checking for AXIS");
130         
131         if (opts.axis) {
132             
133             // Roo.log(opts.axis);
134             
135             // Value - "top right bottom left". If
136             var ax = (opts.axis + "").split(/[,\s]+/);
137             
138             // top axis
139             +ax[0] && axis.push(
140                     chartinst.axis(
141                         x + gutter,
142                         y + gutter,
143                         width - 2 * gutter,
144                         minx,
145                         maxx,
146                         opts.axisxstep || Math.floor((width - 2 * gutter) / 20),
147                         2,
148                         paper));
149             // right axis
150             +ax[1] && axis.push(
151                     chartinst.axis(
152                         x + width - gutter,
153                         y + height - gutter,
154                         height - 2 * gutter,
155                         miny, maxy,
156                         opts.axisystep || Math.floor((height - 2 * gutter) / 20),
157                         3,
158                         paper));
159                         
160             // bottom axis
161             opts.axisxlabels = opts.axisxlabels || [];
162             
163             opts.axisxstep  = opts.axisxstep || (opts.axisxlabels ? opts.axisxlabels.length -1 : false ) || len;
164             
165             var ax_args = {
166                     x : x + gutter,
167                     y: y + height - gutter,
168                     length : width - 2 * gutter + 20, // total width
169                     from: minx ,  // from -- infinity??? if no 'xvalues set'
170                     to : maxx, //to
171                     steps : opts.axisxstep , // 9 when we have 8 items?
172                     orientation : 0, // orientation
173                     labels : opts.axisxlabels || false, // labels
174                     type : "-", // type ofbarhgutter line
175                     dashsize : 5, // dash size
176                     paper: paper,
177                     loffset :   Math.round(gutter / 100 + (1)),
178                     roffset :   Math.round(gutter / 100 + (1)) + 20,
179             };
180             //Roo.log(ax_args);
181 //            +ax[2] && axis.push(
182 //                // bottom
183 //                // x, y, length, from, to, steps, orientation, labels, type, dashsize, paper
184  //               //chartinst.axis( ax_args, paper)
185  //           );
186             
187            +ax[2] && axis.push(
188                // bottom
189                // x, y, length, from, to, steps, orientation, labels, type, dashsize, paper
190                chartinst.axis(
191                    x + gutter,
192                    y + height - gutter,
193                    width - 2 * gutter,
194                    minx,
195                    maxx,
196                    opts.axisxstep || Math.floor((width - 2 * gutter) / 20),
197                    0,
198                    opts.axisxlabels || false,
199                    paper
200                    
201                    ));
202             // left axis
203             +ax[3] && axis.push(
204                 chartinst.axis(
205                 x + gutter,
206                 y + height - gutter,
207                 height - 2 * gutter,
208                 miny,
209                 maxy,
210                 opts.axisystep || Math.floor((height - 2 * gutter) / 20),
211                 1,
212                 paper
213             ));
214         }
215
216         var lines = paper.set(),
217             symbols = paper.set(),
218             line;
219
220         for (i = 0, ii = valuesy.length; i < ii; i++) {
221             if (!opts.nostroke) {
222                 lines.push(line = paper.path().attr({
223                     stroke: colors[i],
224                     "stroke-width": opts.width || 2,
225                     "stroke-linejoin": "round",
226                     "stroke-linecap": "round",
227                     "stroke-dasharray": opts.dash || ""
228                 }));
229             }
230             
231             var sym = Raphael.is(symbol, "array") ? symbol[i] : symbol,
232                 symset = paper.set();
233
234             path = [];
235
236             for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
237                 var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx,
238                     Y = y + height - gutter - (valuesy[i][j] - miny) * ky;
239
240                 (Raphael.is(sym, "array") ? sym[j] : sym) && symset.push(paper[Raphael.is(sym, "array") ? sym[j] : sym](X, Y, (opts.width || 2) * 3).attr({ fill: colors[i], stroke: "none" }));
241                 
242                 var vx = X - (opts.label_padding_x || 0);
243                 var vy = Y - (opts.label_padding_y || 10);
244                 
245                 if(opts.linelabels && opts.linelabels[i] && opts.linelabels[i][j]){
246                     paper.text(vx, vy, opts.linelabels[i][j]);
247                 }
248                 
249                 if (opts.smooth) {
250                     if (j && j != jj - 1) {
251                         var X0 = x + gutter + ((valuesx[i] || valuesx[0])[j - 1] - minx) * kx,
252                             Y0 = y + height - gutter - (valuesy[i][j - 1] - miny) * ky,
253                             X2 = x + gutter + ((valuesx[i] || valuesx[0])[j + 1] - minx) * kx,
254                             Y2 = y + height - gutter - (valuesy[i][j + 1] - miny) * ky,
255                             a = getAnchors(X0, Y0, X, Y, X2, Y2);
256
257                         path = path.concat([a.x1, a.y1, X, Y, a.x2, a.y2]);
258                     }
259
260                     if (!j) {
261                         path = ["M", X, Y, "C", X, Y];
262                     }
263                 } else {
264                     path = path.concat([j ? "L" : "M", X, Y]);
265                 }
266             }
267
268             if (opts.smooth) {
269                 path = path.concat([X, Y, X, Y]);
270             }
271
272             symbols.push(symset);
273
274             if (opts.shade) {
275                 shades[i].attr({ path: path.concat(["L", X, y + height - gutter, "L",  x + gutter + ((valuesx[i] || valuesx[0])[0] - minx) * kx, y + height - gutter, "z"]).join(",") });
276             }
277
278             !opts.nostroke && line.attr({ path: path.join(",") });
279         }
280
281         function createColumns(f) {
282             // unite Xs together
283             var Xs = [];
284
285             for (var i = 0, ii = valuesx.length; i < ii; i++) {
286                 Xs = Xs.concat(valuesx[i]);
287             }
288
289 //            Xs.sort();
290             Xs.sort(function(a,b) { return a - b; });
291             // remove duplicates
292
293             var Xs2 = [],
294                 xs = [];
295
296             for (i = 0, ii = Xs.length; i < ii; i++) {
297                 Xs[i] != Xs[i - 1] && Xs2.push(Xs[i]) && xs.push(x + gutter + (Xs[i] - minx) * kx);
298             }
299
300             Xs = Xs2;
301             ii = Xs.length;
302
303             var cvrs = f || paper.set();
304
305             for (i = 0; i < ii; i++) {
306                 var X = xs[i] - (xs[i] - (xs[i - 1] || x)) / 2,
307                     w = ((xs[i + 1] || x + width) - xs[i]) / 2 + (xs[i] - (xs[i - 1] || x)) / 2,
308                     C;
309
310                 f ? (C = {}) : cvrs.push(C = paper.rect(X - 1, y, Math.max(w + 1, 1), height).attr({ stroke: "none", fill: "#000", opacity: 0 }));
311                 C.values = [];
312                 C.symbols = paper.set();
313                 C.y = [];
314                 C.x = xs[i];
315                 C.axis = Xs[i];
316
317                 for (var j = 0, jj = valuesy.length; j < jj; j++) {
318                     Xs2 = valuesx[j] || valuesx[0];
319
320                     for (var k = 0, kk = Xs2.length; k < kk; k++) {
321                         if (Xs2[k] == Xs[i]) {
322                             C.values.push(valuesy[j][k]);
323                             C.y.push(y + height - gutter - (valuesy[j][k] - miny) * ky);
324                             C.symbols.push(chart.symbols[j][k]);
325                         }
326                     }
327                 }
328
329                 f && f.call(C);
330             }
331
332             !f && (columns = cvrs);
333         }
334
335         function createDots(f) {
336             var cvrs = f || paper.set(),
337                 C;
338
339             for (var i = 0, ii = valuesy.length; i < ii; i++) {
340                 for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
341                     var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx,
342                         nearX = x + gutter + ((valuesx[i] || valuesx[0])[j ? j - 1 : 1] - minx) * kx,
343                         Y = y + height - gutter - (valuesy[i][j] - miny) * ky;
344
345                     f ? (C = {}) : cvrs.push(C = paper.circle(X, Y, Math.abs(nearX - X) / 2).attr({ stroke: "none", fill: "#000", opacity: 0 }));
346                     C.x = X;
347                     C.y = Y;
348                     C.value = valuesy[i][j];
349                     C.line = chart.lines[i];
350                     C.shade = chart.shades[i];
351                     C.symbol = chart.symbols[i][j];
352                     C.symbols = chart.symbols[i];
353                     C.axis = (valuesx[i] || valuesx[0])[j];
354                     f && f.call(C);
355                 }
356             }
357
358             !f && (dots = cvrs);
359         }
360
361         chart.push(lines, shades, symbols, axis, columns, dots);
362         chart.lines = lines;
363         chart.shades = shades;
364         chart.symbols = symbols;
365         chart.axis = axis;
366
367         chart.hoverColumn = function (fin, fout) {
368             !columns && createColumns();
369             columns.mouseover(fin).mouseout(fout);
370             return this;
371         };
372
373         chart.clickColumn = function (f) {
374             !columns && createColumns();
375             columns.click(f);
376             return this;
377         };
378
379         chart.hrefColumn = function (cols) {
380             var hrefs = paper.raphael.is(arguments[0], "array") ? arguments[0] : arguments;
381
382             if (!(arguments.length - 1) && typeof cols == "object") {
383                 for (var x in cols) {
384                     for (var i = 0, ii = columns.length; i < ii; i++) if (columns[i].axis == x) {
385                         columns[i].attr("href", cols[x]);
386                     }
387                 }
388             }
389
390             !columns && createColumns();
391
392             for (i = 0, ii = hrefs.length; i < ii; i++) {
393                 columns[i] && columns[i].attr("href", hrefs[i]);
394             }
395
396             return this;
397         };
398
399         chart.hover = function (fin, fout) {
400             !dots && createDots();
401             dots.mouseover(fin).mouseout(fout);
402             return this;
403         };
404
405         chart.click = function (f) {
406             !dots && createDots();
407             dots.click(f);
408             return this;
409         };
410
411         chart.each = function (f) {
412             createDots(f);
413             return this;
414         };
415
416         chart.eachColumn = function (f) {
417             createColumns(f);
418             return this;
419         };
420
421         return chart;
422     };
423     
424     //inheritance
425     var F = function() {};
426     F.prototype = Raphael.g;
427     Linechart.prototype = new F;
428     
429     //public
430     Raphael.fn.linechart = function(x, y, width, height, valuesx, valuesy, opts) {
431         return new Linechart(this, x, y, width, height, valuesx, valuesy, opts);
432     }
433     
434     
435     
436     
437     
438     Raphael.fn.drawGrid = function (x, y, w, h, wv, hv, color) {
439         color = color || "#000";
440         var path = ["M",
441                         Math.round(x) + .5,
442                         Math.round(y) + .5,
443                         "L",
444                         Math.round(x + w) + .5,
445                         Math.round(y) + .5,
446                         Math.round(x + w) + .5,
447                         Math.round(y + h) + .5,
448                         Math.round(x) + .5,
449                         Math.round(y + h) + .5,
450                         Math.round(x) + .5,
451                         Math.round(y) + .5
452             ],
453             rowHeight = h / hv;
454             
455         //var columnWidth = w / wv;
456         var columnWidth = w / wv;
457
458         for (var i = 1; i < hv; i++) {
459             path = path.concat(["M", Math.round(x) + .5, Math.round(y + i * rowHeight) + .5, "H", Math.round(x + w) + .5]);
460         }
461         for (i = 1; i < wv; i++) {
462             path = path.concat(["M", Math.round(x + i * columnWidth) + .5, Math.round(y) + .5, "V", Math.round(y + h) + .5]);
463         }
464         return this.path(path.join(",")).attr({stroke: color});
465     };
466     
467 })();