Added examples/htdocs/widgetsrising because IT WAS ON THE LIVE SITE BUT NOT CHECKED...
authorMike Taylor <mike@indexdata.com>
Tue, 12 May 2015 08:57:42 +0000 (08:57 +0000)
committerMike Taylor <mike@indexdata.com>
Tue, 12 May 2015 08:57:42 +0000 (08:57 +0000)
examples/htdocs/widgetsrising/index.html [new file with mode: 0644]
examples/htdocs/widgetsrising/nv.d3.css [new file with mode: 0644]
examples/htdocs/widgetsrising/nv.d3.js [new file with mode: 0644]

diff --git a/examples/htdocs/widgetsrising/index.html b/examples/htdocs/widgetsrising/index.html
new file mode 100644 (file)
index 0000000..124abdb
--- /dev/null
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Widgets Rising</title>
+    <link rel="stylesheet" href="http://cdn.jsdelivr.net/jquery.ui/1.11.2/themes/base/minified/jquery-ui.min.css"> 
+    <link rel="stylesheet" href="http://cdn.jsdelivr.net/bootstrap/3.3.1/css/bootstrap.min.css">
+    <link rel="stylesheet" href="http://cdn.jsdelivr.net/bootstrap/3.3.1/css/bootstrap-theme.min.css">
+    <link rel="stylesheet" href="nv.d3.css" />
+  </head>
+  <style>
+    #content {
+      max-width: 860px;
+    }
+    nav {
+      margin-bottom: 20px;
+    }
+  
+    table .progress {
+      margin: 0;
+    }
+
+    /* A somewhat intense way to relatively scale the charts
+       while preserving a chosen aspect ratio */
+    #sumpie {
+      position: relative;
+    }
+    #sumpie:before{
+      content: "";
+      display: block;
+      padding-top: 100%;  /* 1:1 */
+    }
+    #sumpie > svg {
+      position:  absolute;
+      top: 0;
+      left: 0;
+      bottom: 0;
+      right: 0;
+      width: 100%;
+      height: 100%;
+    }
+
+    #sumlines {
+      position: relative;
+    }
+    #sumlines:before{
+      content: "";
+      display: block;
+      padding-top: 50%;
+    }
+    #sumlines > svg {
+      position:  absolute;
+      top: 0;
+      left: 0;
+      bottom: 0;
+      right: 0;
+      width: 100%;
+      height: 100%;
+    }
+  </style>
+  <body>
+    <nav class="navbar-static-top navbar-default">
+      <div class="container-fluid">
+        <div class="navbar-header">
+          <a class="navbar-brand" href="#">Widget Admin</a>
+        </div>
+        <ul class="nav navbar-nav navbar-left">
+          <li><a href="#">Add widget</a></li>
+          <li><a href="#">Billing</a></li>
+          <li><a href="#">Help</a></li>
+        </ul>
+        <ul class="nav navbar-nav navbar-right">
+          <li><p class="navbar-text">Logged in as Syndicated Incorporated</p></li>
+          <li><a href="#">Logout</a></li>
+        </ul>
+      </div>
+    </nav>
+
+    <div id="content" class="container">
+      <div class="col-sm-12"><div class="panel panel-default">
+        <div class="panel-heading"><h3 class="panel-title">Summary</h3></div>
+        <div class="panel-body">
+          <div class="row">
+            <div class="col-sm-7">
+              <h3>Widgets configured: 3<h3>
+              <h3>Widgets active: 2</h3>
+              <h3>Total clicks this month: 3058</h3>
+            </div>
+            <div id="sumpie" class="col-sm-5">
+              <svg></svg>
+            </div>
+          </div>
+          <div class="row">
+            <div id="sumlines" class='with-3d-shadow with-transitions'>
+              <svg></svg>
+            </div>
+          </div>
+        </div>
+      </div></div>
+
+      <div class="col-sm-12"><div class="panel panel-default">
+        <div class="panel-heading"><h3 class="panel-title">Widgets</h3></div>
+        <table class="table">
+          <tr>
+            <th>Widget</th>
+            <th>Actions</th>
+            <th>Monthly clicks</th>
+            <th>Budget</th>
+          </tr>
+          <tr>
+            <td>Widget 1</td>
+            <td>
+              <a class="edit-widget" href="#"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span></a>
+              <a href="#"><span class="glyphicon glyphicon-off" aria-hidden="true"></span></a>
+              <a href="#"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
+            </td>
+            <td>363</td>
+            <td>  
+              <div class="progress">
+                <div class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 60%;">$48.00 / $80.00</div></div>
+            </td>
+          </tr>
+          <tr>
+            <td>Reference</td>
+            <td>
+              <a class="edit-widget" href="#"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span></a>
+              <a href="#"><span class="glyphicon glyphicon-off" aria-hidden="true"></span></a>
+              <a href="#"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
+            </td>
+            <td>210</td>
+            <td>  
+              <div class="progress">
+                <div class="progress-bar" role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%;">$35.00 / $70.00</div></div>
+            </td>
+          </tr>
+          <tr>
+            <td>Historical image</td>
+            <td>
+              <a class="edit-widget" href="#"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span></a>
+              <a href="#"><span class="glyphicon glyphicon-off" aria-hidden="true"></span></a>
+              <a href="#"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
+            </td>
+            <td>471</td>
+            <td>  
+              <div class="progress">
+                <div class="progress-bar" role="progressbar" aria-valuenow="95" aria-valuemin="0" aria-valuemax="100" style="width: 95%;">$95.00 / $100.00</div></div>
+            </td>
+          </tr>
+        </table>
+      </div></div>
+    </div>
+    <script src="http://cdn.jsdelivr.net/jquery/2.1.3/jquery.min.js"></script>
+    <script src="http://cdn.jsdelivr.net/jquery.ui/1.11.2/jquery-ui.min.js"></script>
+    <script src="http://cdn.jsdelivr.net/bootstrap/3.3.1/js/bootstrap.min.js"></script>
+    <script src="http://cdn.jsdelivr.net/d3js/3.5.2/d3.min.js"></script>
+    <script src="nv.d3.js"></script>
+    <script>
+      // Summary pie chart
+      var obj = [{label: "Spent this month", value: 102.65}, {label: "Remaining budget", value: 47.35}];
+      nv.addGraph(function() {
+        var chart = nv.models.pieChart()
+            .x(function(d) { return d.label })
+            .y(function(d) { return d.value })
+            .showLabels(true)     //Display pie labels
+            .pieLabelsOutside(false)
+            .labelThreshold(.05)  //Configure the minimum slice size for labels to show up
+            .labelType("value") //Configure what type of data to show in the label. Can be "key", "value" or "percent"
+            .donut(true)          //Turn on Donut mode. Makes pie chart look tasty!
+            .donutRatio(0.35)     //Configure how big you want the donut hole size to be.
+            .title("January");
+            ;
+
+          d3.select("#sumpie > svg")
+              .datum(obj)
+              .transition().duration(350)
+              .call(chart);
+
+        return chart;
+      });
+
+      function genLine() {
+        var line = [];
+        for (var i = 1; i < 13; i++) {
+          var obj = {};
+          obj.x = i;
+          obj.y = Math.floor(Math.random()*500);
+          line.push(obj);
+        }
+        return line;
+      }
+
+      var obj2 = [
+        {key:"Widget 1", values:genLine()},
+        {key:"Reference", values:genLine()},
+        {key:"Historical image", values:genLine()}
+      ];
+      nv.addGraph(function() {
+        var chart = nv.models.lineWithFocusChart();
+        chart.xAxis.tickFormat(d3.format(',f'));
+        chart.x2Axis.tickFormat(d3.format(',f'));
+        chart.yAxis.tickFormat(d3.format(',f'));
+        chart.yAxis.axisLabel("Clicks");
+        chart.y2Axis.tickFormat(d3.format(',f'));
+        d3.select('#sumlines > svg')
+            .datum(obj2)
+            .call(chart);
+        nv.utils.windowResize(chart.update);
+        return chart;
+      });
+
+    </script>
+  </body>
+</html>
diff --git a/examples/htdocs/widgetsrising/nv.d3.css b/examples/htdocs/widgetsrising/nv.d3.css
new file mode 100644 (file)
index 0000000..54770eb
--- /dev/null
@@ -0,0 +1,624 @@
+
+/********************
+  Default CSS for an svg element nvd3 used
+*/
+svg.nvd3-svg {
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -ms-user-select: none;
+    -moz-user-select: none;
+    user-select: none;
+    display: block;
+}
+
+/********************
+  Box shadow and border radius styling
+*/
+.nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip {
+  -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+  -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+  box-shadow: 0 5px 10px rgba(0,0,0,.2);
+
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+}
+
+/********************
+ * TOOLTIP CSS
+ */
+
+.nvtooltip {
+  position: absolute;
+  background-color: rgba(255,255,255,1.0);
+  padding: 1px;
+  border: 1px solid rgba(0,0,0,.2);
+  z-index: 10000;
+
+  font-family: Arial;
+  font-size: 13px;
+  text-align: left;
+  pointer-events: none;
+
+  white-space: nowrap;
+
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -khtml-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+/*Give tooltips that old fade in transition by
+    putting a "with-transitions" class on the container div.
+*/
+.nvtooltip.with-transitions, .with-transitions .nvtooltip {
+  transition: opacity 50ms linear;
+  -moz-transition: opacity 50ms linear;
+  -webkit-transition: opacity 50ms linear;
+
+  transition-delay: 200ms;
+  -moz-transition-delay: 200ms;
+  -webkit-transition-delay: 200ms;
+}
+
+.nvtooltip.x-nvtooltip,
+.nvtooltip.y-nvtooltip {
+  padding: 8px;
+}
+
+.nvtooltip h3 {
+  margin: 0;
+  padding: 4px 14px;
+  line-height: 18px;
+  font-weight: normal;
+  background-color: rgba(247,247,247,0.75);
+  text-align: center;
+
+  border-bottom: 1px solid #ebebeb;
+
+  -webkit-border-radius: 5px 5px 0 0;
+  -moz-border-radius: 5px 5px 0 0;
+  border-radius: 1px 5px 0 0;
+}
+
+.nvtooltip p {
+  margin: 0;
+  padding: 5px 14px;
+  text-align: center;
+}
+
+.nvtooltip span {
+  display: inline-block;
+  margin: 2px 0;
+}
+
+.nvtooltip table {
+  margin: 6px;
+  border-spacing:0;
+}
+
+
+.nvtooltip table td {
+  padding: 2px 9px 2px 0;
+  vertical-align: middle;
+}
+
+.nvtooltip table td.key {
+  font-weight:normal;
+}
+.nvtooltip table td.value {
+  text-align: right;
+  font-weight: bold;
+}
+
+.nvtooltip table tr.highlight td {
+  padding: 1px 9px 1px 0;
+  border-bottom-style: solid;
+  border-bottom-width: 1px;
+  border-top-style: solid;
+  border-top-width: 1px;
+}
+
+.nvtooltip table td.legend-color-guide div {
+  width: 8px;
+  height: 8px;
+  vertical-align: middle;
+}
+
+.nvtooltip .footer {
+  padding: 3px;
+  text-align: center;
+}
+
+.nvtooltip-pending-removal {
+  position: absolute;
+  pointer-events: none;
+}
+
+.nvd3 text {
+  font: normal 12px Arial;
+}
+
+.nvd3 .nv-background {
+  fill: white;
+  fill-opacity: 0;
+}
+
+.nvd3.nv-noData {
+  font-size: 18px;
+  font-weight: bold;
+}
+
+
+/**********
+*  Brush
+*/
+
+.nv-brush .extent {
+  fill-opacity: .125;
+  shape-rendering: crispEdges;
+}
+
+
+/**********
+*  Legend
+*/
+
+.nvd3 .nv-legend .nv-series {
+  cursor: pointer;
+}
+
+.nvd3 .nv-legend .nv-disabled circle {
+  fill-opacity: 0;
+}
+
+
+/**********
+*  Axes
+*/
+
+.axis {
+  opacity: 1;
+}
+
+.axis.nv-disabled {
+  opacity: 0;
+}
+
+.nvd3 .nv-axis {
+  pointer-events:none;
+}
+
+.nvd3 .nv-axis path {
+  fill: none;
+  stroke: #000;
+  stroke-opacity: .75;
+  shape-rendering: crispEdges;
+}
+
+.nvd3 .nv-axis path.domain {
+  stroke-opacity: .75;
+}
+
+.nvd3 .nv-axis.nv-x path.domain {
+  stroke-opacity: 0;
+}
+
+.nvd3 .nv-axis line {
+  fill: none;
+  stroke: #e5e5e5;
+  shape-rendering: crispEdges;
+}
+
+.nvd3 .nv-axis .zero line,
+/*this selector may not be necessary*/ .nvd3 .nv-axis line.zero {
+  stroke-opacity: .75;
+}
+
+.nvd3 .nv-axis .nv-axisMaxMin text {
+  font-weight: bold;
+}
+
+.nvd3 .x  .nv-axis .nv-axisMaxMin text,
+.nvd3 .x2 .nv-axis .nv-axisMaxMin text,
+.nvd3 .x3 .nv-axis .nv-axisMaxMin text {
+  text-anchor: middle
+}
+
+
+/**********
+*  Brush
+*/
+
+.nv-brush .resize path {
+  fill: #eee;
+  stroke: #666;
+}
+
+
+/**********
+*  Bars
+*/
+
+.nvd3 .nv-bars .negative rect {
+    zfill: brown;
+}
+
+.nvd3 .nv-bars rect {
+  zfill: steelblue;
+  fill-opacity: .75;
+
+  transition: fill-opacity 250ms linear;
+  -moz-transition: fill-opacity 250ms linear;
+  -webkit-transition: fill-opacity 250ms linear;
+}
+
+.nvd3 .nv-bars rect.hover {
+  fill-opacity: 1;
+}
+
+.nvd3 .nv-bars .hover rect {
+  fill: lightblue;
+}
+
+.nvd3 .nv-bars text {
+  fill: rgba(0,0,0,0);
+}
+
+.nvd3 .nv-bars .hover text {
+  fill: rgba(0,0,0,1);
+}
+
+
+/**********
+*  Bars
+*/
+
+.nvd3 .nv-multibar .nv-groups rect,
+.nvd3 .nv-multibarHorizontal .nv-groups rect,
+.nvd3 .nv-discretebar .nv-groups rect {
+  stroke-opacity: 0;
+
+  transition: fill-opacity 250ms linear;
+  -moz-transition: fill-opacity 250ms linear;
+  -webkit-transition: fill-opacity 250ms linear;
+}
+
+.nvd3 .nv-multibar .nv-groups rect:hover,
+.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,
+.nvd3 .nv-discretebar .nv-groups rect:hover {
+  fill-opacity: 1;
+}
+
+.nvd3 .nv-discretebar .nv-groups text,
+.nvd3 .nv-multibarHorizontal .nv-groups text {
+  font-weight: bold;
+  fill: rgba(0,0,0,1);
+  stroke: rgba(0,0,0,0);
+}
+
+/***********
+*  Pie Chart
+*/
+
+.nvd3.nv-pie path {
+  stroke-opacity: 0;
+  transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+  -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+  -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+
+}
+
+.nvd3.nv-pie .nv-pie-title {
+    font-size: 24px;
+    fill: rgba(19, 196, 249, 0.59);
+}
+
+.nvd3.nv-pie .nv-slice text {
+  stroke: #000;
+  stroke-width: 0;
+}
+
+.nvd3.nv-pie path {
+  stroke: #fff;
+  stroke-width: 1px;
+  stroke-opacity: 1;
+}
+
+.nvd3.nv-pie .hover path {
+  fill-opacity: .7;
+}
+.nvd3.nv-pie .nv-label {
+  pointer-events: none;
+}
+.nvd3.nv-pie .nv-label rect {
+  fill-opacity: 0;
+  stroke-opacity: 0;
+}
+
+/**********
+* Lines
+*/
+
+.nvd3 .nv-groups path.nv-line {
+  fill: none;
+  stroke-width: 1.5px;
+}
+
+.nvd3 .nv-groups path.nv-line.nv-thin-line {
+  stroke-width: 1px;
+}
+
+
+.nvd3 .nv-groups path.nv-area {
+  stroke: none;
+}
+
+.nvd3 .nv-line.hover path {
+  stroke-width: 6px;
+}
+
+.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point {
+  fill-opacity: 0;
+  stroke-opacity: 0;
+}
+
+.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point {
+  fill-opacity: .5 !important;
+  stroke-opacity: .5 !important;
+}
+
+
+.with-transitions .nvd3 .nv-groups .nv-point {
+  transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+  -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+  -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+
+}
+
+.nvd3.nv-scatter .nv-groups .nv-point.hover,
+.nvd3 .nv-groups .nv-point.hover {
+  stroke-width: 7px;
+  fill-opacity: .95 !important;
+  stroke-opacity: .95 !important;
+}
+
+
+.nvd3 .nv-point-paths path {
+  stroke: #aaa;
+  stroke-opacity: 0;
+  fill: #eee;
+  fill-opacity: 0;
+}
+
+
+
+.nvd3 .nv-indexLine {
+  cursor: ew-resize;
+}
+
+
+/**********
+* Distribution
+*/
+
+.nvd3 .nv-distribution {
+  pointer-events: none;
+}
+
+
+/**********
+*  Scatter
+*/
+
+.nvd3 .nv-groups .nv-point.hover {
+  stroke-width: 20px;
+  stroke-opacity: .5;
+}
+
+.nvd3 .nv-scatter .nv-point.hover {
+  fill-opacity: 1;
+}
+
+
+/**********
+*  Stacked Area
+*/
+
+.nvd3.nv-stackedarea path.nv-area {
+  fill-opacity: .7;
+  stroke-opacity: 0;
+  transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+  -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+  -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+}
+
+.nvd3.nv-stackedarea path.nv-area.hover {
+  fill-opacity: .9;
+}
+
+
+.nvd3.nv-stackedarea .nv-groups .nv-point {
+  stroke-opacity: 0;
+  fill-opacity: 0;
+}
+
+
+/**********
+*  Line Plus Bar
+*/
+
+.nvd3.nv-linePlusBar .nv-bar rect {
+  fill-opacity: .75;
+}
+
+.nvd3.nv-linePlusBar .nv-bar rect:hover {
+  fill-opacity: 1;
+}
+
+
+/**********
+*  Bullet
+*/
+
+.nvd3.nv-bullet { font: 10px sans-serif; }
+.nvd3.nv-bullet .nv-measure { fill-opacity: .8; }
+.nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; }
+.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; }
+.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; }
+.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; }
+.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; }
+.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; }
+.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; }
+.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; }
+.nvd3.nv-bullet .nv-subtitle { fill: #999; }
+
+
+.nvd3.nv-bullet .nv-range {
+  fill: #bababa;
+  fill-opacity: .4;
+}
+.nvd3.nv-bullet .nv-range:hover {
+  fill-opacity: .7;
+}
+
+
+/**********
+* Sparkline
+*/
+
+.nvd3.nv-sparkline path {
+  fill: none;
+}
+
+.nvd3.nv-sparklineplus g.nv-hoverValue {
+  pointer-events: none;
+}
+
+.nvd3.nv-sparklineplus .nv-hoverValue line {
+  stroke: #333;
+  stroke-width: 1.5px;
+ }
+
+.nvd3.nv-sparklineplus,
+.nvd3.nv-sparklineplus g {
+  pointer-events: all;
+}
+
+.nvd3 .nv-hoverArea {
+  fill-opacity: 0;
+  stroke-opacity: 0;
+}
+
+.nvd3.nv-sparklineplus .nv-xValue,
+.nvd3.nv-sparklineplus .nv-yValue {
+  stroke-width: 0;
+  font-size: .9em;
+  font-weight: normal;
+}
+
+.nvd3.nv-sparklineplus .nv-yValue {
+  stroke: #f66;
+}
+
+.nvd3.nv-sparklineplus .nv-maxValue {
+  stroke: #2ca02c;
+  fill: #2ca02c;
+}
+
+.nvd3.nv-sparklineplus .nv-minValue {
+  stroke: #d62728;
+  fill: #d62728;
+}
+
+.nvd3.nv-sparklineplus .nv-currentValue {
+  font-weight: bold;
+  font-size: 1.1em;
+}
+
+/**********
+* historical stock
+*/
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick {
+  stroke-width: 1px;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover {
+  stroke-width: 2px;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive {
+    stroke: #2ca02c;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative {
+    stroke: #d62728;
+}
+
+.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel {
+  font-weight: bold;
+}
+
+.nvd3.nv-historicalStockChart .nv-dragTarget {
+  fill-opacity: 0;
+  stroke: none;
+  cursor: move;
+}
+
+.nvd3 .nv-brush .extent {
+  fill-opacity: 0 !important;
+}
+
+.nvd3 .nv-brushBackground rect {
+  stroke: #000;
+  stroke-width: .4;
+  fill: #fff;
+  fill-opacity: .7;
+}
+
+
+/**********
+* Parallel Coordinates
+*/
+
+.nvd3 .background path {
+  fill: none;
+  stroke: #EEE;
+  stroke-opacity: .4;
+  shape-rendering: crispEdges;
+}
+
+.nvd3 .foreground path {
+  fill: none;
+  stroke-opacity: .7;
+}
+
+.nvd3 .brush .extent {
+  fill-opacity: .3;
+  stroke: #fff;
+  shape-rendering: crispEdges;
+}
+
+.nvd3 .axis line, .axis path {
+  fill: none;
+  stroke: #000;
+  shape-rendering: crispEdges;
+}
+
+.nvd3 .axis text {
+  text-shadow: 0 1px 0 #fff;
+}
+
+/****
+Interactive Layer
+*/
+.nvd3 .nv-interactiveGuideLine {
+  pointer-events:none;
+}
+.nvd3 line.nv-guideline {
+  stroke: #ccc;
+}
\ No newline at end of file
diff --git a/examples/htdocs/widgetsrising/nv.d3.js b/examples/htdocs/widgetsrising/nv.d3.js
new file mode 100644 (file)
index 0000000..7084436
--- /dev/null
@@ -0,0 +1,11347 @@
+/* nvd3 version 1.7.0(https://github.com/liquidpele/nvd3) 2014-12-13 */
+(function(){
+
+// set up main nv object on window
+var nv = window.nv || {};
+window.nv = nv;
+
+// the major global objects under the nv namespace
+nv.dev = false; //set false when in production
+nv.tooltip = nv.tooltip || {}; // For the tooltip system
+nv.utils = nv.utils || {}; // Utility subsystem
+nv.models = nv.models || {}; //stores all the possible models/components
+nv.charts = {}; //stores all the ready to use charts
+nv.graphs = []; //stores all the graphs currently on the page
+nv.logs = {}; //stores some statistics and potential error messages
+
+nv.dispatch = d3.dispatch('render_start', 'render_end');
+
+// Function bind polyfill
+// Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
+// https://github.com/ariya/phantomjs/issues/10522
+// http://kangax.github.io/compat-table/es5/#Function.prototype.bind
+// phantomJS is used for running the test suite
+if (!Function.prototype.bind) {
+    Function.prototype.bind = function (oThis) {
+        if (typeof this !== "function") {
+            // closest thing possible to the ECMAScript 5 internal IsCallable function
+            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
+        }
+
+        var aArgs = Array.prototype.slice.call(arguments, 1),
+            fToBind = this,
+            fNOP = function () {},
+            fBound = function () {
+                return fToBind.apply(this instanceof fNOP && oThis
+                        ? this
+                        : oThis,
+                    aArgs.concat(Array.prototype.slice.call(arguments)));
+            };
+
+        fNOP.prototype = this.prototype;
+        fBound.prototype = new fNOP();
+        return fBound;
+    };
+}
+
+//  Development render timers - disabled if dev = false
+if (nv.dev) {
+    nv.dispatch.on('render_start', function(e) {
+        nv.logs.startTime = +new Date();
+    });
+
+    nv.dispatch.on('render_end', function(e) {
+        nv.logs.endTime = +new Date();
+        nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
+        nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
+    });
+}
+
+// Logs all arguments, and returns the last so you can test things in place
+// Note: in IE8 console.log is an object not a function, and if modernizr is used
+// then calling Function.prototype.bind with with anything other than a function
+// causes a TypeError to be thrown.
+nv.log = function() {
+    if (nv.dev && window.console && console.log && console.log.apply)
+        console.log.apply(console, arguments);
+    else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
+        var log = Function.prototype.bind.call(console.log, console);
+        log.apply(console, arguments);
+    }
+    return arguments[arguments.length - 1];
+};
+
+// print console warning, should be used by deprecated functions
+nv.deprecated = function(name) {
+    if (nv.dev && console && console.warn) {
+        console.warn('`' + name + '` has been deprecated.');
+    }
+};
+
+// render function is used to queue up chart rendering
+// in non-blocking timeout functions
+nv.render = function render(step) {
+    // number of graphs to generate in each timeout loop
+    step = step || 1;
+
+    nv.render.active = true;
+    nv.dispatch.render_start();
+
+    setTimeout(function() {
+        var chart, graph;
+
+        for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
+            chart = graph.generate();
+            if (typeof graph.callback == typeof(Function)) graph.callback(chart);
+            nv.graphs.push(chart);
+        }
+
+        nv.render.queue.splice(0, i);
+
+        if (nv.render.queue.length) setTimeout(arguments.callee, 0);
+        else {
+            nv.dispatch.render_end();
+            nv.render.active = false;
+        }
+    }, 0);
+};
+
+nv.render.active = false;
+nv.render.queue = [];
+
+// main function to use when adding a new graph, see examples
+nv.addGraph = function(obj) {
+    if (typeof arguments[0] === typeof(Function)) {
+        obj = {generate: arguments[0], callback: arguments[1]};
+    }
+
+    nv.render.queue.push(obj);
+
+    if (!nv.render.active) {
+        nv.render();
+    }
+};/* Utility class to handle creation of an interactive layer.
+ This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
+ containing the X-coordinate. It can also render a vertical line where the mouse is located.
+
+ dispatch.elementMousemove is the important event to latch onto.  It is fired whenever the mouse moves over
+ the rectangle. The dispatch is given one object which contains the mouseX/Y location.
+ It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
+ */
+nv.interactiveGuideline = function() {
+    "use strict";
+
+    var tooltip = nv.models.tooltip();
+
+    //Public settings
+    var width = null;
+    var height = null;
+
+    //Please pass in the bounding chart's top and left margins
+    //This is important for calculating the correct mouseX/Y positions.
+    var margin = {left: 0, top: 0}
+        , xScale = d3.scale.linear()
+        , yScale = d3.scale.linear()
+        , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick')
+        , showGuideLine = true;
+    //Must pass in the bounding chart's <svg> container.
+    //The mousemove event is attached to this container.
+    var svgContainer = null;
+
+    // check if IE by looking for activeX
+    var isMSIE = "ActiveXObject" in window;
+
+
+    function layer(selection) {
+        selection.each(function(data) {
+            var container = d3.select(this);
+            var availableWidth = (width || 960), availableHeight = (height || 400);
+            var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
+                .data([data]);
+            var wrapEnter = wrap.enter()
+                .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
+            wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
+
+            if (!svgContainer) {
+                return;
+            }
+
+            function mouseHandler() {
+                var d3mouse = d3.mouse(this);
+                var mouseX = d3mouse[0];
+                var mouseY = d3mouse[1];
+                var subtractMargin = true;
+                var mouseOutAnyReason = false;
+                if (isMSIE) {
+                    /*
+                     D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
+                     d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
+                     over a rect in IE 10.
+                     However, d3.event.offsetX/Y also returns the mouse coordinates
+                     relative to the triggering <rect>. So we use offsetX/Y on IE.
+                     */
+                    mouseX = d3.event.offsetX;
+                    mouseY = d3.event.offsetY;
+
+                    /*
+                     On IE, if you attach a mouse event listener to the <svg> container,
+                     it will actually trigger it for all the child elements (like <path>, <circle>, etc).
+                     When this happens on IE, the offsetX/Y is set to where ever the child element
+                     is located.
+                     As a result, we do NOT need to subtract margins to figure out the mouse X/Y
+                     position under this scenario. Removing the line below *will* cause
+                     the interactive layer to not work right on IE.
+                     */
+                    if(d3.event.target.tagName !== "svg") {
+                        subtractMargin = false;
+                    }
+
+                    if (d3.event.target.className.baseVal.match("nv-legend")) {
+                        mouseOutAnyReason = true;
+                    }
+
+                }
+
+                if(subtractMargin) {
+                    mouseX -= margin.left;
+                    mouseY -= margin.top;
+                }
+
+                /* If mouseX/Y is outside of the chart's bounds,
+                 trigger a mouseOut event.
+                 */
+                if (mouseX < 0 || mouseY < 0
+                    || mouseX > availableWidth || mouseY > availableHeight
+                    || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
+                    || mouseOutAnyReason
+                    ) {
+
+                    if (isMSIE) {
+                        if (d3.event.relatedTarget
+                            && d3.event.relatedTarget.ownerSVGElement === undefined
+                            && d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) {
+
+                            return;
+                        }
+                    }
+                    dispatch.elementMouseout({
+                        mouseX: mouseX,
+                        mouseY: mouseY
+                    });
+                    layer.renderGuideLine(null); //hide the guideline
+                    return;
+                }
+
+                var pointXValue = xScale.invert(mouseX);
+                dispatch.elementMousemove({
+                    mouseX: mouseX,
+                    mouseY: mouseY,
+                    pointXValue: pointXValue
+                });
+
+                //If user double clicks the layer, fire a elementDblclick
+                if (d3.event.type === "dblclick") {
+                    dispatch.elementDblclick({
+                        mouseX: mouseX,
+                        mouseY: mouseY,
+                        pointXValue: pointXValue
+                    });
+                }
+
+                // if user single clicks the layer, fire elementClick
+                if (d3.event.type === 'click') {
+                    dispatch.elementClick({
+                        mouseX: mouseX,
+                        mouseY: mouseY,
+                        pointXValue: pointXValue
+                    });
+                }
+            }
+
+            svgContainer
+                .on("mousemove",mouseHandler, true)
+                .on("mouseout" ,mouseHandler,true)
+                .on("dblclick" ,mouseHandler)
+                .on("click", mouseHandler)
+            ;
+
+            //Draws a vertical guideline at the given X postion.
+            layer.renderGuideLine = function(x) {
+                if (!showGuideLine) return;
+                var line = wrap.select(".nv-interactiveGuideLine")
+                    .selectAll("line")
+                    .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
+
+                line.enter()
+                    .append("line")
+                    .attr("class", "nv-guideline")
+                    .attr("x1", function(d) { return d;})
+                    .attr("x2", function(d) { return d;})
+                    .attr("y1", availableHeight)
+                    .attr("y2",0)
+                ;
+                line.exit().remove();
+
+            }
+        });
+    }
+
+    layer.dispatch = dispatch;
+    layer.tooltip = tooltip;
+
+    layer.margin = function(_) {
+        if (!arguments.length) return margin;
+        margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
+        margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
+        return layer;
+    };
+
+    layer.width = function(_) {
+        if (!arguments.length) return width;
+        width = _;
+        return layer;
+    };
+
+    layer.height = function(_) {
+        if (!arguments.length) return height;
+        height = _;
+        return layer;
+    };
+
+    layer.xScale = function(_) {
+        if (!arguments.length) return xScale;
+        xScale = _;
+        return layer;
+    };
+
+    layer.showGuideLine = function(_) {
+        if (!arguments.length) return showGuideLine;
+        showGuideLine = _;
+        return layer;
+    };
+
+    layer.svgContainer = function(_) {
+        if (!arguments.length) return svgContainer;
+        svgContainer = _;
+        return layer;
+    };
+
+    return layer;
+};
+
+/* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
+ This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
+
+ For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
+ Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10.  But interactiveBisect will return 5
+ because 28 is closer to 30 than 10.
+
+ Unit tests can be found in: interactiveBisectTest.html
+
+ Has the following known issues:
+ * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
+ * Won't work if there are duplicate x coordinate values.
+ */
+nv.interactiveBisect = function (values, searchVal, xAccessor) {
+    "use strict";
+    if (! (values instanceof Array)) {
+        return null;
+    }
+    if (typeof xAccessor !== 'function') {
+        xAccessor = function(d,i) {
+            return d.x;
+        }
+    }
+
+    var bisect = d3.bisector(xAccessor).left;
+    var index = d3.max([0, bisect(values,searchVal) - 1]);
+    var currentValue = xAccessor(values[index], index);
+
+    if (typeof currentValue === 'undefined') {
+        currentValue = index;
+    }
+
+    if (currentValue === searchVal) {
+        return index; //found exact match
+    }
+
+    var nextIndex = d3.min([index+1, values.length - 1]);
+    var nextValue = xAccessor(values[nextIndex], nextIndex);
+
+    if (typeof nextValue === 'undefined') {
+        nextValue = nextIndex;
+    }
+
+    if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
+        return index;
+    } else {
+        return nextIndex
+    }
+};
+
+/*
+ Returns the index in the array "values" that is closest to searchVal.
+ Only returns an index if searchVal is within some "threshold".
+ Otherwise, returns null.
+ */
+nv.nearestValueIndex = function (values, searchVal, threshold) {
+    "use strict";
+    var yDistMax = Infinity, indexToHighlight = null;
+    values.forEach(function(d,i) {
+        var delta = Math.abs(searchVal - d);
+        if ( delta <= yDistMax && delta < threshold) {
+            yDistMax = delta;
+            indexToHighlight = i;
+        }
+    });
+    return indexToHighlight;
+};
+/* Tooltip rendering model for nvd3 charts.
+ window.nv.models.tooltip is the updated,new way to render tooltips.
+
+ window.nv.tooltip.show is the old tooltip code.
+ window.nv.tooltip.* also has various helper methods.
+ */
+(function() {
+    "use strict";
+    window.nv.tooltip = {};
+
+    /* Model which can be instantiated to handle tooltip rendering.
+     Example usage:
+     var tip = nv.models.tooltip().gravity('w').distance(23)
+     .data(myDataObject);
+
+     tip();    //just invoke the returned function to render tooltip.
+     */
+    window.nv.models.tooltip = function() {
+        //HTML contents of the tooltip.  If null, the content is generated via the data variable.
+        var content = null;
+
+        /*
+        Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
+        Example Format of data:
+        {
+            key: "Date",
+            value: "August 2009",
+            series: [
+                {key: "Series 1", value: "Value 1", color: "#000"},
+                {key: "Series 2", value: "Value 2", color: "#00f"}
+            ]
+        }
+        */
+        var data = null;
+
+        var gravity = 'w'   //Can be 'n','s','e','w'. Determines how tooltip is positioned.
+            ,distance = 50   //Distance to offset tooltip from the mouse location.
+            ,snapDistance = 25   //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
+            ,   fixedTop = null //If not null, this fixes the top position of the tooltip.
+            ,   classes = null  //Attaches additional CSS classes to the tooltip DIV that is created.
+            ,   chartContainer = null   //Parent DIV, of the SVG Container that holds the chart.
+            ,   tooltipElem = null  //actual DOM element representing the tooltip.
+            ,   position = {left: null, top: null}      //Relative position of the tooltip inside chartContainer.
+            ,   enabled = true;  //True -> tooltips are rendered. False -> don't render tooltips.
+
+        //Generates a unique id when you create a new tooltip() object
+        var id = "nvtooltip-" + Math.floor(Math.random() * 100000);
+
+        //CSS class to specify whether element should not have mouse events.
+        var  nvPointerEventsClass = "nv-pointer-events-none";
+
+        //Format function for the tooltip values column
+        var valueFormatter = function(d,i) {
+            return d;
+        };
+
+        //Format function for the tooltip header value.
+        var headerFormatter = function(d) {
+            return d;
+        };
+
+        //By default, the tooltip model renders a beautiful table inside a DIV.
+        //You can override this function if a custom tooltip is desired.
+        var contentGenerator = function(d) {
+            if (content != null) {
+                return content;
+            }
+
+            if (d == null) {
+                return '';
+            }
+
+            var table = d3.select(document.createElement("table"));
+            var theadEnter = table.selectAll("thead")
+                .data([d])
+                .enter().append("thead");
+
+            theadEnter.append("tr")
+                .append("td")
+                .attr("colspan",3)
+                .append("strong")
+                .classed("x-value",true)
+                .html(headerFormatter(d.value));
+
+            var tbodyEnter = table.selectAll("tbody")
+                .data([d])
+                .enter().append("tbody");
+
+            var trowEnter = tbodyEnter.selectAll("tr")
+                    .data(function(p) { return p.series})
+                    .enter()
+                    .append("tr")
+                    .classed("highlight", function(p) { return p.highlight});
+
+            trowEnter.append("td")
+                .classed("legend-color-guide",true)
+                .append("div")
+                .style("background-color", function(p) { return p.color});
+
+            trowEnter.append("td")
+                .classed("key",true)
+                .html(function(p) {return p.key});
+
+            trowEnter.append("td")
+                .classed("value",true)
+                .html(function(p,i) { return valueFormatter(p.value,i) });
+
+
+            trowEnter.selectAll("td").each(function(p) {
+                if (p.highlight) {
+                    var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
+                    var opacity = 0.6;
+                    d3.select(this)
+                        .style("border-bottom-color", opacityScale(opacity))
+                        .style("border-top-color", opacityScale(opacity))
+                    ;
+                }
+            });
+
+            var html = table.node().outerHTML;
+            if (d.footer !== undefined)
+                html += "<div class='footer'>" + d.footer + "</div>";
+            return html;
+
+        };
+
+        var dataSeriesExists = function(d) {
+            if (d && d.series && d.series.length > 0) {
+                return true;
+            }
+            return false;
+        };
+
+        //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
+        function convertViewBoxRatio() {
+            if (chartContainer) {
+                var svg = d3.select(chartContainer);
+                if (svg.node().tagName !== "svg") {
+                    svg = svg.select("svg");
+                }
+                var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
+                if (viewBox) {
+                    viewBox = viewBox.split(' ');
+                    var ratio = parseInt(svg.style('width')) / viewBox[2];
+
+                    position.left = position.left * ratio;
+                    position.top  = position.top * ratio;
+                }
+            }
+        }
+
+        //Creates new tooltip container, or uses existing one on DOM.
+        function getTooltipContainer(newContent) {
+            var body;
+            if (chartContainer) {
+                body = d3.select(chartContainer);
+            } else {
+                body = d3.select("body");
+            }
+
+            var container = body.select(".nvtooltip");
+            if (container.node() === null) {
+                //Create new tooltip div if it doesn't exist on DOM.
+                container = body.append("div")
+                    .attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
+                    .attr("id",id)
+                ;
+            }
+
+            container.node().innerHTML = newContent;
+            container.style("top",0).style("left",0).style("opacity",0);
+            container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
+            container.classed(nvPointerEventsClass,true);
+            return container.node();
+        }
+
+        //Draw the tooltip onto the DOM.
+        function nvtooltip() {
+            if (!enabled) return;
+            if (!dataSeriesExists(data)) return;
+
+            convertViewBoxRatio();
+
+            var left = position.left;
+            var top = (fixedTop != null) ? fixedTop : position.top;
+            var container = getTooltipContainer(contentGenerator(data));
+            tooltipElem = container;
+            if (chartContainer) {
+                var svgComp = chartContainer.getElementsByTagName("svg")[0];
+                var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
+                var svgOffset = {left:0,top:0};
+                if (svgComp) {
+                    var svgBound = svgComp.getBoundingClientRect();
+                    var chartBound = chartContainer.getBoundingClientRect();
+                    var svgBoundTop = svgBound.top;
+
+                    //Defensive code. Sometimes, svgBoundTop can be a really negative
+                    //  number, like -134254. That's a bug.
+                    //  If such a number is found, use zero instead. FireFox bug only
+                    if (svgBoundTop < 0) {
+                        var containerBound = chartContainer.getBoundingClientRect();
+                        svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
+                    }
+                    svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
+                    svgOffset.left = Math.abs(svgBound.left - chartBound.left);
+                }
+                //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
+                //You need to also add any offset between the <svg> element and its containing <div>
+                //Finally, add any offset of the containing <div> on the whole page.
+                left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
+                top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
+            }
+
+            if (snapDistance && snapDistance > 0) {
+                top = Math.floor(top/snapDistance) * snapDistance;
+            }
+
+            nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
+            return nvtooltip;
+        }
+
+        nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
+
+        nvtooltip.content = function(_) {
+            if (!arguments.length) return content;
+            content = _;
+            return nvtooltip;
+        };
+
+        //Returns tooltipElem...not able to set it.
+        nvtooltip.tooltipElem = function() {
+            return tooltipElem;
+        };
+
+        nvtooltip.contentGenerator = function(_) {
+            if (!arguments.length) return contentGenerator;
+            if (typeof _ === 'function') {
+                contentGenerator = _;
+            }
+            return nvtooltip;
+        };
+
+        nvtooltip.data = function(_) {
+            if (!arguments.length) return data;
+            data = _;
+            return nvtooltip;
+        };
+
+        nvtooltip.gravity = function(_) {
+            if (!arguments.length) return gravity;
+            gravity = _;
+            return nvtooltip;
+        };
+
+        nvtooltip.distance = function(_) {
+            if (!arguments.length) return distance;
+            distance = _;
+            return nvtooltip;
+        };
+
+        nvtooltip.snapDistance = function(_) {
+            if (!arguments.length) return snapDistance;
+            snapDistance = _;
+            return nvtooltip;
+        };
+
+        nvtooltip.classes = function(_) {
+            if (!arguments.length) return classes;
+            classes = _;
+            return nvtooltip;
+        };
+
+        nvtooltip.chartContainer = function(_) {
+            if (!arguments.length) return chartContainer;
+            chartContainer = _;
+            return nvtooltip;
+        };
+
+        nvtooltip.position = function(_) {
+            if (!arguments.length) return position;
+            position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
+            position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
+            return nvtooltip;
+        };
+
+        nvtooltip.fixedTop = function(_) {
+            if (!arguments.length) return fixedTop;
+            fixedTop = _;
+            return nvtooltip;
+        };
+
+        nvtooltip.enabled = function(_) {
+            if (!arguments.length) return enabled;
+            enabled = _;
+            return nvtooltip;
+        };
+
+        nvtooltip.valueFormatter = function(_) {
+            if (!arguments.length) return valueFormatter;
+            if (typeof _ === 'function') {
+                valueFormatter = _;
+            }
+            return nvtooltip;
+        };
+
+        nvtooltip.headerFormatter = function(_) {
+            if (!arguments.length) return headerFormatter;
+            if (typeof _ === 'function') {
+                headerFormatter = _;
+            }
+            return nvtooltip;
+        };
+
+        //id() is a read-only function. You can't use it to set the id.
+        nvtooltip.id = function() {
+            return id;
+        };
+
+        return nvtooltip;
+    };
+
+    //Original tooltip.show function. Kept for backward compatibility.
+    // pos = [left,top]
+    nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
+
+        //Create new tooltip div if it doesn't exist on DOM.
+        var   container = document.createElement('div');
+        container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
+
+        var body = parentContainer;
+        if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
+            //If the parent element is an SVG element, place tooltip in the <body> element.
+            body = document.getElementsByTagName('body')[0];
+        }
+
+        container.style.left = 0;
+        container.style.top = 0;
+        container.style.opacity = 0;
+        // Content can also be dom element
+        if (typeof content !== 'string')
+            container.appendChild(content);
+        else
+            container.innerHTML = content;
+        body.appendChild(container);
+
+        //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
+        if (parentContainer) {
+            pos[0] = pos[0] - parentContainer.scrollLeft;
+            pos[1] = pos[1] - parentContainer.scrollTop;
+        }
+        nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
+    };
+
+    //Looks up the ancestry of a DOM element, and returns the first NON-svg node.
+    nv.tooltip.findFirstNonSVGParent = function(Elem) {
+        while(Elem.tagName.match(/^g|svg$/i) !== null) {
+            Elem = Elem.parentNode;
+        }
+        return Elem;
+    };
+
+    //Finds the total offsetTop of a given DOM element.
+    //Looks up the entire ancestry of an element, up to the first relatively positioned element.
+    nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
+        var offsetTop = initialTop;
+
+        do {
+            if( !isNaN( Elem.offsetTop ) ) {
+                offsetTop += (Elem.offsetTop);
+            }
+        } while( Elem = Elem.offsetParent );
+        return offsetTop;
+    };
+
+    //Finds the total offsetLeft of a given DOM element.
+    //Looks up the entire ancestry of an element, up to the first relatively positioned element.
+    nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
+        var offsetLeft = initialLeft;
+
+        do {
+            if( !isNaN( Elem.offsetLeft ) ) {
+                offsetLeft += (Elem.offsetLeft);
+            }
+        } while( Elem = Elem.offsetParent );
+        return offsetLeft;
+    };
+
+    //Global utility function to render a tooltip on the DOM.
+    //pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
+    //gravity = how to orient the tooltip
+    //dist = how far away from the mouse to place tooltip
+    //container = tooltip DIV
+    nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
+
+        var height = parseInt(container.offsetHeight),
+            width = parseInt(container.offsetWidth),
+            windowWidth = nv.utils.windowSize().width,
+            windowHeight = nv.utils.windowSize().height,
+            scrollTop = window.pageYOffset,
+            scrollLeft = window.pageXOffset,
+            left, top;
+
+        windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
+        windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
+
+        gravity = gravity || 's';
+        dist = dist || 20;
+
+        var tooltipTop = function ( Elem ) {
+            return nv.tooltip.findTotalOffsetTop(Elem, top);
+        };
+
+        var tooltipLeft = function ( Elem ) {
+            return nv.tooltip.findTotalOffsetLeft(Elem,left);
+        };
+
+        switch (gravity) {
+            case 'e':
+                left = pos[0] - width - dist;
+                top = pos[1] - (height / 2);
+                var tLeft = tooltipLeft(container);
+                var tTop = tooltipTop(container);
+                if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
+                if (tTop < scrollTop) top = scrollTop - tTop + top;
+                if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
+                break;
+            case 'w':
+                left = pos[0] + dist;
+                top = pos[1] - (height / 2);
+                var tLeft = tooltipLeft(container);
+                var tTop = tooltipTop(container);
+                if (tLeft + width > windowWidth) left = pos[0] - width - dist;
+                if (tTop < scrollTop) top = scrollTop + 5;
+                if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
+                break;
+            case 'n':
+                left = pos[0] - (width / 2) - 5;
+                top = pos[1] + dist;
+                var tLeft = tooltipLeft(container);
+                var tTop = tooltipTop(container);
+                if (tLeft < scrollLeft) left = scrollLeft + 5;
+                if (tLeft + width > windowWidth) left = left - width/2 + 5;
+                if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
+                break;
+            case 's':
+                left = pos[0] - (width / 2);
+                top = pos[1] - height - dist;
+                var tLeft = tooltipLeft(container);
+                var tTop = tooltipTop(container);
+                if (tLeft < scrollLeft) left = scrollLeft + 5;
+                if (tLeft + width > windowWidth) left = left - width/2 + 5;
+                if (scrollTop > tTop) top = scrollTop;
+                break;
+            case 'none':
+                left = pos[0];
+                top = pos[1] - dist;
+                var tLeft = tooltipLeft(container);
+                var tTop = tooltipTop(container);
+                break;
+        }
+
+        container.style.left = left+'px';
+        container.style.top = top+'px';
+        container.style.opacity = 1;
+        container.style.position = 'absolute';
+
+        return container;
+    };
+
+    //Global utility function to remove tooltips from the DOM.
+    nv.tooltip.cleanup = function() {
+
+        // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
+        var tooltips = document.getElementsByClassName('nvtooltip');
+        var purging = [];
+        while(tooltips.length) {
+            purging.push(tooltips[0]);
+            tooltips[0].style.transitionDelay = '0 !important';
+            tooltips[0].style.opacity = 0;
+            tooltips[0].className = 'nvtooltip-pending-removal';
+        }
+
+        setTimeout(function() {
+
+            while (purging.length) {
+                var removeMe = purging.pop();
+                removeMe.parentNode.removeChild(removeMe);
+            }
+        }, 500);
+    };
+
+})();
+
+
+/*
+Gets the browser window size
+
+Returns object with height and width properties
+ */
+nv.utils.windowSize = function() {
+    // Sane defaults
+    var size = {width: 640, height: 480};
+
+    // Earlier IE uses Doc.body
+    if (document.body && document.body.offsetWidth) {
+        size.width = document.body.offsetWidth;
+        size.height = document.body.offsetHeight;
+    }
+
+    // IE can use depending on mode it is in
+    if (document.compatMode=='CSS1Compat' &&
+        document.documentElement &&
+        document.documentElement.offsetWidth ) {
+
+        size.width = document.documentElement.offsetWidth;
+        size.height = document.documentElement.offsetHeight;
+    }
+
+    // Most recent browsers use
+    if (window.innerWidth && window.innerHeight) {
+        size.width = window.innerWidth;
+        size.height = window.innerHeight;
+    }
+    return (size);
+};
+
+
+/*
+Binds callback function to run when window is resized
+ */
+nv.utils.windowResize = function(handler) {
+    if (window.addEventListener) {
+        window.addEventListener('resize', handler);
+    } else {
+        nv.log("ERROR: Failed to bind to window.resize with: ", handler);
+    }
+    // return object with clear function to remove the single added callback.
+    return {
+        callback: handler,
+        clear: function() {
+            window.removeEventListener('resize', handler);
+        }
+    }
+};
+
+
+/*
+Backwards compatible way to implement more d3-like coloring of graphs.
+If passed an array, wrap it in a function which implements the old behavior
+Else return what was passed in
+*/
+nv.utils.getColor = function(color) {
+    //if you pass in nothing, get default colors back
+    if (!arguments.length) {
+        return nv.utils.defaultColor();
+
+    //if passed an array, wrap it in a function
+    } else if(color instanceof Array) {
+        return function(d, i) { return d.color || color[i % color.length]; };
+
+    //if passed a function, return the function, or whatever it may be
+    //external libs, such as angularjs-nvd3-directives use this
+    } else {
+        //can't really help it if someone passes rubbish as color
+        return color;
+    }
+};
+
+
+/*
+Default color chooser uses the index of an object as before.
+ */
+nv.utils.defaultColor = function() {
+    var colors = d3.scale.category20().range();
+    return function(d, i) {
+        return d.color || colors[i % colors.length]
+    };
+};
+
+
+/*
+Returns a color function that takes the result of 'getKey' for each series and
+looks for a corresponding color from the dictionary
+*/
+nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
+    // use default series.key if getKey is undefined
+    getKey = getKey || function(series) { return series.key };
+    defaultColors = defaultColors || d3.scale.category20().range();
+
+    // start at end of default color list and walk back to index 0
+    var defIndex = defaultColors.length;
+
+    return function(series, index) {
+        var key = getKey(series);
+        if (typeof dictionary[key] === 'function') {
+            return dictionary[key]();
+        } else if (dictionary[key] !== undefined) {
+            return dictionary[key];
+        } else {
+            // no match in dictionary, use a default color
+            if (!defIndex) {
+                // used all the default colors, start over
+                defIndex = defaultColors.length;
+            }
+            defIndex = defIndex - 1;
+            return defaultColors[defIndex];
+        }
+    };
+};
+
+
+/*
+From the PJAX example on d3js.org, while this is not really directly needed
+it's a very cool method for doing pjax, I may expand upon it a little bit,
+open to suggestions on anything that may be useful
+*/
+nv.utils.pjax = function(links, content) {
+
+    var load = function(href) {
+        d3.html(href, function(fragment) {
+            var target = d3.select(content).node();
+            target.parentNode.replaceChild(
+                d3.select(fragment).select(content).node(),
+                target);
+            nv.utils.pjax(links, content);
+        });
+    };
+
+    d3.selectAll(links).on("click", function() {
+        history.pushState(this.href, this.textContent, this.href);
+        load(this.href);
+        d3.event.preventDefault();
+    });
+
+    d3.select(window).on("popstate", function() {
+        if (d3.event.state) {
+            load(d3.event.state);
+        }
+    });
+};
+
+
+/*
+For when we want to approximate the width in pixels for an SVG:text element.
+Most common instance is when the element is in a display:none; container.
+Forumla is : text.length * font-size * constant_factor
+*/
+nv.utils.calcApproxTextWidth = function (svgTextElem) {
+    if (typeof svgTextElem.style === 'function'
+        && typeof svgTextElem.text === 'function') {
+
+        var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
+        var textLength = svgTextElem.text().length;
+        return textLength * fontSize * 0.5;
+    }
+    return 0;
+};
+
+
+/*
+Numbers that are undefined, null or NaN, convert them to zeros.
+*/
+nv.utils.NaNtoZero = function(n) {
+    if (typeof n !== 'number'
+        || isNaN(n)
+        || n === null
+        || n === Infinity
+        || n === -Infinity) {
+
+        return 0;
+    }
+    return n;
+};
+
+/*
+Add a way to watch for d3 transition ends to d3
+*/
+d3.selection.prototype.watchTransition = function(renderWatch){
+    var args = [this].concat([].slice.call(arguments, 1));
+    return renderWatch.transition.apply(renderWatch, args);
+};
+
+
+/*
+Helper object to watch when d3 has rendered something
+*/
+nv.utils.renderWatch = function(dispatch, duration) {
+    if (!(this instanceof nv.utils.renderWatch)) {
+        return new nv.utils.renderWatch(dispatch, duration);
+    }
+
+    var _duration = duration !== undefined ? duration : 250;
+    var renderStack = [];
+    var self = this;
+
+    this.models = function(models) {
+        models = [].slice.call(arguments, 0);
+        models.forEach(function(model){
+            model.__rendered = false;
+            (function(m){
+                m.dispatch.on('renderEnd', function(arg){
+                    m.__rendered = true;
+                    self.renderEnd('model');
+                });
+            })(model);
+
+            if (renderStack.indexOf(model) < 0) {
+                renderStack.push(model);
+            }
+        });
+    return this;
+    };
+
+    this.reset = function(duration) {
+        if (duration !== undefined) {
+            _duration = duration;
+        }
+        renderStack = [];
+    };
+
+    this.transition = function(selection, args, duration) {
+        args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
+
+        if (args.length > 1) {
+            duration = args.pop();
+        } else {
+            duration = _duration !== undefined ? _duration : 250;
+        }
+        selection.__rendered = false;
+
+        if (renderStack.indexOf(selection) < 0) {
+            renderStack.push(selection);
+        }
+
+        if (duration === 0) {
+            selection.__rendered = true;
+            selection.delay = function() { return this; };
+            selection.duration = function() { return this; };
+            return selection;
+        } else {
+            if (selection.length === 0) {
+                selection.__rendered = true;
+            } else if (selection.every( function(d){ return !d.length; } )) {
+                selection.__rendered = true;
+            } else {
+                selection.__rendered = false;
+            }
+
+            var n = 0;
+            return selection
+                .transition()
+                .duration(duration)
+                .each(function(){ ++n; })
+                .each('end', function(d, i) {
+                    if (--n === 0) {
+                        selection.__rendered = true;
+                        self.renderEnd.apply(this, args);
+                    }
+                });
+        }
+    };
+
+    this.renderEnd = function() {
+        if (renderStack.every( function(d){ return d.__rendered; } )) {
+            renderStack.forEach( function(d){ d.__rendered = false; });
+            dispatch.renderEnd.apply(this, arguments);
+        }
+    }
+
+};
+
+
+/*
+Takes multiple objects and combines them into the first one (dst)
+example:  nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
+gives:  {a: 2, b: 3, c: 4}
+*/
+nv.utils.deepExtend = function(dst){
+    var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
+    sources.forEach(function(source) {
+        for (key in source) {
+            var isArray = dst[key] instanceof Array;
+            var isObject = typeof dst[key] === 'object';
+            var srcObj = typeof source[key] === 'object';
+
+            if (isObject && !isArray && srcObj) {
+                nv.utils.deepExtend(dst[key], source[key]);
+            } else {
+                dst[key] = source[key];
+            }
+        }
+    });
+};
+
+
+/*
+state utility object, used to track d3 states in the models
+*/
+nv.utils.state = function(){
+    if (!(this instanceof nv.utils.state)) {
+        return new nv.utils.state();
+    }
+    var state = {};
+    var _self = this;
+    var _setState = function(){};
+    var _getState = function(){ return {}; };
+    var init = null;
+    var changed = null;
+
+    this.dispatch = d3.dispatch('change', 'set');
+
+    this.dispatch.on('set', function(state){
+        _setState(state, true);
+    });
+
+    this.getter = function(fn){
+        _getState = fn;
+        return this;
+    };
+
+    this.setter = function(fn, callback) {
+        if (!callback) {
+            callback = function(){};
+        }
+        _setState = function(state, update){
+            fn(state);
+            if (update) {
+                callback();
+            }
+        };
+        return this;
+    };
+
+    this.init = function(state){
+        init = init || {};
+        nv.utils.deepExtend(init, state);
+    };
+
+    var _set = function(){
+        var settings = _getState();
+
+        if (JSON.stringify(settings) === JSON.stringify(state)) {
+            return false;
+        }
+
+        for (var key in settings) {
+            if (state[key] === undefined) {
+                state[key] = {};
+            }
+            state[key] = settings[key];
+            changed = true;
+        }
+        return true;
+    };
+
+    this.update = function(){
+        if (init) {
+            _setState(init, false);
+            init = null;
+        }
+        if (_set.call(this)) {
+            this.dispatch.change(state);
+        }
+    };
+
+};
+
+
+/*
+Snippet of code you can insert into each nv.models.* to give you the ability to
+do things like:
+chart.options({
+  showXAxis: true,
+  tooltips: true
+});
+
+To enable in the chart:
+chart.options = nv.utils.optionsFunc.bind(chart);
+*/
+nv.utils.optionsFunc = function(args) {
+    nv.deprecated('nv.utils.optionsFunc');
+    if (args) {
+        d3.map(args).forEach((function(key,value) {
+            if (typeof this[key] === "function") {
+                this[key](value);
+            }
+        }).bind(this));
+    }
+    return this;
+};
+
+
+/*
+numTicks:  requested number of ticks
+data:  the chart data
+
+returns the number of ticks to actually use on X axis, based on chart data
+to avoid duplicate ticks with the same value
+*/
+nv.utils.calcTicksX = function(numTicks, data) {
+    // find max number of values from all data streams
+    var numValues = 1;
+    var i = 0;
+    for (i; i < data.length; i += 1) {
+        var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
+        numValues = stream_len > numValues ? stream_len : numValues;
+    }
+    nv.log("Requested number of ticks: ", numTicks);
+    nv.log("Calculated max values to be: ", numValues);
+    // make sure we don't have more ticks than values to avoid duplicates
+    numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
+    // make sure we have at least one tick
+    numTicks = numTicks < 1 ? 1 : numTicks;
+    // make sure it's an integer
+    numTicks = Math.floor(numTicks);
+    nv.log("Calculating tick count as: ", numTicks);
+    return numTicks;
+};
+
+
+/*
+returns number of ticks to actually use on Y axis, based on chart data
+*/
+nv.utils.calcTicksY = function(numTicks, data) {
+    // currently uses the same logic but we can adjust here if needed later
+    return nv.utils.calcTicksX(numTicks, data);
+};
+
+
+/*
+Add a particular option from an options object onto chart
+Options exposed on a chart are a getter/setter function that returns chart
+on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
+
+option objects should be generated via Object.create() to provide
+the option of manipulating data via get/set functions.
+*/
+nv.utils.initOption = function(chart, name) {
+    // if it's a call option, just call it directly, otherwise do get/set
+    if (chart._calls && chart._calls[name]) {
+        chart[name] = chart._calls[name];
+    } else {
+        chart[name] = function (_) {
+            if (!arguments.length) return chart._options[name];
+            chart._options[name] = _;
+            return chart;
+        };
+    }
+};
+
+
+/*
+Add all options in an options object to the chart
+*/
+nv.utils.initOptions = function(chart) {
+    var ops = Object.getOwnPropertyNames(chart._options || {});
+    var calls = Object.getOwnPropertyNames(chart._calls || {});
+    ops = ops.concat(calls);
+    for (var i in ops) {
+        nv.utils.initOption(chart, ops[i]);
+    }
+};
+
+
+/*
+Inherit options from a D3 object
+d3.rebind makes calling the function on target actually call it on source
+Also use _d3options so we can track what we inherit for documentation and chained inheritance
+*/
+nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
+    target._d3options = oplist.concat(target._d3options || []);
+    oplist.unshift(d3_source);
+    oplist.unshift(target);
+    d3.rebind.apply(this, oplist);
+};
+
+
+/*
+Remove duplicates from an array
+*/
+nv.utils.arrayUnique = function(a) {
+    return a.sort().filter(function(item, pos) {
+        return !pos || item != a[pos - 1];
+    });
+};
+
+
+/*
+Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
+Necessary since d3 doesn't let you extend its list -_-
+Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
+*/
+nv.utils.symbolMap = d3.map();
+
+
+/*
+Replaces d3.svg.symbol so that we can look both there and our own map
+ */
+nv.utils.symbol = function() {
+    var type,
+        size = 64;
+    function symbol(d,i) {
+        var t = type.call(this,d,i);
+        var s = size.call(this,d,i);
+        if (d3.svg.symbolTypes.indexOf(t) !== -1) {
+            return d3.svg.symbol().type(t).size(s)();
+        } else {
+            return nv.utils.symbolMap.get(t)(s);
+        }
+    }
+    symbol.type = function(_) {
+        if (!arguments.length) return type;
+        type = d3.functor(_);
+        return symbol;
+    };
+    symbol.size = function(_) {
+        if (!arguments.length) return size;
+        size = d3.functor(_);
+        return symbol;
+    };
+    return symbol;
+};
+
+
+/*
+Inherit option getter/setter functions from source to target
+d3.rebind makes calling the function on target actually call it on source
+Also track via _inherited and _d3options so we can track what we inherit
+for documentation generation purposes and chained inheritance
+*/
+nv.utils.inheritOptions = function(target, source) {
+    // inherit all the things
+    var ops = Object.getOwnPropertyNames(source._options || {});
+    var calls = Object.getOwnPropertyNames(source._calls || {});
+    var inherited = source._inherited || [];
+    var d3ops = source._d3options || [];
+    var args = ops.concat(calls).concat(inherited).concat(d3ops);
+    args.unshift(source);
+    args.unshift(target);
+    d3.rebind.apply(this, args);
+    // pass along the lists to keep track of them, don't allow duplicates
+    target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
+    target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
+};
+
+
+/*
+Runs common initialize code on the svg before the chart builds
+*/
+nv.utils.initSVG = function(svg) {
+    svg.classed({'nvd3-svg':true});
+};nv.models.axis = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var axis = d3.svg.axis();
+    var scale = d3.scale.linear();
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = 75 //only used for tickLabel currently
+        , height = 60 //only used for tickLabel currently
+        , axisLabelText = null
+        , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
+        , highlightZero = true
+        , rotateLabels = 0
+        , rotateYLabel = true
+        , staggerLabels = false
+        , isOrdinal = false
+        , ticks = null
+        , axisLabelDistance = 0
+        , duration = 250
+        , dispatch = d3.dispatch('renderEnd')
+        , axisRendered = false
+        , maxMinRendered = false
+        ;
+    axis
+        .scale(scale)
+        .orient('bottom')
+        .tickFormat(function(d) { return d })
+    ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var scale0;
+    var renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    function chart(selection) {
+        renderWatch.reset();
+        selection.each(function(data) {
+            var container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g')
+
+            if (ticks !== null)
+                axis.ticks(ticks);
+            else if (axis.orient() == 'top' || axis.orient() == 'bottom')
+                axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
+
+            //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
+            g.watchTransition(renderWatch, 'axis').call(axis);
+
+            scale0 = scale0 || axis.scale();
+
+            var fmt = axis.tickFormat();
+            if (fmt == null) {
+                fmt = scale0.tickFormat();
+            }
+
+            var axisLabel = g.selectAll('text.nv-axislabel')
+                .data([axisLabelText || null]);
+            axisLabel.exit().remove();
+
+            switch (axis.orient()) {
+                case 'top':
+                    axisLabel.enter().append('text').attr('class', 'nv-axislabel');
+                    var w;
+                    if (scale.range().length < 2) {
+                        w = 0;
+                    } else if (scale.range().length === 2) {
+                        w = scale.range()[1];
+                    } else {
+                        w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
+                    }
+                    axisLabel
+                        .attr('text-anchor', 'middle')
+                        .attr('y', 0)
+                        .attr('x', w/2);
+                    if (showMaxMin) {
+                        var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
+                            .data(scale.domain());
+                        axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
+                        axisMaxMin.exit().remove();
+                        axisMaxMin
+                            .attr('transform', function(d,i) {
+                                return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
+                            })
+                            .select('text')
+                            .attr('dy', '-0.5em')
+                            .attr('y', -axis.tickPadding())
+                            .attr('text-anchor', 'middle')
+                            .text(function(d,i) {
+                                var v = fmt(d);
+                                return ('' + v).match('NaN') ? '' : v;
+                            });
+                        axisMaxMin.watchTransition(renderWatch, 'min-max top')
+                            .attr('transform', function(d,i) {
+                                return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
+                            });
+                    }
+                    break;
+                case 'bottom':
+                    var xLabelMargin = axisLabelDistance + 36;
+                    var maxTextWidth = 30;
+                    var xTicks = g.selectAll('g').select("text");
+                    if (rotateLabels%360) {
+                        //Calculate the longest xTick width
+                        xTicks.each(function(d,i){
+                            var width = this.getBoundingClientRect().width;
+                            if(width > maxTextWidth) maxTextWidth = width;
+                        });
+                        //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
+                        var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
+                        var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
+                        //Rotate all xTicks
+                        xTicks
+                            .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
+                            .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
+                    }
+                    axisLabel.enter().append('text').attr('class', 'nv-axislabel');
+                    var w;
+                    if (scale.range().length < 2) {
+                        w = 0;
+                    } else if (scale.range().length === 2) {
+                        w = scale.range()[1];
+                    } else {
+                        w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
+                    }
+                    axisLabel
+                        .attr('text-anchor', 'middle')
+                        .attr('y', xLabelMargin)
+                        .attr('x', w/2);
+                    if (showMaxMin) {
+                        //if (showMaxMin && !isOrdinal) {
+                        var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
+                            //.data(scale.domain())
+                            .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
+                        axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
+                        axisMaxMin.exit().remove();
+                        axisMaxMin
+                            .attr('transform', function(d,i) {
+                                return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
+                            })
+                            .select('text')
+                            .attr('dy', '.71em')
+                            .attr('y', axis.tickPadding())
+                            .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
+                            .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
+                            .text(function(d,i) {
+                                var v = fmt(d);
+                                return ('' + v).match('NaN') ? '' : v;
+                            });
+                        axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
+                            .attr('transform', function(d,i) {
+                                return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
+                            });
+                    }
+                    if (staggerLabels)
+                        xTicks
+                            .attr('transform', function(d,i) { return 'translate(0,' + nv.utils.NaNtoZero((i % 2 == 0 ? '0' : '12')) + ')' });
+
+                    break;
+                case 'right':
+                    axisLabel.enter().append('text').attr('class', 'nv-axislabel');
+                    axisLabel
+                        .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
+                        .attr('transform', rotateYLabel ? 'rotate(90)' : '')
+                        .attr('y', rotateYLabel ? (-Math.max(margin.right,width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
+                        .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
+                    if (showMaxMin) {
+                        var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
+                            .data(scale.domain());
+                        axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
+                            .style('opacity', 0);
+                        axisMaxMin.exit().remove();
+                        axisMaxMin
+                            .attr('transform', function(d,i) {
+                                return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
+                            })
+                            .select('text')
+                            .attr('dy', '.32em')
+                            .attr('y', 0)
+                            .attr('x', axis.tickPadding())
+                            .style('text-anchor', 'start')
+                            .text(function(d,i) {
+                                var v = fmt(d);
+                                return ('' + v).match('NaN') ? '' : v;
+                            });
+                        axisMaxMin.watchTransition(renderWatch, 'min-max right')
+                            .attr('transform', function(d,i) {
+                                return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
+                            })
+                            .select('text')
+                            .style('opacity', 1);
+                    }
+                    break;
+                case 'left':
+                    /*
+                     //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
+                     var yTicks = g.selectAll('g').select("text");
+                     yTicks.each(function(d,i){
+                     var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
+                     if(labelPadding > width) width = labelPadding;
+                     });
+                     */
+                    axisLabel.enter().append('text').attr('class', 'nv-axislabel');
+                    axisLabel
+                        .style('text-anchor', rotateYLabel ? 'middle' : 'end')
+                        .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
+                        .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 25 - (axisLabelDistance || 0)) : -10)
+                        .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
+                    if (showMaxMin) {
+                        var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
+                            .data(scale.domain());
+                        axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
+                            .style('opacity', 0);
+                        axisMaxMin.exit().remove();
+                        axisMaxMin
+                            .attr('transform', function(d,i) {
+                                return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
+                            })
+                            .select('text')
+                            .attr('dy', '.32em')
+                            .attr('y', 0)
+                            .attr('x', -axis.tickPadding())
+                            .attr('text-anchor', 'end')
+                            .text(function(d,i) {
+                                var v = fmt(d);
+                                return ('' + v).match('NaN') ? '' : v;
+                            });
+                        axisMaxMin.watchTransition(renderWatch, 'min-max right')
+                            .attr('transform', function(d,i) {
+                                return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
+                            })
+                            .select('text')
+                            .style('opacity', 1);
+                    }
+                    break;
+            }
+            axisLabel.text(function(d) { return d });
+
+            if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
+                //check if max and min overlap other values, if so, hide the values that overlap
+                g.selectAll('g') // the g's wrapping each tick
+                    .each(function(d,i) {
+                        d3.select(this).select('text').attr('opacity', 1);
+                        if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it!
+                            if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
+                                d3.select(this).attr('opacity', 0);
+
+                            d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
+                        }
+                    });
+
+                //if Max and Min = 0 only show min, Issue #281
+                if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
+                    wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
+                        return !i ? 1 : 0
+                    });
+                }
+            }
+
+            if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
+                var maxMinRange = [];
+                wrap.selectAll('g.nv-axisMaxMin')
+                    .each(function(d,i) {
+                        try {
+                            if (i) // i== 1, max position
+                                maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4)  //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
+                            else // i==0, min position
+                                maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
+                        }catch (err) {
+                            if (i) // i== 1, max position
+                                maxMinRange.push(scale(d) - 4);  //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
+                            else // i==0, min position
+                                maxMinRange.push(scale(d) + 4);
+                        }
+                    });
+                // the g's wrapping each tick
+                g.selectAll('g').each(function(d,i) {
+                    if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
+                        if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
+                            d3.select(this).remove();
+                        else
+                            d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
+                    }
+                });
+            }
+
+            //highlight zero line ... Maybe should not be an option and should just be in CSS?
+            if (highlightZero) {
+                g.selectAll('.tick')
+                    .filter(function (d) {
+                        return !parseFloat(Math.round(this.__data__ * 100000) / 1000000) && (this.__data__ !== undefined)
+                    }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
+                    .classed('zero', true);
+            }
+            //store old scales for use in transitions on update
+            scale0 = scale.copy();
+
+        });
+
+        renderWatch.renderEnd('axis immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.axis = axis;
+    chart.dispatch = dispatch;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
+        staggerLabels:     {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
+        rotateLabels:      {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
+        rotateYLabel:      {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
+        highlightZero:     {get: function(){return highlightZero;}, set: function(_){highlightZero=_;}},
+        showMaxMin:        {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
+        axisLabel:         {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
+        height:            {get: function(){return height;}, set: function(_){height=_;}},
+        ticks:             {get: function(){return ticks;}, set: function(_){ticks=_;}},
+        width:             {get: function(){return width;}, set: function(_){width=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top !== undefined    ? _.top    : margin.top;
+            margin.right  = _.right !== undefined  ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left !== undefined   ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration=_;
+            renderWatch.reset(duration);
+        }},
+        scale: {get: function(){return scale;}, set: function(_){
+            scale = _;
+            axis.scale(scale);
+            isOrdinal = typeof scale.rangeBands === 'function';
+            nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+    nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
+    nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
+
+    return chart;
+};
+
+// Chart design based on the recommendations of Stephen Few. Implementation
+// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
+// http://projects.instantcognition.com/protovis/bulletchart/
+
+nv.models.bullet = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , orient = 'left' // TODO top & bottom
+        , reverse = false
+        , ranges = function(d) { return d.ranges }
+        , markers = function(d) { return d.markers ? d.markers : [0] }
+        , measures = function(d) { return d.measures }
+        , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
+        , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : []  }
+        , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : []  }
+        , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
+        , width = 380
+        , height = 30
+        , tickFormat = null
+        , color = nv.utils.getColor(['#1f77b4'])
+        , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
+        ;
+
+    function chart(selection) {
+        selection.each(function(d, i) {
+            var availableWidth = width - margin.left - margin.right,
+                availableHeight = height - margin.top - margin.bottom,
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
+                markerz = markers.call(this, d, i).slice().sort(d3.descending),
+                measurez = measures.call(this, d, i).slice().sort(d3.descending),
+                rangeLabelz = rangeLabels.call(this, d, i).slice(),
+                markerLabelz = markerLabels.call(this, d, i).slice(),
+                measureLabelz = measureLabels.call(this, d, i).slice();
+
+            // Setup Scales
+            // Compute the new x-scale.
+            var x1 = d3.scale.linear()
+                .domain( d3.extent(d3.merge([forceX, rangez])) )
+                .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
+
+            // Retrieve the old x-scale, if this is an update.
+            var x0 = this.__chart__ || d3.scale.linear()
+                .domain([0, Infinity])
+                .range(x1.range());
+
+            // Stash the new scale.
+            this.__chart__ = x1;
+
+            var rangeMin = d3.min(rangez), //rangez[2]
+                rangeMax = d3.max(rangez), //rangez[0]
+                rangeAvg = rangez[1];
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
+            gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
+            gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
+            gEnter.append('rect').attr('class', 'nv-measure');
+            gEnter.append('path').attr('class', 'nv-markerTriangle');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
+                w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
+            var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
+                xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
+
+            g.select('rect.nv-rangeMax')
+                .attr('height', availableHeight)
+                .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
+                .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
+                .datum(rangeMax > 0 ? rangeMax : rangeMin)
+
+            g.select('rect.nv-rangeAvg')
+                .attr('height', availableHeight)
+                .attr('width', w1(rangeAvg))
+                .attr('x', xp1(rangeAvg))
+                .datum(rangeAvg)
+
+            g.select('rect.nv-rangeMin')
+                .attr('height', availableHeight)
+                .attr('width', w1(rangeMax))
+                .attr('x', xp1(rangeMax))
+                .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
+                .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
+                .datum(rangeMax > 0 ? rangeMin : rangeMax)
+
+            g.select('rect.nv-measure')
+                .style('fill', color)
+                .attr('height', availableHeight / 3)
+                .attr('y', availableHeight / 3)
+                .attr('width', measurez < 0 ?
+                    x1(0) - x1(measurez[0])
+                    : x1(measurez[0]) - x1(0))
+                .attr('x', xp1(measurez))
+                .on('mouseover', function() {
+                    dispatch.elementMouseover({
+                        value: measurez[0],
+                        label: measureLabelz[0] || 'Current',
+                        pos: [x1(measurez[0]), availableHeight/2]
+                    })
+                })
+                .on('mouseout', function() {
+                    dispatch.elementMouseout({
+                        value: measurez[0],
+                        label: measureLabelz[0] || 'Current'
+                    })
+                });
+
+            var h3 =  availableHeight / 6;
+            if (markerz[0]) {
+                g.selectAll('path.nv-markerTriangle')
+                    .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
+                    .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
+                    .on('mouseover', function() {
+                        dispatch.elementMouseover({
+                            value: markerz[0],
+                            label: markerLabelz[0] || 'Previous',
+                            pos: [x1(markerz[0]), availableHeight/2]
+                        })
+                    })
+                    .on('mouseout', function() {
+                        dispatch.elementMouseout({
+                            value: markerz[0],
+                            label: markerLabelz[0] || 'Previous'
+                        })
+                    });
+            } else {
+                g.selectAll('path.nv-markerTriangle').remove();
+            }
+
+            wrap.selectAll('.nv-range')
+                .on('mouseover', function(d,i) {
+                    var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
+
+                    dispatch.elementMouseover({
+                        value: d,
+                        label: label,
+                        pos: [x1(d), availableHeight/2]
+                    })
+                })
+                .on('mouseout', function(d,i) {
+                    var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
+
+                    dispatch.elementMouseout({
+                        value: d,
+                        label: label
+                    })
+                });
+        });
+
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        ranges:      {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
+        markers:     {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
+        measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
+        forceX:      {get: function(){return forceX;}, set: function(_){forceX=_;}},
+        width:    {get: function(){return width;}, set: function(_){width=_;}},
+        height:    {get: function(){return height;}, set: function(_){height=_;}},
+        tickFormat:    {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
+            orient = _;
+            reverse = orient == 'right' || orient == 'bottom';
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+    return chart;
+};
+
+
+
+// Chart design based on the recommendations of Stephen Few. Implementation
+// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
+// http://projects.instantcognition.com/protovis/bulletchart/
+nv.models.bulletChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var bullet = nv.models.bullet()
+        ;
+
+    var orient = 'left' // TODO top & bottom
+        , reverse = false
+        , margin = {top: 5, right: 40, bottom: 20, left: 120}
+        , ranges = function(d) { return d.ranges }
+        , markers = function(d) { return d.markers ? d.markers : [0] }
+        , measures = function(d) { return d.measures }
+        , width = null
+        , height = 55
+        , tickFormat = null
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + x + '</h3>' +
+                '<p>' + y + '</p>'
+        }
+        , noData = 'No Data Available.'
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
+        ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
+            top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
+            content = tooltip(e.key, e.label, e.value, e, chart);
+
+        nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
+    };
+
+    function chart(selection) {
+        selection.each(function(d, i) {
+            var container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = height - margin.top - margin.bottom,
+                that = this;
+
+            chart.update = function() { chart(selection) };
+            chart.container = this;
+
+            // Display No Data message if there's nothing to show.
+            if (!d || !ranges.call(this, d, i)) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', 18 + margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
+                markerz = markers.call(this, d, i).slice().sort(d3.descending),
+                measurez = measures.call(this, d, i).slice().sort(d3.descending);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-bulletWrap');
+            gEnter.append('g').attr('class', 'nv-titles');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            // Compute the new x-scale.
+            var x1 = d3.scale.linear()
+                .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])  // TODO: need to allow forceX and forceY, and xDomain, yDomain
+                .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
+
+            // Retrieve the old x-scale, if this is an update.
+            var x0 = this.__chart__ || d3.scale.linear()
+                .domain([0, Infinity])
+                .range(x1.range());
+
+            // Stash the new scale.
+            this.__chart__ = x1;
+
+            var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
+                w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
+
+            var title = gEnter.select('.nv-titles').append('g')
+                .attr('text-anchor', 'end')
+                .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
+            title.append('text')
+                .attr('class', 'nv-title')
+                .text(function(d) { return d.title; });
+
+            title.append('text')
+                .attr('class', 'nv-subtitle')
+                .attr('dy', '1em')
+                .text(function(d) { return d.subtitle; });
+
+            bullet
+                .width(availableWidth)
+                .height(availableHeight)
+
+            var bulletWrap = g.select('.nv-bulletWrap');
+            d3.transition(bulletWrap).call(bullet);
+
+            // Compute the tick format.
+            var format = tickFormat || x1.tickFormat( availableWidth / 100 );
+
+            // Update the tick groups.
+            var tick = g.selectAll('g.nv-tick')
+                .data(x1.ticks( availableWidth / 50 ), function(d) {
+                    return this.textContent || format(d);
+                });
+
+            // Initialize the ticks with the old scale, x0.
+            var tickEnter = tick.enter().append('g')
+                .attr('class', 'nv-tick')
+                .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
+                .style('opacity', 1e-6);
+
+            tickEnter.append('line')
+                .attr('y1', availableHeight)
+                .attr('y2', availableHeight * 7 / 6);
+
+            tickEnter.append('text')
+                .attr('text-anchor', 'middle')
+                .attr('dy', '1em')
+                .attr('y', availableHeight * 7 / 6)
+                .text(format);
+
+            // Transition the updating ticks to the new scale, x1.
+            var tickUpdate = d3.transition(tick)
+                .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
+                .style('opacity', 1);
+
+            tickUpdate.select('line')
+                .attr('y1', availableHeight)
+                .attr('y2', availableHeight * 7 / 6);
+
+            tickUpdate.select('text')
+                .attr('y', availableHeight * 7 / 6);
+
+            // Transition the exiting ticks to the new scale, x1.
+            d3.transition(tick.exit())
+                .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
+                .style('opacity', 1e-6)
+                .remove();
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            dispatch.on('tooltipShow', function(e) {
+                e.key = d.title;
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+        });
+
+        d3.timer.flush();
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    bullet.dispatch.on('elementMouseover.tooltip', function(e) {
+        dispatch.tooltipShow(e);
+    });
+
+    bullet.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.bullet = bullet;
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        ranges:      {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
+        markers:     {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
+        measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
+        width:    {get: function(){return width;}, set: function(_){width=_;}},
+        height:    {get: function(){return height;}, set: function(_){height=_;}},
+        tickFormat:    {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
+            orient = _;
+            reverse = orient == 'right' || orient == 'bottom';
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, bullet);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+
+
+nv.models.cumulativeLineChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var lines = nv.models.line()
+        , xAxis = nv.models.axis()
+        , yAxis = nv.models.axis()
+        , legend = nv.models.legend()
+        , controls = nv.models.legend()
+        , interactiveLayer = nv.interactiveGuideline()
+        ;
+
+    var margin = {top: 30, right: 30, bottom: 50, left: 60}
+        , color = nv.utils.defaultColor()
+        , width = null
+        , height = null
+        , showLegend = true
+        , showXAxis = true
+        , showYAxis = true
+        , rightAlignYAxis = false
+        , tooltips = true
+        , showControls = true
+        , useInteractiveGuideline = false
+        , rescaleY = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + '</h3>' +
+                '<p>' +  y + ' at ' + x + '</p>'
+        }
+        , x //can be accessed via chart.xScale()
+        , y //can be accessed via chart.yScale()
+        , id = lines.id()
+        , state = nv.utils.state()
+        , defaultState = null
+        , noData = 'No Data Available.'
+        , average = function(d) { return d.average }
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
+        , transitionDuration = 250
+        , duration = 250
+        , noErrorCheck = false  //if set to TRUE, will bypass an error check in the indexify function.
+        ;
+
+    state.index = 0;
+    state.rescaleY = rescaleY;
+
+    xAxis
+        .orient('bottom')
+        .tickPadding(7)
+    ;
+    yAxis
+        .orient((rightAlignYAxis) ? 'right' : 'left')
+    ;
+
+    controls.updateState(false);
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var dx = d3.scale.linear()
+        , index = {i: 0, x: 0}
+        , renderWatch = nv.utils.renderWatch(dispatch, duration)
+        ;
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
+            y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, null, null, offsetElement);
+    };
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled }),
+                index: index.i,
+                rescaleY: rescaleY
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.index !== undefined)
+                index.i = state.index;
+            if (state.rescaleY !== undefined)
+                rescaleY = state.rescaleY;
+            if (state.active !== undefined)
+                data.forEach(function(series,i) {
+                    series.disabled = !state.active[i];
+                });
+        }
+    };
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(lines);
+        if (showXAxis) renderWatch.models(xAxis);
+        if (showYAxis) renderWatch.models(yAxis);
+        selection.each(function(data) {
+            var container = d3.select(this);
+            nv.utils.initSVG(container);
+            container.classed('nv-chart-' + id, true);
+            var that = this;
+
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+            chart.update = function() {
+                if (duration === 0)
+                    container.call(chart);
+                else
+                    container.transition().duration(duration).call(chart)
+            };
+            chart.container = this;
+
+            state
+                .setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            // DEPRECATED set state.disableddisabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            var indexDrag = d3.behavior.drag()
+                .on('dragstart', dragStart)
+                .on('drag', dragMove)
+                .on('dragend', dragEnd);
+
+
+            function dragStart(d,i) {
+                d3.select(chart.container)
+                    .style('cursor', 'ew-resize');
+            }
+
+            function dragMove(d,i) {
+                index.x = d3.event.x;
+                index.i = Math.round(dx.invert(index.x));
+                updateZero();
+            }
+
+            function dragEnd(d,i) {
+                d3.select(chart.container)
+                    .style('cursor', 'auto');
+
+                // update state and send stateChange with new index
+                state.index = index.i;
+                dispatch.stateChange(state);
+            }
+
+            // Display No Data message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            x = lines.xScale();
+            y = lines.yScale();
+
+            if (!rescaleY) {
+                var seriesDomains = data
+                    .filter(function(series) { return !series.disabled })
+                    .map(function(series,i) {
+                        var initialDomain = d3.extent(series.values, lines.y());
+
+                        //account for series being disabled when losing 95% or more
+                        if (initialDomain[0] < -.95) initialDomain[0] = -.95;
+
+                        return [
+                                (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
+                                (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
+                        ];
+                    });
+
+                var completeDomain = [
+                    d3.min(seriesDomains, function(d) { return d[0] }),
+                    d3.max(seriesDomains, function(d) { return d[1] })
+                ];
+
+                lines.yDomain(completeDomain);
+            } else {
+                lines.yDomain(null);
+            }
+
+            dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
+                .range([0, availableWidth])
+                .clamp(true);
+
+            var data = indexify(index.i, data);
+
+            // Setup containers and skeleton of chart
+            var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
+            var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-interactive');
+            gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
+            gEnter.append('g').attr('class', 'nv-y nv-axis');
+            gEnter.append('g').attr('class', 'nv-background');
+            gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
+            gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+            gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+            // Legend
+            if (showLegend) {
+                legend.width(availableWidth);
+
+                g.select('.nv-legendWrap')
+                    .datum(data)
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                g.select('.nv-legendWrap')
+                    .attr('transform', 'translate(0,' + (-margin.top) +')')
+            }
+
+            // Controls
+            if (showControls) {
+                var controlsData = [
+                    { key: 'Re-scale y-axis', disabled: !rescaleY }
+                ];
+
+                controls
+                    .width(140)
+                    .color(['#444', '#444', '#444'])
+                    .rightAlign(false)
+                    .margin({top: 5, right: 0, bottom: 5, left: 20})
+                ;
+
+                g.select('.nv-controlsWrap')
+                    .datum(controlsData)
+                    .attr('transform', 'translate(0,' + (-margin.top) +')')
+                    .call(controls);
+            }
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            if (rightAlignYAxis) {
+                g.select(".nv-y.nv-axis")
+                    .attr("transform", "translate(" + availableWidth + ",0)");
+            }
+
+            // Show error if series goes below 100%
+            var tempDisabled = data.filter(function(d) { return d.tempDisabled });
+
+            wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
+            if (tempDisabled.length) {
+                wrap.append('text').attr('class', 'tempDisabled')
+                    .attr('x', availableWidth / 2)
+                    .attr('y', '-.71em')
+                    .style('text-anchor', 'end')
+                    .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
+            }
+
+            //Set up interactive layer
+            if (useInteractiveGuideline) {
+                interactiveLayer
+                    .width(availableWidth)
+                    .height(availableHeight)
+                    .margin({left:margin.left,top:margin.top})
+                    .svgContainer(container)
+                    .xScale(x);
+                wrap.select(".nv-interactive").call(interactiveLayer);
+            }
+
+            gEnter.select('.nv-background')
+                .append('rect');
+
+            g.select('.nv-background rect')
+                .attr('width', availableWidth)
+                .attr('height', availableHeight);
+
+            lines
+                //.x(function(d) { return d.x })
+                .y(function(d) { return d.display.y })
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(data.map(function(d,i) {
+                    return d.color || color(d, i);
+                }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
+
+            var linesWrap = g.select('.nv-linesWrap')
+                .datum(data.filter(function(d) { return  !d.disabled && !d.tempDisabled }));
+
+            linesWrap.call(lines);
+
+            //Store a series index number in the data array.
+            data.forEach(function(d,i) {
+                d.seriesIndex = i;
+            });
+
+            var avgLineData = data.filter(function(d) {
+                return !d.disabled && !!average(d);
+            });
+
+            var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
+                .data(avgLineData, function(d) { return d.key; });
+
+            var getAvgLineY = function(d) {
+                //If average lines go off the svg element, clamp them to the svg bounds.
+                var yVal = y(average(d));
+                if (yVal < 0) return 0;
+                if (yVal > availableHeight) return availableHeight;
+                return yVal;
+            };
+
+            avgLines.enter()
+                .append('line')
+                .style('stroke-width',2)
+                .style('stroke-dasharray','10,10')
+                .style('stroke',function (d,i) {
+                    return lines.color()(d,d.seriesIndex);
+                })
+                .attr('x1',0)
+                .attr('x2',availableWidth)
+                .attr('y1', getAvgLineY)
+                .attr('y2', getAvgLineY);
+
+            avgLines
+                .style('stroke-opacity',function(d){
+                    //If average lines go offscreen, make them transparent
+                    var yVal = y(average(d));
+                    if (yVal < 0 || yVal > availableHeight) return 0;
+                    return 1;
+                })
+                .attr('x1',0)
+                .attr('x2',availableWidth)
+                .attr('y1', getAvgLineY)
+                .attr('y2', getAvgLineY);
+
+            avgLines.exit().remove();
+
+            //Create index line
+            var indexLine = linesWrap.selectAll('.nv-indexLine')
+                .data([index]);
+            indexLine.enter().append('rect').attr('class', 'nv-indexLine')
+                .attr('width', 3)
+                .attr('x', -2)
+                .attr('fill', 'red')
+                .attr('fill-opacity', .5)
+                .style("pointer-events","all")
+                .call(indexDrag);
+
+            indexLine
+                .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
+                .attr('height', availableHeight);
+
+            // Setup Axes
+            if (showXAxis) {
+                xAxis
+                    .scale(x)
+                    .ticks( nv.utils.calcTicksX(availableWidth/70, data) )
+                    .tickSize(-availableHeight, 0);
+
+                g.select('.nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + y.range()[0] + ')');
+                g.select('.nv-x.nv-axis')
+                    .call(xAxis);
+            }
+
+            if (showYAxis) {
+                yAxis
+                    .scale(y)
+                    .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
+                    .tickSize( -availableWidth, 0);
+
+                g.select('.nv-y.nv-axis')
+                    .call(yAxis);
+            }
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            function updateZero() {
+                indexLine
+                    .data([index]);
+
+                //When dragging the index line, turn off line transitions.
+                // Then turn them back on when done dragging.
+                var oldDuration = chart.duration();
+                chart.duration(0);
+                chart.update();
+                chart.duration(oldDuration);
+            }
+
+            g.select('.nv-background rect')
+                .on('click', function() {
+                    index.x = d3.mouse(this)[0];
+                    index.i = Math.round(dx.invert(index.x));
+
+                    // update state and send stateChange with new index
+                    state.index = index.i;
+                    dispatch.stateChange(state);
+
+                    updateZero();
+                });
+
+            lines.dispatch.on('elementClick', function(e) {
+                index.i = e.pointIndex;
+                index.x = dx(index.i);
+
+                // update state and send stateChange with new index
+                state.index = index.i;
+                dispatch.stateChange(state);
+
+                updateZero();
+            });
+
+            controls.dispatch.on('legendClick', function(d,i) {
+                d.disabled = !d.disabled;
+                rescaleY = !d.disabled;
+
+                state.rescaleY = rescaleY;
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState)
+                    state[key] = newState[key];
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            interactiveLayer.dispatch.on('elementMousemove', function(e) {
+                lines.clearHighlights();
+                var singlePoint, pointIndex, pointXLocation, allData = [];
+
+                data
+                    .filter(function(series, i) {
+                        series.seriesIndex = i;
+                        return !series.disabled;
+                    })
+                    .forEach(function(series,i) {
+                        pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
+                        lines.highlightPoint(i, pointIndex, true);
+                        var point = series.values[pointIndex];
+                        if (typeof point === 'undefined') return;
+                        if (typeof singlePoint === 'undefined') singlePoint = point;
+                        if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
+                        allData.push({
+                            key: series.key,
+                            value: chart.y()(point, pointIndex),
+                            color: color(series,series.seriesIndex)
+                        });
+                    });
+
+                //Highlight the tooltip entry based on which point the mouse is closest to.
+                if (allData.length > 2) {
+                    var yValue = chart.yScale().invert(e.mouseY);
+                    var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
+                    var threshold = 0.03 * domainExtent;
+                    var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
+                    if (indexToHighlight !== null)
+                        allData[indexToHighlight].highlight = true;
+                }
+
+                var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
+                interactiveLayer.tooltip
+                    .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
+                    .chartContainer(that.parentNode)
+                    .enabled(tooltips)
+                    .valueFormatter(function(d,i) {
+                        return yAxis.tickFormat()(d);
+                    })
+                    .data(
+                    {
+                        value: xValue,
+                        series: allData
+                    }
+                )();
+
+                interactiveLayer.renderGuideLine(pointXLocation);
+            });
+
+            interactiveLayer.dispatch.on("elementMouseout",function(e) {
+                dispatch.tooltipHide();
+                lines.clearHighlights();
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+            // Update chart from a state object passed to event handler
+            dispatch.on('changeState', function(e) {
+
+                if (typeof e.disabled !== 'undefined') {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+
+                    state.disabled = e.disabled;
+                }
+
+                if (typeof e.index !== 'undefined') {
+                    index.i = e.index;
+                    index.x = dx(index.i);
+
+                    state.index = e.index;
+
+                    indexLine
+                        .data([index]);
+                }
+
+                if (typeof e.rescaleY !== 'undefined') {
+                    rescaleY = e.rescaleY;
+                }
+
+                chart.update();
+            });
+
+        });
+
+        renderWatch.renderEnd('cumulativeLineChart immediate');
+
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    lines.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    lines.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Functions
+    //------------------------------------------------------------
+
+    var indexifyYGetter = null;
+    /* Normalize the data according to an index point. */
+    function indexify(idx, data) {
+        if (!indexifyYGetter) indexifyYGetter = lines.y();
+        return data.map(function(line, i) {
+            if (!line.values) {
+                return line;
+            }
+            var indexValue = line.values[idx];
+            if (indexValue == null) {
+                return line;
+            }
+            var v = indexifyYGetter(indexValue, idx);
+
+            //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
+            if (v < -.95 && !noErrorCheck) {
+                //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
+
+                line.tempDisabled = true;
+                return line;
+            }
+
+            line.tempDisabled = false;
+
+            line.values = line.values.map(function(point, pointIndex) {
+                point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
+                return point;
+            });
+
+            return line;
+        })
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.lines = lines;
+    chart.legend = legend;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+    chart.interactiveLayer = interactiveLayer;
+    chart.state = state;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        rescaleY:     {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
+        showControls:     {get: function(){return showControls;}, set: function(_){showControls=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        average: {get: function(){return average;}, set: function(_){average=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+        showXAxis:    {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
+        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
+        noErrorCheck:    {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+        }},
+        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
+            useInteractiveGuideline = _;
+            if (_ === true) {
+                chart.interactive(false);
+                chart.useVoronoi(false);
+            }
+        }},
+        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
+            rightAlignYAxis = _;
+            yAxis.orient( (_) ? 'right' : 'left');
+        }},
+        duration:    {get: function(){return duration;}, set: function(_){
+            duration = _;
+            lines.duration(duration);
+            xAxis.duration(duration);
+            yAxis.duration(duration);
+            renderWatch.reset(duration);
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, lines);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};//TODO: consider deprecating by adding necessary features to multiBar model
+nv.models.discreteBar = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = 960
+        , height = 500
+        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+        , x = d3.scale.ordinal()
+        , y = d3.scale.linear()
+        , getX = function(d) { return d.x }
+        , getY = function(d) { return d.y }
+        , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
+        , color = nv.utils.defaultColor()
+        , showValues = false
+        , valueFormat = d3.format(',.2f')
+        , xDomain
+        , yDomain
+        , xRange
+        , yRange
+        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
+        , rectClass = 'discreteBar'
+        , duration = 250
+        ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var x0, y0;
+    var renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    function chart(selection) {
+        renderWatch.reset();
+        selection.each(function(data) {
+            var availableWidth = width - margin.left - margin.right,
+                availableHeight = height - margin.top - margin.bottom,
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            //add series index to each data point for reference
+            data.forEach(function(series, i) {
+                series.values.forEach(function(point) {
+                    point.series = i;
+                });
+            });
+
+            // Setup Scales
+            // remap and flatten the data for use in calculating the scales' domains
+            var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
+                data.map(function(d) {
+                    return d.values.map(function(d,i) {
+                        return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
+                    })
+                });
+
+            x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
+                .rangeBands(xRange || [0, availableWidth], .1);
+            y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
+
+            // If showValues, pad the Y axis range to account for label height
+            if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
+            else y.range(yRange || [availableHeight, 0]);
+
+            //store old scales if they exist
+            x0 = x0 || x;
+            y0 = y0 || y.copy().range([y(0),y(0)]);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-groups');
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
+            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+                .data(function(d) { return d }, function(d) { return d.key });
+            groups.enter().append('g')
+                .style('stroke-opacity', 1e-6)
+                .style('fill-opacity', 1e-6);
+            groups.exit()
+                .watchTransition(renderWatch, 'discreteBar: exit groups')
+                .style('stroke-opacity', 1e-6)
+                .style('fill-opacity', 1e-6)
+                .remove();
+            groups
+                .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+                .classed('hover', function(d) { return d.hover });
+            groups
+                .watchTransition(renderWatch, 'discreteBar: groups')
+                .style('stroke-opacity', 1)
+                .style('fill-opacity', .75);
+
+            var bars = groups.selectAll('g.nv-bar')
+                .data(function(d) { return d.values });
+            bars.exit().remove();
+
+            var barsEnter = bars.enter().append('g')
+                .attr('transform', function(d,i,j) {
+                    return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
+                })
+                .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
+                    d3.select(this).classed('hover', true);
+                    dispatch.elementMouseover({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                })
+                .on('mouseout', function(d,i) {
+                    d3.select(this).classed('hover', false);
+                    dispatch.elementMouseout({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                })
+                .on('click', function(d,i) {
+                    dispatch.elementClick({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                    d3.event.stopPropagation();
+                })
+                .on('dblclick', function(d,i) {
+                    dispatch.elementDblClick({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                    d3.event.stopPropagation();
+                });
+
+            barsEnter.append('rect')
+                .attr('height', 0)
+                .attr('width', x.rangeBand() * .9 / data.length )
+
+            if (showValues) {
+                barsEnter.append('text')
+                    .attr('text-anchor', 'middle')
+                ;
+
+                bars.select('text')
+                    .text(function(d,i) { return valueFormat(getY(d,i)) })
+                    .watchTransition(renderWatch, 'discreteBar: bars text')
+                    .attr('x', x.rangeBand() * .9 / 2)
+                    .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
+
+                ;
+            } else {
+                bars.selectAll('text').remove();
+            }
+
+            bars
+                .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
+                .style('fill', function(d,i) { return d.color || color(d,i) })
+                .style('stroke', function(d,i) { return d.color || color(d,i) })
+                .select('rect')
+                .attr('class', rectClass)
+                .watchTransition(renderWatch, 'discreteBar: bars rect')
+                .attr('width', x.rangeBand() * .9 / data.length);
+            bars.watchTransition(renderWatch, 'discreteBar: bars')
+                //.delay(function(d,i) { return i * 1200 / data[0].values.length })
+                .attr('transform', function(d,i) {
+                    var left = x(getX(d,i)) + x.rangeBand() * .05,
+                        top = getY(d,i) < 0 ?
+                            y(0) :
+                                y(0) - y(getY(d,i)) < 1 ?
+                            y(0) - 1 : //make 1 px positive bars show up above y=0
+                            y(getY(d,i));
+
+                    return 'translate(' + left + ', ' + top + ')'
+                })
+                .select('rect')
+                .attr('height', function(d,i) {
+                    return  Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
+                });
+
+
+            //store old scales for use in transitions on update
+            x0 = x.copy();
+            y0 = y.copy();
+
+        });
+
+        renderWatch.renderEnd('discreteBar immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:   {get: function(){return width;}, set: function(_){width=_;}},
+        height:  {get: function(){return height;}, set: function(_){height=_;}},
+        forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
+        showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
+        x:       {get: function(){return getX;}, set: function(_){getX=_;}},
+        y:       {get: function(){return getY;}, set: function(_){getY=_;}},
+        xScale:  {get: function(){return x;}, set: function(_){x=_;}},
+        yScale:  {get: function(){return y;}, set: function(_){y=_;}},
+        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
+        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
+        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
+        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
+        valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
+        id:          {get: function(){return id;}, set: function(_){id=_;}},
+        rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.models.discreteBarChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var discretebar = nv.models.discreteBar()
+        , xAxis = nv.models.axis()
+        , yAxis = nv.models.axis()
+        ;
+
+    var margin = {top: 15, right: 10, bottom: 50, left: 60}
+        , width = null
+        , height = null
+        , color = nv.utils.getColor()
+        , showXAxis = true
+        , showYAxis = true
+        , rightAlignYAxis = false
+        , staggerLabels = false
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + x + '</h3>' +
+                '<p>' +  y + '</p>'
+        }
+        , x
+        , y
+        , noData = "No Data Available."
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate','renderEnd')
+        , duration = 250
+        ;
+
+    xAxis
+        .orient('bottom')
+        .highlightZero(false)
+        .showMaxMin(false)
+        .tickFormat(function(d) { return d })
+    ;
+    yAxis
+        .orient((rightAlignYAxis) ? 'right' : 'left')
+        .tickFormat(d3.format(',.1f'))
+    ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
+            y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+    };
+
+    var renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(discretebar);
+        if (showXAxis) renderWatch.models(xAxis);
+        if (showYAxis) renderWatch.models(yAxis);
+
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+            chart.update = function() {
+                dispatch.beforeUpdate();
+                container.transition().duration(duration).call(chart);
+            };
+            chart.container = this;
+
+            // Display No Data message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            x = discretebar.xScale();
+            y = discretebar.yScale().clamp(true);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
+            var defsEnter = gEnter.append('defs');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-x nv-axis');
+            gEnter.append('g').attr('class', 'nv-y nv-axis')
+                .append('g').attr('class', 'nv-zeroLine')
+                .append('line');
+
+            gEnter.append('g').attr('class', 'nv-barsWrap');
+
+            g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            if (rightAlignYAxis) {
+                g.select(".nv-y.nv-axis")
+                    .attr("transform", "translate(" + availableWidth + ",0)");
+            }
+
+            // Main Chart Component(s)
+            discretebar
+                .width(availableWidth)
+                .height(availableHeight);
+
+            var barsWrap = g.select('.nv-barsWrap')
+                .datum(data.filter(function(d) { return !d.disabled }))
+
+            barsWrap.transition().call(discretebar);
+
+
+            defsEnter.append('clipPath')
+                .attr('id', 'nv-x-label-clip-' + discretebar.id())
+                .append('rect');
+
+            g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
+                .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
+                .attr('height', 16)
+                .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
+
+            // Setup Axes
+            if (showXAxis) {
+                xAxis
+                    .scale(x)
+                    .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                    .tickSize(-availableHeight, 0);
+
+                g.select('.nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
+                g.select('.nv-x.nv-axis').call(xAxis);
+
+                var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
+                if (staggerLabels) {
+                    xTicks
+                        .selectAll('text')
+                        .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
+                }
+            }
+
+            if (showYAxis) {
+                yAxis
+                    .scale(y)
+                    .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
+                    .tickSize( -availableWidth, 0);
+
+                g.select('.nv-y.nv-axis').call(yAxis);
+            }
+
+            // Zero line
+            g.select(".nv-zeroLine line")
+                .attr("x1",0)
+                .attr("x2",availableWidth)
+                .attr("y1", y(0))
+                .attr("y2", y(0))
+            ;
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+        });
+
+        renderWatch.renderEnd('discreteBar chart immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.discretebar = discretebar;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
+        showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
+        showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+            discretebar.duration(duration);
+            xAxis.duration(duration);
+            yAxis.duration(duration);
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            discretebar.color(color);
+        }},
+        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
+            rightAlignYAxis = _;
+            yAxis.orient( (_) ? 'right' : 'left');
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, discretebar);
+    nv.utils.initOptions(chart);
+
+    return chart;
+}
+
+nv.models.distribution = function() {
+    "use strict";
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = 400 //technically width or height depending on x or y....
+        , size = 8
+        , axis = 'x' // 'x' or 'y'... horizontal or vertical
+        , getData = function(d) { return d[axis] }  // defaults d.x or d.y
+        , color = nv.utils.defaultColor()
+        , scale = d3.scale.linear()
+        , domain
+        , duration = 250
+        , dispatch = d3.dispatch('renderEnd')
+        ;
+
+    //============================================================
+
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var scale0;
+    var renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    //============================================================
+
+
+    function chart(selection) {
+        renderWatch.reset();
+        selection.each(function(data) {
+            var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
+                naxis = axis == 'x' ? 'y' : 'x',
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            //------------------------------------------------------------
+            // Setup Scales
+
+            scale0 = scale0 || scale;
+
+            //------------------------------------------------------------
+
+
+            //------------------------------------------------------------
+            // Setup containers and skeleton of chart
+
+            var wrap = container.selectAll('g.nv-distribution').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
+
+            //------------------------------------------------------------
+
+
+            var distWrap = g.selectAll('g.nv-dist')
+                .data(function(d) { return d }, function(d) { return d.key });
+
+            distWrap.enter().append('g');
+            distWrap
+                .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
+                .style('stroke', function(d,i) { return color(d, i) });
+
+            var dist = distWrap.selectAll('line.nv-dist' + axis)
+                .data(function(d) { return d.values })
+            dist.enter().append('line')
+                .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
+                .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
+            renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
+                // .transition()
+                .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
+                .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
+                .style('stroke-opacity', 0)
+                .remove();
+            dist
+                .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
+                .attr(naxis + '1', 0)
+                .attr(naxis + '2', size);
+            renderWatch.transition(dist, 'dist')
+                // .transition()
+                .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
+                .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
+
+
+            scale0 = scale.copy();
+
+        });
+        renderWatch.renderEnd('distribution immediate');
+        return chart;
+    }
+
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+    chart.options = nv.utils.optionsFunc.bind(chart);
+    chart.dispatch = dispatch;
+
+    chart.margin = function(_) {
+        if (!arguments.length) return margin;
+        margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
+        margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
+        margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+        margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
+        return chart;
+    };
+
+    chart.width = function(_) {
+        if (!arguments.length) return width;
+        width = _;
+        return chart;
+    };
+
+    chart.axis = function(_) {
+        if (!arguments.length) return axis;
+        axis = _;
+        return chart;
+    };
+
+    chart.size = function(_) {
+        if (!arguments.length) return size;
+        size = _;
+        return chart;
+    };
+
+    chart.getData = function(_) {
+        if (!arguments.length) return getData;
+        getData = d3.functor(_);
+        return chart;
+    };
+
+    chart.scale = function(_) {
+        if (!arguments.length) return scale;
+        scale = _;
+        return chart;
+    };
+
+    chart.color = function(_) {
+        if (!arguments.length) return color;
+        color = nv.utils.getColor(_);
+        return chart;
+    };
+
+    chart.duration = function(_) {
+        if (!arguments.length) return duration;
+        duration = _;
+        renderWatch.reset(duration);
+        return chart;
+    };
+    //============================================================
+
+
+    return chart;
+}
+//TODO: consider deprecating and using multibar with single series for this
+nv.models.historicalBar = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = null
+        , height = null
+        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+        , x = d3.scale.linear()
+        , y = d3.scale.linear()
+        , getX = function(d) { return d.x }
+        , getY = function(d) { return d.y }
+        , forceX = []
+        , forceY = [0]
+        , padData = false
+        , clipEdge = true
+        , color = nv.utils.defaultColor()
+        , xDomain
+        , yDomain
+        , xRange
+        , yRange
+        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
+        , interactive = true
+        ;
+
+    var renderWatch = nv.utils.renderWatch(dispatch, 0);
+
+    function chart(selection) {
+        selection.each(function(data) {
+            renderWatch.reset();
+
+            var container = d3.select(this);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                - margin.left - margin.right;
+            var availableHeight = (height || parseInt(container.style('height')) || 400)
+                - margin.top - margin.bottom;
+
+            nv.utils.initSVG(container);
+
+            // Setup Scales
+            x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
+
+            if (padData)
+                x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
+            else
+                x.range(xRange || [0, availableWidth]);
+
+            y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
+                .range(yRange || [availableHeight, 0]);
+
+            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
+            if (x.domain()[0] === x.domain()[1])
+                x.domain()[0] ?
+                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
+                    : x.domain([-1,1]);
+
+            if (y.domain()[0] === y.domain()[1])
+                y.domain()[0] ?
+                    y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
+                    : y.domain([-1,1]);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
+            var defsEnter = wrapEnter.append('defs');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-bars');
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            container
+                .on('click', function(d,i) {
+                    dispatch.chartClick({
+                        data: d,
+                        index: i,
+                        pos: d3.event,
+                        id: id
+                    });
+                });
+
+            defsEnter.append('clipPath')
+                .attr('id', 'nv-chart-clip-path-' + id)
+                .append('rect');
+
+            wrap.select('#nv-chart-clip-path-' + id + ' rect')
+                .attr('width', availableWidth)
+                .attr('height', availableHeight);
+
+            g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
+
+            var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
+                .data(function(d) { return d }, function(d,i) {return getX(d,i)});
+            bars.exit().remove();
+
+            var barsEnter = bars.enter().append('rect')
+                .attr('x', 0 )
+                .attr('y', function(d,i) {  return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
+                .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
+                .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
+                .on('mouseover', function(d,i) {
+                    if (!interactive) return;
+                    d3.select(this).classed('hover', true);
+                    dispatch.elementMouseover({
+                        point: d,
+                        series: data[0],
+                        pos: [x(getX(d,i)), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: 0,
+                        e: d3.event
+                    });
+
+                })
+                .on('mouseout', function(d,i) {
+                    if (!interactive) return;
+                    d3.select(this).classed('hover', false);
+                    dispatch.elementMouseout({
+                        point: d,
+                        series: data[0],
+                        pointIndex: i,
+                        seriesIndex: 0,
+                        e: d3.event
+                    });
+                })
+                .on('click', function(d,i) {
+                    if (!interactive) return;
+                    dispatch.elementClick({
+                        //label: d[label],
+                        value: getY(d,i),
+                        data: d,
+                        index: i,
+                        pos: [x(getX(d,i)), y(getY(d,i))],
+                        e: d3.event,
+                        id: id
+                    });
+                    d3.event.stopPropagation();
+                })
+                .on('dblclick', function(d,i) {
+                    if (!interactive) return;
+                    dispatch.elementDblClick({
+                        //label: d[label],
+                        value: getY(d,i),
+                        data: d,
+                        index: i,
+                        pos: [x(getX(d,i)), y(getY(d,i))],
+                        e: d3.event,
+                        id: id
+                    });
+                    d3.event.stopPropagation();
+                });
+
+            bars
+                .attr('fill', function(d,i) { return color(d, i); })
+                .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
+                .watchTransition(renderWatch, 'bars')
+                .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
+                //TODO: better width calculations that don't assume always uniform data spacing;w
+                .attr('width', (availableWidth / data[0].values.length) * .9 );
+
+            bars.watchTransition(renderWatch, 'bars')
+                .attr('y', function(d,i) {
+                    var rval = getY(d,i) < 0 ?
+                        y(0) :
+                            y(0) - y(getY(d,i)) < 1 ?
+                        y(0) - 1 :
+                        y(getY(d,i));
+                    return nv.utils.NaNtoZero(rval);
+                })
+                .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
+
+        });
+
+        renderWatch.renderEnd('historicalBar immediate');
+        return chart;
+    }
+
+    //Create methods to allow outside functions to highlight a specific bar.
+    chart.highlightPoint = function(pointIndex, isHoverOver) {
+        d3.select(".nv-historicalBar-" + id)
+            .select(".nv-bars .nv-bar-0-" + pointIndex)
+            .classed("hover", isHoverOver)
+        ;
+    };
+
+    chart.clearHighlights = function() {
+        d3.select(".nv-historicalBar-" + id)
+            .select(".nv-bars .nv-bar.hover")
+            .classed("hover", false)
+        ;
+    };
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:   {get: function(){return width;}, set: function(_){width=_;}},
+        height:  {get: function(){return height;}, set: function(_){height=_;}},
+        forceX:  {get: function(){return forceX;}, set: function(_){forceX=_;}},
+        forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
+        padData: {get: function(){return padData;}, set: function(_){padData=_;}},
+        x:       {get: function(){return getX;}, set: function(_){getX=_;}},
+        y:       {get: function(){return getY;}, set: function(_){getY=_;}},
+        xScale:  {get: function(){return x;}, set: function(_){x=_;}},
+        yScale:  {get: function(){return y;}, set: function(_){y=_;}},
+        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
+        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
+        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
+        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
+        clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
+        id:          {get: function(){return id;}, set: function(_){id=_;}},
+        interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.models.historicalBarChart = function(bar_model) {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var bars = bar_model || nv.models.historicalBar()
+        , xAxis = nv.models.axis()
+        , yAxis = nv.models.axis()
+        , legend = nv.models.legend()
+        , interactiveLayer = nv.interactiveGuideline()
+        ;
+
+
+    var margin = {top: 30, right: 90, bottom: 50, left: 90}
+        , color = nv.utils.defaultColor()
+        , width = null
+        , height = null
+        , showLegend = false
+        , showXAxis = true
+        , showYAxis = true
+        , rightAlignYAxis = false
+        , useInteractiveGuideline = false
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + '</h3>' +
+                '<p>' +  y + ' at ' + x + '</p>'
+        }
+        , x
+        , y
+        , state = {}
+        , defaultState = null
+        , noData = 'No Data Available.'
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
+        , transitionDuration = 250
+        ;
+
+    xAxis
+        .orient('bottom')
+        .tickPadding(7)
+    ;
+    yAxis
+        .orient( (rightAlignYAxis) ? 'right' : 'left')
+    ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var showTooltip = function(e, offsetElement) {
+
+        // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
+        if (offsetElement) {
+            var svg = d3.select(offsetElement).select('svg');
+            var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
+            if (viewBox) {
+                viewBox = viewBox.split(' ');
+                var ratio = parseInt(svg.style('width')) / viewBox[2];
+                e.pos[0] = e.pos[0] * ratio;
+                e.pos[1] = e.pos[1] * ratio;
+            }
+        }
+
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
+            y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, null, null, offsetElement);
+    };
+    var renderWatch = nv.utils.renderWatch(dispatch, 0);
+
+    function chart(selection) {
+        selection.each(function(data) {
+            renderWatch.reset();
+            renderWatch.models(bars);
+            if (showXAxis) renderWatch.models(xAxis);
+            if (showYAxis) renderWatch.models(yAxis);
+
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+
+            chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
+            chart.container = this;
+
+            //set state.disabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display noData message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            x = bars.xScale();
+            y = bars.yScale();
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-x nv-axis');
+            gEnter.append('g').attr('class', 'nv-y nv-axis');
+            gEnter.append('g').attr('class', 'nv-barsWrap');
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+            gEnter.append('g').attr('class', 'nv-interactive');
+
+            // Legend
+            if (showLegend) {
+                legend.width(availableWidth);
+
+                g.select('.nv-legendWrap')
+                    .datum(data)
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                wrap.select('.nv-legendWrap')
+                    .attr('transform', 'translate(0,' + (-margin.top) +')')
+            }
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            if (rightAlignYAxis) {
+                g.select(".nv-y.nv-axis")
+                    .attr("transform", "translate(" + availableWidth + ",0)");
+            }
+
+            //Set up interactive layer
+            if (useInteractiveGuideline) {
+                interactiveLayer
+                    .width(availableWidth)
+                    .height(availableHeight)
+                    .margin({left:margin.left, top:margin.top})
+                    .svgContainer(container)
+                    .xScale(x);
+                wrap.select(".nv-interactive").call(interactiveLayer);
+            }
+            bars
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(data.map(function(d,i) {
+                    return d.color || color(d, i);
+                }).filter(function(d,i) { return !data[i].disabled }));
+
+            var barsWrap = g.select('.nv-barsWrap')
+                .datum(data.filter(function(d) { return !d.disabled }));
+            barsWrap.transition().call(bars);
+
+            // Setup Axes
+            if (showXAxis) {
+                xAxis
+                    .scale(x)
+                    .tickSize(-availableHeight, 0);
+
+                g.select('.nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + y.range()[0] + ')');
+                g.select('.nv-x.nv-axis')
+                    .transition()
+                    .call(xAxis);
+            }
+
+            if (showYAxis) {
+                yAxis
+                    .scale(y)
+                    .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
+                    .tickSize( -availableWidth, 0);
+
+                g.select('.nv-y.nv-axis')
+                    .transition()
+                    .call(yAxis);
+            }
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            interactiveLayer.dispatch.on('elementMousemove', function(e) {
+                bars.clearHighlights();
+
+                var singlePoint, pointIndex, pointXLocation, allData = [];
+                data
+                    .filter(function(series, i) {
+                        series.seriesIndex = i;
+                        return !series.disabled;
+                    })
+                    .forEach(function(series,i) {
+                        pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
+                        bars.highlightPoint(pointIndex,true);
+                        var point = series.values[pointIndex];
+                        if (typeof point === 'undefined') return;
+                        if (typeof singlePoint === 'undefined') singlePoint = point;
+                        if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
+                        allData.push({
+                            key: series.key,
+                            value: chart.y()(point, pointIndex),
+                            color: color(series,series.seriesIndex),
+                            data: series.values[pointIndex]
+                        });
+                    });
+
+                var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
+                interactiveLayer.tooltip
+                    .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
+                    .chartContainer(that.parentNode)
+                    .enabled(tooltips)
+                    .valueFormatter(function(d,i) {
+                        return yAxis.tickFormat()(d);
+                    })
+                    .data(
+                    {
+                        value: xValue,
+                        series: allData
+                    }
+                )();
+
+                interactiveLayer.renderGuideLine(pointXLocation);
+
+            });
+
+            interactiveLayer.dispatch.on("elementMouseout",function(e) {
+                dispatch.tooltipHide();
+                bars.clearHighlights();
+            });
+
+            legend.dispatch.on('legendClick', function(d,i) {
+                d.disabled = !d.disabled;
+
+                if (!data.filter(function(d) { return !d.disabled }).length) {
+                    data.map(function(d) {
+                        d.disabled = false;
+                        wrap.selectAll('.nv-series').classed('disabled', false);
+                        return d;
+                    });
+                }
+
+                state.disabled = data.map(function(d) { return !!d.disabled });
+                dispatch.stateChange(state);
+
+                selection.transition().call(chart);
+            });
+
+            legend.dispatch.on('legendDblclick', function(d) {
+                //Double clicking should always enable current series, and disabled all others.
+                data.forEach(function(d) {
+                    d.disabled = true;
+                });
+                d.disabled = false;
+
+                state.disabled = data.map(function(d) { return !!d.disabled });
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+            dispatch.on('changeState', function(e) {
+
+                if (typeof e.disabled !== 'undefined') {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+
+                    state.disabled = e.disabled;
+                }
+
+                chart.update();
+            });
+        });
+
+        renderWatch.renderEnd('historicalBarChart immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    bars.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    bars.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.bars = bars;
+    chart.legend = legend;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+    chart.interactiveLayer = interactiveLayer;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
+        showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+            bars.color(color);
+        }},
+        duration:    {get: function(){return transitionDuration;}, set: function(_){
+            transitionDuration=_;
+            renderWatch.reset(transitionDuration);
+            yAxis.duration(transitionDuration);
+            xAxis.duration(transitionDuration);
+        }},
+        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
+            rightAlignYAxis = _;
+            yAxis.orient( (_) ? 'right' : 'left');
+        }},
+        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
+            useInteractiveGuideline = _;
+            if (_ === true) {
+                chart.interactive(false);
+            }
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, bars);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+
+// ohlcChart is just a historical chart with oclc bars and some tweaks
+nv.models.ohlcBarChart = function() {
+    var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
+
+    // special default tooltip since we show multiple values per x
+    chart.useInteractiveGuideline(true);
+    chart.interactiveLayer.tooltip.contentGenerator(function(data) {
+        // we assume only one series exists for this chart
+        var d = data.series[0].data;
+        // match line colors as defined in nv.d3.css
+        var color = d.open < d.close ? "2ca02c" : "d62728";
+        return '' +
+            '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
+            '<table>' +
+            '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
+            '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
+            '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
+            '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
+            '</table>';
+    });
+    return chart;
+};nv.models.legend = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 5, right: 0, bottom: 5, left: 0}
+        , width = 400
+        , height = 20
+        , getKey = function(d) { return d.key }
+        , color = nv.utils.defaultColor()
+        , align = true
+        , rightAlign = true
+        , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
+        , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
+        , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
+        ;
+
+    function chart(selection) {
+        selection.each(function(data) {
+            var availableWidth = width - margin.left - margin.right,
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-legend').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
+            var g = wrap.select('g');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            var series = g.selectAll('.nv-series')
+                .data(function(d) { return d });
+            var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
+                .on('mouseover', function(d,i) {
+                    dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
+                })
+                .on('mouseout', function(d,i) {
+                    dispatch.legendMouseout(d,i);
+                })
+                .on('click', function(d,i) {
+                    dispatch.legendClick(d,i);
+                    if (updateState) {
+                        if (radioButtonMode) {
+                            //Radio button mode: set every series to disabled,
+                            //  and enable the clicked series.
+                            data.forEach(function(series) { series.disabled = true});
+                            d.disabled = false;
+                        }
+                        else {
+                            d.disabled = !d.disabled;
+                            if (data.every(function(series) { return series.disabled})) {
+                                //the default behavior of NVD3 legends is, if every single series
+                                // is disabled, turn all series' back on.
+                                data.forEach(function(series) { series.disabled = false});
+                            }
+                        }
+                        dispatch.stateChange({
+                            disabled: data.map(function(d) { return !!d.disabled })
+                        });
+                    }
+                })
+                .on('dblclick', function(d,i) {
+                    dispatch.legendDblclick(d,i);
+                    if (updateState) {
+                        //the default behavior of NVD3 legends, when double clicking one,
+                        // is to set all other series' to false, and make the double clicked series enabled.
+                        data.forEach(function(series) {
+                            series.disabled = true;
+                        });
+                        d.disabled = false;
+                        dispatch.stateChange({
+                            disabled: data.map(function(d) { return !!d.disabled })
+                        });
+                    }
+                });
+            seriesEnter.append('circle')
+                .style('stroke-width', 2)
+                .attr('class','nv-legend-symbol')
+                .attr('r', 5);
+            seriesEnter.append('text')
+                .attr('text-anchor', 'start')
+                .attr('class','nv-legend-text')
+                .attr('dy', '.32em')
+                .attr('dx', '8');
+            series.classed('nv-disabled', function(d) { return d.disabled });
+            series.exit().remove();
+            series.select('circle')
+                .style('fill', function(d,i) { return d.color || color(d,i)})
+                .style('stroke', function(d,i) { return d.color || color(d, i) });
+            series.select('text').text(getKey);
+
+            //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
+            // NEW ALIGNING CODE, TODO: clean up
+            if (align) {
+
+                var seriesWidths = [];
+                series.each(function(d,i) {
+                    var legendText = d3.select(this).select('text');
+                    var nodeTextLength;
+                    try {
+                        nodeTextLength = legendText.node().getComputedTextLength();
+                        // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
+                        if(nodeTextLength <= 0) throw Error();
+                    }
+                    catch(e) {
+                        nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
+                    }
+
+                    seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
+                });
+
+                var seriesPerRow = 0;
+                var legendWidth = 0;
+                var columnWidths = [];
+
+                while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
+                    columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
+                    legendWidth += seriesWidths[seriesPerRow++];
+                }
+                if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
+
+                while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
+                    columnWidths = [];
+                    seriesPerRow--;
+
+                    for (var k = 0; k < seriesWidths.length; k++) {
+                        if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
+                            columnWidths[k % seriesPerRow] = seriesWidths[k];
+                    }
+
+                    legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
+                        return prev + cur;
+                    });
+                }
+
+                var xPositions = [];
+                for (var i = 0, curX = 0; i < seriesPerRow; i++) {
+                    xPositions[i] = curX;
+                    curX += columnWidths[i];
+                }
+
+                series
+                    .attr('transform', function(d, i) {
+                        return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
+                    });
+
+                //position legend as far right as possible within the total width
+                if (rightAlign) {
+                    g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
+                }
+                else {
+                    g.attr('transform', 'translate(0' + ',' + margin.top + ')');
+                }
+
+                height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
+
+            } else {
+
+                var ypos = 5,
+                    newxpos = 5,
+                    maxwidth = 0,
+                    xpos;
+                series
+                    .attr('transform', function(d, i) {
+                        var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
+                        xpos = newxpos;
+
+                        if (width < margin.left + margin.right + xpos + length) {
+                            newxpos = xpos = 5;
+                            ypos += 20;
+                        }
+
+                        newxpos += length;
+                        if (newxpos > maxwidth) maxwidth = newxpos;
+
+                        return 'translate(' + xpos + ',' + ypos + ')';
+                    });
+
+                //position legend as far right as possible within the total width
+                g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
+
+                height = margin.top + margin.bottom + ypos + 15;
+            }
+        });
+
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
+        align:      {get: function(){return align;}, set: function(_){align=_;}},
+        rightAlign:    {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
+        updateState:    {get: function(){return updateState;}, set: function(_){updateState=_;}},
+        radioButtonMode:    {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.models.line = function() {
+    "use strict";
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var  scatter = nv.models.scatter()
+        ;
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = 960
+        , height = 500
+        , color = nv.utils.defaultColor() // a function that returns a color
+        , getX = function(d) { return d.x } // accessor to get the x value from a data point
+        , getY = function(d) { return d.y } // accessor to get the y value from a data point
+        , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined
+        , isArea = function(d) { return d.area } // decides if a line is an area or just a line
+        , clipEdge = false // if true, masks lines within x and y scale
+        , x //can be accessed via chart.xScale()
+        , y //can be accessed via chart.yScale()
+        , interpolate = "linear" // controls the line interpolation
+        , duration = 250
+        , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
+        ;
+
+    scatter
+        .pointSize(16) // default size
+        .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
+    ;
+
+    //============================================================
+
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var x0, y0 //used to store previous scales
+        , renderWatch = nv.utils.renderWatch(dispatch, duration)
+        ;
+
+    //============================================================
+
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(scatter);
+        selection.each(function(data) {
+            var availableWidth = width - margin.left - margin.right,
+                availableHeight = height - margin.top - margin.bottom,
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            // Setup Scales
+            x = scatter.xScale();
+            y = scatter.yScale();
+
+            x0 = x0 || x;
+            y0 = y0 || y;
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
+            var defsEnter = wrapEnter.append('defs');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-groups');
+            gEnter.append('g').attr('class', 'nv-scatterWrap');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            scatter
+                .width(availableWidth)
+                .height(availableHeight);
+
+            var scatterWrap = wrap.select('.nv-scatterWrap');
+            scatterWrap.call(scatter);
+
+            defsEnter.append('clipPath')
+                .attr('id', 'nv-edge-clip-' + scatter.id())
+                .append('rect');
+
+            wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
+                .attr('width', availableWidth)
+                .attr('height', (availableHeight > 0) ? availableHeight : 0);
+
+            g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
+            scatterWrap
+                .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
+
+            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+                .data(function(d) { return d }, function(d) { return d.key });
+            groups.enter().append('g')
+                .style('stroke-opacity', 1e-6)
+                .style('fill-opacity', 1e-6);
+
+            groups.exit().remove();
+
+            groups
+                .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+                .classed('hover', function(d) { return d.hover })
+                .style('fill', function(d,i){ return color(d, i) })
+                .style('stroke', function(d,i){ return color(d, i)});
+            groups.watchTransition(renderWatch, 'line: groups')
+                .style('stroke-opacity', 1)
+                .style('fill-opacity', .5);
+
+            var areaPaths = groups.selectAll('path.nv-area')
+                .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
+            areaPaths.enter().append('path')
+                .attr('class', 'nv-area')
+                .attr('d', function(d) {
+                    return d3.svg.area()
+                        .interpolate(interpolate)
+                        .defined(defined)
+                        .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
+                        .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
+                        .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
+                        //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
+                        .apply(this, [d.values])
+                });
+            groups.exit().selectAll('path.nv-area')
+                .remove();
+
+            areaPaths.watchTransition(renderWatch, 'line: areaPaths')
+                .attr('d', function(d) {
+                    return d3.svg.area()
+                        .interpolate(interpolate)
+                        .defined(defined)
+                        .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
+                        .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
+                        .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
+                        //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
+                        .apply(this, [d.values])
+                });
+
+            var linePaths = groups.selectAll('path.nv-line')
+                .data(function(d) { return [d.values] });
+            linePaths.enter().append('path')
+                .attr('class', 'nv-line')
+                .attr('d',
+                    d3.svg.line()
+                    .interpolate(interpolate)
+                    .defined(defined)
+                    .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
+                    .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
+            );
+
+            linePaths.watchTransition(renderWatch, 'line: linePaths')
+                .attr('d',
+                    d3.svg.line()
+                    .interpolate(interpolate)
+                    .defined(defined)
+                    .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
+                    .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
+            );
+
+            //store old scales for use in transitions on update
+            x0 = x.copy();
+            y0 = y.copy();
+        });
+        renderWatch.renderEnd('line immediate');
+        return chart;
+    }
+
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.scatter = scatter;
+    // Pass through events
+    scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); })
+    scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); })
+    scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); })
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        defined: {get: function(){return defined;}, set: function(_){defined=_;}},
+        interpolate:      {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
+        clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+            scatter.duration(duration);
+        }},
+        isArea: {get: function(){return isArea;}, set: function(_){
+            isArea = d3.functor(_);
+        }},
+        x: {get: function(){return getX;}, set: function(_){
+            getX = _;
+            scatter.x(_);
+        }},
+        y: {get: function(){return getY;}, set: function(_){
+            getY = _;
+            scatter.y(_);
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            scatter.color(color);
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, scatter);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+nv.models.lineChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var lines = nv.models.line()
+        , xAxis = nv.models.axis()
+        , yAxis = nv.models.axis()
+        , legend = nv.models.legend()
+        , interactiveLayer = nv.interactiveGuideline()
+        ;
+
+    var margin = {top: 30, right: 20, bottom: 50, left: 60}
+        , color = nv.utils.defaultColor()
+        , width = null
+        , height = null
+        , showLegend = true
+        , showXAxis = true
+        , showYAxis = true
+        , rightAlignYAxis = false
+        , useInteractiveGuideline = false
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + '</h3>' +
+                '<p>' +  y + ' at ' + x + '</p>'
+        }
+        , x
+        , y
+        , state = nv.utils.state()
+        , defaultState = null
+        , noData = 'No Data Available.'
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
+        , duration = 250
+        ;
+
+    xAxis
+        .orient('bottom')
+        .tickPadding(7)
+    ;
+    yAxis
+        .orient((rightAlignYAxis) ? 'right' : 'left')
+    ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
+            y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, null, null, offsetElement);
+    };
+
+    var renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled })
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.active !== undefined)
+                data.forEach(function(series,i) {
+                    series.disabled = !state.active[i];
+                });
+        }
+    };
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(lines);
+        if (showXAxis) renderWatch.models(xAxis);
+        if (showYAxis) renderWatch.models(yAxis);
+
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+
+            chart.update = function() {
+                if (duration === 0)
+                    container.call(chart);
+                else
+                    container.transition().duration(duration).call(chart)
+            };
+            chart.container = this;
+
+            state
+                .setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            // DEPRECATED set state.disableddisabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display noData message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+
+            // Setup Scales
+            x = lines.xScale();
+            y = lines.yScale();
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append("rect").style("opacity",0);
+            gEnter.append('g').attr('class', 'nv-x nv-axis');
+            gEnter.append('g').attr('class', 'nv-y nv-axis');
+            gEnter.append('g').attr('class', 'nv-linesWrap');
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+            gEnter.append('g').attr('class', 'nv-interactive');
+
+            g.select("rect")
+                .attr("width",availableWidth)
+                .attr("height",(availableHeight > 0) ? availableHeight : 0);
+
+            // Legend
+            if (showLegend) {
+                legend.width(availableWidth);
+
+                g.select('.nv-legendWrap')
+                    .datum(data)
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                wrap.select('.nv-legendWrap')
+                    .attr('transform', 'translate(0,' + (-margin.top) +')')
+            }
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            if (rightAlignYAxis) {
+                g.select(".nv-y.nv-axis")
+                    .attr("transform", "translate(" + availableWidth + ",0)");
+            }
+
+            //Set up interactive layer
+            if (useInteractiveGuideline) {
+                interactiveLayer
+                    .width(availableWidth)
+                    .height(availableHeight)
+                    .margin({left:margin.left, top:margin.top})
+                    .svgContainer(container)
+                    .xScale(x);
+                wrap.select(".nv-interactive").call(interactiveLayer);
+            }
+
+            lines
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(data.map(function(d,i) {
+                    return d.color || color(d, i);
+                }).filter(function(d,i) { return !data[i].disabled }));
+
+
+            var linesWrap = g.select('.nv-linesWrap')
+                .datum(data.filter(function(d) { return !d.disabled }));
+
+            linesWrap.call(lines);
+
+            // Setup Axes
+            if (showXAxis) {
+                xAxis
+                    .scale(x)
+                    .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                    .tickSize(-availableHeight, 0);
+
+                g.select('.nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + y.range()[0] + ')');
+                g.select('.nv-x.nv-axis')
+                    .call(xAxis);
+            }
+
+            if (showYAxis) {
+                yAxis
+                    .scale(y)
+                    .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
+                    .tickSize( -availableWidth, 0);
+
+                g.select('.nv-y.nv-axis')
+                    .call(yAxis);
+            }
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState)
+                    state[key] = newState[key];
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            interactiveLayer.dispatch.on('elementMousemove', function(e) {
+                lines.clearHighlights();
+                var singlePoint, pointIndex, pointXLocation, allData = [];
+                data
+                    .filter(function(series, i) {
+                        series.seriesIndex = i;
+                        return !series.disabled;
+                    })
+                    .forEach(function(series,i) {
+                        pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
+                        lines.highlightPoint(i, pointIndex, true);
+                        var point = series.values[pointIndex];
+                        if (typeof point === 'undefined') return;
+                        if (typeof singlePoint === 'undefined') singlePoint = point;
+                        if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
+                        allData.push({
+                            key: series.key,
+                            value: chart.y()(point, pointIndex),
+                            color: color(series,series.seriesIndex)
+                        });
+                    });
+                //Highlight the tooltip entry based on which point the mouse is closest to.
+                if (allData.length > 2) {
+                    var yValue = chart.yScale().invert(e.mouseY);
+                    var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
+                    var threshold = 0.03 * domainExtent;
+                    var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
+                    if (indexToHighlight !== null)
+                        allData[indexToHighlight].highlight = true;
+                }
+
+                var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
+                interactiveLayer.tooltip
+                    .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
+                    .chartContainer(that.parentNode)
+                    .enabled(tooltips)
+                    .valueFormatter(function(d,i) {
+                        return yAxis.tickFormat()(d);
+                    })
+                    .data(
+                    {
+                        value: xValue,
+                        series: allData
+                    }
+                )();
+
+                interactiveLayer.renderGuideLine(pointXLocation);
+
+            });
+
+            interactiveLayer.dispatch.on('elementClick', function(e) {
+                var pointXLocation, allData = [];
+
+                data.filter(function(series, i) {
+                    series.seriesIndex = i;
+                    return !series.disabled;
+                }).forEach(function(series) {
+                    var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
+                    var point = series.values[pointIndex];
+                    if (typeof point === 'undefined') return;
+                    if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
+                    var yPos = chart.yScale()(chart.y()(point,pointIndex));
+                    allData.push({
+                        point: point,
+                        pointIndex: pointIndex,
+                        pos: [pointXLocation, yPos],
+                        seriesIndex: series.seriesIndex,
+                        series: series
+                    });
+                });
+
+                lines.dispatch.elementClick(allData);
+            });
+
+            interactiveLayer.dispatch.on("elementMouseout",function(e) {
+                dispatch.tooltipHide();
+                lines.clearHighlights();
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+            dispatch.on('changeState', function(e) {
+
+                if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+
+                    state.disabled = e.disabled;
+                }
+
+                chart.update();
+            });
+
+        });
+
+        renderWatch.renderEnd('lineChart immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    lines.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    lines.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.lines = lines;
+    chart.legend = legend;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+    chart.interactiveLayer = interactiveLayer;
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
+        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+            lines.duration(duration);
+            xAxis.duration(duration);
+            yAxis.duration(duration);
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+            lines.color(color);
+        }},
+        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
+            rightAlignYAxis = _;
+            yAxis.orient( rightAlignYAxis ? 'right' : 'left');
+        }},
+        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
+            useInteractiveGuideline = _;
+            if (useInteractiveGuideline) {
+                lines.interactive(false);
+                lines.useVoronoi(false);
+            }
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, lines);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+nv.models.linePlusBarChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var lines = nv.models.line()
+        , lines2 = nv.models.line()
+        , bars = nv.models.historicalBar()
+        , bars2 = nv.models.historicalBar()
+        , xAxis = nv.models.axis()
+        , x2Axis = nv.models.axis()
+        , y1Axis = nv.models.axis()
+        , y2Axis = nv.models.axis()
+        , y3Axis = nv.models.axis()
+        , y4Axis = nv.models.axis()
+        , legend = nv.models.legend()
+        , brush = d3.svg.brush()
+        ;
+
+    var margin = {top: 30, right: 30, bottom: 30, left: 60}
+        , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
+        , width = null
+        , height = null
+        , getX = function(d) { return d.x }
+        , getY = function(d) { return d.y }
+        , color = nv.utils.defaultColor()
+        , showLegend = true
+        , focusEnable = true
+        , focusShowAxisY = false
+        , focusShowAxisX = true
+        , focusHeight = 50
+        , extent
+        , brushExtent = null
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + '</h3>' +
+                '<p>' +  y + ' at ' + x + '</p>';
+        }
+        , x
+        , x2
+        , y1
+        , y2
+        , y3
+        , y4
+        , noData = "No Data Available."
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
+        , transitionDuration = 0
+        , state = nv.utils.state()
+        , defaultState = null
+        ;
+
+    lines
+        .clipEdge(true)
+    ;
+    lines2
+        .interactive(false)
+    ;
+    xAxis
+        .orient('bottom')
+        .tickPadding(5)
+    ;
+    y1Axis
+        .orient('left')
+    ;
+    y2Axis
+        .orient('right')
+    ;
+    x2Axis
+        .orient('bottom')
+        .tickPadding(5)
+    ;
+    y3Axis
+        .orient('left')
+    ;
+    y4Axis
+        .orient('right')
+    ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var showTooltip = function(e, offsetElement) {
+        if (extent) {
+            e.pointIndex += Math.ceil(extent[0]);
+        }
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
+            y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+    };
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled })
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.active !== undefined)
+                data.forEach(function(series,i) {
+                    series.disabled = !state.active[i];
+                });
+        }
+    };
+
+    function chart(selection) {
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight1 = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom - (focusEnable ? focusHeight : 0) ,
+                availableHeight2 = focusHeight - margin2.top - margin2.bottom;
+
+            chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
+            chart.container = this;
+
+            state
+                .setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            // DEPRECATED set state.disableddisabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display No Data message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight1 / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
+            var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
+
+            x = bars.xScale();
+            x2 = x2Axis.scale();
+            y1 = bars.yScale();
+            y2 = lines.yScale();
+            y3 = bars2.yScale();
+            y4 = lines2.yScale();
+
+            var series1 = data
+                .filter(function(d) { return !d.disabled && d.bar })
+                .map(function(d) {
+                    return d.values.map(function(d,i) {
+                        return { x: getX(d,i), y: getY(d,i) }
+                    })
+                });
+
+            var series2 = data
+                .filter(function(d) { return !d.disabled && !d.bar })
+                .map(function(d) {
+                    return d.values.map(function(d,i) {
+                        return { x: getX(d,i), y: getY(d,i) }
+                    })
+                });
+
+            x.range([0, availableWidth]);
+
+            x2  .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
+                .range([0, availableWidth]);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+
+            // this is the main chart
+            var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
+            focusEnter.append('g').attr('class', 'nv-x nv-axis');
+            focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
+            focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
+            focusEnter.append('g').attr('class', 'nv-barsWrap');
+            focusEnter.append('g').attr('class', 'nv-linesWrap');
+
+            // context chart is where you can focus in
+            var contextEnter = gEnter.append('g').attr('class', 'nv-context');
+            contextEnter.append('g').attr('class', 'nv-x nv-axis');
+            contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
+            contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
+            contextEnter.append('g').attr('class', 'nv-barsWrap');
+            contextEnter.append('g').attr('class', 'nv-linesWrap');
+            contextEnter.append('g').attr('class', 'nv-brushBackground');
+            contextEnter.append('g').attr('class', 'nv-x nv-brush');
+
+            //============================================================
+            // Legend
+            //------------------------------------------------------------
+
+            if (showLegend) {
+                legend.width( availableWidth / 2 );
+
+                g.select('.nv-legendWrap')
+                    .datum(data.map(function(series) {
+                        series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
+                        series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
+                        return series;
+                    }))
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight1 = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom - focusHeight;
+                }
+
+                g.select('.nv-legendWrap')
+                    .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
+            }
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            //============================================================
+            // Context chart (focus chart) components
+            //------------------------------------------------------------
+
+            // hide or show the focus context chart
+            g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
+
+            bars2
+                .width(availableWidth)
+                .height(availableHeight2)
+                .color(data.map(function (d, i) {
+                    return d.color || color(d, i);
+                }).filter(function (d, i) {
+                    return !data[i].disabled && data[i].bar
+                }));
+            lines2
+                .width(availableWidth)
+                .height(availableHeight2)
+                .color(data.map(function (d, i) {
+                    return d.color || color(d, i);
+                }).filter(function (d, i) {
+                    return !data[i].disabled && !data[i].bar
+                }));
+
+            var bars2Wrap = g.select('.nv-context .nv-barsWrap')
+                .datum(dataBars.length ? dataBars : [
+                    {values: []}
+                ]);
+            var lines2Wrap = g.select('.nv-context .nv-linesWrap')
+                .datum(!dataLines[0].disabled ? dataLines : [
+                    {values: []}
+                ]);
+
+            g.select('.nv-context')
+                .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
+
+            bars2Wrap.transition().call(bars2);
+            lines2Wrap.transition().call(lines2);
+
+            // context (focus chart) axis controls
+            if (focusShowAxisX) {
+                x2Axis
+                    .ticks(nv.utils.calcTicksX(availableWidth / 100, data))
+                    .tickSize(-availableHeight2, 0);
+                g.select('.nv-context .nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + y3.range()[0] + ')');
+                g.select('.nv-context .nv-x.nv-axis').transition()
+                    .call(x2Axis);
+            }
+
+            if (focusShowAxisY) {
+                y3Axis
+                    .scale(y3)
+                    .ticks( availableHeight2 / 36 )
+                    .tickSize( -availableWidth, 0);
+                y4Axis
+                    .scale(y4)
+                    .ticks( availableHeight2 / 36 )
+                    .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
+
+                g.select('.nv-context .nv-y3.nv-axis')
+                    .style('opacity', dataBars.length ? 1 : 0)
+                    .attr('transform', 'translate(0,' + x2.range()[0] + ')');
+                g.select('.nv-context .nv-y2.nv-axis')
+                    .style('opacity', dataLines.length ? 1 : 0)
+                    .attr('transform', 'translate(' + x2.range()[1] + ',0)');
+
+                g.select('.nv-context .nv-y1.nv-axis').transition()
+                    .call(y3Axis);
+                g.select('.nv-context .nv-y2.nv-axis').transition()
+                    .call(y4Axis);
+            }
+
+            // Setup Brush
+            brush.x(x2).on('brush', onBrush);
+
+            if (brushExtent) brush.extent(brushExtent);
+
+            var brushBG = g.select('.nv-brushBackground').selectAll('g')
+                .data([brushExtent || brush.extent()]);
+
+            var brushBGenter = brushBG.enter()
+                .append('g');
+
+            brushBGenter.append('rect')
+                .attr('class', 'left')
+                .attr('x', 0)
+                .attr('y', 0)
+                .attr('height', availableHeight2);
+
+            brushBGenter.append('rect')
+                .attr('class', 'right')
+                .attr('x', 0)
+                .attr('y', 0)
+                .attr('height', availableHeight2);
+
+            var gBrush = g.select('.nv-x.nv-brush')
+                .call(brush);
+            gBrush.selectAll('rect')
+                //.attr('y', -5)
+                .attr('height', availableHeight2);
+            gBrush.selectAll('.resize').append('path').attr('d', resizePath);
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState)
+                    state[key] = newState[key];
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+            // Update chart from a state object passed to event handler
+            dispatch.on('changeState', function(e) {
+                if (typeof e.disabled !== 'undefined') {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+                    state.disabled = e.disabled;
+                }
+                chart.update();
+            });
+
+            //============================================================
+            // Functions
+            //------------------------------------------------------------
+
+            // Taken from crossfilter (http://square.github.com/crossfilter/)
+            function resizePath(d) {
+                var e = +(d == 'e'),
+                    x = e ? 1 : -1,
+                    y = availableHeight2 / 3;
+                return 'M' + (.5 * x) + ',' + y
+                    + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
+                    + 'V' + (2 * y - 6)
+                    + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
+                    + 'Z'
+                    + 'M' + (2.5 * x) + ',' + (y + 8)
+                    + 'V' + (2 * y - 8)
+                    + 'M' + (4.5 * x) + ',' + (y + 8)
+                    + 'V' + (2 * y - 8);
+            }
+
+
+            function updateBrushBG() {
+                if (!brush.empty()) brush.extent(brushExtent);
+                brushBG
+                    .data([brush.empty() ? x2.domain() : brushExtent])
+                    .each(function(d,i) {
+                        var leftWidth = x2(d[0]) - x2.range()[0],
+                            rightWidth = x2.range()[1] - x2(d[1]);
+                        d3.select(this).select('.left')
+                            .attr('width',  leftWidth < 0 ? 0 : leftWidth);
+
+                        d3.select(this).select('.right')
+                            .attr('x', x2(d[1]))
+                            .attr('width', rightWidth < 0 ? 0 : rightWidth);
+                    });
+            }
+
+            function onBrush() {
+                brushExtent = brush.empty() ? null : brush.extent();
+                extent = brush.empty() ? x2.domain() : brush.extent();
+                dispatch.brush({extent: extent, brush: brush});
+                updateBrushBG();
+
+                // Prepare Main (Focus) Bars and Lines
+                bars
+                    .width(availableWidth)
+                    .height(availableHeight1)
+                    .color(data.map(function(d,i) {
+                        return d.color || color(d, i);
+                    }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
+
+                lines
+                    .width(availableWidth)
+                    .height(availableHeight1)
+                    .color(data.map(function(d,i) {
+                        return d.color || color(d, i);
+                    }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
+
+                var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
+                    .datum(!dataBars.length ? [{values:[]}] :
+                        dataBars
+                            .map(function(d,i) {
+                                return {
+                                    key: d.key,
+                                    values: d.values.filter(function(d,i) {
+                                        return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
+                                    })
+                                }
+                            })
+                );
+
+                var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
+                    .datum(dataLines[0].disabled ? [{values:[]}] :
+                        dataLines
+                            .map(function(d,i) {
+                                return {
+                                    key: d.key,
+                                    values: d.values.filter(function(d,i) {
+                                        return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
+                                    })
+                                }
+                            })
+                );
+
+                // Update Main (Focus) X Axis
+                if (dataBars.length) {
+                    x = bars.xScale();
+                } else {
+                    x = lines.xScale();
+                }
+
+                xAxis
+                    .scale(x)
+                    .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                    .tickSize(-availableHeight1, 0);
+
+                xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
+
+                g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
+                    .call(xAxis);
+
+                // Update Main (Focus) Bars and Lines
+                focusBarsWrap.transition().duration(transitionDuration).call(bars);
+                focusLinesWrap.transition().duration(transitionDuration).call(lines);
+
+                // Setup and Update Main (Focus) Y Axes
+                g.select('.nv-focus .nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + y1.range()[0] + ')');
+
+                y1Axis
+                    .scale(y1)
+                    .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
+                    .tickSize(-availableWidth, 0);
+                y2Axis
+                    .scale(y2)
+                    .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
+                    .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
+
+                g.select('.nv-focus .nv-y1.nv-axis')
+                    .style('opacity', dataBars.length ? 1 : 0);
+                g.select('.nv-focus .nv-y2.nv-axis')
+                    .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0)
+                    .attr('transform', 'translate(' + x.range()[1] + ',0)');
+
+                g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
+                    .call(y1Axis);
+                g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
+                    .call(y2Axis);
+            }
+
+            onBrush();
+
+        });
+
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    lines.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    lines.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    bars.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    bars.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.legend = legend;
+    chart.lines = lines;
+    chart.lines2 = lines2;
+    chart.bars = bars;
+    chart.bars2 = bars2;
+    chart.xAxis = xAxis;
+    chart.x2Axis = x2Axis;
+    chart.y1Axis = y1Axis;
+    chart.y2Axis = y2Axis;
+    chart.y3Axis = y3Axis;
+    chart.y4Axis = y4Axis;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        brushExtent:    {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+        focusEnable:    {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
+        focusHeight:    {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
+        focusShowAxisX:    {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
+        focusShowAxisY:    {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return transitionDuration;}, set: function(_){
+            transitionDuration = _;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+        }},
+        x: {get: function(){return getX;}, set: function(_){
+            getX = _;
+            lines.x(_);
+            lines2.x(_);
+            bars.x(_);
+            bars2.x(_);
+        }},
+        y: {get: function(){return getY;}, set: function(_){
+            getY = _;
+            lines.y(_);
+            lines2.y(_);
+            bars.y(_);
+            bars2.y(_);
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, lines);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+nv.models.lineWithFocusChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var lines = nv.models.line()
+        , lines2 = nv.models.line()
+        , xAxis = nv.models.axis()
+        , yAxis = nv.models.axis()
+        , x2Axis = nv.models.axis()
+        , y2Axis = nv.models.axis()
+        , legend = nv.models.legend()
+        , brush = d3.svg.brush()
+        ;
+
+    var margin = {top: 30, right: 30, bottom: 30, left: 60}
+        , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
+        , color = nv.utils.defaultColor()
+        , width = null
+        , height = null
+        , height2 = 100
+        , x
+        , y
+        , x2
+        , y2
+        , showLegend = true
+        , brushExtent = null
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + '</h3>' +
+                '<p>' +  y + ' at ' + x + '</p>'
+        }
+        , noData = "No Data Available."
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
+        , transitionDuration = 250
+        , state = nv.utils.state()
+        , defaultState = null
+        ;
+
+    lines
+        .clipEdge(true)
+    ;
+    lines2
+        .interactive(false)
+    ;
+    xAxis
+        .orient('bottom')
+        .tickPadding(5)
+    ;
+    yAxis
+        .orient('left')
+    ;
+    x2Axis
+        .orient('bottom')
+        .tickPadding(5)
+    ;
+    y2Axis
+        .orient('left')
+    ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
+            y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, null, null, offsetElement);
+    };
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled })
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.active !== undefined)
+                data.forEach(function(series,i) {
+                    series.disabled = !state.active[i];
+                });
+        }
+    };
+
+    function chart(selection) {
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight1 = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom - height2,
+                availableHeight2 = height2 - margin2.top - margin2.bottom;
+
+            chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
+            chart.container = this;
+
+            state
+                .setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            // DEPRECATED set state.disableddisabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display No Data message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight1 / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            x = lines.xScale();
+            y = lines.yScale();
+            x2 = lines2.xScale();
+            y2 = lines2.yScale();
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+
+            var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
+            focusEnter.append('g').attr('class', 'nv-x nv-axis');
+            focusEnter.append('g').attr('class', 'nv-y nv-axis');
+            focusEnter.append('g').attr('class', 'nv-linesWrap');
+
+            var contextEnter = gEnter.append('g').attr('class', 'nv-context');
+            contextEnter.append('g').attr('class', 'nv-x nv-axis');
+            contextEnter.append('g').attr('class', 'nv-y nv-axis');
+            contextEnter.append('g').attr('class', 'nv-linesWrap');
+            contextEnter.append('g').attr('class', 'nv-brushBackground');
+            contextEnter.append('g').attr('class', 'nv-x nv-brush');
+
+            // Legend
+            if (showLegend) {
+                legend.width(availableWidth);
+
+                g.select('.nv-legendWrap')
+                    .datum(data)
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight1 = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom - height2;
+                }
+
+                g.select('.nv-legendWrap')
+                    .attr('transform', 'translate(0,' + (-margin.top) +')')
+            }
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            // Main Chart Component(s)
+            lines
+                .width(availableWidth)
+                .height(availableHeight1)
+                .color(
+                data
+                    .map(function(d,i) {
+                        return d.color || color(d, i);
+                    })
+                    .filter(function(d,i) {
+                        return !data[i].disabled;
+                    })
+            );
+
+            lines2
+                .defined(lines.defined())
+                .width(availableWidth)
+                .height(availableHeight2)
+                .color(
+                data
+                    .map(function(d,i) {
+                        return d.color || color(d, i);
+                    })
+                    .filter(function(d,i) {
+                        return !data[i].disabled;
+                    })
+            );
+
+            g.select('.nv-context')
+                .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
+
+            var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
+                .datum(data.filter(function(d) { return !d.disabled }))
+
+            d3.transition(contextLinesWrap).call(lines2);
+
+            // Setup Main (Focus) Axes
+            xAxis
+                .scale(x)
+                .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                .tickSize(-availableHeight1, 0);
+
+            yAxis
+                .scale(y)
+                .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
+                .tickSize( -availableWidth, 0);
+
+            g.select('.nv-focus .nv-x.nv-axis')
+                .attr('transform', 'translate(0,' + availableHeight1 + ')');
+
+            // Setup Brush
+            brush
+                .x(x2)
+                .on('brush', function() {
+                    //When brushing, turn off transitions because chart needs to change immediately.
+                    var oldTransition = chart.duration();
+                    chart.duration(0);
+                    onBrush();
+                    chart.duration(oldTransition);
+                });
+
+            if (brushExtent) brush.extent(brushExtent);
+
+            var brushBG = g.select('.nv-brushBackground').selectAll('g')
+                .data([brushExtent || brush.extent()])
+
+            var brushBGenter = brushBG.enter()
+                .append('g');
+
+            brushBGenter.append('rect')
+                .attr('class', 'left')
+                .attr('x', 0)
+                .attr('y', 0)
+                .attr('height', availableHeight2);
+
+            brushBGenter.append('rect')
+                .attr('class', 'right')
+                .attr('x', 0)
+                .attr('y', 0)
+                .attr('height', availableHeight2);
+
+            var gBrush = g.select('.nv-x.nv-brush')
+                .call(brush);
+            gBrush.selectAll('rect')
+                //.attr('y', -5)
+                .attr('height', availableHeight2);
+            gBrush.selectAll('.resize').append('path').attr('d', resizePath);
+
+            onBrush();
+
+            // Setup Secondary (Context) Axes
+            x2Axis
+                .scale(x2)
+                .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                .tickSize(-availableHeight2, 0);
+
+            g.select('.nv-context .nv-x.nv-axis')
+                .attr('transform', 'translate(0,' + y2.range()[0] + ')');
+            d3.transition(g.select('.nv-context .nv-x.nv-axis'))
+                .call(x2Axis);
+
+            y2Axis
+                .scale(y2)
+                .ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
+                .tickSize( -availableWidth, 0);
+
+            d3.transition(g.select('.nv-context .nv-y.nv-axis'))
+                .call(y2Axis);
+
+            g.select('.nv-context .nv-x.nv-axis')
+                .attr('transform', 'translate(0,' + y2.range()[0] + ')');
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState)
+                    state[key] = newState[key];
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+            dispatch.on('changeState', function(e) {
+                if (typeof e.disabled !== 'undefined') {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+                }
+                chart.update();
+            });
+
+            //============================================================
+            // Functions
+            //------------------------------------------------------------
+
+            // Taken from crossfilter (http://square.github.com/crossfilter/)
+            function resizePath(d) {
+                var e = +(d == 'e'),
+                    x = e ? 1 : -1,
+                    y = availableHeight2 / 3;
+                return 'M' + (.5 * x) + ',' + y
+                    + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
+                    + 'V' + (2 * y - 6)
+                    + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
+                    + 'Z'
+                    + 'M' + (2.5 * x) + ',' + (y + 8)
+                    + 'V' + (2 * y - 8)
+                    + 'M' + (4.5 * x) + ',' + (y + 8)
+                    + 'V' + (2 * y - 8);
+            }
+
+
+            function updateBrushBG() {
+                if (!brush.empty()) brush.extent(brushExtent);
+                brushBG
+                    .data([brush.empty() ? x2.domain() : brushExtent])
+                    .each(function(d,i) {
+                        var leftWidth = x2(d[0]) - x.range()[0],
+                            rightWidth = x.range()[1] - x2(d[1]);
+                        d3.select(this).select('.left')
+                            .attr('width',  leftWidth < 0 ? 0 : leftWidth);
+
+                        d3.select(this).select('.right')
+                            .attr('x', x2(d[1]))
+                            .attr('width', rightWidth < 0 ? 0 : rightWidth);
+                    });
+            }
+
+
+            function onBrush() {
+                brushExtent = brush.empty() ? null : brush.extent();
+                var extent = brush.empty() ? x2.domain() : brush.extent();
+
+                //The brush extent cannot be less than one.  If it is, don't update the line chart.
+                if (Math.abs(extent[0] - extent[1]) <= 1) {
+                    return;
+                }
+
+                dispatch.brush({extent: extent, brush: brush});
+
+
+                updateBrushBG();
+
+                // Update Main (Focus)
+                var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
+                    .datum(
+                    data
+                        .filter(function(d) { return !d.disabled })
+                        .map(function(d,i) {
+                            return {
+                                key: d.key,
+                                area: d.area,
+                                values: d.values.filter(function(d,i) {
+                                    return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
+                                })
+                            }
+                        })
+                );
+                focusLinesWrap.transition().duration(transitionDuration).call(lines);
+
+
+                // Update Main (Focus) Axes
+                g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
+                    .call(xAxis);
+                g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
+                    .call(yAxis);
+            }
+        });
+
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    lines.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    lines.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.legend = legend;
+    chart.lines = lines;
+    chart.lines2 = lines2;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+    chart.x2Axis = x2Axis;
+    chart.y2Axis = y2Axis;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        focusHeight:     {get: function(){return height2;}, set: function(_){height2=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+            // line color is handled above?
+        }},
+        interpolate: {get: function(){return lines.interpolate();}, set: function(_){
+            lines.interpolate(_);
+            lines2.interpolate(_);
+        }},
+        xTickFormat: {get: function(){return xAxis.xTickFormat();}, set: function(_){
+            xAxis.xTickFormat(_);
+            x2Axis.xTickFormat(_);
+        }},
+        yTickFormat: {get: function(){return yAxis.yTickFormat();}, set: function(_){
+            yAxis.yTickFormat(_);
+            y2Axis.yTickFormat(_);
+        }},
+        duration:    {get: function(){return transitionDuration;}, set: function(_){
+            transitionDuration=_;
+            yAxis.duration(transitionDuration);
+            xAxis.duration(transitionDuration);
+        }},
+        x: {get: function(){return lines.x();}, set: function(_){
+            lines.x(_);
+            lines2.x(_);
+        }},
+        y: {get: function(){return lines.y();}, set: function(_){
+            lines.y(_);
+            lines2.y(_);
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, lines);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.models.multiBar = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = 960
+        , height = 500
+        , x = d3.scale.ordinal()
+        , y = d3.scale.linear()
+        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+        , getX = function(d) { return d.x }
+        , getY = function(d) { return d.y }
+        , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
+        , clipEdge = true
+        , stacked = false
+        , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
+        , color = nv.utils.defaultColor()
+        , hideable = false
+        , barColor = null // adding the ability to set the color for each rather than the whole group
+        , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
+        , duration = 500
+        , xDomain
+        , yDomain
+        , xRange
+        , yRange
+        , groupSpacing = 0.1
+        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
+        ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var x0, y0 //used to store previous scales
+        , renderWatch = nv.utils.renderWatch(dispatch, duration)
+        ;
+
+    var last_datalength = 0;
+
+    function chart(selection) {
+        renderWatch.reset();
+        selection.each(function(data) {
+            var availableWidth = width - margin.left - margin.right,
+                availableHeight = height - margin.top - margin.bottom,
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            // This function defines the requirements for render complete
+            var endFn = function(d, i) {
+                if (d.series === data.length - 1 && i === data[0].values.length - 1)
+                    return true;
+                return false;
+            };
+
+            if(hideable && data.length) hideable = [{
+                values: data[0].values.map(function(d) {
+                        return {
+                            x: d.x,
+                            y: 0,
+                            series: d.series,
+                            size: 0.01
+                        };}
+                )}];
+
+            if (stacked)
+                data = d3.layout.stack()
+                    .offset(stackOffset)
+                    .values(function(d){ return d.values })
+                    .y(getY)
+                (!data.length && hideable ? hideable : data);
+
+
+            //add series index to each data point for reference
+            data.forEach(function(series, i) {
+                series.values.forEach(function(point) {
+                    point.series = i;
+                });
+            });
+
+            // HACK for negative value stacking
+            if (stacked)
+                data[0].values.map(function(d,i) {
+                    var posBase = 0, negBase = 0;
+                    data.map(function(d) {
+                        var f = d.values[i]
+                        f.size = Math.abs(f.y);
+                        if (f.y<0)  {
+                            f.y1 = negBase;
+                            negBase = negBase - f.size;
+                        } else
+                        {
+                            f.y1 = f.size + posBase;
+                            posBase = posBase + f.size;
+                        }
+                    });
+                });
+
+            // Setup Scales
+            // remap and flatten the data for use in calculating the scales' domains
+            var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
+                data.map(function(d) {
+                    return d.values.map(function(d,i) {
+                        return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
+                    })
+                });
+
+            x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
+                .rangeBands(xRange || [0, availableWidth], groupSpacing);
+
+            y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY)))
+                .range(yRange || [availableHeight, 0]);
+
+            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
+            if (x.domain()[0] === x.domain()[1])
+                x.domain()[0] ?
+                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
+                    : x.domain([-1,1]);
+
+            if (y.domain()[0] === y.domain()[1])
+                y.domain()[0] ?
+                    y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
+                    : y.domain([-1,1]);
+
+            x0 = x0 || x;
+            y0 = y0 || y;
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
+            var defsEnter = wrapEnter.append('defs');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g')
+
+            gEnter.append('g').attr('class', 'nv-groups');
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            defsEnter.append('clipPath')
+                .attr('id', 'nv-edge-clip-' + id)
+                .append('rect');
+            wrap.select('#nv-edge-clip-' + id + ' rect')
+                .attr('width', availableWidth)
+                .attr('height', availableHeight);
+
+            g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
+
+            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+                .data(function(d) { return d }, function(d,i) { return i });
+            groups.enter().append('g')
+                .style('stroke-opacity', 1e-6)
+                .style('fill-opacity', 1e-6);
+
+            var exitTransition = renderWatch
+                .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
+                .attr('y', function(d) { return (stacked ? y0(d.y0) : y0(0)) || 0 })
+                .attr('height', 0)
+                .remove();
+            if (exitTransition.delay)
+                exitTransition.delay(function(d,i) {
+                    var delay = i * (duration / (last_datalength + 1)) - i;
+                    return delay;
+                });
+            groups
+                .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+                .classed('hover', function(d) { return d.hover })
+                .style('fill', function(d,i){ return color(d, i) })
+                .style('stroke', function(d,i){ return color(d, i) });
+            groups
+                .style('stroke-opacity', 1)
+                .style('fill-opacity', 0.75);
+
+            var bars = groups.selectAll('rect.nv-bar')
+                .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
+            bars.exit().remove();
+
+            var barsEnter = bars.enter().append('rect')
+                    .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
+                    .attr('x', function(d,i,j) {
+                        return stacked ? 0 : (j * x.rangeBand() / data.length )
+                    })
+                    .attr('y', function(d) { return y0(stacked ? d.y0 : 0) || 0 })
+                    .attr('height', 0)
+                    .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
+                    .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
+                ;
+            bars
+                .style('fill', function(d,i,j){ return color(d, j, i);  })
+                .style('stroke', function(d,i,j){ return color(d, j, i); })
+                .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
+                    d3.select(this).classed('hover', true);
+                    dispatch.elementMouseover({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                })
+                .on('mouseout', function(d,i) {
+                    d3.select(this).classed('hover', false);
+                    dispatch.elementMouseout({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                })
+                .on('click', function(d,i) {
+                    dispatch.elementClick({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                    d3.event.stopPropagation();
+                })
+                .on('dblclick', function(d,i) {
+                    dispatch.elementDblClick({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                    d3.event.stopPropagation();
+                });
+            bars
+                .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
+                .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
+
+            if (barColor) {
+                if (!disabled) disabled = data.map(function() { return true });
+                bars
+                    .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(  disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i]  })[j]   ).toString(); })
+                    .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(  disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i]  })[j]   ).toString(); });
+            }
+
+            var barSelection =
+                bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
+                    .delay(function(d,i) {
+                        return i * duration / data[0].values.length;
+                    });
+            if (stacked)
+                barSelection
+                    .attr('y', function(d,i) {
+                        return y((stacked ? d.y1 : 0));
+                    })
+                    .attr('height', function(d,i) {
+                        return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
+                    })
+                    .attr('x', function(d,i) {
+                        return stacked ? 0 : (d.series * x.rangeBand() / data.length )
+                    })
+                    .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
+            else
+                barSelection
+                    .attr('x', function(d,i) {
+                        return d.series * x.rangeBand() / data.length
+                    })
+                    .attr('width', x.rangeBand() / data.length)
+                    .attr('y', function(d,i) {
+                        return getY(d,i) < 0 ?
+                            y(0) :
+                                y(0) - y(getY(d,i)) < 1 ?
+                            y(0) - 1 :
+                            y(getY(d,i)) || 0;
+                    })
+                    .attr('height', function(d,i) {
+                        return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
+                    });
+
+            //store old scales for use in transitions on update
+            x0 = x.copy();
+            y0 = y.copy();
+
+            // keep track of the last data value length for transition calculations
+            if (data[0] && data[0].values) {
+                last_datalength = data[0].values.length;
+            }
+
+        });
+
+        renderWatch.renderEnd('multibar immediate');
+
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:   {get: function(){return width;}, set: function(_){width=_;}},
+        height:  {get: function(){return height;}, set: function(_){height=_;}},
+        x:       {get: function(){return getX;}, set: function(_){getX=_;}},
+        y:       {get: function(){return getY;}, set: function(_){getY=_;}},
+        xScale:  {get: function(){return x;}, set: function(_){x=_;}},
+        yScale:  {get: function(){return y;}, set: function(_){y=_;}},
+        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
+        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
+        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
+        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
+        forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
+        stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
+        stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
+        clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
+        disabled:    {get: function(){return disabled;}, set: function(_){disabled=_;}},
+        id:          {get: function(){return id;}, set: function(_){id=_;}},
+        hideable:    {get: function(){return hideable;}, set: function(_){hideable=_;}},
+        groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }},
+        barColor:  {get: function(){return barColor;}, set: function(_){
+            barColor = nv.utils.getColor(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.models.multiBarChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var multibar = nv.models.multiBar()
+        , xAxis = nv.models.axis()
+        , yAxis = nv.models.axis()
+        , legend = nv.models.legend()
+        , controls = nv.models.legend()
+        ;
+
+    var margin = {top: 30, right: 20, bottom: 50, left: 60}
+        , width = null
+        , height = null
+        , color = nv.utils.defaultColor()
+        , showControls = true
+        , showLegend = true
+        , showXAxis = true
+        , showYAxis = true
+        , rightAlignYAxis = false
+        , reduceXTicks = true // if false a tick will show for every data point
+        , staggerLabels = false
+        , rotateLabels = 0
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + '</h3>' +
+                '<p>' +  y + ' on ' + x + '</p>'
+        }
+        , x //can be accessed via chart.xScale()
+        , y //can be accessed via chart.yScale()
+        , state = nv.utils.state()
+        , defaultState = null
+        , noData = "No Data Available."
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
+        , controlWidth = function() { return showControls ? 180 : 0 }
+        , duration = 250
+        ;
+
+    state.stacked = false // DEPRECATED Maintained for backward compatibility
+
+    multibar
+        .stacked(false)
+    ;
+    xAxis
+        .orient('bottom')
+        .tickPadding(7)
+        .highlightZero(true)
+        .showMaxMin(false)
+        .tickFormat(function(d) { return d })
+    ;
+    yAxis
+        .orient((rightAlignYAxis) ? 'right' : 'left')
+        .tickFormat(d3.format(',.1f'))
+    ;
+
+    controls.updateState(false);
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var renderWatch = nv.utils.renderWatch(dispatch);
+    var stacked = false;
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
+            y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+    };
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled }),
+                stacked: stacked
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.stacked !== undefined)
+                stacked = state.stacked;
+            if (state.active !== undefined)
+                data.forEach(function(series,i) {
+                    series.disabled = !state.active[i];
+                });
+        }
+    };
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(multibar);
+        if (showXAxis) renderWatch.models(xAxis);
+        if (showYAxis) renderWatch.models(yAxis);
+
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+            chart.update = function() {
+                if (duration === 0)
+                    container.call(chart);
+                else
+                    container.transition()
+                        .duration(duration)
+                        .call(chart);
+            };
+            chart.container = this;
+
+            state
+                .setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            // DEPRECATED set state.disableddisabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display noData message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            x = multibar.xScale();
+            y = multibar.yScale();
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-x nv-axis');
+            gEnter.append('g').attr('class', 'nv-y nv-axis');
+            gEnter.append('g').attr('class', 'nv-barsWrap');
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+            gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+            // Legend
+            if (showLegend) {
+                legend.width(availableWidth - controlWidth());
+
+                if (multibar.barColor())
+                    data.forEach(function(series,i) {
+                        series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
+                    });
+
+                g.select('.nv-legendWrap')
+                    .datum(data)
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                g.select('.nv-legendWrap')
+                    .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
+            }
+
+            // Controls
+            if (showControls) {
+                var controlsData = [
+                    { key: 'Grouped', disabled: multibar.stacked() },
+                    { key: 'Stacked', disabled: !multibar.stacked() }
+                ];
+
+                controls.width(controlWidth()).color(['#444', '#444', '#444']);
+                g.select('.nv-controlsWrap')
+                    .datum(controlsData)
+                    .attr('transform', 'translate(0,' + (-margin.top) +')')
+                    .call(controls);
+            }
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+            if (rightAlignYAxis) {
+                g.select(".nv-y.nv-axis")
+                    .attr("transform", "translate(" + availableWidth + ",0)");
+            }
+
+            // Main Chart Component(s)
+            multibar
+                .disabled(data.map(function(series) { return series.disabled }))
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(data.map(function(d,i) {
+                    return d.color || color(d, i);
+                }).filter(function(d,i) { return !data[i].disabled }));
+
+
+            var barsWrap = g.select('.nv-barsWrap')
+                .datum(data.filter(function(d) { return !d.disabled }));
+
+            barsWrap.call(multibar);
+
+            // Setup Axes
+            if (showXAxis) {
+                xAxis
+                    .scale(x)
+                    .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                    .tickSize(-availableHeight, 0);
+
+                g.select('.nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + y.range()[0] + ')');
+                g.select('.nv-x.nv-axis')
+                    .call(xAxis);
+
+                var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
+
+                xTicks
+                    .selectAll('line, text')
+                    .style('opacity', 1)
+
+                if (staggerLabels) {
+                    var getTranslate = function(x,y) {
+                        return "translate(" + x + "," + y + ")";
+                    };
+
+                    var staggerUp = 5, staggerDown = 17;  //pixels to stagger by
+                    // Issue #140
+                    xTicks
+                        .selectAll("text")
+                        .attr('transform', function(d,i,j) {
+                            return  getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
+                        });
+
+                    var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
+                    g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
+                        .attr("transform", function(d,i) {
+                            return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
+                        });
+                }
+
+                if (reduceXTicks)
+                    xTicks
+                        .filter(function(d,i) {
+                            return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
+                        })
+                        .selectAll('text, line')
+                        .style('opacity', 0);
+
+                if(rotateLabels)
+                    xTicks
+                        .selectAll('.tick text')
+                        .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
+                        .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
+
+                g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
+                    .style('opacity', 1);
+            }
+
+            if (showYAxis) {
+                yAxis
+                    .scale(y)
+                    .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
+                    .tickSize( -availableWidth, 0);
+
+                g.select('.nv-y.nv-axis')
+                    .call(yAxis);
+            }
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState)
+                    state[key] = newState[key];
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            controls.dispatch.on('legendClick', function(d,i) {
+                if (!d.disabled) return;
+                controlsData = controlsData.map(function(s) {
+                    s.disabled = true;
+                    return s;
+                });
+                d.disabled = false;
+
+                switch (d.key) {
+                    case 'Grouped':
+                        multibar.stacked(false);
+                        break;
+                    case 'Stacked':
+                        multibar.stacked(true);
+                        break;
+                }
+
+                state.stacked = multibar.stacked();
+                dispatch.stateChange(state);
+
+                chart.update();
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode)
+            });
+
+            // Update chart from a state object passed to event handler
+            dispatch.on('changeState', function(e) {
+
+                if (typeof e.disabled !== 'undefined') {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+
+                    state.disabled = e.disabled;
+                }
+
+                if (typeof e.stacked !== 'undefined') {
+                    multibar.stacked(e.stacked);
+                    state.stacked = e.stacked;
+                    stacked = e.stacked;
+                }
+
+                chart.update();
+            });
+        });
+
+        renderWatch.renderEnd('multibarchart immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    multibar.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    multibar.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.multibar = multibar;
+    chart.legend = legend;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+    chart.state = state;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
+        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
+        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+        reduceXTicks:    {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
+        rotateLabels:    {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
+        staggerLabels:    {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            multibar.duration(duration);
+            xAxis.duration(duration);
+            yAxis.duration(duration);
+            renderWatch.reset(duration);
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+        }},
+        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
+            rightAlignYAxis = _;
+            yAxis.orient( rightAlignYAxis ? 'right' : 'left');
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, multibar);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.models.multiBarHorizontal = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = 960
+        , height = 500
+        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+        , x = d3.scale.ordinal()
+        , y = d3.scale.linear()
+        , getX = function(d) { return d.x }
+        , getY = function(d) { return d.y }
+        , getYerr = function(d) { return d.yErr }
+        , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
+        , color = nv.utils.defaultColor()
+        , barColor = null // adding the ability to set the color for each rather than the whole group
+        , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
+        , stacked = false
+        , showValues = false
+        , showBarLabels = false
+        , valuePadding = 60
+        , valueFormat = d3.format(',.2f')
+        , delay = 1200
+        , xDomain
+        , yDomain
+        , xRange
+        , yRange
+        , duration = 250
+        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
+        ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var x0, y0; //used to store previous scales
+    var renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    function chart(selection) {
+        renderWatch.reset();
+        selection.each(function(data) {
+            var availableWidth = width - margin.left - margin.right,
+                availableHeight = height - margin.top - margin.bottom,
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            if (stacked)
+                data = d3.layout.stack()
+                    .offset('zero')
+                    .values(function(d){ return d.values })
+                    .y(getY)
+                (data);
+
+            //add series index to each data point for reference
+            data.forEach(function(series, i) {
+                series.values.forEach(function(point) {
+                    point.series = i;
+                });
+            });
+
+            // HACK for negative value stacking
+            if (stacked)
+                data[0].values.map(function(d,i) {
+                    var posBase = 0, negBase = 0;
+                    data.map(function(d) {
+                        var f = d.values[i]
+                        f.size = Math.abs(f.y);
+                        if (f.y<0)  {
+                            f.y1 = negBase - f.size;
+                            negBase = negBase - f.size;
+                        } else
+                        {
+                            f.y1 = posBase;
+                            posBase = posBase + f.size;
+                        }
+                    });
+                });
+
+            // Setup Scales
+            // remap and flatten the data for use in calculating the scales' domains
+            var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
+                data.map(function(d) {
+                    return d.values.map(function(d,i) {
+                        return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
+                    })
+                });
+
+            x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
+                .rangeBands(xRange || [0, availableHeight], .1);
+
+            y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY)))
+
+            if (showValues && !stacked)
+                y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
+            else
+                y.range(yRange || [0, availableWidth]);
+
+            x0 = x0 || x;
+            y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
+
+            // Setup containers and skeleton of chart
+            var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
+            var defsEnter = wrapEnter.append('defs');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-groups');
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+                .data(function(d) { return d }, function(d,i) { return i });
+            groups.enter().append('g')
+                .style('stroke-opacity', 1e-6)
+                .style('fill-opacity', 1e-6);
+            groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
+                .style('stroke-opacity', 1e-6)
+                .style('fill-opacity', 1e-6)
+                .remove();
+            groups
+                .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+                .classed('hover', function(d) { return d.hover })
+                .style('fill', function(d,i){ return color(d, i) })
+                .style('stroke', function(d,i){ return color(d, i) });
+            groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
+                .style('stroke-opacity', 1)
+                .style('fill-opacity', .75);
+
+            var bars = groups.selectAll('g.nv-bar')
+                .data(function(d) { return d.values });
+            bars.exit().remove();
+
+            var barsEnter = bars.enter().append('g')
+                .attr('transform', function(d,i,j) {
+                    return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
+                });
+
+            barsEnter.append('rect')
+                .attr('width', 0)
+                .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
+
+            bars
+                .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
+                    d3.select(this).classed('hover', true);
+                    dispatch.elementMouseover({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                })
+                .on('mouseout', function(d,i) {
+                    d3.select(this).classed('hover', false);
+                    dispatch.elementMouseout({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                })
+                .on('click', function(d,i) {
+                    dispatch.elementClick({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                    d3.event.stopPropagation();
+                })
+                .on('dblclick', function(d,i) {
+                    dispatch.elementDblClick({
+                        value: getY(d,i),
+                        point: d,
+                        series: data[d.series],
+                        pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
+                        pointIndex: i,
+                        seriesIndex: d.series,
+                        e: d3.event
+                    });
+                    d3.event.stopPropagation();
+                });
+
+            if (getYerr(data[0],0)) {
+                barsEnter.append('polyline');
+
+                bars.select('polyline')
+                    .attr('fill', 'none')
+                    .attr('points', function(d,i) {
+                        var xerr = getYerr(d,i)
+                            , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
+                        xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
+                        xerr = xerr.map(function(e) { return y(e) - y(0); });
+                        var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
+                        return a.map(function (path) { return path.join(',') }).join(' ');
+                    })
+                    .attr('transform', function(d,i) {
+                        var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
+                        return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
+                    });
+            }
+
+            barsEnter.append('text');
+
+            if (showValues && !stacked) {
+                bars.select('text')
+                    .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
+                    .attr('y', x.rangeBand() / (data.length * 2))
+                    .attr('dy', '.32em')
+                    .html(function(d,i) {
+                        var t = valueFormat(getY(d,i))
+                            , yerr = getYerr(d,i);
+                        if (yerr === undefined)
+                            return t;
+                        if (!yerr.length)
+                            return t + '&plusmn;' + valueFormat(Math.abs(yerr));
+                        return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
+                    });
+                bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
+                    .select('text')
+                    .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
+            } else {
+                bars.selectAll('text').text('');
+            }
+
+            if (showBarLabels && !stacked) {
+                barsEnter.append('text').classed('nv-bar-label',true);
+                bars.select('text.nv-bar-label')
+                    .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
+                    .attr('y', x.rangeBand() / (data.length * 2))
+                    .attr('dy', '.32em')
+                    .text(function(d,i) { return getX(d,i) });
+                bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
+                    .select('text.nv-bar-label')
+                    .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
+            }
+            else {
+                bars.selectAll('text.nv-bar-label').text('');
+            }
+
+            bars
+                .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
+
+            if (barColor) {
+                if (!disabled) disabled = data.map(function() { return true });
+                bars
+                    .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(  disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i]  })[j]   ).toString(); })
+                    .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(  disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i]  })[j]   ).toString(); });
+            }
+
+            if (stacked)
+                bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
+                    .attr('transform', function(d,i) {
+                        return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
+                    })
+                    .select('rect')
+                    .attr('width', function(d,i) {
+                        return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
+                    })
+                    .attr('height', x.rangeBand() );
+            else
+                bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
+                    .attr('transform', function(d,i) {
+                        //TODO: stacked must be all positive or all negative, not both?
+                        return 'translate(' +
+                            (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
+                            + ',' +
+                            (d.series * x.rangeBand() / data.length
+                                +
+                                x(getX(d,i)) )
+                            + ')'
+                    })
+                    .select('rect')
+                    .attr('height', x.rangeBand() / data.length )
+                    .attr('width', function(d,i) {
+                        return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
+                    });
+
+            //store old scales for use in transitions on update
+            x0 = x.copy();
+            y0 = y.copy();
+
+        });
+
+        renderWatch.renderEnd('multibarHorizontal immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:   {get: function(){return width;}, set: function(_){width=_;}},
+        height:  {get: function(){return height;}, set: function(_){height=_;}},
+        x:       {get: function(){return getX;}, set: function(_){getX=_;}},
+        y:       {get: function(){return getY;}, set: function(_){getY=_;}},
+        yErr:       {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
+        xScale:  {get: function(){return x;}, set: function(_){x=_;}},
+        yScale:  {get: function(){return y;}, set: function(_){y=_;}},
+        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
+        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
+        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
+        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
+        forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
+        stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
+        showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
+        // this shows the group name, seems pointless?
+        //showBarLabels:    {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
+        disabled:     {get: function(){return disabled;}, set: function(_){disabled=_;}},
+        id:           {get: function(){return id;}, set: function(_){id=_;}},
+        valueFormat:  {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
+        valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }},
+        barColor:  {get: function(){return color;}, set: function(_){
+            barColor = nv.utils.getColor(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+nv.models.multiBarHorizontalChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var multibar = nv.models.multiBarHorizontal()
+        , xAxis = nv.models.axis()
+        , yAxis = nv.models.axis()
+        , legend = nv.models.legend().height(30)
+        , controls = nv.models.legend().height(30)
+        ;
+
+    var margin = {top: 30, right: 20, bottom: 50, left: 60}
+        , width = null
+        , height = null
+        , color = nv.utils.defaultColor()
+        , showControls = true
+        , showLegend = true
+        , showXAxis = true
+        , showYAxis = true
+        , stacked = false
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + ' - ' + x + '</h3>' +
+                '<p>' +  y + '</p>'
+        }
+        , x //can be accessed via chart.xScale()
+        , y //can be accessed via chart.yScale()
+        , state = nv.utils.state()
+        , defaultState = null
+        , noData = 'No Data Available.'
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
+        , controlWidth = function() { return showControls ? 180 : 0 }
+        , duration = 250
+        ;
+
+    state.stacked = false; // DEPRECATED Maintained for backward compatibility
+
+    multibar
+        .stacked(stacked)
+    ;
+    xAxis
+        .orient('left')
+        .tickPadding(5)
+        .highlightZero(false)
+        .showMaxMin(false)
+        .tickFormat(function(d) { return d })
+    ;
+    yAxis
+        .orient('bottom')
+        .tickFormat(d3.format(',.1f'))
+    ;
+
+    controls.updateState(false);
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
+            y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
+    };
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled }),
+                stacked: stacked
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.stacked !== undefined)
+                stacked = state.stacked;
+            if (state.active !== undefined)
+                data.forEach(function(series,i) {
+                    series.disabled = !state.active[i];
+                });
+        }
+    };
+
+    var renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(multibar);
+        if (showXAxis) renderWatch.models(xAxis);
+        if (showYAxis) renderWatch.models(yAxis);
+
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+            chart.update = function() { container.transition().duration(duration).call(chart) };
+            chart.container = this;
+
+            stacked = multibar.stacked();
+
+            state
+                .setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            // DEPRECATED set state.disableddisabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display No Data message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            x = multibar.xScale();
+            y = multibar.yScale();
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-x nv-axis');
+            gEnter.append('g').attr('class', 'nv-y nv-axis')
+                .append('g').attr('class', 'nv-zeroLine')
+                .append('line');
+            gEnter.append('g').attr('class', 'nv-barsWrap');
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+            gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+            // Legend
+            if (showLegend) {
+                legend.width(availableWidth - controlWidth());
+
+                if (multibar.barColor())
+                    data.forEach(function(series,i) {
+                        series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
+                    });
+
+                g.select('.nv-legendWrap')
+                    .datum(data)
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                g.select('.nv-legendWrap')
+                    .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
+            }
+
+            // Controls
+            if (showControls) {
+                var controlsData = [
+                    { key: 'Grouped', disabled: multibar.stacked() },
+                    { key: 'Stacked', disabled: !multibar.stacked() }
+                ];
+
+                controls.width(controlWidth()).color(['#444', '#444', '#444']);
+                g.select('.nv-controlsWrap')
+                    .datum(controlsData)
+                    .attr('transform', 'translate(0,' + (-margin.top) +')')
+                    .call(controls);
+            }
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            // Main Chart Component(s)
+            multibar
+                .disabled(data.map(function(series) { return series.disabled }))
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(data.map(function(d,i) {
+                    return d.color || color(d, i);
+                }).filter(function(d,i) { return !data[i].disabled }));
+
+            var barsWrap = g.select('.nv-barsWrap')
+                .datum(data.filter(function(d) { return !d.disabled }));
+
+            barsWrap.transition().call(multibar);
+
+            // Setup Axes
+            if (showXAxis) {
+                xAxis
+                    .scale(x)
+                    .ticks( nv.utils.calcTicksY(availableHeight/24, data) )
+                    .tickSize(-availableWidth, 0);
+
+                g.select('.nv-x.nv-axis').call(xAxis);
+
+                var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
+
+                xTicks
+                    .selectAll('line, text');
+            }
+
+            if (showYAxis) {
+                yAxis
+                    .scale(y)
+                    .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                    .tickSize( -availableHeight, 0);
+
+                g.select('.nv-y.nv-axis')
+                    .attr('transform', 'translate(0,' + availableHeight + ')');
+                g.select('.nv-y.nv-axis').call(yAxis);
+            }
+
+            // Zero line
+            g.select(".nv-zeroLine line")
+                .attr("x1", y(0))
+                .attr("x2", y(0))
+                .attr("y1", 0)
+                .attr("y2", -availableHeight)
+            ;
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState)
+                    state[key] = newState[key];
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            controls.dispatch.on('legendClick', function(d,i) {
+                if (!d.disabled) return;
+                controlsData = controlsData.map(function(s) {
+                    s.disabled = true;
+                    return s;
+                });
+                d.disabled = false;
+
+                switch (d.key) {
+                    case 'Grouped':
+                        multibar.stacked(false);
+                        break;
+                    case 'Stacked':
+                        multibar.stacked(true);
+                        break;
+                }
+
+                state.stacked = multibar.stacked();
+                dispatch.stateChange(state);
+                stacked = multibar.stacked();
+
+                chart.update();
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+            // Update chart from a state object passed to event handler
+            dispatch.on('changeState', function(e) {
+
+                if (typeof e.disabled !== 'undefined') {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+
+                    state.disabled = e.disabled;
+                }
+
+                if (typeof e.stacked !== 'undefined') {
+                    multibar.stacked(e.stacked);
+                    state.stacked = e.stacked;
+                    stacked = e.stacked;
+                }
+
+                chart.update();
+            });
+        });
+        renderWatch.renderEnd('multibar horizontal chart immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    multibar.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    multibar.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.multibar = multibar;
+    chart.legend = legend;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+    chart.state = state;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
+        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
+        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+            multibar.duration(duration);
+            xAxis.duration(duration);
+            yAxis.duration(duration);
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, multibar);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+nv.models.multiChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 30, right: 20, bottom: 50, left: 60},
+        color = nv.utils.defaultColor(),
+        width = null,
+        height = null,
+        showLegend = true,
+        tooltips = true,
+        tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + '</h3>' +
+                '<p>' +  y + ' at ' + x + '</p>'
+        },
+        x,
+        y,
+        noData = 'No Data Available.',
+        yDomain1,
+        yDomain2,
+        getX = function(d) { return d.x },
+        getY = function(d) { return d.y},
+        interpolate = 'monotone'
+        ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var x = d3.scale.linear(),
+        yScale1 = d3.scale.linear(),
+        yScale2 = d3.scale.linear(),
+
+        lines1 = nv.models.line().yScale(yScale1),
+        lines2 = nv.models.line().yScale(yScale2),
+
+        bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
+        bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
+
+        stack1 = nv.models.stackedArea().yScale(yScale1),
+        stack2 = nv.models.stackedArea().yScale(yScale2),
+
+        xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
+        yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
+        yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
+
+        legend = nv.models.legend().height(30),
+        dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
+            y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
+    };
+
+    function chart(selection) {
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+
+            chart.update = function() { container.transition().call(chart); };
+            chart.container = this;
+
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+            var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
+            var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
+            var dataBars1 =  data.filter(function(d) {return d.type == 'bar'  && d.yAxis == 1});
+            var dataBars2 =  data.filter(function(d) {return d.type == 'bar'  && d.yAxis == 2});
+            var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
+            var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
+
+            // Display noData message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
+                .map(function(d) {
+                    return d.values.map(function(d,i) {
+                        return { x: d.x, y: d.y }
+                    })
+                });
+
+            var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
+                .map(function(d) {
+                    return d.values.map(function(d,i) {
+                        return { x: d.x, y: d.y }
+                    })
+                });
+
+            x   .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
+                .range([0, availableWidth]);
+
+            var wrap = container.selectAll('g.wrap.multiChart').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
+
+            gEnter.append('g').attr('class', 'x axis');
+            gEnter.append('g').attr('class', 'y1 axis');
+            gEnter.append('g').attr('class', 'y2 axis');
+            gEnter.append('g').attr('class', 'lines1Wrap');
+            gEnter.append('g').attr('class', 'lines2Wrap');
+            gEnter.append('g').attr('class', 'bars1Wrap');
+            gEnter.append('g').attr('class', 'bars2Wrap');
+            gEnter.append('g').attr('class', 'stack1Wrap');
+            gEnter.append('g').attr('class', 'stack2Wrap');
+            gEnter.append('g').attr('class', 'legendWrap');
+
+            var g = wrap.select('g');
+
+            var color_array = data.map(function(d,i) {
+                return data[i].color || color(d, i);
+            });
+
+            if (showLegend) {
+                legend.color(color_array);
+                legend.width( availableWidth / 2 );
+
+                g.select('.legendWrap')
+                    .datum(data.map(function(series) {
+                        series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
+                        series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
+                        return series;
+                    }))
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                g.select('.legendWrap')
+                    .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
+            }
+
+            lines1
+                .width(availableWidth)
+                .height(availableHeight)
+                .interpolate(interpolate)
+                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
+            lines2
+                .width(availableWidth)
+                .height(availableHeight)
+                .interpolate(interpolate)
+                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
+            bars1
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
+            bars2
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
+            stack1
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
+            stack2
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
+
+            g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            var lines1Wrap = g.select('.lines1Wrap')
+                .datum(
+                    dataLines1.filter(function(d){return !d.disabled})
+                );
+            var bars1Wrap = g.select('.bars1Wrap')
+                .datum(
+                    dataBars1.filter(function(d){return !d.disabled})
+                );
+            var stack1Wrap = g.select('.stack1Wrap')
+                .datum(
+                    dataStack1.filter(function(d){return !d.disabled})
+                );
+
+            var lines2Wrap = g.select('.lines2Wrap')
+                .datum(
+                    dataLines2.filter(function(d){return !d.disabled})
+                );
+            var bars2Wrap = g.select('.bars2Wrap')
+                .datum(
+                    dataBars2.filter(function(d){return !d.disabled})
+                );
+            var stack2Wrap = g.select('.stack2Wrap')
+                .datum(
+                    dataStack2.filter(function(d){return !d.disabled})
+                );
+
+            var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
+                return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
+            }).concat([{x:0, y:0}]) : []
+            var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
+                return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
+            }).concat([{x:0, y:0}]) : []
+
+            yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
+                .range([0, availableHeight])
+
+            yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
+                .range([0, availableHeight])
+
+            lines1.yDomain(yScale1.domain())
+            bars1.yDomain(yScale1.domain())
+            stack1.yDomain(yScale1.domain())
+
+            lines2.yDomain(yScale2.domain())
+            bars2.yDomain(yScale2.domain())
+            stack2.yDomain(yScale2.domain())
+
+            if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
+            if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
+
+            if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
+            if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
+
+            if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
+            if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
+
+            xAxis
+                .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                .tickSize(-availableHeight, 0);
+
+            g.select('.x.axis')
+                .attr('transform', 'translate(0,' + availableHeight + ')');
+            d3.transition(g.select('.x.axis'))
+                .call(xAxis);
+
+            yAxis1
+                .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
+                .tickSize( -availableWidth, 0);
+
+
+            d3.transition(g.select('.y1.axis'))
+                .call(yAxis1);
+
+            yAxis2
+                .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
+                .tickSize( -availableWidth, 0);
+
+            d3.transition(g.select('.y2.axis'))
+                .call(yAxis2);
+
+            g.select('.y1.axis')
+                .classed('nv-disabled', series1.length ? false : true)
+                .attr('transform', 'translate(' + x.range()[0] + ',0)');
+
+            g.select('.y2.axis')
+                .classed('nv-disabled', series2.length ? false : true)
+                .attr('transform', 'translate(' + x.range()[1] + ',0)');
+
+            legend.dispatch.on('stateChange', function(newState) {
+                chart.update();
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+        });
+
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    lines1.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    lines1.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    lines2.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    lines2.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    bars1.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    bars1.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    bars2.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    bars2.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    stack1.dispatch.on('tooltipShow', function(e) {
+        //disable tooltips when value ~= 0
+        //// TODO: consider removing points from voronoi that have 0 value instead of this hack
+        if (!Math.round(stack1.y()(e.point) * 100)) {  // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
+            setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
+            return false;
+        }
+
+        e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
+            dispatch.tooltipShow(e);
+    });
+
+    stack1.dispatch.on('tooltipHide', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    stack2.dispatch.on('tooltipShow', function(e) {
+        //disable tooltips when value ~= 0
+        //// TODO: consider removing points from voronoi that have 0 value instead of this hack
+        if (!Math.round(stack2.y()(e.point) * 100)) {  // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
+            setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
+            return false;
+        }
+
+        e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
+            dispatch.tooltipShow(e);
+    });
+
+    stack2.dispatch.on('tooltipHide', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    lines1.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    lines1.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    lines2.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    lines2.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Global getters and setters
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.lines1 = lines1;
+    chart.lines2 = lines2;
+    chart.bars1 = bars1;
+    chart.bars2 = bars2;
+    chart.stack1 = stack1;
+    chart.stack2 = stack2;
+    chart.xAxis = xAxis;
+    chart.yAxis1 = yAxis1;
+    chart.yAxis2 = yAxis2;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        yDomain1:      {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
+        yDomain2:    {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+        interpolate:    {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }},
+        x: {get: function(){return getX;}, set: function(_){
+            getX = _;
+            lines1.x(_);
+            bars1.x(_);
+        }},
+        y: {get: function(){return getY;}, set: function(_){
+            getY = _;
+            lines1.y(_);
+            bars1.y(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+
+nv.models.ohlcBar = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = null
+        , height = null
+        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+        , x = d3.scale.linear()
+        , y = d3.scale.linear()
+        , getX = function(d) { return d.x }
+        , getY = function(d) { return d.y }
+        , getOpen = function(d) { return d.open }
+        , getClose = function(d) { return d.close }
+        , getHigh = function(d) { return d.high }
+        , getLow = function(d) { return d.low }
+        , forceX = []
+        , forceY = []
+        , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
+        , clipEdge = true
+        , color = nv.utils.defaultColor()
+        , interactive = false
+        , xDomain
+        , yDomain
+        , xRange
+        , yRange
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
+        ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    function chart(selection) {
+        selection.each(function(data) {
+            var container = d3.select(this);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                - margin.left - margin.right;
+            var availableHeight = (height || parseInt(container.style('height')) || 400)
+                - margin.top - margin.bottom;
+
+            nv.utils.initSVG(container);
+
+            // Setup Scales
+            x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
+
+            if (padData)
+                x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
+            else
+                x.range(xRange || [0, availableWidth]);
+
+            y.domain(yDomain || [
+                    d3.min(data[0].values.map(getLow).concat(forceY)),
+                    d3.max(data[0].values.map(getHigh).concat(forceY))
+                ]
+            ).range(yRange || [availableHeight, 0]);
+
+            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
+            if (x.domain()[0] === x.domain()[1])
+                x.domain()[0] ?
+                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
+                    : x.domain([-1,1]);
+
+            if (y.domain()[0] === y.domain()[1])
+                y.domain()[0] ?
+                    y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
+                    : y.domain([-1,1]);
+
+            // Setup containers and skeleton of chart
+            var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
+            var defsEnter = wrapEnter.append('defs');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-ticks');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            container
+                .on('click', function(d,i) {
+                    dispatch.chartClick({
+                        data: d,
+                        index: i,
+                        pos: d3.event,
+                        id: id
+                    });
+                });
+
+            defsEnter.append('clipPath')
+                .attr('id', 'nv-chart-clip-path-' + id)
+                .append('rect');
+
+            wrap.select('#nv-chart-clip-path-' + id + ' rect')
+                .attr('width', availableWidth)
+                .attr('height', availableHeight);
+
+            g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
+
+            var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
+                .data(function(d) { return d });
+            ticks.exit().remove();
+
+            var ticksEnter = ticks.enter().append('path')
+                .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
+                .attr('d', function(d,i) {
+                    var w = (availableWidth / data[0].values.length) * .9;
+                    return 'm0,0l0,'
+                        + (y(getOpen(d,i))
+                            - y(getHigh(d,i)))
+                        + 'l'
+                        + (-w/2)
+                        + ',0l'
+                        + (w/2)
+                        + ',0l0,'
+                        + (y(getLow(d,i)) - y(getOpen(d,i)))
+                        + 'l0,'
+                        + (y(getClose(d,i))
+                            - y(getLow(d,i)))
+                        + 'l'
+                        + (w/2)
+                        + ',0l'
+                        + (-w/2)
+                        + ',0z';
+                })
+                .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
+                .attr('fill', function(d,i) { return color[0]; })
+                .attr('stroke', function(d,i) { return color[0]; })
+                .attr('x', 0 )
+                .attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
+                .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
+
+            // the bar colors are controlled by CSS currently
+            ticks.attr('class', function(d,i,j) {
+                return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
+            });
+
+            d3.transition(ticks)
+                .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
+                .attr('d', function(d,i) {
+                    var w = (availableWidth / data[0].values.length) * .9;
+                    return 'm0,0l0,'
+                        + (y(getOpen(d,i))
+                            - y(getHigh(d,i)))
+                        + 'l'
+                        + (-w/2)
+                        + ',0l'
+                        + (w/2)
+                        + ',0l0,'
+                        + (y(getLow(d,i))
+                            - y(getOpen(d,i)))
+                        + 'l0,'
+                        + (y(getClose(d,i))
+                            - y(getLow(d,i)))
+                        + 'l'
+                        + (w/2)
+                        + ',0l'
+                        + (-w/2)
+                        + ',0z';
+                });
+        });
+
+        return chart;
+    }
+
+
+    //Create methods to allow outside functions to highlight a specific bar.
+    chart.highlightPoint = function(pointIndex, isHoverOver) {
+        chart.clearHighlights();
+        d3.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
+            .classed("hover", isHoverOver)
+        ;
+    };
+
+    chart.clearHighlights = function() {
+        d3.select(".nv-ohlcBar .nv-tick.hover")
+            .classed("hover", false)
+        ;
+    };
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:    {get: function(){return width;}, set: function(_){width=_;}},
+        height:   {get: function(){return height;}, set: function(_){height=_;}},
+        xScale:   {get: function(){return x;}, set: function(_){x=_;}},
+        yScale:   {get: function(){return y;}, set: function(_){y=_;}},
+        xDomain:  {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
+        yDomain:  {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
+        xRange:   {get: function(){return xRange;}, set: function(_){xRange=_;}},
+        yRange:   {get: function(){return yRange;}, set: function(_){yRange=_;}},
+        forceX:   {get: function(){return forceX;}, set: function(_){forceX=_;}},
+        forceY:   {get: function(){return forceY;}, set: function(_){forceY=_;}},
+        padData:  {get: function(){return padData;}, set: function(_){padData=_;}},
+        clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
+        id:       {get: function(){return id;}, set: function(_){id=_;}},
+        interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
+
+        x:     {get: function(){return getX;}, set: function(_){getX=_;}},
+        y:     {get: function(){return getY;}, set: function(_){getY=_;}},
+        open:  {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
+        close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
+        high:  {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
+        low:   {get: function(){return getLow;}, set: function(_){getLow=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    != undefined ? _.top    : margin.top;
+            margin.right  = _.right  != undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   != undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+    return chart;
+};
+
+// Code adapted from Jason Davies' "Parallel Coordinates"
+// http://bl.ocks.org/jasondavies/1341281
+
+nv.models.parallelCoordinates = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 30, right: 10, bottom: 10, left: 10}
+        , width = null
+        , height = null
+        , x = d3.scale.ordinal()
+        , y = {}
+        , dimensions = []
+        , color = nv.utils.defaultColor()
+        , filters = []
+        , active = []
+        , dispatch = d3.dispatch('brush')
+        ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    function chart(selection) {
+        selection.each(function(data) {
+            var container = d3.select(this);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                - margin.left - margin.right;
+            var availableHeight = (height || parseInt(container.style('height')) || 400)
+                - margin.top - margin.bottom;
+
+            nv.utils.initSVG(container);
+
+            active = data; //set all active before first brush call
+
+            //This is a placeholder until this chart is made resizeable
+            chart.update = function() { };
+
+            // Setup Scales
+            x.rangePoints([0, availableWidth], 1).domain(dimensions);
+
+            // Extract the list of dimensions and create a scale for each.
+            dimensions.forEach(function(d) {
+                y[d] = d3.scale.linear()
+                    .domain(d3.extent(data, function(p) { return +p[d]; }))
+                    .range([availableHeight, 0]);
+
+                y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
+
+                return d != 'name';
+            });
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap');
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            var line = d3.svg.line(),
+                axis = d3.svg.axis().orient('left'),
+                background,
+                foreground;
+
+            // Add grey background lines for context.
+            background = gEnter.append('g')
+                .attr('class', 'background')
+                .selectAll('path')
+                .data(data)
+                .enter().append('path')
+                .attr('d', path)
+            ;
+
+            // Add blue foreground lines for focus.
+            foreground = gEnter.append('g')
+                .attr('class', 'foreground')
+                .selectAll('path')
+                .data(data)
+                .enter().append('path')
+                .attr('d', path)
+                .attr('stroke', color)
+            ;
+
+            // Add a group element for each dimension.
+            var dimension = g.selectAll('.dimension')
+                .data(dimensions)
+                .enter().append('g')
+                .attr('class', 'dimension')
+                .attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; });
+
+            // Add an axis and title.
+            dimension.append('g')
+                .attr('class', 'axis')
+                .each(function(d) { d3.select(this).call(axis.scale(y[d])); })
+                .append('text')
+                .attr('text-anchor', 'middle')
+                .attr('y', -9)
+                .text(String);
+
+            // Add and store a brush for each axis.
+            dimension.append('g')
+                .attr('class', 'brush')
+                .each(function(d) { d3.select(this).call(y[d].brush); })
+                .selectAll('rect')
+                .attr('x', -8)
+                .attr('width', 16);
+
+            // Returns the path for a given data point.
+            function path(d) {
+                return line(dimensions.map(function(p) { return [x(p), y[p](d[p])]; }));
+            }
+
+            // Handles a brush event, toggling the display of foreground lines.
+            function brush() {
+                var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
+                    extents = actives.map(function(p) { return y[p].brush.extent(); });
+
+                filters = []; //erase current filters
+                actives.forEach(function(d,i) {
+                    filters[i] = {
+                        dimension: d,
+                        extent: extents[i]
+                    }
+                });
+
+                active = []; //erase current active list
+                foreground.style('display', function(d) {
+                    var isActive = actives.every(function(p, i) {
+                        return extents[i][0] <= d[p] && d[p] <= extents[i][1];
+                    });
+                    if (isActive) active.push(d);
+                    return isActive ? null : 'none';
+                });
+
+                dispatch.brush({
+                    filters: filters,
+                    active: active
+                });
+            }
+        });
+
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        dimensions: {get: function(){return dimensions;}, set: function(_){dimensions=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
+            margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
+            margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+            margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+    return chart;
+};
+nv.models.pie = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = 500
+        , height = 500
+        , getX = function(d) { return d.x }
+        , getY = function(d) { return d.y }
+        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+        , color = nv.utils.defaultColor()
+        , valueFormat = d3.format(',.2f')
+        , labelFormat = d3.format('%')
+        , showLabels = true
+        , pieLabelsOutside = true
+        , donutLabelsOutside = false
+        , labelType = "key"
+        , labelThreshold = .02 //if slice percentage is under this, don't show label
+        , donut = false
+        , title = false
+        , growOnHover = true
+        , titleOffset = 0
+        , labelSunbeamLayout = false
+        , startAngle = false
+        , padAngle = false
+        , endAngle = false
+        , cornerRadius = 0
+        , donutRatio = 0.5
+        , duration = 250
+        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
+        ;
+
+
+    //============================================================
+    // chart function
+    //------------------------------------------------------------
+
+    var renderWatch = nv.utils.renderWatch(dispatch);
+
+    function chart(selection) {
+        renderWatch.reset();
+        selection.each(function(data) {
+            var availableWidth = width - margin.left - margin.right
+                ,availableHeight = height - margin.top - margin.bottom
+                ,radius = Math.min(availableWidth, availableHeight) / 2
+                ,arcRadius = radius-(radius / 5)
+                ,container = d3.select(this)
+                ;
+            nv.utils.initSVG(container);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
+            var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+            var g_pie = gEnter.append('g').attr('class', 'nv-pie');
+            gEnter.append('g').attr('class', 'nv-pieLabels');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+            g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
+            g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
+
+            //
+            container.on('click', function(d,i) {
+                dispatch.chartClick({
+                    data: d,
+                    index: i,
+                    pos: d3.event,
+                    id: id
+                });
+            });
+
+
+            var arc = d3.svg.arc().outerRadius(arcRadius);
+            var arcOver = d3.svg.arc().outerRadius(arcRadius + 5);
+
+            if (startAngle) {
+                arc.startAngle(startAngle);
+                arcOver.startAngle(startAngle);
+            }
+            if (endAngle) {
+                arc.endAngle(endAngle);
+                arcOver.endAngle(endAngle);
+            }
+            if (donut) {
+                arc.innerRadius(radius * donutRatio);
+                arcOver.innerRadius(radius * donutRatio);
+            }
+
+            // Setup the Pie chart and choose the data element
+            var pie = d3.layout.pie()
+                .sort(null)
+                .value(function(d) { return d.disabled ? 0 : getY(d) });
+
+            // padAngle added in d3 3.5
+            if (pie.padAngle && padAngle) {
+                pie.padAngle(padAngle);
+            }
+
+            if (arc.cornerRadius && cornerRadius) {
+                arc.cornerRadius(cornerRadius);
+                arcOver.cornerRadius(cornerRadius);
+            }
+
+            // if title is specified and donut, put it in the middle
+            if (donut && title) {
+                var title_g = g_pie.append('g').attr('class', 'nv-pie');
+
+                title_g.append("text")
+                    .style("text-anchor", "middle")
+                    .attr('class', 'nv-pie-title')
+                    .text(function (d) {
+                        return title;
+                    })
+                    .attr("dy", "0.35em") // trick to vertically center text
+                    .attr('transform', function(d, i) {
+                        return 'translate(0, '+ titleOffset + ')';
+                    });
+            }
+
+            var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
+            var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
+
+            slices.exit().remove();
+            pieLabels.exit().remove();
+
+            var ae = slices.enter().append('g')
+            ae.attr('class', 'nv-slice')
+            ae.on('mouseover', function(d,i){
+                d3.select(this).classed('hover', true);
+                if (growOnHover) {
+                    d3.select(this).select("path").transition()
+                        .duration(70)
+                        .attr("d", arcOver);
+                }
+                dispatch.elementMouseover({
+                    label: getX(d.data),
+                    value: getY(d.data),
+                    point: d.data,
+                    pointIndex: i,
+                    pos: [d3.event.pageX, d3.event.pageY],
+                    id: id,
+                    color: d3.select(this).style("fill")
+                });
+            });
+            ae.on('mouseout', function(d,i){
+                d3.select(this).classed('hover', false);
+                if (growOnHover) {
+                    d3.select(this).select("path").transition()
+                        .duration(50)
+                        .attr("d", arc);
+                }
+                dispatch.elementMouseout({
+                    label: getX(d.data),
+                    value: getY(d.data),
+                    point: d.data,
+                    index: i,
+                    id: id
+                });
+            });
+            ae.on('click', function(d,i) {
+                dispatch.elementClick({
+                    label: getX(d.data),
+                    value: getY(d.data),
+                    point: d.data,
+                    index: i,
+                    pos: d3.event,
+                    id: id
+                });
+                d3.event.stopPropagation();
+            });
+            ae.on('dblclick', function(d,i) {
+                dispatch.elementDblClick({
+                    label: getX(d.data),
+                    value: getY(d.data),
+                    point: d.data,
+                    index: i,
+                    pos: d3.event,
+                    id: id
+                });
+                d3.event.stopPropagation();
+            });
+
+            slices.attr('fill', function(d,i) { return color(d, i); })
+            slices.attr('stroke', function(d,i) { return color(d, i); });
+
+            var paths = ae.append('path').each(function(d) {
+                this._current = d;
+            });
+
+            slices.select('path')
+                .transition()
+                .attr('d', arc)
+                .attrTween('d', arcTween);
+
+            if (showLabels) {
+                // This does the normal label
+                var labelsArc = d3.svg.arc().innerRadius(0);
+
+                if (pieLabelsOutside){
+                    var labelsArc = arc;
+                }
+
+                if (donutLabelsOutside) {
+                    labelsArc = d3.svg.arc().outerRadius(arc.outerRadius());
+                }
+
+                pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
+                    var group = d3.select(this);
+
+                    group.attr('transform', function(d) {
+                        if (labelSunbeamLayout) {
+                            d.outerRadius = arcRadius + 10; // Set Outer Coordinate
+                            d.innerRadius = arcRadius + 15; // Set Inner Coordinate
+                            var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
+                            if ((d.startAngle+d.endAngle)/2 < Math.PI) {
+                                rotateAngle -= 90;
+                            } else {
+                                rotateAngle += 90;
+                            }
+                            return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
+                        } else {
+                            d.outerRadius = radius + 10; // Set Outer Coordinate
+                            d.innerRadius = radius + 15; // Set Inner Coordinate
+                            return 'translate(' + labelsArc.centroid(d) + ')'
+                        }
+                    });
+
+                    group.append('rect')
+                        .style('stroke', '#fff')
+                        .style('fill', '#fff')
+                        .attr("rx", 3)
+                        .attr("ry", 3);
+
+                    group.append('text')
+                        .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned
+                        .style('fill', '#000')
+
+                });
+
+                var labelLocationHash = {};
+                var avgHeight = 14;
+                var avgWidth = 140;
+                var createHashKey = function(coordinates) {
+                    return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
+                };
+
+                pieLabels.watchTransition(renderWatch,'pie labels').attr('transform', function(d) {
+                    if (labelSunbeamLayout) {
+                        d.outerRadius = arcRadius + 10; // Set Outer Coordinate
+                        d.innerRadius = arcRadius + 15; // Set Inner Coordinate
+                        var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
+                        if ((d.startAngle+d.endAngle)/2 < Math.PI) {
+                            rotateAngle -= 90;
+                        } else {
+                            rotateAngle += 90;
+                        }
+                        return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
+                    } else {
+                        d.outerRadius = radius + 10; // Set Outer Coordinate
+                        d.innerRadius = radius + 15; // Set Inner Coordinate
+
+                        /*
+                         Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
+                         Each label location is hashed, and if a hash collision occurs, we assume an overlap.
+                         Adjust the label's y-position to remove the overlap.
+                         */
+                        var center = labelsArc.centroid(d);
+                        if(d.value){
+                            var hashKey = createHashKey(center);
+                            if (labelLocationHash[hashKey]) {
+                                center[1] -= avgHeight;
+                            }
+                            labelLocationHash[createHashKey(center)] = true;
+                        }
+                        return 'translate(' + center + ')'
+                    }
+                });
+
+                pieLabels.select(".nv-label text")
+                    .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned
+                    .text(function(d, i) {
+                        var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
+                        var labelTypes = {
+                            "key" : getX(d.data),
+                            "value": getY(d.data),
+                            "percent": labelFormat(percent)
+                        };
+                        return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
+                    })
+                ;
+            }
+
+
+            // Computes the angle of an arc, converting from radians to degrees.
+            function angle(d) {
+                var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
+                return a > 90 ? a - 180 : a;
+            }
+
+            function arcTween(a) {
+                a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
+                a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
+                if (!donut) a.innerRadius = 0;
+                var i = d3.interpolate(this._current, a);
+                this._current = i(0);
+                return function(t) {
+                    return arc(i(t));
+                };
+            }
+        });
+
+        renderWatch.renderEnd('pie immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
+        title:      {get: function(){return title;}, set: function(_){title=_;}},
+        titleOffset:    {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
+        labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
+        labelFormat:    {get: function(){return labelFormat;}, set: function(_){labelFormat=_;}},
+        valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
+        x:          {get: function(){return getX;}, set: function(_){getX=_;}},
+        id:         {get: function(){return id;}, set: function(_){id=_;}},
+        endAngle:   {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
+        startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
+        padAngle:   {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
+        cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
+        donutRatio:   {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
+        pieLabelsOutside:   {get: function(){return pieLabelsOutside;}, set: function(_){pieLabelsOutside=_;}},
+        donutLabelsOutside: {get: function(){return donutLabelsOutside;}, set: function(_){donutLabelsOutside=_;}},
+        labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
+        donut:              {get: function(){return donut;}, set: function(_){donut=_;}},
+        growOnHover:        {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
+            margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
+            margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+            margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
+        }},
+        y: {get: function(){return getY;}, set: function(_){
+            getY=d3.functor(_);
+        }},
+        color: {get: function(){return color;}, set: function(_){
+            color=nv.utils.getColor(_);
+        }},
+        labelType:          {get: function(){return labelType;}, set: function(_){
+            labelType= _ || 'key';
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+    return chart;
+};
+nv.models.pieChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var pie = nv.models.pie();
+    var legend = nv.models.legend();
+
+    var margin = {top: 30, right: 20, bottom: 20, left: 20}
+        , width = null
+        , height = null
+        , showLegend = true
+        , color = nv.utils.defaultColor()
+        , tooltips = true
+        , tooltip = function(key, y, e, graph) {
+            return '<h3 style="background-color: '
+                + e.color + '">' + key + '</h3>'
+                + '<p>' +  y + '</p>';
+        }
+        , state = nv.utils.state()
+        , defaultState = null
+        , noData = "No Data Available."
+        , duration = 250
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
+        ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var showTooltip = function(e, offsetElement) {
+        var tooltipLabel = pie.x()(e.point);
+        var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
+            top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
+            y = pie.valueFormat()(pie.y()(e.point)),
+            content = tooltip(tooltipLabel, y, e, chart)
+            ;
+        nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+    };
+
+    var renderWatch = nv.utils.renderWatch(dispatch);
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled })
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.active !== undefined) {
+                data.forEach(function (series, i) {
+                    series.disabled = !state.active[i];
+                });
+            }
+        }
+    };
+
+    //============================================================
+    // Chart function
+    //------------------------------------------------------------
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(pie);
+
+        selection.each(function(data) {
+            var container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            var that = this;
+            var availableWidth = (width || parseInt(container.style('width'), 10) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height'), 10) || 400)
+                    - margin.top - margin.bottom
+                ;
+
+            chart.update = function() { container.transition().call(chart); };
+            chart.container = this;
+
+            state.setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            //set state.disabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display No Data message if there's nothing to show.
+            if (!data || !data.length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-pieWrap');
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+
+            // Legend
+            if (showLegend) {
+                legend.width( availableWidth ).key(pie.x());
+
+                wrap.select('.nv-legendWrap')
+                    .datum(data)
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                wrap.select('.nv-legendWrap')
+                    .attr('transform', 'translate(0,' + (-margin.top) +')');
+            }
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            // Main Chart Component(s)
+            pie.width(availableWidth).height(availableHeight);
+            var pieWrap = g.select('.nv-pieWrap').datum([data]);
+            d3.transition(pieWrap).call(pie);
+
+            // Event Handling/Dispatching (in chart's scope)
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState) {
+                    state[key] = newState[key];
+                }
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            pie.dispatch.on('elementMouseout.tooltip', function(e) {
+                dispatch.tooltipHide(e);
+            });
+
+            // Update chart from a state object passed to event handler
+            dispatch.on('changeState', function(e) {
+                if (typeof e.disabled !== 'undefined') {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+                    state.disabled = e.disabled;
+                }
+                chart.update();
+            });
+
+        });
+
+        renderWatch.renderEnd('pieChart immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    pie.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    dispatch.on('tooltipShow', function(e) {
+        if (tooltips) showTooltip(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.legend = legend;
+    chart.dispatch = dispatch;
+    chart.pie = pie;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    // use Object get/set functionality to map between vars and chart functions
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        noData:         {get: function(){return noData;},         set: function(_){noData=_;}},
+        tooltipContent: {get: function(){return tooltip;},        set: function(_){tooltip=_;}},
+        tooltips:       {get: function(){return tooltips;},       set: function(_){tooltips=_;}},
+        showLegend:     {get: function(){return showLegend;},     set: function(_){showLegend=_;}},
+        defaultState:   {get: function(){return defaultState;},   set: function(_){defaultState=_;}},
+        // options that require extra logic in the setter
+        color: {get: function(){return color;}, set: function(_){
+            color = _;
+            legend.color(color);
+            pie.color(color);
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+        }}
+    });
+    nv.utils.inheritOptions(chart, pie);
+    nv.utils.initOptions(chart);
+    return chart;
+};
+
+nv.models.scatter = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin       = {top: 0, right: 0, bottom: 0, left: 0}
+        , width        = null
+        , height       = null
+        , color        = nv.utils.defaultColor() // chooses color
+        , id           = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
+        , x            = d3.scale.linear()
+        , y            = d3.scale.linear()
+        , z            = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
+        , getX         = function(d) { return d.x } // accessor to get the x value
+        , getY         = function(d) { return d.y } // accessor to get the y value
+        , getSize      = function(d) { return d.size || 1} // accessor to get the point size
+        , getShape     = function(d) { return d.shape || 'circle' } // accessor to get point shape
+        , forceX       = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
+        , forceY       = [] // List of numbers to Force into the Y scale
+        , forceSize    = [] // List of numbers to Force into the Size scale
+        , interactive  = true // If true, plots a voronoi overlay for advanced point intersection
+        , pointActive  = function(d) { return !d.notActive } // any points that return false will be filtered out
+        , padData      = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
+        , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
+        , clipEdge     = false // if true, masks points within x and y scale
+        , clipVoronoi  = true // if true, masks each point with a circle... can turn off to slightly increase performance
+        , clipRadius   = function() { return 25 } // function to get the radius for voronoi point clips
+        , xDomain      = null // Override x domain (skips the calculation from data)
+        , yDomain      = null // Override y domain
+        , xRange       = null // Override x range
+        , yRange       = null // Override y range
+        , sizeDomain   = null // Override point size domain
+        , sizeRange    = null
+        , singlePoint  = false
+        , dispatch     = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
+        , useVoronoi   = true
+        , duration     = 250
+        ;
+
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var x0, y0, z0 // used to store previous scales
+        , timeoutID
+        , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
+        , renderWatch = nv.utils.renderWatch(dispatch, duration)
+        ;
+
+    function chart(selection) {
+        renderWatch.reset();
+        selection.each(function(data) {
+            var container = d3.select(this);
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                - margin.left - margin.right;
+            var availableHeight = (height || parseInt(container.style('height')) || 400)
+                - margin.top - margin.bottom;
+
+            nv.utils.initSVG(container);
+
+            //add series index to each data point for reference
+            data.forEach(function(series, i) {
+                series.values.forEach(function(point) {
+                    point.series = i;
+                });
+            });
+
+            // Setup Scales
+            // remap and flatten the data for use in calculating the scales' domains
+            var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
+                d3.merge(
+                    data.map(function(d) {
+                        return d.values.map(function(d,i) {
+                            return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
+                        })
+                    })
+                );
+
+            x   .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
+
+            if (padData && data[0])
+                x.range(xRange || [(availableWidth * padDataOuter +  availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length)  ]);
+            //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
+            else
+                x.range(xRange || [0, availableWidth]);
+
+            y   .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
+                .range(yRange || [availableHeight, 0]);
+
+            z   .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
+                .range(sizeRange || [16, 256]);
+
+            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
+            if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
+            if (x.domain()[0] === x.domain()[1])
+                x.domain()[0] ?
+                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
+                    : x.domain([-1,1]);
+
+            if (y.domain()[0] === y.domain()[1])
+                y.domain()[0] ?
+                    y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
+                    : y.domain([-1,1]);
+
+            if ( isNaN(x.domain()[0])) {
+                x.domain([-1,1]);
+            }
+
+            if ( isNaN(y.domain()[0])) {
+                y.domain([-1,1]);
+            }
+
+            x0 = x0 || x;
+            y0 = y0 || y;
+            z0 = z0 || z;
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
+            var defsEnter = wrapEnter.append('defs');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-groups');
+            gEnter.append('g').attr('class', 'nv-point-paths');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            defsEnter.append('clipPath')
+                .attr('id', 'nv-edge-clip-' + id)
+                .append('rect');
+
+            wrap.select('#nv-edge-clip-' + id + ' rect')
+                .attr('width', availableWidth)
+                .attr('height', (availableHeight > 0) ? availableHeight : 0);
+
+            g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
+
+            function updateInteractiveLayer() {
+
+                if (!interactive) return false;
+
+                var eventElements;
+
+                var vertices = d3.merge(data.map(function(group, groupIndex) {
+                        return group.values
+                            .map(function(point, pointIndex) {
+                                // *Adding noise to make duplicates very unlikely
+                                // *Injecting series and point index for reference
+                                /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
+                                 */
+                                var pX = getX(point,pointIndex);
+                                var pY = getY(point,pointIndex);
+
+                                return [x(pX)+ Math.random() * 1e-7,
+                                        y(pY)+ Math.random() * 1e-7,
+                                    groupIndex,
+                                    pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
+                            })
+                            .filter(function(pointArray, pointIndex) {
+                                return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
+                            })
+                    })
+                );
+
+                //inject series and point index for reference into voronoi
+                if (useVoronoi === true) {
+
+                    if(vertices.length < 3) {
+                        // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
+                        vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
+                        vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
+                        vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
+                        vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
+                    }
+
+                    // keep voronoi sections from going more than 10 outside of graph
+                    // to avoid overlap with other things like legend etc
+                    var bounds = d3.geom.polygon([
+                        [-10,-10],
+                        [-10,height + 10],
+                        [width + 10,height + 10],
+                        [width + 10,-10]
+                    ]);
+
+                    var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
+                        return {
+                            'data': bounds.clip(d),
+                            'series': vertices[i][2],
+                            'point': vertices[i][3]
+                        }
+                    });
+
+                    // nuke all voronoi paths on reload and recreate them
+                    wrap.select('.nv-point-paths').selectAll('path').remove();
+                    var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
+                    pointPaths
+                        .enter().append("svg:path")
+                        .attr("d", function(d) {
+                            if (!d || !d.data || d.data.length === 0)
+                                return 'M 0 0';
+                            else
+                                return "M" + d.data.join(",") + "Z";
+                        })
+                        .attr("id", function(d,i) {
+                            return "nv-path-"+i; })
+                        .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; })
+                        ;
+                        // chain these to above to see the voronoi elements (good for debugging)
+                        //.style("fill", d3.rgb(230, 230, 230))
+                        //.style('fill-opacity', 0.4)
+                        //.style('stroke-opacity', 1)
+                        //.style("stroke", d3.rgb(200,200,200));
+
+                    if (clipVoronoi) {
+                        // voronoi sections are already set to clip,
+                        // just create the circles with the IDs they expect
+                        var clips = wrap.append("svg:g").attr("id", "nv-point-clips");
+                        clips.selectAll("clipPath")
+                            .data(vertices)
+                            .enter().append("svg:clipPath")
+                            .attr("id", function(d, i) { return "nv-clip-"+i;})
+                            .append("svg:circle")
+                            .attr('cx', function(d) { return d[0]; })
+                            .attr('cy', function(d) { return d[1]; })
+                            .attr('r', clipRadius);
+                    }
+
+                    var mouseEventCallback = function(d,mDispatch) {
+                        if (needsUpdate) return 0;
+                        var series = data[d.series];
+                        if (typeof series === 'undefined') return;
+                        var point  = series.values[d.point];
+
+                        mDispatch({
+                            point: point,
+                            series: series,
+                            pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
+                            seriesIndex: d.series,
+                            pointIndex: d.point
+                        });
+                    };
+
+                    pointPaths
+                        .on('click', function(d) {
+                            mouseEventCallback(d, dispatch.elementClick);
+                        })
+                        .on('dblclick', function(d) {
+                            mouseEventCallback(d, dispatch.elementDblClick);
+                        })
+                        .on('mouseover', function(d) {
+                            mouseEventCallback(d, dispatch.elementMouseover);
+                        })
+                        .on('mouseout', function(d, i) {
+                            mouseEventCallback(d, dispatch.elementMouseout);
+                        });
+
+                } else {
+                    /*
+                     // bring data in form needed for click handlers
+                     var dataWithPoints = vertices.map(function(d, i) {
+                     return {
+                     'data': d,
+                     'series': vertices[i][2],
+                     'point': vertices[i][3]
+                     }
+                     });
+                     */
+
+                    // add event handlers to points instead voronoi paths
+                    wrap.select('.nv-groups').selectAll('.nv-group')
+                        .selectAll('.nv-point')
+                        //.data(dataWithPoints)
+                        //.style('pointer-events', 'auto') // recativate events, disabled by css
+                        .on('click', function(d,i) {
+                            //nv.log('test', d, i);
+                            if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
+                            var series = data[d.series],
+                                point  = series.values[i];
+
+                            dispatch.elementClick({
+                                point: point,
+                                series: series,
+                                pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
+                                seriesIndex: d.series,
+                                pointIndex: i
+                            });
+                        })
+                        .on('mouseover', function(d,i) {
+                            if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
+                            var series = data[d.series],
+                                point  = series.values[i];
+
+                            dispatch.elementMouseover({
+                                point: point,
+                                series: series,
+                                pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
+                                seriesIndex: d.series,
+                                pointIndex: i
+                            });
+                        })
+                        .on('mouseout', function(d,i) {
+                            if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
+                            var series = data[d.series],
+                                point  = series.values[i];
+
+                            dispatch.elementMouseout({
+                                point: point,
+                                series: series,
+                                seriesIndex: d.series,
+                                pointIndex: i
+                            });
+                        });
+                }
+
+                needsUpdate = false;
+            }
+
+            needsUpdate = true;
+            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+                .data(function(d) { return d }, function(d) { return d.key });
+            groups.enter().append('g')
+                .style('stroke-opacity', 1e-6)
+                .style('fill-opacity', 1e-6);
+            groups.exit()
+                .remove();
+            groups
+                .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+                .classed('hover', function(d) { return d.hover });
+            groups.watchTransition(renderWatch, 'scatter: groups')
+                .style('fill', function(d,i) { return color(d, i) })
+                .style('stroke', function(d,i) { return color(d, i) })
+                .style('stroke-opacity', 1)
+                .style('fill-opacity', .5);
+
+            // create the points
+            var points = groups.selectAll('path.nv-point')
+                .data(function(d) { return d.values });
+            points.enter().append('path')
+                .style('fill', function (d,i) { return d.color })
+                .style('stroke', function (d,i) { return d.color })
+                .attr('transform', function(d,i) {
+                    return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
+                })
+                .attr('d',
+                    nv.utils.symbol()
+                    .type(getShape)
+                    .size(function(d,i) { return z(getSize(d,i)) })
+            );
+            points.exit().remove();
+            groups.exit().selectAll('path.nv-point')
+                .watchTransition(renderWatch, 'scatter exit')
+                .attr('transform', function(d,i) {
+                    return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
+                })
+                .remove();
+            points.each(function(d,i) {
+                d3.select(this)
+                    .classed('nv-point', true)
+                    .classed('nv-point-' + i, true)
+                    .classed('hover',false)
+                ;
+            });
+            points
+                .watchTransition(renderWatch, 'scatter points')
+                .attr('transform', function(d,i) {
+                    //nv.log(d,i,getX(d,i), x(getX(d,i)));
+                    return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
+                })
+                .attr('d',
+                    nv.utils.symbol()
+                    .type(getShape)
+                    .size(function(d,i) { return z(getSize(d,i)) })
+            );
+
+            // Delay updating the invisible interactive layer for smoother animation
+            clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
+            timeoutID = setTimeout(updateInteractiveLayer, 300);
+            //updateInteractiveLayer();
+
+            //store old scales for use in transitions on update
+            x0 = x.copy();
+            y0 = y.copy();
+            z0 = z.copy();
+
+        });
+        renderWatch.renderEnd('scatter immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    // utility function calls provided by this chart
+    chart._calls = new function() {
+        this.clearHighlights = function () {
+            d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover", false);
+            return null;
+        };
+        this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
+            d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
+                .classed("hover", isHoverOver);
+        };
+    };
+
+    // trigger calls from events too
+    dispatch.on('elementMouseover.point', function(d) {
+        if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
+    });
+
+    dispatch.on('elementMouseout.point', function(d) {
+        if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
+    });
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:        {get: function(){return width;}, set: function(_){width=_;}},
+        height:       {get: function(){return height;}, set: function(_){height=_;}},
+        xScale:       {get: function(){return x;}, set: function(_){x=_;}},
+        yScale:       {get: function(){return y;}, set: function(_){y=_;}},
+        pointScale:   {get: function(){return z;}, set: function(_){z=_;}},
+        xDomain:      {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
+        yDomain:      {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
+        pointDomain:  {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
+        xRange:       {get: function(){return xRange;}, set: function(_){xRange=_;}},
+        yRange:       {get: function(){return yRange;}, set: function(_){yRange=_;}},
+        pointRange:   {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
+        forceX:       {get: function(){return forceX;}, set: function(_){forceX=_;}},
+        forceY:       {get: function(){return forceY;}, set: function(_){forceY=_;}},
+        forcePoint:   {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
+        interactive:  {get: function(){return interactive;}, set: function(_){interactive=_;}},
+        pointActive:  {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
+        padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
+        padData:      {get: function(){return padData;}, set: function(_){padData=_;}},
+        clipEdge:     {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
+        clipVoronoi:  {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
+        clipRadius:   {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
+        id:           {get: function(){return id;}, set: function(_){id=_;}},
+
+
+        // simple functor options
+        x:     {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
+        y:     {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
+        pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
+        pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+        }},
+        color: {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }},
+        useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
+            useVoronoi = _;
+            if (useVoronoi === false) {
+                clipVoronoi = false;
+            }
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+    return chart;
+};
+
+nv.models.scatterChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var scatter      = nv.models.scatter()
+        , xAxis        = nv.models.axis()
+        , yAxis        = nv.models.axis()
+        , legend       = nv.models.legend()
+        , distX        = nv.models.distribution()
+        , distY        = nv.models.distribution()
+        ;
+
+    var margin       = {top: 30, right: 20, bottom: 50, left: 75}
+        , width        = null
+        , height       = null
+        , color        = nv.utils.defaultColor()
+        , x            = scatter.xScale()
+        , y            = scatter.yScale()
+        , showDistX    = false
+        , showDistY    = false
+        , showLegend   = true
+        , showXAxis    = true
+        , showYAxis    = true
+        , rightAlignYAxis = false
+        , tooltips     = true
+        , tooltipX     = function(key, x, y) { return '<strong>' + x + '</strong>' }
+        , tooltipY     = function(key, x, y) { return '<strong>' + y + '</strong>' }
+        , tooltip      = function(key, x, y, date) { return '<h3>' + key + '</h3>'
+            + '<p>' + date + '</p>' }
+        , state = nv.utils.state()
+        , defaultState = null
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
+        , noData       = "No Data Available."
+        , duration = 250
+        ;
+
+    scatter
+        .xScale(x)
+        .yScale(y)
+    ;
+    xAxis
+        .orient('bottom')
+        .tickPadding(10)
+    ;
+    yAxis
+        .orient((rightAlignYAxis) ? 'right' : 'left')
+        .tickPadding(10)
+    ;
+    distX
+        .axis('x')
+    ;
+    distY
+        .axis('y')
+    ;
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var x0, y0
+        , renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    var showTooltip = function(e, offsetElement) {
+        //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
+            leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
+            topY = e.pos[1] + ( offsetElement.offsetTop || 0),
+            xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
+            yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
+
+        if( tooltipX != null )
+            nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
+        if( tooltipY != null )
+            nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
+        if( tooltip != null )
+            nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
+    };
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled })
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.active !== undefined)
+                data.forEach(function(series,i) {
+                    series.disabled = !state.active[i];
+                });
+        }
+    };
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(scatter);
+        if (showXAxis) renderWatch.models(xAxis);
+        if (showYAxis) renderWatch.models(yAxis);
+        if (showDistX) renderWatch.models(distX);
+        if (showDistY) renderWatch.models(distY);
+
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+            chart.update = function() {
+                if (duration === 0)
+                    container.call(chart);
+                else
+                    container.transition().duration(duration).call(chart);
+            };
+            chart.container = this;
+
+            state
+                .setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            // DEPRECATED set state.disableddisabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display noData message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                renderWatch.renderEnd('scatter immediate');
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            x = scatter.xScale();
+            y = scatter.yScale();
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            // background for pointer events
+            gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
+
+            gEnter.append('g').attr('class', 'nv-x nv-axis');
+            gEnter.append('g').attr('class', 'nv-y nv-axis');
+            gEnter.append('g').attr('class', 'nv-scatterWrap');
+            gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
+            gEnter.append('g').attr('class', 'nv-distWrap');
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            if (rightAlignYAxis) {
+                g.select(".nv-y.nv-axis")
+                    .attr("transform", "translate(" + availableWidth + ",0)");
+            }
+
+            // Legend
+            if (showLegend) {
+                legend.width( availableWidth / 2 );
+
+                wrap.select('.nv-legendWrap')
+                    .datum(data)
+                    .call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                wrap.select('.nv-legendWrap')
+                    .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
+            }
+
+            // Main Chart Component(s)
+            scatter
+                .width(availableWidth)
+                .height(availableHeight)
+                .color(data.map(function(d,i) {
+                    return d.color || color(d, i);
+                }).filter(function(d,i) { return !data[i].disabled }));
+
+            wrap.select('.nv-scatterWrap')
+                .datum(data.filter(function(d) { return !d.disabled }))
+                .call(scatter);
+
+
+            wrap.select('.nv-regressionLinesWrap')
+                .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
+
+            var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
+                .data(function (d) {
+                    return d;
+                });
+
+            regWrap.enter().append('g').attr('class', 'nv-regLines');
+
+            var regLine = regWrap.selectAll('.nv-regLine')
+                .data(function (d) {
+                    return [d]
+                });
+
+            regLine.enter()
+                .append('line').attr('class', 'nv-regLine')
+                .style('stroke-opacity', 0);
+
+            // don't add lines unless we have slope and intercept to use
+            regLine.filter(function(d) {
+                return d.intercept && d.slope;
+            })
+                .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
+                .attr('x1', x.range()[0])
+                .attr('x2', x.range()[1])
+                .attr('y1', function (d, i) {
+                    return y(x.domain()[0] * d.slope + d.intercept)
+                })
+                .attr('y2', function (d, i) {
+                    return y(x.domain()[1] * d.slope + d.intercept)
+                })
+                .style('stroke', function (d, i, j) {
+                    return color(d, j)
+                })
+                .style('stroke-opacity', function (d, i) {
+                    return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
+                });
+
+            // Setup Axes
+            if (showXAxis) {
+                xAxis
+                    .scale(x)
+                    .ticks( xAxis.ticks() ? xAxis.ticks() : nv.utils.calcTicksX(availableWidth/100, data) )
+                    .tickSize( -availableHeight , 0);
+
+                g.select('.nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + y.range()[0] + ')')
+                    .call(xAxis);
+            }
+
+            if (showYAxis) {
+                yAxis
+                    .scale(y)
+                    .ticks( yAxis.ticks() ? yAxis.ticks() : nv.utils.calcTicksY(availableHeight/36, data) )
+                    .tickSize( -availableWidth, 0);
+
+                g.select('.nv-y.nv-axis')
+                    .call(yAxis);
+            }
+
+
+            if (showDistX) {
+                distX
+                    .getData(scatter.x())
+                    .scale(x)
+                    .width(availableWidth)
+                    .color(data.map(function(d,i) {
+                        return d.color || color(d, i);
+                    }).filter(function(d,i) { return !data[i].disabled }));
+                gEnter.select('.nv-distWrap').append('g')
+                    .attr('class', 'nv-distributionX');
+                g.select('.nv-distributionX')
+                    .attr('transform', 'translate(0,' + y.range()[0] + ')')
+                    .datum(data.filter(function(d) { return !d.disabled }))
+                    .call(distX);
+            }
+
+            if (showDistY) {
+                distY
+                    .getData(scatter.y())
+                    .scale(y)
+                    .width(availableHeight)
+                    .color(data.map(function(d,i) {
+                        return d.color || color(d, i);
+                    }).filter(function(d,i) { return !data[i].disabled }));
+                gEnter.select('.nv-distWrap').append('g')
+                    .attr('class', 'nv-distributionY');
+                g.select('.nv-distributionY')
+                    .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
+                    .datum(data.filter(function(d) { return !d.disabled }))
+                    .call(distY);
+            }
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState)
+                    state[key] = newState[key];
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+
+            scatter.dispatch.on('elementMouseover.tooltip', function(e) {
+                d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
+                    .attr('y1', e.pos[1] - availableHeight);
+                d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
+                    .attr('x2', e.pos[0] + distX.size());
+
+                e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+                dispatch.tooltipShow(e);
+            });
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+            // Update chart from a state object passed to event handler
+            dispatch.on('changeState', function(e) {
+
+                if (typeof e.disabled !== 'undefined') {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+
+                    state.disabled = e.disabled;
+                }
+
+                chart.update();
+            });
+
+            //store old scales for use in transitions on update
+            x0 = x.copy();
+            y0 = y.copy();
+
+        });
+
+        renderWatch.renderEnd('scatter with line immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    scatter.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+
+        d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
+            .attr('y1', 0);
+        d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
+            .attr('x2', distY.size());
+    });
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.scatter = scatter;
+    chart.legend = legend;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+    chart.distX = distX;
+    chart.distY = distY;
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showDistX:  {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
+        showDistY:  {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        showXAxis:  {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
+        showYAxis:  {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
+        tooltips:   {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:   {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        tooltipXContent:  {get: function(){return tooltipX;}, set: function(_){tooltipX=_;}},
+        tooltipYContent:  {get: function(){return tooltipY;}, set: function(_){tooltipY=_;}},
+        defaultState:     {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
+        noData:     {get: function(){return noData;}, set: function(_){noData=_;}},
+        duration:   {get: function(){return duration;}, set: function(_){duration=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
+            rightAlignYAxis = _;
+            yAxis.orient( (_) ? 'right' : 'left');
+        }},
+        color: {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+            distX.color(color);
+            distY.color(color);
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, scatter);
+    nv.utils.initOptions(chart);
+    return chart;
+};
+
+nv.models.sparkline = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 2, right: 0, bottom: 2, left: 0}
+        , width = 400
+        , height = 32
+        , animate = true
+        , x = d3.scale.linear()
+        , y = d3.scale.linear()
+        , getX = function(d) { return d.x }
+        , getY = function(d) { return d.y }
+        , color = nv.utils.getColor(['#000'])
+        , xDomain
+        , yDomain
+        , xRange
+        , yRange
+        ;
+
+    function chart(selection) {
+        selection.each(function(data) {
+            var availableWidth = width - margin.left - margin.right,
+                availableHeight = height - margin.top - margin.bottom,
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            // Setup Scales
+            x   .domain(xDomain || d3.extent(data, getX ))
+                .range(xRange || [0, availableWidth]);
+
+            y   .domain(yDomain || d3.extent(data, getY ))
+                .range(yRange || [availableHeight, 0]);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
+
+            var paths = wrap.selectAll('path')
+                .data(function(d) { return [d] });
+            paths.enter().append('path');
+            paths.exit().remove();
+            paths
+                .style('stroke', function(d,i) { return d.color || color(d, i) })
+                .attr('d', d3.svg.line()
+                    .x(function(d,i) { return x(getX(d,i)) })
+                    .y(function(d,i) { return y(getY(d,i)) })
+            );
+
+            // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
+            var points = wrap.selectAll('circle.nv-point')
+                .data(function(data) {
+                    var yValues = data.map(function(d, i) { return getY(d,i); });
+                    function pointIndex(index) {
+                        if (index != -1) {
+                            var result = data[index];
+                            result.pointIndex = index;
+                            return result;
+                        } else {
+                            return null;
+                        }
+                    }
+                    var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
+                        minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
+                        currentPoint = pointIndex(yValues.length - 1);
+                    return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
+                });
+            points.enter().append('circle');
+            points.exit().remove();
+            points
+                .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
+                .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
+                .attr('r', 2)
+                .attr('class', function(d,i) {
+                    return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
+                            getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
+                });
+        });
+
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:     {get: function(){return width;}, set: function(_){width=_;}},
+        height:    {get: function(){return height;}, set: function(_){height=_;}},
+        xDomain:   {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
+        yDomain:   {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
+        xRange:    {get: function(){return xRange;}, set: function(_){xRange=_;}},
+        yRange:    {get: function(){return yRange;}, set: function(_){yRange=_;}},
+        xScale:    {get: function(){return x;}, set: function(_){x=_;}},
+        yScale:    {get: function(){return y;}, set: function(_){y=_;}},
+        animate:   {get: function(){return animate;}, set: function(_){animate=_;}},
+
+        //functor options
+        x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
+        y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }}
+    });
+
+    nv.utils.initOptions(chart);
+    return chart;
+};
+
+nv.models.sparklinePlus = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var sparkline = nv.models.sparkline();
+
+    var margin = {top: 15, right: 100, bottom: 10, left: 50}
+        , width = null
+        , height = null
+        , x
+        , y
+        , index = []
+        , paused = false
+        , xTickFormat = d3.format(',r')
+        , yTickFormat = d3.format(',.2f')
+        , showValue = true
+        , alignValue = true
+        , rightAlignValue = false
+        , noData = "No Data Available."
+        ;
+
+    function chart(selection) {
+        selection.each(function(data) {
+            var container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+            chart.update = function() { chart(selection) };
+            chart.container = this;
+
+            // Display No Data message if there's nothing to show.
+            if (!data || !data.length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            var currentValue = sparkline.y()(data[data.length-1], data.length-1);
+
+            // Setup Scales
+            x = sparkline.xScale();
+            y = sparkline.yScale();
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-sparklineWrap');
+            gEnter.append('g').attr('class', 'nv-valueWrap');
+            gEnter.append('g').attr('class', 'nv-hoverArea');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            // Main Chart Component(s)
+            var sparklineWrap = g.select('.nv-sparklineWrap');
+
+            sparkline.width(availableWidth).height(availableHeight);
+            sparklineWrap.call(sparkline);
+
+            var valueWrap = g.select('.nv-valueWrap');
+            var value = valueWrap.selectAll('.nv-currentValue')
+                .data([currentValue]);
+
+            value.enter().append('text').attr('class', 'nv-currentValue')
+                .attr('dx', rightAlignValue ? -8 : 8)
+                .attr('dy', '.9em')
+                .style('text-anchor', rightAlignValue ? 'end' : 'start');
+
+            value
+                .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
+                .attr('y', alignValue ? function(d) { return y(d) } : 0)
+                .style('fill', sparkline.color()(data[data.length-1], data.length-1))
+                .text(yTickFormat(currentValue));
+
+            gEnter.select('.nv-hoverArea').append('rect')
+                .on('mousemove', sparklineHover)
+                .on('click', function() { paused = !paused })
+                .on('mouseout', function() { index = []; updateValueLine(); });
+
+            g.select('.nv-hoverArea rect')
+                .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
+                .attr('width', availableWidth + margin.left + margin.right)
+                .attr('height', availableHeight + margin.top);
+
+            //index is currently global (within the chart), may or may not keep it that way
+            function updateValueLine() {
+                if (paused) return;
+
+                var hoverValue = g.selectAll('.nv-hoverValue').data(index)
+
+                var hoverEnter = hoverValue.enter()
+                    .append('g').attr('class', 'nv-hoverValue')
+                    .style('stroke-opacity', 0)
+                    .style('fill-opacity', 0);
+
+                hoverValue.exit()
+                    .transition().duration(250)
+                    .style('stroke-opacity', 0)
+                    .style('fill-opacity', 0)
+                    .remove();
+
+                hoverValue
+                    .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
+                    .transition().duration(250)
+                    .style('stroke-opacity', 1)
+                    .style('fill-opacity', 1);
+
+                if (!index.length) return;
+
+                hoverEnter.append('line')
+                    .attr('x1', 0)
+                    .attr('y1', -margin.top)
+                    .attr('x2', 0)
+                    .attr('y2', availableHeight);
+
+                hoverEnter.append('text').attr('class', 'nv-xValue')
+                    .attr('x', -6)
+                    .attr('y', -margin.top)
+                    .attr('text-anchor', 'end')
+                    .attr('dy', '.9em')
+
+                g.select('.nv-hoverValue .nv-xValue')
+                    .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
+
+                hoverEnter.append('text').attr('class', 'nv-yValue')
+                    .attr('x', 6)
+                    .attr('y', -margin.top)
+                    .attr('text-anchor', 'start')
+                    .attr('dy', '.9em')
+
+                g.select('.nv-hoverValue .nv-yValue')
+                    .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
+            }
+
+            function sparklineHover() {
+                if (paused) return;
+
+                var pos = d3.mouse(this)[0] - margin.left;
+
+                function getClosestIndex(data, x) {
+                    var distance = Math.abs(sparkline.x()(data[0], 0) - x);
+                    var closestIndex = 0;
+                    for (var i = 0; i < data.length; i++){
+                        if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
+                            distance = Math.abs(sparkline.x()(data[i], i) - x);
+                            closestIndex = i;
+                        }
+                    }
+                    return closestIndex;
+                }
+
+                index = [getClosestIndex(data, Math.round(x.invert(pos)))];
+                updateValueLine();
+            }
+
+        });
+
+        return chart;
+    }
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.sparkline = sparkline;
+
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:           {get: function(){return width;}, set: function(_){width=_;}},
+        height:          {get: function(){return height;}, set: function(_){height=_;}},
+        xTickFormat:     {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
+        yTickFormat:     {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
+        showValue:       {get: function(){return showValue;}, set: function(_){showValue=_;}},
+        alignValue:      {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
+        rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
+        noData:          {get: function(){return noData;}, set: function(_){noData=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, sparkline);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.models.stackedArea = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var margin = {top: 0, right: 0, bottom: 0, left: 0}
+        , width = 960
+        , height = 500
+        , color = nv.utils.defaultColor() // a function that computes the color
+        , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
+        , getX = function(d) { return d.x } // accessor to get the x value from a data point
+        , getY = function(d) { return d.y } // accessor to get the y value from a data point
+        , style = 'stack'
+        , offset = 'zero'
+        , order = 'default'
+        , interpolate = 'linear'  // controls the line interpolation
+        , clipEdge = false // if true, masks lines within x and y scale
+        , x //can be accessed via chart.xScale()
+        , y //can be accessed via chart.yScale()
+        , scatter = nv.models.scatter()
+        , duration = 250
+        , dispatch =  d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout','renderEnd')
+        ;
+
+    // scatter is interactive by default, but this chart isn't so must disable
+    scatter.interactive(false);
+
+    scatter
+        .pointSize(2.2) // default size
+        .pointDomain([2.2, 2.2]) // all the same size by default
+    ;
+
+    /************************************
+     * offset:
+     *   'wiggle' (stream)
+     *   'zero' (stacked)
+     *   'expand' (normalize to 100%)
+     *   'silhouette' (simple centered)
+     *
+     * order:
+     *   'inside-out' (stream)
+     *   'default' (input order)
+     ************************************/
+
+    var renderWatch = nv.utils.renderWatch(dispatch, duration);
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(scatter);
+        selection.each(function(data) {
+            var availableWidth = width - margin.left - margin.right,
+                availableHeight = height - margin.top - margin.bottom,
+                container = d3.select(this);
+            nv.utils.initSVG(container);
+
+            // Setup Scales
+            x = scatter.xScale();
+            y = scatter.yScale();
+
+            var dataRaw = data;
+            // Injecting point index into each point because d3.layout.stack().out does not give index
+            data.forEach(function(aseries, i) {
+                aseries.seriesIndex = i;
+                aseries.values = aseries.values.map(function(d, j) {
+                    d.index = j;
+                    d.seriesIndex = i;
+                    return d;
+                });
+            });
+
+            var dataFiltered = data.filter(function(series) {
+                return !series.disabled;
+            });
+
+            data = d3.layout.stack()
+                .order(order)
+                .offset(offset)
+                .values(function(d) { return d.values })  //TODO: make values customizeable in EVERY model in this fashion
+                .x(getX)
+                .y(getY)
+                .out(function(d, y0, y) {
+                    var yHeight = (getY(d) === 0) ? 0 : y;
+                    d.display = {
+                        y: yHeight,
+                        y0: y0
+                    };
+                })
+            (dataFiltered);
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
+            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
+            var defsEnter = wrapEnter.append('defs');
+            var gEnter = wrapEnter.append('g');
+            var g = wrap.select('g');
+
+            gEnter.append('g').attr('class', 'nv-areaWrap');
+            gEnter.append('g').attr('class', 'nv-scatterWrap');
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            scatter
+                .width(availableWidth)
+                .height(availableHeight)
+                .x(getX)
+                .y(function(d) { return d.display.y + d.display.y0 })
+                .forceY([0])
+                .color(data.map(function(d,i) {
+                    return d.color || color(d, d.seriesIndex);
+                }));
+
+            var scatterWrap = g.select('.nv-scatterWrap')
+                .datum(data);
+
+            scatterWrap.call(scatter);
+
+            defsEnter.append('clipPath')
+                .attr('id', 'nv-edge-clip-' + id)
+                .append('rect');
+
+            wrap.select('#nv-edge-clip-' + id + ' rect')
+                .attr('width', availableWidth)
+                .attr('height', availableHeight);
+
+            g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
+
+            var area = d3.svg.area()
+                .x(function(d,i)  { return x(getX(d,i)) })
+                .y0(function(d) {
+                    return y(d.display.y0)
+                })
+                .y1(function(d) {
+                    return y(d.display.y + d.display.y0)
+                })
+                .interpolate(interpolate);
+
+            var zeroArea = d3.svg.area()
+                .x(function(d,i)  { return x(getX(d,i)) })
+                .y0(function(d) { return y(d.display.y0) })
+                .y1(function(d) { return y(d.display.y0) });
+
+            var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
+                .data(function(d) { return d });
+
+            path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
+                .attr('d', function(d,i){
+                    return zeroArea(d.values, d.seriesIndex);
+                })
+                .on('mouseover', function(d,i) {
+                    d3.select(this).classed('hover', true);
+                    dispatch.areaMouseover({
+                        point: d,
+                        series: d.key,
+                        pos: [d3.event.pageX, d3.event.pageY],
+                        seriesIndex: d.seriesIndex
+                    });
+                })
+                .on('mouseout', function(d,i) {
+                    d3.select(this).classed('hover', false);
+                    dispatch.areaMouseout({
+                        point: d,
+                        series: d.key,
+                        pos: [d3.event.pageX, d3.event.pageY],
+                        seriesIndex: d.seriesIndex
+                    });
+                })
+                .on('click', function(d,i) {
+                    d3.select(this).classed('hover', false);
+                    dispatch.areaClick({
+                        point: d,
+                        series: d.key,
+                        pos: [d3.event.pageX, d3.event.pageY],
+                        seriesIndex: d.seriesIndex
+                    });
+                });
+
+            path.exit().remove();
+            path.style('fill', function(d,i){
+                    return d.color || color(d, d.seriesIndex)
+                })
+                .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
+            path.watchTransition(renderWatch,'stackedArea path')
+                .attr('d', function(d,i) {
+                    return area(d.values,i)
+                });
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            scatter.dispatch.on('elementMouseover.area', function(e) {
+                g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
+            });
+            scatter.dispatch.on('elementMouseout.area', function(e) {
+                g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
+            });
+
+            //Special offset functions
+            chart.d3_stackedOffset_stackPercent = function(stackData) {
+                var n = stackData.length,    //How many series
+                    m = stackData[0].length,     //how many points per series
+                    k = 1 / n,
+                    i,
+                    j,
+                    o,
+                    y0 = [];
+
+                for (j = 0; j < m; ++j) { //Looping through all points
+                    for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through series'
+                        o += getY(dataRaw[i].values[j]);   //total value of all points at a certian point in time.
+                    }
+
+                    if (o) for (i = 0; i < n; i++) {
+                        stackData[i][j][1] /= o;
+                    } else {
+                        for (i = 0; i < n; i++) {
+                            stackData[i][j][1] = k;
+                        }
+                    }
+                }
+                for (j = 0; j < m; ++j) y0[j] = 0;
+                return y0;
+            };
+
+        });
+
+        renderWatch.renderEnd('stackedArea immediate');
+        return chart;
+    }
+
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    scatter.dispatch.on('elementClick.area', function(e) {
+        dispatch.areaClick(e);
+    });
+    scatter.dispatch.on('elementMouseover.tooltip', function(e) {
+        e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
+            dispatch.tooltipShow(e);
+    });
+    scatter.dispatch.on('elementMouseout.tooltip', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    //============================================================
+    // Global getters and setters
+    //------------------------------------------------------------
+
+    chart.dispatch = dispatch;
+    chart.scatter = scatter;
+
+    chart.interpolate = function(_) {
+        if (!arguments.length) return interpolate;
+        interpolate = _;
+        return chart;
+    };
+
+    chart.duration = function(_) {
+        if (!arguments.length) return duration;
+        duration = _;
+        renderWatch.reset(duration);
+        scatter.duration(duration);
+        return chart;
+    };
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
+        offset:      {get: function(){return offset;}, set: function(_){offset=_;}},
+        order:    {get: function(){return order;}, set: function(_){order=_;}},
+        interpolate:    {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
+
+        // simple functor options
+        x:     {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
+        y:     {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+        }},
+        style: {get: function(){return style;}, set: function(_){
+            style = _;
+            switch (style) {
+                case 'stack':
+                    chart.offset('zero');
+                    chart.order('default');
+                    break;
+                case 'stream':
+                    chart.offset('wiggle');
+                    chart.order('inside-out');
+                    break;
+                case 'stream-center':
+                    chart.offset('silhouette');
+                    chart.order('inside-out');
+                    break;
+                case 'expand':
+                    chart.offset('expand');
+                    chart.order('default');
+                    break;
+                case 'stack_percent':
+                    chart.offset(chart.d3_stackedOffset_stackPercent);
+                    chart.order('default');
+                    break;
+            }
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+            scatter.duration(duration);
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, scatter);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.models.stackedAreaChart = function() {
+    "use strict";
+
+    //============================================================
+    // Public Variables with Default Settings
+    //------------------------------------------------------------
+
+    var stacked = nv.models.stackedArea()
+        , xAxis = nv.models.axis()
+        , yAxis = nv.models.axis()
+        , legend = nv.models.legend()
+        , controls = nv.models.legend()
+        , interactiveLayer = nv.interactiveGuideline()
+        ;
+
+    var margin = {top: 30, right: 25, bottom: 50, left: 60}
+        , width = null
+        , height = null
+        , color = nv.utils.defaultColor()
+        , showControls = true
+        , showLegend = true
+        , showXAxis = true
+        , showYAxis = true
+        , rightAlignYAxis = false
+        , useInteractiveGuideline = false
+        , tooltips = true
+        , tooltip = function(key, x, y, e, graph) {
+            return '<h3>' + key + '</h3>' +
+                '<p>' +  y + ' on ' + x + '</p>'
+        }
+        , x //can be accessed via chart.xScale()
+        , y //can be accessed via chart.yScale()
+        , yAxisTickFormat = d3.format(',.2f')
+        , state = nv.utils.state()
+        , defaultState = null
+        , noData = 'No Data Available.'
+        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
+        , controlWidth = 250
+        , cData = ['Stacked','Stream','Expanded']
+        , controlLabels = {}
+        , duration = 250
+        ;
+
+    state.style = stacked.style();
+    xAxis.orient('bottom').tickPadding(7);
+    yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
+
+    controls.updateState(false);
+
+    //============================================================
+    // Private Variables
+    //------------------------------------------------------------
+
+    var renderWatch = nv.utils.renderWatch(dispatch);
+    var style = stacked.style();
+
+    var showTooltip = function(e, offsetElement) {
+        var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+            top = e.pos[1] + ( offsetElement.offsetTop || 0),
+            x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
+            y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
+            content = tooltip(e.series.key, x, y, e, chart);
+
+        nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+    };
+
+    var stateGetter = function(data) {
+        return function(){
+            return {
+                active: data.map(function(d) { return !d.disabled }),
+                style: stacked.style()
+            };
+        }
+    };
+
+    var stateSetter = function(data) {
+        return function(state) {
+            if (state.style !== undefined)
+                style = state.style;
+            if (state.active !== undefined)
+                data.forEach(function(series,i) {
+                    series.disabled = !state.active[i];
+                });
+        }
+    };
+
+    function chart(selection) {
+        renderWatch.reset();
+        renderWatch.models(stacked);
+        if (showXAxis) renderWatch.models(xAxis);
+        if (showYAxis) renderWatch.models(yAxis);
+
+        selection.each(function(data) {
+            var container = d3.select(this),
+                that = this;
+            nv.utils.initSVG(container);
+
+            var availableWidth = (width  || parseInt(container.style('width')) || 960)
+                    - margin.left - margin.right,
+                availableHeight = (height || parseInt(container.style('height')) || 400)
+                    - margin.top - margin.bottom;
+
+            chart.update = function() { container.transition().duration(duration).call(chart); };
+            chart.container = this;
+
+            state
+                .setter(stateSetter(data), chart.update)
+                .getter(stateGetter(data))
+                .update();
+
+            // DEPRECATED set state.disabled
+            state.disabled = data.map(function(d) { return !!d.disabled });
+
+            if (!defaultState) {
+                var key;
+                defaultState = {};
+                for (key in state) {
+                    if (state[key] instanceof Array)
+                        defaultState[key] = state[key].slice(0);
+                    else
+                        defaultState[key] = state[key];
+                }
+            }
+
+            // Display No Data message if there's nothing to show.
+            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+                var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+                noDataText.enter().append('text')
+                    .attr('class', 'nvd3 nv-noData')
+                    .attr('dy', '-.7em')
+                    .style('text-anchor', 'middle');
+
+                noDataText
+                    .attr('x', margin.left + availableWidth / 2)
+                    .attr('y', margin.top + availableHeight / 2)
+                    .text(function(d) { return d });
+
+                return chart;
+            } else {
+                container.selectAll('.nv-noData').remove();
+            }
+
+            // Setup Scales
+            x = stacked.xScale();
+            y = stacked.yScale();
+
+            // Setup containers and skeleton of chart
+            var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
+            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
+            var g = wrap.select('g');
+
+            gEnter.append("rect").style("opacity",0);
+            gEnter.append('g').attr('class', 'nv-x nv-axis');
+            gEnter.append('g').attr('class', 'nv-y nv-axis');
+            gEnter.append('g').attr('class', 'nv-stackedWrap');
+            gEnter.append('g').attr('class', 'nv-legendWrap');
+            gEnter.append('g').attr('class', 'nv-controlsWrap');
+            gEnter.append('g').attr('class', 'nv-interactive');
+
+            g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
+
+            // Legend
+            if (showLegend) {
+                var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
+
+                legend.width(legendWidth);
+                g.select('.nv-legendWrap').datum(data).call(legend);
+
+                if ( margin.top != legend.height()) {
+                    margin.top = legend.height();
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                g.select('.nv-legendWrap')
+                    .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
+            }
+
+            // Controls
+            if (showControls) {
+                var controlsData = [
+                    {
+                        key: controlLabels.stacked || 'Stacked',
+                        metaKey: 'Stacked',
+                        disabled: stacked.style() != 'stack',
+                        style: 'stack'
+                    },
+                    {
+                        key: controlLabels.stream || 'Stream',
+                        metaKey: 'Stream',
+                        disabled: stacked.style() != 'stream',
+                        style: 'stream'
+                    },
+                    {
+                        key: controlLabels.expanded || 'Expanded',
+                        metaKey: 'Expanded',
+                        disabled: stacked.style() != 'expand',
+                        style: 'expand'
+                    },
+                    {
+                        key: controlLabels.stack_percent || 'Stack %',
+                        metaKey: 'Stack_Percent',
+                        disabled: stacked.style() != 'stack_percent',
+                        style: 'stack_percent'
+                    }
+                ];
+
+                controlWidth = (cData.length/3) * 260;
+                controlsData = controlsData.filter(function(d) {
+                    return cData.indexOf(d.metaKey) !== -1;
+                });
+
+                controls
+                    .width( controlWidth )
+                    .color(['#444', '#444', '#444']);
+
+                g.select('.nv-controlsWrap')
+                    .datum(controlsData)
+                    .call(controls);
+
+                if ( margin.top != Math.max(controls.height(), legend.height()) ) {
+                    margin.top = Math.max(controls.height(), legend.height());
+                    availableHeight = (height || parseInt(container.style('height')) || 400)
+                        - margin.top - margin.bottom;
+                }
+
+                g.select('.nv-controlsWrap')
+                    .attr('transform', 'translate(0,' + (-margin.top) +')');
+            }
+
+            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+            if (rightAlignYAxis) {
+                g.select(".nv-y.nv-axis")
+                    .attr("transform", "translate(" + availableWidth + ",0)");
+            }
+
+            //Set up interactive layer
+            if (useInteractiveGuideline) {
+                interactiveLayer
+                    .width(availableWidth)
+                    .height(availableHeight)
+                    .margin({left: margin.left, top: margin.top})
+                    .svgContainer(container)
+                    .xScale(x);
+                wrap.select(".nv-interactive").call(interactiveLayer);
+            }
+
+            stacked
+                .width(availableWidth)
+                .height(availableHeight);
+
+            var stackedWrap = g.select('.nv-stackedWrap')
+                .datum(data);
+
+            stackedWrap.transition().call(stacked);
+
+            // Setup Axes
+            if (showXAxis) {
+                xAxis.scale(x)
+                    .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
+                    .tickSize( -availableHeight, 0);
+
+                g.select('.nv-x.nv-axis')
+                    .attr('transform', 'translate(0,' + availableHeight + ')');
+
+                g.select('.nv-x.nv-axis')
+                    .transition().duration(0)
+                    .call(xAxis);
+            }
+
+            if (showYAxis) {
+                yAxis.scale(y)
+                    .ticks(stacked.offset() == 'wiggle' ? 0 : nv.utils.calcTicksY(availableHeight/36, data) )
+                    .tickSize(-availableWidth, 0)
+                    .setTickFormat( (stacked.style() == 'expand' || stacked.style() == 'stack_percent')
+                        ? d3.format('%') : yAxisTickFormat);
+
+                g.select('.nv-y.nv-axis')
+                    .transition().duration(0)
+                    .call(yAxis);
+            }
+
+            //============================================================
+            // Event Handling/Dispatching (in chart's scope)
+            //------------------------------------------------------------
+
+            stacked.dispatch.on('areaClick.toggle', function(e) {
+                if (data.filter(function(d) { return !d.disabled }).length === 1)
+                    data.forEach(function(d) {
+                        d.disabled = false;
+                    });
+                else
+                    data.forEach(function(d,i) {
+                        d.disabled = (i != e.seriesIndex);
+                    });
+
+                state.disabled = data.map(function(d) { return !!d.disabled });
+                dispatch.stateChange(state);
+
+                chart.update();
+            });
+
+            legend.dispatch.on('stateChange', function(newState) {
+                for (var key in newState)
+                    state[key] = newState[key];
+                dispatch.stateChange(state);
+                chart.update();
+            });
+
+            controls.dispatch.on('legendClick', function(d,i) {
+                if (!d.disabled) return;
+
+                controlsData = controlsData.map(function(s) {
+                    s.disabled = true;
+                    return s;
+                });
+                d.disabled = false;
+
+                stacked.style(d.style);
+
+
+                state.style = stacked.style();
+                dispatch.stateChange(state);
+
+                chart.update();
+            });
+
+            interactiveLayer.dispatch.on('elementMousemove', function(e) {
+                stacked.clearHighlights();
+                var singlePoint, pointIndex, pointXLocation, allData = [];
+                data
+                    .filter(function(series, i) {
+                        series.seriesIndex = i;
+                        return !series.disabled;
+                    })
+                    .forEach(function(series,i) {
+                        pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
+                        stacked.highlightPoint(i, pointIndex, true);
+                        var point = series.values[pointIndex];
+                        if (typeof point === 'undefined') return;
+                        if (typeof singlePoint === 'undefined') singlePoint = point;
+                        if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
+
+                        //If we are in 'expand' mode, use the stacked percent value instead of raw value.
+                        var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
+                        allData.push({
+                            key: series.key,
+                            value: tooltipValue,
+                            color: color(series,series.seriesIndex),
+                            stackedValue: point.display
+                        });
+                    });
+
+                allData.reverse();
+
+                //Highlight the tooltip entry based on which stack the mouse is closest to.
+                if (allData.length > 2) {
+                    var yValue = chart.yScale().invert(e.mouseY);
+                    var yDistMax = Infinity, indexToHighlight = null;
+                    allData.forEach(function(series,i) {
+
+                        //To handle situation where the stacked area chart is negative, we need to use absolute values
+                        //when checking if the mouse Y value is within the stack area.
+                        yValue = Math.abs(yValue);
+                        var stackedY0 = Math.abs(series.stackedValue.y0);
+                        var stackedY = Math.abs(series.stackedValue.y);
+                        if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
+                        {
+                            indexToHighlight = i;
+                            return;
+                        }
+                    });
+                    if (indexToHighlight != null)
+                        allData[indexToHighlight].highlight = true;
+                }
+
+                var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
+
+                //If we are in 'expand' mode, force the format to be a percentage.
+                var valueFormatter = (stacked.style() == 'expand') ?
+                    function(d,i) {return d3.format(".1%")(d);} :
+                    function(d,i) {return yAxis.tickFormat()(d); };
+                interactiveLayer.tooltip
+                    .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
+                    .chartContainer(that.parentNode)
+                    .enabled(tooltips)
+                    .valueFormatter(valueFormatter)
+                    .data(
+                    {
+                        value: xValue,
+                        series: allData
+                    }
+                )();
+
+                interactiveLayer.renderGuideLine(pointXLocation);
+
+            });
+
+            interactiveLayer.dispatch.on("elementMouseout",function(e) {
+                dispatch.tooltipHide();
+                stacked.clearHighlights();
+            });
+
+
+            dispatch.on('tooltipShow', function(e) {
+                if (tooltips) showTooltip(e, that.parentNode);
+            });
+
+            // Update chart from a state object passed to event handler
+            dispatch.on('changeState', function(e) {
+
+                if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
+                    data.forEach(function(series,i) {
+                        series.disabled = e.disabled[i];
+                    });
+
+                    state.disabled = e.disabled;
+                }
+
+                if (typeof e.style !== 'undefined') {
+                    stacked.style(e.style);
+                    style = e.style;
+                }
+
+                chart.update();
+            });
+
+        });
+
+        renderWatch.renderEnd('stacked Area chart immediate');
+        return chart;
+    }
+
+    //============================================================
+    // Event Handling/Dispatching (out of chart's scope)
+    //------------------------------------------------------------
+
+    stacked.dispatch.on('tooltipShow', function(e) {
+        e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+        dispatch.tooltipShow(e);
+    });
+
+    stacked.dispatch.on('tooltipHide', function(e) {
+        dispatch.tooltipHide(e);
+    });
+
+    dispatch.on('tooltipHide', function() {
+        if (tooltips) nv.tooltip.cleanup();
+    });
+
+    //============================================================
+    // Expose Public Variables
+    //------------------------------------------------------------
+
+    // expose chart's sub-components
+    chart.dispatch = dispatch;
+    chart.stacked = stacked;
+    chart.legend = legend;
+    chart.controls = controls;
+    chart.xAxis = xAxis;
+    chart.yAxis = yAxis;
+    chart.interactiveLayer = interactiveLayer;
+
+    yAxis.setTickFormat = yAxis.tickFormat;
+
+    chart.dispatch = dispatch;
+    chart.options = nv.utils.optionsFunc.bind(chart);
+
+    chart._options = Object.create({}, {
+        // simple options, just get/set the necessary values
+        width:      {get: function(){return width;}, set: function(_){width=_;}},
+        height:     {get: function(){return height;}, set: function(_){height=_;}},
+        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
+        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
+        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
+        tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
+        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
+        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
+        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
+        showControls:    {get: function(){return showControls;}, set: function(_){showControls=_;}},
+        controlLabels:    {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
+        yAxisTickFormat:    {get: function(){return yAxisTickFormat;}, set: function(_){yAxisTickFormat=_;}},
+
+        // options that require extra logic in the setter
+        margin: {get: function(){return margin;}, set: function(_){
+            margin.top    = _.top    !== undefined ? _.top    : margin.top;
+            margin.right  = _.right  !== undefined ? _.right  : margin.right;
+            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
+            margin.left   = _.left   !== undefined ? _.left   : margin.left;
+        }},
+        duration: {get: function(){return duration;}, set: function(_){
+            duration = _;
+            renderWatch.reset(duration);
+            stacked.duration(duration);
+            xAxis.duration(duration);
+            yAxis.duration(duration);
+        }},
+        color:  {get: function(){return color;}, set: function(_){
+            color = nv.utils.getColor(_);
+            legend.color(color);
+            stacked.color(color);
+        }},
+        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
+            rightAlignYAxis = _;
+            yAxis.orient( rightAlignYAxis ? 'right' : 'left');
+        }},
+        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
+            useInteractiveGuideline = !!_;
+            if (_) {
+                chart.interactive(false);
+                chart.useVoronoi(false);
+            }
+        }}
+    });
+
+    nv.utils.inheritOptions(chart, stacked);
+    nv.utils.initOptions(chart);
+
+    return chart;
+};
+
+nv.version = "1.7.0";
+})();
\ No newline at end of file