Parcourir la source

HUE-5188 [editor] Add heatmap functionality to the leaflet map chart

Enrico Berti il y a 9 ans
Parent
commit
6d6982c

Fichier diff supprimé car celui-ci est trop grand
+ 10 - 0
desktop/core/src/desktop/static/desktop/ext/js/leaflet/leaflet.heat.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 6
desktop/core/src/desktop/static/desktop/ext/js/plotly.min.js


+ 76 - 7
desktop/core/src/desktop/static/desktop/js/ko.charts.js

@@ -410,6 +410,10 @@
         if ($(element).data('_map') != null) {
           _map = $(element).data('_map');
           _map.removeLayer($(element).data('_markerLayer'));
+          if ($(element).data('_heatLayer')){
+            _map.removeLayer($(element).data('_heatLayer'));
+            $(element).data('_heatLayer', null);
+          }
         }
 
         var _clusterGroup = L.markerClusterGroup({
@@ -486,6 +490,38 @@
               }
 
             }
+
+            var heatData = [];
+
+            function sumHeatData(data) {
+              var sumData = [];
+              data.forEach(function (d) {
+                var found = false;
+                if (d.length === 3) {
+                  sumData.forEach(function (s) {
+                    if (d[0] == s[0] && d[1] == s[1]) {
+                      found = true;
+                      s[2] += d[2];
+                    }
+                  });
+                }
+                if (!found) {
+                  sumData.push(d);
+                }
+              });
+              return sumData;
+            }
+
+            function getMaxIntensity(data) {
+              var maxIntensity = 0;
+              data.forEach(function (d) {
+                if (d[2] > maxIntensity) {
+                  maxIntensity = d[2];
+                }
+              });
+              return maxIntensity;
+            }
+
             _data.forEach(function (item) {
               if (item && item.lng != null && item.lat != null) {
                 var _addMarker = false;
@@ -500,20 +536,49 @@
                 }
                 if (_addMarker) {
                   var _marker = L.marker([item.lat, item.lng]);
-                  if (item.label != null) {
-                    _marker.bindPopup($.isArray(item.label) ? item.label.join("") : item.label);
+                  if (item.isHeat) {
+                    if (item.intensity != null) {
+                      heatData.push([item.lat, item.lng, item.intensity]);
+                    }
+                    else {
+                      heatData.push([item.lat, item.lng]);
+                    }
+                  }
+                  else {
+                    if (item.label != null) {
+                      _marker.bindPopup($.isArray(item.label) ? item.label.join("") : item.label);
+                    }
                   }
                   _clusterGroup.addLayer(_marker);
                 }
               }
             });
+            var heat;
+            if (heatData.length > 0) {
+              heatData = sumHeatData(heatData);
+              heat = L.heatLayer(heatData);
+              if (heatData[0].length === 3) { // it has intensity
+                heat.setOptions(getMaxIntensity(heatData));
+              }
+            }
 
