Skip to content

Commit

Permalink
[geo] introduce "Auto Zoom" control (apache#4389)
Browse files Browse the repository at this point in the history
* [geo] introduce "Auto Zoom" control

On geospatial visualization, checking the "Auto Zoom" control makes it
such that the viewport is fitted to the data upon rendering the chart.

For dashboards with region filters, the map should jump to the right
position.

Eventually we should enhance this to fly and ease to the position in an
animated way.

* Added TODO notes
  • Loading branch information
mistercrunch authored Feb 13, 2018
1 parent d107869 commit d0f50de
Show file tree
Hide file tree
Showing 16 changed files with 141 additions and 34 deletions.
1 change: 1 addition & 0 deletions superset/assets/javascripts/chart/Chart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ class Chart extends React.PureComponent {
});
this.props.actions.chartRenderingSucceeded(this.props.chartKey);
} catch (e) {
console.error(e); // eslint-disable-line
this.props.actions.chartRenderingFailed(e, this.props.chartKey);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const propTypes = {
]),
isFloat: PropTypes.bool,
isInt: PropTypes.bool,
disabled: PropTypes.bool,
};

const defaultProps = {
Expand All @@ -21,6 +22,7 @@ const defaultProps = {
value: '',
isInt: false,
isFloat: false,
disabled: false,
};

export default class TextControl extends React.Component {
Expand Down Expand Up @@ -63,6 +65,7 @@ export default class TextControl extends React.Component {
onChange={this.onChange}
onFocus={this.props.onFocus}
value={value}
disabled={this.props.disabled}
/>
</FormGroup>
</div>
Expand Down
8 changes: 8 additions & 0 deletions superset/assets/javascripts/explore/stores/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,14 @@ export const controls = {
default: false,
},

autozoom: {
type: 'CheckboxControl',
label: t('Auto Zoom'),
default: true,
renderTrigger: true,
description: t('When checked, the map will zoom to your data after each query'),
},

show_perc: {
type: 'CheckboxControl',
label: t('Show percentage'),
Expand Down
11 changes: 8 additions & 3 deletions superset/assets/javascripts/explore/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ export const visTypes = {
label: t('Map'),
controlSetRows: [
['mapbox_style', 'viewport'],
['color_picker', null],
['color_picker', 'autozoom'],
['grid_size', 'extruded'],
],
},
Expand Down Expand Up @@ -407,7 +407,7 @@ export const visTypes = {
label: t('Map'),
controlSetRows: [
['mapbox_style', 'viewport'],
['color_picker', null],
['color_picker', 'autozoom'],
['grid_size', 'extruded'],
],
},
Expand Down Expand Up @@ -448,7 +448,7 @@ export const visTypes = {
controlSetRows: [
['mapbox_style', 'viewport'],
['color_picker', 'line_width'],
['reverse_long_lat', null],
['reverse_long_lat', 'autozoom'],
],
},
{
Expand Down Expand Up @@ -479,6 +479,7 @@ export const visTypes = {
label: t('Map'),
controlSetRows: [
['mapbox_style', 'viewport'],
['autozoom', null],
],
},
{
Expand Down Expand Up @@ -521,6 +522,7 @@ export const visTypes = {
label: t('Map'),
controlSetRows: [
['mapbox_style', 'viewport'],
// TODO ['autozoom', null],
],
},
{
Expand Down Expand Up @@ -600,6 +602,7 @@ export const visTypes = {
label: t('Map'),
controlSetRows: [
['mapbox_style', 'viewport'],
['autozoom', null],
],
},
{
Expand Down Expand Up @@ -635,8 +638,10 @@ export const visTypes = {
},
{
label: t('Map'),
expanded: true,
controlSetRows: [
['mapbox_style', 'viewport'],
['autozoom', null],
],
},
{
Expand Down
4 changes: 3 additions & 1 deletion superset/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"distributions": "^1.0.0",
"dompurify": "^1.0.3",
"fastdom": "^1.0.6",
"geojson-extent": "^0.3.2",
"geolib": "^2.0.24",
"immutable": "^3.8.2",
"jed": "^1.1.1",
Expand Down Expand Up @@ -105,7 +106,7 @@
"supercluster": "https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40",
"underscore": "^1.8.3",
"urijs": "^1.18.10",
"viewport-mercator-project": "^2.1.0"
"viewport-mercator-project": "^5.0.0"
},
"devDependencies": {
"babel-cli": "^6.14.0",
Expand Down Expand Up @@ -137,6 +138,7 @@
"less": "^2.6.1",
"less-loader": "^4.0.3",
"mocha": "^3.2.0",
"npm-check-updates": "^2.14.0",
"react-addons-test-utils": "^15.6.2",
"react-test-renderer": "^15.6.2",
"redux-mock-store": "^1.2.3",
Expand Down
1 change: 1 addition & 0 deletions superset/assets/visualizations/deckgl/DeckGLContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default class DeckGLContainer extends React.Component {
componentWillReceiveProps(nextProps) {
this.setState(() => ({
viewport: { ...nextProps.viewport },
previousViewport: this.state.viewport,
}));
}
componentWillUnmount() {
Expand Down
15 changes: 14 additions & 1 deletion superset/assets/visualizations/deckgl/layers/arc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import DeckGLContainer from './../DeckGLContainer';
import * as common from './common';
import sandboxedEval from '../../../javascripts/modules/sandbox';

function getPoints(data) {
const points = [];
data.forEach((d) => {
points.push(d.sourcePosition);
points.push(d.targetPosition);
});
return points;
}

function getLayer(formData, payload, slice) {
const fd = formData;
const fc = fd.color_picker;
Expand All @@ -32,11 +41,15 @@ function getLayer(formData, payload, slice) {

function deckArc(slice, payload, setControlValue) {
const layer = getLayer(slice.formData, payload, slice);
const viewport = {
let viewport = {
...slice.formData.viewport,
width: slice.width(),
height: slice.height(),
};

if (slice.formData.autozoom) {
viewport = common.fitViewport(viewport, getPoints(payload.data.arcs));
}
ReactDOM.render(
<DeckGLContainer
mapboxApiAccessToken={payload.data.mapboxApiKey}
Expand Down
24 changes: 24 additions & 0 deletions superset/assets/visualizations/deckgl/layers/common.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import dompurify from 'dompurify';
import { fitBounds } from 'viewport-mercator-project';

import sandboxedEval from '../../../javascripts/modules/sandbox';

export function getBounds(points) {
const latExt = d3.extent(points, d => d[1]);
const lngExt = d3.extent(points, d => d[0]);
return [
[lngExt[0], latExt[0]],
[lngExt[1], latExt[1]],
];
}

export function fitViewport(viewport, points, padding = 10) {
const bounds = getBounds(points);
return {
...viewport,
...fitBounds({
height: viewport.height,
width: viewport.width,
padding,
bounds,
}),
};
}

export function commonLayerProps(formData, slice) {
const fd = formData;
let onHover;
Expand Down
8 changes: 6 additions & 2 deletions superset/assets/visualizations/deckgl/layers/geojson.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';

import { GeoJsonLayer } from 'deck.gl';
// TODO import geojsonExtent from 'geojson-extent';

import DeckGLContainer from './../DeckGLContainer';

import * as common from './common';
import { hexToRGB } from '../../../javascripts/modules/colors';
import sandboxedEval from '../../../javascripts/modules/sandbox';
Expand Down Expand Up @@ -100,6 +99,11 @@ function deckGeoJson(slice, payload, setControlValue) {
width: slice.width(),
height: slice.height(),
};
if (slice.formData.autozoom) {
// TODO get this to work
// viewport = common.fitViewport(viewport, geojsonExtent(payload.data.features));
}

ReactDOM.render(
<DeckGLContainer
mapboxApiAccessToken={payload.data.mapboxApiKey}
Expand Down
11 changes: 10 additions & 1 deletion superset/assets/visualizations/deckgl/layers/grid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@ function getLayer(formData, payload, slice) {
});
}

function getPoints(data) {
return data.map(d => d.position);
}

function deckGrid(slice, payload, setControlValue) {
const layer = getLayer(slice.formData, payload, slice);
const viewport = {
let viewport = {
...slice.formData.viewport,
width: slice.width(),
height: slice.height(),
};

if (slice.formData.autozoom) {
viewport = common.fitViewport(viewport, getPoints(payload.data.features));
}

ReactDOM.render(
<DeckGLContainer
mapboxApiAccessToken={payload.data.mapboxApiKey}
Expand Down
11 changes: 10 additions & 1 deletion superset/assets/visualizations/deckgl/layers/hex.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@ function getLayer(formData, payload, slice) {
});
}

function getPoints(data) {
return data.map(d => d.position);
}

function deckHex(slice, payload, setControlValue) {
const layer = getLayer(slice.formData, payload, slice);
const viewport = {
let viewport = {
...slice.formData.viewport,
width: slice.width(),
height: slice.height(),
};

if (slice.formData.autozoom) {
viewport = common.fitViewport(viewport, getPoints(payload.data.features));
}

ReactDOM.render(
<DeckGLContainer
mapboxApiAccessToken={payload.data.mapboxApiKey}
Expand Down
15 changes: 14 additions & 1 deletion superset/assets/visualizations/deckgl/layers/path.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,26 @@ function getLayer(formData, payload, slice) {
});
}

function getPoints(data) {
let points = [];
data.forEach((d) => {
points = points.concat(d.path);
});
return points;
}

function deckPath(slice, payload, setControlValue) {
const layer = getLayer(slice.formData, payload, slice);
const viewport = {
let viewport = {
...slice.formData.viewport,
width: slice.width(),
height: slice.height(),
};

if (slice.formData.autozoom) {
viewport = common.fitViewport(viewport, getPoints(payload.data.features));
}

ReactDOM.render(
<DeckGLContainer
mapboxApiAccessToken={payload.data.mapboxApiKey}
Expand Down
24 changes: 17 additions & 7 deletions superset/assets/visualizations/deckgl/layers/scatter.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';

import { ScatterplotLayer } from 'deck.gl';

import DeckGLContainer from './../DeckGLContainer';

import * as common from './common';
import { getColorFromScheme, hexToRGB } from '../../../javascripts/modules/colors';
import { unitToRadius } from '../../../javascripts/modules/geo';
import sandboxedEval from '../../../javascripts/modules/sandbox';

function getPoints(data) {
return data.map(d => d.position);
}

function getLayer(formData, payload, slice) {
const fd = formData;
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
Expand Down Expand Up @@ -50,17 +52,25 @@ function getLayer(formData, payload, slice) {

function deckScatter(slice, payload, setControlValue) {
const layer = getLayer(slice.formData, payload, slice);
const viewport = {
...slice.formData.viewport,
width: slice.width(),
height: slice.height(),
const fd = slice.formData;
const width = slice.width();
const height = slice.height();
let viewport = {
...fd.viewport,
width,
height,
};

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}
mapStyle={fd.mapbox_style}
setControlValue={setControlValue}
/>,
document.getElementById(slice.containerId),
Expand Down
9 changes: 8 additions & 1 deletion superset/assets/visualizations/deckgl/layers/screengrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,20 @@ function getLayer(formData, payload, slice) {
});
}

function getPoints(data) {
return data.map(d => d.position);
}

function deckScreenGrid(slice, payload, setControlValue) {
const layer = getLayer(slice.formData, payload, slice);
const viewport = {
let viewport = {
...slice.formData.viewport,
width: slice.width(),
height: slice.height(),
};
if (slice.formData.autozoom) {
viewport = common.fitViewport(viewport, getPoints(payload.data.features));
}
ReactDOM.render(
<DeckGLContainer
mapboxApiAccessToken={payload.data.mapboxApiKey}
Expand Down
Loading

0 comments on commit d0f50de

Please sign in to comment.