Browse Source

HUE-1797 [beeswax] Integrate new graph lib and multi columns

Changed jHueBlueprint to the new NVD3 and HueCharts bindings on Beeswax/Impala
Added new leaflet map binding to charts
Enrico Berti 11 years ago
parent
commit
fa30f4e839

+ 242 - 154
apps/beeswax/src/beeswax/templates/execute.mako

@@ -23,6 +23,7 @@
 
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
+<%namespace name="dashboard" file="common_dashboard.mako" />
 
 ${ commonheader(_('Query'), app_name, user) | n,unicode }
 ${layout.menubar(section='query')}
@@ -403,17 +404,19 @@ ${layout.menubar(section='query')}
         </div>
 
         <div class="tab-pane" id="chart">
-          <pre data-bind="visible: $root.design.results.columns().length == 0">${_('There is currently no data to build a chart on.')}</pre>
+          <pre data-bind="visible: $root.design.results.empty() || $root.design.results.expired()">${_('There is currently no data to build a chart on.')}</pre>
           <div class="alert hide">
             <strong>${_('Warning:')}</strong> ${_('the results on the chart have been limited to 1000 rows.')}
           </div>
-          <div data-bind="visible: $root.design.results.columns().length > 0" style="text-align: center">
+
+          <div data-bind="visible: ! $root.design.results.empty() && ! $root.design.results.expired()" style="text-align: center">
           <form class="form-inline">
             ${_('Chart type')}&nbsp;
             <div class="btn-group" data-toggle="buttons-radio">
-              <a rel="tooltip" data-placement="top" title="${_('Bars')}" id="blueprintBars" href="javascript:void(0)" class="btn"><i class="fa fa-bar-chart-o"></i></a>
-              <a rel="tooltip" data-placement="top" title="${_('Lines')}" id="blueprintLines" href="javascript:void(0)" class="btn"><i class="fa fa-signal"></i></a>
-              <a rel="tooltip" data-placement="top" title="${_('Map')}" id="blueprintMap" href="javascript:void(0)" class="btn"><i class="fa fa-map-marker"></i></a>
+              <a rel="tooltip" data-placement="top" title="${_('Bars')}" id="blueprintBars" href="javascript:void(0)" class="btn"><i class="hcha hcha-bar-chart"></i></a>
+              <a rel="tooltip" data-placement="top" title="${_('Lines')}" id="blueprintLines" href="javascript:void(0)" class="btn"><i class="hcha hcha-line-chart"></i></a>
+              <a rel="tooltip" data-placement="top" title="${_('Pie')}" id="blueprintPie" href="javascript:void(0)" class="btn"><i class="hcha hcha-pie-chart"></i></a>
+              <a rel="tooltip" data-placement="top" title="${_('Map')}" id="blueprintMap" href="javascript:void(0)" class="btn"><i class="hcha hcha-map-chart"></i></a>
             </div>&nbsp;&nbsp;
             <span id="blueprintAxis" class="hide">
               <label>${_('X-Axis')}
@@ -436,7 +439,23 @@ ${layout.menubar(section='query')}
             </span>
           </form>
           </div>
-          <div  data-bind="visible: $root.design.results.columns().length > 0" id="blueprint" class="empty">${_("Please select a chart type.")}</div>
+          <div data-bind="visible: ! $root.design.results.empty() && ! $root.design.results.expired()" id="blueprint" class="empty center">${_("Please select a chart type.")}</div>
+          <div style="margin: 10px" data-bind="visible: ! $root.design.results.empty() && ! $root.design.results.expired()">
+            <div id="pieChart" data-bind="pieChart: {data: {counts: $root.chartData}, fqs: ko.observableArray([]),
+              transformer: pieChartDataTransformer,
+              maxWidth: 350 }, visible: $root.chartType() == 'pie'"></div>
+
+            <div id="barChart" data-bind="barChart: {datum: {counts: $root.chartData}, fqs: ko.observableArray([]),
+                  transformer: barChartDataTransformer}, visible: $root.chartType() == 'bars'"></div>
+
+            <div id="lineChart" data-bind="lineChart: {datum: {counts: $root.chartData},
+                  transformer: lineChartDataTransformer,
+                  showControls: false }, visible: $root.chartType() == 'lines'"></div>
+
+            <div id="leafletMapChart" data-bind="leafletMapChart: {datum: {counts: $root.chartData},
+                  transformer: leafletMapChartDataTransformer,
+                  showControls: false }, visible: $root.chartType() == 'map'"></div>
+          </div>
         </div>
         <!-- /ko -->
       </div>
@@ -659,7 +678,7 @@ ${layout.menubar(section='query')}
     <a href="#" class="close" data-dismiss="modal">&times;</a>
     % if has_metastore:
     <a class="tableLink pull-right" href="#" target="_blank" style="margin-right: 20px;margin-top:6px">
-      <iclass="fa fa-external-link"></i> ${ _('View in Metastore Browser') }
+      <i class="fa fa-external-link"></i> ${ _('View in Metastore Browser') }
     </a>
     % endif
 
@@ -677,6 +696,7 @@ ${layout.menubar(section='query')}
   </div>
 </div>
 
+
 <script src="/static/js/hue.json.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/jquery/plugins/jquery-ui-1.10.4.draggable-droppable-sortable.min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/routie-0.3.0.min.js" type="text/javascript" charset="utf-8"></script>
@@ -696,12 +716,6 @@ ${layout.menubar(section='query')}
 <link href="/static/ext/css/bootstrap-editable.css" rel="stylesheet">
 <script src="/static/ext/js/bootstrap-editable.min.js"></script>
 