-            window.setTimeout(function(){
+            window.setTimeout(function () {
               if (!$("#command" + $(element).parents(".card-widget").attr("id")).is(":checked")) {
                 _map.fitBounds(_clusterGroup.getBounds());
               }
               if ($(element).find('.leaflet-tile-pane').children().length > 0) {
-                _map.addLayer(_clusterGroup);
+                if (heatData.length == 0) {
+                  _map.addLayer(_clusterGroup);
+                  $('.leaflet-heatmap-layer').remove();
+                }
+                else {
+                  try {
+                    $('.leaflet-heatmap-layer').remove();
+                    heat.addTo(_map);
+                  }
+                  catch (e) {
+                  } // context2D not initialized yet
+                }
               }
               if (_options.onComplete != null) {
                 _options.onComplete();
@@ -521,12 +586,12 @@
             }, 0);
 
             if (huePubSub) {
-              huePubSub.subscribe('resize.leaflet.map', function(){
+              huePubSub.subscribe('resize.leaflet.map', function () {
                 if ($(element).data('_map')) {
                   $(element).data('_map').invalidateSize();
                   if ($(element).data('_markerLayer')) {
                     try {
-                      $(element).data('_map').fitBounds(_bounds);
+                      $(element).data('_map').fitBounds($(element).data('_markerLayer').getBounds());
                     }
                     catch (e) {
                     }
@@ -544,12 +609,16 @@
         var previousMarkerLayer = $(element).data('_markerLayer');
         if (previousMarkerLayer) {
           window.setTimeout(function () {
-            previousMarkerLayer.removeLayers(previousMarkerLayer.getLayers());
+            try {
+              previousMarkerLayer.removeLayers(previousMarkerLayer.getLayers());
+            }
+            catch(e){}
           }, 0);
         }
 
         $(element).data('_map', _map);
         $(element).data('_markerLayer', _clusterGroup);
+        $(element).data('_heatLayer', heat);
         if (_options.onComplete != null) {
           _options.onComplete();
         }

+ 8 - 0
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -631,7 +631,9 @@ var EditorViewModel = (function() {
     self.chartYSingle = ko.observable(typeof snippet.chartYSingle != "undefined" && snippet.chartYSingle != null ? snippet.chartYSingle : null);
     self.chartYMulti = ko.observableArray(typeof snippet.chartYMulti != "undefined" && snippet.chartYMulti != null ? snippet.chartYMulti : []);
     self.chartData = ko.observableArray(typeof snippet.chartData != "undefined" && snippet.chartData != null ? snippet.chartData : []);
+    self.chartMapType = ko.observable(typeof snippet.chartMapType != "undefined" && snippet.chartMapType != null ? snippet.chartMapType : 'marker');
     self.chartMapLabel = ko.observable(typeof snippet.chartMapLabel != "undefined" && snippet.chartMapLabel != null ? snippet.chartMapLabel : null);
+    self.chartMapHeat = ko.observable(typeof snippet.chartMapHeat != "undefined" && snippet.chartMapHeat != null ? snippet.chartMapHeat : null);
 
     self.hasDataForChart = ko.computed(function () {
       if (self.chartType() == ko.HUE_CHARTS.TYPES.BARCHART || self.chartType() == ko.HUE_CHARTS.TYPES.LINECHART || self.chartType() == ko.HUE_CHARTS.TYPES.TIMELINECHART) {
@@ -685,7 +687,9 @@ var EditorViewModel = (function() {
       self.chartX(guessMetaField(self.previousChartOptions.chartX));
       self.chartXPivot(self.previousChartOptions.chartXPivot);
       self.chartYSingle(guessMetaField(self.previousChartOptions.chartYSingle));
+      self.chartMapType(self.previousChartOptions.chartMapType);
       self.chartMapLabel(guessMetaField(self.previousChartOptions.chartMapLabel));
+      self.chartMapHeat(self.previousChartOptions.chartMapHeat);
       self.chartYMulti(guessMetaFields(self.previousChartOptions.chartYMulti) || []);
       self.chartSorting(self.previousChartOptions.chartSorting);
       self.chartScatterGroup(self.previousChartOptions.chartScatterGroup);
@@ -801,7 +805,9 @@ var EditorViewModel = (function() {
         chartX: typeof self.chartX() !== "undefined" ? self.chartX() : self.previousChartOptions.chartX,
         chartXPivot: typeof self.chartXPivot() !== "undefined" ? self.chartXPivot() : self.previousChartOptions.chartXPivot,
         chartYSingle: typeof self.chartYSingle() !== "undefined" ? self.chartYSingle() : self.previousChartOptions.chartYSingle,
+        chartMapType: typeof self.chartMapType() !== "undefined" ? self.chartMapType() : self.previousChartOptions.chartMapType,
         chartMapLabel: typeof self.chartMapLabel() !== "undefined" ? self.chartMapLabel() : self.previousChartOptions.chartMapLabel,
+        chartMapHeat: typeof self.chartMapHeat() !== "undefined" ? self.chartMapHeat() : self.previousChartOptions.chartMapHeat,
         chartYMulti: typeof self.chartYMulti() !== "undefined" ? self.chartYMulti() : self.previousChartOptions.chartYMulti,
         chartSorting: typeof self.chartSorting() !== "undefined" ? self.chartSorting() : self.previousChartOptions.chartSorting,
         chartScatterGroup: typeof self.chartScatterGroup() !== "undefined" ? self.chartScatterGroup() : self.previousChartOptions.chartScatterGroup,
@@ -2156,7 +2162,9 @@ var EditorViewModel = (function() {
             chartX: typeof snippet.chartX() !== "undefined" ? snippet.chartX() : snippet.previousChartOptions.chartX,
             chartXPivot: typeof snippet.chartXPivot() !== "undefined" ? snippet.chartXPivot() : snippet.previousChartOptions.chartXPivot,
             chartYSingle: typeof snippet.chartYSingle() !== "undefined" ? snippet.chartYSingle() : snippet.previousChartOptions.chartYSingle,
+            chartMapType: typeof snippet.chartMapType() !== "undefined" ? snippet.chartMapType() : snippet.previousChartOptions.chartMapType,
             chartMapLabel: typeof snippet.chartMapLabel() !== "undefined" ? snippet.chartMapLabel() : snippet.previousChartOptions.chartMapLabel,
+            chartMapHeat: typeof snippet.chartMapHeat() !== "undefined" ? snippet.chartMapHeat() : snippet.previousChartOptions.chartMapHeat,
             chartYMulti: typeof snippet.chartYMulti() !== "undefined" ? snippet.chartYMulti() : snippet.previousChartOptions.chartYMulti,
             chartScope: typeof snippet.chartScope() !== "undefined" ? snippet.chartScope() : snippet.previousChartOptions.chartScope,
             chartTimelineType: typeof snippet.chartTimelineType() !== "undefined" ? snippet.chartTimelineType() : snippet.previousChartOptions.chartTimelineType,

+ 34 - 2
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -129,6 +129,7 @@ from notebook.conf import ENABLE_QUERY_BUILDER, ENABLE_QUERY_SCHEDULING, ENABLE_
 
 <script src="${ static('desktop/ext/js/leaflet/leaflet.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/ext/js/leaflet/leaflet.markercluster.js') }" type="text/javascript" charset="utf-8"></script>
+<script src="${ static('desktop/ext/js/leaflet/leaflet.heat.js') }" type="text/javascript" charset="utf-8"></script>
 
 <script src="${ static('desktop/ext/js/d3.v3.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/js/nv.d3.js') }" type="text/javascript" charset="utf-8"></script>
@@ -1670,12 +1671,35 @@ ${ hueIcons.symbols() }
       <select data-bind="options: result.cleanedMeta, value: chartXPivot, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column to pivot...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column to pivot...") }', update: chartXPivot, dropdownAutoWidth: true}" class="input-medium"></select>
     </div>
 
-    <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() == ko.HUE_CHARTS.TYPES.MAP">
+    <!-- ko if: chartType() != '' && chartType() == ko.HUE_CHARTS.TYPES.MAP -->
+    <ul class="nav nav-list" style="border: none; background-color: #FFF">
+      <li class="nav-header">${_('type')}</li>
+    </ul>
+    <div data-bind="visible: chartType() != ''">
+      <select data-bind="selectedOptions: chartMapType, optionsCaption: '${_ko('Choose a type...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a type...") }', update: chartMapType, dropdownAutoWidth: true}">
+        <option value="marker">${ _("Markers") }</option>
+        <option value="heat">${ _("Heatmap") }</option>
+      </select>
+    </div>
+
+    <!-- ko if: chartMapType() == 'marker' -->
+    <ul class="nav nav-list" style="border: none; background-color: #FFF">
       <li class="nav-header">${_('label')}</li>
     </ul>
-    <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP">
+    <div>
       <select data-bind="options: result.cleanedMeta, value: chartMapLabel, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartMapLabel, dropdownAutoWidth: true}" class="input-medium"></select>
     </div>
+    <!-- /ko -->
+
+    <!-- ko if: chartMapType() == 'heat' -->
+    <ul class="nav nav-list" style="border: none; background-color: #FFF">
+      <li class="nav-header">${_('intensity')}</li>
+    </ul>
+    <div>
+      <select data-bind="options: result.cleanedNumericMeta, value: chartMapHeat, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartMapHeat, dropdownAutoWidth: true}" class="input-medium"></select>
+    </div>
+    <!-- /ko -->
+    <!-- /ko -->
 
     <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
       <li class="nav-header">${_('scatter size')}</li>
@@ -2694,6 +2718,7 @@ ${ hueIcons.symbols() }
       var _idxLat = -1;
       var _idxLng = -1;
       var _idxLabel = -1;
+      var _idxHeat = -1;
       rawDatum.snippet.result.meta().forEach(function (col, idx) {
         if (col.name == rawDatum.snippet.chartX()) {
           _idxLat = idx;
@@ -2704,6 +2729,9 @@ ${ hueIcons.symbols() }
         if (col.name == rawDatum.snippet.chartMapLabel()) {
           _idxLabel = idx;
         }
+        if (col.name == rawDatum.snippet.chartMapHeat()) {
+          _idxHeat = idx;
+        }
       });
       if (rawDatum.snippet.chartMapLabel() != null) {
         $(rawDatum.counts()).each(function (cnt, item) {
@@ -2712,6 +2740,8 @@ ${ hueIcons.symbols() }
               lat: Math.min(Math.max(MIN_LAT, item[_idxLat]), MAX_LAT),
               lng: Math.min(Math.max(MIN_LNG, item[_idxLng]), MAX_LNG),
               label: hueUtils.html2text(item[_idxLabel]),
+              isHeat: rawDatum.snippet.chartMapType() === 'heat',
+              intensity: _idxHeat > -1 ? (item[_idxHeat]*1 != NaN ? item[_idxHeat]*1 : null) : null,
               obj: item
             });
           }
@@ -2722,6 +2752,8 @@ ${ hueIcons.symbols() }
             _data.push({
               lat: Math.min(Math.max(MIN_LAT, item[_idxLat]), MAX_LAT),
               lng: Math.min(Math.max(MIN_LNG, item[_idxLng]), MAX_LNG),
+              isHeat: rawDatum.snippet.chartMapType() === 'heat',
+              intensity: _idxHeat > -1 ? (item[_idxHeat]*1 != NaN ? item[_idxHeat]*1 : null) : null,
               obj: item
             });
           }

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff