<!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) + '&deg;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);