[IMP] [FIX] kanban: updated justgage and sparklin libraries; fixed gauge widget showi...
[odoo/odoo.git] / addons / web / static / lib / justgage / justgage.js
1 /**\r
2  * JustGage - this is work-in-progress, unreleased, unofficial code, so it might not work top-notch :)\r
3  * Check http://www.justgage.com for official releases\r
4  * Licensed under MIT.\r
5  * @author Bojan Djuricic  (@Toorshia)\r
6  *\r
7  * LATEST UPDATES\r
8 \r
9  * -----------------------------\r
10  * April 18, 2013.\r
11  * -----------------------------\r
12      * parentNode - use this instead of id, to attach gauge to node which is outside of DOM tree - https://github.com/toorshia/justgage/issues/48\r
13      * width - force gauge width\r
14      * height - force gauge height\r
15 \r
16  * -----------------------------\r
17  * April 17, 2013.\r
18  * -----------------------------\r
19      * fix - https://github.com/toorshia/justgage/issues/49\r
20 \r
21  * -----------------------------\r
22  * April 01, 2013.\r
23  * -----------------------------\r
24      * fix - https://github.com/toorshia/justgage/issues/46\r
25 \r
26  * -----------------------------\r
27  * March 26, 2013.\r
28  * -----------------------------\r
29      * customSectors - define specific color for value range (0-10 : red, 10-30 : blue etc.)\r
30 \r
31  * -----------------------------\r
32  * March 23, 2013.\r
33  * -----------------------------\r
34      * counter - option to animate value  in counting fashion\r
35      * fix - https://github.com/toorshia/justgage/issues/45\r
36 \r
37  * -----------------------------\r
38  * March 13, 2013.\r
39  * -----------------------------\r
40      * refresh method - added optional 'max' parameter to use when you need to update max value\r
41 \r
42  * -----------------------------\r
43  * February 26, 2013.\r
44  * -----------------------------\r
45      * decimals - option to define/limit number of decimals when not using humanFriendly or customRenderer to display value\r
46      * fixed a missing parameters bug when calling generateShadow()  for IE < 9\r
47 \r
48  * -----------------------------\r
49  * December 31, 2012.\r
50  * -----------------------------\r
51      * fixed text y-position for hidden divs - workaround for Raphael <tspan> 'dy' bug - https://github.com/DmitryBaranovskiy/raphael/issues/491\r
52      * 'show' parameters, like showMinMax are now 'hide' because I am lame developer - please update these in your setups\r
53      * Min and Max labels are now auto-off when in donut mode\r
54      * Start angle in donut mode is now 90\r
55      * donutStartAngle - option to define start angle for donut\r
56 \r
57  * -----------------------------\r
58  * November 25, 2012.\r
59  * -----------------------------\r
60      * Option to define custom rendering function for displayed value\r
61 \r
62  * -----------------------------\r
63  * November 19, 2012.\r
64  * -----------------------------\r
65      * Config.value is now updated after gauge refresh\r
66 \r
67  * -----------------------------\r
68  * November 13, 2012.\r
69  * -----------------------------\r
70      * Donut display mode added\r
71      * Option to hide value label\r
72      * Option to enable responsive gauge size\r
73      * Removed default title attribute\r
74      * Option to accept min and max defined as string values\r
75      * Option to configure value symbol\r
76      * Fixed bad aspect ratio calculations\r
77      * Option to configure minimum font size for all texts\r
78      * Option to show shorthand big numbers (human friendly)\r
79      */\r
80 \r
81  JustGage = function(config) {\r
82 \r
83   // if (!config.id) {alert("Missing id parameter for gauge!"); return false;}\r
84   // if (!document.getElementById(config.id)) {alert("No element with id: \""+config.id+"\" found!"); return false;}\r
85 \r
86   var obj = this;\r
87 \r
88   // configurable parameters\r
89   obj.config =\r
90   {\r
91     // id : string\r
92     // this is container element id\r
93     id : config.id,\r
94 \r
95     // parentNode : node object\r
96     // this is container element\r
97     parentNode : (config.parentNode) ? config.parentNode : null,\r
98 \r
99     // width : int\r
100     // gauge width\r
101     width : (config.width) ? config.width : null,\r
102 \r
103     // height : int\r
104     // gauge height\r
105     height : (config.height) ? config.height : null,\r
106 \r
107     // title : string\r
108     // gauge title\r
109     title : (config.title) ? config.title : "",\r
110 \r
111     // titleFontColor : string\r
112     // color of gauge title\r
113     titleFontColor : (config.titleFontColor) ? config.titleFontColor : "#999999",\r
114 \r
115     // value : int\r
116     // value gauge is showing\r
117     value : (config.value) ? config.value : 0,\r
118 \r
119     // valueFontColor : string\r
120     // color of label showing current value\r
121     valueFontColor : (config.valueFontColor) ? config.valueFontColor : "#010101",\r
122 \r
123     // symbol : string\r
124     // special symbol to show next to value\r
125     symbol : (config.symbol) ? config.symbol : "",\r
126 \r
127     // min : int\r
128     // min value\r
129     min : (config.min !== undefined) ? parseFloat(config.min) : 0,\r
130 \r
131     // max : int\r
132     // max value\r
133     max : (config.max !== undefined) ? parseFloat(config.max) : 100,\r
134 \r
135     // humanFriendlyDecimal : int\r
136     // number of decimal places for our human friendly number to contain\r
137     humanFriendlyDecimal : (config.humanFriendlyDecimal) ? config.humanFriendlyDecimal : 0,\r
138 \r
139     // textRenderer: func\r
140     // function applied before rendering text\r
141     textRenderer  : (config.textRenderer) ? config.textRenderer : null,\r
142 \r
143     // gaugeWidthScale : float\r
144     // width of the gauge element\r
145     gaugeWidthScale : (config.gaugeWidthScale) ? config.gaugeWidthScale : 1.0,\r
146 \r
147     // gaugeColor : string\r
148     // background color of gauge element\r
149     gaugeColor : (config.gaugeColor) ? config.gaugeColor : "#edebeb",\r
150 \r
151     // label : string\r
152     // text to show below value\r
153     label : (config.label) ? config.label : "",\r
154 \r
155     // labelFontColor : string\r
156     // color of label showing label under value\r
157     labelFontColor : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3",\r
158 \r
159     // shadowOpacity : int\r
160     // 0 ~ 1\r
161     shadowOpacity : (config.shadowOpacity) ? config.shadowOpacity : 0.2,\r
162 \r
163     // shadowSize: int\r
164     // inner shadow size\r
165     shadowSize : (config.shadowSize) ? config.shadowSize : 5,\r
166 \r
167     // shadowVerticalOffset : int\r
168     // how much shadow is offset from top\r
169     shadowVerticalOffset : (config.shadowVerticalOffset) ? config.shadowVerticalOffset : 3,\r
170 \r
171     // levelColors : string[]\r
172     // colors of indicator, from lower to upper, in RGB format\r
173     levelColors : (config.levelColors) ? config.levelColors : [\r
174     "#a9d70b",\r
175     "#f9c802",\r
176     "#ff0000"\r
177     ],\r
178 \r
179     // startAnimationTime : int\r
180     // length of initial animation\r
181     startAnimationTime : (config.startAnimationTime) ? config.startAnimationTime : 700,\r
182 \r
183     // startAnimationType : string\r
184     // type of initial animation (linear, >, <,  <>, bounce)\r
185     startAnimationType : (config.startAnimationType) ? config.startAnimationType : ">",\r
186 \r
187     // refreshAnimationTime : int\r
188     // length of refresh animation\r
189     refreshAnimationTime : (config.refreshAnimationTime) ? config.refreshAnimationTime : 700,\r
190 \r
191     // refreshAnimationType : string\r
192     // type of refresh animation (linear, >, <,  <>, bounce)\r
193     refreshAnimationType : (config.refreshAnimationType) ? config.refreshAnimationType : ">",\r
194 \r
195     // donutStartAngle : int\r
196     // angle to start from when in donut mode\r
197     donutStartAngle : (config.donutStartAngle) ? config.donutStartAngle : 90,\r
198 \r
199     // valueMinFontSize : int\r
200     // absolute minimum font size for the value\r
201     valueMinFontSize : config.valueMinFontSize || 16,\r
202 \r
203     // titleMinFontSize\r
204     // absolute minimum font size for the title\r
205     titleMinFontSize : config.titleMinFontSize || 10,\r
206 \r
207     // labelMinFontSize\r
208     // absolute minimum font size for the label\r
209     labelMinFontSize : config.labelMinFontSize || 10,\r
210 \r
211     // minLabelMinFontSize\r
212     // absolute minimum font size for the minimum label\r
213     minLabelMinFontSize : config.minLabelMinFontSize || 10,\r
214 \r
215     // maxLabelMinFontSize\r
216     // absolute minimum font size for the maximum label\r
217     maxLabelMinFontSize : config.maxLabelMinFontSize || 10,\r
218 \r
219     // hideValue : bool\r
220     // hide value text\r
221     hideValue : (config.hideValue) ? config.hideValue : false,\r
222 \r
223     // hideMinMax : bool\r
224     // hide min and max values\r
225     hideMinMax : (config.hideMinMax) ? config.hideMinMax : false,\r
226 \r
227     // hideInnerShadow : bool\r
228     // hide inner shadow\r
229     hideInnerShadow : (config.hideInnerShadow) ? config.hideInnerShadow : false,\r
230 \r
231     // humanFriendly : bool\r
232     // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M)\r
233     humanFriendly : (config.humanFriendly) ? config.humanFriendly : false,\r
234 \r
235     // noGradient : bool\r
236     // whether to use gradual color change for value, or sector-based\r
237     noGradient : (config.noGradient) ? config.noGradient : false,\r
238 \r
239     // donut : bool\r
240     // show full donut gauge\r
241     donut : (config.donut) ? config.donut : false,\r
242 \r
243     // relativeGaugeSize : bool\r
244     // whether gauge size should follow changes in container element size\r
245     relativeGaugeSize : (config.relativeGaugeSize) ? config.relativeGaugeSize : false,\r
246 \r
247     // counter : bool\r
248     // animate level number change\r
249     counter : (config.counter) ? config.counter : false,\r
250 \r
251     // decimals : int\r
252     // number of digits after floating point\r
253     decimals : (config.decimals) ? config.decimals : 0,\r
254 \r
255     // customSectors : [] of objects\r
256     // number of digits after floating point\r
257     customSectors : (config.customSectors) ? config.customSectors : []\r
258   };\r
259 \r
260   // variables\r
261   var\r
262   canvasW,\r
263   canvasH,\r
264   widgetW,\r
265   widgetH,\r
266   aspect,\r
267   dx,\r
268   dy,\r
269   titleFontSize,\r
270   titleX,\r
271   titleY,\r
272   valueFontSize,\r
273   valueX,\r
274   valueY,\r
275   labelFontSize,\r
276   labelX,\r
277   labelY,\r
278   minFontSize,\r
279   minX,\r
280   minY,\r
281   maxFontSize,\r
282   maxX,\r
283   maxY;\r
284 \r
285   // overflow values\r
286   if (obj.config.value > obj.config.max) obj.config.value = obj.config.max;\r
287   if (obj.config.value < obj.config.min) obj.config.value = obj.config.min;\r
288   obj.originalValue = config.value;\r
289 \r
290   // create canvas\r
291   if (obj.config.id !== null && (document.getElementById(obj.config.id)) !== null) {\r
292     obj.canvas = Raphael(obj.config.id, "100%", "100%");\r
293   } else if (obj.config.parentNode !== null) {\r
294     obj.canvas = Raphael(obj.config.parentNode, "100%", "100%");\r
295   }\r
296 \r
297   if (obj.config.relativeGaugeSize === true) {\r
298     obj.canvas.setViewBox(0, 0, 200, 150, true);\r
299   }\r
300 \r
301   // canvas dimensions\r
302   if (obj.config.relativeGaugeSize === true) {\r
303     canvasW = 200;\r
304     canvasH = 150;\r
305   } else if (obj.config.width !== null && obj.config.height !== null) {\r
306     canvasW = obj.config.width;\r
307     canvasH = obj.config.height;\r
308   } else if (obj.config.parentNode !== null) {\r
309     obj.canvas.setViewBox(0, 0, 200, 150, true);\r
310     canvasW = 200;\r
311     canvasH = 150;\r
312   } else {\r
313     canvasW = getStyle(document.getElementById(obj.config.id), "width").slice(0, -2) * 1;\r
314     canvasH = getStyle(document.getElementById(obj.config.id), "height").slice(0, -2) * 1;\r
315   }\r
316 \r
317   // widget dimensions\r
318   if (obj.config.donut === true) {\r
319 \r
320     // DONUT *******************************\r
321 \r
322     // width more than height\r
323     if(canvasW > canvasH) {\r
324       widgetH = canvasH;\r
325       widgetW = widgetH;\r
326     // width less than height\r
327   } else if (canvasW < canvasH) {\r
328     widgetW = canvasW;\r
329     widgetH = widgetW;\r
330       // if height don't fit, rescale both\r
331       if(widgetH > canvasH) {\r
332         aspect = widgetH / canvasH;\r
333         widgetH = widgetH / aspect;\r
334         widgetW = widgetH / aspect;\r
335       }\r
336     // equal\r
337   } else {\r
338     widgetW = canvasW;\r
339     widgetH = widgetW;\r
340   }\r
341 \r
342     // delta\r
343     dx = (canvasW - widgetW)/2;\r
344     dy = (canvasH - widgetH)/2;\r
345 \r
346     // title\r
347     titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10;\r
348     titleX = dx + widgetW / 2;\r
349     titleY = dy + widgetH / 11;\r
350 \r
351     // value\r
352     valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18;\r
353     valueX = dx + widgetW / 2;\r
354     if(obj.config.label !== '') {\r
355       valueY = dy + widgetH / 1.85;\r
356     } else {\r
357       valueY = dy + widgetH / 1.7;\r
358     }\r
359 \r
360     // label\r
361     labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;\r
362     labelX = dx + widgetW / 2;\r
363     labelY = valueY + labelFontSize;\r
364 \r
365     // min\r
366     minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;\r
367     minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2 ;\r
368     minY = labelY;\r
369 \r
370     // max\r
371     maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;\r
372     maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2 ;\r
373     maxY = labelY;\r
374 \r
375   } else {\r
376     // HALF *******************************\r
377 \r
378     // width more than height\r
379     if(canvasW > canvasH) {\r
380       widgetH = canvasH;\r
381       widgetW = widgetH * 1.25;\r
382       //if width doesn't fit, rescale both\r
383       if(widgetW > canvasW) {\r
384         aspect = widgetW / canvasW;\r
385         widgetW = widgetW / aspect;\r
386         widgetH = widgetH / aspect;\r
387       }\r
388     // width less than height\r
389   } else if (canvasW < canvasH) {\r
390     widgetW = canvasW;\r
391     widgetH = widgetW / 1.25;\r
392       // if height don't fit, rescale both\r
393       if(widgetH > canvasH) {\r
394         aspect = widgetH / canvasH;\r
395         widgetH = widgetH / aspect;\r
396         widgetW = widgetH / aspect;\r
397       }\r
398     // equal\r
399   } else {\r
400     widgetW = canvasW;\r
401     widgetH = widgetW * 0.75;\r
402   }\r
403 \r
404     // delta\r
405     dx = (canvasW - widgetW)/2;\r
406     dy = (canvasH - widgetH)/2;\r
407 \r
408     // title\r
409     titleFontSize = ((widgetH / 8) > obj.config.titleMinFontSize) ? (widgetH / 10) : obj.config.titleMinFontSize;\r
410     titleX = dx + widgetW / 2;\r
411     titleY = dy + widgetH / 6.4;\r
412 \r
413     // value\r
414     valueFontSize = ((widgetH / 6.5) > obj.config.valueMinFontSize) ? (widgetH / 6.5) : obj.config.valueMinFontSize;\r
415     valueX = dx + widgetW / 2;\r
416     valueY = dy + widgetH / 1.275;\r
417 \r
418     // label\r
419     labelFontSize = ((widgetH / 16) > obj.config.labelMinFontSize) ? (widgetH / 16) : obj.config.labelMinFontSize;\r
420     labelX = dx + widgetW / 2;\r
421     labelY = valueY + valueFontSize / 2 + 5;\r
422 \r
423     // min\r
424     minFontSize = ((widgetH / 16) > obj.config.minLabelMinFontSize) ? (widgetH / 16) : obj.config.minLabelMinFontSize;\r
425     minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2 ;\r
426     minY = labelY;\r
427 \r
428     // max\r
429     maxFontSize = ((widgetH / 16) > obj.config.maxLabelMinFontSize) ? (widgetH / 16) : obj.config.maxLabelMinFontSize;\r
430     maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2 ;\r
431     maxY = labelY;\r
432   }\r
433 \r
434   // parameters\r
435   obj.params  = {\r
436     canvasW : canvasW,\r
437     canvasH : canvasH,\r
438     widgetW : widgetW,\r
439     widgetH : widgetH,\r
440     dx : dx,\r
441     dy : dy,\r
442     titleFontSize : titleFontSize,\r
443     titleX : titleX,\r
444     titleY : titleY,\r
445     valueFontSize : valueFontSize,\r
446     valueX : valueX,\r
447     valueY : valueY,\r
448     labelFontSize : labelFontSize,\r
449     labelX : labelX,\r
450     labelY : labelY,\r
451     minFontSize : minFontSize,\r
452     minX : minX,\r
453     minY : minY,\r
454     maxFontSize : maxFontSize,\r
455     maxX : maxX,\r
456     maxY : maxY\r
457   };\r
458 \r
459   // var clear\r
460   canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null\r
461 \r
462   // pki - custom attribute for generating gauge paths\r
463   obj.canvas.customAttributes.pki = function (value, min, max, w, h, dx, dy, gws, donut) {\r
464 \r
465     var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path;\r
466 \r
467     if (donut) {\r
468       alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;\r
469       Ro = w / 2 - w / 7;\r
470       Ri = Ro - w / 6.666666666666667 * gws;\r
471 \r
472       Cx = w / 2 + dx;\r
473       Cy = h / 1.95 + dy;\r
474 \r
475       Xo = w / 2 + dx + Ro * Math.cos(alpha);\r
476       Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);\r
477       Xi = w / 2 + dx + Ri * Math.cos(alpha);\r
478       Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);\r
479 \r
480       path += "M" + (Cx - Ri) + "," + Cy + " ";\r
481       path += "L" + (Cx - Ro) + "," + Cy + " ";\r
482       if (value > ((max - min) / 2)) {\r
483         path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " ";\r
484       }\r
485       path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";\r
486       path += "L" + Xi + "," + Yi + " ";\r
487       if (value > ((max - min) / 2)) {\r
488         path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " ";\r
489       }\r
490       path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";\r
491       path += "Z ";\r
492 \r
493       return { path: path };\r
494 \r
495     } else {\r
496       alpha = (1 - (value - min) / (max - min)) * Math.PI;\r
497       Ro = w / 2 - w / 10;\r
498       Ri = Ro - w / 6.666666666666667 * gws;\r
499 \r
500       Cx = w / 2 + dx;\r
501       Cy = h / 1.25 + dy;\r
502 \r
503       Xo = w / 2 + dx + Ro * Math.cos(alpha);\r
504       Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);\r
505       Xi = w / 2 + dx + Ri * Math.cos(alpha);\r
506       Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);\r
507 \r
508       path += "M" + (Cx - Ri) + "," + Cy + " ";\r
509       path += "L" + (Cx - Ro) + "," + Cy + " ";\r
510       path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";\r
511       path += "L" + Xi + "," + Yi + " ";\r
512       path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";\r
513       path += "Z ";\r
514 \r
515       return { path: path };\r
516     }\r
517 \r
518     // var clear\r
519     alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null;\r
520   };\r
521 \r
522   // gauge\r
523   obj.gauge = obj.canvas.path().attr({\r
524       "stroke": "none",\r
525       "fill": obj.config.gaugeColor,\r
526       pki: [\r
527         obj.config.max,\r
528         obj.config.min,\r
529         obj.config.max,\r
530         obj.params.widgetW,\r
531         obj.params.widgetH,\r
532         obj.params.dx,\r
533         obj.params.dy,\r
534         obj.config.gaugeWidthScale,\r
535         obj.config.donut\r
536       ]\r
537   });\r
538 \r
539   // level\r
540   obj.level = obj.canvas.path().attr({\r
541     "stroke": "none",\r
542     "fill": getColor(obj.config.value, (obj.config.value - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors),\r
543     pki: [\r
544       obj.config.min,\r
545       obj.config.min,\r
546       obj.config.max,\r
547       obj.params.widgetW,\r
548       obj.params.widgetH,\r
549       obj.params.dx,\r
550       obj.params.dy,\r
551       obj.config.gaugeWidthScale,\r
552       obj.config.donut\r
553     ]\r
554   });\r
555   if(obj.config.donut) {\r
556     obj.level.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW/2 + obj.params.dx) + ", " + (obj.params.widgetH/1.95 + obj.params.dy));\r
557   }\r
558 \r
559   // title\r
560   obj.txtTitle = obj.canvas.text(obj.params.titleX, obj.params.titleY, obj.config.title);\r
561   obj.txtTitle.attr({\r
562     "font-size":obj.params.titleFontSize,\r
563     "font-weight":"bold",\r
564     "font-family":"Arial",\r
565     "fill":obj.config.titleFontColor,\r
566     "fill-opacity":"1"\r
567   });\r
568   setDy(obj.txtTitle, obj.params.titleFontSize, obj.params.titleY);\r
569 \r
570   // value\r
571   obj.txtValue = obj.canvas.text(obj.params.valueX, obj.params.valueY, 0);\r
572   obj.txtValue.attr({\r
573     "font-size":obj.params.valueFontSize,\r
574     "font-weight":"bold",\r
575     "font-family":"Arial",\r
576     "fill":obj.config.valueFontColor,\r
577     "fill-opacity":"0"\r
578   });\r
579   setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);\r
580 \r
581   // label\r
582   obj.txtLabel = obj.canvas.text(obj.params.labelX, obj.params.labelY, obj.config.label);\r
583   obj.txtLabel.attr({\r
584     "font-size":obj.params.labelFontSize,\r
585     "font-weight":"normal",\r
586     "font-family":"Arial",\r
587     "fill":obj.config.labelFontColor,\r
588     "fill-opacity":"0"\r
589   });\r
590   setDy(obj.txtLabel, obj.params.labelFontSize, obj.params.labelY);\r
591 \r
592   // min\r
593   obj.txtMinimum = obj.config.min;\r
594   if( obj.config.humanFriendly ) obj.txtMinimum = humanFriendlyNumber( obj.config.min, obj.config.humanFriendlyDecimal );\r
595   obj.txtMin = obj.canvas.text(obj.params.minX, obj.params.minY, obj.txtMinimum);\r
596   obj.txtMin.attr({\r
597     "font-size":obj.params.minFontSize,\r
598     "font-weight":"normal",\r
599     "font-family":"Arial",\r
600     "fill":obj.config.labelFontColor,\r
601     "fill-opacity": (obj.config.hideMinMax || obj.config.donut)? "0" : "1"\r
602   });\r
603   setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY);\r
604 \r
605   // max\r
606   obj.txtMaximum = obj.config.max;\r
607   if( obj.config.humanFriendly ) obj.txtMaximum = humanFriendlyNumber( obj.config.max, obj.config.humanFriendlyDecimal );\r
608   obj.txtMax = obj.canvas.text(obj.params.maxX, obj.params.maxY, obj.txtMaximum);\r
609   obj.txtMax.attr({\r
610     "font-size":obj.params.maxFontSize,\r
611     "font-weight":"normal",\r
612     "font-family":"Arial",\r
613     "fill":obj.config.labelFontColor,\r
614     "fill-opacity": (obj.config.hideMinMax || obj.config.donut)? "0" : "1"\r
615   });\r
616   setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);\r
617 \r
618   var defs = obj.canvas.canvas.childNodes[1];\r
619   var svg = "http://www.w3.org/2000/svg";\r
620 \r
621   if (ie < 9) {\r
622     onCreateElementNsReady(function() {\r
623       obj.generateShadow(svg, defs);\r
624     });\r
625   } else {\r
626     obj.generateShadow(svg, defs);\r
627   }\r
628 \r
629   // var clear\r
630   defs, svg = null;\r
631 \r
632   // set value to display\r
633   if(obj.config.textRenderer) {\r
634     obj.originalValue = obj.config.textRenderer(obj.originalValue);\r
635   } else if(obj.config.humanFriendly) {\r
636     obj.originalValue = humanFriendlyNumber( obj.originalValue, obj.config.humanFriendlyDecimal ) + obj.config.symbol;\r
637   } else {\r
638     obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol;\r
639   }\r
640 \r
641   if(obj.config.counter === true) {\r
642     //on each animation frame\r
643     eve.on("raphael.anim.frame." + (obj.level.id), function() {\r
644       var currentValue = obj.level.attr("pki");\r
645       if(obj.config.textRenderer) {\r
646         obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue[0])));\r
647       } else if(obj.config.humanFriendly) {\r
648         obj.txtValue.attr("text", humanFriendlyNumber( Math.floor(currentValue[0]), obj.config.humanFriendlyDecimal ) + obj.config.symbol);\r
649       } else {\r
650         obj.txtValue.attr("text", (currentValue[0] * 1).toFixed(obj.config.decimals) + obj.config.symbol);\r
651       }\r
652       setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);\r
653       currentValue = null;\r
654     });\r
655     //on animation end\r
656     eve.on("raphael.anim.finish." + (obj.level.id), function() {\r
657       obj.txtValue.attr({"text" : obj.originalValue});\r
658       setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);\r
659     });\r
660   } else {\r
661     //on animation start\r
662     eve.on("raphael.anim.start." + (obj.level.id), function() {\r
663       obj.txtValue.attr({"text" : obj.originalValue});\r
664       setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);\r
665     });\r
666   }\r
667 \r
668   // animate gauge level, value & label\r
669   obj.level.animate({\r
670     pki: [\r
671       obj.config.value,\r
672       obj.config.min,\r
673       obj.config.max,\r
674       obj.params.widgetW,\r
675       obj.params.widgetH,\r
676       obj.params.dx,\r
677       obj.params.dy,\r
678       obj.config.gaugeWidthScale,\r
679       obj.config.donut\r
680     ]\r
681   }, obj.config.startAnimationTime, obj.config.startAnimationType);\r
682   obj.txtValue.animate({"fill-opacity":(obj.config.hideValue)?"0":"1"}, obj.config.startAnimationTime, obj.config.startAnimationType);\r
683   obj.txtLabel.animate({"fill-opacity":"1"}, obj.config.startAnimationTime, obj.config.startAnimationType);\r
684 };\r
685 \r
686 /** Refresh gauge level */\r
687 JustGage.prototype.refresh = function(val, max) {\r
688 \r
689   var obj = this;\r
690   var displayVal, color, max = max || null;\r
691 \r
692   // set new max\r
693   if(max !== null) {\r
694     obj.config.max = max;\r
695 \r
696     obj.txtMaximum = obj.config.max;\r
697     if( obj.config.humanFriendly ) obj.txtMaximum = humanFriendlyNumber( obj.config.max, obj.config.humanFriendlyDecimal );\r
698     obj.txtMax.attr({"text" : obj.txtMaximum});\r
699     setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);\r
700   }\r
701 \r
702   // overflow values\r
703   displayVal = val;\r
704   if ((val * 1) > (obj.config.max * 1)) {val = (obj.config.max * 1);}\r
705   if ((val * 1) < (obj.config.min * 1)) {val = (obj.config.min * 1);}\r
706 \r
707   color = getColor(val, (val - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors);\r
708 \r
709   if(obj.config.textRenderer) {\r
710     displayVal = obj.config.textRenderer(displayVal);\r
711   } else if( obj.config.humanFriendly ) {\r
712     displayVal = humanFriendlyNumber( displayVal, obj.config.humanFriendlyDecimal ) + obj.config.symbol;\r
713   } else {\r
714     displayVal = (displayVal * 1).toFixed(obj.config.decimals) + obj.config.symbol;\r
715   }\r
716   obj.originalValue = displayVal;\r
717   obj.config.value = val * 1;\r
718 \r
719   if(!obj.config.counter) {\r
720     obj.txtValue.attr({"text":displayVal});\r
721     setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);\r
722   }\r
723 \r
724   obj.level.animate({\r
725     pki: [\r
726       obj.config.value,\r
727       obj.config.min,\r
728       obj.config.max,\r
729       obj.params.widgetW,\r
730       obj.params.widgetH,\r
731       obj.params.dx,\r
732       obj.params.dy,\r
733       obj.config.gaugeWidthScale,\r
734       obj.config.donut\r
735     ],\r
736     "fill":color\r
737   },  obj.config.refreshAnimationTime, obj.config.refreshAnimationType);\r
738 \r
739   // var clear\r
740   obj, displayVal, color, max = null;\r
741 };\r
742 \r
743 /** Generate shadow */\r
744 JustGage.prototype.generateShadow = function(svg, defs) {\r
745 \r
746   var obj = this;\r
747   var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3;\r
748 \r
749   // FILTER\r
750   gaussFilter = document.createElementNS(svg,"filter");\r
751   gaussFilter.setAttribute("id","inner-shadow");\r
752   defs.appendChild(gaussFilter);\r
753 \r
754   // offset\r
755   feOffset = document.createElementNS(svg,"feOffset");\r
756   feOffset.setAttribute("dx", 0);\r
757   feOffset.setAttribute("dy", obj.config.shadowVerticalOffset);\r
758   gaussFilter.appendChild(feOffset);\r
759 \r
760   // blur\r
761   feGaussianBlur = document.createElementNS(svg,"feGaussianBlur");\r
762   feGaussianBlur.setAttribute("result","offset-blur");\r
763   feGaussianBlur.setAttribute("stdDeviation", obj.config.shadowSize);\r
764   gaussFilter.appendChild(feGaussianBlur);\r
765 \r
766   // composite 1\r
767   feComposite1 = document.createElementNS(svg,"feComposite");\r
768   feComposite1.setAttribute("operator","out");\r
769   feComposite1.setAttribute("in", "SourceGraphic");\r
770   feComposite1.setAttribute("in2","offset-blur");\r
771   feComposite1.setAttribute("result","inverse");\r
772   gaussFilter.appendChild(feComposite1);\r
773 \r
774   // flood\r
775   feFlood = document.createElementNS(svg,"feFlood");\r
776   feFlood.setAttribute("flood-color","black");\r
777   feFlood.setAttribute("flood-opacity", obj.config.shadowOpacity);\r
778   feFlood.setAttribute("result","color");\r
779   gaussFilter.appendChild(feFlood);\r
780 \r
781   // composite 2\r
782   feComposite2 = document.createElementNS(svg,"feComposite");\r
783   feComposite2.setAttribute("operator","in");\r
784   feComposite2.setAttribute("in", "color");\r
785   feComposite2.setAttribute("in2","inverse");\r
786   feComposite2.setAttribute("result","shadow");\r
787   gaussFilter.appendChild(feComposite2);\r
788 \r
789   // composite 3\r
790   feComposite3 = document.createElementNS(svg,"feComposite");\r
791   feComposite3.setAttribute("operator","over");\r
792   feComposite3.setAttribute("in", "shadow");\r
793   feComposite3.setAttribute("in2","SourceGraphic");\r
794   gaussFilter.appendChild(feComposite3);\r
795 \r
796   // set shadow\r
797   if (!obj.config.hideInnerShadow) {\r
798     obj.canvas.canvas.childNodes[2].setAttribute("filter", "url(#inner-shadow)");\r
799     obj.canvas.canvas.childNodes[3].setAttribute("filter", "url(#inner-shadow)");\r
800   }\r
801 \r
802   // var clear\r
803   gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null;\r
804 \r
805 };\r
806 \r
807 /** Get color for value */\r
808 function getColor(val, pct, col, noGradient, custSec) {\r
809 \r
810   var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color;\r
811   var noGradient = noGradient || custSec.length > 0;\r
812 \r
813   if(custSec.length > 0) {\r
814     for(var i = 0; i < custSec.length; i++) {\r
815       if(val > custSec[i].lo && val <= custSec[i].hi) {\r
816         return custSec[i].color;\r
817       }\r
818     }\r
819   }\r
820 \r
821   no = col.length;\r
822   if (no === 1) return col[0];\r
823   inc = (noGradient) ? (1 / no) : (1 / (no - 1));\r
824   colors = [];\r
825   for (var i = 0; i < col.length; i++) {\r
826     percentage = (noGradient) ? (inc * (i + 1)) : (inc * i);\r
827     rval = parseInt((cutHex(col[i])).substring(0,2),16);\r
828     gval = parseInt((cutHex(col[i])).substring(2,4),16);\r
829     bval = parseInt((cutHex(col[i])).substring(4,6),16);\r
830     colors[i] = { pct: percentage, color: { r: rval, g: gval, b: bval  } };\r
831   }\r
832 \r
833   if(pct === 0) {\r
834     return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')';\r
835   }\r
836 \r
837   for (var j = 0; j < colors.length; j++) {\r
838     if (pct <= colors[j].pct) {\r
839       if (noGradient) {\r
840         return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')';\r
841       } else {\r
842         lower = colors[j - 1];\r
843         upper = colors[j];\r
844         range = upper.pct - lower.pct;\r
845         rangePct = (pct - lower.pct) / range;\r
846         pctLower = 1 - rangePct;\r
847         pctUpper = rangePct;\r
848         color = {\r
849           r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),\r
850           g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),\r
851           b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)\r
852         };\r
853         return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';\r
854       }\r
855     }\r
856   }\r
857 \r
858 }\r
859 \r
860 /** Fix Raphael display:none tspan dy attribute bug */\r
861 function setDy(elem, fontSize, txtYpos) {\r
862   if (!ie || ie > 9) {\r
863     elem.node.firstChild.attributes.dy.value = 0;\r
864   }\r
865 }\r
866 \r
867 /** Random integer  */\r
868 function getRandomInt (min, max) {\r
869   return Math.floor(Math.random() * (max - min + 1)) + min;\r
870 }\r
871 \r
872 /**  Cut hex  */\r
873 function cutHex(str) {\r
874   return (str.charAt(0)=="#") ? str.substring(1,7):str;\r
875 }\r
876 \r
877 /**  Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */\r
878 function humanFriendlyNumber( n, d ) {\r
879   var p, d2, i, s;\r
880 \r
881   p = Math.pow;\r
882   d2 = p(10, d);\r
883   i = 7;\r
884   while( i ) {\r
885     s = p(10,i--*3);\r
886     if( s <= n ) {\r
887      n = Math.round(n*d2/s)/d2+"KMGTPE"[i];\r
888    }\r
889  }\r
890  return n;\r
891 }\r
892 \r
893 /**  Get style  */\r
894 function getStyle(oElm, strCssRule){\r
895   var strValue = "";\r
896   if(document.defaultView && document.defaultView.getComputedStyle){\r
897     strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);\r
898   }\r
899   else if(oElm.currentStyle){\r
900     strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){\r
901       return p1.toUpperCase();\r
902     });\r
903     strValue = oElm.currentStyle[strCssRule];\r
904   }\r
905   return strValue;\r
906 }\r
907 \r
908 /**  Create Element NS Ready  */\r
909 function onCreateElementNsReady(func) {\r
910   if (document.createElementNS !== undefined) {\r
911     func();\r
912   } else {\r
913     setTimeout(function() { onCreateElementNsReady(func); }, 100);\r
914   }\r
915 }\r
916 \r
917 /**  Get IE version  */\r
918 // ----------------------------------------------------------\r
919 // A short snippet for detecting versions of IE in JavaScript\r
920 // without resorting to user-agent sniffing\r
921 // ----------------------------------------------------------\r
922 // If you're not in IE (or IE version is less than 5) then:\r
923 // ie === undefined\r
924 // If you're in IE (>=5) then you can determine which version:\r
925 // ie === 7; // IE7\r
926 // Thus, to detect IE:\r
927 // if (ie) {}\r
928 // And to detect the version:\r
929 // ie === 6 // IE6\r
930 // ie > 7 // IE8, IE9 ...\r
931 // ie < 9 // Anything less than IE9\r
932 // ----------------------------------------------------------\r
933 // UPDATE: Now using Live NodeList idea from @jdalton\r
934 var ie = (function(){\r
935 \r
936   var undef,\r
937   v = 3,\r
938   div = document.createElement('div'),\r
939   all = div.getElementsByTagName('i');\r
940 \r
941   while (\r
942     div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',\r
943     all[0]\r
944     );\r
945     return v > 4 ? v : undef;\r
946 }());