mirror of https://github.com/apache/superset.git
Add play slider to screengrid (#4647)
* Improved granularity parsing * Add unit tests * Explicit cast to int * Screengrid play slider * Clean code * Refactor common code
This commit is contained in:
parent
41c158edac
commit
47c085fd00
|
@ -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'],
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
@ -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 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) {
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue