You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ma...@apache.org on 2018/04/09 21:02:23 UTC

[incubator-superset] branch master updated: Add play slider to screengrid (#4647)

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 47c085f  Add play slider to screengrid (#4647)
47c085f is described below

commit 47c085fd00ae855934728089d0497d86c1651035
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Mon Apr 9 14:02:20 2018 -0700

    Add play slider to screengrid (#4647)
    
    * Improved granularity parsing
    
    * Add unit tests
    
    * Explicit cast to int
    
    * Screengrid play slider
    
    * Clean code
    
    * Refactor common code
---
 .../assets/javascripts/explore/stores/controls.jsx |   2 +-
 superset/assets/javascripts/modules/time.js        |  18 ++++
 .../visualizations/deckgl/layers/scatter.jsx       |  23 +----
 .../visualizations/deckgl/layers/screengrid.jsx    | 102 ++++++++++++++++++---
 superset/viz.py                                    |   7 ++
 5 files changed, 121 insertions(+), 31 deletions(-)

diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx
index 5bd825a..b675b03 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -232,7 +232,7 @@ export const controls = {
     description: t('Choose the position of the legend'),
     type: 'SelectControl',
     clearable: false,
-    default: 'Top right',
+    default: 'tr',
     choices: [
       ['tl', 'Top left'],
       ['tr', 'Top right'],
diff --git a/superset/assets/javascripts/modules/time.js b/superset/assets/javascripts/modules/time.js
new file mode 100644
index 0000000..0c13dae
--- /dev/null
+++ b/superset/assets/javascripts/modules/time.js
@@ -0,0 +1,18 @@
+import parseIsoDuration from 'parse-iso-duration';
+
+
+export const getPlaySliderParams = function (timestamps, timeGrain) {
+  let start = Math.min(...timestamps);
+  let end = Math.max(...timestamps);
+
+  // lock start and end to the closest steps
+  const step = parseIsoDuration(timeGrain);
+  start -= start % step;
+  end += step - end % step;
+
+  const values = timeGrain != null ? [start, start + step] : [start, end];
+  const disabled = timestamps.every(timestamp => timestamp === null);
+
+  return { start, end, step, values, disabled };
+};
+
diff --git a/superset/assets/visualizations/deckgl/layers/scatter.jsx b/superset/assets/visualizations/deckgl/layers/scatter.jsx
index 052a7ab..112f270 100644
--- a/superset/assets/visualizations/deckgl/layers/scatter.jsx
+++ b/superset/assets/visualizations/deckgl/layers/scatter.jsx
@@ -4,7 +4,6 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import PropTypes from 'prop-types';
 
-import parseIsoDuration from 'parse-iso-duration';
 import { ScatterplotLayer } from 'deck.gl';
 
 import AnimatableDeckGLContainer from '../AnimatableDeckGLContainer';
@@ -12,6 +11,7 @@ import Legend from '../../Legend';
 
 import * as common from './common';
 import { getColorFromScheme, hexToRGB } from '../../../javascripts/modules/colors';
+import { getPlaySliderParams } from '../../../javascripts/modules/time';
 import { unitToRadius } from '../../../javascripts/modules/geo';
 import sandboxedEval from '../../../javascripts/modules/sandbox';
 
@@ -97,20 +97,10 @@ class DeckGLScatter extends React.PureComponent {
   /* eslint-disable no-unused-vars */
   static getDerivedStateFromProps(nextProps, prevState) {
     const fd = nextProps.slice.formData;
-    const timeGrain = fd.time_grain_sqla || fd.granularity || 'PT1M';
 
-    // find start and end based on the data
+    const timeGrain = fd.time_grain_sqla || fd.granularity || 'PT1M';
     const timestamps = nextProps.payload.data.features.map(f => f.__timestamp);
-    let start = Math.min(...timestamps);
-    let end = Math.max(...timestamps);
-
-    // lock start and end to the closest steps
-    const step = parseIsoDuration(timeGrain);
-    start -= start % step;
-    end += step - end % step;
-
-    const values = timeGrain != null ? [start, start + step] : [start, end];
-    const disabled = timestamps.every(timestamp => timestamp === null);
+    const { start, end, step, values, disabled } = getPlaySliderParams(timestamps, timeGrain);
 
     const categories = getCategories(fd, nextProps.payload);
 
@@ -200,14 +190,11 @@ class DeckGLScatter extends React.PureComponent {
 DeckGLScatter.propTypes = propTypes;
 
 function deckScatter(slice, payload, setControlValue) {
-  const layer = getLayer(slice.formData, payload, slice);
   const fd = slice.formData;
-  const width = slice.width();
-  const height = slice.height();
   let viewport = {
     ...fd.viewport,
-    width,
-    height,
+    width: slice.width(),
+    height: slice.height(),
   };
 
   if (fd.autozoom) {
diff --git a/superset/assets/visualizations/deckgl/layers/screengrid.jsx b/superset/assets/visualizations/deckgl/layers/screengrid.jsx
index 7d6742e..df11b5c 100644
--- a/superset/assets/visualizations/deckgl/layers/screengrid.jsx
+++ b/superset/assets/visualizations/deckgl/layers/screengrid.jsx
@@ -1,14 +1,22 @@
+/* eslint no-underscore-dangle: ["error", { "allow": ["", "__timestamp"] }] */
+
 import React from 'react';
 import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
 
 import { ScreenGridLayer } from 'deck.gl';
 
-import DeckGLContainer from './../DeckGLContainer';
+import AnimatableDeckGLContainer from '../AnimatableDeckGLContainer';
 
 import * as common from './common';
+import { getPlaySliderParams } from '../../../javascripts/modules/time';
 import sandboxedEval from '../../../javascripts/modules/sandbox';
 
-function getLayer(formData, payload, slice) {
+function getPoints(data) {
+  return data.map(d => d.position);
+}
+
+function getLayer(formData, payload, slice, filters) {
   const fd = formData;
   const c = fd.color_picker;
   let data = payload.data.features.map(d => ({
@@ -22,6 +30,12 @@ function getLayer(formData, payload, slice) {
     data = jsFnMutator(data);
   }
 
+  if (filters != null) {
+    filters.forEach((f) => {
+      data = data.filter(f);
+    });
+  }
+
   // Passing a layer creator function instead of a layer since the
   // layer needs to be regenerated at each render
   return new ScreenGridLayer({
@@ -37,27 +51,91 @@ function getLayer(formData, payload, slice) {
   });
 }
 
-function getPoints(data) {
-  return data.map(d => d.position);
+const propTypes = {
+  slice: PropTypes.object.isRequired,
+  payload: PropTypes.object.isRequired,
+  setControlValue: PropTypes.func.isRequired,
+  viewport: PropTypes.object.isRequired,
+};
+
+class DeckGLScreenGrid extends React.PureComponent {
+  /* eslint-disable no-unused-vars */
+  static getDerivedStateFromProps(nextProps, prevState) {
+    const fd = nextProps.slice.formData;
+
+    const timeGrain = fd.time_grain_sqla || fd.granularity || 'PT1M';
+    const timestamps = nextProps.payload.data.features.map(f => f.__timestamp);
+    const { start, end, step, values, disabled } = getPlaySliderParams(timestamps, timeGrain);
+
+    return { start, end, step, values, disabled };
+  }
+  constructor(props) {
+    super(props);
+    this.state = DeckGLScreenGrid.getDerivedStateFromProps(props);
+
+    this.getLayers = this.getLayers.bind(this);
+  }
+  componentWillReceiveProps(nextProps) {
+    this.setState(DeckGLScreenGrid.getDerivedStateFromProps(nextProps, this.state));
+  }
+  getLayers(values) {
+    const filters = [];
+
+    // time filter
+    if (values[0] === values[1] || values[1] === this.end) {
+      filters.push(d => d.__timestamp >= values[0] && d.__timestamp <= values[1]);
+    } else {
+      filters.push(d => d.__timestamp >= values[0] && d.__timestamp < values[1]);
+    }
+
+    const layer = getLayer(
+      this.props.slice.formData,
+      this.props.payload,
+      this.props.slice,
+      filters);
+
+    return [layer];
+  }
+  render() {
+    return (
+      <div>
+        <AnimatableDeckGLContainer
+          getLayers={this.getLayers}
+          start={this.state.start}
+          end={this.state.end}
+          step={this.state.step}
+          values={this.state.values}
+          disabled={this.state.disabled}
+          viewport={this.props.viewport}
+          mapboxApiAccessToken={this.props.payload.data.mapboxApiKey}
+          mapStyle={this.props.slice.formData.mapbox_style}
+          setControlValue={this.props.setControlValue}
+        />
+      </div>
+    );
+  }
 }
 
+DeckGLScreenGrid.propTypes = propTypes;
+
 function deckScreenGrid(slice, payload, setControlValue) {
-  const layer = getLayer(slice.formData, payload, slice);
+  const fd = slice.formData;
   let viewport = {
-    ...slice.formData.viewport,
+    ...fd.viewport,
     width: slice.width(),
     height: slice.height(),
   };
-  if (slice.formData.autozoom) {
+
+  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}
+    <DeckGLScreenGrid
+      slice={slice}
+      payload={payload}
       setControlValue={setControlValue}
+      viewport={viewport}
     />,
     document.getElementById(slice.containerId),
   );
diff --git a/superset/viz.py b/superset/viz.py
index 42e8e5a..87dd1a9 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -2103,11 +2103,18 @@ class DeckScreengrid(BaseDeckGLViz):
     viz_type = 'deck_screengrid'
     verbose_name = _('Deck.gl - Screen Grid')
     spatial_control_keys = ['spatial']
+    is_timeseries = True
+
+    def query_obj(self):
+        fd = self.form_data
+        self.is_timeseries = fd.get('time_grain_sqla') or fd.get('granularity')
+        return super(DeckScreengrid, self).query_obj()
 
     def get_properties(self, d):
         return {
             'position': d.get('spatial'),
             'weight': d.get(self.metric) or 1,
+            '__timestamp': d.get(DTTM_ALIAS) or d.get('__time'),
         }
 
 

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