Skip to content

Commit

Permalink
Geoviz state management fix (#6260)
Browse files Browse the repository at this point in the history
* Fix deckgl getPoints

* Fix CSS

* Fix zoom

* Fix CategoricalDeckGLContainer

* Fix cypress
  • Loading branch information
betodealmeida authored Nov 8, 2018
1 parent 0584e36 commit a57603a
Show file tree
Hide file tree
Showing 13 changed files with 2,090 additions and 66 deletions.
1 change: 1 addition & 0 deletions .flaskenv
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
FLASK_APP=superset:app
FLASK_ENV=development
2 changes: 1 addition & 1 deletion superset/assets/cypress_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ superset/bin/superset db upgrade
superset/bin/superset load_test_users
superset/bin/superset load_examples
superset/bin/superset init
superset/bin/superset runserver &
flask run -p 8081 --with-threads --reload --debugger &

cd "$(dirname "$0")"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('getBreakPoints', () => {

it('returns sorted break points', () => {
const fd = { break_points: ['0', '10', '100', '50', '1000'] };
const result = getBreakPoints(fd);
const result = getBreakPoints(fd, []);
const expected = ['0', '10', '50', '100', '1000'];
expect(result).toEqual(expected);
});
Expand Down
13 changes: 11 additions & 2 deletions superset/assets/src/visualizations/PlaySlider.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
.play-slider {
position: relative;
height: 20px;
display: flex;
height: 40px;
width: 100%;
margin: 0;
}

.play-slider-controls {
flex: 0 0 80px;
text-align: middle;
}

.play-slider-scrobbler {
flex: 1;
}

.slider.slider-horizontal {
width: 100% !important;
}
Expand Down
13 changes: 6 additions & 7 deletions superset/assets/src/visualizations/PlaySlider.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import Mousetrap from 'mousetrap';
import { Row, Col } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import BootrapSliderWrapper from '../components/BootstrapSliderWrapper';
import './PlaySlider.css';
Expand Down Expand Up @@ -124,13 +123,13 @@ export default class PlaySlider extends React.PureComponent {
render() {
const { start, end, step, orientation, reversed, disabled, range, values } = this.props;
return (
<Row className="play-slider">
<Col md={1} className="padded">
<div className="play-slider">
<div className="play-slider-controls padded">
<i className="fa fa-step-backward fa-lg slider-button " onClick={this.stepBackward} />
<i className={this.getPlayClass()} onClick={this.play} />
<i className="fa fa-step-forward fa-lg slider-button " onClick={this.stepForward} />
</Col>
<Col md={11} className="padded">
</div>
<div className="play-slider-scrobbler padded">
<BootrapSliderWrapper
value={range ? values : values[0]}
range={range}
Expand All @@ -143,8 +142,8 @@ export default class PlaySlider extends React.PureComponent {
reversed={reversed}
disabled={disabled ? 'disabled' : 'enabled'}
/>
</Col>
</Row>
</div>
</div>
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
import DeckGLContainer from './DeckGLContainer';
import PlaySlider from '../PlaySlider';

const PLAYSLIDER_HEIGHT = 20; // px

const propTypes = {
getLayers: PropTypes.func.isRequired,
start: PropTypes.number.isRequired,
Expand All @@ -30,6 +32,14 @@ export default class AnimatableDeckGLContainer extends React.Component {
super(props);
const { getLayers, start, end, getStep, values, disabled, viewport, ...other } = props;
this.other = other;

this.onViewportChange = this.onViewportChange.bind(this);
}
onViewportChange(viewport) {
const originalViewport = this.props.disabled
? { ...viewport }
: { ...viewport, height: viewport.height + PLAYSLIDER_HEIGHT };
this.props.onViewportChange(originalViewport);
}
render() {
const {
Expand All @@ -41,18 +51,24 @@ export default class AnimatableDeckGLContainer extends React.Component {
children,
getLayers,
values,
viewport,
onViewportChange,
onValuesChange,
viewport,
} = this.props;
const layers = getLayers(values);

// leave space for the play slider
const modifiedViewport = {
...viewport,
height: disabled ? viewport.height : viewport.height - PLAYSLIDER_HEIGHT,
};

return (
<div>
<DeckGLContainer
{...this.other}
viewport={viewport}
viewport={modifiedViewport}
layers={layers}
onViewportChange={onViewportChange}
onViewportChange={this.onViewportChange}
/>
{!disabled &&
<PlaySlider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getScale } from '../../modules/colors/CategoricalColorNamespace';
import { hexToRGB } from '../../modules/colors';
import { getPlaySliderParams } from '../../modules/time';
import sandboxedEval from '../../modules/sandbox';
import { fitViewport } from './layers/common';

function getCategories(fd, data) {
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
Expand All @@ -34,6 +35,7 @@ const propTypes = {
setControlValue: PropTypes.func.isRequired,
viewport: PropTypes.object.isRequired,
getLayer: PropTypes.func.isRequired,
getPoints: PropTypes.func.isRequired,
payload: PropTypes.object.isRequired,
onAddFilter: PropTypes.func,
setTooltip: PropTypes.func,
Expand All @@ -49,19 +51,59 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
constructor(props) {
super(props);

const fd = props.formData;
const timeGrain = fd.time_grain_sqla || fd.granularity || 'PT1M';
const timestamps = props.payload.data.features.map(f => f.__timestamp);
const { start, end, getStep, values, disabled } = getPlaySliderParams(timestamps, timeGrain);
const categories = getCategories(fd, props.payload.data.features);
this.state = { start, end, getStep, values, disabled, categories, viewport: props.viewport };
this.state = CategoricalDeckGLContainer.getDerivedStateFromProps(props);

this.getLayers = this.getLayers.bind(this);
this.onValuesChange = this.onValuesChange.bind(this);
this.onViewportChange = this.onViewportChange.bind(this);
this.toggleCategory = this.toggleCategory.bind(this);
this.showSingleCategory = this.showSingleCategory.bind(this);
}
static getDerivedStateFromProps(props, state) {
const features = props.payload.data.features || [];
const timestamps = features.map(f => f.__timestamp);
const categories = getCategories(props.formData, features);

// the state is computed only from the payload; if it hasn't changed, do
// not recompute state since this would reset selections and/or the play
// slider position due to changes in form controls
if (state && props.payload.form_data === state.formData) {
return { ...state, categories };
}

// the granularity has to be read from the payload form_data, not the
// props formData which comes from the instantaneous controls state
const granularity = (
props.payload.form_data.time_grain_sqla ||
props.payload.form_data.granularity ||
'P1D'
);

const {
start,
end,
getStep,
values,
disabled,
} = getPlaySliderParams(timestamps, granularity);

const viewport = props.formData.autozoom
? fitViewport(props.viewport, props.getPoints(features))
: props.viewport;

return {
start,
end,
getStep,
values,
disabled,
viewport,
selected: [],
lastClick: 0,
formData: props.payload.form_data,
categories,
};
}
onValuesChange(values) {
this.setState({
values: Array.isArray(values)
Expand All @@ -80,7 +122,9 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
onAddFilter,
setTooltip,
} = this.props;
let features = [...payload.data.features];
let features = payload.data.features
? [...payload.data.features]
: [];

// Add colors from categories or fixed color
features = this.addColor(features, fd);
Expand Down
38 changes: 20 additions & 18 deletions superset/assets/src/visualizations/deckgl/DeckGLContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import MapGL from 'react-map-gl';
import DeckGL from 'deck.gl';
import 'mapbox-gl/dist/mapbox-gl.css';

const TICK = 1000; // milliseconds

const propTypes = {
viewport: PropTypes.object.isRequired,
layers: PropTypes.array.isRequired,
Expand All @@ -22,42 +24,42 @@ export default class DeckGLContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
viewport: props.viewport,
previousViewport: props.viewport,
timer: setInterval(this.tick, TICK),
};
this.tick = this.tick.bind(this);
this.onViewportChange = this.onViewportChange.bind(this);
}
componentWillMount() {
const timer = setInterval(this.tick, 1000);
this.setState(() => ({ timer }));
}
componentWillReceiveProps(nextProps) {
this.setState(() => ({
viewport: { ...nextProps.viewport },
previousViewport: this.state.viewport,
}));
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.viewport !== prevState.viewport) {
return {
viewport: { ...nextProps.viewport },
previousViewport: prevState.viewport,
};
}
return null;
}
componentWillUnmount() {
clearInterval(this.state.timer);
}
onViewportChange(viewport) {
const vp = Object.assign({}, viewport);
delete vp.width;
delete vp.height;
const newVp = { ...this.state.viewport, ...vp };
// delete vp.width;
// delete vp.height;
const newVp = { ...this.state.previousViewport, ...vp };

this.setState(() => ({ viewport: newVp }));
// this.setState(() => ({ viewport: newVp }));
this.props.onViewportChange(newVp);
}
tick() {
// Limiting updating viewport controls through Redux at most 1*sec
if (this.state.previousViewport !== this.state.viewport) {
if (this.state && this.state.previousViewport !== this.props.viewport) {
const setCV = this.props.setControlValue;
const vp = this.state.viewport;
const vp = this.props.viewport;
if (setCV) {
setCV('viewport', vp);
}
this.setState(() => ({ previousViewport: this.state.viewport }));
this.setState(() => ({ previousViewport: this.props.viewport }));
}
}
layers() {
Expand All @@ -68,7 +70,7 @@ export default class DeckGLContainer extends React.Component {
return this.props.layers;
}
render() {
const { viewport } = this.state;
const { viewport } = this.props;
return (
<MapGL
{...viewport}
Expand Down
7 changes: 2 additions & 5 deletions superset/assets/src/visualizations/deckgl/factory.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,9 @@ export function createCategoricalDeckGLComponent(getLayer, getPoints) {
setControlValue,
onAddFilter,
setTooltip,
viewport: originalViewport,
viewport,
} = props;

const viewport = formData.autozoom
? fitViewport(originalViewport, getPoints(payload.data.features))
: originalViewport;

return (
<CategoricalDeckGLContainer
formData={formData}
Expand All @@ -76,6 +72,7 @@ export function createCategoricalDeckGLComponent(getLayer, getPoints) {
payload={payload}
onAddFilter={onAddFilter}
setTooltip={setTooltip}
getPoints={getPoints}
/>
);
}
Expand Down
Loading

0 comments on commit a57603a

Please sign in to comment.