superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From maximebeauche...@apache.org
Subject [incubator-superset] branch master updated: [geo] add support for deck.gl's path layer (#4067)
Date Tue, 19 Dec 2017 20:38:06 GMT
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 e4903e6  [geo] add support for deck.gl's path layer (#4067)
e4903e6 is described below

commit e4903e6dc665aa0b4928031f6bf721884229bbe7
Author: Maxime Beauchemin <maximebeauchemin@gmail.com>
AuthorDate: Tue Dec 19 12:38:03 2017 -0800

    [geo] add support for deck.gl's path layer (#4067)
    
    * [geo] add support for deck.gl's path layer
    
    Works with json and polyline data.
    
    * Lint
---
 setup.py                                           |   1 +
 .../assets/images/viz_thumbnails/deck_path.png     | Bin 0 -> 523094 bytes
 .../assets/javascripts/explore/stores/controls.jsx |  38 +++++++++++++++
 .../assets/javascripts/explore/stores/visTypes.js  |  24 ++++++++++
 superset/assets/visualizations/deckgl/path.jsx     |  39 ++++++++++++++++
 superset/assets/visualizations/main.js             |   2 +
 superset/cli.py                                    |   3 ++
 superset/data/__init__.py                          |  33 ++++++++++++-
 superset/data/bart-lines.json.gz                   | Bin 0 -> 1267 bytes
 superset/viz.py                                    |  51 +++++++++++++++++----
 10 files changed, 182 insertions(+), 9 deletions(-)

diff --git a/setup.py b/setup.py
index 8d0dba3..0ecd1ef 100644
--- a/setup.py
+++ b/setup.py
@@ -66,6 +66,7 @@ setup(
         'pandas==0.20.3',
         'parsedatetime==2.0.0',
         'pathlib2==2.3.0',
+        'polyline==1.3.2',
         'pydruid==0.3.1',
         'PyHive>=0.4.0',
         'python-dateutil==2.6.0',
diff --git a/superset/assets/images/viz_thumbnails/deck_path.png b/superset/assets/images/viz_thumbnails/deck_path.png
new file mode 100644
index 0000000..eede9da
Binary files /dev/null and b/superset/assets/images/viz_thumbnails/deck_path.png differ
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx
index 5c18b4d..7117712 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -1721,5 +1721,43 @@ export const controls = {
       t('Partitions whose height to parent height proportions are ' +
       'below this value are pruned'),
   },
+
+  line_column: {
+    type: 'SelectControl',
+    label: t('Lines column'),
+    default: null,
+    description: t('The database columns that contains lines information'),
+    mapStateToProps: state => ({
+      choices: (state.datasource) ? state.datasource.all_cols : [],
+    }),
+    validators: [v.nonEmpty],
+  },
+  line_type: {
+    type: 'SelectControl',
+    label: t('Lines encoding'),
+    clearable: false,
+    default: 'json',
+    description: t('The encoding format of the lines'),
+    choices: [
+      ['polyline', 'Polyline'],
+      ['json', 'JSON'],
+    ],
+  },
+
+  line_width: {
+    type: 'TextControl',
+    label: t('Line width'),
+    renderTrigger: true,
+    isInt: true,
+    default: 10,
+    description: t('The width of the lines'),
+  },
+
+  reverse_long_lat: {
+    type: 'CheckboxControl',
+    label: t('Reverse Lat & Long'),
+    default: false,
+  },
+
 };
 export default controls;
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js
index ef9dc41..2c5f2a6 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -397,6 +397,30 @@ export const visTypes = {
     },
   },
 
