/** * JustGage - a handy JavaScript plugin for generating and animating nice & clean dashboard gauges. * Copyright (c) 2012 Bojan Djuricic - pindjur(at)gmail(dot)com | http://www.madcog.com * Licensed under MIT. * Date: 31/07/2012 * @author Bojan Djuricic (@Toorshia) * @version 1.0 * * http://www.justgage.com */ JustGage = function(config) { if (!config.id) {alert("Missing id parameter for gauge!"); return false;} if (!document.getElementById(config.id)) {alert("No element with id: \""+config.id+"\" found!"); return false;} // configurable parameters this.config = { // id : string // this is container element id id : config.id, // title : string // gauge title title : (config.title) ? config.title : "Title", // titleFontColor : string // color of gauge title titleFontColor : (config.titleFontColor) ? config.titleFontColor : "#999999", // value : int // value gauge is showing value : (config.value) ? config.value : 0, // valueFontColor : string // color of label showing current value valueFontColor : (config.valueFontColor) ? config.valueFontColor : "#010101", // min : int // min value min : (config.min) ? config.min : 0, // max : int // max value max : (config.max) ? config.max : 100, // showMinMax : bool // hide or display min and max values showMinMax : (config.showMinMax != null) ? config.showMinMax : true, // gaugeWidthScale : float // width of the gauge element gaugeWidthScale : (config.gaugeWidthScale) ? config.gaugeWidthScale : 1.0, // gaugeColor : string // background color of gauge element gaugeColor : (config.gaugeColor) ? config.gaugeColor : "#edebeb", // label : string // text to show below value label : (config.label) ? config.label : "", // showInnerShadow : bool // give gauge element small amount of inner shadow showInnerShadow : (config.showInnerShadow != null) ? config.showInnerShadow : true, // shadowOpacity : int // 0 ~ 1 shadowOpacity : (config.shadowOpacity) ? config.shadowOpacity : 0.2, // shadowSize: int // inner shadow size shadowSize : (config.shadowSize) ? config.shadowSize : 5, // shadowVerticalOffset : int // how much shadow is offset from top shadowVerticalOffset : (config.shadowVerticalOffset) ? config.shadowVerticalOffset : 3, // levelColors : string[] // colors of indicator, from lower to upper, in RGB format levelColors : (config.levelColors) ? config.levelColors : percentColors, // levelColorsGradient : bool // whether to use gradual color change for value, or sector-based levelColorsGradient : (config.levelColorsGradient != null) ? config.levelColorsGradient : true, // labelFontColor : string // color of label showing label under value labelFontColor : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3", // startAnimationTime : int // length of initial animation startAnimationTime : (config.startAnimationTime) ? config.startAnimationTime : 700, // startAnimationType : string // type of initial animation (linear, >, <, <>, bounce) startAnimationType : (config.startAnimationType) ? config.startAnimationType : ">", // refreshAnimationTime : int // length of refresh animation refreshAnimationTime : (config.refreshAnimationTime) ? config.refreshAnimationTime : 700, // refreshAnimationType : string // type of refresh animation (linear, >, <, <>, bounce) refreshAnimationType : (config.refreshAnimationType) ? config.refreshAnimationType : ">" }; // overflow values if (config.value > this.config.max) this.config.value = this.config.max; if (config.value < this.config.min) this.config.value = this.config.min; this.originalValue = config.value; // canvas this.canvas = Raphael(this.config.id, "100%", "100%"); // canvas dimensions //var canvasW = document.getElementById(this.config.id).clientWidth; //var canvasH = document.getElementById(this.config.id).clientHeight; var canvasW = getStyle(document.getElementById(this.config.id), "width").slice(0, -2) * 1; var canvasH = getStyle(document.getElementById(this.config.id), "height").slice(0, -2) * 1; // widget dimensions var widgetW, widgetH; if ((canvasW / canvasH) > 1.25) { widgetW = 1.25 * canvasH; widgetH = canvasH; } else { widgetW = canvasW; widgetH = canvasW / 1.25; } // delta var dx = (canvasW - widgetW)/2; var dy = (canvasH - widgetH)/2; // title var titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10; var titleX = dx + widgetW / 2; var titleY = dy + widgetH / 6.5; // value var valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 6.4) : 16; var valueX = dx + widgetW / 2; var valueY = dy + widgetH / 1.4; // label var labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; var labelX = dx + widgetW / 2; //var labelY = dy + widgetH / 1.126760563380282; var labelY = valueY + valueFontSize / 2 + 6; // min var minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; var minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ; var minY = dy + widgetH / 1.126760563380282; // max var maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; var maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ; var maxY = dy + widgetH / 1.126760563380282; // parameters this.params = { canvasW : canvasW, canvasH : canvasH, widgetW : widgetW, widgetH : widgetH, dx : dx, dy : dy, titleFontSize : titleFontSize, titleX : titleX, titleY : titleY, valueFontSize : valueFontSize, valueX : valueX, valueY : valueY, labelFontSize : labelFontSize, labelX : labelX, labelY : labelY, minFontSize : minFontSize, minX : minX, minY : minY, maxFontSize : maxFontSize, maxX : maxX, maxY : maxY }; // pki - custom attribute for generating gauge paths this.canvas.customAttributes.pki = function (value, min, max, w, h, dx, dy, gws) { var alpha = (1 - (value - min) / (max - min)) * Math.PI , Ro = w / 2 - w / 10, Ri = Ro - w / 6.666666666666667 * gws, Cx = w / 2 + dx, Cy = h / 1.25 + dy, Xo = w / 2 + dx + Ro * Math.cos(alpha), Yo = h - (h - Cy) + dy - Ro * Math.sin(alpha), Xi = w / 2 + dx + Ri * Math.cos(alpha), Yi = h - (h - Cy) + dy - Ri * Math.sin(alpha), path; path += "M" + (Cx - Ri) + "," + Cy + " "; path += "L" + (Cx - Ro) + "," + Cy + " "; path += "A" + Ro + "," + Ro + " 0 0,1 " + Xo + "," + Yo + " "; path += "L" + Xi + "," + Yi + " "; path += "A" + Ri + "," + Ri + " 0 0,0 " + (Cx - Ri) + "," + Cy + " "; path += "z "; return { path: path }; } // gauge this.gauge = this.canvas.path().attr({ "stroke": "none", "fill": this.config.gaugeColor, pki: [this.config.max, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale] }); this.gauge.id = this.config.id+"-gauge"; // level this.level = this.canvas.path().attr({ "stroke": "none", "fill": getColorForPercentage((this.config.value - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.levelColorsGradient), pki: [this.config.min, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale] }); this.level.id = this.config.id+"-level"; // title this.txtTitle = this.canvas.text(this.params.titleX, this.params.titleY, this.config.title); this.txtTitle. attr({ "font-size":this.params.titleFontSize, "font-weight":"bold", "font-family":"Arial", "fill":this.config.titleFontColor, "fill-opacity":"1" }); this.txtTitle.id = this.config.id+"-txttitle"; // value this.txtValue = this.canvas.text(this.params.valueX, this.params.valueY, this.originalValue); this.txtValue. attr({ "font-size":this.params.valueFontSize, "font-weight":"bold", "font-family":"Arial", "fill":this.config.valueFontColor, "fill-opacity":"0" }); this.txtValue.id = this.config.id+"-txtvalue"; // label this.txtLabel = this.canvas.text(this.params.labelX, this.params.labelY, this.config.label); this.txtLabel. attr({ "font-size":this.params.labelFontSize, "font-weight":"normal", "font-family":"Arial", "fill":this.config.labelFontColor, "fill-opacity":"0" }); this.txtLabel.id = this.config.id+"-txtlabel"; // min this.txtMin = this.canvas.text(this.params.minX, this.params.minY, this.config.min); this.txtMin. attr({ "font-size":this.params.minFontSize, "font-weight":"normal", "font-family":"Arial", "fill":this.config.labelFontColor, "fill-opacity": (this.config.showMinMax == true)? "1" : "0" }); this.txtMin.id = this.config.id+"-txtmin"; // max this.txtMax = this.canvas.text(this.params.maxX, this.params.maxY, this.config.max); this.txtMax. attr({ "font-size":this.params.maxFontSize, "font-weight":"normal", "font-family":"Arial", "fill":this.config.labelFontColor, "fill-opacity": (this.config.showMinMax == true)? "1" : "0" }); this.txtMax.id = this.config.id+"-txtmax"; var defs = this.canvas.canvas.childNodes[1]; var svg = "http://www.w3.org/2000/svg"; if (ie < 9) { onCreateElementNsReady(function() { this.generateShadow(); }); } else { this.generateShadow(svg, defs); } // animate this.level.animate({pki: [this.config.value, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale]}, this.config.startAnimationTime, this.config.startAnimationType); this.txtValue.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType); this.txtLabel.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType); }; // refresh gauge level JustGage.prototype.refresh = function(val) { // overflow values originalVal = val; if (val > this.config.max) {val = this.config.max;} if (val < this.config.min) {val = this.config.min;} var color = getColorForPercentage((val - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.levelColorsGradient); this.canvas.getById(this.config.id+"-txtvalue").attr({"text":originalVal}); this.canvas.getById(this.config.id+"-level").animate({pki: [val, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale], "fill":color}, this.config.refreshAnimationTime, this.config.refreshAnimationType); }; var percentColors = [ "#a9d70b", "#f9c802", "#ff0000" ] JustGage.prototype.generateShadow = function(svg, defs) { // FILTER var gaussFilter=document.createElementNS(svg,"filter"); gaussFilter.setAttribute("id", this.config.id + "-inner-shadow"); defs.appendChild(gaussFilter); // offset var feOffset = document.createElementNS(svg,"feOffset"); feOffset.setAttribute("dx", 0); feOffset.setAttribute("dy", this.config.shadowVerticalOffset); gaussFilter.appendChild(feOffset); // blur var feGaussianBlur = document.createElementNS(svg,"feGaussianBlur"); feGaussianBlur.setAttribute("result","offset-blur"); feGaussianBlur.setAttribute("stdDeviation", this.config.shadowSize); gaussFilter.appendChild(feGaussianBlur); // composite 1 var feComposite1 = document.createElementNS(svg,"feComposite"); feComposite1.setAttribute("operator","out"); feComposite1.setAttribute("in", "SourceGraphic"); feComposite1.setAttribute("in2","offset-blur"); feComposite1.setAttribute("result","inverse"); gaussFilter.appendChild(feComposite1); // flood var feFlood = document.createElementNS(svg,"feFlood"); feFlood.setAttribute("flood-color","black"); feFlood.setAttribute("flood-opacity", this.config.shadowOpacity); feFlood.setAttribute("result","color"); gaussFilter.appendChild(feFlood); // composite 2 var feComposite2 = document.createElementNS(svg,"feComposite"); feComposite2.setAttribute("operator","in"); feComposite2.setAttribute("in", "color"); feComposite2.setAttribute("in2","inverse"); feComposite2.setAttribute("result","shadow"); gaussFilter.appendChild(feComposite2); // composite 3 var feComposite3 = document.createElementNS(svg,"feComposite"); feComposite3.setAttribute("operator","over"); feComposite3.setAttribute("in", "shadow"); feComposite3.setAttribute("in2","SourceGraphic"); gaussFilter.appendChild(feComposite3); // set shadow if (this.config.showInnerShadow == true) { this.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)"); this.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)"); } } var getColorForPercentage = function(pct, col, grad) { var no = col.length; if (no === 1) return col[0]; var inc = (grad) ? (1 / (no - 1)) : (1 / no); var colors = new Array(); for (var i = 0; i < col.length; i++) { var percentage = (grad) ? (inc * i) : (inc * (i + 1)); var rval = parseInt((cutHex(col[i])).substring(0,2),16); var gval = parseInt((cutHex(col[i])).substring(2,4),16); var bval = parseInt((cutHex(col[i])).substring(4,6),16); colors[i] = { pct: percentage, color: { r: rval, g: gval, b: bval } }; } if(pct == 0) return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')'; for (var i = 0; i < colors.length; i++) { if (pct <= colors[i].pct) { if (grad == true) { var lower = colors[i - 1]; var upper = colors[i]; var range = upper.pct - lower.pct; var rangePct = (pct - lower.pct) / range; var pctLower = 1 - rangePct; var pctUpper = rangePct; var color = { r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper), g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper), b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper) }; return 'rgb(' + [color.r, color.g, color.b].join(',') + ')'; } else { return 'rgb(' + [colors[i].color.r, colors[i].color.g, colors[i].color.b].join(',') + ')'; } } } } function getRandomInt (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function cutHex(str) {return (str.charAt(0)=="#") ? str.substring(1,7):str} function getStyle(oElm, strCssRule){ var strValue = ""; if(document.defaultView && document.defaultView.getComputedStyle){ strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); } else if(oElm.currentStyle){ strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){ return p1.toUpperCase(); }); strValue = oElm.currentStyle[strCssRule]; } return strValue; } function onCreateElementNsReady(func) { if (document.createElementNS != undefined) { func(); } else { setTimeout(function() { onCreateElementNsReady(func); }, 100); } } // ---------------------------------------------------------- // A short snippet for detecting versions of IE in JavaScript // without resorting to user-agent sniffing // ---------------------------------------------------------- // If you're not in IE (or IE version is less than 5) then: // ie === undefined // If you're in IE (>=5) then you can determine which version: // ie === 7; // IE7 // Thus, to detect IE: // if (ie) {} // And to detect the version: // ie === 6 // IE6 // ie > 7 // IE8, IE9 ... // ie < 9 // Anything less than IE9 // ---------------------------------------------------------- // UPDATE: Now using Live NodeList idea from @jdalton var ie = (function(){ var undef, v = 3, div = document.createElement('div'), all = div.getElementsByTagName('i'); while ( div.innerHTML = '', all[0] ); return v > 4 ? v : undef; }());