diff --git a/superset/assets/images/viz_thumbnails/deck_polygon.png b/superset/assets/images/viz_thumbnails/deck_polygon.png new file mode 100644 index 000000000000..dfae8616ffac Binary files /dev/null and b/superset/assets/images/viz_thumbnails/deck_polygon.png differ diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx index fbfc893aaa27..1ebad1efdfe7 100644 --- a/superset/assets/javascripts/explore/stores/controls.jsx +++ b/superset/assets/javascripts/explore/stores/controls.jsx @@ -571,6 +571,16 @@ export const controls = { }), }, + polygon: { + type: 'SelectControl', + label: t('Polygon Column'), + validators: [v.nonEmpty], + description: t('Select the polygon column. Each row should contain JSON.array(N) of [longitude, latitude] points'), + mapStateToProps: state => ({ + choices: (state.datasource) ? state.datasource.all_cols : [], + }), + }, + point_radius_scale: { type: 'SelectControl', freeForm: true, diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js index e8b95cd4aca6..b175ff3fa882 100644 --- a/superset/assets/javascripts/explore/stores/visTypes.js +++ b/superset/assets/javascripts/explore/stores/visTypes.js @@ -517,6 +517,46 @@ export const visTypes = { ], }, + deck_polygon: { + label: t('Deck.gl - Polygon'), + requiresTime: true, + controlPanelSections: [ + { + label: t('Query'), + expanded: true, + controlSetRows: [ + ['line_column', 'line_type'], + ['row_limit', null], + ], + }, + { + label: t('Map'), + controlSetRows: [ + ['mapbox_style', 'viewport'], + ['reverse_long_lat', null], + ], + }, + { + label: t('Polygon Settings'), + controlSetRows: [ + ['fill_color_picker', 'stroke_color_picker'], + ['filled', 'stroked'], + ['extruded', null], + ['point_radius_scale', null], + ], + }, + { + label: t('Advanced'), + controlSetRows: [ + ['js_columns'], + ['js_datapoint_mutator'], + ['js_tooltip'], + ['js_onclick_href'], + ], + }, + ], + }, + deck_arc: { label: t('Deck.gl - Arc'), requiresTime: true, diff --git a/superset/assets/visualizations/deckgl/layers/index.js b/superset/assets/visualizations/deckgl/layers/index.js index 4d14196cc26d..4e354e535e16 100644 --- a/superset/assets/visualizations/deckgl/layers/index.js +++ b/superset/assets/visualizations/deckgl/layers/index.js @@ -6,6 +6,7 @@ import deck_hex from './hex'; import deck_scatter from './scatter'; import deck_geojson from './geojson'; import deck_arc from './arc'; +import deck_polygon from './polygon'; const layerGenerators = { deck_grid, @@ -15,5 +16,6 @@ const layerGenerators = { deck_scatter, deck_geojson, deck_arc, + deck_polygon, }; export default layerGenerators; diff --git a/superset/assets/visualizations/deckgl/layers/polygon.jsx b/superset/assets/visualizations/deckgl/layers/polygon.jsx new file mode 100644 index 000000000000..05c32c619d70 --- /dev/null +++ b/superset/assets/visualizations/deckgl/layers/polygon.jsx @@ -0,0 +1,28 @@ +import { PolygonLayer } from 'deck.gl'; + +import * as common from './common'; +import sandboxedEval from '../../../javascripts/modules/sandbox'; + +export default function polygonLayer(formData, payload, slice) { + const fd = formData; + const fc = fd.fill_color_picker; + let data = payload.data.features.map(d => ({ + ...d, + fillColor: [fc.r, fc.g, fc.b, 255 * fc.a], + })); + + if (fd.js_datapoint_mutator) { + // Applying user defined data mutator if defined + const jsFnMutator = sandboxedEval(fd.js_datapoint_mutator); + data = data.map(jsFnMutator); + } + + return new PolygonLayer({ + id: `path-layer-${fd.slice_id}`, + data, + filled: fd.filled, + stroked: fd.stoked, + extruded: fd.extruded, + ...common.commonLayerProps(fd, slice), + }); +} diff --git a/superset/assets/visualizations/main.js b/superset/assets/visualizations/main.js index a455cf89b6a2..93c12e861775 100644 --- a/superset/assets/visualizations/main.js +++ b/superset/assets/visualizations/main.js @@ -48,6 +48,7 @@ export const VIZ_TYPES = { deck_geojson: 'deck_geojson', deck_multi: 'deck_multi', deck_arc: 'deck_arc', + deck_polygon: 'deck_polygon', }; const vizMap = { @@ -95,6 +96,7 @@ const vizMap = { [VIZ_TYPES.deck_path]: deckglFactory, [VIZ_TYPES.deck_geojson]: deckglFactory, [VIZ_TYPES.deck_arc]: deckglFactory, + [VIZ_TYPES.deck_polygon]: deckglFactory, [VIZ_TYPES.deck_multi]: require('./deckgl/multi.jsx'), }; export default vizMap; diff --git a/superset/data/__init__.py b/superset/data/__init__.py index bddc01432a46..c5c8241c529a 100644 --- a/superset/data/__init__.py +++ b/superset/data/__init__.py @@ -1552,6 +1552,36 @@ def load_paris_iris_geojson(): tbl.fetch_metadata() +def load_sf_population_polygons(): + tbl_name = 'sf_population_polygons' + + with gzip.open(os.path.join(DATA_FOLDER, 'sf_population.json.gz')) as f: + df = pd.read_json(f) + df['contour'] = df.contour.map(json.dumps) + + df.to_sql( + tbl_name, + db.engine, + if_exists='replace', + chunksize=500, + dtype={ + 'zipcode': BigInteger, + 'population': BigInteger, + 'contour': Text, + 'area': BigInteger, + }, + index=False) + print("Creating table {} reference".format(tbl_name)) + tbl = db.session.query(TBL).filter_by(table_name=tbl_name).first() + if not tbl: + tbl = TBL(table_name=tbl_name) + tbl.description = "Population density of San Francisco" + tbl.database = get_or_create_main_db() + db.session.merge(tbl) + db.session.commit() + tbl.fetch_metadata() + + def load_bart_lines(): tbl_name = 'bart_lines' with gzip.open(os.path.join(DATA_FOLDER, 'bart-lines.json.gz')) as f: diff --git a/superset/data/sf_population.json.gz b/superset/data/sf_population.json.gz new file mode 100644 index 000000000000..53ba13acd780 Binary files /dev/null and b/superset/data/sf_population.json.gz differ diff --git a/superset/viz.py b/superset/viz.py index 65cc9757684f..acef0d74bbe9 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -1895,6 +1895,7 @@ def get_data(self, df): if extra_props: feature['extraProps'] = extra_props features.append(feature) + return { 'features': features, 'mapboxApiKey': config.get('MAPBOX_API_KEY'), @@ -1978,11 +1979,11 @@ class DeckPathViz(BaseDeckGLViz): viz_type = 'deck_path' verbose_name = _('Deck.gl - Paths') + deck_viz_key = 'path' deser_map = { 'json': json.loads, 'polyline': polyline.decode, } - spatial_control_keys = ['spatial'] def query_obj(self): d = super(DeckPathViz, self).query_obj() @@ -2000,10 +2001,19 @@ def get_properties(self, d): if fd.get('reverse_long_lat'): path = (path[1], path[0]) return { - 'path': path, + self.deck_viz_key: path, } +class DeckPolygon(DeckPathViz): + + """deck.gl's Polygon Layer""" + + viz_type = 'deck_polygon' + deck_viz_key = 'polygon' + verbose_name = _('Deck.gl - Polygon') + + class DeckHex(BaseDeckGLViz): """deck.gl's DeckLayer"""