You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ma...@apache.org on 2018/01/17 22:01:34 UTC

[incubator-superset] branch master updated: Improve deck.gl GeoJSON visualization (#4220)

This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 01043c9  Improve deck.gl GeoJSON visualization (#4220)
01043c9 is described below

commit 01043c9bf45150b430f40e8833111c2b0eb2d5e3
Author: Maxime Beauchemin <ma...@gmail.com>
AuthorDate: Wed Jan 17 14:01:32 2018 -0800

    Improve deck.gl GeoJSON visualization (#4220)
    
    * Improve geoJSON
    
    * Addressing comments
    
    * lint
---
 superset/assets/package.json                       |  1 +
 .../visualizations/deckgl/DeckGLContainer.jsx      |  1 +
 .../visualizations/deckgl/layers/geojson.jsx       | 72 +++++++++++++++------
 .../assets/visualizations/deckgl/layers/grid.jsx   |  1 -
 superset/viz.py                                    | 73 +++++++++-------------
 5 files changed, 86 insertions(+), 62 deletions(-)

diff --git a/superset/assets/package.json b/superset/assets/package.json
index 943e048..c944ad2 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -66,6 +66,7 @@
     "jquery": "3.1.1",
     "lodash.throttle": "^4.1.1",
     "luma.gl": "^5.0.1",
+    "mapbox-gl": "^0.43.0",
     "mathjs": "^3.16.3",
     "moment": "2.18.1",
     "mustache": "^2.2.1",
diff --git a/superset/assets/visualizations/deckgl/DeckGLContainer.jsx b/superset/assets/visualizations/deckgl/DeckGLContainer.jsx
index 64ee934..3166917 100644
--- a/superset/assets/visualizations/deckgl/DeckGLContainer.jsx
+++ b/superset/assets/visualizations/deckgl/DeckGLContainer.jsx
@@ -2,6 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import MapGL from 'react-map-gl';
 import DeckGL from 'deck.gl';
+import 'mapbox-gl/dist/mapbox-gl.css';
 
 const propTypes = {
   viewport: PropTypes.object.isRequired,
diff --git a/superset/assets/visualizations/deckgl/layers/geojson.jsx b/superset/assets/visualizations/deckgl/layers/geojson.jsx
index 3ee1f62..c21ce3b 100644
--- a/superset/assets/visualizations/deckgl/layers/geojson.jsx
+++ b/superset/assets/visualizations/deckgl/layers/geojson.jsx
@@ -14,40 +14,74 @@ const propertyMap = {
   'stroke-width': 'strokeWidth',
 };
 
-const convertGeoJsonColorProps = (p, colors) => {
-  const obj = Object.assign(...Object.keys(p).map(k => ({
-    [(propertyMap[k]) ? propertyMap[k] : k]: p[k] })));
-
+const alterProps = (props, propOverrides) => {
+  const newProps = {};
+  Object.keys(props).forEach((k) => {
+    if (k in propertyMap) {
+      newProps[propertyMap[k]] = props[k];
+    } else {
+      newProps[k] = props[k];
+    }
+  });
+  if (typeof props.fillColor === 'string') {
+    newProps.fillColor = hexToRGB(p.fillColor);
+  }
+  if (typeof props.strokeColor === 'string') {
+    newProps.strokeColor = hexToRGB(p.strokeColor);
+  }
   return {
-    ...obj,
-    fillColor: (colors.fillColor[3] !== 0) ? colors.fillColor : hexToRGB(obj.fillColor),
-    strokeColor: (colors.strokeColor[3] !== 0) ? colors.strokeColor : hexToRGB(obj.strokeColor),
+    ...newProps,
+    ...propOverrides,
   };
 };
+let features;
+const recurseGeoJson = (node, propOverrides, jsFnMutator, extraProps) => {
+  if (node && node.features) {
+    node.features.forEach((obj) => {
+      recurseGeoJson(obj, propOverrides, jsFnMutator, node.extraProps || extraProps);
+    });
+  }
+  if (node && node.geometry) {
+    const newNode = {
+      ...node,
+      properties: alterProps(node.properties, propOverrides),
+    };
+    if (jsFnMutator) {
+      jsFnMutator(newNode);
+    }
+    if (!newNode.extraProps) {
+      newNode.extraProps = extraProps;
+    }
+    features.push(newNode);
+  }
+};
 
 export default function geoJsonLayer(formData, payload, slice) {
   const fd = formData;
   const fc = fd.fill_color_picker;
   const sc = fd.stroke_color_picker;
-  let data = payload.data.geojson.features.map(d => ({
-    ...d,
-    properties: convertGeoJsonColorProps(
-      d.properties, {
-        fillColor: [fc.r, fc.g, fc.b, 255 * fc.a],
-        strokeColor: [sc.r, sc.g, sc.b, 255 * sc.a],
-      }),
-  }));
+  const fillColor = [fc.r, fc.g, fc.b, 255 * fc.a];
+  const strokeColor = [sc.r, sc.g, sc.b, 255 * sc.a];
+  const propOverrides = {};
+  if (fillColor[3] > 0) {
+    propOverrides.fillColor = fillColor;
+  }
+  if (strokeColor[3] > 0) {
+    propOverrides.strokeColor = strokeColor;
+  }
 
+  let jsFnMutator;
   if (fd.js_datapoint_mutator) {
     // Applying user defined data mutator if defined
-    const jsFnMutator = sandboxedEval(fd.js_datapoint_mutator);
-    data = data.map(jsFnMutator);
+    jsFnMutator = sandboxedEval(fd.js_datapoint_mutator);
   }
 
+  features = [];
+  recurseGeoJson(payload.data, propOverrides, jsFnMutator);
   return new GeoJsonLayer({
-    id: `path-layer-${fd.slice_id}`,
-    data,
+    id: `geojson-layer-${fd.slice_id}`,
     filled: fd.filled,
+    data: features,
     stroked: fd.stroked,
     extruded: fd.extruded,
     pointRadiusScale: fd.point_radius_scale,
diff --git a/superset/assets/visualizations/deckgl/layers/grid.jsx b/superset/assets/visualizations/deckgl/layers/grid.jsx
index 51b1e03..a461eb9 100644
--- a/superset/assets/visualizations/deckgl/layers/grid.jsx
+++ b/superset/assets/visualizations/deckgl/layers/grid.jsx
@@ -7,7 +7,6 @@ export default function getLayer(formData, payload) {
     ...d,
     color: [c.r, c.g, c.b, 255 * c.a],
   }));
-
   return new GridLayer({
     id: `grid-layer-${fd.slice_id}`,
     data,
diff --git a/superset/viz.py b/superset/viz.py
index 9d6a57d..65cc975 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -1825,14 +1825,6 @@ class BaseDeckGLViz(BaseViz):
         self.metric = self.form_data.get('size')
         return [self.metric] if self.metric else []
 
-    def get_properties(self, d):
-        return {
-            'weight': d.get(self.metric) or 1,
-        }
-
-    def get_position(self, d):
-        raise Exception('Not implemented in child class!')
-
     def process_spatial_query_obj(self, key, group_by):
         spatial = self.form_data.get(key)
         if spatial is None:
@@ -1898,16 +1890,19 @@ class BaseDeckGLViz(BaseViz):
 
         features = []
         for d in df.to_dict(orient='records'):
-            feature = dict(
-                position=self.get_position(d),
-                props=self.get_js_columns(d),
-                **self.get_properties(d))
+            feature = self.get_properties(d)
+            extra_props = self.get_js_columns(d)
+            if extra_props:
+                feature['extraProps'] = extra_props
             features.append(feature)
         return {
             'features': features,
             'mapboxApiKey': config.get('MAPBOX_API_KEY'),
         }
 
+    def get_properties(self, d):
+        raise NotImplementedError()
+
 
 class DeckScatterViz(BaseDeckGLViz):
 
@@ -1923,9 +1918,6 @@ class DeckScatterViz(BaseDeckGLViz):
             fd.get('point_radius_fixed') or {'type': 'fix', 'value': 500})
         return super(DeckScatterViz, self).query_obj()
 
-    def get_position(self, d):
-        return d['spatial']
-
     def get_metrics(self):
         self.metric = None
         if self.point_radius_fixed.get('type') == 'metric':
@@ -1937,6 +1929,7 @@ class DeckScatterViz(BaseDeckGLViz):
         return {
             'radius': self.fixed_value if self.fixed_value else d.get(self.metric),
             'cat_color': d.get(self.dim) if self.dim else None,
+            'position': d.get('spatial'),
         }
 
     def get_data(self, df):
@@ -1957,8 +1950,11 @@ class DeckScreengrid(BaseDeckGLViz):
     verbose_name = _('Deck.gl - Screen Grid')
     spatial_control_keys = ['spatial']
 
-    def get_position(self, d):
-        return d['spatial']
+    def get_properties(self, d):
+        return {
+            'position': d.get('spatial'),
+            'weight': d.get(self.metric) or 1,
+        }
 
 
 class DeckGrid(BaseDeckGLViz):
@@ -1969,8 +1965,11 @@ class DeckGrid(BaseDeckGLViz):
     verbose_name = _('Deck.gl - 3D Grid')
     spatial_control_keys = ['spatial']
 
-    def get_position(self, d):
-        return d['spatial']
+    def get_properties(self, d):
+        return {
+            'position': d.get('spatial'),
+            'weight': d.get(self.metric) or 1,
+        }
 
 
 class DeckPathViz(BaseDeckGLViz):
@@ -1985,9 +1984,6 @@ class DeckPathViz(BaseDeckGLViz):
     }
     spatial_control_keys = ['spatial']
 
-    def get_position(self, d):
-        return d['spatial']
-
     def query_obj(self):
         d = super(DeckPathViz, self).query_obj()
         line_col = self.form_data.get('line_column')
@@ -2016,8 +2012,11 @@ class DeckHex(BaseDeckGLViz):
     verbose_name = _('Deck.gl - 3D HEX')
     spatial_control_keys = ['spatial']
 
-    def get_position(self, d):
-        return d['spatial']
+    def get_properties(self, d):
+        return {
+            'position': d.get('spatial'),
+            'weight': d.get(self.metric) or 1,
+        }
 
 
 class DeckGeoJson(BaseDeckGLViz):
@@ -2029,22 +2028,14 @@ class DeckGeoJson(BaseDeckGLViz):
 
     def query_obj(self):
         d = super(DeckGeoJson, self).query_obj()
-        d['columns'] = [self.form_data.get('geojson')]
+        d['columns'] += [self.form_data.get('geojson')]
         d['metrics'] = []
         d['groupby'] = []
         return d
 
-    def get_data(self, df):
-        fd = self.form_data
-        geojson = {
-            'type': 'FeatureCollection',
-            'features': [json.loads(item) for item in df[fd.get('geojson')]],
-        }
-
-        return {
-            'geojson': geojson,
-            'mapboxApiKey': config.get('MAPBOX_API_KEY'),
-        }
+    def get_properties(self, d):
+        geojson = d.get(self.form_data.get('geojson'))
+        return json.loads(geojson)
 
 
 class DeckArc(BaseDeckGLViz):
@@ -2055,14 +2046,12 @@ class DeckArc(BaseDeckGLViz):
     verbose_name = _('Deck.gl - Arc')
     spatial_control_keys = ['start_spatial', 'end_spatial']
 
-    def get_position(self, d):
-        deck_map = {
-            'start_spatial': 'sourcePosition',
-            'end_spatial': 'targetPosition',
+    def get_properties(self, d):
+        return {
+            'sourcePosition': d.get('start_spatial'),
+            'targetPosition': d.get('end_spatial'),
         }
 
-        return {deck_map[key]: d[key] for key in self.spatial_control_keys}
-
     def get_data(self, df):
         d = super(DeckArc, self).get_data(df)
         arcs = d['features']

-- 
To stop receiving notification emails like this one, please contact
['"commits@superset.apache.org" <co...@superset.apache.org>'].