You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@superset.apache.org by GitBox <gi...@apache.org> on 2018/04/09 21:02:22 UTC

[GitHub] mistercrunch closed pull request #4647: Add play slider to screengrid

mistercrunch closed pull request #4647: Add play slider to screengrid
URL: https://github.com/apache/incubator-superset/pull/4647
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx
index 5bd825a633..b675b03e50 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 0000000000..0c13dae859
--- /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 052a7ab7c1..112f270fb0 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 7d6742e6e8..df11b5c5ee 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 42e8e5a97a..87dd1a9ebe 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'),
         }
 
 


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services