Administrator
2023-04-21 195945efc5db921a4c9eb8cf9421c172273293f5
提交 | 用户 | 时间
58d006 1 /**!
A 2  * easyPieChart
3  * Lightweight plugin to render simple, animated and retina optimized pie charts
4  *
5  * @license 
6  * @author Robert Fleischmann <rendro87@gmail.com> (http://robert-fleischmann.de)
7  * @version 2.1.6
8  **/
9
10 (function(root, factory) {
11     if(typeof exports === 'object') {
12         module.exports = factory(require('jquery'));
13     }
14     else if(typeof define === 'function' && define.amd) {
15         define(['jquery'], factory);
16     }
17     else {
18         factory(root.jQuery);
19     }
20 }(this, function($) {
21
22 /**
23  * Renderer to render the chart on a canvas object
24  * @param {DOMElement} el      DOM element to host the canvas (root of the plugin)
25  * @param {object}     options options object of the plugin
26  */
27 var CanvasRenderer = function(el, options) {
28     var cachedBackground;
29     var canvas = document.createElement('canvas');
30
31     el.appendChild(canvas);
32
33     if (typeof(G_vmlCanvasManager) !== 'undefined') {
34         G_vmlCanvasManager.initElement(canvas);
35     }
36
37     var ctx = canvas.getContext('2d');
38
39     canvas.width = canvas.height = options.size;
40
41     // canvas on retina devices
42     var scaleBy = 1;
43     if (window.devicePixelRatio > 1) {
44         scaleBy = window.devicePixelRatio;
45         canvas.style.width = canvas.style.height = [options.size, 'px'].join('');
46         canvas.width = canvas.height = options.size * scaleBy;
47         ctx.scale(scaleBy, scaleBy);
48     }
49
50     // move 0,0 coordinates to the center
51     ctx.translate(options.size / 2, options.size / 2);
52
53     // rotate canvas -90deg
54     ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI);
55
56     var radius = (options.size - options.lineWidth) / 2;
57     if (options.scaleColor && options.scaleLength) {
58         radius -= options.scaleLength + 2; // 2 is the distance between scale and bar
59     }
60
61     // IE polyfill for Date
62     Date.now = Date.now || function() {
63         return +(new Date());
64     };
65
66     /**
67      * Draw a circle around the center of the canvas
68      * @param {strong} color     Valid CSS color string
69      * @param {number} lineWidth Width of the line in px
70      * @param {number} percent   Percentage to draw (float between -1 and 1)
71      */
72     var drawCircle = function(color, lineWidth, percent) {
73         percent = Math.min(Math.max(-1, percent || 0), 1);
74         var isNegative = percent <= 0 ? true : false;
75
76         ctx.beginPath();
77         ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, isNegative);
78
79         ctx.strokeStyle = color;
80         ctx.lineWidth = lineWidth;
81
82         ctx.stroke();
83     };
84
85     /**
86      * Draw the scale of the chart
87      */
88     var drawScale = function() {
89         var offset;
90         var length;
91
92         ctx.lineWidth = 1;
93         ctx.fillStyle = options.scaleColor;
94
95         ctx.save();
96         for (var i = 24; i > 0; --i) {
97             if (i % 6 === 0) {
98                 length = options.scaleLength;
99                 offset = 0;
100             } else {
101                 length = options.scaleLength * 0.6;
102                 offset = options.scaleLength - length;
103             }
104             ctx.fillRect(-options.size/2 + offset, 0, length, 1);
105             ctx.rotate(Math.PI / 12);
106         }
107         ctx.restore();
108     };
109
110     /**
111      * Request animation frame wrapper with polyfill
112      * @return {function} Request animation frame method or timeout fallback
113      */
114     var reqAnimationFrame = (function() {
115         return  window.requestAnimationFrame ||
116                 window.webkitRequestAnimationFrame ||
117                 window.mozRequestAnimationFrame ||
118                 function(callback) {
119                     window.setTimeout(callback, 1000 / 60);
120                 };
121     }());
122
123     /**
124      * Draw the background of the plugin including the scale and the track
125      */
126     var drawBackground = function() {
127         if(options.scaleColor) drawScale();
128         if(options.trackColor) drawCircle(options.trackColor, options.trackWidth || options.lineWidth, 1);
129     };
130
131   /**
132     * Canvas accessor
133    */
134   this.getCanvas = function() {
135     return canvas;
136   };
137
138   /**
139     * Canvas 2D context 'ctx' accessor
140    */
141   this.getCtx = function() {
142     return ctx;
143   };
144
145     /**
146      * Clear the complete canvas
147      */
148     this.clear = function() {
149         ctx.clearRect(options.size / -2, options.size / -2, options.size, options.size);
150     };
151
152     /**
153      * Draw the complete chart
154      * @param {number} percent Percent shown by the chart between -100 and 100
155      */
156     this.draw = function(percent) {
157         // do we need to render a background
158         if (!!options.scaleColor || !!options.trackColor) {
159             // getImageData and putImageData are supported
160             if (ctx.getImageData && ctx.putImageData) {
161                 if (!cachedBackground) {
162                     drawBackground();
163                     cachedBackground = ctx.getImageData(0, 0, options.size * scaleBy, options.size * scaleBy);
164                 } else {
165                     ctx.putImageData(cachedBackground, 0, 0);
166                 }
167             } else {
168                 this.clear();
169                 drawBackground();
170             }
171         } else {
172             this.clear();
173         }
174
175         ctx.lineCap = options.lineCap;
176
177         // if barcolor is a function execute it and pass the percent as a value
178         var color;
179         if (typeof(options.barColor) === 'function') {
180             color = options.barColor(percent);
181         } else {
182             color = options.barColor;
183         }
184
185         // draw bar
186         drawCircle(color, options.lineWidth, percent / 100);
187     }.bind(this);
188
189     /**
190      * Animate from some percent to some other percentage
191      * @param {number} from Starting percentage
192      * @param {number} to   Final percentage
193      */
194     this.animate = function(from, to) {
195         var startTime = Date.now();
196         options.onStart(from, to);
197         var animation = function() {
198             var process = Math.min(Date.now() - startTime, options.animate.duration);
199             var currentValue = options.easing(this, process, from, to - from, options.animate.duration);
200             this.draw(currentValue);
201             options.onStep(from, to, currentValue);
202             if (process >= options.animate.duration) {
203                 options.onStop(from, to);
204             } else {
205                 reqAnimationFrame(animation);
206             }
207         }.bind(this);
208
209         reqAnimationFrame(animation);
210     }.bind(this);
211 };
212
213 var EasyPieChart = function(el, opts) {
214     var defaultOptions = {
215         barColor: '#ef1e25',
216         trackColor: '#f9f9f9',
217         scaleColor: '#dfe0e0',
218         scaleLength: 5,
219         lineCap: 'round',
220         lineWidth: 3,
221         trackWidth: undefined,
222         size: 110,
223         rotate: 0,
224         animate: {
225             duration: 1000,
226             enabled: true
227         },
228         easing: function (x, t, b, c, d) { // more can be found here: http://gsgd.co.uk/sandbox/jquery/easing/
229             t = t / (d/2);
230             if (t < 1) {
231                 return c / 2 * t * t + b;
232             }
233             return -c/2 * ((--t)*(t-2) - 1) + b;
234         },
235         onStart: function(from, to) {
236             return;
237         },
238         onStep: function(from, to, currentValue) {
239             return;
240         },
241         onStop: function(from, to) {
242             return;
243         }
244     };
245
246     // detect present renderer
247     if (typeof(CanvasRenderer) !== 'undefined') {
248         defaultOptions.renderer = CanvasRenderer;
249     } else if (typeof(SVGRenderer) !== 'undefined') {
250         defaultOptions.renderer = SVGRenderer;
251     } else {
252         throw new Error('Please load either the SVG- or the CanvasRenderer');
253     }
254
255     var options = {};
256     var currentValue = 0;
257
258     /**
259      * Initialize the plugin by creating the options object and initialize rendering
260      */
261     var init = function() {
262         this.el = el;
263         this.options = options;
264
265         // merge user options into default options
266         for (var i in defaultOptions) {
267             if (defaultOptions.hasOwnProperty(i)) {
268                 options[i] = opts && typeof(opts[i]) !== 'undefined' ? opts[i] : defaultOptions[i];
269                 if (typeof(options[i]) === 'function') {
270                     options[i] = options[i].bind(this);
271                 }
272             }
273         }
274
275         // check for jQuery easing
276         if (typeof(options.easing) === 'string' && typeof(jQuery) !== 'undefined' && jQuery.isFunction(jQuery.easing[options.easing])) {
277             options.easing = jQuery.easing[options.easing];
278         } else {
279             options.easing = defaultOptions.easing;
280         }
281
282         // process earlier animate option to avoid bc breaks
283         if (typeof(options.animate) === 'number') {
284             options.animate = {
285                 duration: options.animate,
286                 enabled: true
287             };
288         }
289
290         if (typeof(options.animate) === 'boolean' && !options.animate) {
291             options.animate = {
292                 duration: 1000,
293                 enabled: options.animate
294             };
295         }
296
297         // create renderer
298         this.renderer = new options.renderer(el, options);
299
300         // initial draw
301         this.renderer.draw(currentValue);
302
303         // initial update
304         if (el.dataset && el.dataset.percent) {
305             this.update(parseFloat(el.dataset.percent));
306         } else if (el.getAttribute && el.getAttribute('data-percent')) {
307             this.update(parseFloat(el.getAttribute('data-percent')));
308         }
309         
310         el.style['width'] = el.style['height'] = options.size + 'px';//ACE
311         el.style['lineHeight'] = (options.size - 1) + 'px';//ACE
312     }.bind(this);
313
314     /**
315      * Update the value of the chart
316      * @param  {number} newValue Number between 0 and 100
317      * @return {object}          Instance of the plugin for method chaining
318      */
319     this.update = function(newValue) {
320         newValue = parseFloat(newValue);
321         if (options.animate.enabled) {
322             this.renderer.animate(currentValue, newValue);
323         } else {
324             this.renderer.draw(newValue);
325         }
326         currentValue = newValue;
327         return this;
328     }.bind(this);
329
330     /**
331      * Disable animation
332      * @return {object} Instance of the plugin for method chaining
333      */
334     this.disableAnimation = function() {
335         options.animate.enabled = false;
336         return this;
337     };
338
339     /**
340      * Enable animation
341      * @return {object} Instance of the plugin for method chaining
342      */
343     this.enableAnimation = function() {
344         options.animate.enabled = true;
345         return this;
346     };
347
348     init();
349 };
350
351 $.fn.easyPieChart = function(options) {
352     return this.each(function() {
353         var instanceOptions;
354
355         if (!$.data(this, 'easyPieChart')) {
356             instanceOptions = $.extend({}, options, $(this).data());
357             $.data(this, 'easyPieChart', new EasyPieChart(this, instanceOptions));
358         }
359     });
360 };
361
362
363 }));