mistercrunch closed pull request #4389: [geo] introduce "Auto Zoom" control URL: https://github.com/apache/incubator-superset/pull/4389
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/chart/Chart.jsx b/superset/assets/javascripts/chart/Chart.jsx index 7bb71aca47..cc18bb4fe6 100644 --- a/superset/assets/javascripts/chart/Chart.jsx +++ b/superset/assets/javascripts/chart/Chart.jsx @@ -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); } } diff --git a/superset/assets/javascripts/explore/components/controls/TextControl.jsx b/superset/assets/javascripts/explore/components/controls/TextControl.jsx index bfe3f99177..ed1238e509 100644 --- a/superset/assets/javascripts/explore/components/controls/TextControl.jsx +++ b/superset/assets/javascripts/explore/components/controls/TextControl.jsx @@ -13,6 +13,7 @@ const propTypes = { ]), isFloat: PropTypes.bool, isInt: PropTypes.bool, + disabled: PropTypes.bool, }; const defaultProps = { @@ -21,6 +22,7 @@ const defaultProps = { value: '', isInt: false, isFloat: false, + disabled: false, }; export default class TextControl extends React.Component { @@ -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> diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx index 8e3420bd81..e8a34d950d 100644 --- a/superset/assets/javascripts/explore/stores/controls.jsx +++ b/superset/assets/javascripts/explore/stores/controls.jsx @@ -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'), diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js index 9d62eada88..3135b2613d 100644 --- a/superset/assets/javascripts/explore/stores/visTypes.js +++ b/superset/assets/javascripts/explore/stores/visTypes.js @@ -369,7 +369,7 @@ export const visTypes = { label: t('Map'), controlSetRows: [ ['mapbox_style', 'viewport'], - ['color_picker', null], + ['color_picker', 'autozoom'], ['grid_size', 'extruded'], ], }, @@ -407,7 +407,7 @@ export const visTypes = { label: t('Map'), controlSetRows: [ ['mapbox_style', 'viewport'], - ['color_picker', null], + ['color_picker', 'autozoom'], ['grid_size', 'extruded'], ], }, @@ -448,7 +448,7 @@ export const visTypes = { controlSetRows: [ ['mapbox_style', 'viewport'], ['color_picker', 'line_width'], - ['reverse_long_lat', null], + ['reverse_long_lat', 'autozoom'], ], }, { @@ -479,6 +479,7 @@ export const visTypes = { label: t('Map'), controlSetRows: [ ['mapbox_style', 'viewport'], + ['autozoom', null], ], }, { @@ -521,6 +522,7 @@ export const visTypes = { label: t('Map'), controlSetRows: [ ['mapbox_style', 'viewport'], + // TODO ['autozoom', null], ], }, { @@ -600,6 +602,7 @@ export const visTypes = { label: t('Map'), controlSetRows: [ ['mapbox_style', 'viewport'], + ['autozoom', null], ], }, { @@ -635,8 +638,10 @@ export const visTypes = { }, { label: t('Map'), + expanded: true, controlSetRows: [ ['mapbox_style', 'viewport'], + ['autozoom', null], ], }, { diff --git a/superset/assets/package.json b/superset/assets/package.json index abc978c079..fbfa32a27d 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -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", @@ -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", @@ -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", diff --git a/superset/assets/visualizations/deckgl/DeckGLContainer.jsx b/superset/assets/visualizations/deckgl/DeckGLContainer.jsx index 3166917744..1b7ca317ca 100644 --- a/superset/assets/visualizations/deckgl/DeckGLContainer.jsx +++ b/superset/assets/visualizations/deckgl/DeckGLContainer.jsx @@ -33,6 +33,7 @@ export default class DeckGLContainer extends React.Component { componentWillReceiveProps(nextProps) { this.setState(() => ({ viewport: { ...nextProps.viewport }, + previousViewport: this.state.viewport, })); } componentWillUnmount() { diff --git a/superset/assets/visualizations/deckgl/layers/arc.jsx b/superset/assets/visualizations/deckgl/layers/arc.jsx index ebeff3cb89..43583e0170 100644 --- a/superset/assets/visualizations/deckgl/layers/arc.jsx +++ b/superset/assets/visualizations/deckgl/layers/arc.jsx @@ -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; @@ -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} diff --git a/superset/assets/visualizations/deckgl/layers/common.js b/superset/assets/visualizations/deckgl/layers/common.js index 7f11213b6c..4692401cf8 100644 --- a/superset/assets/visualizations/deckgl/layers/common.js +++ b/superset/assets/visualizations/deckgl/layers/common.js @@ -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; diff --git a/superset/assets/visualizations/deckgl/layers/geojson.jsx b/superset/assets/visualizations/deckgl/layers/geojson.jsx index 7f0936304e..fe7805a279 100644 --- a/superset/assets/visualizations/deckgl/layers/geojson.jsx +++ b/superset/assets/visualizations/deckgl/layers/geojson.jsx @@ -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'; @@ -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} diff --git a/superset/assets/visualizations/deckgl/layers/grid.jsx b/superset/assets/visualizations/deckgl/layers/grid.jsx index 1e7ff1d70b..6f92f0688c 100644 --- a/superset/assets/visualizations/deckgl/layers/grid.jsx +++ b/superset/assets/visualizations/deckgl/layers/grid.jsx @@ -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} diff --git a/superset/assets/visualizations/deckgl/layers/hex.jsx b/superset/assets/visualizations/deckgl/layers/hex.jsx index 7dc4d8b025..f0fb925da5 100644 --- a/superset/assets/visualizations/deckgl/layers/hex.jsx +++ b/superset/assets/visualizations/deckgl/layers/hex.jsx @@ -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} diff --git a/superset/assets/visualizations/deckgl/layers/path.jsx b/superset/assets/visualizations/deckgl/layers/path.jsx index a20c2bbdb0..4b45a0ba13 100644 --- a/superset/assets/visualizations/deckgl/layers/path.jsx +++ b/superset/assets/visualizations/deckgl/layers/path.jsx @@ -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} diff --git a/superset/assets/visualizations/deckgl/layers/scatter.jsx b/superset/assets/visualizations/deckgl/layers/scatter.jsx index ed1dd792af..646a9afef7 100644 --- a/superset/assets/visualizations/deckgl/layers/scatter.jsx +++ b/superset/assets/visualizations/deckgl/layers/scatter.jsx @@ -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 }; @@ -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), diff --git a/superset/assets/visualizations/deckgl/layers/screengrid.jsx b/superset/assets/visualizations/deckgl/layers/screengrid.jsx index 7494c67d3d..7d6742e6e8 100644 --- a/superset/assets/visualizations/deckgl/layers/screengrid.jsx +++ b/superset/assets/visualizations/deckgl/layers/screengrid.jsx @@ -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} diff --git a/superset/data/__init__.py b/superset/data/__init__.py index e1d0b67558..be89c1a7ec 100644 --- a/superset/data/__init__.py +++ b/superset/data/__init__.py @@ -1266,10 +1266,10 @@ def load_deck_dash(): "point_radius_fixed": {"type": "metric", "value": "count"}, "point_unit": "square_m", "row_limit": 5000, - "since": "2014-01-01", + "since": None, "size": "count", "time_grain_sqla": "Time Column", - "until": "now", + "until": None, "viewport": { "bearing": -4.952916738791771, "latitude": 37.78926922909199, @@ -1305,9 +1305,9 @@ def load_deck_dash(): "granularity_sqla": "date", "size": "count", "viz_type": "deck_screengrid", - "since": "2014-01-01", + "since": None, "point_radius": "Auto", - "until": "now", + "until": None, "color_picker": { "a": 1, "r": 14, @@ -1352,10 +1352,10 @@ def load_deck_dash(): "granularity_sqla": "date", "size": "count", "viz_type": "deck_hex", - "since": "2014-01-01", + "since": None, "point_radius_unit": "Pixels", "point_radius": "Auto", - "until": "now", + "until": None, "color_picker": { "a": 1, "r": 14, @@ -1401,10 +1401,10 @@ def load_deck_dash(): "granularity_sqla": "date", "size": "count", "viz_type": "deck_grid", - "since": "2014-01-01", + "since": None, "point_radius_unit": "Pixels", "point_radius": "Auto", - "until": "now", + "until": None, "color_picker": { "a": 1, "r": 14, @@ -1446,8 +1446,8 @@ def load_deck_dash(): "slice_id": 41, "granularity_sqla": None, "time_grain_sqla": None, - "since": "7 days ago", - "until": "now", + "since": None, + "until": None, "line_column": "contour", "line_type": "json", "mapbox_style": "mapbox://styles/mapbox/light-v9", @@ -1515,8 +1515,8 @@ def load_deck_dash(): "slice_id": 42, "granularity_sqla": "date", "time_grain_sqla": "Time Column", - "since": "2014-01-01", - "until": "now", + "since": None, + "until": None, "start_spatial": { "type": "latlong", "latCol": "LATITUDE", @@ -1573,8 +1573,8 @@ def load_deck_dash(): "slice_id": 43, "viz_type": "deck_path", "time_grain_sqla": "Time Column", - "since": "7 days ago", - "until": "now", + "since": None, + "until": None, "line_column": "path_json", "line_type": "json", "row_limit": 5000, diff --git a/superset/viz.py b/superset/viz.py index 5c36e40891..c09b3cf51a 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -291,7 +291,6 @@ def get_payload(self, query_obj=None): if df is not None and len(df.index) == 0: raise Exception('No data') payload['data'] = self.get_data(df) - del payload['df'] return payload @@ -1932,7 +1931,6 @@ def process_spatial_data_obj(self, key, df): spatial = self.form_data.get(key) if spatial is None: raise ValueError(_('Bad spatial key')) - if spatial.get('type') == 'latlong': df[key] = list(zip(df[spatial.get('lonCol')], df[spatial.get('latCol')])) elif spatial.get('type') == 'delimited': ---------------------------------------------------------------- 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: us...@infra.apache.org With regards, Apache Git Services