/** * @namespace ludo.progress */ /** * Progress Bar * The progress bar is created using SVG. It is made out of 4 elements in this stacking order(bottom to top) * * 1) The background svg path rendered with the css class '.ludo-progress-bg' and styles defined in bgStyles object * 2) Eventual background image defined in bgPattern. If the background path(1) has a border, the background image will be * shrinked to fit inside. The background image will be repeated when smaller than the progress bar. If bigger, it will be scaled * down. * 3) Progress Bar SVG path * 4) Eventual background image defined in frontPattern. * * * Demo: <a href="../demo/progress/bar.php">Progress Bar Demo</a> * * @namespace progress * @class ludo.progress.Bar * @augments ludo.progress.Base * @param {Object} config * @param {Number} config.steps Number of progress bar steps, default = 10 * @param {Number} config.progress Initial step, default = 0 * @param {String} config.bgPattern Path to background image for the progress bar background. * @param {String} config.frontPattern Path to background image for the progress bar. The background images will be repeated if smaller than the progress bar. If bigger, it will be scaled down. * @param {float} config.textSizeRatio Size of text relative to height of progress bar, default = 0.6 * @param {float} config.borderRadius Fixed border radius, default = height / 2 * @param {float} config.bgStyles SVG background styles * @param {float} config.barStyles SVG moving bar styles * @param {float} config.textStyles Styling of text on progress bar * @param {Function} config.easing Easing function for animation. default: ludo.svg.easing.linear * @param {Number} config.animationDuration Animation duration in milliseconds (1/1000s) - default: 100 * @fires ludo.progress.Bar#change Fired when value is changed. Arguments. 1) Percent completed 2) current step 3) number of steps, 4) ludo.progress.Bar 4) Current percentage. * If animated, the change event will be triggered once animation is complete. * @fires ludo.progress.Bar#animate Fired during progress animation and when value is changed. This is a good event * to listen to when you want to update texts for the progress bar. Arguments. 1) Animated percent completed */ ludo.progress.Bar = new Class({ Extends: ludo.View, type: 'progress.Bar', orientation: 'horizontal', steps: 10, progress: 0, lastProgress: 0, lastSteps: 0, lastRatio: 0, outerBorderWidth: 0, innerBorderWidth: 0, borderRadius: undefined, textSizeRatio: 0.6, bgStyles: undefined, barStyles: undefined, textStyles: undefined, _text: undefined, animationDuration: 100, debugRect: undefined, bgPattern: undefined, frontPattern: undefined, patternSize: undefined, frontPatternSize: undefined, __construct: function (config) { this.parent(config); this.setConfigParams(config, ['animationDuration', 'steps', 'progress', 'borderRadius', 'textSizeRatio', 'bgStyles', 'barStyles', 'textStyles', 'bgPattern', 'frontPattern','easing']); if (!this.layout.height) { this.layout.height = 25; } this.lastSteps = this.steps; if(this.easing == undefined){ this.easing = ludo.svg.easing.linear; } if (config.text != undefined) { this._text = config.text; } }, __rendered: function () { this.parent(); this.renderBar(); if(this.progress != 0){ this.onChange(); } }, /** * Increment progress bar * @param {Number} by * @param {Boolean} animate * @memberof ludo.progress.Bar.prototype */ increment: function (by, animate) { by = by != undefined ? by : 1; animate = animate != undefined ? animate : true; if(by != 0)this.lastProgress = this.progress; this.progress += by; this.progress = Math.max(0, this.progress); this.progress = Math.min(this.progress, this.steps); if (this.progress != this.lastProgress || this.steps != this.lastSteps) { var ratio = this.progress / this.steps; this.ratio(ratio, animate); } }, onChange: function () { var ratio = this.progress / this.steps; this.fireEvent('change', [ratio * 100, this.progress, this.steps, this, ratio]); }, resize: function (size) { this.parent(size); this.resizeItems(); }, setSteps: function (steps, animate) { this.lastSteps = this.steps; this.steps = steps; this.increment(0, animate); }, /** * Set new progress value * @param {Number} progress * @param {Boolean} animate * @memberof ludo.progress.Bar.prototype */ setProgress: function (progress, animate) { this.increment(progress - this.progress, animate); }, createClipPath: function () { var s = this.svg(); this.els.clipPath = s.$('clipPath'); s.appendDef(this.els.clipPath); this.els.clipRect = s.$('rect', { x: 0, y: 0, width: 0, height: 0 }); this.els.clipPath.append(this.els.clipRect); this.els.clipPathBg = s.$('clipPath'); this.els.clipPathBgPath = s.$('path'); this.els.clipPathBg.append(this.els.clipPathBgPath); s.append(this.els.clipPathBg); }, getPattern: function (image, sizeKey, imageKey) { var s = this.svg(); var p = s.$('pattern'); p.set('width', 0.2); p.set('height', 1); p.set('x', 0); p.set('y', 0); var img = this.els[imageKey] = s.$('image'); var that = this; img.set('opacity', 0); img.on('load', function () { var bbox = this.getBBox(); that[sizeKey] = { x: bbox.width, y: bbox.height }; img.set('opacity', 1); that.updatePatternSize(); }.bind(img)); img.set('xlink:href', image); p.append(img); s.appendDef(p); return p; }, createPattern: function () { if(this.bgPattern){ this.els.bgPattern = this.getPattern(this.bgPattern, 'patternSize','bgImage'); } if(this.frontPattern){ this.els.frontPattern = this.getPattern(this.frontPattern, 'frontPatternSize','frontImage'); } }, applyPattern:function(){ var s = this.svg(); if(this.bgPattern){ this.els.patternRect = s.$('rect'); s.append(this.els.patternRect); this.els.patternRect.setPattern(this.els.bgPattern); this.els.patternRect.set('x', 0); this.els.patternRect.set('y', 0); this.els.patternRect.clip(this.els.clipPathBg); } if(this.frontPattern){ this.els.frontPatternPath = s.$('path'); s.append(this.els.frontPatternPath); this.els.frontPatternPath.setPattern(this.els.frontPattern); this.els.frontPatternPath.clip(this.els.clipPath); } }, updatePatternSize: function () { if (this.patternSize != undefined) { this.els.bgPattern.set('width', Math.min(1, this.patternSize.x / this.svg().width)); this.els.bgPattern.set('height', Math.min(1, this.patternSize.y / this.svg().height)); } if(this.frontPatternSize){ this.els.frontPattern.set('width', Math.min(1, this.frontPatternSize.x / this.svg().width)); this.els.frontPattern.set('height', Math.min(1, this.frontPatternSize.y / this.svg().height)); } }, renderBar: function () { var s = this.svg(); this.createClipPath(); this.createPattern(); this.applyPattern(); var cls = 'ludo-progress-bg'; var styles = ludo.svg.Util.pathStyles(cls); this.outerBorderWidth = parseInt(styles['stroke-width']); s.addStyleSheet(cls + '-svg', styles); var bg = this.els.bg = s.$('path'); bg.addClass(cls + '-svg'); s.append(bg); if (this.bgStyles != undefined) { bg.css(this.bgStyles); if(this.bgStyles['stroke-width'] != undefined)this.outerBorderWidth = parseInt(this.bgStyles['stroke-width']); } bg.set('stroke-linecap', 'round'); bg.set('stroke-linejoin', 'round'); if(this.els.patternRect){ this.els.patternRect.toFront(); } this.els.g = s.$('g'); s.append(this.els.g); var el = this.els.bar = s.$('path'); cls = 'ludo-progress-pr'; styles = ludo.svg.Util.pathStyles(cls); this.innerBorderWidth = parseInt(styles['stroke-width']); s.addStyleSheet(cls + '-svg', styles); el.addClass(cls + '-svg'); this.els.g.append(el); if (this.barStyles != undefined) { el.css(this.barStyles); if(this.barStyles['stroke-width'] != undefined)this.innerBorderWidth = parseInt(this.barStyles['stroke-width']); } el.set('stroke-linecap', 'round'); el.set('stroke-linejoin', 'round'); this.els.g.clip(this.els.clipPath); if (this._text != undefined) { this.text(this._text); } if(this.els.frontPatternPath){ this.els.frontPatternPath.toFront(); } }, bar: function () { return this.els.bar; }, resizeItems: function () { this.els.bg.set('d', this.bgPath()); if (this.els.patternRect) { this.els.patternRect.set('width', this.svg().width); this.els.patternRect.set('height', this.svg().height); } var padding = (this.outerBorderWidth / 2) + (this.innerBorderWidth / 2); this.els.bar.set('d', this.bgPath(padding)); if(this.els.frontPatternPath){ this.els.frontPatternPath.set('d', this.bgPath(padding)); } this.els.clipPathBgPath.set('d', this.bgPath(padding)); this.positionTextNode(); this.percent(this.progress / this.steps * 100); this.els.clipRect.set('height', this.svg().height); this.els.clipRect.set('x', this.outerBorderWidth); this.updatePatternSize(); this.ratio(this.progress / this.steps, false); }, positionTextNode: function () { if (this.els.textNode) { this.els.textNode.set('x', this.getBody().width() / 2); this.els.textNode.set('y', (this.svg().height / 2)); this.els.textNode.css('font-size', this.svg().height * this.textSizeRatio); } }, bgPath: function (extraPadding) { extraPadding = extraPadding || 0; var padding = (this.outerBorderWidth / 2) + extraPadding; var w = this.svg().width; var h = this.svg().height; var radius = this.borderRadius ? this.borderRadius : this.orientation == 'horizontal' ? (h - padding) / 2 : (w - padding) / 2; if (this.orientation == 'horizontal') { radius = Math.min(radius, (h - padding) / 2); } else { radius = Math.min(radius, (w - padding) / 2); } var offset = radius; var path; if (this.borderRadius || 1 == 1) { path = [ 'M', offset, padding, 'L', w - offset, padding, 'A', radius, radius, 90, 0, 1, w - padding, padding + radius, 'L', w - padding, h - radius, 'A', radius, radius, 90, 0, 1, w - offset, h - padding, 'L', offset, h - padding, 'A', radius, radius, 90, 0, 1, padding, h - padding - radius, 'L', padding, radius, 'A', radius, radius, 90, 0, 1, offset, padding ]; } else { var e = 0; path = [ 'M', offset, padding, 'L', w - offset + e, padding, 'A', radius, radius, 180, 0, 1, w - offset + e, h - padding, 'L', offset - e, h - padding, 'A', radius, radius, 180, 0, 1, offset - e, padding ]; } return path.join(' '); }, /** * Display text on progress bar. * @param {String} text * @memberof ludo.progress.Bar.prototype * @example { id: 'progress', type: 'progress.Bar', borderRadius: 3, steps: 100, layout: { width: 300, height:30 }, listeners:{ // Update text on animate animate:function (percent) { this.text(percent.toFixed(0) + '%'); } } } */ text: function (txt) { if (this.els.textNode == undefined) { this.els.textNode = this.svg().$('text'); var styles = ludo.svg.Util.textStyles('ludo-progress-text'); this.svg().addStyleSheet('ludo-progress-text-svg', styles); this.els.textNode.addClass('ludo-progress-text-svg'); this.els.textNode.set('text-anchor', 'middle'); this.els.textNode.set('alignment-baseline', 'central'); this.svg().append(this.els.textNode); if (this.textStyles != undefined) { this.els.textNode.css(this.textStyles); } this.positionTextNode(); } this.els.textNode.text(txt); }, animate: function (percent) { this.percent(percent, true); }, /** * Update or get percent completed. * @param {Number} percent New percentage value * @param {Boolean} animate True to animate, default = false * @memberof ludo.progress.Bar.prototype * @example * var progressBar = ludo.$('progressBar'); * // get percent completed with no decimals * var percent = progressBar.percent(); * // update percent and animate it * progressBar.percent(20, true); */ percent: function (percent, animate) { if(arguments.length == 0){ return (this.progress / this.steps * 100).toFixed(0); } this.ratio(percent / 100, animate); }, /** * Get or set ratio. To get current ratio, send no arguments to this function. * * The ratio is also updated automatically when you use the increment function. * * @param {Number} ratio New ratio - 0 = starting, 1 = finished * @param {Boolean} animate True to animate, default = false * @memberof ludo.progress.Bar.prototype * @example * var progressBar = ludo.$('progressBar'); * // get ratio * var ratio = progressBar.ratio(); * // set ratio * progressBar.ratio(0.5, true); * */ ratio: function (ratio, animate) { if (arguments.length == 0) { return this.progress / this.steps; } ratio = ludo.util.clamp(ratio, 0, 1); var w = (this.svg().width - this.outerBorderWidth) * ratio; if (animate) { var diff = ratio - this.lastRatio; this.els.clipRect.animate({ width: w }, { duration: this.animationDuration, easing: this.easing, complete: function () { this.lastRatio = ratio; this.fireEvent('animate', this.lastRatio * 100); this.onChange(); }.bind(this), progress: function (t) { var prs = Math.min(100, (this.lastRatio + (diff * t)) * 100); this.fireEvent('animate', prs); }.bind(this) }); } else { this.els.clipRect.set('width', w); if(ratio != this.lastRatio){ this.onChange(); } this.lastRatio = ratio; this.fireEvent('animate', this.lastRatio * 100); } } });