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.