flot-rails 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/README.md +2 -2
  2. data/lib/flot/rails/version.rb +2 -2
  3. data/vendor/assets/javascripts/excanvas.js +1428 -1427
  4. data/vendor/assets/javascripts/excanvas.min.js +1 -1
  5. data/vendor/assets/javascripts/jquery.colorhelpers.js +179 -179
  6. data/vendor/assets/javascripts/jquery.colorhelpers.min.js +21 -1
  7. data/vendor/assets/javascripts/jquery.flot.canvas.js +317 -0
  8. data/vendor/assets/javascripts/jquery.flot.canvas.min.js +28 -0
  9. data/vendor/assets/javascripts/jquery.flot.categories.js +190 -0
  10. data/vendor/assets/javascripts/jquery.flot.categories.min.js +44 -0
  11. data/vendor/assets/javascripts/jquery.flot.crosshair.js +176 -167
  12. data/vendor/assets/javascripts/jquery.flot.crosshair.min.js +59 -1
  13. data/vendor/assets/javascripts/jquery.flot.errorbars.js +353 -0
  14. data/vendor/assets/javascripts/jquery.flot.errorbars.min.js +63 -0
  15. data/vendor/assets/javascripts/jquery.flot.fillbetween.js +226 -183
  16. data/vendor/assets/javascripts/jquery.flot.fillbetween.min.js +30 -1
  17. data/vendor/assets/javascripts/jquery.flot.image.js +241 -238
  18. data/vendor/assets/javascripts/jquery.flot.image.min.js +53 -1
  19. data/vendor/assets/javascripts/jquery.flot.js +2980 -2599
  20. data/vendor/assets/javascripts/jquery.flot.min.js +26 -4
  21. data/vendor/assets/javascripts/jquery.flot.navigate.js +345 -336
  22. data/vendor/assets/javascripts/jquery.flot.navigate.min.js +96 -1
  23. data/vendor/assets/javascripts/jquery.flot.pie.js +561 -499
  24. data/vendor/assets/javascripts/jquery.flot.pie.min.js +56 -1
  25. data/vendor/assets/javascripts/jquery.flot.resize.js +60 -60
  26. data/vendor/assets/javascripts/jquery.flot.resize.min.js +21 -1
  27. data/vendor/assets/javascripts/jquery.flot.selection.js +360 -344
  28. data/vendor/assets/javascripts/jquery.flot.selection.min.js +79 -1
  29. data/vendor/assets/javascripts/jquery.flot.stack.js +188 -184
  30. data/vendor/assets/javascripts/jquery.flot.stack.min.js +36 -1
  31. data/vendor/assets/javascripts/jquery.flot.symbol.js +71 -70
  32. data/vendor/assets/javascripts/jquery.flot.symbol.min.js +14 -1
  33. data/vendor/assets/javascripts/jquery.flot.threshold.js +142 -103
  34. data/vendor/assets/javascripts/jquery.flot.threshold.min.js +43 -1
  35. data/vendor/assets/javascripts/jquery.flot.time.js +424 -0
  36. data/vendor/assets/javascripts/jquery.flot.time.min.js +9 -0
  37. metadata +25 -7