-<script src="/static/ext/js/jquery/plugins/jquery.flot.min.js" type="text/javascript" charset="utf-8"></script>
-<script src="/static/ext/js/jquery/plugins/jquery.flot.categories.min.js" type="text/javascript" charset="utf-8"></script>
-<script src="/static/ext/js/leaflet/leaflet.js" type="text/javascript" charset="utf-8"></script>
-<script src="/static/js/jquery.blueprint.js" type="text/javascript" charset="utf-8"></script>
-
-
 <style type="text/css">
   h1 {
     margin-bottom: 5px;
@@ -923,8 +937,8 @@ ${layout.menubar(section='query')}
 
 </style>
 
-<link href="/static/ext/css/leaflet.css" rel="stylesheet">
-<link href="/static/ext/css/hue-filetypes.css" rel="stylesheet">
+<link rel="stylesheet" href="/static/ext/css/hue-filetypes.css">
+<link rel="stylesheet" href="/static/ext/css/hue-charts.css">
 
 <script src="/static/ext/js/jquery/plugins/jquery-fieldselection.js" type="text/javascript"></script>
 <script src="/beeswax/static/js/autocomplete.utils.js" type="text/javascript" charset="utf-8"></script>
@@ -932,6 +946,9 @@ ${layout.menubar(section='query')}
 <link rel="stylesheet" href="/static/ext/chosen/chosen.min.css">
 <script src="/static/ext/chosen/chosen.jquery.min.js" type="text/javascript" charset="utf-8"></script>
 
+${ dashboard.import_charts() }
+
+
 <script type="text/javascript" charset="utf-8">
 var codeMirror, renderNavigator, resetNavigator, resizeNavigator, dataTable, renderRecent;
 
@@ -1620,168 +1637,150 @@ function isStringColumn(type) {
 
 var map;
 var graphHasBeenPredicted = false;
-// Logs
-var logsAtEnd = true;
-$(document).ready(function () {
-  var labels = {
-    MRJOB: "${_('MR Job')}",
-    MRJOBS: "${_('MR Jobs')}"
-  };
-
-  $(window).resize(function () {
-    resizeLogs();
-  });
+function getMapBounds(lats, lngs) {
+  lats = lats.sort();
+  lngs = lngs.sort();
+  return [
+    [lats[lats.length - 1], lngs[lngs.length - 1]], // north-east
+    [lats[0], lngs[0]] // south-west
+  ]
+}
 
-  $("a[href='#log']").on("shown", function () {
-    resizeLogs();
-  });
+function generateGraph(graphType) {
+  $("#chart").height(Math.max($(window).height() - $("#blueprint").offset().top + 30, 500));
+  $("#chart .alert").addClass("hide");
+  if (graphType != "") {
+    $("#blueprint").attr("class", "").attr("style", "").empty();
+    if (graphType == ko.HUE_CHARTS.TYPES.MAP) {
+      if ($("#blueprintLat").val() != "-1" && $("#blueprintLng").val() != "-1") {
+        var _latCol = $("#blueprintLat").val() * 1;
+        var _lngCol = $("#blueprintLng").val() * 1;
+        var _descCol = $("#blueprintDesc").val() * 1;
+        var _lats = [];
+        var _lngs = [];
+        $("#resultTable>tbody>tr>td:nth-child(" + _latCol + ")").each(function (cnt) {
+          _lats.push($.trim($(this).text()) * 1);
+        });
+        $("#resultTable>tbody>tr>td:nth-child(" + _lngCol + ")").each(function (cnt) {
+          _lngs.push($.trim($(this).text()) * 1);
+        });
 
-  function getMapBounds(lats, lngs) {
-    lats = lats.sort();
-    lngs = lngs.sort();
-    return [
-      [lats[lats.length - 1], lngs[lngs.length - 1]], // north-east
-      [lats[0], lngs[0]] // south-west
-    ]
-  }
+        var _koData = [];
 
-  function generateGraph(graphType) {
-    $("#chart").height(Math.max($(window).height() - $("#blueprint").offset().top + 30, 500));
-    $("#chart .alert").addClass("hide");
-    if (graphType != "") {
-      if (map != null){
         try {
-          map.remove();
+          $("#resultTable>tbody>tr>td:nth-child(" + _latCol + ")").each(function (cnt) {
+            if (cnt < 1000) {
+              if (_descCol != "-1") {
+                _koData.push({ lat: $.trim($(this).text()) * 1, lng: $.trim($("#resultTable>tbody>tr:nth-child(" + (cnt + 1) + ")>td:nth-child(" + _lngCol + ")").text()) * 1, label: $.trim($("#resultTable>tbody>tr:nth-child(" + (cnt + 1) + ")>td:nth-child(" + _descCol + ")").text())});
+              }
+              else {
+                _koData.push({ lat: $.trim($(this).text()) * 1, lng: $.trim($("#resultTable>tbody>tr:nth-child(" + (cnt + 1) + ")>td:nth-child(" + _lngCol + ")").text()) * 1});
+              }
+            }
+          });
+          viewModel.chartData(_koData);
         }
         catch (err) {
           if (typeof console != "undefined") {
             console.error(err);
           }
         }
-      }
-      $("#blueprint").attr("class", "").attr("style", "").empty();
-      $("#blueprint").data("plugin_jHueBlueprint", null);
-      if (graphType == $.jHueBlueprint.TYPES.MAP) {
-        L.DomUtil.get("blueprint")._leaflet = false;
-        if ($("#blueprintLat").val() != "-1" && $("#blueprintLng").val() != "-1") {
-          var _latCol = $("#blueprintLat").val() * 1;
-          var _lngCol = $("#blueprintLng").val() * 1;
-          var _descCol = $("#blueprintDesc").val() * 1;
-          var _lats = [];
-          var _lngs = [];
-          $("#resultTable>tbody>tr>td:nth-child(" + _latCol + ")").each(function (cnt) {
-            _lats.push($.trim($(this).text()) * 1);
-          });
-          $("#resultTable>tbody>tr>td:nth-child(" + _lngCol + ")").each(function (cnt) {
-            _lngs.push($.trim($(this).text()) * 1);
-          });
-          $("#blueprint").height($("#blueprint").parent().height() - 100);
-          try {
-            map = L.map("blueprint").fitBounds(getMapBounds(_lats, _lngs));
-
-            L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
-              attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
-            }).addTo(map);
-
-            $("#resultTable>tbody>tr>td:nth-child(" + _latCol + ")").each(function (cnt) {
-              if (cnt < 1000) {
-                if (_descCol != "-1") {
-                  L.marker([$.trim($(this).text()) * 1, $.trim($("#resultTable>tbody>tr:nth-child(" + (cnt + 1) + ")>td:nth-child(" + _lngCol + ")").text()) * 1]).addTo(map).bindPopup($.trim($("#resultTable>tbody>tr:nth-child(" + (cnt + 1) + ")>td:nth-child(" + _descCol + ")").text()));
-                }
-                else {
-                  L.marker([$.trim($(this).text()) * 1, $.trim($("#resultTable>tbody>tr:nth-child(" + (cnt + 1) + ")>td:nth-child(" + _lngCol + ")").text()) * 1]).addTo(map);
-                }
-              }
-            });
-          }
-          catch (err) {
-            if (typeof console != "undefined") {
-              console.error(err);
-            }
-          }
-          if ($("#resultTable>tbody>tr>td:nth-child(" + _latCol + ")").length > 1000){
-            $("#chart .alert").removeClass("hide");
-          }
-        }
-        else {
-          $("#blueprint").addClass("empty").css("text-align", "center").text("${_("Please select the latitude and longitude columns.")}");
+        if ($("#resultTable>tbody>tr>td:nth-child(" + _latCol + ")").length > 1000){
+          $("#chart .alert").removeClass("hide");
         }
       }
       else {
-        if ($("#blueprintX").val() != "-1" && $("#blueprintY").val() != "-1") {
-          var _x = $("#blueprintX").val() * 1;
-          var _y = $("#blueprintY").val() * 1;
-          var _data = [];
-          $("#resultTable>tbody>tr>td:nth-child(" + _x + ")").each(function (cnt) {
-            if (cnt < 1000) {
-              _data.push([$.trim($(this).text()), $.trim($("#resultTable>tbody>tr:nth-child(" + (cnt + 1) + ")>td:nth-child(" + _y + ")").text()) * 1]);
-            }
-          });
-          if ($("#resultTable>tbody>tr>td:nth-child(" + _x + ")").length > 1000){
-            $("#chart .alert").removeClass("hide");
-          }
-          $("#blueprint").jHueBlueprint({
-            data: _data,
-            label: $("#resultTable>thead>tr>th:nth-child(" + _y + ")").text(),
-            type: graphType,
-            color: $.jHueBlueprint.COLORS.BLUE,
-            isCategories: true,
-            fill: true,
-            enableSelection: false,
-            height: $("#blueprint").parent().height() - 100
-          });
-          if (_data.length > 30){
-            $(".flot-x-axis .flot-tick-label").hide();
+        $("#blueprint").addClass("empty").css("text-align", "center").text("${_("Please select the latitude and longitude columns.")}");
+      }
+    }
+    else {
+      if ($("#blueprintX").val() != "-1" && $("#blueprintY").val() != "-1") {
+        var _x = $("#blueprintX").val() * 1;
+        var _y = $("#blueprintY").val() * 1;
+        var _koData = [];
+        $("#resultTable>tbody>tr>td:nth-child(" + _x + ")").each(function (cnt) {
+          if (cnt < 1000) {
+            _koData.push({ value: $.trim($(this).text()), count: $.trim($("#resultTable>tbody>tr:nth-child(" + (cnt + 1) + ")>td:nth-child(" + _y + ")").text()) * 1});
           }
-        }
-        else {
-          $("#blueprint").addClass("empty").text("${_("Please select the columns you would like to see in this chart.")}");
+        });
+
+        viewModel.chartData(_koData);
+
+        if ($("#resultTable>tbody>tr>td:nth-child(" + _x + ")").length > 1000){
+          $("#chart .alert").removeClass("hide");
         }
       }
+      else {
+        $("#blueprint").addClass("empty").css("text-align", "center").text("${_("Please select the columns you would like to see in this chart.")}");
+      }
     }
   }
+}
 
-  function getGraphType() {
-    var _type = "";
-    if ($("#blueprintBars").hasClass("active")) {
-      _type = $.jHueBlueprint.TYPES.BARCHART;
-    }
-    if ($("#blueprintLines").hasClass("active")) {
-      _type = $.jHueBlueprint.TYPES.LINECHART;
-    }
-    if ($("#blueprintMap").hasClass("active")) {
-      _type = $.jHueBlueprint.TYPES.MAP;
-    }
-    return _type;
+function getGraphType() {
+  var _type = "";
+  if ($("#blueprintBars").hasClass("active")) {
+    _type = ko.HUE_CHARTS.TYPES.BARCHART;
+  }
+  if ($("#blueprintLines").hasClass("active")) {
+    _type = ko.HUE_CHARTS.TYPES.LINECHART;
+  }
+  if ($("#blueprintMap").hasClass("active")) {
+    _type = ko.HUE_CHARTS.TYPES.MAP;
   }
+  if ($("#blueprintPie").hasClass("active")) {
+    _type = ko.HUE_CHARTS.TYPES.PIECHART;
+  }
+  viewModel.chartType(_type);
+  return _type;
+}
 
 
-  function predictGraph() {
-    if (!graphHasBeenPredicted) {
-      graphHasBeenPredicted = true;
-      var _firstAllString, _firstAllNumeric;
-      var _cols = viewModel.design.results.columns();
-      $(_cols).each(function (cnt, col) {
-        if (cnt > 0){
-          if (_firstAllString == null && !isNumericColumn(col.type)) {
-            _firstAllString = cnt + 1;
-          }
-          if (_firstAllNumeric == null && isNumericColumn(col.type)) {
-            _firstAllNumeric = cnt + 1;
-          }
+function predictGraph() {
+  if (!graphHasBeenPredicted) {
+    graphHasBeenPredicted = true;
+    var _firstAllString, _firstAllNumeric;
+    var _cols = viewModel.design.results.columns();
+    $(_cols).each(function (cnt, col) {
+      if (cnt > 0){
+        if (_firstAllString == null && !isNumericColumn(col.type)) {
+          _firstAllString = cnt + 1;
+        }
+        if (_firstAllNumeric == null && isNumericColumn(col.type)) {
+          _firstAllNumeric = cnt + 1;
         }
-      });
-
-      if (_firstAllString != null && _firstAllNumeric != null) {
-        $("#blueprintBars").addClass("active");
-        $("#blueprintAxis").removeClass("hide");
-        $("#blueprintLatLng").addClass("hide");
-        $("#blueprintX").val(_firstAllString);
-        $("#blueprintY").val(_firstAllNumeric);
       }
+    });
+
+    if (_firstAllString != null && _firstAllNumeric != null) {
+      $("#blueprintBars").addClass("active");
+      $("#blueprintLines").removeClass("active");
+      $("#blueprintPie").removeClass("active");
+      $("#blueprintMap").removeClass("active");
+      $("#blueprintAxis").removeClass("hide");
+      $("#blueprintLatLng").addClass("hide");
+      $("#blueprintX").val(_firstAllString);
+      $("#blueprintY").val(_firstAllNumeric);
     }
-    generateGraph(getGraphType());
   }
+  generateGraph(getGraphType());
+}
+
+// Logs
+var logsAtEnd = true;
+$(document).ready(function () {
+  var labels = {
+    MRJOB: "${_('MR Job')}",
+    MRJOBS: "${_('MR Jobs')}"
+  };
+
+  $(window).resize(function () {
+    resizeLogs();
+  });
+
+  $("a[href='#log']").on("shown", function () {
+    resizeLogs();
+  });
 
   $(".blueprintSelect").on("change", function () {
     generateGraph(getGraphType())
@@ -1790,17 +1789,26 @@ $(document).ready(function () {
   $("#blueprintBars").on("click", function () {
     $("#blueprintAxis").removeClass("hide");
     $("#blueprintLatLng").addClass("hide");
-    generateGraph($.jHueBlueprint.TYPES.BARCHART)
+    viewModel.chartType(ko.HUE_CHARTS.TYPES.BARCHART);
+    generateGraph(ko.HUE_CHARTS.TYPES.BARCHART)
   });
   $("#blueprintLines").on("click", function () {
     $("#blueprintAxis").removeClass("hide");
     $("#blueprintLatLng").addClass("hide");
-    generateGraph($.jHueBlueprint.TYPES.LINECHART)
+    viewModel.chartType(ko.HUE_CHARTS.TYPES.LINECHART);
+    generateGraph(ko.HUE_CHARTS.TYPES.LINECHART)
+  });
+  $("#blueprintPie").on("click", function () {
+    $("#blueprintAxis").removeClass("hide");
+    $("#blueprintLatLng").addClass("hide");
+    viewModel.chartType(ko.HUE_CHARTS.TYPES.PIECHART);
+    generateGraph(ko.HUE_CHARTS.TYPES.PIECHART)
   });
   $("#blueprintMap").on("click", function () {
     $("#blueprintAxis").addClass("hide");
     $("#blueprintLatLng").removeClass("hide");
-    generateGraph($.jHueBlueprint.TYPES.MAP)
+    viewModel.chartType(ko.HUE_CHARTS.TYPES.MAP);
+    generateGraph(ko.HUE_CHARTS.TYPES.MAP)
   });
 
   $("#log pre:eq(1)").scroll(function () {
@@ -2614,6 +2622,86 @@ ko.applyBindings(viewModel);
   $(document).ready(queryEvents);
 % endif
 
+
+// chart related stuff
+
+
+function pieChartDataTransformer(data) {
+  var _data = [];
+  $(data.counts()).each(function (cnt, item) {
+    _data.push({
+      label: item.value,
+      value: item.count,
+      obj: item
+    });
+  });
+  return _data;
+}
+
+function leafletMapChartDataTransformer(data) {
+  if (data != null && data.counts != null) return data.counts();
+}
+
+function barChartDataTransformer(rawDatum) {
+  var _datum = [];
+  var _data = [];
+
+  $(rawDatum.counts()).each(function (cnt, item) {
+    if (typeof item.from != "undefined") {
+      _data.push({
+        series: 0,
+        x: item.from,
+        x_end: item.to,
+        y: item.value,
+        obj: item
+      });
+    }
+    else {
+      _data.push({
+        series: 0,
+        x: item.value,
+        y: item.count,
+        obj: item
+      });
+    }
+  });
+  _datum.push({
+    key: $("#blueprintY option:selected").text(),
+    values: _data
+  });
+  return _datum;
+}
+
+function lineChartDataTransformer(rawDatum) {
+  var _datum = [];
+  var _data = [];
+    $(rawDatum.counts()).each(function (cnt, item) {
+      if (typeof item.from != "undefined") {
+        _data.push({
+          series: 0,
+          x: item.from,
+          x_end: item.to,
+          y: item.value,
+          obj: item
+        });
+      }
+      else {
+        _data.push({
+          series: 0,
+          x: item.value,
+          y: item.count,
+          obj: item
+        });
+      }
+    });
+    _datum.push({
+      key: $("#blueprintY option:selected").text(),
+      values: _data
+    });
+  return _datum;
+}
+
+
 </script>
 
 ${ commonfooter(messages) | n,unicode }

+ 3 - 0
apps/beeswax/static/js/beeswax.vm.js

@@ -73,6 +73,9 @@ function BeeswaxViewModel(server) {
 
   self.design = ko.mapping.fromJS(DESIGN_DEFAULTS);
 
+  self.chartType = ko.observable("bars");
+  self.chartData = ko.observableArray();
+
   self.server = ko.observable(server);
   self.databases = ko.observableArray();
   self.selectedDatabase = ko.observable(0);

+ 318 - 176
desktop/core/static/js/ko.charts.js

@@ -14,35 +14,54 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+ko.HUE_CHARTS = {
+  TYPES: {
+    LINECHART: "lines",
+    BARCHART: "bars",
+    POINTCHART: "points",
+    PIECHART: "pie",
+    MAP: "map"
+  }
+};
 
 ko.bindingHandlers.pieChart = {
-    init: function (element, valueAccessor) {
-      var _options = valueAccessor();
-      var _data = _options.transformer(_options.data);
-      $(element).css("marginLeft", "auto");
-      $(element).css("marginRight", "auto");
-      if (typeof _options.maxWidth != "undefined"){
-        var _max = _options.maxWidth*1;
-        $(element).width(Math.min($(element).parent().width(), _max));
-      }
+  update: function (element, valueAccessor) {
+    var _options = valueAccessor();
+    var _data = _options.transformer(_options.data);
+    $(element).css("marginLeft", "auto");
+    $(element).css("marginRight", "auto");
+    if (typeof _options.maxWidth != "undefined") {
+      var _max = _options.maxWidth * 1;
+      $(element).width(Math.min($(element).parent().width(), _max));
+    }
 
+    if ($(element).find('svg').length > 0 && _data.length == 0) {
+      $(element).find('svg').empty();
+    }
+
+    if (_data.length > 0 && isNaN(_data[0].value)) {
+      _data = [];
+      $(element).find('svg').empty();
+    }
+
+    if ($(element).is(":visible")) {
       nv.addGraph(function () {
         var _chart = nv.models.growingPieChart()
-                .x(function (d) {
-                  return d.label
-                })
-                .y(function (d) {
-                  return d.value
-                })
-                .height($(element).width())
-                .showLabels(true).showLegend(false);
+            .x(function (d) {
+              return d.label
+            })
+            .y(function (d) {
+              return d.value
+            })
+            .height($(element).width())
+            .showLabels(true).showLegend(false);
 
         var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
 
         _d3.datum(_data)
-                .transition().duration(150)
-                .each("end", _options.onComplete)
-                .call(_chart);
+            .transition().duration(150)
+            .each("end", _options.onComplete != null ? _options.onComplete : void(0))
+            .call(_chart);
         if (_options.fqs) {
           $.each(_options.fqs(), function (cnt, item) {
             if (item.field() == _options.field()) {
@@ -53,9 +72,9 @@ ko.bindingHandlers.pieChart = {
 
         nv.utils.windowResize(_chart.update);
         $(element).height($(element).width());
-        $(element).parents(".card-widget").on("resize", function(){
-          if (typeof _options.maxWidth != "undefined"){
-            var _max = _options.maxWidth*1;
+        $(element).parents(".card-widget").on("resize", function () {
+          if (typeof _options.maxWidth != "undefined") {
+            var _max = _options.maxWidth * 1;
             $(element).width(Math.min($(element).parent().width(), _max));
           }
           $(element).height($(element).width());
@@ -66,32 +85,112 @@ ko.bindingHandlers.pieChart = {
       }, function () {
         var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
         _d3.selectAll(".nv-slice").on('click',
-          function (d, i) {
-            chartsUpdatingState();
-            _options.onClick(d);
-          });
+            function (d, i) {
+              if (typeof _options.onClick != "undefined") {
+                chartsUpdatingState();
+                _options.onClick(d);
+              }
+            });
       });
     }
-  };
+  }
+};
 
 ko.bindingHandlers.barChart = {
-  init: function (element, valueAccessor) {
+  update: function (element, valueAccessor) {
     barChartBuilder(element, valueAccessor(), false);
   }
 };
 
 ko.bindingHandlers.timelineChart = {
-  init: function (element, valueAccessor) {
+  update: function (element, valueAccessor) {
     barChartBuilder(element, valueAccessor(), true);
   }
 };
 
 ko.bindingHandlers.lineChart = {
-  init: function (element, valueAccessor) {
+  update: function (element, valueAccessor) {
     lineChartBuilder(element, valueAccessor());
   }
 };
 
+ko.bindingHandlers.leafletMapChart = {
+  update: function (element, valueAccessor) {
+    var _options = valueAccessor();
+    var _data = _options.transformer(valueAccessor().datum);
+
+
+    function getMapBounds(lats, lngs) {
+      lats = lats.sort();
+      lngs = lngs.sort();
+      return [
+        [lats[lats.length - 1], lngs[lngs.length - 1]], // north-east
+        [lats[0], lngs[0]] // south-west
+      ]
+    }
+
+    if ($(element).data("map") != null) {
+      try {
+        $(element).data("map").remove();
+      }
+      catch (err) {
+        if (typeof console != "undefined") {
+          console.error(err);
+        }
+      }
+    }
+
+    var _lats = [];
+    _data.forEach(function (item) {
+      if (item.lat != null) {
+        _lats.push(item.lat);
+      }
+    });
+    var _lngs = [];
+    _data.forEach(function (item) {
+      if (item.lng != null) {
+        _lngs.push(item.lng);
+      }
+    });
+
+    $(element).height($(element).parents(".tab-pane").height() - 100);
+
+    var _map = null;
+    if (element._map != null) {
+      element._leaflet = false;
+      element._map.remove();
+    }
+
+    if (_lats.length > 0 && _lngs.length > 0) {
+      try {
+        if (_map == null) {
+          _map = L.map($(element).attr("id"));
+          L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
+            attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributorss'
+          }).addTo(_map);
+        }
+
+        _map.fitBounds(getMapBounds(_lats, _lngs));
+
+        _data.forEach(function (item) {
+          if (item.lng != null && item.lat != null) {
+            var _marker = L.marker([item.lat, item.lng]).addTo(_map);
+            if (item.label != null) {
+              _marker.bindPopup(item.label);
+            }
+          }
+        });
+      }
+      catch (err) {
+        $.jHueNotify.error(err.message);
+      }
+    }
+    element._map = _map;
+
+  }
+};
+
+
 ko.bindingHandlers.mapChart = {
   render: function (element, valueAccessor) {
 
@@ -103,17 +202,17 @@ ko.bindingHandlers.mapChart = {
     $(element).css("marginLeft", "auto");
     $(element).css("marginRight", "auto");
 
-    if (typeof _options.maxWidth != "undefined"){
-        var _max = _options.maxWidth*1;
-        $(element).width(Math.min($(element).parent().width(), _max));
-      }
+    if (typeof _options.maxWidth != "undefined") {
+      var _max = _options.maxWidth * 1;
+      $(element).width(Math.min($(element).parent().width(), _max));
+    }
 
     $(element).height($(element).width() / 2.23);
 
     var _scope = typeof _options.data.scope != "undefined" ? String(_options.data.scope) : "world";
     var _data = _options.transformer(_options.data);
     var _maxWeight = 0;
-    $(_data).each(function(cnt, item) {
+    $(_data).each(function (cnt, item) {
       if (item.value > _maxWeight) _maxWeight = item.value;
     });
 
@@ -127,12 +226,12 @@ ko.bindingHandlers.mapChart = {
     if (_options.isScale) {
       _fills["defaultFill"] = HueColors.WHITE;
       var _colors = HueColors.scale(HueColors.LIGHT_BLUE, HueColors.DARK_BLUE, _data.length);
-      $(_colors).each(function(cnt, item) {
+      $(_colors).each(function (cnt, item) {
         _fills["fill_" + cnt] = item;
       });
-      $(_data).each(function(cnt, item) {
+      $(_data).each(function (cnt, item) {
         var _place = item.label.toUpperCase();
-        if (_place != null){
+        if (_place != null) {
           _mapdata[_place] = {
             fillKey: "fill_" + (Math.floor(item.value / _chunk) - 1),
             id: _place,
@@ -150,9 +249,9 @@ ko.bindingHandlers.mapChart = {
     else {
       _fills["defaultFill"] = HueColors.LIGHT_BLUE;
       _fills["selected"] = HueColors.DARK_BLUE;
-      $(_data).each(function(cnt, item) {
-      var _place = item.label.toUpperCase();
-        if (_place != null){
+      $(_data).each(function (cnt, item) {
+        var _place = item.label.toUpperCase();
+        if (_place != null) {
           _mapdata[_place] = {
             fillKey: "selected",
             id: _place,
@@ -169,37 +268,38 @@ ko.bindingHandlers.mapChart = {
     }
 
     var _map = null;
+
     function createDatamap(element, options, fills, mapData, nonCountries, mapHovers) {
       _map = new Datamap({
         element: element,
         fills: fills,
         scope: _scope,
         data: mapData,
-        onClick: function(data) {
+        onClick: function (data) {
           if (typeof options.onClick != "undefined") {
             chartsUpdatingState();
             options.onClick(data);
           }
         },
-        done: function(datamap) {
+        done: function (datamap) {
           var _bubbles = [];
           if (options.enableGeocoding) {
-            $(nonCountries).each(function(cnt, item){
-                HueGeo.getCityCoordinates(item.label, function(lat, lng){
-                    _bubbles.push({
-                      fillKey: "selected",
-                      label: item.label,
-                      value: item.value,
-                      radius: 4,
-                      latitude: lat,
-                      longitude: lng
-                    });
-                    _map.bubbles(_bubbles, {
-                      popupTemplate: function(geo, data) {
-                        return '<div class="hoverinfo" style="text-align: center"><strong>'  + data.label + '</strong><br/>' + item.value + '</div>'
-                      }
-                    });
+            $(nonCountries).each(function (cnt, item) {
+              HueGeo.getCityCoordinates(item.label, function (lat, lng) {
+                _bubbles.push({
+                  fillKey: "selected",
+                  label: item.label,
+                  value: item.value,
+                  radius: 4,
+                  latitude: lat,
+                  longitude: lng
                 });
+                _map.bubbles(_bubbles, {
+                  popupTemplate: function (geo, data) {
+                    return '<div class="hoverinfo" style="text-align: center"><strong>' + data.label + '</strong><br/>' + item.value + '</div>'
+                  }
+                });
+              });
             });
           }
         },
@@ -212,21 +312,23 @@ ko.bindingHandlers.mapChart = {
           highlightBorderColor: HueColors.BLUE,
           selectedFillColor: HueColors.DARKER_BLUE,
           selectedBorderColor: HueColors.DARKER_BLUE,
-          popupTemplate: function(geography, data) {
-          var _hover = '';
-          if (data != null) {
-            _hover = '<br/>' + mapHovers[data.id];
-          }
+          popupTemplate: function (geography, data) {
+            var _hover = '';
+            if (data != null) {
+              _hover = '<br/>' + mapHovers[data.id];
+            }
             return '<div class="hoverinfo" style="text-align: center"><strong>' + geography.properties.name + '</strong>' + _hover + '</div>'
           }
         }
       });
-      options.onComplete();
+      if (options.onComplete != null) {
+        options.onComplete();
+      }
     }
 
     createDatamap(element, _options, _fills, _mapdata, _noncountries, _maphovers)
 
-    $(element).parents(".card-widget").one("resize", function(){
+    $(element).parents(".card-widget").one("resize", function () {
       ko.bindingHandlers.mapChart.render(element, valueAccessor);
     });
   },
@@ -239,43 +341,55 @@ ko.bindingHandlers.mapChart = {
 };
 
 
-
 function lineChartBuilder(element, options) {
   var _datum = options.transformer(options.datum);
   $(element).height(300);
+  if ($(element).find('svg').length > 0 && (_datum.length == 0 || _datum[0].values.length == 0)) {
+    $(element).find('svg').empty();
+  }
+  if (_datum.length > 0 && _datum[0].values.length > 0 && (isNaN(_datum[0].values[0].x) || isNaN(_datum[0].values[0].y))) {
+    _datum = [];
+    $(element).find('svg').empty();
+  }
 
-  nv.addGraph(function () {
-    var _chart = nv.models.lineWithBrushChart();
-    if (_datum.length > 0 && _datum[0].values.length > 10){
-      _chart.enableSelection();
-    }
-    _chart.onSelectRange(function(from, to){
-      chartsUpdatingState();
-      options.onSelectRange(from, to);
-    });
-    _chart.xAxis.showMaxMin(false);
-
-    _chart.yAxis
-        .tickFormat(d3.format(',0f'));
+  if ($(element).is(":visible")) {
+    nv.addGraph(function () {
+      var _chart = nv.models.lineWithBrushChart();
+      if (_datum.length > 0 && _datum[0].values.length > 10) {
+        _chart.enableSelection();
+      }
+      if (options.showControls != null) {
+        _chart.showControls(false);
+      }
+      _chart.onSelectRange(function (from, to) {
+        chartsUpdatingState();
+        options.onSelectRange(from, to);
+      });
+      _chart.xAxis.showMaxMin(false);
 
-    var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
-    _d3.datum(_datum)
-        .transition().duration(150)
-        .each("end", options.onComplete)
-        .call(_chart);
+      _chart.yAxis
+          .tickFormat(d3.format(',0f'));
 
-    nv.utils.windowResize(_chart.update);
+      var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
+      _d3.datum(_datum)
+          .transition().duration(150)
+          .each("end", options.onComplete != null ? options.onComplete : void(0))
+          .call(_chart);
 
-    return _chart;
-  }, function () {
-    var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
-    _d3.selectAll(".nv-line").on('click',
-      function (d, i) {
-        chartsUpdatingState();
-        options.onClick(d);
-      });
-  });
+      nv.utils.windowResize(_chart.update);
 
+      return _chart;
+    }, function () {
+      var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
+      _d3.selectAll(".nv-line").on('click',
+          function (d, i) {
+            if (typeof options.onClick != "undefined") {
+              chartsUpdatingState();
+              options.onClick(d);
+            }
+          });
+    });
+  }
 }
 
 
@@ -283,105 +397,133 @@ function barChartBuilder(element, options, isTimeline) {
   var _datum = options.transformer(options.datum);
   $(element).height(300);
 
-  nv.addGraph(function () {
-    var _chart;
-
-    var insertLinebreaks = function (d) {
-      var _el = d3.select(this);
-      var _mom = moment(d);
-      if (_mom != null && _mom.isValid()) {
-        var _words = _mom.format("hh:mm:ss YYYY-MM-DD").split(" ");
-        _el.text('');
-        for (var i = 0; i < _words.length; i++) {
-          var tspan = _el.append("tspan").text(_words[i]);
-          if (i > 0) {
-            tspan.attr("x", 0).attr("dy", '15');
+  if ($(element).find('svg').length > 0 && (_datum.length == 0 || _datum[0].values.length == 0)) {
+    $(element).find('svg').empty();
+  }
+
+  if (_datum.length > 0 && _datum[0].values.length > 0 && isNaN(_datum[0].values[0].y)) {
+    _datum = [];
+    $(element).find('svg').empty();
+  }
+
+  if ($(element).is(":visible")) {
+    nv.addGraph(function () {
+      var _chart;
+
+
+      var insertLinebreaks = function (d) {
+        var _el = d3.select(this);
+        var _mom = moment(d);
+        if (_mom != null && _mom.isValid()) {
+          var _words = _mom.format("hh:mm:ss YYYY-MM-DD").split(" ");
+          _el.text('');
+          for (var i = 0; i < _words.length; i++) {
+            var tspan = _el.append("tspan").text(_words[i]);
+            if (i > 0) {
+              tspan.attr("x", 0).attr("dy", '15');
+            }
           }
         }
-      }
-    };
+      };
 
-    if (isTimeline) {
-      _chart = nv.models.multiBarWithBrushChart();
-      if (_datum.length > 0 && _datum[0].values.length > 10){
-        _chart.enableSelection();
-      }
-      _chart.onSelectRange(function(from, to){
-        chartsUpdatingState();
-        options.onSelectRange(from, to);
-      });
-      _chart.xAxis.tickFormat(d3.time.format("%Y-%m-%d %H:%M:%S"));
-      _chart.multibar.hideable(true);
-      _chart.multibar.stacked(typeof options.stacked != "undefined" ? options.stacked : false);
-      _chart.onStateChange(options.onStateChange);
-      _chart.onChartUpdate(function(){
-        _d3.selectAll("g.nv-x.nv-axis g text").each(insertLinebreaks);
-      });
-    }
-    else {
-      var _isDiscrete = false;
-      for (var j=0;j<_datum.length;j++){
-        for (var i=0;i<_datum[j].values.length;i++){
-          if (isNaN(_datum[j].values[i].x * 1)){
-            _isDiscrete = true;
-            break;
-          }
+      if (isTimeline) {
+        if ($(element).find('svg').length > 0 && $(element).find('.nv-discreteBarWithAxes').length > 0) {
+          $(element).find('svg').empty();
         }
-      }
-      if (_isDiscrete){
-        _chart = nv.models.growingDiscreteBarChart()
-        .x(function(d) { return d.x })
-        .y(function(d) { return d.y })
-        .staggerLabels(true);
-      }
-      else {
         _chart = nv.models.multiBarWithBrushChart();
-        if (_datum.length > 0 && _datum[0].values.length > 10){
+        if (_datum.length > 0 && _datum[0].values.length > 10) {
           _chart.enableSelection();
         }
-        _chart.xAxis.showMaxMin(false).tickFormat(d3.format(',0f'));
+        _chart.onSelectRange(function (from, to) {
+          chartsUpdatingState();
+          options.onSelectRange(from, to);
+        });
+        _chart.xAxis.tickFormat(d3.time.format("%Y-%m-%d %H:%M:%S"));
         _chart.multibar.hideable(true);
         _chart.multibar.stacked(typeof options.stacked != "undefined" ? options.stacked : false);
         _chart.onStateChange(options.onStateChange);
-        _chart.onSelectRange(function(from, to){
-          chartsUpdatingState();
-          options.onSelectRange(from, to);
+        _chart.onChartUpdate(function () {
+          _d3.selectAll("g.nv-x.nv-axis g text").each(insertLinebreaks);
         });
       }
-    }
-    _chart.transitionDuration(0);
-
-    _chart.yAxis
-        .tickFormat(d3.format(',0f'));
-
-    var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
-    _d3.datum(_datum)
-        .transition().duration(150)
-        .each("end", function(){
-          options.onComplete();
-          if (isTimeline) {
-            _d3.selectAll("g.nv-x.nv-axis g text").each(insertLinebreaks);
+      else {
+        var _isDiscrete = false;
+        for (var j = 0; j < _datum.length; j++) {
+          for (var i = 0; i < _datum[j].values.length; i++) {
+            if (isNaN(_datum[j].values[i].x * 1)) {
+              _isDiscrete = true;
+              break;
+            }
           }
-        }).call(_chart);
-
-    $.each(options.fqs(), function(cnt, item){
-      if (item.field() == options.field){
-        _chart.selectBars(item.filter());
+        }
+        if (_isDiscrete) {
+          if ($(element).find('svg').length > 0 && $(element).find('.nv-multiBarWithLegend').length > 0) {
+            $(element).find('svg').empty();
+          }
+          _chart = nv.models.growingDiscreteBarChart()
+              .x(function (d) {
+                return d.x
+              })
+              .y(function (d) {
+                return d.y
+              })
+              .staggerLabels(true);
+        }
+        else {
+          if ($(element).find('svg').length > 0 && $(element).find('.nv-discreteBarWithAxes').length > 0) {
+            $(element).find('svg').empty();
+          }
+          _chart = nv.models.multiBarWithBrushChart();
+          if (_datum.length > 0 && _datum[0].values.length > 10) {
+            _chart.enableSelection();
+          }
+          _chart.xAxis.showMaxMin(false).tickFormat(d3.format(',0f'));
+          _chart.multibar.hideable(true);
+          _chart.multibar.stacked(typeof options.stacked != "undefined" ? options.stacked : false);
+          _chart.onStateChange(options.onStateChange);
+          _chart.onSelectRange(function (from, to) {
+            chartsUpdatingState();
+            options.onSelectRange(from, to);
+          });
+        }
       }
-    });
+      _chart.transitionDuration(0);
 
-    nv.utils.windowResize(_chart.update);
+      _chart.yAxis
+          .tickFormat(d3.format(',0f'));
 
-    return _chart;
-  }, function () {
-    var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
-    _d3.selectAll(".nv-bar").on("click",
-      function (d, i) {
-        chartsUpdatingState();
-        options.onClick(d);
+      var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
+      _d3.datum(_datum)
+          .transition().duration(150)
+          .each("end", function () {
+            if (options.onComplete != null) {
+              options.onComplete();
+            }
+            if (isTimeline) {
+              _d3.selectAll("g.nv-x.nv-axis g text").each(insertLinebreaks);
+            }
+          }).call(_chart);
+
+      $.each(options.fqs(), function (cnt, item) {
+        if (item.field() == options.field) {
+          _chart.selectBars(item.filter());
+        }
       });
-  });
 
+      nv.utils.windowResize(_chart.update);
+
+      return _chart;
+    }, function () {
+      var _d3 = ($(element).find('svg').length > 0) ? d3.select($(element).find('svg')[0]) : d3.select($(element)[0]).append('svg');
+      _d3.selectAll(".nv-bar").on("click",
+          function (d, i) {
+            if (typeof options.onClick != "undefined") {
+              chartsUpdatingState();
+              options.onClick(d);
+            }
+          });
+    });
+  }
 }
 
 function chartsUpdatingState() {