Statistics
| Branch: | Revision:

blinker / firefox.plugin / data / lib / circle-progress.js @ 76dd22bd

History | View | Annotate | Download (12.164 KB)

1
/*
2
jquery-circle-progress - jQuery Plugin to draw animated circular progress bars
3

4
URL: http://kottenator.github.io/jquery-circle-progress/
5
Author: Rostyslav Bryzgunov <kottenator@gmail.com>
6
Version: 1.1.2
7
License: MIT
8
*/
9
(function($) {
10
    function CircleProgress(config) {
11
        this.init(config);
12
    }
13

    
14
    CircleProgress.prototype = {
15
        //----------------------------------------------- public options -----------------------------------------------
16
        /**
17
         * This is the only required option. It should be from 0.0 to 1.0
18
         * @type {number}
19
         */
20
        value: 0.0,
21

    
22
        /**
23
         * Size of the circle / canvas in pixels
24
         * @type {number}
25
         */
26
        size: 100.0,
27

    
28
        /**
29
         * Initial angle for 0.0 value in radians
30
         * @type {number}
31
         */
32
        startAngle: -Math.PI,
33

    
34
        /**
35
         * Width of the arc. By default it's auto-calculated as 1/14 of size, but you may set it explicitly in pixels
36
         * @type {number|string}
37
         */
38
        thickness: 'auto',
39

    
40
        /**
41
         * Fill of the arc. You may set it to:
42
         *   - solid color:
43
         *     - { color: '#3aeabb' }
44
         *     - { color: 'rgba(255, 255, 255, .3)' }
45
         *   - linear gradient (left to right):
46
         *     - { gradient: ['#3aeabb', '#fdd250'], gradientAngle: Math.PI / 4 }
47
         *     - { gradient: ['red', 'green', 'blue'], gradientDirection: [x0, y0, x1, y1] }
48
         *   - image:
49
         *     - { image: 'http://i.imgur.com/pT0i89v.png' }
50
         *     - { image: imageObject }
51
         *     - { color: 'lime', image: 'http://i.imgur.com/pT0i89v.png' } - color displayed until the image is loaded
52
         */
53
        fill: {
54
            gradient: ['#3aeabb', '#fdd250']
55
        },
56

    
57
        /**
58
         * Color of the "empty" arc. Only a color fill supported by now
59
         * @type {string}
60
         */
61
        emptyFill: 'rgba(0, 0, 0, .1)',
62

    
63
        /**
64
         * Animation config (see jQuery animations: http://api.jquery.com/animate/)
65
         */
66
        animation: {
67
            duration: 1200,
68
            easing: 'circleProgressEasing'
69
        },
70

    
71
        /**
72
         * Default animation starts at 0.0 and ends at specified `value`. Let's call this direct animation.
73
         * If you want to make reversed animation then you should set `animationStartValue` to 1.0.
74
         * Also you may specify any other value from 0.0 to 1.0
75
         * @type {number}
76
         */
77
        animationStartValue: 0.0,
78

    
79
        /**
80
         * Reverse animation and arc draw
81
         * @type {boolean}
82
         */
83
        reverse: false,
84

    
85
        /**
86
         * Arc line cap ('butt' (default), 'round' and 'square')
87
         * Read more: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.lineCap
88
         * @type {string}
89
         */
90
        lineCap: 'butt',
91

    
92
        //-------------------------------------- protected properties and methods --------------------------------------
93
        /**
94
         * @protected
95
         */
96
        constructor: CircleProgress,
97

    
98
        /**
99
         * Container element. Should be passed into constructor config
100
         * @protected
101
         * @type {jQuery}
102
         */
103
        el: null,
104

    
105
        /**
106
         * Canvas element. Automatically generated and prepended to the {@link CircleProgress.el container}
107
         * @protected
108
         * @type {HTMLCanvasElement}
109
         */
110
        canvas: null,
111

    
112
        /**
113
         * 2D-context of the {@link CircleProgress.canvas canvas}
114
         * @protected
115
         * @type {CanvasRenderingContext2D}
116
         */
117
        ctx: null,
118

    
119
        /**
120
         * Radius of the outer circle. Automatically calculated as {@link CircleProgress.size} / 2
121
         * @protected
122
         * @type {number}
123
         */
124
        radius: 0.0,
125

    
126
        /**
127
         * Fill of the main arc. Automatically calculated, depending on {@link CircleProgress.fill} option
128
         * @protected
129
         * @type {string|CanvasGradient|CanvasPattern}
130
         */
131
        arcFill: null,
132

    
133
        /**
134
         * Last rendered frame value
135
         * @protected
136
         * @type {number}
137
         */
138
        lastFrameValue: 0.0,
139

    
140
        /**
141
         * Init/re-init the widget
142
         * @param {object} config Config
143
         */
144
        init: function(config) {
145
            $.extend(this, config);
146
            this.radius = this.size / 2;
147
            this.initWidget();
148
            this.initFill();
149
            this.draw();
150
        },
151

    
152
        /**
153
         * @protected
154
         */
155
        initWidget: function() {
156
            var canvas = this.canvas = this.canvas || $('<canvas>').prependTo(this.el)[0];
157
            canvas.width = this.size;
158
            canvas.height = this.size;
159
            this.ctx = canvas.getContext('2d');
160
        },
161

    
162
        /**
163
         * This method sets {@link CircleProgress.arcFill}
164
         * It could do this async (on image load)
165
         * @protected
166
         */
167
        initFill: function() {
168
            var self = this,
169
                fill = this.fill,
170
                ctx = this.ctx,
171
                size = this.size;
172

    
173
            if (!fill)
174
                throw Error("The fill is not specified!");
175

    
176
            if (fill.color)
177
                this.arcFill = fill.color;
178

    
179
            if (fill.gradient) {
180
                var gr = fill.gradient;
181

    
182
                if (gr.length == 1) {
183
                    this.arcFill = gr[0];
184
                } else if (gr.length > 1) {
185
                    var ga = fill.gradientAngle || 0, // gradient direction angle; 0 by default
186
                        gd = fill.gradientDirection || [
187
                            size / 2 * (1 - Math.cos(ga)), // x0
188
                            size / 2 * (1 + Math.sin(ga)), // y0
189
                            size / 2 * (1 + Math.cos(ga)), // x1
190
                            size / 2 * (1 - Math.sin(ga))  // y1
191
                        ];
192

    
193
                    var lg = ctx.createLinearGradient.apply(ctx, gd);
194

    
195
                    for (var i = 0; i < gr.length; i++) {
196
                        var color = gr[i],
197
                            pos = i / (gr.length - 1);
198

    
199
                        if ($.isArray(color)) {
200
                            pos = color[1];
201
                            color = color[0];
202
                        }
203

    
204
                        lg.addColorStop(pos, color);
205
                    }
206

    
207
                    this.arcFill = lg;
208
                }
209
            }
210

    
211
            if (fill.image) {
212
                var img;
213

    
214
                if (fill.image instanceof Image) {
215
                    img = fill.image;
216
                } else {
217
                    img = new Image();
218
                    img.src = fill.image;
219
                }
220

    
221
                if (img.complete)
222
                    setImageFill();
223
                else
224
                    img.onload = setImageFill;
225
            }
226

    
227
            function setImageFill() {
228
                var bg = $('<canvas>')[0];
229
                bg.width = self.size;
230
                bg.height = self.size;
231
                bg.getContext('2d').drawImage(img, 0, 0, size, size);
232
                self.arcFill = self.ctx.createPattern(bg, 'no-repeat');
233
                self.drawFrame(self.lastFrameValue);
234
            }
235
        },
236

    
237
        draw: function() {
238
            if (this.animation)
239
                this.drawAnimated(this.value);
240
            else
241
                this.drawFrame(this.value);
242
        },
243

    
244
        /**
245
         * @protected
246
         * @param {number} v Frame value
247
         */
248
        drawFrame: function(v) {
249
            this.lastFrameValue = v;
250
            this.ctx.clearRect(0, 0, this.size, this.size);
251
            this.drawEmptyArc(v);
252
            this.drawArc(v);
253
        },
254

    
255
        /**
256
         * @protected
257
         * @param {number} v Frame value
258
         */
259
        drawArc: function(v) {
260
            var ctx = this.ctx,
261
                r = this.radius,
262
                t = this.getThickness(),
263
                a = this.startAngle;
264

    
265
            ctx.save();
266
            ctx.beginPath();
267

    
268
            if (!this.reverse) {
269
                ctx.arc(r, r, r - t / 2, a, a + Math.PI * 2 * v);
270
            } else {
271
                ctx.arc(r, r, r - t / 2, a - Math.PI * 2 * v, a);
272
            }
273

    
274
            ctx.lineWidth = t;
275
            ctx.lineCap = this.lineCap;
276
            ctx.strokeStyle = this.arcFill;
277
            ctx.stroke();
278
            ctx.restore();
279
        },
280

    
281
        /**
282
         * @protected
283
         * @param {number} v Frame value
284
         */
285
        drawEmptyArc: function(v) {
286
            var ctx = this.ctx,
287
                r = this.radius,
288
                t = this.getThickness(),
289
                a = this.startAngle;
290

    
291
            if (v < 1) {
292
                ctx.save();
293
                ctx.beginPath();
294

    
295
                if (v <= 0) {
296
                    ctx.arc(r, r, r - t / 2, 0, Math.PI * 2);
297
                } else {
298
                    if (!this.reverse) {
299
                        ctx.arc(r, r, r - t / 2, a + Math.PI * 2 * v, a);
300
                    } else {
301
                        ctx.arc(r, r, r - t / 2, a, a - Math.PI * 2 * v);
302
                    }
303
                }
304

    
305
                ctx.lineWidth = t;
306
                ctx.strokeStyle = this.emptyFill;
307
                ctx.stroke();
308
                ctx.restore();
309
            }
310
        },
311

    
312
        /**
313
         * @protected
314
         * @param {number} v Value
315
         */
316
        drawAnimated: function(v) {
317
            var self = this,
318
                el = this.el;
319

    
320
            el.trigger('circle-animation-start');
321

    
322
            $(this.canvas)
323
                .stop(true, true)
324
                .css({ animationProgress: 0 })
325
                .animate({ animationProgress: 1 }, $.extend({}, this.animation, {
326
                    step: function(animationProgress) {
327
                        var stepValue = self.animationStartValue * (1 - animationProgress) + v * animationProgress;
328
                        self.drawFrame(stepValue);
329
                        el.trigger('circle-animation-progress', [animationProgress, stepValue]);
330
                    },
331
                    complete: function() {
332
                        el.trigger('circle-animation-end');
333
                    }
334
                }));
335
        },
336

    
337
        /**
338
         * @protected
339
         * @returns {number}
340
         */
341
        getThickness: function() {
342
            return $.isNumeric(this.thickness) ? this.thickness : this.size / 14;
343
        }
344
    };
345

    
346
    //-------------------------------------------- Initiating jQuery plugin --------------------------------------------
347
    $.circleProgress = {
348
        // Default options (you may override them)
349
        defaults: CircleProgress.prototype
350
    };
351

    
352
    // ease-in-out-cubic
353
    $.easing.circleProgressEasing = function(x, t, b, c, d) {
354
        if ((t /= d / 2) < 1)
355
            return c / 2 * t * t * t + b;
356
        return c / 2 * ((t -= 2) * t * t + 2) + b;
357
    };
358

    
359
    /**
360
     * Draw animated circular progress bar.
361
     *
362
     * Appends <canvas> to the element or updates already appended one.
363
     *
364
     * If animated, throws 3 events:
365
     *
366
     *   - circle-animation-start(jqEvent)
367
     *   - circle-animation-progress(jqEvent, animationProgress, stepValue) - multiple event;
368
     *                                                                        animationProgress: from 0.0 to 1.0;
369
     *                                                                        stepValue: from 0.0 to value
370
     *   - circle-animation-end(jqEvent)
371
     *
372
     * @param config Example: { value: 0.75, size: 50, animation: false };
373
     *                you may set any of public options;
374
     *                `animation` may be set to false;
375
     *                you may also use .circleProgress('widget') to get the canvas
376
     */
377
    $.fn.circleProgress = function(config) {
378
        var dataName = 'circle-progress';
379

    
380
        if (config == 'widget') {
381
            var data = this.data(dataName);
382
            return data && data.canvas;
383
        }
384

    
385
        return this.each(function() {
386
            var el = $(this),
387
                instance = el.data(dataName),
388
                cfg = $.isPlainObject(config) ? config : {};
389

    
390
            if (instance) {
391
                instance.init(cfg);
392
            } else {
393
                cfg.el = el;
394
                instance = new CircleProgress(cfg);
395
                el.data(dataName, instance);
396
            }
397
        });
398
    };
399
})(jQuery);