@@ -1 +1,96 @@
1
- (function(i){i.fn.drag=function(j,k,l){if(k){this.bind("dragstart",j)}if(l){this.bind("dragend",l)}return !j?this.trigger("drag"):this.bind("drag",k?k:j)};var d=i.event,c=d.special,h=c.drag={not:":input",distance:0,which:1,dragging:false,setup:function(j){j=i.extend({distance:h.distance,which:h.which,not:h.not},j||{});j.distance=e(j.distance);d.add(this,"mousedown",f,j);if(this.attachEvent){this.attachEvent("ondragstart",a)}},teardown:function(){d.remove(this,"mousedown",f);if(this===h.dragging){h.dragging=h.proxy=false}g(this,true);if(this.detachEvent){this.detachEvent("ondragstart",a)}}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}};function f(j){var k=this,l,m=j.data||{};if(m.elem){k=j.dragTarget=m.elem;j.dragProxy=h.proxy||k;j.cursorOffsetX=m.pageX-m.left;j.cursorOffsetY=m.pageY-m.top;j.offsetX=j.pageX-j.cursorOffsetX;j.offsetY=j.pageY-j.cursorOffsetY}else{if(h.dragging||(m.which>0&&j.which!=m.which)||i(j.target).is(m.not)){return}}switch(j.type){case"mousedown":i.extend(m,i(k).offset(),{elem:k,target:j.target,pageX:j.pageX,pageY:j.pageY});d.add(document,"mousemove mouseup",f,m);g(k,false);h.dragging=null;return false;case !h.dragging&&"mousemove":if(e(j.pageX-m.pageX)+e(j.pageY-m.pageY)<m.distance){break}j.target=m.target;l=b(j,"dragstart",k);if(l!==false){h.dragging=k;h.proxy=j.dragProxy=i(l||k)[0]}case"mousemove":if(h.dragging){l=b(j,"drag",k);if(c.drop){c.drop.allowed=(l!==false);c.drop.handler(j)}if(l!==false){break}j.type="mouseup"}case"mouseup":d.remove(document,"mousemove mouseup",f);if(h.dragging){if(c.drop){c.drop.handler(j)}b(j,"dragend",k)}g(k,true);h.dragging=h.proxy=m.elem=false;break}return true}function b(m,k,j){m.type=k;var l=i.event.handle.call(j,m);return l===false?false:l||m.result}function e(j){return Math.pow(j,2)}function a(){return(h.dragging===false)}function g(j,k){if(!j){return}j.unselectable=k?"off":"on";j.onselectstart=function(){return k};if(j.style){j.style.MozUserSelect=k?"":"none"}}})(jQuery);(function(f){var e=["DOMMouseScroll","mousewheel"];f.event.special.mousewheel={setup:function(){if(this.addEventListener){for(var a=e.length;a;){this.addEventListener(e[--a],d,false)}}else{this.onmousewheel=d}},teardown:function(){if(this.removeEventListener){for(var a=e.length;a;){this.removeEventListener(e[--a],d,false)}}else{this.onmousewheel=null}}};f.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}});function d(b){var h=[].slice.call(arguments,1),a=0,c=true;b=f.event.fix(b||window.event);b.type="mousewheel";if(b.wheelDelta){a=b.wheelDelta/120}if(b.detail){a=-b.detail/3}h.unshift(b,a);return f.event.handle.apply(this,h)}})(jQuery);(function(b){var a={xaxis:{zoomRange:null,panRange:null},zoom:{interactive:false,trigger:"dblclick",amount:1.5},pan:{interactive:false,cursor:"move",frameRate:20}};function c(o){function m(q,p){var r=o.offset();r.left=q.pageX-r.left;r.top=q.pageY-r.top;if(p){o.zoomOut({center:r})}else{o.zoom({center:r})}}function d(p,q){m(p,q<0);return false}var i="default",g=0,e=0,n=null;function f(p){if(p.which!=1){return false}var q=o.getPlaceholder().css("cursor");if(q){i=q}o.getPlaceholder().css("cursor",o.getOptions().pan.cursor);g=p.pageX;e=p.pageY}function j(q){var p=o.getOptions().pan.frameRate;if(n||!p){return}n=setTimeout(function(){o.pan({left:g-q.pageX,top:e-q.pageY});g=q.pageX;e=q.pageY;n=null},1/p*1000)}function h(p){if(n){clearTimeout(n);n=null}o.getPlaceholder().css("cursor",i);o.pan({left:g-p.pageX,top:e-p.pageY})}function l(q,p){var r=q.getOptions();if(r.zoom.interactive){p[r.zoom.trigger](m);p.mousewheel(d)}if(r.pan.interactive){p.bind("dragstart",{distance:10},f);p.bind("drag",j);p.bind("dragend",h)}}o.zoomOut=function(p){if(!p){p={}}if(!p.amount){p.amount=o.getOptions().zoom.amount}p.amount=1/p.amount;o.zoom(p)};o.zoom=function(q){if(!q){q={}}var x=q.center,r=q.amount||o.getOptions().zoom.amount,p=o.width(),t=o.height();if(!x){x={left:p/2,top:t/2}}var s=x.left/p,v=x.top/t,u={x:{min:x.left-s*p/r,max:x.left+(1-s)*p/r},y:{min:x.top-v*t/r,max:x.top+(1-v)*t/r}};b.each(o.getAxes(),function(z,C){var D=C.options,B=u[C.direction].min,w=u[C.direction].max,E=D.zoomRange;if(E===false){return}B=C.c2p(B);w=C.c2p(w);if(B>w){var A=B;B=w;w=A}var y=w-B;if(E&&((E[0]!=null&&y<E[0])||(E[1]!=null&&y>E[1]))){return}D.min=B;D.max=w});o.setupGrid();o.draw();if(!q.preventEvent){o.getPlaceholder().trigger("plotzoom",[o])}};o.pan=function(p){var q={x:+p.left,y:+p.top};if(isNaN(q.x)){q.x=0}if(isNaN(q.y)){q.y=0}b.each(o.getAxes(),function(s,u){var v=u.options,t,r,w=q[u.direction];t=u.c2p(u.p2c(u.min)+w),r=u.c2p(u.p2c(u.max)+w);var x=v.panRange;if(x===false){return}if(x){if(x[0]!=null&&x[0]>t){w=x[0]-t;t+=w;r+=w}if(x[1]!=null&&x[1]<r){w=x[1]-r;t+=w;r+=w}}v.min=t;v.max=r});o.setupGrid();o.draw();if(!p.preventEvent){o.getPlaceholder().trigger("plotpan",[o])}};function k(q,p){p.unbind(q.getOptions().zoom.trigger,m);p.unbind("mousewheel",d);p.unbind("dragstart",f);p.unbind("drag",j);p.unbind("dragend",h);if(n){clearTimeout(n)}}o.hooks.bindEvents.push(l);o.hooks.shutdown.push(k)}b.plot.plugins.push({init:c,options:a,name:"navigate",version:"1.3"})})(jQuery);
1
+ /* Flot plugin for adding the ability to pan and zoom the plot.
2
+
3
+ Copyright (c) 2007-2013 IOLA and Ole Laursen.
4
+ Licensed under the MIT license.
5
+
6
+ The default behaviour is double click and scrollwheel up/down to zoom in, drag
7
+ to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and
8
+ plot.pan( offset ) so you easily can add custom controls. It also fires
9
+ "plotpan" and "plotzoom" events, useful for synchronizing plots.
10
+
11
+ The plugin supports these options:
12
+
13
+ zoom: {
14
+ interactive: false
15
+ trigger: "dblclick" // or "click" for single click
16
+ amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
17
+ }
18
+
19
+ pan: {
20
+ interactive: false
21
+ cursor: "move" // CSS mouse cursor value used when dragging, e.g. "pointer"
22
+ frameRate: 20
23
+ }
24
+
25
+ xaxis, yaxis, x2axis, y2axis: {
26
+ zoomRange: null // or [ number, number ] (min range, max range) or false
27
+ panRange: null // or [ number, number ] (min, max) or false
28
+ }
29
+
30
+ "interactive" enables the built-in drag/click behaviour. If you enable
31
+ interactive for pan, then you'll have a basic plot that supports moving
32
+ around; the same for zoom.
33
+
34
+ "amount" specifies the default amount to zoom in (so 1.5 = 150%) relative to
35
+ the current viewport.
36
+
37
+ "cursor" is a standard CSS mouse cursor string used for visual feedback to the
38
+ user when dragging.
39
+
40
+ "frameRate" specifies the maximum number of times per second the plot will
41
+ update itself while the user is panning around on it (set to null to disable
42
+ intermediate pans, the plot will then not update until the mouse button is
43
+ released).
44
+
45
+ "zoomRange" is the interval in which zooming can happen, e.g. with zoomRange:
46
+ [1, 100] the zoom will never scale the axis so that the difference between min
47
+ and max is smaller than 1 or larger than 100. You can set either end to null
48
+ to ignore, e.g. [1, null]. If you set zoomRange to false, zooming on that axis
49
+ will be disabled.
50
+
51
+ "panRange" confines the panning to stay within a range, e.g. with panRange:
52
+ [-10, 20] panning stops at -10 in one end and at 20 in the other. Either can
53
+ be null, e.g. [-10, null]. If you set panRange to false, panning on that axis
54
+ will be disabled.
55
+
56
+ Example API usage:
57
+
58
+ plot = $.plot(...);
59
+
60
+ // zoom default amount in on the pixel ( 10, 20 )
61
+ plot.zoom({ center: { left: 10, top: 20 } });
62
+
63
+ // zoom out again
64
+ plot.zoomOut({ center: { left: 10, top: 20 } });
65
+
66
+ // zoom 200% in on the pixel (10, 20)
67
+ plot.zoom({ amount: 2, center: { left: 10, top: 20 } });
68
+
69
+ // pan 100 pixels to the left and 20 down
70
+ plot.pan({ left: -100, top: 20 })
71
+
72
+ Here, "center" specifies where the center of the zooming should happen. Note
73
+ that this is defined in pixel space, not the space of the data points (you can
74
+ use the p2c helpers on the axes in Flot to help you convert between these).
75
+
76
+ "amount" is the amount to zoom the viewport relative to the current range, so
77
+ 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You
78
+ can set the default in the options.
79
+
80
+ */
81
+ /*
82
+ jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
83
+ Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
84
+ */(function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY)<l.distance)break;h.target=l.target,k=f(h,"dragstart",j),k!==!1&&(d.dragging=j,d.proxy=h.dragProxy=a(k||j)[0]);case"mousemove":if(d.dragging){if(k=f(h,"drag",j),c.drop&&(c.drop.allowed=k!==!1,c.drop.handler(h)),k!==!1)break;h.type="mouseup"}case"mouseup":b.remove(document,"mousemove mouseup",e),d.dragging&&(c.drop&&c.drop.handler(h),f(h,"dragend",j)),i(j,!0),d.dragging=d.proxy=l.elem=!1}return!0}function f(b,c,d){b.type=c;var e=a.event.dispatch.call(d,b);return e===!1?!1:e||b.result}function g(a){return Math.pow(a,2)}function h(){return d.dragging===!1}function i(a,b){a&&(a.unselectable=b?"off":"on",a.onselectstart=function(){return b},a.style&&(a.style.MozUserSelect=b?"":"none"))}a.fn.drag=function(a,b,c){return b&&this.bind("dragstart",a),c&&this.bind("dragend",c),a?this.bind("drag",b?b:a):this.trigger("drag")};var b=a.event,c=b.special,d=c.drag={not:":input",distance:0,which:1,dragging:!1,setup:function(c){c=a.extend({distance:d.distance,which:d.which,not:d.not},c||{}),c.distance=g(c.distance),b.add(this,"mousedown",e,c),this.attachEvent&&this.attachEvent("ondragstart",h)},teardown:function(){b.remove(this,"mousedown",e),this===d.dragging&&(d.dragging=d.proxy=!1),i(this,!0),this.detachEvent&&this.detachEvent("ondragstart",h)}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}}})(jQuery);
85
+ /* jquery.mousewheel.min.js
86
+ * Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
87
+ * Licensed under the MIT License (LICENSE.txt).
88
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
89
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
90
+ * Thanks to: Seamus Leahy for adding deltaX and deltaY
91
+ *
92
+ * Version: 3.0.6
93
+ *
94
+ * Requires: 1.2.2+
95
+ */(function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;void 0!==b.axis&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);void 0!==b.wheelDeltaY&&(g=b.wheelDeltaY/120);void 0!==b.wheelDeltaX&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]=d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,!1);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,!1);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);
96
+ (function(e){function n(t){function n(e,n){var r=t.offset();r.left=e.pageX-r.left,r.top=e.pageY-r.top,n?t.zoomOut({center:r}):t.zoom({center:r})}function r(e,t){return n(e,t<0),!1}function a(e){if(e.which!=1)return!1;var n=t.getPlaceholder().css("cursor");n&&(i=n),t.getPlaceholder().css("cursor",t.getOptions().pan.cursor),s=e.pageX,o=e.pageY}function f(e){var n=t.getOptions().pan.frameRate;if(u||!n)return;u=setTimeout(function(){t.pan({left:s-e.pageX,top:o-e.pageY}),s=e.pageX,o=e.pageY,u=null},1/n*1e3)}function l(e){u&&(clearTimeout(u),u=null),t.getPlaceholder().css("cursor",i),t.pan({left:s-e.pageX,top:o-e.pageY})}function c(e,t){var i=e.getOptions();i.zoom.interactive&&(t[i.zoom.trigger](n),t.mousewheel(r)),i.pan.interactive&&(t.bind("dragstart",{distance:10},a),t.bind("drag",f),t.bind("dragend",l))}function h(e,t){t.unbind(e.getOptions().zoom.trigger,n),t.unbind("mousewheel",r),t.unbind("dragstart",a),t.unbind("drag",f),t.unbind("dragend",l),u&&clearTimeout(u)}var i="default",s=0,o=0,u=null;t.zoomOut=function(e){e||(e={}),e.amount||(e.amount=t.getOptions().zoom.amount),e.amount=1/e.amount,t.zoom(e)},t.zoom=function(n){n||(n={});var r=n.center,i=n.amount||t.getOptions().zoom.amount,s=t.width(),o=t.height();r||(r={left:s/2,top:o/2});var u=r.left/s,a=r.top/o,f={x:{min:r.left-u*s/i,max:r.left+(1-u)*s/i},y:{min:r.top-a*o/i,max:r.top+(1-a)*o/i}};e.each(t.getAxes(),function(e,t){var n=t.options,r=f[t.direction].min,i=f[t.direction].max,s=n.zoomRange,o=n.panRange;if(s===!1)return;r=t.c2p(r),i=t.c2p(i);if(r>i){var u=r;r=i,i=u}o&&(o[0]!=null&&r<o[0]&&(r=o[0]),o[1]!=null&&i>o[1]&&(i=o[1]));var a=i-r;if(s&&(s[0]!=null&&a<s[0]||s[1]!=null&&a>s[1]))return;n.min=r,n.max=i}),t.setupGrid(),t.draw(),n.preventEvent||t.getPlaceholder().trigger("plotzoom",[t,n])},t.pan=function(n){var r={x:+n.left,y:+n.top};isNaN(r.x)&&(r.x=0),isNaN(r.y)&&(r.y=0),e.each(t.getAxes(),function(e,t){var n=t.options,i,s,o=r[t.direction];i=t.c2p(t.p2c(t.min)+o),s=t.c2p(t.p2c(t.max)+o);var u=n.panRange;if(u===!1)return;u&&(u[0]!=null&&u[0]>i&&(o=u[0]-i,i+=o,s+=o),u[1]!=null&&u[1]<s&&(o=u[1]-s,i+=o,s+=o)),n.min=i,n.max=s}),t.setupGrid(),t.draw(),n.preventEvent||t.getPlaceholder().trigger("plotpan",[t,n])},t.hooks.bindEvents.push(c),t.hooks.shutdown.push(h)}var t={xaxis:{zoomRange:null,panRange:null},zoom:{interactive:!1,trigger:"dblclick",amount:1.5},pan:{interactive:!1,cursor:"move",frameRate:20}};e.plot.plugins.push({init:n,options:t,name:"navigate",version:"1.3"})})(jQuery);
@@ -1,659 +1,714 @@
1
- /*
2
- Flot plugin for rendering pie charts. The plugin assumes the data is
3
- coming is as a single data value for each series, and each of those
4
- values is a positive value or zero (negative numbers don't make
5
- any sense and will cause strange effects). The data values do
6
- NOT need to be passed in as percentage values because it
7
- internally calculates the total and percentages.
8
-
9
- * Created by Brian Medendorp, June 2009
10
- * Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars
11
-
12
- * Changes:
13
- 2009-10-22: lineJoin set to round
14
- 2009-10-23: IE full circle fix, donut
15
- 2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera
16
- 2009-11-17: Added IE hover capability submitted by Anthony Aragues
17
- 2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well)
18
-
19
-
20
- Available options are:
21
- series: {
22
- pie: {
23
- show: true/false
24
- radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
25
- innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
26
- startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
27
- tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
28
- offset: {
29
- top: integer value to move the pie up or down
30
- left: integer value to move the pie left or right, or 'auto'
31
- },
32
- stroke: {
33
- color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
34
- width: integer pixel width of the stroke
35
- },
36
- label: {
37
- show: true/false, or 'auto'
38
- formatter: a user-defined function that modifies the text/style of the label text
39
- radius: 0-1 for percentage of fullsize, or a specified pixel length
40
- background: {
41
- color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
42
- opacity: 0-1
1
+ /* Flot plugin for rendering pie charts.
2
+
3
+ Copyright (c) 2007-2013 IOLA and Ole Laursen.
4
+ Licensed under the MIT license.
5
+
6
+ The plugin assumes that each series has a single data value, and that each
7
+ value is a positive integer or zero. Negative numbers don't make sense for a
8
+ pie chart, and have unpredictable results. The values do NOT need to be
9
+ passed in as percentages; the plugin will calculate the total and per-slice
10
+ percentages internally.
11
+
12
+ * Created by Brian Medendorp
13
+
14
+ * Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
15
+
16
+ The plugin supports these options:
17
+
18
+ series: {
19
+ pie: {
20
+ show: true/false
21
+ radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
22
+ innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
23
+ startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
24
+ tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
25
+ offset: {
26
+ top: integer value to move the pie up or down
27
+ left: integer value to move the pie left or right, or 'auto'
43
28
  },
44
- threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
45
- },
46
- combine: {
47
- threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
48
- color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
49
- label: any text value of what the combined slice should be labeled
50
- }
51
- highlight: {
52
- opacity: 0-1
29
+ stroke: {
30
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
31
+ width: integer pixel width of the stroke
32
+ },
33
+ label: {
34
+ show: true/false, or 'auto'
35
+ formatter: a user-defined function that modifies the text/style of the label text
36
+ radius: 0-1 for percentage of fullsize, or a specified pixel length
37
+ background: {
38
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
39
+ opacity: 0-1
40
+ },
41
+ threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
42
+ },
43
+ combine: {
44
+ threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
45
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
46
+ label: any text value of what the combined slice should be labeled
47
+ }
48
+ highlight: {
49
+ opacity: 0-1
50
+ }
53
51
  }
54
52
  }
55
- }
56
53
 
57
54
  More detail and specific examples can be found in the included HTML file.
58
55
 
59
56
  */
