|
@@ -14,6 +14,7 @@
|
|
})(typeof window === "undefined" ? this : window, function(global, undefined) {
|
|
})(typeof window === "undefined" ? this : window, function(global, undefined) {
|
|
|
|
|
|
var document = global.document,
|
|
var document = global.document,
|
|
|
|
+ slice = Array.prototype.slice,
|
|
requestAnimationFrame = (global.requestAnimationFrame ||
|
|
requestAnimationFrame = (global.requestAnimationFrame ||
|
|
global.mozRequestAnimationFrame ||
|
|
global.mozRequestAnimationFrame ||
|
|
global.webkitRequestAnimationFrame ||
|
|
global.webkitRequestAnimationFrame ||
|
|
@@ -49,10 +50,12 @@
|
|
};
|
|
};
|
|
|
|
|
|
function animate() {
|
|
function animate() {
|
|
- var progress = (currentIteration++) / iterations;
|
|
|
|
- var value = change * easing(progress) + start;
|
|
|
|
|
|
+ var progress = currentIteration / iterations,
|
|
|
|
+ value = change * easing(progress) + start;
|
|
// console.log(progress + ", " + value);
|
|
// console.log(progress + ", " + value);
|
|
- step(value);
|
|
|
|
|
|
+ step(value, currentIteration);
|
|
|
|
+ currentIteration += 1;
|
|
|
|
+
|
|
if(progress < 1) {
|
|
if(progress < 1) {
|
|
requestAnimationFrame(animate);
|
|
requestAnimationFrame(animate);
|
|
}
|
|
}
|
|
@@ -64,17 +67,41 @@
|
|
|
|
|
|
|
|
|
|
var Gauge = (function() {
|
|
var Gauge = (function() {
|
|
-
|
|
|
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
|
|
|
|
var GaugeDefaults = {
|
|
var GaugeDefaults = {
|
|
|
|
+ centerX: 50,
|
|
|
|
+ centerY: 50
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ var defaultOptions = {
|
|
|
|
+ dialRadius: 40,
|
|
dialStartAngle: 135,
|
|
dialStartAngle: 135,
|
|
dialEndAngle: 45,
|
|
dialEndAngle: 45,
|
|
- centerX: 500,
|
|
|
|
- centerY: 500,
|
|
|
|
- radius: 400
|
|
|
|
|
|
+ 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
|
|
* A utility function to create SVG dom tree
|
|
* @param {String} name The SVG element name
|
|
* @param {String} name The SVG element name
|
|
@@ -104,15 +131,18 @@
|
|
return percentage * gaugeSpanAngle / 100;
|
|
return percentage * gaugeSpanAngle / 100;
|
|
}
|
|
}
|
|
|
|
|
|
- function normalize(value, limit) {
|
|
|
|
|
|
+ function normalize(value, min, limit) {
|
|
var val = Number(value);
|
|
var val = Number(value);
|
|
if(val > limit) return limit;
|
|
if(val > limit) return limit;
|
|
- if(val < 0) return 0;
|
|
|
|
|
|
+ if(val < min) return min;
|
|
return val;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
|
|
- function getValueInPercentage(value, limit) {
|
|
|
|
- return 100 * value / limit;
|
|
|
|
|
|
+ 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);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -143,10 +173,6 @@
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
- function defaultLabelRenderer(theValue) {
|
|
|
|
- return Math.round(theValue);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/**
|
|
/**
|
|
* Creates a Gauge object. This should be called without the 'new' operator. Various options
|
|
* Creates a Gauge object. This should be called without the 'new' operator. Various options
|
|
* can be passed for the gauge:
|
|
* can be passed for the gauge:
|
|
@@ -163,25 +189,29 @@
|
|
* @return a Gauge object
|
|
* @return a Gauge object
|
|
*/
|
|
*/
|
|
return function Gauge(elem, opts) {
|
|
return function Gauge(elem, opts) {
|
|
- opts = opts || {};
|
|
|
|
|
|
+ opts = shallowCopy({}, defaultOptions, opts);
|
|
var gaugeContainer = elem,
|
|
var gaugeContainer = elem,
|
|
- limit = opts.max || 100,
|
|
|
|
- value = normalize(opts.value || 0, limit),
|
|
|
|
- radius = opts.radius || 400,
|
|
|
|
- displayValue = opts.showValue === false ? false : true,
|
|
|
|
- valueLabelRender = typeof (opts.label) === "function" ? opts.label : defaultLabelRenderer,
|
|
|
|
- startAngle = typeof (opts.dialStartAngle) === "undefined" ? 135 : opts.dialStartAngle,
|
|
|
|
- endAngle = typeof (opts.dialEndAngle) === "undefined" ? 45 : opts.dialEndAngle,
|
|
|
|
- valueDialClass = typeof (opts.valueDialClass) === "undefined" ? 'value' : opts.valueDialClass,
|
|
|
|
- valueTextClass = typeof (opts.valueTextClass) === "undefined" ? 'value-text' : opts.valueTextClass,
|
|
|
|
- dialClass = typeof (opts.dialClass) === "undefined" ? 'dial' : opts.dialClass,
|
|
|
|
- gaugeClass = typeof (opts.gaugeClass) === "undefined" ? 'gauge' : opts.gaugeClass,
|
|
|
|
- gaugeTextElem,
|
|
|
|
|
|
+ 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,
|
|
gaugeValuePath,
|
|
|
|
+ label = opts.label,
|
|
|
|
+ viewBox = opts.viewBox,
|
|
instance;
|
|
instance;
|
|
|
|
|
|
if(startAngle < endAngle) {
|
|
if(startAngle < endAngle) {
|
|
- console.log("WARNING! Start angle should be greater than end angle. Swapping");
|
|
|
|
|
|
+ console.log("WARN! startAngle < endAngle, Swapping");
|
|
var tmp = startAngle;
|
|
var tmp = startAngle;
|
|
startAngle = endAngle;
|
|
startAngle = endAngle;
|
|
endAngle = tmp;
|
|
endAngle = tmp;
|
|
@@ -193,75 +223,113 @@
|
|
end = coords.end,
|
|
end = coords.end,
|
|
largeArcFlag = typeof(largeArc) === "undefined" ? 1 : largeArc;
|
|
largeArcFlag = typeof(largeArc) === "undefined" ? 1 : largeArc;
|
|
|
|
|
|
- return ["M", start.x, start.y, "A", radius, radius, "0", largeArcFlag, "1", end.x, end.y].join(" ");
|
|
|
|
|
|
+ return [
|
|
|
|
+ "M", start.x, start.y,
|
|
|
|
+ "A", radius, radius, 0, largeArcFlag, 1, end.x, end.y
|
|
|
|
+ ].join(" ");
|
|
}
|
|
}
|
|
|
|
|
|
function initializeGauge(elem) {
|
|
function initializeGauge(elem) {
|
|
- gaugeTextElem = svg("text", {
|
|
|
|
|
|
+ gaugeValueElem = svg("text", {
|
|
|
|
+ x: 50,
|
|
|
|
+ y: 50,
|
|
|
|
+ fill: "#999",
|
|
"class": valueTextClass,
|
|
"class": valueTextClass,
|
|
- "x": 500,
|
|
|
|
- "y": 550,
|
|
|
|
- "font-size": "700%",
|
|
|
|
|
|
+ "font-size": "100%",
|
|
"font-family": "sans-serif",
|
|
"font-family": "sans-serif",
|
|
- "font-weight": "bold",
|
|
|
|
- "text-anchor": "middle"
|
|
|
|
|
|
+ "font-weight": "normal",
|
|
|
|
+ "text-anchor": "middle",
|
|
|
|
+ "alignment-baseline": "middle",
|
|
|
|
+ "dominant-baseline": "central"
|
|
});
|
|
});
|
|
|
|
+
|
|
gaugeValuePath = svg("path", {
|
|
gaugeValuePath = svg("path", {
|
|
"class": valueDialClass,
|
|
"class": valueDialClass,
|
|
- "fill": "transparent",
|
|
|
|
- "stroke": "#666",
|
|
|
|
- "stroke-width": 25,
|
|
|
|
- "d": pathString(radius, startAngle, startAngle) // value of 0
|
|
|
|
|
|
+ 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 angle = getAngle(100, 360 - Math.abs(startAngle - endAngle));
|
|
var flag = angle <= 180 ? 0 : 1;
|
|
var flag = angle <= 180 ? 0 : 1;
|
|
- var gaugeElement = svg("svg", {"viewBox": "0 0 1000 1000", "class": gaugeClass},
|
|
|
|
|
|
+ var gaugeElement = svg("svg", {"viewBox": viewBox || "0 0 100 100", "class": gaugeClass},
|
|
[
|
|
[
|
|
svg("path", {
|
|
svg("path", {
|
|
"class": dialClass,
|
|
"class": dialClass,
|
|
- "fill": "transparent",
|
|
|
|
- "stroke": "#eee",
|
|
|
|
- "stroke-width": 20,
|
|
|
|
- "d": pathString(radius, startAngle, endAngle, flag)
|
|
|
|
|
|
+ fill: "none",
|
|
|
|
+ stroke: "#eee",
|
|
|
|
+ "stroke-width": 2,
|
|
|
|
+ d: pathString(radius, startAngle, endAngle, flag)
|
|
}),
|
|
}),
|
|
- gaugeTextElem,
|
|
|
|
|
|
+ gaugeValueElem,
|
|
gaugeValuePath
|
|
gaugeValuePath
|
|
]
|
|
]
|
|
);
|
|
);
|
|
elem.appendChild(gaugeElement);
|
|
elem.appendChild(gaugeElement);
|
|
}
|
|
}
|
|
|
|
|
|
- function updateGauge(theValue) {
|
|
|
|
- var val = getValueInPercentage(theValue, limit),
|
|
|
|
|
|
+ function updateGauge(theValue, frame) {
|
|
|
|
+ var val = getValueInPercentage(theValue, min, limit),
|
|
// angle = getAngle(val, 360 - Math.abs(endAngle - startAngle)),
|
|
// angle = getAngle(val, 360 - Math.abs(endAngle - startAngle)),
|
|
angle = getAngle(val, 360 - Math.abs(startAngle - endAngle)),
|
|
angle = getAngle(val, 360 - Math.abs(startAngle - endAngle)),
|
|
// this is because we are using arc greater than 180deg
|
|
// this is because we are using arc greater than 180deg
|
|
flag = angle <= 180 ? 0 : 1;
|
|
flag = angle <= 180 ? 0 : 1;
|
|
- (displayValue && (gaugeTextElem.textContent = valueLabelRender.call(opts, theValue)));
|
|
|
|
|
|
+ if(displayValue) {
|
|
|
|
+ gaugeValueElem.textContent = label.call(opts, theValue);
|
|
|
|
+ }
|
|
gaugeValuePath.setAttribute("d", pathString(radius, startAngle, angle + startAngle, flag));
|
|
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 = {
|
|
instance = {
|
|
setMaxValue: function(max) {
|
|
setMaxValue: function(max) {
|
|
limit = max;
|
|
limit = max;
|
|
},
|
|
},
|
|
setValue: function(val) {
|
|
setValue: function(val) {
|
|
- value = normalize(val, limit);
|
|
|
|
|
|
+ value = normalize(val, min, limit);
|
|
|
|
+ if(gaugeColor) {
|
|
|
|
+ setGaugeColor(value, 0)
|
|
|
|
+ }
|
|
updateGauge(value);
|
|
updateGauge(value);
|
|
},
|
|
},
|
|
setValueAnimated: function(val, duration) {
|
|
setValueAnimated: function(val, duration) {
|
|
var oldVal = value;
|
|
var oldVal = value;
|
|
- value = normalize(val, limit);
|
|
|
|
|
|
+ value = normalize(val, min, limit);
|
|
if(oldVal === value) {
|
|
if(oldVal === value) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if(gaugeColor) {
|
|
|
|
+ setGaugeColor(value, duration);
|
|
|
|
+ }
|
|
Animation({
|
|
Animation({
|
|
start: oldVal || 0,
|
|
start: oldVal || 0,
|
|
end: value,
|
|
end: value,
|
|
duration: duration || 1,
|
|
duration: duration || 1,
|
|
- step: function(val) {
|
|
|
|
- updateGauge(Math.round(val * 100) / 100);
|
|
|
|
|
|
+ step: function(val, frame) {
|
|
|
|
+ updateGauge(val, frame);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
},
|
|
},
|
|
@@ -271,7 +339,7 @@
|
|
};
|
|
};
|
|
|
|
|
|
initializeGauge(gaugeContainer);
|
|
initializeGauge(gaugeContainer);
|
|
- updateGauge(value);
|
|
|
|
|
|
+ instance.setValue(value);
|
|
return instance;
|
|
return instance;
|
|
};
|
|
};
|
|
})();
|
|
})();
|