+  deck_path: {
+    label: t('Deck.gl - Grid'),
+    requiresTime: true,
+    controlPanelSections: [
+      {
+        label: t('Query'),
+        expanded: true,
+        controlSetRows: [
+          ['line_column', 'line_type'],
+          ['row_limit', null],
+        ],
+      },
+      {
+        label: t('Map'),
+        expanded: true,
+        controlSetRows: [
+          ['mapbox_style', 'viewport'],
+          ['color_picker', 'line_width'],
+          ['reverse_long_lat', null],
+        ],
+      },
+    ],
+  },
+
   deck_screengrid: {
     label: t('Deck.gl - Screen grid'),
     requiresTime: true,
diff --git a/superset/assets/visualizations/deckgl/path.jsx b/superset/assets/visualizations/deckgl/path.jsx
new file mode 100644
index 0000000..c814adc
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/path.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { PathLayer } from 'deck.gl';
+
+import DeckGLContainer from './DeckGLContainer';
+
+function deckPath(slice, payload, setControlValue) {
+  const fd = slice.formData;
+  const c = fd.color_picker;
+  const fixedColor = [c.r, c.g, c.b, 255 * c.a];
+  const data = payload.data.paths.map(path => ({
+    path,
+    width: fd.line_width,
+    color: fixedColor,
+  }));
+
+  const layer = new PathLayer({
+    id: `path-layer-${slice.containerId}`,
+    data,
+    rounded: true,
+    widthScale: 1,
+  });
+  const viewport = {
+    ...fd.viewport,
+    width: slice.width(),
+    height: slice.height(),
+  };
+  ReactDOM.render(
+    <DeckGLContainer
+      mapboxApiAccessToken={payload.data.mapboxApiKey}
+      viewport={viewport}
+      layers={[layer]}
+      mapStyle={fd.mapbox_style}
+      setControlValue={setControlValue}
+    />,
+    document.getElementById(slice.containerId),
+  );
+}
+module.exports = deckPath;
diff --git a/superset/assets/visualizations/main.js b/superset/assets/visualizations/main.js
index fdcb7b1..06d30a1 100644
--- a/superset/assets/visualizations/main.js
+++ b/superset/assets/visualizations/main.js
@@ -42,6 +42,7 @@ export const VIZ_TYPES = {
   deck_screengrid: 'deck_screengrid',
   deck_grid: 'deck_grid',
   deck_hex: 'deck_hex',
+  deck_path: 'deck_path',
 };
 
 const vizMap = {
@@ -86,5 +87,6 @@ const vizMap = {
   [VIZ_TYPES.deck_screengrid]: require('./deckgl/screengrid.jsx'),
   [VIZ_TYPES.deck_grid]: require('./deckgl/grid.jsx'),
   [VIZ_TYPES.deck_hex]: require('./deckgl/hex.jsx'),
+  [VIZ_TYPES.deck_path]: require('./deckgl/path.jsx'),
 };
 export default vizMap;
diff --git a/superset/cli.py b/superset/cli.py
index 16500ac..56ead72 100755
--- a/superset/cli.py
+++ b/superset/cli.py
@@ -146,6 +146,9 @@ def load_examples(load_test_data):
     print('Loading flights data')
     data.load_flights()
 
+    print('Loading bart lines data')
+    data.load_bart_lines()
+
 
 @manager.option(
     '-d', '--datasource',
diff --git a/superset/data/__init__.py b/superset/data/__init__.py
index 742a32b..b3cb4a8 100644
--- a/superset/data/__init__.py
+++ b/superset/data/__init__.py
@@ -12,8 +12,9 @@ import random
 import textwrap
 
 import pandas as pd
-from sqlalchemy import BigInteger, Date, DateTime, Float, String
+from sqlalchemy import BigInteger, Date, DateTime, Float, String, Text
 import geohash
+import polyline
 
 from superset import app, db, utils
 from superset.connectors.connector_registry import ConnectorRegistry
@@ -1519,3 +1520,33 @@ def load_flights():
     db.session.merge(obj)
     db.session.commit()
     obj.fetch_metadata()
+
+
+def load_bart_lines():
+    tbl_name = 'bart_lines'
+    with gzip.open(os.path.join(DATA_FOLDER, 'bart-lines.json.gz')) as f:
+        df = pd.read_json(f, encoding='latin-1')
+        df['path_json'] = df.path.map(json.dumps)
+        df['polyline'] = df.path.map(polyline.encode)
+        del df['path']
+    df.to_sql(
+        tbl_name,
+        db.engine,
+        if_exists='replace',
+        chunksize=500,
+        dtype={
+            'color': String(255),
+            'name': String(255),
+            'polyline': Text,
+            'path_json': Text,
+        },
+        index=False)
+    print("Creating table {} reference".format(tbl_name))
+    tbl = db.session.query(TBL).filter_by(table_name=tbl_name).first()
+    if not tbl:
+        tbl = TBL(table_name=tbl_name)
+    tbl.description = "BART lines"
+    tbl.database = get_or_create_main_db()
+    db.session.merge(tbl)
+    db.session.commit()
+    tbl.fetch_metadata()
diff --git a/superset/data/bart-lines.json.gz b/superset/data/bart-lines.json.gz
new file mode 100644
index 0000000..91f50fb
Binary files /dev/null and b/superset/data/bart-lines.json.gz differ
diff --git a/superset/viz.py b/superset/viz.py
index 85bc854..55f2603 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -27,6 +27,7 @@ from markdown import markdown
 import numpy as np
 import pandas as pd
 from pandas.tseries.frequencies import to_offset
+import polyline
 import simplejson as json
 from six import PY3, string_types, text_type
 from six.moves import reduce
@@ -1796,13 +1797,14 @@ class BaseDeckGLViz(BaseViz):
         gb = []
 
         spatial = fd.get('spatial')
-        if spatial.get('type') == 'latlong':
-            gb += [spatial.get('lonCol')]
-            gb += [spatial.get('latCol')]
-        elif spatial.get('type') == 'delimited':
-            gb += [spatial.get('lonlatCol')]
-        elif spatial.get('type') == 'geohash':
-            gb += [spatial.get('geohashCol')]
+        if spatial:
+            if spatial.get('type') == 'latlong':
+                gb += [spatial.get('lonCol')]
+                gb += [spatial.get('latCol')]
+            elif spatial.get('type') == 'delimited':
+                gb += [spatial.get('lonlatCol')]
+            elif spatial.get('type') == 'geohash':
+                gb += [spatial.get('geohashCol')]
 
         if fd.get('dimension'):
             gb += [fd.get('dimension')]
@@ -1863,8 +1865,10 @@ class DeckScatterViz(BaseDeckGLViz):
         return super(DeckScatterViz, self).query_obj()
 
     def get_metrics(self):
+        self.metric = None
         if self.point_radius_fixed.get('type') == 'metric':
-            return [self.point_radius_fixed.get('value')]
+            self.metric = self.point_radius_fixed.get('value')
+            return [self.metric]
         return None
 
     def get_properties(self, d):
@@ -1899,6 +1903,37 @@ class DeckGrid(BaseDeckGLViz):
     verbose_name = _('Deck.gl - 3D Grid')
 
 
+class DeckPathViz(BaseDeckGLViz):
+
+    """deck.gl's PathLayer"""
+
+    viz_type = 'deck_path'
+    verbose_name = _('Deck.gl - Paths')
+    deser_map = {
+        'json': json.loads,
+        'polyline': polyline.decode,
+    }
+
+    def query_obj(self):
+        d = super(DeckPathViz, self).query_obj()
+        d['groupby'] = []
+        d['metrics'] = []
+        d['columns'] = [self.form_data.get('line_column')]
+        return d
+
+    def get_data(self, df):
+        fd = self.form_data
+        deser = self.deser_map[fd.get('line_type')]
+        paths = [deser(s) for s in df[fd.get('line_column')]]
+        if fd.get('reverse_long_lat'):
+            paths = [[(point[1], point[0]) for point in path] for path in paths]
+        d = {
+            'mapboxApiKey': config.get('MAPBOX_API_KEY'),
+            'paths': paths,
+        }
+        return d
+
+
 class DeckHex(BaseDeckGLViz):
 
     """deck.gl's DeckLayer"""

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

Mime
View raw message