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 e0bbb0c  [geo] introduce "Auto Zoom" control (#4389)
e0bbb0c is described below

commit e0bbb0c77e37356af3286d99775cb4da0398b936
Author: Maxime Beauchemin <maximebeauche...@gmail.com>
AuthorDate: Tue Feb 13 11:10:15 2018 -0800

    [geo] introduce "Auto Zoom" control (#4389)
    
    * [geo] introduce "Auto Zoom" control
    
    On geospatial visualization, checking the "Auto Zoom" control makes it
    such that the viewport is fitted to the data upon rendering the chart.
    
    For dashboards with region filters, the map should jump to the right
    position.
    
    Eventually we should enhance this to fly and ease to the position in an
    animated way.
    
    * Added TODO notes
---
 superset/assets/javascripts/chart/Chart.jsx        |  1 +
 .../explore/components/controls/TextControl.jsx    |  3 +++
 .../assets/javascripts/explore/stores/controls.jsx |  8 +++++++
 .../assets/javascripts/explore/stores/visTypes.js  | 11 ++++++---
 superset/assets/package.json                       |  4 +++-
 .../visualizations/deckgl/DeckGLContainer.jsx      |  1 +
 .../assets/visualizations/deckgl/layers/arc.jsx    | 15 +++++++++++-
 .../assets/visualizations/deckgl/layers/common.js  | 24 +++++++++++++++++++
 .../visualizations/deckgl/layers/geojson.jsx       |  8 +++++--
 .../assets/visualizations/deckgl/layers/grid.jsx   | 11 ++++++++-
 .../assets/visualizations/deckgl/layers/hex.jsx    | 11 ++++++++-
 .../assets/visualizations/deckgl/layers/path.jsx   | 15 +++++++++++-
 .../visualizations/deckgl/layers/scatter.jsx       | 24 +++++++++++++------
 .../visualizations/deckgl/layers/screengrid.jsx    |  9 ++++++-
 superset/data/__init__.py                          | 28 +++++++++++-----------
 superset/viz.py                                    |  2 --
 16 files changed, 141 insertions(+), 34 deletions(-)

diff --git a/superset/assets/javascripts/chart/Chart.jsx 
b/superset/assets/javascripts/chart/Chart.jsx
index 7bb71ac..cc18bb4 100644
--- a/superset/assets/javascripts/chart/Chart.jsx
+++ b/superset/assets/javascripts/chart/Chart.jsx
@@ -188,6 +188,7 @@ class Chart extends React.PureComponent {
       });
       this.props.actions.chartRenderingSucceeded(this.props.chartKey);
     } catch (e) {
+      console.error(e);  // eslint-disable-line
       this.props.actions.chartRenderingFailed(e, this.props.chartKey);
     }
   }
diff --git 
a/superset/assets/javascripts/explore/components/controls/TextControl.jsx 
b/superset/assets/javascripts/explore/components/controls/TextControl.jsx
index bfe3f99..ed1238e 100644
--- a/superset/assets/javascripts/explore/components/controls/TextControl.jsx
+++ b/superset/assets/javascripts/explore/components/controls/TextControl.jsx
@@ -13,6 +13,7 @@ const propTypes = {
   ]),
   isFloat: PropTypes.bool,
   isInt: PropTypes.bool,
