<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EDGE, IE10, IE=9; IE=8; IE=7" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>jQuery SVG Gauge Plugin</title>
<link rel="stylesheet" href="style.css" />
<script data-require="jquery@*" data-semver="2.0.3" src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<link data-require="jqueryui@*" data-semver="1.10.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/css/smoothness/jquery-ui-1.10.0.custom.min.css" />
<script data-require="jqueryui@*" data-semver="1.10.0" src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/jquery-ui.js"></script>
<script data-require="raphael@*" data-semver="2.1.0" src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="geometry.js"></script>
<script src="raphael-extensions.js"></script>
<script src="jquery-svg-gauge.js"></script>
</head>
<body>
<div class="demo">
<!--
<p>Note: There seems to be a transformation rotation precision issue in IE8. Applying this <a href="https://groups.google.com/forum/?fromgroups=#!msg/raphaeljs/sYY-CTMw6DM/USHIDoKSc0cJ" target="_blank">patch to Raphael</a> seems to address the issue. While there are no CDN</p>
-->
<h1>jQuery SVG Gauge Plugin</h1>
<p>You don't need to learn Raphael.js to use this.</p>
<p>This jQuery plugin uses Raphael.js internally to draw customizable SVG Gauges, and allows you to set/get values by simply using .val()</p>
<p>View README for details</p>
<p>Blogged at: <a href="http://terryyoung.blogspot.hk/2013/12/jquery-svg-gauge-plugin-10.html" target="_blank">http://terryyoung.blogspot.hk/2013/12/jquery-svg-gauge-plugin-10.html</a></p>
<p>Click on each gauge to view sample code.</p>
<div class="metal-border speedometer-half" id="example1A">
<div class="example"></div>
</div>
<div class="code" id="code_example1A"></div>
<script type="text/javascript" class="example" id="script_example1A">
// default functionality
$(document).ready(function() {
$('#example1A div.example').svgGauge({
'value': 75
});
});
</script>
<div class="metal-border speedometer-half" id="example1B">
<div class="example"></div>
<div class="gauge-value"></div>
<div class="gauge-percentage"></div>
</div>
<div class="code" id="code_example1B"></div>
<script type="text/javascript" class="example" id="script_example1B">
// positive startingIncline, symmetrical speedometer
$(document).ready(function() {
$('#example1B div.example').svgGauge({
'value': 75,
'minValue': 0,
'maxValue': 500,
'startingIncline': 30,
'minorTicks': {
'count': 1
},
'valuePrecision': 0,
'percentagePrecision': 0,
'hand': {
'centerPointRadius': 2
},
'onAnimate': function(data, gauge) {
var $this = $(this),
$parent = $this.parent(),
$value = $parent.find('div.gauge-value'),
$percentage = $parent.find('div.gauge-percentage');
$value.html(data.value + '/' + gauge.maxValue);
$percentage.html((data.percentage * 100).toFixed(0) + '%');
}
});
});
</script>
<div class="metal-border speedometer-half" id="example1C">
<div class="example"></div>
</div>
<div class="code" id="code_example1C"></div>
<script type="text/javascript" class="example" id="script_example1C">
// symmetrical speedometer, mimic full zone area fill
$(document).ready(function() {
$('#example1C div.example').svgGauge({
'value': 75,
'startingIncline': 0,
'minorTicks': {
'count': 1
},
'zones': {
'normal': {
'attr': {
'stroke-width': 70,
'opacity': 0.3
}
},
'warning': {
'attr': {
'stroke-width': 70,
'opacity': 0.3
}
},
'critical': {
'attr': {
'stroke-width': 70,
'opacity': 0.3
}
}
}
});
});
</script>
<div class="metal-border speedometer-half" id="example1D">
<div class="example"></div>
<div class="gauge-value"></div>
</div>
<div class="code" id="code_example1D"></div>
<script type="text/javascript" class="example" id="script_example1D">
// two color zones example, asymmetrical speedometer
$(document).ready(function() {
$('#example1D div.example').svgGauge({
'value': 75,
'startingIncline': 0,
'endingIncline': 150,
'margin': 10,
'majorTicks': {
'count': 10,
'length': 10,
'attr': {
'stroke-width': 2
}
},
'minorTicks': {
'count': 1,
'length': 4,
'attr': {
'stroke-width': 2
}
},
'zones': {
'normal': {
'show': false,
'fromPercentage': 0,
'toPercentage': 0.3
},
'warning': {
'fromPercentage': 0.3,
'toPercentage': 0.7,
'attr': {
'stroke': '#FFA100',
'stroke-width': 5
}
},
'critical': {
'fromPercentage': 0.7,
'toPercentage': 1,
'attr': {
'stroke-width': 10
}
}
},
'hand': {
'centerPointRadius': 6,
'length': 45
},
'onAnimate': function(data, gauge) {
var $this = $(this),
$parent = $this.parent(),
$value = $parent.find('div.gauge-value');
$value.html(data.value.toFixed(0));
}
});
});
</script>
<br class="clear" />
<br class="clear" />
<div class="metal-border speedometer-3quarters" id="example1E">
<div class="example"></div>
<div class="gauge-percentage"></div>
<div class="gauge-overlay normal"></div>
<div class="gauge-status normal"></div>
</div>
<div class="code" id="code_example1E"></div>
<script type="text/javascript" class="example" id="script_example1E">
// negative startingIncline symmetrical speedometer
$(document).ready(function() {
$('#example1E div.example').svgGauge({
'value': 75,
'startingIncline': -30,
'percentagePrecision': 0,
'onZoneChange': function(zone, data, gauge) {
var $this = $(this),
$parent = $this.parent(),
$status = $parent.find('div.gauge-status'),
$overlay = $parent.find('div.gauge-overlay');
$status.add($overlay).removeClass('normal warning critical')
.addClass(zone);
$status.html(zone);
},
'onAnimate': function(data, gauge) {
var $this = $(this),
$parent = $this.parent(),
$percentage = $parent.find('div.gauge-percentage');
$percentage.html('CPU: ' + data.percentageString);
}
});
});
</script>
<div class="metal-border speedometer-3quarters" id="example1F">
<div class="example"></div>
</div>
<div class="code" id="code_example1F"></div>
<script type="text/javascript" class="example" id="script_example1F">
// asymmetrical speedometer example
$(document).ready(function() {
$('#example1F div.example').svgGauge({
'value': 100,
'minValue': 0,
'maxValue': 270,
'startingIncline': -90,
'endingIncline': 180,
'margin': 15,
'majorTicks': {
'count': 6,
'length': 10,
'attr': {
'stroke': '#023',
'stroke-width': 3
}
},
'minorTicks': {
'count': 1,
'attr': {
'stroke': '#003'
}
},
'hand': {
'centerPointRadius': 2,
'length': 50,
'attr': {
'stroke': '#003'
}
},
'zones': {
'normal': {
'fromPercentage': 0,
'toPercentage': 3 / 6,
'attr': {
'stroke': '#018905',
'stroke-width': 4
}
},
'warning': {
'show': false,
'fromPercentage': 3 / 6,
'toPercentage': 5 / 6
},
'critical': {
'fromPercentage': 5 / 6,
'toPercentage': 1,
'attr': {
'stroke-width': 10
}
}
}
});
});
</script>
<div class="metal-border speedometer-3quarters" id="example1G">
<div class="example"></div>
<div class="gauge-value"></div>
</div>
<div class="code" id="code_example1G"></div>
<script type="text/javascript" class="example" id="script_example1G">
// thermometer example:
// blue zone represents negative values
// red zone represents values over 40
$(document).ready(function() {
$('#example1G div.example').svgGauge({
'value': 25.5,
'minValue': -20,
'maxValue': 60,
'startingIncline': -60,
'endingIncline': 180,
'margin': 8,
'majorTicks': {
'count': 8,
'length': 20,
'attr': {
'stroke': '#023',
'stroke-width': 3
}
},
'minorTicks': {
'count': 4,
'attr': {
'stroke': '#023'
}
},
'hand': {
'centerPointRadius': 2,
'length': 50,
'attr': {
'stroke': '#003'
}
},
'zones': {
'normal': {
'fromPercentage': 0,
'toPercentage': 2 / 8,
'attr': {
'stroke': '#10E5ED',
'stroke-width': 20
}
},
'warning': {
'fromPercentage': 2 / 8,
'toPercentage': 60 / 80,
'attr': {
'stroke': '#cdcdcd',
'stroke-width': 20
}
},
'critical': {
'fromPercentage': 60 / 80,
'toPercentage': 1,
'attr': {
'stroke-width': 20
}
}
},
'onAnimate': function(data, gauge) {
var $this = $(this),
$parent = $this.parent(),
$value = $parent.find('div.gauge-value');
$value.html(data.value.toFixed(1) + '°C');
}
});
});
</script>
<div class="metal-border speedometer-3quarters" id="example1H">
<div class="example"></div>
</div>
<div class="code" id="code_example1H"></div>
<script type="text/javascript" class="example" id="script_example1H">
// thermometer example:
// blue zone represents negative values
// red zone represents values over 40
$(document).ready(function() {
$('#example1H div.example').svgGauge({
'value': 25.5,
'minValue': -30,
'maxValue': 60,
'startingIncline': 0,
'endingIncline': 270,
'margin': 15,
'majorTicks': {
'count': 9,
'length': 20,
'attr': {
'stroke': '#023',
'stroke-width': 3
}
},
'minorTicks': {
'count': 1,
'attr': {
'stroke': '#023'
}
},
'hand': {
'centerPointRadius': 2,
'length': 50,
'attr': {
'stroke': '#003'
}
},
'zones': {
'normal': {
'fromPercentage': 0,
'toPercentage': 1 / 3,
'attr': {
'stroke-width': 6
}
},
'warning': {
'fromPercentage': 1 / 3,
'toPercentage': 2 / 3,
'attr': {
'stroke-width': 6
}
},
'critical': {
'fromPercentage': 2 / 3,
'toPercentage': 1,
'attr': {
'stroke-width': 20
}
}
}
});
});
</script>
<br class="clear" />
<div class="demo">
<p>Value setters and getters</p>
<div class="default-gauge" id="example2">
<div class="example"></div>
</div>
Type any value from 0 to 100
<br>
<input type="text" maxlength="3" size="3" id="example2_val" autocomplete="off" value="100">
<button id="example2_set">Set</button>
<button id="example2_get">Get</button>
<div class="code" id="code_example2"></div>
<script type="text/javascript" class="example" id="script_example2">
$(document).ready(function() {
$('#example2').svgGauge({
'value': 100,
'minValue': 0,
'maxValue': 100,
'startingIncline': -90,
'endingIncline': 180,
'arc': {
'attr': {
'stroke': '#005d01'
}
},
'minorTicks': {
'attr': {
'stroke': '#005d01'
}
},
'majorTicks': {
'attr': {
'stroke': '#005d01'
}
},
'hand': {
'attr': {
'fill': '#00CA02'
}
}
});
$('#example2_set').on('click', function() {
var newVal = $('#example2_val').val();
$('#example2').val(newVal);
});
$('#example2_get').on('click', function() {
alert($('#example2').val());
});
});
</script>
</div>
</div>
<br class="clear" />
<script type="text/javascript">
/**
* The following is *NOT* usage code of jQuery SVG Gauge,
* it converts actual sample scripts into tooltips.
*/
$(document).ready(function() {
var $demos = $('div.demo');
$('div.example').each(function(i, el) {
var $example = $(el),
id = $example.parent().attr('id'),
scriptID = 'script_' + id,
codeID = 'code_' + id,
$script = $('#' + scriptID),
$code = $('#' + codeID);
$code
.html('<pre>' + $script.text() + '</pre>')
.hide()
.on('click', 'div.close', function(event) {
$(this).closest('div.code').hide();
});
$example.parent().on('click', function(event) {
var $this = $(this),
$code = $('#code_' + $this.attr('id'));
$('div.code').hide();
$('div.demo-close').remove();
$code.show().position({
my: 'left top',
at: 'center center',
of: $this
});
$('<div class="demo-close"></div>').prependTo('body').position({
my: 'right top',
at: 'right+10 top-10',
of: $code
});
});
});
$('body').on('click', 'div.demo-close', function(event) {
$('div.demo-close').remove();
$('div.code').hide();
});
function demo() {
$demos.first().find('div.example').svgGauge('randomize');
}
window.demoTimer = setInterval(demo, 2000);
});
</script>
</body>
</html>
/**
* Common styles -- not required
*/
html, body
{
font-family:Consolas;
font-size:10pt;
background-color:#222;
color:#bec8cb;
border:0;
margin:0;
padding:0;
}
a, a:visited {
color:cyan;
}
br.clear
{
clear: both;
}
div.code
{
background-color:#293730;
color:white;
padding:3px;
border:2px solid #50534f;
font-size:10pt;
min-height: 150px;
position:absolute;
overflow:auto;
width:480px;
height:320px;
z-index:9999;
border-radius:10px;
display:none;
opacity:0.95;
}
div.demo-close {
position:absolute;
right:5px;
top:5px;
background-image:url('data:text/javascript;base64,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');
background-repeat: no-repeat;
background-position:top right;
cursor:pointer;
display:block;
width:36px;
height:36px;
z-index:10000;
color:#005d01;
}
div.demo
{
border-top:1px inset #454545;
padding: 8px;
/*width:100%;*/
min-height: 220px;
}
/************************ EXAMPLE 1 ************************/
/* default speedometer face style */
div.default-gauge {
width:150px;
height:160px;
border:1px solid #232;
border-radius:10px;
overflow:hidden;
background-color:#121;
cursor:pointer;
}
div.metal-border {
float:left;
margin-right:10px;
width:153px;
height:164px;
padding:5px;
cursor:pointer;
position:relative;
background: -moz-linear-gradient(
top,
#ffffff 0%,
#ebebeb 50%,
#dbdbdb 50%,
#b5b5b5);
background: -webkit-gradient(
linear, left top, left bottom,
from(#ffffff),
color-stop(0.50, #ebebeb),
color-stop(0.50, #dbdbdb),
to(#b5b5b5));
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 15px;
border: 1px solid #949494;
-moz-box-shadow:
0px 1px 3px rgba(000,000,000,0.5),
inset 0px 0px 2px rgba(255,255,255,1);
-webkit-box-shadow:
0px 1px 3px rgba(000,000,000,0.5),
inset 0px 0px 2px rgba(255,255,255,1);
box-shadow:
0px 1px 3px rgba(000,000,000,0.5),
inset 0px 0px 2px rgba(255,255,255,1);
text-shadow:
0px -1px 0px rgba(000,000,000,0.2),
0px 1px 0px rgba(255,255,255,1);
}
/* fan-shaped speedometer */
div.speedometer-half,
div.metal-border.speedometer-half div.example {
height:90px;
}
div.metal-border.speedometer-half
{
height:95px;
}
div.speedometer-3quarters,
div.metal-border.speedometer-3quarters div.example {
height:150px;
}
div.metal-border.speedometer-3quarters
{
height:155px;
}
/* white speedometer */
div.metal-border div.example {
border:1px solid #232;
border-radius:10px;
background-color: #ffffff;
background: -moz-linear-gradient(
top,
#878787 0%,
#dedede 50%,
#dedede 50%,
#adaaad);
background: -webkit-gradient(
linear, left top, left bottom,
from(#878787),
color-stop(0.50, #dedede),
color-stop(0.50, #dedede),
to(#adaaad));
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
border: 1px solid #949494;
-moz-box-shadow:
0px 1px 0px rgba(000,000,000,0.5),
inset 1px 1px 3px rgba(13,13,13,1);
-webkit-box-shadow:
0px 1px 0px rgba(000,000,000,0.5),
inset 1px 1px 3px rgba(13,13,13,1);
box-shadow:
0px 1px 0px rgba(000,000,000,0.5),
inset 1px 1px 3px rgba(13,13,13,1);
text-shadow:
0px -1px 0px rgba(000,000,000,0.2),
0px 1px 0px rgba(255,255,255,1);
}
div.gauge-value,
div.gauge-percentage,
div.gauge-status {
position:absolute;
}
#example1B div.gauge-value {
top:72px;
right:15px;
width:75px;
text-align:right;
font-size:10pt;
color:#000;
}
#example1B div.gauge-percentage {
top:35px;
width:150px;
font-size:32pt;
font-weight:bold;
color:#000;
text-align:center;
opacity:0.2;
}
#example1D div.gauge-value {
top:60px;
right:15px;
width:75px;
font-size:22pt;
color:#000;
text-align:right;
}
#example1E div.gauge-status {
top:120px;
left:38px;
width:80px;
padding:5px 3px 2px 3px;
font-size:9pt;
text-align:center;
vertical-align:bottom;
border-radius:8px;
-moz-box-shadow:
0px 1px 0px rgba(000,000,000,0.5),
inset 1px 1px 3px rgba(13,13,13,1);
-webkit-box-shadow:
0px 1px 0px rgba(000,000,000,0.5),
inset 1px 1px 3px rgba(13,13,13,1);
box-shadow:
0px 1px 0px rgba(000,000,000,0.5),
inset 1px 1px 3px rgba(13,13,13,1);
}
#example1E div.gauge-status.normal,
#example1E div.gauge-overlay.normal {
background-color:#009b0b;
color:#aef9b6;
text-shadow:none;
}
#example1E div.gauge-status.warning,
#example1E div.gauge-overlay.warning {
background-color:#efeb00;
color:#81610b;
text-shadow:none;
}
#example1E div.gauge-status.critical,
#example1E div.gauge-overlay.critical {
background-color:#c60217;
color:#f9c9d5;
text-shadow:none;
}
#example1E div.gauge-overlay {
opacity:0.25;
border-radius:70px;
width:122px;
height:122px;
position:absolute;
top:20px;
left:20px;
}
#example1E div.gauge-percentage {
top:90px;
width:150px;
font-size:12pt;
text-align:center;
color:#565656;
opacity:0.5;
}
#example1G div.gauge-value {
top:110px;
right:20px;
width:120px;
font-size:16pt;
color:#000;
text-align:right;
}
div.demo div.example {
float:left;
margin-right:10px;
}
div.demo div.code {
font-family: Consolas;
font-size:9pt;
margin-right:10px;
padding-left:5px;
background-color:#292929;
}
# jQuery SVG Gauge Plugin
You don't need to learn Raphael.js to use this.
This jQuery plugin uses Raphael.js internally to draw customizable SVG Gauges, and allows you to set/get values by simply using `.val()`
## License
[WTFPL](http://en.wikipedia.org/wiki/WTFPL)
## Dependencies
* jQuery 1.9 or above
* Raphael.js 2.1.1 or above - as the 'Transformation Rotation Precision issue was resolved', see [https://github.com/DmitryBaranovskiy/raphael/issues/653]
* You will also need geometry.js and raphael-extensions.js I included in this Plunker
## Some notable features
* Let's you create an SVG Gauge in jQuery fashion, and does the RaphaelJS work for you
* Get and Set gauge values using `.val()`
* `onAnimate` and `onZoneChange` event handlers
* Configurable start and end angles of the gauge
* Configurable zones of the gauge
* Cosmetic aspects of the gauge are exposed as options, as much as possible. The thickness, length, color of major and minor tick marks, gauge hand, gauge arc, zones, etc
/**
*
* These are a collection of Geometry functions under the namespace "geom"
*
* Well, it is good enough for what I currently need, I'm not aiming to build an exhaustive library.
*
* @author Terry Young <terryyounghk [at] gmail.com>
* @access public
*
*/
var geom = {
/**
* Distance Formula
*/
getDistance: function(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
},
/**
* Mid-Point Formula
*/
getMidPoint: function(a, b) {
return {
x: (a.x + b.x) / 2,
y: (a.y + b.y) / 2
};
},
/**
* Returns an object containing all bounding-box related information
* relative to an SVG coordinate system, i.e. top-left is 0,0
* @param w
* @param h
* @param cx (Optional)
* @param cy (Optional)
* @param undefined (Optional) (Do not pass in this parameter)
* @return {Object}
*/
getBoundingBox: function(w, h, cx, cy, undefined) {
if (cx === undefined && cy === undefined) {
cx = w / 2;
cy = h / 2;
}
return {
width: w,
height: h,
center: {
x: cx,
y: cy
},
top: {
x: cx,
y: cy - h / 2
},
bottom: {
x: cx,
y: cy + h / 2
},
left: {
x: cx - w / 2,
y: cy
},
right: {
x: cx + w / 2,
y: cy
},
topLeft: {
x: cx - w / 2,
y: cy - h / 2
},
topRight: {
x: cx + w / 2,
y: cy - h / 2
},
bottomLeft: {
x: cx - w / 2,
y: cy + h / 2
},
bottomRight: {
x: cx + w / 2,
y: cy + h / 2
}
};
},
/**
* Check if point C is the mid-point of A and B
*/
isMidPoint: function(a, b, c) {
return geom.coordinatesAreEqual(c, geom.getMidPoint(a, b));
},
/**
* Gradient Formula (i.e. Slope)
*/
getSlope: function(a, b) {
return (b.y - a.y) / (b.x - a.x);
},
/**
* Get the angle of the slope produced by coordinates A and B.
* It convert the Inverse-Tangent of the slope to Degrees and returns it.
*/
getAngle: function(a, b) {
return geom.toDegrees(Math.atan(geom.getSlope(a, b)));
},
/**
* Accepts any number of coordinate objects as arguments and return true they are all collinear.
* i.e. all points lie on the same straight line.
*/
isCollinear: function() {
var a = arguments;
if (a.length <= 1) {
// hmm... what to do here, should we throw an error?
} else {
var s1 = geom.getSlope(a[0], a[1]); // get the first slope
for (var i = 1, j = a.length - 1; i < j; i++) {
var s2 = geom.getSlope(a[i], a[i + 1]);
if (s1 != s2) {
return false;
}
return true;
}
}
},
/**
*
* Gets the number of distinct diagonals of a polygon
*
* @author Terry Young <terryyounghk [at] gmail.com>
* @access public
*
* @param v
* number of vertices of the polygon
*
*/
getDiagonals: function(v) {
return (v * (v - 3)) / 2;
},
/**
* This function returns the x,y coordinates where two lines L1 and L2 intersect, given that
* L1 is defined by two points A(x1,y1) and B(x2,y2), and L2 is defined by two points C(x3,y3) and D(x4,y4).
*
* "g" is an optional parameter.
*
* When g is false, then our aim is to return a line-segment intersection,
* (i.e. L1 and L2 are line segments, and may or may not intersect even when they are parallel)
* where as when true, we find the line-line intersection.
* (i.e. L1 and L2 are infinitely long lines, that the only case they do not collide is when they are parellel,
* and that if there's an intersection, it does not necessarily situate at the point within any of the two line segments)
*
* @credits This is a modified version inspired by
* http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/1968345#1968345
*/
getIntersection: function(a, b, c, d, g) {
var s1_x = b.x - a.x,
s1_y = b.y - a.y,
s2_x = d.x - c.x,
s2_y = d.y - c.y;
var s = (-s1_y * (a.x - c.x) + s1_x * (a.y - c.y)) / (-s2_x * s1_y + s1_x * s2_y),
t = (s2_x * (a.y - c.y) - s2_y * (a.x - c.x)) / (-s2_x * s1_y + s1_x * s2_y);
g = !! g; // convert to boolean
// if these are NaN, we've found a collinear case
// if any of these are +Inifinity or -Infinity,
var x = a.x + (t * s1_x),
y = a.y + (t * s1_y);
if ((g || (s >= 0 && s <= 1 && t >= 0 && t <= 1))) { // && isFinite(x) && isFinite(y)) {
// Collision detected
return {
x: x,
y: y
};
}
return false; // No collision
},
/**
* Helper function: returns an {x: ..., y: ...} object by accepting two values
*/
point: function(x, y) {
return {
'x': x,
'y': y
};
},
/**
* Helper function: takes in a coordinate and returns a formatted string. e.g. 5,-11
* "cts" means Coordinates To String
*/
cts: function(c) {
return [c.x, c.y].join(',');
},
/**
* Helper function: takes in two {x: ... y: ...} objects and see if they are equal
*/
coordinatesAreEqual: function(a, b) {
return (a.x === b.x && a.y === b.y);
},
/**
* Helper function: expects a Radian and converts to Degress
*/
toDegrees: function(rad) {
return rad * 180 / Math.PI;
},
/**
* Helper function: expects a Radian and converts to Degress
*/
toRadians: function(deg) {
return deg * Math.PI / 180;
}
};
/**
*
* This is an extension to RaphaelJS for drawing Arcs
*
* @author Terry Young <terryyounghk [at] gmail.com>
* @access public
*
*/
window.RaphaelExtensions = {
ca: {
/**
* @usage make an arc at 50,50 with a radius of 30 that grows from 0 to 40 of 100 with a bounce
* var my_arc = archtype.path().attr({
* "stroke": "#f00",
* "stroke-width": 14,
* arc: [50, 50, 0, 100, 50]
* });
*
* my_arc.animate({
* arc: [50, 50, amount, 100, 50]
* }, 1500, "bounce");
*
* @param cx
* @param cy
* @param value
* @param total
* @param radius
* @param startingIncline
* @param endingIncline
* @return {Object}
*/
gaugeArc: function (cx, cy, value, total, radius, startingIncline, endingIncline) {
startingIncline = (startingIncline === undefined)
? 0
: Math.max(-89, Math.min(89, startingIncline));
endingIncline = (startingIncline === undefined)
? 180 - startingIncline
: Math.max(91, Math.min(269, endingIncline));
var alpha = Math.abs(endingIncline - startingIncline) / total * value,
a = (180 - alpha) * Math.PI / 180,
x = cx + radius * Math.cos(a),
y = cy - radius * Math.sin(a),
path;
path = [
["M", cx - radius, cy],
["A", radius, radius,
0,
+(alpha > 180),
1,
x,
y]
];
return {
path: path
};
},
gaugeArcZone: function (cx, cy, fromPercentage, toPercentage, radius, startingIncline, endingIncline) {
var arc = this.paper.path().attr({
gaugeArc: [cx, cy, 100, 100, radius, startingIncline, endingIncline]
}),
length = arc.getTotalLength(),
path = arc.getSubpath(length * fromPercentage, length * toPercentage);
arc.remove();
return {
path: path
}
}
}
};
/**
* jQuery SVG Gauge Plugin 1.0.0
*
* This jQuery plugin requires Raphael 2.x
*
* License: WTFPL
* Copyright (C) 2013 Terry Young <terryyounghk at gmail dot com>
*/
(function($, Raphael, RaphaelExtensions, undefined) {
/**
* The SVGGauge instance will be stored in this.data(dataKey) for internal reference
* @type {String}
*/
var dataKey = 'jquery-svg-gauge';
/**
* Available methods of .svgGauge()
* @type {Object}
*/
var methods = {
/**
* Initializes the svgGauge
* @param options
* @return {*} chaining
*/
init: function(options) {
return this.each(function(i, el) {
var $el = $(el);
$el.data(dataKey, new SVGGauge($el, options));
});
},
/**
* Returns the svgGauge instance
* @return {*}
*/
widget: function() {
return this.data(dataKey);
},
/**
* Returns the Raphael paper object, in case you ever need to access it directly
*/
paper: function() {
return (this.data(dataKey)) ? this.data(dataKey).paper : undefined;
},
/**
* Sets/Gets the value of the svgGauge
*
* Obsolete. As we are going to duck-punch $.fn.val below, you can then just use $('#gauge').val() to set/get the value.
*
* @param value
* @return {*}
*/
value: function(value) {
var instance = this.data(dataKey);
return (value === undefined) ? instance.val() : instance.val(value);
},
/**
* Returns the minValue of the svgGauge
* @return {int}
*/
minValue: function() {
return (this.data(dataKey)) ? this.data(dataKey).minValue : undefined;
},
/**
* Returns the maxValue of the svgGauge
* @return {int}
*/
maxValue: function() {
return (this.data(dataKey)) ? this.data(dataKey).maxValue : undefined;
},
/**
*
* @return {*}
*/
shutdown: function() {
return this.each(function(i, el) {
$(el).data(dataKey).shutdown();
});
},
/**
* For demo. Sets a random value to the svgGauge
* @return {*}
*/
randomize: function() {
return this.each(function(i, el) {
$(el).data(dataKey).randomize();
});
}
};
$.fn.svgGauge = function(method) {
// Method calling logic
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.svgGauge');
return undefined;
}
};
// -----------------------------------------------------------------------------------------------------------------
// DUCK PUNCH !!!!!!!!!!!!!!!!!!!!
//
// Usages:
//
// $('#gauge').val(100); // Sets the value of the svgGauge to 100
// alert($('#gauge').val()); // alerts '100'
//
var _oldval = $.fn.val;
$.fn.val = function(value) {
if (value === undefined) {
// return the first svgGauge's value
var instance = this.first().data(dataKey);
if (instance instanceof SVGGauge) {
return instance.val();
} else {
// else, let the original .val() kick in
return _oldval.apply(this, arguments);
}
} else {
// apply the value to all elements in the set
return this.each(function(i, el) {
var $el = $(el),
instance = $el.data(dataKey);
if (instance instanceof SVGGauge) {
instance.val(value);
} else {
_oldval.apply($el, [value]);
}
});
}
};
//
// QUACK !!!!!!!!!!!!!!!!!!!!
// -----------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------
// Beyond this line is the internal SVGGauge constructor and prototype
// The default properties are within the SVGGauge's init() method
//
var NORMAL = 1,
WARNING = 2,
CRITICAL = 3,
ZONES = [];
// map integer values to text strings
ZONES[NORMAL] = 'normal';
ZONES[WARNING] = 'warning';
ZONES[CRITICAL] = 'critical';
function SVGGauge(jqObject, options) {
this.init();
$.extend(true, this, this.defaults); // First extend the new instance with the defaults
$.extend(true, this, options || {}); // Then extend the new instance with options, if specified, which overrides the defaults
this.originalOptions = options; // reserved
this.container = jqObject.get(0); // required by Raphael: http://raphaeljs.com/reference.html#Raphael
this._validateOptions(); // some values need to be ensured they are within acceptable ranges
this.create();
this.draw();
// prevent from overriding
if (options && options['value'] !== undefined) {
this.value = this.minValue;
this.val(options.value);
}
this.testing = function() {
console.warn(this.value);
}
}
SVGGauge.prototype = {
init: function() {
var msg;
if (!geom) {
msg = 'Geom JavaScript Library is required for the jQuery svgGauge plugin.';
} else if (!Raphael) {
msg = 'Raphael JavaScript Library is required for the jQuery svgGauge plugin.';
} else if (!Raphael.type) {
msg = 'Your browser does not support vector graphics.';
} else {
this.defaults = {
'value': 0,
'minValue': 0,
'maxValue': 100,
'valuePrecision': 2 // the maximum number of decimals for returned values
,
'percentagePrecision': 0 // the maximum number of decimals for returned percentages
,
'width': 150 // The width of the Raphael paper
,
'height': 150 // The height of the Raphael paper
,
'margin': 7
// 90
// |
// 0 _____|_____
// |
// -90
,
'startingIncline': 0,
'endingIncline': undefined,
'majorTicks': {
'show': true,
'count': 5,
'length': 15,
'attr': {
'stroke': '#000000', // translates to SVG stroke
'opacity': 1,
'stroke-width': 2 // translates to SVG stroke-width
}
},
'minorTicks': {
'show': true,
'count': 5,
'length': 7,
'attr': {
'stroke': '#000000', // translates to SVG stroke
'opacity': 1,
'stroke-width': 1 // translates to SVG stroke-width
}
},
'arc': {
'show': true,
'attr': {
'stroke': '#000000', // translates to SVG stroke
'opacity': 1,
'stroke-width': 2 // translates to SVG stroke-width
}
},
'zones': {
'normal': {
'show': true,
'fromPercentage': 0,
'toPercentage': 0.6,
'attr': {
'stroke': '#27ff1d', // translates to SVG stroke
'opacity': 1,
'stroke-width': 7 // translates to SVG stroke-width
}
},
'warning': {
'show': true,
'fromPercentage': 0.6,
'toPercentage': 0.8,
'attr': {
'stroke': '#EDE910', // translates to SVG stroke
'opacity': 1,
'stroke-width': 7 // translates to SVG stroke-width
}
},
'critical': {
'show': true,
'fromPercentage': 0.8,
'toPercentage': 1,
'attr': {
'stroke': '#EF0E0E', // translates to SVG stroke
'opacity': 1,
'stroke-width': 7 // translates to SVG stroke-width
}
}
},
'hand': {
'centerPointRadius': 4,
'length': 65,
'attr': {
'fill': '#333'
}
},
// animation
'duration': 700, // total duration of the entire animated sequence
'easing': 'easeOut', // Easing effect when gauge hand animates
/**
* This functions fires on each frame of the gauge meter animation
* The scope of this function is the HTML container of the SVGGauge widget instance
*
* @param data All real-time information based on the current angle of the gauge hand.
* 'data' has the following properties:
* data.value
* data.percentage e.g. 0.834 (where gauge.percentagePrecision is 1)
* data.percentageString e.g. 83.4% (where gauge.percentagePrecision is 1)
* data.angle The angle relative to gauge.startingIncline
* data.totalAngle gauge.endingIncline - gauge.startingIncline
* data.zone The zone as Integer (1=normal, 2=warning, 3=critical)
* @param gauge The SVGGauge instance
*/
'onAnimate': function(data, gauge) {},
/**
* This function fires at the moment the gauge meter crosses into another zone
* The scope of this function is the HTML container of the SVGGauge widget instance
*
* @param zone The zone as string. Possible values: 'normal', warning', 'critical'.
* @param data All real-time information based on the current angle of the gauge hand.
* 'data' has the following properties:
* data.value
* data.percentage e.g. 0.834 (where gauge.percentagePrecision is 1)
* data.percentageString e.g. 83.4% (where gauge.percentagePrecision is 1)
* data.angle The angle relative to gauge.startingIncline
* data.totalAngle gauge.endingIncline - gauge.startingIncline
* data.zone The zone as Integer (1=normal, 2=warning, 3=critical)
* data.previousZone The zone as Integer (1=normal, 2=warning, 3=critical)
* @param gauge The SVGGauge instance
*/
'onZoneChange': function(zone, data, gauge) {}
};
this.sets = {}; // reserved object for holding different types of Raphael Sets
}
if (msg !== undefined) {
return (window.console && console.warn(msg)) || alert(msg);
}
return true;
},
/**
* This function creates the Raphael paper
*/
create: function() {
this.paper = Raphael(this.container, this.width, this.height);
$.extend(true, this.paper, RaphaelExtensions);
try {
delete window.RaphaelExtensions;
} catch (e) {
window['RaphaelExtensions'] = undefined;
}
this.paper.HTMLcontainer = this.container; // add a reference of the SVG document's HTML container to the paper
},
/**
* This is a triage function which draws the gauge components
*/
draw: function() {
//this.paper.circle(b.center.x, b.center.y, this.radius);
this._drawGaugeZones();
this._drawGaugeTicks();
this._drawHand();
},
_drawHand: function() {
var b = this.bbox,
dotRadius = this.hand.centerPointRadius,
rx = dotRadius, // for clarity
ry = dotRadius, // for clarity
handAttr = $.extend({}, this.hand.attr, {
'stroke-width': 0 // until future versions of this plugin draws the hand in a less sloppy way, we won't have stroke-widths
}),
dot = this.paper.ellipse(b.center.x, b.center.y, rx, ry)
.attr(handAttr),
handLength = this.hand.length,
hand = this.paper.path([
['M', [b.center.x, b.center.y - ry].join(',')],
['L', [b.center.x - handLength, b.center.y].join(',')],
['L', [b.center.x, b.center.y + ry].join(',')]
].join(''))
.attr(handAttr),
handSet = this.paper.set();
handSet.push(dot, hand);
handSet.rotate(this.startingIncline, b.center.x, b.center.y);
this.sets.hand = handSet;
},
_drawGaugeZones: function() {
var b = this.bbox,
instance = this,
zoneSet = this.paper.set();
$.each(this.zones, function(key, zone) {
var zonePath = instance.paper.path()
.attr($.extend(true, {}, zone.attr, {
'gaugeArcZone': [
b.center.x,
b.center.y,
zone.fromPercentage,
zone.toPercentage,
instance.radius - zone.attr['stroke-width'] / 2,
instance.startingIncline,
instance.endingIncline
]
}))
.rotate(instance.startingIncline, b.center.x, b.center.x);
if (!zone.show) {
zonePath.hide();
}
zoneSet.push(zonePath);
});
var arcPath = this.paper.path()
.attr($.extend(true, {}, this.arc.attr, {
'gaugeArcZone': [
b.center.x,
b.center.y,
0,
1,
this.radius,
this.startingIncline,
this.endingIncline
]
}))
.rotate(instance.startingIncline, b.center.x, b.center.x);
if (!this.arc.show) {
arcPath.hide();
}
this.sets.arc = arcPath;
$.extend(this.sets, {
'zones': zoneSet
});
},
_drawGaugeTicks: function() {
// draw gauge ticks
var majorTickGuideSet = this.paper.set(),
majorTickSet = this.paper.set(),
minorTickGuideSet = this.paper.set(),
minorTickSet = this.paper.set(),
iMajorTicks = this.majorTicks.count,
iMinorTicks = this.minorTicks.count,
majorTickAngleIncrement = this.totalAngle / iMajorTicks,
minorTickAngleIncrement = majorTickAngleIncrement / (iMinorTicks + 1),
b = this.bbox,
iTotalLength;
for (var i = 0,
k = 1,
j = iMajorTicks,
a1 = this.startingIncline; i <= j; a1 = this.startingIncline + k * majorTickAngleIncrement, i++, k++) {
var majorTickGuide = this.paper.path([
'M', [b.center.x, b.center.y].join(','),
'L', [b.left.x, b.left.y].join(',')
].join(''))
.attr({
'fill-opacity': 0,
'stroke-opacity': 0,
'stroke-width': 0
})
.rotate(a1, b.center.x, b.center.y);
iTotalLength = majorTickGuide.getTotalLength();
var majorTick = this.paper.path(
majorTickGuide.getSubpath(iTotalLength - this.majorTicks.length, iTotalLength)
)
.rotate(a1, b.center.x, b.center.y)
.attr(this.majorTicks.attr);
if (i < j) {
for (var a2 = a1 + minorTickAngleIncrement,
l = 0,
m = 1,
n = iMinorTicks; l <= n; a2 = a1 + m * minorTickAngleIncrement, l++, m++) {
var minorTickGuide = this.paper.path([
'M', [b.center.x, b.center.y].join(','),
'L', [b.left.x, b.left.y].join(',')
].join(''))
.attr({
'fill-opacity': 0,
'stroke-opacity': 0,
'stroke-width': 0
})
.rotate(a1, b.center.x, b.center.y);
iTotalLength = minorTickGuide.getTotalLength();
var minorTick = this.paper.path(
minorTickGuide.getSubpath(iTotalLength - this.minorTicks.length, iTotalLength)
)
.rotate(a2, b.center.x, b.center.y)
.attr(this.minorTicks.attr);
minorTickGuideSet.push(minorTickGuide);
minorTickSet.push(minorTick);
}
}
majorTickGuideSet.push(majorTickGuide);
majorTickSet.push(majorTick);
}
if (!this.majorTicks.show) {
majorTickSet.hide();
}
if (!this.minorTicks.show) {
minorTickSet.hide();
}
$.extend(this.sets, {
'majorTicks': {
'guides': majorTickGuideSet,
'ticks': majorTickSet
},
'minorTicks': {
'guides': minorTickGuideSet,
'ticks': minorTickSet
}
});
},
_validateOptions: function() {
var w = this.width - this.margin * 2,
h = this.height - this.margin * 2,
cx = this.width / 2,
cy = this.height / 2,
b = geom.getBoundingBox(w, h, cx, cy);
this.bbox = b; // bounding box information
this.radius = this.width / 2 - this.margin; // this.radius is a derived property
// ensure startingIncline is within 2nd and 3rd quadrant
if (!$.isNumeric(this.startingIncline)) {
this.startingIncline = 0;
} else {
this.startingIncline = Math.max(-89, Math.min(89, this.startingIncline));
}
// ensure endingIncline is within 1st and 4th quadrant
if (!$.isNumeric(this.endingIncline)) {
this.endingIncline = 180 + -this.startingIncline; // symmetrical by default
} else {
this.endingIncline = Math.max(91, Math.min(269, this.endingIncline));
}
this.totalAngle = this.endingIncline - this.startingIncline;
},
_getBox: function() {
var w = this.width,
h = this.height;
return {
}
},
/**
* This function sets or gets the value of the gauge
* @param value
* @return {*} Returns the value of gauge is no arguments provided. Returns true upon successful assignment, false otherwise.
*/
val: function(value) {
// getter
if (value === undefined) {
return this.value;
}
// skip setter if there no change in value
if (value == this.value) {
return true;
}
// must be evaluated as numeric
if (!$.isNumeric(value)) {
return false;
}
// F.T.I.
value = value * 1; // ensure it is numeric
// setter
// make sure 'value' is within the allowed range
value = Math.max(Math.min(value.toFixed(this.valuePrecision), this.maxValue), this.minValue);
//console.warn(this.minValue);
var b = this.bbox,
finalPercentage = (value - this.minValue) / Math.abs(this.maxValue - this.minValue),
totalAngle = Math.abs(this.endingIncline - this.startingIncline),
finalAngle = totalAngle * finalPercentage + this.startingIncline,
instance = this,
handSet = this.sets.hand,
previousZone = this.zone;
function onAnimate(obj) {
var data = instance._getRealtimeValues();
if ($.isFunction(instance.onAnimate)) {
instance.onAnimate.apply(instance.container, [data, instance]);
}
if (previousZone != data.zone) {
if ($.isFunction(instance.onZoneChange)) {
data.previousZone = previousZone;
instance.onZoneChange.apply(instance.container, [ZONES[data.zone], data, instance]);
}
previousZone = data.zone;
}
}
eve.on('raphael.anim.frame.' + handSet[1].id, onAnimate);
handSet.animate({
transform: ['r', finalAngle, b.center.x, b.center.y]
}, this.duration, this.easing, function() {
eve.unbind('raphael.anim.frame.' + handSet[1].id, onAnimate);
});
// ...
this.value = value;
return true;
},
_getRealtimeValues: function() {
var hand = this.sets.hand[0],
currentRotation = hand.attr('transform')[0][1] * 1,
currentAngle = currentRotation - this.startingIncline,
totalAngle = Math.abs(this.endingIncline - this.startingIncline),
currentPercentage = currentAngle / totalAngle,
currentValue = Math.abs(this.maxValue - this.minValue) * currentPercentage + this.minValue,
currentZone = (this.zones.normal.fromPercentage <= currentPercentage && currentPercentage < this.zones.normal.toPercentage) ? NORMAL : (this.zones.warning.fromPercentage <= currentPercentage && currentPercentage < this.zones.warning.toPercentage) ? WARNING : (this.zones.critical.fromPercentage <= currentPercentage && currentPercentage < this.zones.critical.toPercentage) ? CRITICAL : 0; // unknown
// for display purposes
currentPercentage = currentPercentage.toFixed(this.percentagePrecision + 2) * 1; // e.g. 0.08543 = 85.43%
currentValue = currentValue.toFixed(this.valuePrecision) * 1;
return {
'value': currentValue,
'percentage': currentPercentage,
'percentageString': (currentPercentage * 100).toFixed(this.percentagePrecision) + '%',
'angle': currentAngle,
'zone': currentZone,
'totalAngle': totalAngle
};
},
/**
* For demo purposes
* @return {*}
*/
randomize: function() {
var iMin = this.minValue,
iMax = this.maxValue,
iRand = Math.floor(Math.random() * (iMax - iMin + 1) + iMin);
this.val(iRand);
return this;
}
};
})(jQuery, Raphael, window.RaphaelExtensions);