UNPKG

angularjs-gauge

Version:

A Gauge directive for Angular 1.x apps and dashboards

367 lines (304 loc) 13.7 kB
(function (angular) { 'use strict'; angular .module('angularjs-gauge', []) .directive('ngGauge', gaugeMeterDirective) .provider('ngGauge', gaugeMeterProviderFn); gaugeMeterProviderFn.$inject = []; function gaugeMeterProviderFn() { var defaultOptions = { size: 200, value: undefined, min: 0, max: 100, cap: 'butt', thick: 6, type: 'full', foregroundColor: 'rgba(0, 150, 136, 1)', backgroundColor: 'rgba(0, 0, 0, 0.1)', duration: 1500, fractionSize: null, labelOnly: false, }; this.setOptions = function (customOptions) { if (!(customOptions && angular.isObject(customOptions))) throw new Error('Invalid option type specified in the ngGaugeProvider'); defaultOptions = angular.merge(defaultOptions, customOptions); }; var ngGauge = { getOptions: function () { return angular.extend({}, defaultOptions); } }; this.$get = function () { return ngGauge; }; } gaugeMeterDirective.$inject = ['ngGauge']; function gaugeMeterDirective(ngGauge) { var tpl = '<div style="display:inline-block;text-align:center;position:relative;">' + '<span ng-show="{{!labelOnly}}"><u>{{prepend}}</u>' + '<span ng-if="fractionSize === null">{{value | number}}</span>' + '<span ng-if="fractionSize !== null">{{value | number: fractionSize}}</span>' + '<u>{{append}}</u></span>' + '<b>{{ label }}</b>' + '<canvas></canvas></div>'; var Gauge = function (element, options) { this.element = element.find('canvas')[0]; this.text = element.find('span'); this.legend = element.find('b'); this.unit = element.find('u'); this.context = this.element.getContext('2d'); this.options = options; this.init(); }; Gauge.prototype = { init: function () { this.setupStyles(); this.create(null, null); }, setupStyles: function () { this.context.canvas.width = this.options.size; this.context.canvas.height = this.options.size; this.context.lineCap = this.options.cap; this.context.lineWidth = this.options.thick; var lfs = this.options.size * 0.22, llh = this.options.size; this.text.css({ display: 'inline-block', fontWeight: 'normal', width: '100%', position: 'absolute', textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', fontSize: lfs + 'px', lineHeight: llh + 'px' }); this.unit.css({ textDecoration: 'none', fontSize: '0.6em', fontWeight: 200, opacity: 0.8 }); var fs = this.options.labelOnly ? lfs * 0.8 : this.options.size / 13; var lh = this.options.labelOnly ? llh : (5 * fs) + parseInt(this.options.size); this.legend.css({ display: 'inline-block', width: '100%', position: 'absolute', textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', fontWeight: 'normal', fontSize: fs + 'px', lineHeight: lh + 'px' }); }, create: function (nv, ov) { var self = this, type = this.getType(), bounds = this.getBounds(type), duration = this.getDuration(), min = this.getMin(), max = this.getMax(), value = this.clamp(this.getValue(), min, max), start = bounds.head, unit = (bounds.tail - bounds.head) / (max - min), displacement = unit * (value - min), tail = bounds.tail, color = this.getForegroundColorByRange(value), requestID, startTime; if (nv && ov) { displacement = unit * nv - unit * ov; } function animate(timestamp) { timestamp = timestamp || new Date().getTime(); var runtime = timestamp - startTime; var progress = Math.min(runtime / duration, 1); // never exceed 100% var previousProgress = ov ? (ov * unit) : 0; var middle = start + previousProgress + displacement * progress; self.drawShell(start, middle, tail, color); if (runtime < duration) { requestID = window.requestAnimationFrame(function (timestamp) { animate(timestamp); }); } else { cancelAnimationFrame(requestID); } } requestAnimationFrame(function (timestamp) { startTime = timestamp || new Date().getTime(); animate(timestamp); }); }, getBounds: function (type) { var head, tail; if (type == 'semi') { head = Math.PI; tail = 2 * Math.PI; } else if (type == 'full') { head = 1.5 * Math.PI; tail = 3.5 * Math.PI; } else if (type === 'arch') { head = 0.8 * Math.PI; tail = 2.2 * Math.PI; } return { head: head, tail: tail }; }, drawShell: function (start, middle, tail, color) { var context = this.context, center = this.getCenter(), radius = this.getRadius(), foregroundColor = color, backgroundColor = this.getBackgroundColor(); this.clear(); middle = Math.max(middle, start); // never below 0% middle = Math.min(middle, tail); // never exceed 100% context.beginPath(); context.strokeStyle = backgroundColor; context.arc(center.x, center.y, radius, middle, tail, false); context.stroke(); context.beginPath(); context.strokeStyle = foregroundColor; context.arc(center.x, center.y, radius, start, middle, false); context.stroke(); }, clear: function () { this.context.clearRect(0, 0, this.getWidth(), this.getHeight()); }, update: function (nv, ov) { this.create(nv, ov); }, destroy: function () { this.clear(); }, getRadius: function () { var center = this.getCenter(); return center.x - this.getThickness(); }, getCenter: function () { var x = this.getWidth() / 2, y = this.getHeight() / 2; return { x: x, y: y }; }, getValue: function () { return this.options.value; }, getMin: function () { return this.options.min; }, getMax: function () { return this.options.max; }, getWidth: function () { return this.context.canvas.width; }, getHeight: function () { return this.context.canvas.height; }, getThickness: function () { return this.options.thick; }, getBackgroundColor: function () { return this.options.backgroundColor; }, getForegroundColor: function () { return this.options.foregroundColor; }, getForegroundColorByRange: function (value) { var isNumber = function (value) { return value != undefined && !isNaN(parseFloat(value)) && !isNaN(Number(value)); }; var match = Object.keys(this.options.thresholds) .filter(function (item) { return isNumber(item) && Number(item) <= value; }) .sort(function(a,b) {return Number(a) > Number(b);}).reverse()[0]; return match !== undefined ? this.options.thresholds[match].color || this.getForegroundColor() : this.getForegroundColor(); }, getLineCap: function () { return this.options.cap; }, getType: function () { return this.options.type; }, getDuration: function () { return this.options.duration; }, clamp: function (value, min, max) { return Math.max(min, Math.min(max, value)); } }; return { restrict: 'E', replace: true, template: tpl, scope: { append: '@?', backgroundColor: '@?', cap: '@?', foregroundColor: '@?', label: '@?', labelOnly: '@?', prepend: '@?', size: '@?', thick: '@?', type: '@?', duration: '@?', value: '=?', min: '=?', max: '=?', thresholds: '=?', fractionSize: '=?' }, link: function (scope, element) { var defaults = ngGauge.getOptions(); // fetching default settings from provider scope.min = angular.isDefined(scope.min) ? scope.min : defaults.min; scope.max = angular.isDefined(scope.max) ? scope.max : defaults.max; scope.value = angular.isDefined(scope.value) ? scope.value : defaults.value; scope.size = angular.isDefined(scope.size) ? scope.size : defaults.size; scope.cap = angular.isDefined(scope.cap) ? scope.cap : defaults.cap; scope.thick = angular.isDefined(scope.thick) ? scope.thick : defaults.thick; scope.type = angular.isDefined(scope.type) ? scope.type : defaults.type; scope.duration = angular.isDefined(scope.duration) ? scope.duration : defaults.duration; scope.labelOnly = angular.isDefined(scope.labelOnly) ? scope.labelOnly : defaults.labelOnly; scope.foregroundColor = angular.isDefined(scope.foregroundColor) ? scope.foregroundColor : defaults.foregroundColor; scope.backgroundColor = angular.isDefined(scope.backgroundColor) ? scope.backgroundColor : defaults.backgroundColor; scope.thresholds = angular.isDefined(scope.thresholds) ? scope.thresholds : {}; scope.fractionSize = angular.isDefined(scope.fractionSize) ? scope.fractionSize : defaults.fractionSize; var gauge = new Gauge(element, scope); scope.$watch('value', watchData, false); scope.$watch('min', watchData, false); scope.$watch('max', watchData, false); scope.$watch('cap', watchOther, false); scope.$watch('thick', watchOther, false); scope.$watch('type', watchOther, false); scope.$watch('size', watchOther, false); scope.$watch('duration', watchOther, false); scope.$watch('foregroundColor', watchOther, false); scope.$watch('backgroundColor', watchOther, false); scope.$watch('thresholds', watchOther, false); scope.$watch('fractionSize', watchData, false); scope.$on('$destroy', function () { }); scope.$on('$resize', function () { }); function watchData(nv, ov) { if (!gauge) return; if (!angular.isDefined(nv) || angular.equals(nv, ov)) return; gauge.update(nv, ov); } function watchOther(nv, ov) { if (!angular.isDefined(nv) || angular.equals(nv, ov)) return; gauge.destroy(); gauge.init(); } } }; } }(angular));