+  disabled: PropTypes.bool,
 };
 
 const defaultProps = {
@@ -21,6 +22,7 @@ const defaultProps = {
   value: '',
   isInt: false,
   isFloat: false,
+  disabled: false,
 };
 
 export default class TextControl extends React.Component {
@@ -63,6 +65,7 @@ export default class TextControl extends React.Component {
             onChange={this.onChange}
             onFocus={this.props.onFocus}
             value={value}
+            disabled={this.props.disabled}
           />
         </FormGroup>
       </div>
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx 
b/superset/assets/javascripts/explore/stores/controls.jsx
index 8e3420b..e8a34d9 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -331,6 +331,14 @@ export const controls = {
     default: false,
   },
 
+  autozoom: {
+    type: 'CheckboxControl',
+    label: t('Auto Zoom'),
+    default: true,
+    renderTrigger: true,
+    description: t('When checked, the map will zoom to your data after each 
query'),
+  },
+
   show_perc: {
     type: 'CheckboxControl',
     label: t('Show percentage'),
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js 
b/superset/assets/javascripts/explore/stores/visTypes.js
index 9d62ead..3135b26 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -369,7 +369,7 @@ export const visTypes = {
         label: t('Map'),
         controlSetRows: [
           ['mapbox_style', 'viewport'],
-          ['color_picker', null],
+          ['color_picker', 'autozoom'],
           ['grid_size', 'extruded'],
         ],
       },
@@ -407,7 +407,7 @@ export const visTypes = {
         label: t('Map'),
         controlSetRows: [
           ['mapbox_style', 'viewport'],
-          ['color_picker', null],
+          ['color_picker', 'autozoom'],
           ['grid_size', 'extruded'],
         ],
       },
@@ -448,7 +448,7 @@ export const visTypes = {
         controlSetRows: [
           ['mapbox_style', 'viewport'],
           ['color_picker', 'line_width'],
-          ['reverse_long_lat', null],
+          ['reverse_long_lat', 'autozoom'],
         ],
       },
       {
@@ -479,6 +479,7 @@ export const visTypes = {
         label: t('Map'),
         controlSetRows: [
           ['mapbox_style', 'viewport'],
+          ['autozoom', null],
         ],
       },
       {
@@ -521,6 +522,7 @@ export const visTypes = {
         label: t('Map'),
         controlSetRows: [
           ['mapbox_style', 'viewport'],
+          // TODO ['autozoom', null],
         ],
       },
       {
@@ -600,6 +602,7 @@ export const visTypes = {
         label: t('Map'),
         controlSetRows: [
           ['mapbox_style', 'viewport'],
+          ['autozoom', null],
         ],
       },
       {
@@ -635,8 +638,10 @@ export const visTypes = {
       },
       {
         label: t('Map'),
+        expanded: true,
         controlSetRows: [
           ['mapbox_style', 'viewport'],
+          ['autozoom', null],
         ],
       },
       {
diff --git a/superset/assets/package.json b/superset/assets/package.json
index abc978c..fbfa32a 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -60,6 +60,7 @@
     "distributions": "^1.0.0",
     "dompurify": "^1.0.3",
     "fastdom": "^1.0.6",
+    "geojson-extent": "^0.3.2",
     "geolib": "^2.0.24",
     "immutable": "^3.8.2",
     "jed": "^1.1.1",
@@ -105,7 +106,7 @@
     "supercluster": 
"https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40";,
     "underscore": "^1.8.3",
     "urijs": "^1.18.10",
-    "viewport-mercator-project": "^2.1.0"
+    "viewport-mercator-project": "^5.0.0"
   },
   "devDependencies": {
     "babel-cli": "^6.14.0",
@@ -137,6 +138,7 @@
     "less": "^2.6.1",
     "less-loader": "^4.0.3",
     "mocha": "^3.2.0",
+    "npm-check-updates": "^2.14.0",
     "react-addons-test-utils": "^15.6.2",
     "react-test-renderer": "^15.6.2",
     "redux-mock-store": "^1.2.3",
diff --git a/superset/assets/visualizations/deckgl/DeckGLContainer.jsx 
b/superset/assets/visualizations/deckgl/DeckGLContainer.jsx
index 3166917..1b7ca31 100644
--- a/superset/assets/visualizations/deckgl/DeckGLContainer.jsx
+++ b/superset/assets/visualizations/deckgl/DeckGLContainer.jsx
@@ -33,6 +33,7 @@ export default class DeckGLContainer extends React.Component {
   componentWillReceiveProps(nextProps) {
     this.setState(() => ({
       viewport: { ...nextProps.viewport },
+      previousViewport: this.state.viewport,
     }));
   }
   componentWillUnmount() {
diff --git a/superset/assets/visualizations/deckgl/layers/arc.jsx 
b/superset/assets/visualizations/deckgl/layers/arc.jsx
index ebeff3c..43583e0 100644
--- a/superset/assets/visualizations/deckgl/layers/arc.jsx
+++ b/superset/assets/visualizations/deckgl/layers/arc.jsx
@@ -8,6 +8,15 @@ import DeckGLContainer from './../DeckGLContainer';
 import * as common from './common';
 import sandboxedEval from '../../../javascripts/modules/sandbox';
 
+function getPoints(data) {
+  const points = [];
+  data.forEach((d) => {
+    points.push(d.sourcePosition);
+    points.push(d.targetPosition);
+  });
+  return points;
+}
+
 function getLayer(formData, payload, slice) {
   const fd = formData;
   const fc = fd.color_picker;
@@ -32,11 +41,15 @@ function getLayer(formData, payload, slice) {
 
 function deckArc(slice, payload, setControlValue) {
   const layer = getLayer(slice.formData, payload, slice);
-  const viewport = {
+  let viewport = {
     ...slice.formData.viewport,
     width: slice.width(),
     height: slice.height(),
   };
+
+  if (slice.formData.autozoom) {
+    viewport = common.fitViewport(viewport, getPoints(payload.data.arcs));
+  }
   ReactDOM.render(
     <DeckGLContainer
       mapboxApiAccessToken={payload.data.mapboxApiKey}
diff --git a/superset/assets/visualizations/deckgl/layers/common.js 
b/superset/assets/visualizations/deckgl/layers/common.js
index 7f11213..4692401 100644
--- a/superset/assets/visualizations/deckgl/layers/common.js
+++ b/superset/assets/visualizations/deckgl/layers/common.js
@@ -1,6 +1,30 @@
 import dompurify from 'dompurify';
+import { fitBounds } from 'viewport-mercator-project';
+
 import sandboxedEval from '../../../javascripts/modules/sandbox';
 
+export function getBounds(points) {
+  const latExt = d3.extent(points, d => d[1]);
+  const lngExt = d3.extent(points, d => d[0]);
+  return [
+    [lngExt[0], latExt[0]],
+    [lngExt[1], latExt[1]],
+  ];
+}
+
+export function fitViewport(viewport, points, padding = 10) {
+  const bounds = getBounds(points);
+  return {
+    ...viewport,
+    ...fitBounds({
+      height: viewport.height,
+      width: viewport.width,
+      padding,
+      bounds,
+    }),
+  };
+}
+
 export function commonLayerProps(formData, slice) {
   const fd = formData;
   let onHover;
diff --git a/superset/assets/visualizations/deckgl/layers/geojson.jsx 
b/superset/assets/visualizations/deckgl/layers/geojson.jsx
index 7f09363..fe7805a 100644
--- a/superset/assets/visualizations/deckgl/layers/geojson.jsx
+++ b/superset/assets/visualizations/deckgl/layers/geojson.jsx
@@ -1,10 +1,9 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
-
 import { GeoJsonLayer } from 'deck.gl';
+// TODO import geojsonExtent from 'geojson-extent';
 
 import DeckGLContainer from './../DeckGLContainer';
-
 import * as common from './common';
 import { hexToRGB } from '../../../javascripts/modules/colors';
 import sandboxedEval from '../../../javascripts/modules/sandbox';
@@ -100,6 +99,11 @@ function deckGeoJson(slice, payload, setControlValue) {
     width: slice.width(),
     height: slice.height(),
   };
+  if (slice.formData.autozoom) {
+    // TODO get this to work
+    // viewport = common.fitViewport(viewport, 
geojsonExtent(payload.data.features));
+  }
+
   ReactDOM.render(
     <DeckGLContainer
       mapboxApiAccessToken={payload.data.mapboxApiKey}
diff --git a/superset/assets/visualizations/deckgl/layers/grid.jsx 
b/superset/assets/visualizations/deckgl/layers/grid.jsx
index 1e7ff1d..6f92f06 100644
--- a/superset/assets/visualizations/deckgl/layers/grid.jsx
+++ b/superset/assets/visualizations/deckgl/layers/grid.jsx
@@ -37,13 +37,22 @@ function getLayer(formData, payload, slice) {
   });
 }
 
+function getPoints(data) {
+  return data.map(d => d.position);
+}
+
 function deckGrid(slice, payload, setControlValue) {
   const layer = getLayer(slice.formData, payload, slice);
-  const viewport = {
+  let viewport = {
     ...slice.formData.viewport,
     width: slice.width(),
     height: slice.height(),
   };
+
+  if (slice.formData.autozoom) {
+    viewport = common.fitViewport(viewport, getPoints(payload.data.features));
+  }
+
   ReactDOM.render(
     <DeckGLContainer
       mapboxApiAccessToken={payload.data.mapboxApiKey}
diff --git a/superset/assets/visualizations/deckgl/layers/hex.jsx 
b/superset/assets/visualizations/deckgl/layers/hex.jsx
index 7dc4d8b..f0fb925 100644
--- a/superset/assets/visualizations/deckgl/layers/hex.jsx
+++ b/superset/assets/visualizations/deckgl/layers/hex.jsx
@@ -37,13 +37,22 @@ function getLayer(formData, payload, slice) {
   });
 }
 
+function getPoints(data) {
+  return data.map(d => d.position);
+}
+
 function deckHex(slice, payload, setControlValue) {
   const layer = getLayer(slice.formData, payload, slice);
-  const viewport = {
+  let viewport = {
     ...slice.formData.viewport,
     width: slice.width(),
     height: slice.height(),
   };
+
+  if (slice.formData.autozoom) {
+    viewport = common.fitViewport(viewport, getPoints(payload.data.features));
+  }
+
   ReactDOM.render(
     <DeckGLContainer
       mapboxApiAccessToken={payload.data.mapboxApiKey}
diff --git a/superset/assets/visualizations/deckgl/layers/path.jsx 
b/superset/assets/visualizations/deckgl/layers/path.jsx
index a20c2bb..4b45a0b 100644
--- a/superset/assets/visualizations/deckgl/layers/path.jsx
+++ b/superset/assets/visualizations/deckgl/layers/path.jsx
@@ -33,13 +33,26 @@ function getLayer(formData, payload, slice) {
   });
 }
 
+function getPoints(data) {
+  let points = [];
+  data.forEach((d) => {
+    points = points.concat(d.path);
+  });
+  return points;
+}
+
 function deckPath(slice, payload, setControlValue) {
   const layer = getLayer(slice.formData, payload, slice);
-  const viewport = {
+  let viewport = {
     ...slice.formData.viewport,
     width: slice.width(),
     height: slice.height(),
   };
+
+  if (slice.formData.autozoom) {
+    viewport = common.fitViewport(viewport, getPoints(payload.data.features));
+  }
+
   ReactDOM.render(
     <DeckGLContainer
       mapboxApiAccessToken={payload.data.mapboxApiKey}
diff --git a/superset/assets/visualizations/deckgl/layers/scatter.jsx 
b/superset/assets/visualizations/deckgl/layers/scatter.jsx
index ed1dd79..646a9af 100644
--- a/superset/assets/visualizations/deckgl/layers/scatter.jsx
+++ b/superset/assets/visualizations/deckgl/layers/scatter.jsx
@@ -1,15 +1,17 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
-
 import { ScatterplotLayer } from 'deck.gl';
 
 import DeckGLContainer from './../DeckGLContainer';
-
 import * as common from './common';
 import { getColorFromScheme, hexToRGB } from 
'../../../javascripts/modules/colors';
 import { unitToRadius } from '../../../javascripts/modules/geo';
 import sandboxedEval from '../../../javascripts/modules/sandbox';
 
+function getPoints(data) {
+  return data.map(d => d.position);
+}
+
 function getLayer(formData, payload, slice) {
   const fd = formData;
   const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
@@ -50,17 +52,25 @@ function getLayer(formData, payload, slice) {
 
 function deckScatter(slice, payload, setControlValue) {
   const layer = getLayer(slice.formData, payload, slice);
-  const viewport = {
-    ...slice.formData.viewport,
-    width: slice.width(),
-    height: slice.height(),
+  const fd = slice.formData;
+  const width = slice.width();
+  const height = slice.height();
+  let viewport = {
+    ...fd.viewport,
+    width,
+    height,
   };
+
+  if (fd.autozoom) {
+    viewport = common.fitViewport(viewport, getPoints(payload.data.features));
+  }
+
   ReactDOM.render(
     <DeckGLContainer
       mapboxApiAccessToken={payload.data.mapboxApiKey}
       viewport={viewport}
       layers={[layer]}
-      mapStyle={slice.formData.mapbox_style}
+      mapStyle={fd.mapbox_style}
       setControlValue={setControlValue}
     />,
     document.getElementById(slice.containerId),
diff --git a/superset/assets/visualizations/deckgl/layers/screengrid.jsx 
b/superset/assets/visualizations/deckgl/layers/screengrid.jsx
index 7494c67..7d6742e 100644
--- a/superset/assets/visualizations/deckgl/layers/screengrid.jsx
+++ b/superset/assets/visualizations/deckgl/layers/screengrid.jsx
@@ -37,13 +37,20 @@ function getLayer(formData, payload, slice) {
   });
 }
 
+function getPoints(data) {
+  return data.map(d => d.position);
+}
+
 function deckScreenGrid(slice, payload, setControlValue) {
   const layer = getLayer(slice.formData, payload, slice);
-  const viewport = {
+  let viewport = {
     ...slice.formData.viewport,
     width: slice.width(),
     height: slice.height(),
   };
+  if (slice.formData.autozoom) {
+    viewport = common.fitViewport(viewport, getPoints(payload.data.features));
+  }
   ReactDOM.render(
     <DeckGLContainer
       mapboxApiAccessToken={payload.data.mapboxApiKey}
diff --git a/superset/data/__init__.py b/superset/data/__init__.py
index e1d0b67..be89c1a 100644
--- a/superset/data/__init__.py
+++ b/superset/data/__init__.py
@@ -1266,10 +1266,10 @@ def load_deck_dash():
         "point_radius_fixed": {"type": "metric", "value": "count"},
         "point_unit": "square_m",
         "row_limit": 5000,
-        "since": "2014-01-01",
+        "since": None,
         "size": "count",
         "time_grain_sqla": "Time Column",
-        "until": "now",
+        "until": None,
         "viewport": {
             "bearing": -4.952916738791771,
             "latitude": 37.78926922909199,
@@ -1305,9 +1305,9 @@ def load_deck_dash():
         "granularity_sqla": "date",
         "size": "count",
         "viz_type": "deck_screengrid",
-        "since": "2014-01-01",
+        "since": None,
         "point_radius": "Auto",
-        "until": "now",
+        "until": None,
         "color_picker": {
             "a": 1,
             "r": 14,
@@ -1352,10 +1352,10 @@ def load_deck_dash():
         "granularity_sqla": "date",
         "size": "count",
         "viz_type": "deck_hex",
-        "since": "2014-01-01",
+        "since": None,
         "point_radius_unit": "Pixels",
         "point_radius": "Auto",
-        "until": "now",
+        "until": None,
         "color_picker": {
             "a": 1,
             "r": 14,
@@ -1401,10 +1401,10 @@ def load_deck_dash():
         "granularity_sqla": "date",
         "size": "count",
         "viz_type": "deck_grid",
-        "since": "2014-01-01",
+        "since": None,
         "point_radius_unit": "Pixels",
         "point_radius": "Auto",
-        "until": "now",
+        "until": None,
         "color_picker": {
             "a": 1,
             "r": 14,
@@ -1446,8 +1446,8 @@ def load_deck_dash():
             "slice_id": 41,
             "granularity_sqla": None,
             "time_grain_sqla": None,
-            "since": "7 days ago",
-            "until": "now",
+            "since": None,
+            "until": None,
             "line_column": "contour",
             "line_type": "json",
             "mapbox_style": "mapbox://styles/mapbox/light-v9",
@@ -1515,8 +1515,8 @@ def load_deck_dash():
             "slice_id": 42,
             "granularity_sqla": "date",
             "time_grain_sqla": "Time Column",
-            "since": "2014-01-01",
-            "until": "now",
+            "since": None,
+            "until": None,
             "start_spatial": {
                 "type": "latlong",
                 "latCol": "LATITUDE",
@@ -1573,8 +1573,8 @@ def load_deck_dash():
         "slice_id": 43,
         "viz_type": "deck_path",
         "time_grain_sqla": "Time Column",
-        "since": "7 days ago",
-        "until": "now",
+        "since": None,
+        "until": None,
         "line_column": "path_json",
         "line_type": "json",
         "row_limit": 5000,
diff --git a/superset/viz.py b/superset/viz.py
index 5c36e40..c09b3cf 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -291,7 +291,6 @@ class BaseViz(object):
         if df is not None and len(df.index) == 0:
             raise Exception('No data')
         payload['data'] = self.get_data(df)
-
         del payload['df']
         return payload
 
@@ -1932,7 +1931,6 @@ class BaseDeckGLViz(BaseViz):
         spatial = self.form_data.get(key)
         if spatial is None:
             raise ValueError(_('Bad spatial key'))
-
         if spatial.get('type') == 'latlong':
             df[key] = list(zip(df[spatial.get('lonCol')], 
df[spatial.get('latCol')]))
         elif spatial.get('type') == 'delimited':

-- 
To stop receiving notification emails like this one, please contact
maximebeauche...@apache.org.

Reply via email to