60
57
 
61
- (function ($)
62
- {
63
- function init(plot) // this is the "body" of the plugin
64
- {
65
- var canvas = null;
66
- var target = null;
67
- var maxRadius = null;
68
- var centerLeft = null;
69
- var centerTop = null;
70
- var total = 0;
71
- var redraw = true;
72
- var redrawAttempts = 10;
73
- var shrink = 0.95;
74
- var legendWidth = 0;
75
- var processed = false;
76
- var raw = false;
77
-
78
- // interactive variables
79
- var highlights = [];
80
-
58
+ (function($) {
59
+
60
+ // Maximum redraw attempts when fitting labels within the plot
61
+
62
+ var REDRAW_ATTEMPTS = 10;
63
+
64
+ // Factor by which to shrink the pie when fitting labels within the plot
65
+
66
+ var REDRAW_SHRINK = 0.95;
67
+
68
+ function init(plot) {
69
+
70
+ var canvas = null,
71
+ target = null,
72
+ maxRadius = null,
73
+ centerLeft = null,
74
+ centerTop = null,
75
+ processed = false,
76
+ ctx = null;
77
+
78
+ // interactive variables
79
+
80
+ var highlights = [];
81
+
81
82
  // add hook to determine if pie plugin in enabled, and then perform necessary operations
82
- plot.hooks.processOptions.push(checkPieEnabled);
83
- plot.hooks.bindEvents.push(bindEvents);
84
-
85
- // check to see if the pie plugin is enabled
86
- function checkPieEnabled(plot, options)
87
- {
88
- if (options.series.pie.show)
89
- {
90
- //disable grid
83
+
84
+ plot.hooks.processOptions.push(function(plot, options) {
85
+ if (options.series.pie.show) {
86
+
91
87
  options.grid.show = false;
92
-
88
+
93
89
  // set labels.show
94
- if (options.series.pie.label.show=='auto')
95
- if (options.legend.show)
90
+
91
+ if (options.series.pie.label.show == "auto") {
92
+ if (options.legend.show) {
96
93
  options.series.pie.label.show = false;
97
- else
94
+ } else {
98
95
  options.series.pie.label.show = true;
99
-
96
+ }
97
+ }
98
+
100
99
  // set radius
101
- if (options.series.pie.radius=='auto')
102
- if (options.series.pie.label.show)
100
+
101
+ if (options.series.pie.radius == "auto") {
102
+ if (options.series.pie.label.show) {
103
103
  options.series.pie.radius = 3/4;
104
- else
104
+ } else {
105
105
  options.series.pie.radius = 1;
106
-
106
+ }
107
+ }
108
+
107
109
  // ensure sane tilt
108
- if (options.series.pie.tilt>1)
109
- options.series.pie.tilt=1;
110
- if (options.series.pie.tilt<0)
111
- options.series.pie.tilt=0;
112
-
113
- // add processData hook to do transformations on the data
114
- plot.hooks.processDatapoints.push(processDatapoints);
115
- plot.hooks.drawOverlay.push(drawOverlay);
116
-
117
- // add draw hook
118
- plot.hooks.draw.push(draw);
110
+
111
+ if (options.series.pie.tilt > 1) {
112
+ options.series.pie.tilt = 1;
113
+ } else if (options.series.pie.tilt < 0) {
114
+ options.series.pie.tilt = 0;
115
+ }
119
116
  }
120
- }
121
-
122
- // bind hoverable events
123
- function bindEvents(plot, eventHolder)
124
- {
117
+ });
118
+
119
+ plot.hooks.bindEvents.push(function(plot, eventHolder) {
125
120
  var options = plot.getOptions();
126
-
127
- if (options.series.pie.show && options.grid.hoverable)
128
- eventHolder.unbind('mousemove').mousemove(onMouseMove);
129
-
130
- if (options.series.pie.show && options.grid.clickable)
131
- eventHolder.unbind('click').click(onClick);
132
- }
133
-
134
-
135
- // debugging function that prints out an object
136
- function alertObject(obj)
137
- {
138
- var msg = '';
139
- function traverse(obj, depth)
140
- {
141
- if (!depth)
142
- depth = 0;
143
- for (var i = 0; i < obj.length; ++i)
144
- {
145
- for (var j=0; j<depth; j++)
146
- msg += '\t';
147
-
148
- if( typeof obj[i] == "object")
149
- { // its an object
150
- msg += ''+i+':\n';
151
- traverse(obj[i], depth+1);
152
- }
153
- else
154
- { // its a value
155
- msg += ''+i+': '+obj[i]+'\n';
156
- }
121
+ if (options.series.pie.show) {
122
+ if (options.grid.hoverable) {
123
+ eventHolder.unbind("mousemove").mousemove(onMouseMove);
124
+ }
125
+ if (options.grid.clickable) {
126
+ eventHolder.unbind("click").click(onClick);
157
127
  }
158
128
  }
159
- traverse(obj);
160
- alert(msg);
161
- }
162
-
163
- function calcTotal(data)
164
- {
165
- for (var i = 0; i < data.length; ++i)
166
- {
167
- var item = parseFloat(data[i].data[0][1]);
168
- if (item)
169
- total += item;
129
+ });
130
+
131
+ plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
132
+ var options = plot.getOptions();
133
+ if (options.series.pie.show) {
134
+ processDatapoints(plot, series, data, datapoints);
170
135
  }
171
- }
172
-
173
- function processDatapoints(plot, series, data, datapoints)
174
- {
175
- if (!processed)
176
- {
136
+ });
137
+
138
+ plot.hooks.drawOverlay.push(function(plot, octx) {
139
+ var options = plot.getOptions();
140
+ if (options.series.pie.show) {
141
+ drawOverlay(plot, octx);
142
+ }
143
+ });
144
+
145
+ plot.hooks.draw.push(function(plot, newCtx) {
146
+ var options = plot.getOptions();
147
+ if (options.series.pie.show) {
148
+ draw(plot, newCtx);
149
+ }
150
+ });
151
+
152
+ function processDatapoints(plot, series, datapoints) {
153
+ if (!processed) {
177
154
  processed = true;
178
-
179
155
  canvas = plot.getCanvas();
180
156
  target = $(canvas).parent();
181
157
  options = plot.getOptions();
182
-
183
158
  plot.setData(combine(plot.getData()));
184
159
  }
185
160
  }
186
-
187
- function setupPie()
188
- {
189
- legendWidth = target.children().filter('.legend').children().width();
190
-
191
- // calculate maximum radius and center point
192
- maxRadius = Math.min(canvas.width,(canvas.height/options.series.pie.tilt))/2;
193
- centerTop = (canvas.height/2)+options.series.pie.offset.top;
194
- centerLeft = (canvas.width/2);
195
-
196
- if (options.series.pie.offset.left=='auto')
197
- if (options.legend.position.match('w'))
198
- centerLeft += legendWidth/2;
199
- else
200
- centerLeft -= legendWidth/2;
201
- else
202
- centerLeft += options.series.pie.offset.left;
203
-
204
- if (centerLeft<maxRadius)
205
- centerLeft = maxRadius;
206
- else if (centerLeft>canvas.width-maxRadius)
207
- centerLeft = canvas.width-maxRadius;
208
- }
209
-
210
- function fixData(data)
211
- {
212
- for (var i = 0; i < data.length; ++i)
213
- {
214
- if (typeof(data[i].data)=='number')
215
- data[i].data = [[1,data[i].data]];
216
- else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined')
217
- {
218
- if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined')
219
- data[i].label = data[i].data.label; // fix weirdness coming from flot
220
- data[i].data = [[1,0]];
221
-
161
+
162
+ function combine(data) {
163
+
164
+ var total = 0,
165
+ combined = 0,
166
+ numCombined = 0,
167
+ color = options.series.pie.combine.color,
168
+ newdata = [];
169
+
170
+ // Fix up the raw data from Flot, ensuring the data is numeric
171
+
172
+ for (var i = 0; i < data.length; ++i) {
173
+
174
+ var value = data[i].data;
175
+
176
+ // If the data is an array, we'll assume that it's a standard
177
+ // Flot x-y pair, and are concerned only with the second value.
178
+
179
+ // Note how we use the original array, rather than creating a
180
+ // new one; this is more efficient and preserves any extra data
181
+ // that the user may have stored in higher indexes.
182
+
183
+ if ($.isArray(value)) {
184
+ if ($.isNumeric(value[1])) {
185
+ value[1] = +value[1];
186
+ } else {
187
+ value[1] = 0;
188
+ }
189
+ } else if ($.isNumeric(value)) {
190
+ value = [1, +value];
191
+ } else {
192
+ value = [1, 0];
222
193
  }
194
+
195
+ data[i].data = [value];
223
196
  }
224
- return data;
225
- }
226
-
227
- function combine(data)
228
- {
229
- data = fixData(data);
230
- calcTotal(data);
231
- var combined = 0;
232
- var numCombined = 0;
233
- var color = options.series.pie.combine.color;
234
-
235
- var newdata = [];
236
- for (var i = 0; i < data.length; ++i)
237
- {
238
- // make sure its a number
239
- data[i].data[0][1] = parseFloat(data[i].data[0][1]);
240
- if (!data[i].data[0][1])
241
- data[i].data[0][1] = 0;
242
-
243
- if (data[i].data[0][1]/total<=options.series.pie.combine.threshold)
244
- {
245
- combined += data[i].data[0][1];
197
+
198
+ // Sum up all the slices, so we can calculate percentages for each
199
+
200
+ for (var i = 0; i < data.length; ++i) {
201
+ total += data[i].data[0][1];
202
+ }
203
+
204
+ // Count the number of slices with percentages below the combine
205
+ // threshold; if it turns out to be just one, we won't combine.
206
+
207
+ for (var i = 0; i < data.length; ++i) {
208
+ var value = data[i].data[0][1];
209
+ if (value / total <= options.series.pie.combine.threshold) {
210
+ combined += value;
246
211
  numCombined++;
247
- if (!color)
212
+ if (!color) {
248
213
  color = data[i].color;
249
- }
250
- else
251
- {
214
+ }
215
+ }
216
+ }
217
+
218
+ for (var i = 0; i < data.length; ++i) {
219
+ var value = data[i].data[0][1];
220
+ if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
252
221
  newdata.push({
253
- data: [[1,data[i].data[0][1]]],
254
- color: data[i].color,
222
+ data: [[1, value]],
223
+ color: data[i].color,
255
224
  label: data[i].label,
256
- angle: (data[i].data[0][1]*(Math.PI*2))/total,
257
- percent: (data[i].data[0][1]/total*100)
225
+ angle: value * Math.PI * 2 / total,
226
+ percent: value / (total / 100)
258
227
  });
259
228
  }
260
229
  }
261
- if (numCombined>0)
230
+
231
+ if (numCombined > 1) {
262
232
  newdata.push({
263
- data: [[1,combined]],
264
- color: color,
233
+ data: [[1, combined]],
234
+ color: color,
265
235
  label: options.series.pie.combine.label,
266
- angle: (combined*(Math.PI*2))/total,
267
- percent: (combined/total*100)
236
+ angle: combined * Math.PI * 2 / total,
237
+ percent: combined / (total / 100)
268
238
  });
239
+ }
240
+
269
241
  return newdata;
270
- }
271
-
272
- function draw(plot, newCtx)
273
- {
274
- if (!target) return; // if no series were passed
242
+ }
243
+
244
+ function draw(plot, newCtx) {
245
+
246
+ if (!target) {
247
+ return; // if no series were passed
248
+ }
249
+
250
+ var canvasWidth = plot.getPlaceholder().width(),
251
+ canvasHeight = plot.getPlaceholder().height(),
252
+ legendWidth = target.children().filter(".legend").children().width() || 0;
253
+
275
254
  ctx = newCtx;
276
-
277
- setupPie();
278
- var slices = plot.getData();
279
-
280
- var attempts = 0;
281
- while (redraw && attempts<redrawAttempts)
282
- {
283
- redraw = false;
284
- if (attempts>0)
285
- maxRadius *= shrink;
255
+
256
+ // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
257
+
258
+ // When combining smaller slices into an 'other' slice, we need to
259
+ // add a new series. Since Flot gives plugins no way to modify the
260
+ // list of series, the pie plugin uses a hack where the first call
261
+ // to processDatapoints results in a call to setData with the new
262
+ // list of series, then subsequent processDatapoints do nothing.
263
+
264
+ // The plugin-global 'processed' flag is used to control this hack;
265
+ // it starts out false, and is set to true after the first call to
266
+ // processDatapoints.
267
+
268
+ // Unfortunately this turns future setData calls into no-ops; they
269
+ // call processDatapoints, the flag is true, and nothing happens.
270
+
271
+ // To fix this we'll set the flag back to false here in draw, when
272
+ // all series have been processed, so the next sequence of calls to
273
+ // processDatapoints once again starts out with a slice-combine.
274
+ // This is really a hack; in 0.9 we need to give plugins a proper
275
+ // way to modify series before any processing begins.
276
+
277
+ processed = false;
278
+
279
+ // calculate maximum radius and center point
280
+
281
+ maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
282
+ centerTop = canvasHeight / 2 + options.series.pie.offset.top;
283
+ centerLeft = canvasWidth / 2;
284
+
285
+ if (options.series.pie.offset.left == "auto") {
286
+ if (options.legend.position.match("w")) {
287
+ centerLeft += legendWidth / 2;
288
+ } else {
289
+ centerLeft -= legendWidth / 2;
290
+ }
291
+ } else {
292
+ centerLeft += options.series.pie.offset.left;
293
+ }
294
+
295
+ if (centerLeft < maxRadius) {
296
+ centerLeft = maxRadius;
297
+ } else if (centerLeft > canvasWidth - maxRadius) {
298
+ centerLeft = canvasWidth - maxRadius;
299
+ }
300
+
301
+ var slices = plot.getData(),
302
+ attempts = 0;
303
+
304
+ // Keep shrinking the pie's radius until drawPie returns true,
305
+ // indicating that all the labels fit, or we try too many times.
306
+
307
+ do {
308
+ if (attempts > 0) {
309
+ maxRadius *= REDRAW_SHRINK;
310
+ }
286
311
  attempts += 1;
287
312
  clear();
288
- if (options.series.pie.tilt<=0.8)
313
+ if (options.series.pie.tilt <= 0.8) {
289
314
  drawShadow();
290
- drawPie();
291
- }
292
- if (attempts >= redrawAttempts) {
315
+ }
316
+ } while (!drawPie() && attempts < REDRAW_ATTEMPTS)
317
+
318
+ if (attempts >= REDRAW_ATTEMPTS) {
293
319
  clear();
294
- target.prepend('<div class="error">Could not draw pie with labels contained inside canvas</div>');
320
+ target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
295
321
  }
296
-
297
- if ( plot.setSeries && plot.insertLegend )
298
- {
322
+
323
+ if (plot.setSeries && plot.insertLegend) {
299
324
  plot.setSeries(slices);
300
325
  plot.insertLegend();
301
326
  }
302
-
327
+
303
328
  // we're actually done at this point, just defining internal functions at this point
304
-
305
- function clear()
306
- {
307
- ctx.clearRect(0,0,canvas.width,canvas.height);
308
- target.children().filter('.pieLabel, .pieLabelBackground').remove();
329
+
330
+ function clear() {
331
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
332
+ target.children().filter(".pieLabel, .pieLabelBackground").remove();
309
333
  }
310
-
311
- function drawShadow()
312
- {
313
- var shadowLeft = 5;
314
- var shadowTop = 15;
334
+
335
+ function drawShadow() {
336
+
337
+ var shadowLeft = options.series.pie.shadow.left;
338
+ var shadowTop = options.series.pie.shadow.top;
315
339
  var edge = 10;
316
- var alpha = 0.02;
317
-
318
- // set radius
319
- if (options.series.pie.radius>1)
320
- var radius = options.series.pie.radius;
321
- else
322
- var radius = maxRadius * options.series.pie.radius;
323
-
324
- if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge)
340
+ var alpha = options.series.pie.shadow.alpha;
341
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
342
+
343
+ if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
325
344
  return; // shadow would be outside canvas, so don't draw it
326
-
345
+ }
346
+
327
347
  ctx.save();
328
348
  ctx.translate(shadowLeft,shadowTop);
329
349
  ctx.globalAlpha = alpha;
330
- ctx.fillStyle = '#000';
350
+ ctx.fillStyle = "#000";
331
351
 
332
352
  // center and rotate to starting position
353
+
333
354
  ctx.translate(centerLeft,centerTop);
334
355
  ctx.scale(1, options.series.pie.tilt);
335
-
356
+
336
357
  //radius -= edge;
337
- for (var i=1; i<=edge; i++)
338
- {
358
+
359
+ for (var i = 1; i <= edge; i++) {
339
360
  ctx.beginPath();
340
- ctx.arc(0,0,radius,0,Math.PI*2,false);
361
+ ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
341
362
  ctx.fill();
342
363
  radius -= i;
343
- }
344
-
364
+ }
365
+
345
366
  ctx.restore();
346
367
  }
347
-
348
- function drawPie()
349
- {
350
- startAngle = Math.PI*options.series.pie.startAngle;
351
-
352
- // set radius
353
- if (options.series.pie.radius>1)
354
- var radius = options.series.pie.radius;
355
- else
356
- var radius = maxRadius * options.series.pie.radius;
357
-
368
+
369
+ function drawPie() {
370
+
371
+ var startAngle = Math.PI * options.series.pie.startAngle;
372
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
373
+
358
374
  // center and rotate to starting position
375
+
359
376
  ctx.save();
360
377
  ctx.translate(centerLeft,centerTop);
361
378
  ctx.scale(1, options.series.pie.tilt);
362
379
  //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
363
-
380
+
364
381
  // draw slices
382
+
365
383
  ctx.save();
366
384
  var currentAngle = startAngle;
367
- for (var i = 0; i < slices.length; ++i)
368
- {
385
+ for (var i = 0; i < slices.length; ++i) {
369
386
  slices[i].startAngle = currentAngle;
370
387
  drawSlice(slices[i].angle, slices[i].color, true);
371
388
  }
372
389
  ctx.restore();
373
-
390
+
374
391
  // draw slice outlines
375
- ctx.save();
376
- ctx.lineWidth = options.series.pie.stroke.width;
377
- currentAngle = startAngle;
378
- for (var i = 0; i < slices.length; ++i)
379
- drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
380
- ctx.restore();
381
-
392
+
393
+ if (options.series.pie.stroke.width > 0) {
394
+ ctx.save();
395
+ ctx.lineWidth = options.series.pie.stroke.width;
396
+ currentAngle = startAngle;
397
+ for (var i = 0; i < slices.length; ++i) {
398
+ drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
399
+ }
400
+ ctx.restore();
401
+ }
402
+
382
403
  // draw donut hole
404
+
383
405
  drawDonutHole(ctx);
384
-
385
- // draw labels
386
- if (options.series.pie.label.show)
387
- drawLabels();
388
-
389
- // restore to original state
406
+
390
407
  ctx.restore();
391
-
392
- function drawSlice(angle, color, fill)
393
- {
394
- if (angle<=0)
408
+
409
+ // Draw the labels, returning true if they fit within the plot
410
+
411
+ if (options.series.pie.label.show) {
412
+ return drawLabels();
413
+ } else return true;
414
+
415
+ function drawSlice(angle, color, fill) {
416
+
417
+ if (angle <= 0 || isNaN(angle)) {
395
418
  return;
396
-
397
- if (fill)
419
+ }
420
+
421
+ if (fill) {
398
422
  ctx.fillStyle = color;
399
- else
400
- {
423
+ } else {
401
424
  ctx.strokeStyle = color;
402
- ctx.lineJoin = 'round';
425
+ ctx.lineJoin = "round";
403
426
  }
404
-
427
+
405
428
  ctx.beginPath();
406
- if (Math.abs(angle - Math.PI*2) > 0.000000001)
407
- ctx.moveTo(0,0); // Center of the pie
408
- else if ($.browser.msie)
409
- angle -= 0.0001;
410
- //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera
411
- ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false);
429
+ if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
430
+ ctx.moveTo(0, 0); // Center of the pie
431
+ }
432
+
433
+ //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
434
+ ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
435
+ ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
412
436
  ctx.closePath();
413
437
  //ctx.rotate(angle); // This doesn't work properly in Opera
414
438
  currentAngle += angle;
415
-
416
- if (fill)
439
+
440
+ if (fill) {
417
441
  ctx.fill();
418
- else
442
+ } else {
419
443
  ctx.stroke();
444
+ }
420
445
  }
421
-
422
- function drawLabels()
423
- {
446
+
447
+ function drawLabels() {
448
+
424
449
  var currentAngle = startAngle;
425
-
426
- // set radius
427
- if (options.series.pie.label.radius>1)
428
- var radius = options.series.pie.label.radius;
429
- else
430
- var radius = maxRadius * options.series.pie.label.radius;
431
-
432
- for (var i = 0; i < slices.length; ++i)
433
- {
434
- if (slices[i].percent >= options.series.pie.label.threshold*100)
435
- drawLabel(slices[i], currentAngle, i);
450
+ var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
451
+
452
+ for (var i = 0; i < slices.length; ++i) {
453
+ if (slices[i].percent >= options.series.pie.label.threshold * 100) {
454
+ if (!drawLabel(slices[i], currentAngle, i)) {
455
+ return false;
456
+ }
457
+ }
436
458
  currentAngle += slices[i].angle;
437
459
  }
438
-
439
- function drawLabel(slice, startAngle, index)
440
- {
441
- if (slice.data[0][1]==0)
442
- return;
443
-
460
+
461
+ return true;
462
+
463
+ function drawLabel(slice, startAngle, index) {
464
+
465
+ if (slice.data[0][1] == 0) {
466
+ return true;
467
+ }
468
+
444
469
  // format label text
470
+
445
471
  var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
446
- if (lf)
472
+
473
+ if (lf) {
447
474
  text = lf(slice.label, slice);
448
- else
475
+ } else {
449
476
  text = slice.label;
450
- if (plf)
477
+ }
478
+
479
+ if (plf) {
451
480
  text = plf(text, slice);
452
-
453
- var halfAngle = ((startAngle+slice.angle) + startAngle)/2;
481
+ }
482
+
483
+ var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
454
484
  var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
455
485
  var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
456
-
457
- var html = '<span class="pieLabel" id="pieLabel'+index+'" style="position:absolute;top:' + y + 'px;left:' + x + 'px;">' + text + "</span>";
486
+
487
+ var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
458
488
  target.append(html);
459
- var label = target.children('#pieLabel'+index);
460
- var labelTop = (y - label.height()/2);
461
- var labelLeft = (x - label.width()/2);
462
- label.css('top', labelTop);
463
- label.css('left', labelLeft);
464
-
489
+
490
+ var label = target.children("#pieLabel" + index);
491
+ var labelTop = (y - label.height() / 2);
492
+ var labelLeft = (x - label.width() / 2);
493
+
494
+ label.css("top", labelTop);
495
+ label.css("left", labelLeft);
496
+
465
497
  // check to make sure that the label is not outside the canvas
466
- if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0)
467
- redraw = true;
468
-
498
+
499
+ if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
500
+ return false;
501
+ }
502
+
469
503
  if (options.series.pie.label.background.opacity != 0) {
504
+
470
505
  // put in the transparent background separately to avoid blended labels and label boxes
506
+
471
507
  var c = options.series.pie.label.background.color;
508
+
472
509
  if (c == null) {
473
510
  c = slice.color;
474
511
  }
475
- var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;';
476
- $('<div class="pieLabelBackground" style="position:absolute;width:' + label.width() + 'px;height:' + label.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').insertBefore(label).css('opacity', options.series.pie.label.background.opacity);
512
+
513
+ var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
514
+ $("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
515
+ .css("opacity", options.series.pie.label.background.opacity)
516
+ .insertBefore(label);
477
517
  }
518
+
519
+ return true;
478
520
  } // end individual label function
479
521
  } // end drawLabels function
480
522
  } // end drawPie function
481
523
  } // end draw function
482
-
483
- // Placed here because it needs to be accessed from multiple locations
484
- function drawDonutHole(layer)
485
- {
486
- // draw donut hole
487
- if(options.series.pie.innerRadius > 0)
488
- {
524
+
525
+ // Placed here because it needs to be accessed from multiple locations
526
+
527
+ function drawDonutHole(layer) {
528
+ if (options.series.pie.innerRadius > 0) {
529
+
489
530
  // subtract the center
531
+
490
532
  layer.save();
491
- innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
492
- layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color
533
+ var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
534
+ layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
493
535
  layer.beginPath();
494
536
  layer.fillStyle = options.series.pie.stroke.color;
495
- layer.arc(0,0,innerRadius,0,Math.PI*2,false);
537
+ layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
496
538
  layer.fill();
497
539
  layer.closePath();
498
540
  layer.restore();
499
-
541
+
500
542
  // add inner stroke
543
+
501
544
  layer.save();
502
545
  layer.beginPath();
503
546
  layer.strokeStyle = options.series.pie.stroke.color;
504
- layer.arc(0,0,innerRadius,0,Math.PI*2,false);
547
+ layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
505
548
  layer.stroke();
506
549
  layer.closePath();
507
550
  layer.restore();
551
+
508
552
  // TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
509
553
  }
510
554
  }
511
-
555
+
512
556
  //-- Additional Interactive related functions --
513
-
514
- function isPointInPoly(poly, pt)
515
- {
557
+
558
+ function isPointInPoly(poly, pt) {
516
559
  for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
517
560
  ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
518
561
  && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
519
562
  && (c = !c);
520
563
  return c;
521
564
  }
522
-
523
- function findNearbySlice(mouseX, mouseY)
524
- {
565
+
566
+ function findNearbySlice(mouseX, mouseY) {
567
+
525
568
  var slices = plot.getData(),
526
569
  options = plot.getOptions(),
527
- radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
528
-
529
- for (var i = 0; i < slices.length; ++i)
530
- {
531
- var s = slices[i];
532
-
533
- if(s.pie.show)
534
- {
570
+ radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
571
+ x, y;
572
+
573
+ for (var i = 0; i < slices.length; ++i) {
574
+
575
+ var s = slices[i];
576
+
577
+ if (s.pie.show) {
578
+
535
579
  ctx.save();
536
580
  ctx.beginPath();
537
- ctx.moveTo(0,0); // Center of the pie
581
+ ctx.moveTo(0, 0); // Center of the pie
538
582
  //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
539
- ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false);
583
+ ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
584
+ ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
540
585
  ctx.closePath();
541
- x = mouseX-centerLeft;
542
- y = mouseY-centerTop;
543
- if(ctx.isPointInPath)
544
- {
545
- if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop))
546
- {
547
- //alert('found slice!');
586
+ x = mouseX - centerLeft;
587
+ y = mouseY - centerTop;
588
+
589
+ if (ctx.isPointInPath) {
590
+ if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
548
591
  ctx.restore();
549
- return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i};
592
+ return {
593
+ datapoint: [s.percent, s.data],
594
+ dataIndex: 0,
595
+ series: s,
596
+ seriesIndex: i
597
+ };
550
598
  }
551
- }
552
- else
553
- {
554
- // excanvas for IE doesn;t support isPointInPath, this is a workaround.
555
- p1X = (radius * Math.cos(s.startAngle));
556
- p1Y = (radius * Math.sin(s.startAngle));
557
- p2X = (radius * Math.cos(s.startAngle+(s.angle/4)));
558
- p2Y = (radius * Math.sin(s.startAngle+(s.angle/4)));
559
- p3X = (radius * Math.cos(s.startAngle+(s.angle/2)));
560
- p3Y = (radius * Math.sin(s.startAngle+(s.angle/2)));
561
- p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5)));
562
- p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5)));
563
- p5X = (radius * Math.cos(s.startAngle+s.angle));
564
- p5Y = (radius * Math.sin(s.startAngle+s.angle));
565
- arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]];
566
- arrPoint = [x,y];
599
+ } else {
600
+
601
+ // excanvas for IE doesn;t support isPointInPath, this is a workaround.
602
+
603
+ var p1X = radius * Math.cos(s.startAngle),
604
+ p1Y = radius * Math.sin(s.startAngle),
605
+ p2X = radius * Math.cos(s.startAngle + s.angle / 4),
606
+ p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
607
+ p3X = radius * Math.cos(s.startAngle + s.angle / 2),
608
+ p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
609
+ p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
610
+ p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
611
+ p5X = radius * Math.cos(s.startAngle + s.angle),
612
+ p5Y = radius * Math.sin(s.startAngle + s.angle),
613
+ arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
614
+ arrPoint = [x, y];
615
+
567
616
  // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
568
- if(isPointInPoly(arrPoly, arrPoint))
569
- {
617
+
618
+ if (isPointInPoly(arrPoly, arrPoint)) {
570
619
  ctx.restore();
571
- return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i};
572
- }
620
+ return {
621
+ datapoint: [s.percent, s.data],
622
+ dataIndex: 0,
623
+ series: s,
624
+ seriesIndex: i
625
+ };
626
+ }
573
627
  }
628
+
574
629
  ctx.restore();
575
630
  }
576
631
  }
577
-
632
+
578
633
  return null;
579
634
  }
580
635
 
581
- function onMouseMove(e)
582
- {
583
- triggerClickHoverEvent('plothover', e);
636
+ function onMouseMove(e) {
637
+ triggerClickHoverEvent("plothover", e);
638
+ }
639
+
640
+ function onClick(e) {
641
+ triggerClickHoverEvent("plotclick", e);
584
642
  }
585
-
586
- function onClick(e)
587
- {
588
- triggerClickHoverEvent('plotclick', e);
589
- }
590
643
 
591
644
  // trigger click or hover event (they send the same parameters so we share their code)
592
- function triggerClickHoverEvent(eventname, e)
593
- {
594
- var offset = plot.offset(),
595
- canvasX = parseInt(e.pageX - offset.left),
596
- canvasY = parseInt(e.pageY - offset.top),
597
- item = findNearbySlice(canvasX, canvasY);
598
-
599
- if (options.grid.autoHighlight)
600
- {
645
+
646
+ function triggerClickHoverEvent(eventname, e) {
647
+
648
+ var offset = plot.offset();
649
+ var canvasX = parseInt(e.pageX - offset.left);
650
+ var canvasY = parseInt(e.pageY - offset.top);
651
+ var item = findNearbySlice(canvasX, canvasY);
652
+
653
+ if (options.grid.autoHighlight) {
654
+
601
655
  // clear auto-highlights
602
- for (var i = 0; i < highlights.length; ++i)
603
- {
656
+
657
+ for (var i = 0; i < highlights.length; ++i) {
604
658
  var h = highlights[i];
605
- if (h.auto == eventname && !(item && h.series == item.series))
659
+ if (h.auto == eventname && !(item && h.series == item.series)) {
606
660
  unhighlight(h.series);
661
+ }
607
662
  }
608
663
  }
609
-
664
+
610
665
  // highlight the slice
611
- if (item)
612
- highlight(item.series, eventname);
613
-
666
+
667
+ if (item) {
668
+ highlight(item.series, eventname);
669
+ }
670
+
614
671
  // trigger any hover bind events
672
+
615
673
  var pos = { pageX: e.pageX, pageY: e.pageY };
616
- target.trigger(eventname, [ pos, item ]);
674
+ target.trigger(eventname, [pos, item]);
617
675
  }
618
676
 
619
- function highlight(s, auto)
620
- {
621
- if (typeof s == "number")
622
- s = series[s];
677
+ function highlight(s, auto) {
678
+ //if (typeof s == "number") {
679
+ // s = series[s];
680
+ //}
623
681
 
624
682
  var i = indexOfHighlight(s);
625
- if (i == -1)
626
- {
683
+
684
+ if (i == -1) {
627
685
  highlights.push({ series: s, auto: auto });
628
686
  plot.triggerRedrawOverlay();
629
- }
630
- else if (!auto)
687
+ } else if (!auto) {
631
688
  highlights[i].auto = false;
689
+ }
632
690
  }
633
691
 
634
- function unhighlight(s)
635
- {
636
- if (s == null)
637
- {
692
+ function unhighlight(s) {
693
+ if (s == null) {
638
694
  highlights = [];
639
695
  plot.triggerRedrawOverlay();
640
696
  }
641
-
642
- if (typeof s == "number")
643
- s = series[s];
697
+
698
+ //if (typeof s == "number") {
699
+ // s = series[s];
700
+ //}
644
701
 
645
702
  var i = indexOfHighlight(s);
646
- if (i != -1)
647
- {
703
+
704
+ if (i != -1) {
648
705
  highlights.splice(i, 1);
649
706
  plot.triggerRedrawOverlay();
650
707
  }
651
708
  }
652
709
 
653
- function indexOfHighlight(s)
654
- {
655
- for (var i = 0; i < highlights.length; ++i)
656
- {
710
+ function indexOfHighlight(s) {
711
+ for (var i = 0; i < highlights.length; ++i) {
657
712
  var h = highlights[i];
658
713
  if (h.series == s)
659
714
  return i;
@@ -661,65 +716,71 @@ More detail and specific examples can be found in the included HTML file.
661
716
  return -1;
662
717
  }
663
718
 
664
- function drawOverlay(plot, octx)
665
- {
666
- //alert(options.series.pie.radius);
719
+ function drawOverlay(plot, octx) {
720
+
667
721
  var options = plot.getOptions();
668
- //alert(options.series.pie.radius);
669
-
722
+
670
723
  var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
671
724
 
672
725
  octx.save();
673
726
  octx.translate(centerLeft, centerTop);
674
727
  octx.scale(1, options.series.pie.tilt);
675
-
676
- for (i = 0; i < highlights.length; ++i)
728
+
729
+ for (var i = 0; i < highlights.length; ++i) {
677
730
  drawHighlight(highlights[i].series);
678
-
731
+ }
732
+
679
733
  drawDonutHole(octx);
680
734
 
681
735
  octx.restore();
682
736
 
683
- function drawHighlight(series)
684
- {
685
- if (series.angle < 0) return;
686
-
737
+ function drawHighlight(series) {
738
+
739
+ if (series.angle <= 0 || isNaN(series.angle)) {
740
+ return;
741
+ }
742
+
687
743
  //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
688
- octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor
689
-
744
+ octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
690
745
  octx.beginPath();
691
- if (Math.abs(series.angle - Math.PI*2) > 0.000000001)
692
- octx.moveTo(0,0); // Center of the pie
693
- octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false);
746
+ if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
747
+ octx.moveTo(0, 0); // Center of the pie
748
+ }
749
+ octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
750
+ octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
694
751
  octx.closePath();
695
752
  octx.fill();
696
753
  }
697
-
698
- }
699
-
754
+ }
700
755
  } // end init (plugin body)
701
-
756
+
702
757
  // define pie specific options and their default values
758
+
703
759
  var options = {
704
760
  series: {
705
761
  pie: {
706
762
  show: false,
707
- radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
708
- innerRadius:0, /* for donut */
763
+ radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
764
+ innerRadius: 0, /* for donut */
709
765
  startAngle: 3/2,
710
766
  tilt: 1,
767
+ shadow: {
768
+ left: 5, // shadow left offset
769
+ top: 15, // shadow top offset
770
+ alpha: 0.02 // shadow alpha
771
+ },
711
772
  offset: {
712
773
  top: 0,
713
- left: 'auto'
774
+ left: "auto"
714
775
  },
715
776
  stroke: {
716
- color: '#FFF',
777
+ color: "#fff",
717
778
  width: 1
718
779
  },
719
780
  label: {
720
- show: 'auto',
721
- formatter: function(label, slice){
722
- return '<div style="font-size:x-small;text-align:center;padding:2px;color:'+slice.color+';">'+label+'<br/>'+Math.round(slice.percent)+'%</div>';
781
+ show: "auto",
782
+ formatter: function(label, slice) {
783
+ return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
723
784
  }, // formatter function
724
785
  radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
725
786
  background: {
@@ -731,20 +792,21 @@ More detail and specific examples can be found in the included HTML file.
731
792
  combine: {
732
793
  threshold: -1, // percentage at which to combine little slices into one larger slice
733
794
  color: null, // color to give the new slice (auto-generated if null)
734
- label: 'Other' // label to give the new slice
795
+ label: "Other" // label to give the new slice
735
796
  },
736
797
  highlight: {
737
- //color: '#FFF', // will add this functionality once parseColor is available
798
+ //color: "#fff", // will add this functionality once parseColor is available
738
799
  opacity: 0.5
739
800
  }
740
801
  }
741
802
  }
742
803
  };
743
-
804
+
744
805
  $.plot.plugins.push({
745
806
  init: init,
746
807
  options: options,
747
808
  name: "pie",
748
- version: "1.0"
809
+ version: "1.1"
749
810
  });
811
+
750
812
  })(jQuery);