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: Multi layers DECK.GL visualization (#4096)
Date Tue, 26 Dec 2017 18:47:32 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 45686a1  Multi layers DECK.GL visualization (#4096)
45686a1 is described below

commit 45686a1af6a8d3469c0c28cec3a57c9b137f146b
Author: Maxime Beauchemin <maximebeauchemin@gmail.com>
AuthorDate: Tue Dec 26 10:47:29 2017 -0800

    Multi layers DECK.GL visualization (#4096)
    
    * Multi layers DECK.GL viz
    
    * Fix tests
    
    * rebasing
    
    * Fix error handling in chartActions
    
    * Addressing comments
---
 setup.py                                           |   2 +-
 superset/assets/images/viz_thumbnails/multi.png    | Bin 0 -> 761211 bytes
 superset/assets/javascripts/chart/chartAction.js   |  15 +++++-
 .../assets/javascripts/explore/stores/controls.jsx |  19 +++++++
 .../assets/javascripts/explore/stores/visTypes.js  |  17 ++++++-
 .../deckgl/{path.jsx => factory.jsx}               |  20 ++------
 superset/assets/visualizations/deckgl/grid.jsx     |  43 ----------------
 superset/assets/visualizations/deckgl/hex.jsx      |  43 ----------------
 .../visualizations/deckgl/{ => layers}/geojson.jsx |  30 ++---------
 .../assets/visualizations/deckgl/layers/grid.jsx   |  23 +++++++++
 .../assets/visualizations/deckgl/layers/hex.jsx    |  23 +++++++++
 .../assets/visualizations/deckgl/layers/index.js   |  17 +++++++
 .../assets/visualizations/deckgl/layers/path.jsx   |  19 +++++++
 .../visualizations/deckgl/layers/scatter.jsx       |  35 +++++++++++++
 .../visualizations/deckgl/layers/screengrid.jsx    |  23 +++++++++
 superset/assets/visualizations/deckgl/multi.jsx    |  44 +++++++++++++++++
 superset/assets/visualizations/deckgl/scatter.jsx  |  55 ---------------------
 .../assets/visualizations/deckgl/screengrid.jsx    |  43 ----------------
 superset/assets/visualizations/main.js             |  15 +++---
 superset/views/core.py                             |   2 +-
 superset/viz.py                                    |  28 +++++++++++
 21 files changed, 280 insertions(+), 236 deletions(-)

diff --git a/setup.py b/setup.py
index 0ecd1ef..f662e88 100644
--- a/setup.py
+++ b/setup.py
@@ -73,7 +73,7 @@ setup(
         'pyyaml>=3.11',
         'requests==2.17.3',
         'simplejson==3.10.0',
-        'six==1.10.0',
+        'six==1.11.0',
         'sqlalchemy==1.1.9',
         'sqlalchemy-utils==0.32.16',
         'sqlparse==0.2.3',
diff --git a/superset/assets/images/viz_thumbnails/multi.png b/superset/assets/images/viz_thumbnails/multi.png
new file mode 100644
index 0000000..be62cd4
Binary files /dev/null and b/superset/assets/images/viz_thumbnails/multi.png differ
diff --git a/superset/assets/javascripts/chart/chartAction.js b/superset/assets/javascripts/chart/chartAction.js
index a6341dd..393400d 100644
--- a/superset/assets/javascripts/chart/chartAction.js
+++ b/superset/assets/javascripts/chart/chartAction.js
@@ -120,7 +120,20 @@ export function runQuery(formData, force = false, timeout = 60, key)
{
         if (err.statusText === 'timeout') {
           dispatch(chartUpdateTimeout(err.statusText, timeout, key));
         } else if (err.statusText !== 'abort') {
-          dispatch(chartUpdateFailed(err.responseJSON, key));
+          let errObject;
+          if (err.responseJSON) {
+            errObject = err.responseJSON;
+          } else if (err.stack) {
+            errObject = {
+              error: 'Unexpected error: ' + err.description,
+              stacktrace: err.stack,
+            };
+          } else {
+            errObject = {
+              error: 'Unexpected error.',
+            };
+          }
+          dispatch(chartUpdateFailed(errObject, key));
         }
       });
     const annotationLayers = formData.annotation_layers || [];
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx
index 7aee160..70cc231 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -1389,6 +1389,7 @@ export const controls = {
   mapbox_style: {
     type: 'SelectControl',
     label: t('Map Style'),
+    clearable: false,
     renderTrigger: true,
     choices: [
       ['mapbox://styles/mapbox/streets-v9', 'Streets'],
@@ -1816,5 +1817,23 @@ export const controls = {
         and returns a similarly shaped object. {sandboxedEvalInfo}
       </p>),
   },
+
+  deck_slices: {
+    type: 'SelectAsyncControl',
+    multi: true,
+    label: t('deck.gl charts'),
+    validators: [v.nonEmpty],
+    default: [],
+    description: t('Pick a set of deck.gl charts to layer on top of one another'),
+    dataEndpoint: '/sliceasync/api/read?_flt_0_viz_type=deck_',
+    placeholder: t('Select charts'),
+    onAsyncErrorMessage: t('Error while fetching charts'),
+    mutator: (data) => {
+      if (!data || !data.result) {
+        return [];
+      }
+      return data.result.map(o => ({ value: o.id, label: o.slice_name }));
+    },
+  },
 };
 export default controls;
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js
index f4720f9..f2e668f 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -338,6 +338,21 @@ export const visTypes = {
     },
   },
 
+  deck_multi: {
+    label: t('Deck.gl - Multiple Layers'),
+    requiresTime: true,
+    controlPanelSections: [
+      {
+        label: t('Map'),
+        expanded: true,
+        controlSetRows: [
+          ['mapbox_style', 'viewport'],
+          ['deck_slices', null],
+        ],
+      },
+    ],
+  },
+
   deck_hex: {
     label: t('Deck.gl - Hexagons'),
     requiresTime: true,
@@ -398,7 +413,7 @@ export const visTypes = {
   },
 
   deck_path: {
-    label: t('Deck.gl - Grid'),
+    label: t('Deck.gl - Paths'),
     requiresTime: true,
     controlPanelSections: [
       {
diff --git a/superset/assets/visualizations/deckgl/path.jsx b/superset/assets/visualizations/deckgl/factory.jsx
similarity index 54%
rename from superset/assets/visualizations/deckgl/path.jsx
rename to superset/assets/visualizations/deckgl/factory.jsx
index c814adc..d715bc1 100644
--- a/superset/assets/visualizations/deckgl/path.jsx
+++ b/superset/assets/visualizations/deckgl/factory.jsx
@@ -1,25 +1,12 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
-import { PathLayer } from 'deck.gl';
 
 import DeckGLContainer from './DeckGLContainer';
+import layerGenerators from './layers';
 
-function deckPath(slice, payload, setControlValue) {
+export default function deckglFactory(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 layer = layerGenerators[fd.viz_type](fd, payload);
   const viewport = {
     ...fd.viewport,
     width: slice.width(),
@@ -36,4 +23,3 @@ function deckPath(slice, payload, setControlValue) {
     document.getElementById(slice.containerId),
   );
 }
-module.exports = deckPath;
diff --git a/superset/assets/visualizations/deckgl/grid.jsx b/superset/assets/visualizations/deckgl/grid.jsx
deleted file mode 100644
index 1ef2394..0000000
--- a/superset/assets/visualizations/deckgl/grid.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { GridLayer } from 'deck.gl';
-
-import DeckGLContainer from './DeckGLContainer';
-
-function deckScreenGridLayer(slice, payload, setControlValue) {
-  const fd = slice.formData;
-  const c = fd.color_picker;
-  const data = payload.data.features.map(d => ({
-    ...d,
-    color: [c.r, c.g, c.b, 255 * c.a],
-  }));
-
-  const layer = new GridLayer({
-    id: `grid-layer-${slice.containerId}`,
-    data,
-    pickable: true,
-    cellSize: fd.grid_size,
-    minColor: [0, 0, 0, 0],
-    extruded: fd.extruded,
-    maxColor: [c.r, c.g, c.b, 255 * c.a],
-    outline: false,
-    getElevationValue: points => points.reduce((sum, point) => sum + point.weight,
0),
-    getColorValue: points => points.reduce((sum, point) => sum + point.weight, 0),
-  });
-  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 = deckScreenGridLayer;
diff --git a/superset/assets/visualizations/deckgl/hex.jsx b/superset/assets/visualizations/deckgl/hex.jsx
deleted file mode 100644
index 9526825..0000000
--- a/superset/assets/visualizations/deckgl/hex.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { HexagonLayer } from 'deck.gl';
-
-import DeckGLContainer from './DeckGLContainer';
-
-function deckHex(slice, payload, setControlValue) {
-  const fd = slice.formData;
-  const c = fd.color_picker;
-  const data = payload.data.features.map(d => ({
-    ...d,
-    color: [c.r, c.g, c.b, 255 * c.a],
-  }));
-
-  const layer = new HexagonLayer({
-    id: `hex-layer-${slice.containerId}`,
-    data,
-    pickable: true,
-    radius: fd.grid_size,
-    minColor: [0, 0, 0, 0],
-    extruded: fd.extruded,
-    maxColor: [c.r, c.g, c.b, 255 * c.a],
-    outline: false,
-    getElevationValue: points => points.reduce((sum, point) => sum + point.weight,
0),
-    getColorValue: points => points.reduce((sum, point) => sum + point.weight, 0),
-  });
-  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 = deckHex;
diff --git a/superset/assets/visualizations/deckgl/geojson.jsx b/superset/assets/visualizations/deckgl/layers/geojson.jsx
similarity index 59%
rename from superset/assets/visualizations/deckgl/geojson.jsx
rename to superset/assets/visualizations/deckgl/layers/geojson.jsx
index 080d7ee..11a7b83 100644
--- a/superset/assets/visualizations/deckgl/geojson.jsx
+++ b/superset/assets/visualizations/deckgl/layers/geojson.jsx
@@ -1,9 +1,6 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
 import { GeoJsonLayer } from 'deck.gl';
-import { hexToRGB } from '../../javascripts/modules/colors';
+import { hexToRGB } from '../../../javascripts/modules/colors';
 
-import DeckGLContainer from './DeckGLContainer';
 
 const propertyMap = {
   fillColor: 'fillColor',
@@ -26,8 +23,8 @@ const convertGeoJsonColorProps = (p, colors) => {
   };
 };
 
-function DeckGeoJsonLayer(slice, payload, setControlValue) {
-  const fd = slice.formData;
+export default function geoJsonLayer(formData, payload) {
+  const fd = formData;
   const fc = fd.fill_color_picker;
   const sc = fd.stroke_color_picker;
   const data = payload.data.geojson.features.map(d => ({
@@ -39,29 +36,12 @@ function DeckGeoJsonLayer(slice, payload, setControlValue) {
       }),
   }));
 
-  const layer = new GeoJsonLayer({
-    id: 'geojson-layer',
+  return new GeoJsonLayer({
+    id: `path-layer-${fd.slice_id}`,
     data,
     filled: true,
     stroked: false,
     extruded: true,
     pointRadiusScale: fd.point_radius_scale,
   });
-
-  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 = DeckGeoJsonLayer;
diff --git a/superset/assets/visualizations/deckgl/layers/grid.jsx b/superset/assets/visualizations/deckgl/layers/grid.jsx
new file mode 100644
index 0000000..51b1e03
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/layers/grid.jsx
@@ -0,0 +1,23 @@
+import { GridLayer } from 'deck.gl';
+
+export default function getLayer(formData, payload) {
+  const fd = formData;
+  const c = fd.color_picker;
+  const data = payload.data.features.map(d => ({
+    ...d,
+    color: [c.r, c.g, c.b, 255 * c.a],
+  }));
+
+  return new GridLayer({
+    id: `grid-layer-${fd.slice_id}`,
+    data,
+    pickable: true,
+    cellSize: fd.grid_size,
+    minColor: [0, 0, 0, 0],
+    extruded: fd.extruded,
+    maxColor: [c.r, c.g, c.b, 255 * c.a],
+    outline: false,
+    getElevationValue: points => points.reduce((sum, point) => sum + point.weight,
0),
+    getColorValue: points => points.reduce((sum, point) => sum + point.weight, 0),
+  });
+}
diff --git a/superset/assets/visualizations/deckgl/layers/hex.jsx b/superset/assets/visualizations/deckgl/layers/hex.jsx
new file mode 100644
index 0000000..0e33e94
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/layers/hex.jsx
@@ -0,0 +1,23 @@
+import { HexagonLayer } from 'deck.gl';
+
+export default function getLayer(formData, payload) {
+  const fd = formData;
+  const c = fd.color_picker;
+  const data = payload.data.features.map(d => ({
+    ...d,
+    color: [c.r, c.g, c.b, 255 * c.a],
+  }));
+
+  return new HexagonLayer({
+    id: `hex-layer-${fd.slice_id}`,
+    data,
+    pickable: true,
+    radius: fd.grid_size,
+    minColor: [0, 0, 0, 0],
+    extruded: fd.extruded,
+    maxColor: [c.r, c.g, c.b, 255 * c.a],
+    outline: false,
+    getElevationValue: points => points.reduce((sum, point) => sum + point.weight,
0),
+    getColorValue: points => points.reduce((sum, point) => sum + point.weight, 0),
+  });
+}
diff --git a/superset/assets/visualizations/deckgl/layers/index.js b/superset/assets/visualizations/deckgl/layers/index.js
new file mode 100644
index 0000000..a382af5
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/layers/index.js
@@ -0,0 +1,17 @@
+/* eslint camelcase: 0 */
+import deck_grid from './grid';
+import deck_screengrid from './screengrid';
+import deck_path from './path';
+import deck_hex from './hex';
+import deck_scatter from './scatter';
+import deck_geojson from './geojson';
+
+const layerGenerators = {
+  deck_grid,
+  deck_screengrid,
+  deck_path,
+  deck_hex,
+  deck_scatter,
+  deck_geojson,
+};
+export default layerGenerators;
diff --git a/superset/assets/visualizations/deckgl/layers/path.jsx b/superset/assets/visualizations/deckgl/layers/path.jsx
new file mode 100644
index 0000000..c288ff0
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/layers/path.jsx
@@ -0,0 +1,19 @@
+import { PathLayer } from 'deck.gl';
+
+export default function getLayer(formData, payload) {
+  const fd = 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,
+  }));
+
+  return new PathLayer({
+    id: `path-layer-${fd.slice_id}`,
+    data,
+    rounded: true,
+    widthScale: 1,
+  });
+}
diff --git a/superset/assets/visualizations/deckgl/layers/scatter.jsx b/superset/assets/visualizations/deckgl/layers/scatter.jsx
new file mode 100644
index 0000000..d44e727
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/layers/scatter.jsx
@@ -0,0 +1,35 @@
+import { ScatterplotLayer } from 'deck.gl';
+
+import { getColorFromScheme, hexToRGB } from '../../../javascripts/modules/colors';
+import { unitToRadius } from '../../../javascripts/modules/geo';
+
+export default function getLayer(formData, payload) {
+  const fd = formData;
+  const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
+  const fixedColor = [c.r, c.g, c.b, 255 * c.a];
+
+  const data = payload.data.features.map((d) => {
+    let radius = unitToRadius(fd.point_unit, d.radius) || 10;
+    if (fd.multiplier) {
+      radius *= fd.multiplier;
+    }
+    let color;
+    if (fd.dimension) {
+      color = hexToRGB(getColorFromScheme(d.cat_color, fd.color_scheme), c.a * 255);
+    } else {
+      color = fixedColor;
+    }
+    return {
+      ...d,
+      radius,
+      color,
+    };
+  });
+  return new ScatterplotLayer({
+    id: `scatter-layer-${fd.slice_id}`,
+    data,
+    pickable: true,
+    fp64: true,
+    outline: false,
+  });
+}
diff --git a/superset/assets/visualizations/deckgl/layers/screengrid.jsx b/superset/assets/visualizations/deckgl/layers/screengrid.jsx
new file mode 100644
index 0000000..54edd9e
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/layers/screengrid.jsx
@@ -0,0 +1,23 @@
+import { ScreenGridLayer } from 'deck.gl';
+
+export default function getLayer(formData, payload) {
+  const fd = formData;
+  const c = fd.color_picker;
+  const data = payload.data.features.map(d => ({
+    ...d,
+    color: [c.r, c.g, c.b, 255 * c.a],
+  }));
+
+  // Passing a layer creator function instead of a layer since the
+  // layer needs to be regenerated at each render
+  return new ScreenGridLayer({
+    id: `screengrid-layer-${fd.slice_id}`,
+    data,
+    pickable: true,
+    cellSizePixels: fd.grid_size,
+    minColor: [c.r, c.g, c.b, 0],
+    maxColor: [c.r, c.g, c.b, 255 * c.a],
+    outline: false,
+    getWeight: d => d.weight || 0,
+  });
+}
diff --git a/superset/assets/visualizations/deckgl/multi.jsx b/superset/assets/visualizations/deckgl/multi.jsx
new file mode 100644
index 0000000..63f1a88
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/multi.jsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import $ from 'jquery';
+
+import DeckGLContainer from './DeckGLContainer';
+import { getExploreUrl } from '../../javascripts/explore/exploreUtils';
+import layerGenerators from './layers';
+
+
+function deckMulti(slice, payload, setControlValue) {
+  if (!slice.subSlicesLayers) {
+    slice.subSlicesLayers = {}; // eslint-disable-line no-param-reassign
+  }
+  const fd = slice.formData;
+  const render = () => {
+    const viewport = {
+      ...fd.viewport,
+      width: slice.width(),
+      height: slice.height(),
+    };
+    const layers = Object.keys(slice.subSlicesLayers).map(k => slice.subSlicesLayers[k]);
+    ReactDOM.render(
+      <DeckGLContainer
+        mapboxApiAccessToken={payload.data.mapboxApiKey}
+        viewport={viewport}
+        layers={layers}
+        mapStyle={fd.mapbox_style}
+        setControlValue={setControlValue}
+      />,
+      document.getElementById(slice.containerId),
+    );
+  };
+  render();
+  payload.data.slices.forEach((subslice) => {
+    const url = getExploreUrl(subslice.form_data, 'json');
+    $.get(url, (data) => {
+      // Late import to avoid circular deps
+      const layer = layerGenerators[subslice.form_data.viz_type](subslice.form_data, data);
+      slice.subSlicesLayers[subslice.slice_id] = layer; // eslint-disable-line no-param-reassign
+      render();
+    });
+  });
+}
+module.exports = deckMulti;
diff --git a/superset/assets/visualizations/deckgl/scatter.jsx b/superset/assets/visualizations/deckgl/scatter.jsx
deleted file mode 100644
index 18cec55..0000000
--- a/superset/assets/visualizations/deckgl/scatter.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { ScatterplotLayer } from 'deck.gl';
-
-import DeckGLContainer from './DeckGLContainer';
-import { getColorFromScheme, hexToRGB } from '../../javascripts/modules/colors';
-import { unitToRadius } from '../../javascripts/modules/geo';
-
-function deckScatter(slice, payload, setControlValue) {
-  const fd = slice.formData;
-  const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
-  const fixedColor = [c.r, c.g, c.b, 255 * c.a];
-
-  const data = payload.data.features.map((d) => {
-    let radius = unitToRadius(fd.point_unit, d.radius) || 10;
-    if (fd.multiplier) {
-      radius *= fd.multiplier;
-    }
-    let color;
-    if (fd.dimension) {
-      color = hexToRGB(getColorFromScheme(d.cat_color, fd.color_scheme), c.a * 255);
-    } else {
-      color = fixedColor;
-    }
-    return {
-      ...d,
-      radius,
-      color,
-    };
-  });
-
-  const layer = new ScatterplotLayer({
-    id: `scatter-layer-${slice.containerId}`,
-    data,
-    pickable: true,
-    fp64: true,
-    outline: false,
-  });
-  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 = deckScatter;
diff --git a/superset/assets/visualizations/deckgl/screengrid.jsx b/superset/assets/visualizations/deckgl/screengrid.jsx
deleted file mode 100644
index b8b58ec..0000000
--- a/superset/assets/visualizations/deckgl/screengrid.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { ScreenGridLayer } from 'deck.gl';
-
-import DeckGLContainer from './DeckGLContainer';
-
-function deckScreenGridLayer(slice, payload, setControlValue) {
-  const fd = slice.formData;
-  const c = fd.color_picker;
-  const data = payload.data.features.map(d => ({
-    ...d,
-    color: [c.r, c.g, c.b, 255 * c.a],
-  }));
-
-  const viewport = {
-    ...fd.viewport,
-    width: slice.width(),
-    height: slice.height(),
-  };
-  // Passing a layer creator function instead of a layer since the
-  // layer needs to be regenerated at each render
-  const layer = () => new ScreenGridLayer({
-    id: `screengrid-layer-${slice.containerId}`,
-    data,
-    pickable: true,
-    cellSizePixels: fd.grid_size,
-    minColor: [c.r, c.g, c.b, 0],
-    maxColor: [c.r, c.g, c.b, 255 * c.a],
-    outline: false,
-    getWeight: d => d.weight || 0,
-  });
-  ReactDOM.render(
-    <DeckGLContainer
-      mapboxApiAccessToken={payload.data.mapboxApiKey}
-      viewport={viewport}
-      layers={[layer]}
-      mapStyle={fd.mapbox_style}
-      setControlValue={setControlValue}
-    />,
-    document.getElementById(slice.containerId),
-  );
-}
-module.exports = deckScreenGridLayer;
diff --git a/superset/assets/visualizations/main.js b/superset/assets/visualizations/main.js
index e692c10..af7b040 100644
--- a/superset/assets/visualizations/main.js
+++ b/superset/assets/visualizations/main.js
@@ -1,4 +1,5 @@
 /* eslint-disable global-require */
+import deckglFactory from './deckgl/factory';
 
 // You ***should*** use these to reference viz_types in code
 export const VIZ_TYPES = {
@@ -44,6 +45,7 @@ export const VIZ_TYPES = {
   deck_hex: 'deck_hex',
   deck_path: 'deck_path',
   deck_geojson: 'deck_geojson',
+  deck_multi: 'deck_multi',
 };
 
 const vizMap = {
@@ -84,11 +86,12 @@ const vizMap = {
   [VIZ_TYPES.event_flow]: require('./EventFlow.jsx'),
   [VIZ_TYPES.paired_ttest]: require('./paired_ttest.jsx'),
   [VIZ_TYPES.partition]: require('./partition.js'),
-  [VIZ_TYPES.deck_scatter]: require('./deckgl/scatter.jsx'),
-  [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'),
-  [VIZ_TYPES.deck_geojson]: require('./deckgl/geojson.jsx'),
+  [VIZ_TYPES.deck_scatter]: deckglFactory,
+  [VIZ_TYPES.deck_screengrid]: deckglFactory,
+  [VIZ_TYPES.deck_grid]: deckglFactory,
+  [VIZ_TYPES.deck_hex]: deckglFactory,
+  [VIZ_TYPES.deck_path]: deckglFactory,
+  [VIZ_TYPES.deck_geojson]: deckglFactory,
+  [VIZ_TYPES.deck_multi]: require('./deckgl/multi.jsx'),
 };
 export default vizMap;
diff --git a/superset/views/core.py b/superset/views/core.py
index 02e6f1d..802fda9 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -493,7 +493,7 @@ appbuilder.add_view(
 
 class SliceAsync(SliceModelView):  # noqa
     list_columns = [
-        'slice_link', 'viz_type',
+        'id', 'slice_link', 'viz_type', 'slice_name',
         'creator', 'modified', 'icons']
     label_columns = {
         'icons': ' ',
diff --git a/superset/viz.py b/superset/viz.py
index 2a6b494..bb052d1 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -86,6 +86,8 @@ class BaseViz(object):
         """Returns a pandas dataframe based on the query object"""
         if not query_obj:
             query_obj = self.query_obj()
+        if not query_obj:
+            return None
 
         self.error_msg = ''
         self.results = None
@@ -1768,6 +1770,32 @@ class MapboxViz(BaseViz):
         }
 
 
+class DeckGLMultiLayer(BaseViz):
+
+    """Pile on multiple DeckGL layers"""
+
+    viz_type = 'deck_multi'
+    verbose_name = _('Deck.gl - Multiple Layers')
+
+    is_timeseries = False
+    credits = '<a href="https://uber.github.io/deck.gl/">deck.gl</a>'
+
+    def query_obj(self):
+        return None
+
+    def get_data(self, df):
+        fd = self.form_data
+        # Late imports to avoid circular import issues
+        from superset.models.core import Slice
+        from superset import db
+        slice_ids = fd.get('deck_slices')
+        slices = db.session.query(Slice).filter(Slice.id.in_(slice_ids)).all()
+        return {
+            'mapboxApiKey': config.get('MAPBOX_API_KEY'),
+            'slices': [slc.data for slc in slices],
+        }
+
+
 class BaseDeckGLViz(BaseViz):
 
     """Base class for deck.gl visualizations"""

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

Mime
View raw message