| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 | /* global window, define, module */(function(global, factory) {  var Gauge = factory(global);  if(typeof define === "function" && define.amd) {    // AMD support    define(function() {return Gauge;});  }else if(typeof module === "object" && module.exports) {    // CommonJS support    module.exports = Gauge;  }else {    // We are probably running in the browser    global.Gauge = Gauge;  }})(typeof window === "undefined" ? this : window, function(global, undefined) {  var document = global.document,    slice = Array.prototype.slice,    requestAnimationFrame = (global.requestAnimationFrame ||        global.mozRequestAnimationFrame ||        global.webkitRequestAnimationFrame ||        global.msRequestAnimationFrame ||        function(cb) {          return setTimeout(cb, 1000 / 60);        });  // EXPERIMENTAL!!  /**   * Simplistic animation function for animating the gauge. That's all!   * Options are:   * {   *  duration: 1,    // In seconds   *  start: 0,       // The start value   *  end: 100,       // The end value   *  step: function, // REQUIRED! The step function that will be passed the value and does something   *  easing: function // The easing function. Default is easeInOutCubic   * }   */  function Animation(options) {    var duration = options.duration,        currentIteration = 1,        iterations = 60 * duration,        start = options.start || 0,        end = options.end,        change = end - start,        step = options.step,        easing = options.easing || function easeInOutCubic(pos) {          // https://github.com/danro/easing-js/blob/master/easing.js          if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);          return 0.5 * (Math.pow((pos-2),3) + 2);        };    function animate() {      var progress = currentIteration / iterations,           value = change * easing(progress) + start;      // console.log(progress + ", " + value);      step(value, currentIteration);      currentIteration += 1;      if(progress < 1) {        requestAnimationFrame(animate);      }    }    // start!    requestAnimationFrame(animate);  }  var Gauge = (function() {    var SVG_NS = "http://www.w3.org/2000/svg";    var GaugeDefaults = {      centerX: 50,      centerY: 50    };    var defaultOptions = {      dialRadius: 40,      dialStartAngle: 135,      dialEndAngle: 45,      value: 0,      max: 100,      min: 0,      valueDialClass: "value",      valueClass: "value-text",      dialClass: "dial",      gaugeClass: "gauge",      showValue: true,      gaugeColor: null,      label: function(val) {return Math.round(val);}    };    function shallowCopy(/* source, ...targets*/) {      var target = arguments[0], sources = slice.call(arguments, 1);      sources.forEach(function(s) {        for(var k in s) {          if(s.hasOwnProperty(k)) {            target[k] = s[k];          }        }      });      return target;    }    /**     * A utility function to create SVG dom tree     * @param {String} name The SVG element name     * @param {Object} attrs The attributes as they appear in DOM e.g. stroke-width and not strokeWidth     * @param {Array} children An array of children (can be created by this same function)     * @return The SVG element     */    function svg(name, attrs, children) {      var elem = document.createElementNS(SVG_NS, name);      for(var attrName in attrs) {        elem.setAttribute(attrName, attrs[attrName]);      }      if(children) {        children.forEach(function(c) {          elem.appendChild(c);        });      }      return elem;    }    /**     * Translates percentage value to angle. e.g. If gauge span angle is 180deg, then 50%     * will be 90deg     */    function getAngle(percentage, gaugeSpanAngle) {      return percentage * gaugeSpanAngle / 100;    }    function normalize(value, min, limit) {      var val = Number(value);      if(val > limit) return limit;      if(val < min) return min;      return val;    }    function getValueInPercentage(value, min, max) {      var newMax = max - min, newVal = value - min;      return 100 * newVal / newMax;      // var absMin = Math.abs(min);      // return 100 * (absMin + value) / (max + absMin);    }    /**     * Gets cartesian co-ordinates for a specified radius and angle (in degrees)     * @param cx {Number} The center x co-oriinate     * @param cy {Number} The center y co-ordinate     * @param radius {Number} The radius of the circle     * @param angle {Number} The angle in degrees     * @return An object with x,y co-ordinates     */    function getCartesian(cx, cy, radius, angle) {      var rad = angle * Math.PI / 180;      return {        x: Math.round((cx + radius * Math.cos(rad)) * 1000) / 1000,        y: Math.round((cy + radius * Math.sin(rad)) * 1000) / 1000      };    }    // Returns start and end points for dial    // i.e. starts at 135deg ends at 45deg with large arc flag    // REMEMBER!! angle=0 starts on X axis and then increases clockwise    function getDialCoords(radius, startAngle, endAngle) {      var cx = GaugeDefaults.centerX,          cy = GaugeDefaults.centerY;      return {        end: getCartesian(cx, cy, radius, endAngle),      	start: getCartesian(cx, cy, radius, startAngle)      };    }    /**     * Creates a Gauge object. This should be called without the 'new' operator. Various options     * can be passed for the gauge:     * {     *    dialStartAngle: The angle to start the dial. MUST be greater than dialEndAngle. Default 135deg     *    dialEndAngle: The angle to end the dial. Default 45deg     *    radius: The gauge's radius. Default 400     *    max: The maximum value of the gauge. Default 100     *    value: The starting value of the gauge. Default 0     *    label: The function on how to render the center label (Should return a value)     * }     * @param {Element} elem The DOM into which to render the gauge     * @param {Object} opts The gauge options     * @return a Gauge object     */    return function Gauge(elem, opts) {      opts = shallowCopy({}, defaultOptions, opts);      var gaugeContainer = elem,          limit = opts.max,          min = opts.min,          value = normalize(opts.value, min, limit),          radius = opts.dialRadius,          displayValue = opts.showValue,          startAngle = opts.dialStartAngle,          endAngle = opts.dialEndAngle,          valueDialClass = opts.valueDialClass,          valueTextClass = opts.valueClass,          valueLabelClass = opts.valueLabelClass,          dialClass = opts.dialClass,          gaugeClass = opts.gaugeClass,          gaugeColor = opts.color,          gaugeValueElem,          gaugeValuePath,          label = opts.label,          viewBox = opts.viewBox,          instance;      if(startAngle < endAngle) {        console.log("WARN! startAngle < endAngle, Swapping");        var tmp = startAngle;        startAngle = endAngle;        endAngle = tmp;      }      function pathString(radius, startAngle, endAngle, largeArc) {        var coords = getDialCoords(radius, startAngle, endAngle),            start = coords.start,            end = coords.end,            largeArcFlag = typeof(largeArc) === "undefined" ? 1 : largeArc;        return [          "M", start.x, start.y,           "A", radius, radius, 0, largeArcFlag, 1, end.x, end.y        ].join(" ");      }      function initializeGauge(elem) {        gaugeValueElem = svg("text", {          x: 50,          y: 50,          fill: "#999",          "class": valueTextClass,          "font-size": "100%",          "font-family": "sans-serif",          "font-weight": "normal",          "text-anchor": "middle",          "alignment-baseline": "middle",          "dominant-baseline": "central"        });        gaugeValuePath = svg("path", {          "class": valueDialClass,          fill: "none",          stroke: "#666",          "stroke-width": 2.5,          d: pathString(radius, startAngle, startAngle) // value of 0        });        var angle = getAngle(100, 360 - Math.abs(startAngle - endAngle));        var flag = angle <= 180 ? 0 : 1;        var gaugeElement = svg("svg", {"viewBox": viewBox || "0 0 100 100", "class": gaugeClass},          [            svg("path", {              "class": dialClass,              fill: "none",              stroke: "#eee",              "stroke-width": 2,              d: pathString(radius, startAngle, endAngle, flag)            }),            gaugeValueElem,            gaugeValuePath          ]        );        elem.appendChild(gaugeElement);      }      function updateGauge(theValue, frame) {        var val = getValueInPercentage(theValue, min, limit),            // angle = getAngle(val, 360 - Math.abs(endAngle - startAngle)),            angle = getAngle(val, 360 - Math.abs(startAngle - endAngle)),            // this is because we are using arc greater than 180deg            flag = angle <= 180 ? 0 : 1;        if(displayValue) {          gaugeValueElem.textContent = label.call(opts, theValue);        }        gaugeValuePath.setAttribute("d", pathString(radius, startAngle, angle + startAngle, flag));      }      function setGaugeColor(value, duration) {                var c = gaugeColor(value),             dur = duration * 1000,            pathTransition = "stroke " + dur + "ms ease";            // textTransition = "fill " + dur + "ms ease";        gaugeValuePath.style = [          "stroke: " + c,          "-webkit-transition: " + pathTransition,          "-moz-transition: " + pathTransition,          "transition: " + pathTransition,        ].join(";");        /*        gaugeValueElem.style = [          "fill: " + c,          "-webkit-transition: " + textTransition,          "-moz-transition: " + textTransition,          "transition: " + textTransition,        ].join(";");        */      }      instance = {        setMaxValue: function(max) {          limit = max;        },        setValue: function(val) {          value = normalize(val, min, limit);          if(gaugeColor) {            setGaugeColor(value, 0)          }          updateGauge(value);        },        setValueAnimated: function(val, duration) {        	var oldVal = value;          value = normalize(val, min, limit);          if(oldVal === value) {            return;          }          if(gaugeColor) {            setGaugeColor(value, duration);          }          Animation({            start: oldVal || 0,            end: value,            duration: duration || 1,            step: function(val, frame) {              updateGauge(val, frame);            }          });        },        getValue: function() {          return value;        }      };      initializeGauge(gaugeContainer);      instance.setValue(value);      return instance;    };  })();  return Gauge;});
 |