| |
| /* |
| * |
| * Copyright 2016 RIFT.IO Inc |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| // (function(window){ |
| /**! |
| * @license |
| * HTML5 Canvas Gauge implementation |
| * |
| * This code is subject to MIT license. |
| * |
| * Copyright (c) 2012 Mykhailo Stadnyk <mikhus@gmail.com> |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy of |
| * this software and associated documentation files (the "Software"), to deal in |
| * the Software without restriction, including without limitation the rights to use, |
| * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the |
| * Software, and to permit persons to whom the Software is furnished to do so, |
| * subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in all |
| * copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
| * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
| * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
| * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| * |
| * @authors: Mykhailo Stadnyk <mikhus@gmail.com> |
| * Chris Poile <poile@edwards.usask.ca> |
| * Luca Invernizzi <http://www.lucainvernizzi.net> |
| * Robert Blackburn <http://www.rwblackburn.com> |
| */ |
| |
| /** |
| * @param {Object} config |
| * @constructor |
| */ |
| var Gauge = function (config) { |
| Gauge.Collection.push(this); |
| |
| /** |
| * Default gauge configuration |
| * @struct |
| */ |
| this.config = { |
| isAggregate: false, |
| renderTo: null, |
| width: 200, |
| height: 200, |
| title: false, |
| maxValue: 100, |
| minValue: 0, |
| majorTicks: [], |
| minorTicks: 10, |
| strokeTicks: true, |
| units: false, |
| valueFormat: { "int": 3, "dec": 2 }, |
| majorTicksFormat: { "int": 1, "dec": 0 }, |
| glow: true, |
| animation: { |
| delay: 10, |
| duration: 250, |
| fn: 'cycle' |
| }, |
| colors: { |
| plate: '#fff', |
| majorTicks: '#444', |
| minorTicks: '#666', |
| title: '#888', |
| units: '#888', |
| numbers: '#444', |
| needle: { start: 'rgba(240, 128, 128, 1)', end: 'rgba(255, 160, 122, .9)' } |
| }, |
| highlights: [ |
| { |
| from: 20, |
| to: 60, |
| color: '#eee' |
| }, |
| { |
| from: 60, |
| to: 80, |
| color: '#ccc' |
| }, |
| { |
| from: 80, |
| to: 100, |
| color: '#999' |
| } |
| ] |
| }; |
| |
| var |
| value = 0, |
| self = this, |
| fromValue = 0, |
| toValue = 0, |
| imready = false |
| ; |
| |
| /** |
| * Sets a new value to gauge and updates the gauge view |
| * |
| * @param {number} val - the new value to set to the gauge |
| * @return {Gauge} this - returns self |
| */ |
| this.setValue = function (val) { |
| |
| fromValue = config.animation ? value : val; |
| |
| var dv = (config.maxValue - config.minValue) / 100; |
| |
| toValue = val > config.maxValue ? |
| config.maxValue + dv : |
| val < config.minValue ? |
| config.minValue - dv : |
| val |
| ; |
| |
| value = val; |
| |
| if (toValue >= fromValue) { |
| config.arrow = 'up'; |
| } else { |
| config.arrow = 'down'; |
| } |
| |
| config.animation ? animate() : this.draw(); |
| |
| return this; |
| }; |
| |
| /** |
| * Sets a new value to gauge and updates the gauge view without |
| * any animation (even if configured) |
| * |
| * @param {number} val - the new value to set to the gauge |
| * @return {Gauge} this - returns self |
| */ |
| this.setRawValue = function (val) { |
| fromValue = value = val; |
| this.draw(); |
| return this; |
| }; |
| |
| /** |
| * Clears the value of the gauge |
| * @return {Gauge} |
| */ |
| this.clear = function () { |
| value = fromValue = toValue = this.config.minValue; |
| this.draw(); |
| return this; |
| }; |
| |
| |
| /** |
| * Returns the current value been set to the gauge |
| * |
| * @return {number} value - current gauge's value |
| */ |
| this.getValue = function () { |
| return value; |
| }; |
| |
| /** |
| * Ready event for the gauge. Use it whenever you |
| * initialize the gauge to be assured it was fully drawn |
| * before you start the update on it |
| * |
| * @event {Function} onready |
| */ |
| this.onready = function () { |
| }; |
| |
| function applyRecursive(dst, src) { |
| for (var i in src) { |
| // modification by Chris Poile, Oct 08, 2012. More correct check of an Array instance |
| if (typeof src[i] == "object" && !(Object.prototype.toString.call(src[i]) === '[object Array]') && i != 'renderTo') { |
| if (typeof dst[i] != "object") { |
| dst[i] = {}; |
| } |
| |
| applyRecursive(dst[i], src[i]); |
| } else { |
| dst[i] = src[i]; |
| } |
| } |
| }; |
| |
| applyRecursive(this.config, config); |
| |
| this.config.minValue = parseFloat(this.config.minValue); |
| this.config.maxValue = parseFloat(this.config.maxValue); |
| |
| config = this.config; |
| fromValue = value = config.minValue; |
| |
| if (!config.renderTo) { |
| throw Error("Canvas element was not specified when creating the Gauge object!"); |
| } |
| |
| var |
| canvas = config.renderTo.tagName ? config.renderTo : document.getElementById(config.renderTo), |
| ctx = canvas.getContext('2d'), |
| cache, CW, CH, CX, CY, max, cctx |
| ; |
| |
| function baseInit() { |
| canvas.width = config.width; |
| canvas.height = config.height; |
| |
| cache = canvas.cloneNode(true); |
| cctx = cache.getContext('2d'); |
| CW = canvas.width; |
| CH = canvas.height; |
| CX = CW / 2; |
| CY = CH / 2; |
| max = CX < CY ? CX : CY; |
| |
| cache.i8d = false; |
| |
| // translate cache to have 0, 0 in center |
| cctx.translate(CX, CY); |
| cctx.save(); |
| |
| // translate canvas to have 0,0 in center |
| ctx.translate(CX, CY); |
| ctx.save(); |
| }; |
| |
| // do basic initialization |
| baseInit(); |
| |
| /** |
| * Updates the gauge config |
| * |
| * @param {Object} config |
| * @return {Gauge} |
| */ |
| this.updateConfig = function (config) { |
| applyRecursive(this.config, config); |
| baseInit(); |
| this.draw(); |
| return this; |
| }; |
| |
| var animateFx = { |
| linear: function (p) { |
| return p; |
| }, |
| quad: function (p) { |
| return Math.pow(p, 2); |
| }, |
| quint: function (p) { |
| return Math.pow(p, 5); |
| }, |
| cycle: function (p) { |
| return 1 - Math.sin(Math.acos(p)); |
| }, |
| bounce: function (p) { |
| return 1 - (function (p) { |
| for (var a = 0, b = 1; 1; a += b, b /= 2) { |
| if (p >= (7 - 4 * a) / 11) { |
| return -Math.pow((11 - 6 * a - 11 * p) / 4, 2) + Math.pow(b, 2); |
| } |
| } |
| })(1 - p); |
| }, |
| elastic: function (p) { |
| return 1 - (function (p) { |
| var x = 1.5; |
| return Math.pow(2, 10 * (p - 1)) * Math.cos(20 * Math.PI * x / 3 * p); |
| })(1 - p); |
| } |
| }; |
| |
| var animateInterval = null; |
| |
| function _animate(opts) { |
| var start = new Date; |
| |
| animateInterval = setInterval(function () { |
| var |
| timePassed = new Date - start, |
| progress = timePassed / opts.duration |
| ; |
| |
| if (progress > 1) { |
| progress = 1; |
| } |
| |
| var animateFn = typeof opts.delta == "function" ? |
| opts.delta : |
| animateFx[opts.delta] |
| ; |
| |
| var delta = animateFn(progress); |
| opts.step(delta); |
| |
| if (progress == 1) { |
| clearInterval(animateInterval); |
| } |
| }, opts.delay || 10); |
| }; |
| |
| function animate() { |
| animateInterval && clearInterval(animateInterval); // stop previous animation |
| var |
| path = (toValue - fromValue), |
| from = fromValue, |
| cfg = config.animation |
| ; |
| |
| _animate({ |
| delay: cfg.delay, |
| duration: cfg.duration, |
| delta: cfg.fn, |
| step: function (delta) { |
| fromValue = parseFloat(from) + path * delta; |
| self.draw(); |
| } |
| }); |
| }; |
| |
| // defaults |
| ctx.lineCap = "round"; |
| |
| /** |
| * Drows the gauge. Normally this function should be used to |
| * initally draw the gauge |
| * |
| * @return {Gauge} this - returns the self Gauge object |
| */ |
| this.draw = function () { |
| if (!cache.i8d) { |
| // clear the cache |
| cctx.clearRect(-CX, -CY, CW, CH); |
| cctx.save(); |
| |
| var tmp = {ctx: ctx}; |
| ctx = cctx; |
| |
| drawPlate(); |
| drawHighlights(); |
| drawMinorTicks(); |
| drawMajorTicks(); |
| drawNumbers(); |
| drawTitle(); |
| drawUnits(); |
| |
| cache.i8d = true; |
| ctx = tmp.ctx; |
| delete tmp.ctx; |
| } |
| |
| // clear the canvas |
| ctx.clearRect(-CX, -CY, CW, CH); |
| ctx.save(); |
| |
| ctx.drawImage(cache, -CX, -CY, CW, CH); |
| |
| if (!Gauge.initialized) { |
| var iv = setInterval(function () { |
| if (!Gauge.initialized) { |
| return; |
| } |
| |
| clearInterval(iv); |
| |
| drawValueBox(); |
| drawNeedle(); |
| // drawArrow(); |
| |
| if (!imready) { |
| self.onready && self.onready(); |
| imready = true; |
| } |
| }, 10); |
| } else { |
| drawValueBox(); |
| drawNeedle(); |
| // drawArrow(); |
| |
| if (!imready) { |
| self.onready && self.onready(); |
| imready = true; |
| } |
| } |
| |
| return this; |
| }; |
| |
| /** |
| * Transforms degrees to radians |
| */ |
| function radians(degrees) { |
| return degrees * Math.PI / 180; |
| }; |
| |
| /** |
| * Linear gradient |
| */ |
| function lgrad(clrFrom, clrTo, len) { |
| var grad = ctx.createLinearGradient(0, 0, 0, len); |
| grad.addColorStop(0, clrFrom); |
| grad.addColorStop(1, clrTo); |
| |
| return grad; |
| }; |
| |
| function drawPlate() { |
| var |
| r0 = max / 100 * 93, |
| d0 = max - r0, |
| r1 = max / 100 * 91, |
| d1 = max - r1, |
| r2 = max / 100 * 88, |
| d2 = max - r2, |
| r3 = max / 100 * 85; |
| |
| ctx.save(); |
| |
| if (config.glow) { |
| ctx.shadowBlur = d0; |
| ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; |
| } |
| |
| ctx.beginPath(); |
| ctx.arc(0, 0, r0, 0, Math.PI * 2, true); |
| // ctx.fillStyle = lgrad( '#ddd', '#aaa', r0); |
| ctx.fillStyle = lgrad('hsla(0, 0%, 17%, 1)', 'hsla(0, 0%, 0%, 1)', r0); |
| //ctx.fill(); |
| |
| //ctx.restore(); |
| |
| ctx.beginPath(); |
| ctx.arc(0, 0, r1, 0, Math.PI * 2, true); |
| // ctx.fillStyle = lgrad( '#fafafa', '#ccc', r1); |
| ctx.fillStyle = lgrad('hsla(0, 0%, 47%, 1)', 'hsla(0, 0%, 33%, 1)', r1); |
| //ctx.fill(); |
| |
| ctx.beginPath(); |
| ctx.arc(0, 0, r2, 0, Math.PI * 2, true); |
| // ctx.fillStyle = lgrad( '#eee', '#f0f0f0', r2); |
| ctx.fillStyle = lgrad('hsla(0, 0%, 33%, 1)', 'hsla(0, 0%, 43%, 1)', r2); |
| //ctx.fill(); |
| |
| ctx.beginPath(); |
| ctx.arc(0, 0, r3, 0, Math.PI * 2, true); |
| ctx.fillStyle = config.colors.plate; |
| //ctx.fill(); |
| |
| ctx.save(); |
| }; |
| |
| /** |
| * Formats a number for display on the dial's plate using the majorTicksFormat config option. |
| * |
| * @param {number} num The number to format |
| * @returns {string} The formatted number |
| */ |
| function formatMajorTickNumber(num) { |
| var r, isDec = false; |
| |
| // First, force the correct number of digits right of the decimal. |
| if (config.majorTicksFormat.dec === 0) { |
| r = Math.round(num).toString(); |
| } else { |
| r = num.toFixed(config.majorTicksFormat.dec); |
| } |
| |
| // Second, force the correct number of digits left of the decimal. |
| if (config.majorTicksFormat["int"] > 1) { |
| // Does this number have a decimal? |
| isDec = (r.indexOf('.') > -1); |
| |
| // Is this number a negative number? |
| if (r.indexOf('-') > -1) { |
| return '-' + [ |
| config.majorTicksFormat["int"] + config.majorTicksFormat.dec + 2 + (isDec ? 1 : 0) - r.length |
| ].join('0') + r.replace('-', ''); |
| } else { |
| return [ |
| config.majorTicksFormat["int"] + config.majorTicksFormat.dec + 1 + (isDec ? 1 : 0) - r.length |
| ].join('0') + r; |
| } |
| } else { |
| return r; |
| } |
| } |
| |
| // major ticks draw |
| function drawMajorTicks() { |
| var r = max / 100 * 81; |
| |
| ctx.lineWidth = 1; |
| ctx.strokeStyle = config.colors.majorTicks; |
| ctx.save(); |
| |
| if (config.majorTicks.length === 0) { |
| var numberOfDefaultTicks = 5; |
| var tickSize = (config.maxValue - config.minValue) / numberOfDefaultTicks; |
| |
| for (var i = 0; i < numberOfDefaultTicks; i++) { |
| config.majorTicks.push(formatMajorTickNumber(config.minValue + (tickSize * i))); |
| } |
| config.majorTicks.push(formatMajorTickNumber(config.maxValue)); |
| } |
| |
| for (var i = 0; i < config.majorTicks.length; ++i) { |
| var a = 45 + i * (270 / (config.majorTicks.length - 1)); |
| ctx.rotate(radians(a)); |
| |
| ctx.beginPath(); |
| ctx.moveTo(0, r); |
| ctx.lineTo(0, r - max / 100 * 15); |
| ctx.stroke(); |
| |
| ctx.restore(); |
| ctx.save(); |
| } |
| |
| if (config.strokeTicks) { |
| ctx.rotate(radians(90)); |
| |
| ctx.beginPath(); |
| ctx.arc(0, 0, r, radians(45), radians(315), false); |
| ctx.stroke(); |
| ctx.restore(); |
| |
| ctx.save(); |
| } |
| }; |
| |
| // minor ticks draw |
| function drawMinorTicks() { |
| var r = max / 100 * 81; |
| |
| ctx.lineWidth = 1; |
| ctx.strokeStyle = config.colors.minorTicks; |
| |
| ctx.save(); |
| |
| var len = config.minorTicks * (config.majorTicks.length - 1); |
| |
| for (var i = 0; i < len; ++i) { |
| var a = 45 + i * (270 / len); |
| ctx.rotate(radians(a)); |
| |
| ctx.beginPath(); |
| ctx.moveTo(0, r); |
| ctx.lineTo(0, r - max / 100 * 7.5); |
| ctx.stroke(); |
| |
| ctx.restore(); |
| ctx.save(); |
| } |
| }; |
| |
| // tick numbers draw |
| function drawNumbers() { |
| //var r = max / 100 * 55; |
| // |
| //for (var i = 0; i < config.majorTicks.length; ++i) { |
| // var |
| // a = 45 + i * (270 / (config.majorTicks.length - 1)), |
| // p = rpoint(r, radians(a)) |
| // ; |
| // |
| // ctx.font = 20 * (max / 200) + "px Arial"; |
| // ctx.fillStyle = config.colors.numbers; |
| // ctx.lineWidth = 0; |
| // ctx.textAlign = "center"; |
| // ctx.fillText(config.majorTicks[i], p.x, p.y + 3); |
| //} |
| }; |
| |
| // title draw |
| function drawTitle() { |
| if (!config.title) { |
| return; |
| } |
| |
| ctx.save(); |
| ctx.font = 24 * (max / 200) + "px Arial"; |
| ctx.fillStyle = config.colors.title; |
| ctx.textAlign = "center"; |
| ctx.fillText(config.title, 0, -max / 4.25); |
| ctx.restore(); |
| }; |
| |
| // units draw |
| function drawUnits() { |
| if (!config.units) { |
| return; |
| } |
| |
| ctx.save(); |
| ctx.font = 22 * (max / 200) + "px Arial"; |
| ctx.fillStyle = config.colors.units; |
| ctx.textAlign = "center"; |
| ctx.fillText(config.units, 0, max / 3.25); |
| ctx.restore(); |
| }; |
| |
| |
| function drawArrow() { |
| |
| if (config.arrow != "false") { |
| |
| if (config.arrow == "up") { |
| var r = max / 100 * 2.0; |
| y0 = max / 100 * 64; |
| y1 = max / 100 * 55; |
| y2 = max / 100 * 53; |
| y3 = max / 100 * 50; |
| y4 = max / 100 * 45; |
| arrow_color = "rgba(107, 184, 20, 1)"; |
| |
| var x0 = max / 100 * -8 |
| var x1 = max / 100 * -6 |
| var x2 = max / 100 * -1.5 |
| var x3 = max / 100 * 0 |
| var x4 = max / 100 * 1.5 |
| var x5 = max / 100 * 6 |
| var x6 = max / 100 * 8 |
| |
| ctx.beginPath(); |
| ctx.moveTo(x0, y2); |
| ctx.lineTo(x3 - r, y4 + r); |
| ctx.arcTo(x3, y4 - r, x3 + r, y4 + r, r * 1.09); |
| ctx.lineTo(x6, y2); |
| ctx.arcTo(x6 + r/2.0, y2 + r/1.0, x5, y1, r*.9) |
| ctx.lineTo(x4, y3); |
| ctx.lineTo(x4, y0); |
| ctx.arcTo(x3, y0 + r, x2, y0, r*.9); |
| ctx.lineTo(x2, y3); |
| ctx.lineTo(x1, y1); |
| ctx.arcTo(x1 - r, y1 - r/2.0, x0, y2, r*1.09) |
| |
| |
| ctx.closePath(); |
| ctx.fillStyle = arrow_color; |
| ctx.fill(); |
| } else { |
| var r = max / 100 * 2.0; |
| var y0 = max / 100 * 45; |
| var y1 = max / 100 * 54; |
| var y2 = max / 100 * 56; |
| var y3 = max / 100 * 59; |
| var y4 = max / 100 * 64; |
| var arrow_color = "rgba(252, 38, 50, 1)"; |
| |
| var x0 = max / 100 * -8 |
| var x1 = max / 100 * -6 |
| var x2 = max / 100 * -1.5 |
| var x3 = max / 100 * 0 |
| var x4 = max / 100 * 1.5 |
| var x5 = max / 100 * 6 |
| var x6 = max / 100 * 8 |
| |
| ctx.beginPath(); |
| ctx.moveTo(x0, y2); |
| ctx.lineTo(x3 - r, y4 - r); |
| ctx.arcTo(x3, y4 + r, x3 + r, y4 - r, r * 1.09); |
| ctx.lineTo(x6, y2); |
| ctx.arcTo(x6 + r/2.0, y2 - r/1.0, x5, y1, r*.9) |
| ctx.lineTo(x4, y3); |
| ctx.lineTo(x4, y0); |
| ctx.arcTo(x3, y0 - r, x2, y0, r*.9); |
| ctx.lineTo(x2, y3); |
| ctx.lineTo(x1, y1); |
| ctx.arcTo(x1 - r, y1 + r/2.0, x0, y2, r*1.09) |
| |
| |
| ctx.closePath(); |
| ctx.fillStyle = arrow_color; |
| ctx.fill(); |
| } |
| ctx.save(); |
| ctx.restore(); |
| } |
| } |
| |
| function padValue(val) { |
| var cdec = config.valueFormat['dec'] |
| var cint = config.valueFormat['int'] |
| |
| val = parseFloat(val); |
| var n = (val < 0); |
| |
| val = Math.abs(val); |
| |
| if (cdec > 0) { |
| val = val.toFixed(cdec).toString().split('.'); |
| |
| for (var i = 0, s = cint - val[0].length; i < s; ++i) { |
| val[0] = '0' + val[0]; |
| } |
| |
| val = (n ? '-' : '') + val[0] + '.' + val[1]; |
| } else { |
| val = Math.round(val).toString(); |
| |
| for (var i = 0, s = cint - val.length; i < s; ++i) { |
| val = '0' + val; |
| } |
| |
| val = (n ? '-' : '') + val |
| } |
| |
| return val; |
| }; |
| |
| function rpoint(r, a) { |
| var |
| x = 0, y = r, |
| |
| sin = Math.sin(a), |
| cos = Math.cos(a), |
| |
| X = x * cos - y * sin, |
| Y = x * sin + y * cos |
| ; |
| |
| return { x: X, y: Y }; |
| }; |
| function clearCircle(x, y, radius) |
| { |
| ctx.beginPath(); |
| ctx.arc(x, y, radius, 0, 2 * Math.PI, false); |
| ctx.clip(); |
| ctx.clearRect(x - radius - 1, y - radius - 1, |
| radius * 2 + 2, radius * 2 + 2); |
| |
| }; |
| // draws the highlight colors |
| function drawHighlights() { |
| ctx.save(); |
| |
| var r1 = max / 100 * 81; |
| var r2 = r1 - max / 100 * 15; |
| |
| for (var i = 0, s = config.highlights.length; i < s; i++) { |
| var |
| hlt = config.highlights[i], |
| vd = (config.maxValue - config.minValue) / 270, |
| sa = radians(45 + (hlt.from - config.minValue) / vd), |
| ea = radians(45 + (hlt.to - config.minValue) / vd) |
| ; |
| |
| ctx.beginPath(); |
| |
| ctx.rotate(radians(90)); |
| ctx.arc(0, 0, r1, sa, ea, false); |
| ctx.restore(); |
| ctx.save(); |
| |
| var |
| ps = rpoint(r2, sa), |
| pe = rpoint(r1, sa) |
| ; |
| ctx.moveTo(ps.x, ps.y); |
| ctx.lineTo(pe.x, pe.y); |
| |
| var |
| ps1 = rpoint(r1, ea), |
| pe1 = rpoint(r2, ea) |
| ; |
| |
| ctx.lineTo(ps1.x, ps1.y); |
| ctx.lineTo(pe1.x, pe1.y); |
| ctx.lineTo(ps.x, ps.y); |
| |
| ctx.closePath(); |
| |
| ctx.fillStyle = hlt.color; |
| ctx.fill(); |
| |
| ctx.beginPath(); |
| ctx.rotate(radians(90)); |
| ctx.arc(0, 0, r2, sa - 0.2, ea + 0.2, false); |
| ctx.restore(); |
| |
| ctx.closePath(); |
| |
| ctx.fillStyle = config.colors.plate; |
| ctx.fill(); |
| ctx.save(); |
| ctx.imageSmoothingEnabled = true |
| //clearCircle(0, 0, 100) |
| |
| } |
| }; |
| |
| // drows the gauge needle |
| function drawNeedle() { |
| var |
| r1 = 0 , |
| r2 = 0 , |
| rIn = max / 100 * 85, |
| rOut = max / 100 * 63, |
| rP = max / 100 * 59, |
| pad1 = max / 100 * 3, |
| pad2 = max / 100 * 2.5, |
| |
| shad = function () { |
| ctx.shadowOffsetX = 2; |
| ctx.shadowOffsetY = 2; |
| ctx.shadowBlur = 10; |
| // ctx.shadowColor = 'rgba(188, 143, 143, 0.45)'; |
| ctx.shadowColor = 'rgba(50, 50, 50, .3)'; |
| } |
| ; |
| |
| shad(); |
| |
| ctx.save(); |
| |
| if (fromValue < 0) { |
| fromValue = Math.abs(config.minValue - fromValue); |
| } else if (config.minValue > 0) { |
| fromValue -= config.minValue |
| } else { |
| fromValue = Math.abs(config.minValue) + fromValue; |
| } |
| |
| ctx.rotate(radians(45 + fromValue / ((config.maxValue - config.minValue) / 270))); |
| |
| ctx.beginPath(); |
| ctx.lineTo(-pad2, rOut); |
| ctx.lineTo(-pad2, rIn); |
| ctx.lineTo(pad2, rIn); |
| ctx.lineTo(pad2, rOut); |
| ctx.lineTo(0, rP); |
| ctx.closePath(); |
| ctx.strokeStyle = "#999" |
| ctx.stroke(); |
| |
| ctx.fillStyle = lgrad( |
| config.colors.needle.start, |
| config.colors.needle.end, |
| rIn - rOut |
| ); |
| ctx.fill(); |
| |
| // ctx.beginPath(); |
| // ctx.lineTo(-_pad2, _rOut); |
| // ctx.lineTo(-_pad2, _rIn); |
| // ctx.lineTo(_pad2, _rIn); |
| // ctx.lineTo(_pad2, _rOut); |
| // ctx.lineTo(0, _rOut - 5); |
| // ctx.closePath(); |
| |
| // ctx.fillStyle = "#ccc" |
| // ctx.fill(); |
| |
| ctx.beginPath(); |
| ctx.lineTo(-pad2, rIn); |
| ctx.lineTo(-pad2, rIn); |
| ctx.lineTo(-pad1, 0); |
| ctx.lineTo(-pad2, rOut); |
| ctx.lineTo(pad2 / 2 - 2, rOut); |
| ctx.closePath(); |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.2)'; |
| |
| //ctx.fill(); |
| |
| ctx.restore(); |
| |
| //shad(); |
| |
| ctx.beginPath(); |
| ctx.arc(0, 0, r2 +.5, 0, Math.PI * 2, true); |
| // ctx.fillStyle = lgrad( '#f0f0f0', '#ccc', r1); |
| ctx.fillStyle = lgrad('#3b3b3b', '#121212', r1); |
| //ctx.fill(); |
| |
| ctx.restore(); |
| |
| ctx.beginPath(); |
| ctx.arc(0, 0, r2, 0, Math.PI * 2, true); |
| // ctx.fillStyle = lgrad( "#e8e8e8", "#f5f5f5", r2); |
| ctx.fillStyle = 'rgba(255,255,255,1)'; |
| //ctx.fill(); |
| }; |
| |
| function roundRect(x, y, w, h, r) { |
| ctx.beginPath(); |
| |
| ctx.moveTo(x + r, y); |
| ctx.lineTo(x + w - r, y); |
| |
| ctx.quadraticCurveTo(x + w, y, x + w, y + r); |
| ctx.lineTo(x + w, y + h - r); |
| |
| ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); |
| ctx.lineTo(x + r, y + h); |
| |
| ctx.quadraticCurveTo(x, y + h, x, y + h - r); |
| ctx.lineTo(x, y + r); |
| |
| ctx.quadraticCurveTo(x, y, x + r, y); |
| |
| ctx.closePath(); |
| }; |
| |
| // value box draw |
| function drawValueBox() { |
| ctx.save(); |
| |
| ctx.font = 100 + " " + 73 * (max / 200) + "px 'roboto-light"; |
| var |
| text = padValue(value), |
| tw = ctx.measureText('-' + padValue(0)).width, |
| y = max - max / 100 * 96, |
| x = 0, |
| th = 0.12 * max |
| ; |
| |
| ctx.save(); |
| |
| roundRect( |
| -tw / 2 - 0.025 * max, |
| y - th - 0.04 * max, |
| tw + 0.05 * max, |
| th + 0.07 * max, |
| 0.025 * max |
| ); |
| |
| var grd = ctx.createRadialGradient( |
| x, |
| y - 0.12 * max - 0.025 * max + (0.12 * max + 0.045 * max) / 2, |
| max / 10, |
| x, |
| y - 0.12 * max - 0.025 * max + (0.12 * max + 0.045 * max) / 2, |
| max / 5 |
| ); |
| |
| // grd.addColorStop( 0, "#888"); |
| // grd.addColorStop( 1, "#666"); |
| |
| // ctx.strokeStyle = grd; |
| // ctx.lineWidth = 0.05 * max; |
| // ctx.stroke(); |
| |
| // ctx.shadowBlur = 0.012 * max; |
| // ctx.shadowColor = 'rgba(0, 0, 0, 1)'; |
| |
| // ctx.fillStyle = "#babab2"; |
| // ctx.fill(); |
| |
| // ctx.restore(); |
| |
| // ctx.shadowOffsetX = 0.004 * max; |
| // ctx.shadowOffsetY = 0.004 * max; |
| // ctx.shadowBlur = 0.012 * max; |
| // ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'; |
| |
| // ctx.fillStyle = "#444"; |
| ctx.fillStyle = "rgba(50,50,50,1)"; |
| // ctx.fillStyle = "rgba(50,50,50,1)"; |
| ctx.textAlign = "center"; |
| |
| ctx.fillText(text, -x, y); |
| |
| |
| ctx.save(); |
| ctx.font = 100 + " " + 20 * (max / 200) + "px 'roboto-light"; |
| //ctx.fillText(config.unit, -x, y + 30); |
| ctx.restore(); |
| |
| }; |
| }; |
| |
| // initialize |
| Gauge.initialized = false; |
| (function () { |
| var |
| d = document, |
| h = d.getElementsByTagName('head')[0], |
| ie = navigator.userAgent.toLocaleLowerCase().indexOf('msie') != -1, |
| url = 'fonts/digital-7-mono.' + (ie ? 'eot' : 'ttf'), |
| |
| // RW: don't use mono font, this was causing err in js console |
| text = '', |
| // text = "@font-face {" + |
| // "font-family: 'Led';" + |
| // "src: url('" + url + "');" + |
| // "}", |
| ss, |
| r = d.createElement('style') |
| ; |
| |
| r.type = 'text/css'; |
| |
| if (ie) { |
| h.appendChild(r); |
| ss = r.styleSheet; |
| ss.cssText = text; |
| } else { |
| try { |
| r.appendChild(d.createTextNode(text)); |
| } catch (e) { |
| r.cssText = text; |
| } |
| |
| h.appendChild(r); |
| |
| ss = r.styleSheet ? r.styleSheet : |
| (r.sheet || d.styleSheets[d.styleSheets.length - 1]) |
| ; |
| } |
| |
| var iv = setInterval(function () { |
| if (!d.body) { |
| return; |
| } |
| |
| clearInterval(iv); |
| |
| var dd = d.createElement('div'); |
| |
| dd.style.fontFamily = 'Led'; |
| dd.style.position = 'absolute'; |
| dd.style.height = dd.style.width = 0; |
| dd.style.overflow = 'hidden'; |
| |
| dd.innerHTML = '.'; |
| |
| d.body.appendChild(dd); |
| |
| setTimeout(function () { // no other way to handle font is rendered by a browser |
| // just give the browser around 250ms to do that :( |
| Gauge.initialized = true; |
| dd.parentNode.removeChild(dd); |
| }, 250); |
| }, 1); |
| })(); |
| |
| Gauge.Collection = []; |
| Gauge.Collection.get = function (id) { |
| var self = this; |
| |
| if (typeof(id) == 'string') { |
| for (var i = 0, s = self.length; i < s; i++) { |
| var canvas = self[i].config.renderTo.tagName ? self[i].config.renderTo : document.getElementById(self[i].config.renderTo); |
| if (canvas.getAttribute('id') == id) { |
| return self[i]; |
| } |
| } |
| } else if (typeof(id) == 'number') { |
| return self[id]; |
| } else { |
| return null; |
| } |
| }; |
| |
| function domReady(handler) { |
| if (window.addEventListener) { |
| window.addEventListener('DOMContentLoaded', handler, false); |
| } else { |
| window.attachEvent('onload', handler); |
| } |
| } |
| |
| domReady(function () { |
| function toCamelCase(arr) { |
| var str = arr[0]; |
| for (var i = 1, s = arr.length; i < s; i++) { |
| str += arr[i].substr(0, 1).toUpperCase() + arr[i].substr(1, arr[i].length - 1); |
| } |
| return str; |
| }; |
| |
| function trim(str) { |
| return str.replace(/^\s+|\s+$/g, ''); |
| }; |
| |
| var c = document.getElementsByTagName('canvas'); |
| |
| for (var i = 0, s = c.length; i < s; i++) { |
| |
| if (c[i].getAttribute('data-type') == 'canv-gauge') { |
| var |
| gauge = c[i], |
| config = {}, |
| prop, |
| w = parseInt(gauge.getAttribute('width'), 10), |
| h = parseInt(gauge.getAttribute('height'), 10) |
| ; |
| |
| config.renderTo = gauge; |
| |
| if (w) { |
| config.width = w; |
| } |
| |
| if (h) { |
| config.height = h; |
| } |
| |
| for (var ii = 0, ss = gauge.attributes.length; ii < ss; ii++) { |
| prop = gauge.attributes.item(ii).nodeName; |
| |
| if (prop != 'data-type' && prop.substr(0, 5) == 'data-') { |
| var |
| cfgProp = prop.substr(5, prop.length - 5).toLowerCase().split('-'), |
| attrValue = gauge.getAttribute(prop) |
| ; |
| |
| if (!attrValue) { |
| continue; |
| } |
| |
| switch (cfgProp[0]) { |
| case 'colors' : |
| { |
| if (cfgProp[1]) { |
| if (!config.colors) { |
| config.colors = {}; |
| } |
| |
| if (cfgProp[1] == 'needle') { |
| var parts = attrValue.split(/\s+/); |
| |
| if (parts[0] && parts[1]) { |
| config.colors.needle = { start: parts[0], end: parts[1] }; |
| } |
| else { |
| config.colors.needle = attrValue; |
| } |
| } |
| else { |
| cfgProp.shift(); |
| config.colors[toCamelCase(cfgProp)] = attrValue; |
| } |
| } |
| break; |
| } |
| case 'highlights' : |
| { |
| if (!config.highlights) { |
| config.highlights = []; |
| } |
| |
| var hls = attrValue.match(/(?:(?:-?\d*\.)?(-?\d+){1,2} ){2}(?:(?:#|0x)?(?:[0-9A-F|a-f]){3,8}|rgba?\(.*?\))/g); |
| |
| for (var j = 0, l = hls.length; j < l; j++) { |
| var |
| cfg = trim(hls[j]).split(/\s+/), |
| hlCfg = {} |
| ; |
| |
| if (cfg[0] && cfg[0] != '') { |
| hlCfg.from = cfg[0]; |
| } |
| |
| if (cfg[1] && cfg[1] != '') { |
| hlCfg.to = cfg[1]; |
| } |
| |
| if (cfg[2] && cfg[2] != '') { |
| hlCfg.color = cfg[2]; |
| } |
| |
| config.highlights.push(hlCfg); |
| } |
| break; |
| } |
| case 'animation' : |
| { |
| if (cfgProp[1]) { |
| if (!config.animation) { |
| config.animation = {}; |
| } |
| |
| if (cfgProp[1] == 'fn' && /^\s*function\s*\(/.test(attrValue)) { |
| attrValue = eval('(' + attrValue + ')'); |
| } |
| |
| config.animation[cfgProp[1]] = attrValue; |
| } |
| break; |
| } |
| default : |
| { |
| var cfgName = toCamelCase(cfgProp); |
| |
| if (cfgName == 'onready') { |
| continue; |
| } |
| |
| if (cfgName == 'majorTicks') { |
| attrValue = attrValue.split(/\s+/); |
| } |
| else if (cfgName == 'strokeTicks' || cfgName == 'glow') { |
| attrValue = attrValue == 'true' ? true : false; |
| } |
| else if (cfgName == 'valueFormat') { |
| var val = attrValue.split('.'); |
| |
| if (val.length == 2) { |
| attrValue = { |
| 'int': parseInt(val[0], 10), |
| 'dec': parseInt(val[1], 10) |
| } |
| } |
| else { |
| continue; |
| } |
| } |
| |
| config[cfgName] = attrValue; |
| break; |
| } |
| } |
| } |
| } |
| |
| var g = new Gauge(config); |
| |
| if (gauge.getAttribute('data-value')) { |
| g.setRawValue(parseFloat(gauge.getAttribute('data-value'))); |
| } |
| |
| if (gauge.getAttribute('data-onready')) { |
| g.onready = function () { |
| eval(this.config.renderTo.getAttribute('data-onready')); |
| }; |
| } |
| |
| g.draw(); |
| } |
| } |
| }); |
| module.exports = Gauge; |
| // window['Gauge'] = Gauge; |
| |
| // })(window); |