superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From b...@apache.org
Subject [incubator-superset] branch master updated: [deck polygon] add support for geohash (#5712)
Date Tue, 28 Aug 2018 18:10:45 GMT
This is an automated email from the ASF dual-hosted git repository.

beto 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 60ecd72  [deck polygon] add support for geohash (#5712)
60ecd72 is described below

commit 60ecd72aac85b8faddeeb339f24299224ecbca91
Author: Maxime Beauchemin <maximebeauchemin@gmail.com>
AuthorDate: Tue Aug 28 11:09:10 2018 -0700

    [deck polygon] add support for geohash (#5712)
    
    * [deck polygon] add support for geohash
    
    + improvements:
    * added autozoom support
    * support for metric & aggregations (only aggregates if metric is picked)
    * fixed stroke
    * fixed opacity
    * introduced a SliderControl
    
    * addressing comments, fixing build
    
    * Addressing comments
---
 superset/assets/package.json                       |   3 +-
 superset/assets/src/chart/Chart.jsx                |   2 +-
 .../src/components/BootstrapSliderWrapper.css      |   8 ++
 .../src/components/BootstrapSliderWrapper.jsx      |  12 ++
 .../src/dashboard/reducers/dashboardState.js       |  62 ++++----
 .../explore/components/controls/SliderControl.jsx  |  35 +++++
 .../src/explore/components/controls/index.js       |   2 +
 superset/assets/src/explore/controls.jsx           |  18 ++-
 superset/assets/src/explore/visTypes.jsx           |  33 ++++-
 superset/assets/src/modules/colors.js              |  28 ++--
 superset/assets/src/visualizations/PlaySlider.css  |   8 --
 superset/assets/src/visualizations/PlaySlider.jsx  |   5 +-
 .../src/visualizations/deckgl/DeckGLContainer.jsx  |   3 +-
 .../src/visualizations/deckgl/layers/common.js     |  12 +-
 .../src/visualizations/deckgl/layers/polygon.jsx   |  39 +++++-
 superset/assets/yarn.lock                          | 156 +++++++++++++++++----
 superset/views/core.py                             |   2 +-
 superset/viz.py                                    |  34 +++--
 18 files changed, 343 insertions(+), 119 deletions(-)

diff --git a/superset/assets/package.json b/superset/assets/package.json
index afc983b..bded7ec 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -61,7 +61,7 @@
     "d3-tip": "^0.9.1",
     "datamaps": "^0.5.8",
     "datatables.net-bs": "^1.10.15",
-    "deck.gl": "^5.1.4",
+    "deck.gl": "^5.3.4",
     "distributions": "^1.0.0",
     "dnd-core": "^2.6.0",
     "dompurify": "^1.0.3",
@@ -72,7 +72,6 @@
     "jed": "^1.1.1",
     "jquery": "3.1.1",
     "lodash.throttle": "^4.1.1",
-    "luma.gl": "^5.1.4",
     "mapbox-gl": "^0.45.0",
     "mathjs": "^3.20.2",
     "moment": "^2.20.1",
diff --git a/superset/assets/src/chart/Chart.jsx b/superset/assets/src/chart/Chart.jsx
index ed49eea..c593cee 100644
--- a/superset/assets/src/chart/Chart.jsx
+++ b/superset/assets/src/chart/Chart.jsx
@@ -189,7 +189,7 @@ class Chart extends React.PureComponent {
           className="chart-tooltip"
           id="chart-tooltip"
           placement="right"
-          positionTop={this.state.tooltip.y - 10}
+          positionTop={this.state.tooltip.y + 30}
           positionLeft={this.state.tooltip.x + 30}
           arrowOffsetTop={10}
         >
diff --git a/superset/assets/src/components/BootstrapSliderWrapper.css b/superset/assets/src/components/BootstrapSliderWrapper.css
new file mode 100644
index 0000000..f2fef45
--- /dev/null
+++ b/superset/assets/src/components/BootstrapSliderWrapper.css
@@ -0,0 +1,8 @@
+.BootstrapSliderWrapper .slider-selection {
+    background: #efefef;
+}
+
+.BootstrapSliderWrapper .slider-handle {
+    background: #b3b3b3;
+}
+
diff --git a/superset/assets/src/components/BootstrapSliderWrapper.jsx b/superset/assets/src/components/BootstrapSliderWrapper.jsx
new file mode 100644
index 0000000..6bcda11
--- /dev/null
+++ b/superset/assets/src/components/BootstrapSliderWrapper.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import ReactBootstrapSlider from 'react-bootstrap-slider';
+import 'bootstrap-slider/dist/css/bootstrap-slider.min.css';
+import './BootstrapSliderWrapper.css';
+
+export default function BootstrapSliderWrapper(props) {
+  return (
+    <span className="BootstrapSliderWrapper">
+      <ReactBootstrapSlider {...props} />
+    </span>
+  );
+}
diff --git a/superset/assets/src/dashboard/reducers/dashboardState.js b/superset/assets/src/dashboard/reducers/dashboardState.js
index 1479f88..c703af8 100644
--- a/superset/assets/src/dashboard/reducers/dashboardState.js
+++ b/superset/assets/src/dashboard/reducers/dashboardState.js
@@ -95,46 +95,34 @@ export default function dashboardStateReducer(state = {}, action) {
       let filters = state.filters;
       const { chart, col, vals: nextVals, merge, refresh } = action;
       const sliceId = chart.id;
-      const filterKeys = [
-        '__time_range',
-        '__time_col',
-        '__time_grain',
-        '__time_origin',
-        '__granularity',
-      ];
-      if (
-        filterKeys.indexOf(col) >= 0 ||
-        action.chart.formData.groupby.indexOf(col) !== -1
-      ) {
-        let newFilter = {};
-        if (!(sliceId in filters)) {
-          // if no filters existed for the slice, set them
-          newFilter = { [col]: nextVals };
-        } else if ((filters[sliceId] && !(col in filters[sliceId])) || !merge) {
-          // If no filters exist for this column, or we are overwriting them
-          newFilter = { ...filters[sliceId], [col]: nextVals };
-        } else if (filters[sliceId][col] instanceof Array) {
-          newFilter[col] = [...filters[sliceId][col], ...nextVals];
-        } else {
-          newFilter[col] = [filters[sliceId][col], ...nextVals];
-        }
-        filters = { ...filters, [sliceId]: newFilter };
+      let newFilter = {};
+      if (!(sliceId in filters)) {
+        // if no filters existed for the slice, set them
+        newFilter = { [col]: nextVals };
+      } else if ((filters[sliceId] && !(col in filters[sliceId])) || !merge) {
+        // If no filters exist for this column, or we are overwriting them
+        newFilter = { ...filters[sliceId], [col]: nextVals };
+      } else if (filters[sliceId][col] instanceof Array) {
+        newFilter[col] = [...filters[sliceId][col], ...nextVals];
+      } else {
+        newFilter[col] = [filters[sliceId][col], ...nextVals];
+      }
+      filters = { ...filters, [sliceId]: newFilter };
 
-        // remove any empty filters so they don't pollute the logs
-        Object.keys(filters).forEach(chartId => {
-          Object.keys(filters[chartId]).forEach(column => {
-            if (
-              !filters[chartId][column] ||
-              filters[chartId][column].length === 0
-            ) {
-              delete filters[chartId][column];
-            }
-          });
-          if (Object.keys(filters[chartId]).length === 0) {
-            delete filters[chartId];
+      // remove any empty filters so they don't pollute the logs
+      Object.keys(filters).forEach(chartId => {
+        Object.keys(filters[chartId]).forEach(column => {
+          if (
+            !filters[chartId][column] ||
+            filters[chartId][column].length === 0
+          ) {
+            delete filters[chartId][column];
           }
         });
-      }
+        if (Object.keys(filters[chartId]).length === 0) {
+          delete filters[chartId];
+        }
+      });
       return { ...state, filters, refresh };
     },
     [SET_UNSAVED_CHANGES]() {
diff --git a/superset/assets/src/explore/components/controls/SliderControl.jsx b/superset/assets/src/explore/components/controls/SliderControl.jsx
new file mode 100644
index 0000000..eee6fa5
--- /dev/null
+++ b/superset/assets/src/explore/components/controls/SliderControl.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import BootstrapSliderWrapper from '../../../components/BootstrapSliderWrapper';
+import ControlHeader from '../ControlHeader';
+
+const propTypes = {
+  onChange: PropTypes.func,
+  value: PropTypes.oneOfType([
+    PropTypes.string,
+    PropTypes.number,
+  ]),
+};
+
+const defaultProps = {
+  onChange: () => {},
+};
+
+export default function SliderControl(props) {
+  // This wouldn't be necessary but might as well
+  return (
+    <div>
+      <ControlHeader {...props} />
+      <BootstrapSliderWrapper
+        {...props}
+        change={(obj) => {
+          props.onChange(obj.target.value);
+        }}
+      />
+    </div>
+  );
+}
+
+SliderControl.propTypes = propTypes;
+SliderControl.defaultProps = defaultProps;
diff --git a/superset/assets/src/explore/components/controls/index.js b/superset/assets/src/explore/components/controls/index.js
index 4a2df42..cc9651f 100644
--- a/superset/assets/src/explore/components/controls/index.js
+++ b/superset/assets/src/explore/components/controls/index.js
@@ -10,6 +10,7 @@ import FixedOrMetricControl from './FixedOrMetricControl';
 import HiddenControl from './HiddenControl';
 import SelectAsyncControl from './SelectAsyncControl';
 import SelectControl from './SelectControl';
+import SliderControl from './SliderControl';
 import SpatialControl from './SpatialControl';
 import TextAreaControl from './TextAreaControl';
 import TextControl from './TextControl';
@@ -32,6 +33,7 @@ const controlMap = {
   HiddenControl,
   SelectAsyncControl,
   SelectControl,
+  SliderControl,
   SpatialControl,
   TextAreaControl,
   TextControl,
diff --git a/superset/assets/src/explore/controls.jsx b/superset/assets/src/explore/controls.jsx
index bee2da0..cb46684 100644
--- a/superset/assets/src/explore/controls.jsx
+++ b/superset/assets/src/explore/controls.jsx
@@ -1470,10 +1470,10 @@ export const controls = {
 
   table_filter: {
     type: 'CheckboxControl',
-    label: t('Table Filter'),
+    label: t('Emit Filter Events'),
     renderTrigger: true,
     default: false,
-    description: t('Whether to apply filter when table cell is clicked'),
+    description: t('Whether to apply filter when items are clicked'),
   },
 
   align_pn: {
@@ -1777,6 +1777,17 @@ export const controls = {
     'Between 0 and 1.'),
   },
 
+  opacity: {
+    type: 'SliderControl',
+    label: t('Opacity'),
+    default: 80,
+    step: 1,
+    min: 0,
+    max: 100,
+    renderTrigger: true,
+    description: t('Opacity, expects values between 0 and 100'),
+  },
+
   viewport: {
     type: 'ViewportControl',
     label: t('Viewport'),
@@ -2121,6 +2132,7 @@ export const controls = {
     choices: [
       ['polyline', 'Polyline'],
       ['json', 'JSON'],
+      ['geohash', 'geohash (square)'],
     ],
   },
 
@@ -2242,7 +2254,7 @@ export const controls = {
     label: t('Filled'),
     renderTrigger: true,
     description: t('Whether to fill the objects'),
-    default: false,
+    default: true,
   },
 
   normalized: {
diff --git a/superset/assets/src/explore/visTypes.jsx b/superset/assets/src/explore/visTypes.jsx
index d4578e0..d0c8c60 100644
--- a/superset/assets/src/explore/visTypes.jsx
+++ b/superset/assets/src/explore/visTypes.jsx
@@ -605,6 +605,14 @@ export const visTypes = {
         ],
       },
     ],
+    controlOverrides: {
+      line_type: {
+        choices: [
+          ['polyline', 'Polyline'],
+          ['json', 'JSON'],
+        ],
+      },
+    },
   },
 
   deck_screengrid: {
@@ -703,25 +711,31 @@ export const visTypes = {
         label: t('Query'),
         expanded: true,
         controlSetRows: [
-          ['line_column', 'line_type'],
-          ['row_limit', 'filter_nulls'],
           ['adhoc_filters'],
+          ['metric'],
+          ['row_limit', null],
+          ['line_column', 'line_type'],
+          ['reverse_long_lat', 'filter_nulls'],
         ],
       },
       {
         label: t('Map'),
+        expanded: true,
         controlSetRows: [
           ['mapbox_style', 'viewport'],
-          ['reverse_long_lat', null],
+          ['autozoom', null],
         ],
       },
       {
         label: t('Polygon Settings'),
+        expanded: true,
         controlSetRows: [
           ['fill_color_picker', 'stroke_color_picker'],
           ['filled', 'stroked'],
           ['extruded', null],
-          ['point_radius_scale', null],
+          ['line_width', null],
+          ['linear_color_scheme', 'opacity'],
+          ['table_filter', null],
         ],
       },
       {
@@ -734,6 +748,17 @@ export const visTypes = {
         ],
       },
     ],
+    controlOverrides: {
+      metric: {
+        validators: [],
+      },
+      line_column: {
+        label: t('Polygon Column'),
+      },
+      line_type: {
+        label: t('Polygon Encoding'),
+      },
+    },
   },
 
   deck_arc: {
diff --git a/superset/assets/src/modules/colors.js b/superset/assets/src/modules/colors.js
index 0bb3221..1cb9eed 100644
--- a/superset/assets/src/modules/colors.js
+++ b/superset/assets/src/modules/colors.js
@@ -525,6 +525,16 @@ export const spectrums = {
   ],
 };
 
+export function hexToRGB(hex, alpha = 255) {
+  if (!hex) {
+    return [0, 0, 0, alpha];
+  }
+  const r = parseInt(hex.slice(1, 3), 16);
+  const g = parseInt(hex.slice(3, 5), 16);
+  const b = parseInt(hex.slice(5, 7), 16);
+  return [r, g, b, alpha];
+}
+
 /**
  * Get a color from a scheme specific palette (scheme)
  * The function cycles through the palette while memoizing labels
@@ -566,7 +576,7 @@ export const getColorFromScheme = (function () {
   };
 }());
 
-export const colorScalerFactory = function (colors, data, accessor, extents) {
+export const colorScalerFactory = function (colors, data, accessor, extents, outputRGBA =
false) {
   // Returns a linear scaler our of an array of color
   if (!Array.isArray(colors)) {
     /* eslint no-param-reassign: 0 */
@@ -581,15 +591,9 @@ export const colorScalerFactory = function (colors, data, accessor, extents)
{
   }
   const chunkSize = (ext[1] - ext[0]) / (colors.length - 1);
   const points = colors.map((col, i) => ext[0] + (i * chunkSize));
-  return d3.scale.linear().domain(points).range(colors).clamp(true);
-};
-
-export function hexToRGB(hex, alpha = 255) {
-  if (!hex) {
-    return [0, 0, 0, alpha];
+  const scaler = d3.scale.linear().domain(points).range(colors).clamp(true);
+  if (outputRGBA) {
+    return v => hexToRGB(scaler(v));
   }
-  const r = parseInt(hex.slice(1, 3), 16);
-  const g = parseInt(hex.slice(3, 5), 16);
-  const b = parseInt(hex.slice(5, 7), 16);
-  return [r, g, b, alpha];
-}
+  return scaler;
+};
diff --git a/superset/assets/src/visualizations/PlaySlider.css b/superset/assets/src/visualizations/PlaySlider.css
index df0fe77..e4919db 100644
--- a/superset/assets/src/visualizations/PlaySlider.css
+++ b/superset/assets/src/visualizations/PlaySlider.css
@@ -5,14 +5,6 @@
     margin: 0;
 }
 
-.slider-selection {
-    background: #efefef;
-}
-
-.slider-handle {
-    background: #b3b3b3;
-}
-
 .slider.slider-horizontal {
     width: 100% !important;
 }
diff --git a/superset/assets/src/visualizations/PlaySlider.jsx b/superset/assets/src/visualizations/PlaySlider.jsx
index 107aa55..fbe635e 100644
--- a/superset/assets/src/visualizations/PlaySlider.jsx
+++ b/superset/assets/src/visualizations/PlaySlider.jsx
@@ -4,8 +4,7 @@ import { Row, Col } from 'react-bootstrap';
 
 import Mousetrap from 'mousetrap';
 
-import 'bootstrap-slider/dist/css/bootstrap-slider.min.css';
-import ReactBootstrapSlider from 'react-bootstrap-slider';
+import BootrapSliderWrapper from '../components/BootstrapSliderWrapper';
 import './PlaySlider.css';
 
 import { t } from '../locales';
@@ -120,7 +119,7 @@ export default class PlaySlider extends React.PureComponent {
           <i className="fa fa-step-forward fa-lg slider-button " onClick={this.step} />
         </Col>
         <Col md={11} className="padded">
-          <ReactBootstrapSlider
+          <BootrapSliderWrapper
             value={range ? values : values[0]}
             range={range}
             formatter={this.formatter}
diff --git a/superset/assets/src/visualizations/deckgl/DeckGLContainer.jsx b/superset/assets/src/visualizations/deckgl/DeckGLContainer.jsx
index 1b7ca31..435a541 100644
--- a/superset/assets/src/visualizations/deckgl/DeckGLContainer.jsx
+++ b/superset/assets/src/visualizations/deckgl/DeckGLContainer.jsx
@@ -7,7 +7,7 @@ import 'mapbox-gl/dist/mapbox-gl.css';
 const propTypes = {
   viewport: PropTypes.object.isRequired,
   layers: PropTypes.array.isRequired,
-  setControlValue: PropTypes.func.isRequired,
+  setControlValue: PropTypes.func,
   mapStyle: PropTypes.string,
   mapboxApiAccessToken: PropTypes.string.isRequired,
   onViewportChange: PropTypes.func,
@@ -15,6 +15,7 @@ const propTypes = {
 const defaultProps = {
   mapStyle: 'light',
   onViewportChange: () => {},
+  setControlValue: () => {},
 };
 
 export default class DeckGLContainer extends React.Component {
diff --git a/superset/assets/src/visualizations/deckgl/layers/common.js b/superset/assets/src/visualizations/deckgl/layers/common.js
index 6fb15df..7eed061 100644
--- a/superset/assets/src/visualizations/deckgl/layers/common.js
+++ b/superset/assets/src/visualizations/deckgl/layers/common.js
@@ -34,12 +34,18 @@ export function fitViewport(viewport, points, padding = 10) {
 export function commonLayerProps(formData, slice) {
   const fd = formData;
   let onHover;
+  let tooltipContentGenerator;
   if (fd.js_tooltip) {
-    const jsTooltip = sandboxedEval(fd.js_tooltip);
+    const unsanitizedTooltipGenerator = sandboxedEval(fd.js_tooltip);
+    tooltipContentGenerator = o => dompurify.sanitize(unsanitizedTooltipGenerator(o));
+  } else if (fd.line_column && fd.line_type === 'geohash') {
+    tooltipContentGenerator = o => `${fd.line_column}: ${o.object[fd.line_column]}`;
+  }
+  if (tooltipContentGenerator) {
     onHover = (o) => {
       if (o.picked) {
         slice.setTooltip({
-          content: dompurify.sanitize(jsTooltip(o)),
+          content: tooltipContentGenerator(o),
           x: o.x,
           y: o.y,
         });
@@ -54,6 +60,8 @@ export function commonLayerProps(formData, slice) {
       const href = sandboxedEval(fd.js_onclick_href)(o);
       window.open(href);
     };
+  } else if (fd.table_filter && fd.line_type === 'geohash') {
+    onClick = o => slice.addFilter(fd.line_column, [o.object[fd.line_column]], false);
   }
   return {
     onClick,
diff --git a/superset/assets/src/visualizations/deckgl/layers/polygon.jsx b/superset/assets/src/visualizations/deckgl/layers/polygon.jsx
index ae8a34c..c0ac6d0 100644
--- a/superset/assets/src/visualizations/deckgl/layers/polygon.jsx
+++ b/superset/assets/src/visualizations/deckgl/layers/polygon.jsx
@@ -2,19 +2,36 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 
 import { PolygonLayer } from 'deck.gl';
+import _ from 'underscore';
+import d3 from 'd3';
 
 import DeckGLContainer from './../DeckGLContainer';
 
 import * as common from './common';
+import { colorScalerFactory } from '../../../modules/colors';
 import sandboxedEval from '../../../modules/sandbox';
 
+function getPoints(features) {
+  return _.flatten(features.map(d => d.polygon), true);
+}
+
 function getLayer(formData, payload, slice) {
   const fd = formData;
   const fc = fd.fill_color_picker;
-  let data = payload.data.features.map(d => ({
-    ...d,
-    fillColor: [fc.r, fc.g, fc.b, 255 * fc.a],
-  }));
+  const sc = fd.stroke_color_picker;
+  let data = [...payload.data.features];
+  const mainMetric = payload.data.metricLabels.length ? payload.data.metricLabels[0] :  null;
+
+  let colorScaler;
+  if (mainMetric) {
+    const ext = d3.extent(data, d => d[mainMetric]);
+    const scaler = colorScalerFactory(fd.linear_color_scheme, null, null, ext, true);
+    colorScaler = (d) => {
+      const c = scaler(d[mainMetric]);
+      c[3] = (fd.opacity / 100.0) * 255;
+      return c;
+    };
+  }
 
   if (fd.js_data_mutator) {
     // Applying user defined data mutator if defined
@@ -26,19 +43,29 @@ function getLayer(formData, payload, slice) {
     id: `path-layer-${fd.slice_id}`,
     data,
     filled: fd.filled,
-    stroked: fd.stoked,
+    stroked: fd.stroked,
+    getFillColor: colorScaler || [fc.r, fc.g, fc.b, 255 * fc.a],
+    getLineColor: [sc.r, sc.g, sc.b, 255 * sc.a],
+    getLineWidth: fd.line_width,
     extruded: fd.extruded,
+    fp64: true,
     ...common.commonLayerProps(fd, slice),
   });
 }
 
 function deckPolygon(slice, payload, setControlValue) {
   const layer = getLayer(slice.formData, payload, slice);
-  const viewport = {
+  const fd = slice.formData;
+  let viewport = {
     ...slice.formData.viewport,
     width: slice.width(),
     height: slice.height(),
   };
+
+  if (fd.autozoom) {
+    viewport = common.fitViewport(viewport, getPoints(payload.data.features));
+  }
+
   ReactDOM.render(
     <DeckGLContainer
       mapboxApiAccessToken={payload.data.mapboxApiKey}
diff --git a/superset/assets/yarn.lock b/superset/assets/yarn.lock
index e0b611d..587e500 100644
--- a/superset/assets/yarn.lock
+++ b/superset/assets/yarn.lock
@@ -230,9 +230,9 @@
     d3-array "^1.2.0"
     prop-types "^15.5.10"
 
-"@deck.gl/core@^5.3.1":
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/@deck.gl/core/-/core-5.3.1.tgz#acfc1e5fefd3b12e9142419b0aeb77c07885626c"
+"@deck.gl/core@^5.3.3":
+  version "5.3.3"
+  resolved "https://registry.yarnpkg.com/@deck.gl/core/-/core-5.3.3.tgz#a13c07e5fa3e22297fd450d6da8ab9aac334b1f0"
   dependencies:
     luma.gl "^5.3.0"
     math.gl "^1.2.1"
@@ -241,19 +241,19 @@
     seer "^0.2.4"
     viewport-mercator-project "^5.1.0"
 
-"@deck.gl/layers@^5.3.2":
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/@deck.gl/layers/-/layers-5.3.2.tgz#c76b9a7890305a5d6a0fdd56bd0d0d68d4046f6f"
+"@deck.gl/layers@^5.3.4":
+  version "5.3.4"
+  resolved "https://registry.yarnpkg.com/@deck.gl/layers/-/layers-5.3.4.tgz#ab3de1bf8bb68d67772642acbb4e0f87f4f11300"
   dependencies:
-    "@deck.gl/core" "^5.3.1"
+    "@deck.gl/core" "^5.3.3"
     d3-hexbin "^0.2.1"
     earcut "^2.0.6"
 
-"@deck.gl/react@^5.3.1":
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/@deck.gl/react/-/react-5.3.1.tgz#0c16fac59061924eb3509dea06c837bcef8044f2"
+"@deck.gl/react@^5.3.3":
+  version "5.3.3"
+  resolved "https://registry.yarnpkg.com/@deck.gl/react/-/react-5.3.3.tgz#e7352934f6742d3ce672a394cbff312aab5ccaa0"
   dependencies:
-    "@deck.gl/core" "^5.3.1"
+    "@deck.gl/core" "^5.3.3"
     prop-types "^15.6.0"
 
 "@mapbox/geojson-area@0.2.2":
@@ -830,6 +830,12 @@ acorn@^5.0.0, acorn@^5.5.0, acorn@^5.6.2:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
 
+add-dom-event-listener@1.x:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.0.2.tgz#8faed2c41008721cf111da1d30d995b85be42bed"
+  dependencies:
+    object-assign "4.x"
+
 ajv-keywords@^2.1.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
@@ -1951,19 +1957,19 @@ babel-register@^6.24.1, babel-register@^6.26.0, babel-register@^6.9.0:
     mkdirp "^0.5.1"
     source-map-support "^0.4.15"
 
-babel-runtime@^5.6.18:
-  version "5.8.38"
-  resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19"
-  dependencies:
-    core-js "^1.0.0"
-
-babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0,
babel-runtime@^6.26.0:
+babel-runtime@6.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0,
babel-runtime@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
   dependencies:
     core-js "^2.4.0"
     regenerator-runtime "^0.11.0"
 
+babel-runtime@^5.6.18:
+  version "5.8.38"
+  resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19"
+  dependencies:
+    core-js "^1.0.0"
+
 babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
@@ -2578,7 +2584,7 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
-classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5:
+classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5, classnames@^2.2.6:
   version "2.2.6"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
 
@@ -2818,10 +2824,20 @@ complex.js@2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.4.tgz#d8e7cfb9652d1e853e723386421c1a0ca7a48373"
 
+component-classes@^1.2.5:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/component-classes/-/component-classes-1.2.6.tgz#c642394c3618a4d8b0b8919efccbbd930e5cd691"
+  dependencies:
+    component-indexof "0.0.3"
+
 component-emitter@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
 
+component-indexof@0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/component-indexof/-/component-indexof-0.0.3.tgz#11d091312239eb8f32c8f25ae9cb002ffe8d3c24"
+
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -3012,6 +3028,13 @@ crypto-random-string@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
 
+css-animation@^1.3.2:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/css-animation/-/css-animation-1.4.1.tgz#5b8813125de0fbbbb0bbe1b472ae84221469b7a8"
+  dependencies:
+    babel-runtime "6.x"
+    component-classes "^1.2.5"
+
 css-color-names@0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@@ -3397,13 +3420,13 @@ decimal.js@9.0.1:
   version "9.0.1"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-9.0.1.tgz#1cc8b228177da7ab6498c1cc06eb130a290e6e1e"
 
-deck.gl@^5.1.4:
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/deck.gl/-/deck.gl-5.3.2.tgz#2297d820fb8fb02eab95ac6a03261a689500bc11"
+deck.gl@^5.3.4:
+  version "5.3.4"
+  resolved "https://registry.yarnpkg.com/deck.gl/-/deck.gl-5.3.4.tgz#35e5a7087ef0d8ca7811d06a721ea289edbe7c24"
   dependencies:
-    "@deck.gl/core" "^5.3.1"
-    "@deck.gl/layers" "^5.3.2"
-    "@deck.gl/react" "^5.3.1"
+    "@deck.gl/core" "^5.3.3"
+    "@deck.gl/layers" "^5.3.4"
+    "@deck.gl/react" "^5.3.3"
 
 decode-uri-component@^0.2.0:
   version "0.2.0"
@@ -3586,6 +3609,10 @@ doctrine@^2.1.0:
   dependencies:
     esutils "^2.0.2"
 
+dom-align@^1.7.0:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.8.0.tgz#c0e89b5b674c6e836cd248c52c2992135f093654"
+
 "dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.2.0, dom-helpers@^3.2.1:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
@@ -6384,7 +6411,7 @@ lodash.isplainobject@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
 
-lodash.keys@^3.0.0:
+lodash.keys@^3.0.0, lodash.keys@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
   dependencies:
@@ -6518,7 +6545,7 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
     pseudomap "^1.0.2"
     yallist "^2.1.2"
 
-luma.gl@^5.1.4, luma.gl@^5.3.0:
+luma.gl@^5.3.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/luma.gl/-/luma.gl-5.3.0.tgz#a93b2f34489d8230eb6d8c871335800d9b83ee67"
   dependencies:
@@ -7378,14 +7405,14 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
 
+object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+
 object-assign@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
 
-object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
-
 object-copy@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
@@ -8329,6 +8356,23 @@ randomfill@^1.0.3:
     randombytes "^2.0.5"
     safe-buffer "^5.1.0"
 
+rc-align@^2.4.0:
+  version "2.4.3"
+  resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-2.4.3.tgz#b9b3c2a6d68adae71a8e1d041cd5e3b2a655f99a"
+  dependencies:
+    babel-runtime "^6.26.0"
+    dom-align "^1.7.0"
+    prop-types "^15.5.8"
+    rc-util "^4.0.4"
+
+rc-animate@2.x:
+  version "2.4.4"
+  resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.4.4.tgz#a05a784c747beef140d99ff52b6117711bef4b1e"
+  dependencies:
+    babel-runtime "6.x"
+    css-animation "^1.3.2"
+    prop-types "15.x"
+
 rc-config-loader@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/rc-config-loader/-/rc-config-loader-2.0.1.tgz#8c8452f59bdd10d448a67762dccf7c1b247db860"
@@ -8341,6 +8385,46 @@ rc-config-loader@^2.0.1:
     path-exists "^2.1.0"
     require-from-string "^2.0.1"
 
+rc-slider@^8.6.1:
+  version "8.6.1"
+  resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-8.6.1.tgz#ee5e0380dbdf4b5de6955a265b0d4ff6196405d1"
+  dependencies:
+    babel-runtime "6.x"
+    classnames "^2.2.5"
+    prop-types "^15.5.4"
+    rc-tooltip "^3.7.0"
+    rc-util "^4.0.4"
+    shallowequal "^1.0.1"
+    warning "^3.0.0"
+
+rc-tooltip@^3.7.0:
+  version "3.7.2"
+  resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-3.7.2.tgz#3698656d4bacd51b72d9e327bed15d1d5a9f1b27"
+  dependencies:
+    babel-runtime "6.x"
+    prop-types "^15.5.8"
+    rc-trigger "^2.2.2"
+
+rc-trigger@^2.2.2:
+  version "2.5.4"
+  resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.5.4.tgz#9088a24ba5a811b254f742f004e38a9e2f8843fb"
+  dependencies:
+    babel-runtime "6.x"
+    classnames "^2.2.6"
+    prop-types "15.x"
+    rc-align "^2.4.0"
+    rc-animate "2.x"
+    rc-util "^4.4.0"
+
+rc-util@^4.0.4, rc-util@^4.4.0:
+  version "4.5.1"
+  resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.5.1.tgz#0e435057174c024901c7600ba8903dd03da3ab39"
+  dependencies:
+    add-dom-event-listener "1.x"
+    babel-runtime "6.x"
+    prop-types "^15.5.10"
+    shallowequal "^0.2.2"
+
 rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@@ -9469,6 +9553,16 @@ shallow-copy@~0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170"
 
+shallowequal@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e"
+  dependencies:
+    lodash.keys "^3.1.2"
+
+shallowequal@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
+
 shapefile@0.3:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/shapefile/-/shapefile-0.3.1.tgz#9bb9a429bd6086a0cfb03962d14cfdf420ffba12"
diff --git a/superset/views/core.py b/superset/views/core.py
index 2cb7bf4..97a7da9 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -11,7 +11,6 @@ import os
 import re
 import time
 import traceback
-from urllib import parse
 
 from flask import (
     flash, g, Markup, redirect, render_template, request, Response, url_for,
@@ -25,6 +24,7 @@ from flask_babel import lazy_gettext as _
 import pandas as pd
 import simplejson as json
 from six import text_type
+from six.moves.urllib import parse
 import sqlalchemy as sqla
 from sqlalchemy import and_, create_engine, or_, update
 from sqlalchemy.engine.url import make_url
diff --git a/superset/viz.py b/superset/viz.py
index 5f4cea8..601411c 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -10,7 +10,7 @@ from __future__ import division
 from __future__ import print_function
 from __future__ import unicode_literals
 
-from collections import defaultdict
+from collections import defaultdict, OrderedDict
 import copy
 from datetime import datetime, timedelta
 import hashlib
@@ -100,7 +100,7 @@ class BaseViz(object):
         self.process_metrics()
 
     def process_metrics(self):
-        self.metric_dict = {}
+        self.metric_dict = OrderedDict()
         fd = self.form_data
         for mkey in METRIC_KEYS:
             val = fd.get(mkey)
@@ -2208,6 +2208,7 @@ class BaseDeckGLViz(BaseViz):
         return {
             'features': features,
             'mapboxApiKey': config.get('MAPBOX_API_KEY'),
+            'metricLabels': self.metric_labels,
         }
 
     def get_properties(self, d):
@@ -2304,6 +2305,17 @@ class DeckGrid(BaseDeckGLViz):
         return super(DeckGrid, self).get_data(df)
 
 
+def geohash_to_json(geohash_code):
+    p = geohash.bbox(geohash_code)
+    return [
+        [p.get('w'), p.get('n')],
+        [p.get('e'), p.get('n')],
+        [p.get('e'), p.get('s')],
+        [p.get('w'), p.get('s')],
+        [p.get('w'), p.get('n')],
+    ]
+
+
 class DeckPathViz(BaseDeckGLViz):
 
     """deck.gl's PathLayer"""
@@ -2314,26 +2326,32 @@ class DeckPathViz(BaseDeckGLViz):
     deser_map = {
         'json': json.loads,
         'polyline': polyline.decode,
+        'geohash': geohash_to_json,
     }
 
     def query_obj(self):
         d = super(DeckPathViz, self).query_obj()
         line_col = self.form_data.get('line_column')
         if d['metrics']:
+            self.has_metrics = True
             d['groupby'].append(line_col)
         else:
+            self.has_metrics = False
             d['columns'].append(line_col)
         return d
 
     def get_properties(self, d):
         fd = self.form_data
-        deser = self.deser_map[fd.get('line_type')]
-        path = deser(d[fd.get('line_column')])
+        line_type = fd.get('line_type')
+        deser = self.deser_map[line_type]
+        line_column = fd.get('line_column')
+        path = deser(d[line_column])
         if fd.get('reverse_long_lat'):
-            path = (path[1], path[0])
-        return {
-            self.deck_viz_key: path,
-        }
+            path = [(o[1], o[0]) for o in path]
+        d[self.deck_viz_key] = path
+        if line_type != 'geohash':
+            del d[line_column]
+        return d
 
 
 class DeckPolygon(DeckPathViz):


Mime
View raw message