Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added DeckGL.Polygon Layer w/ JS controls #4227

Merged
merged 15 commits into from
Jan 18, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions superset/assets/javascripts/explore/stores/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions superset/assets/javascripts/explore/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions superset/assets/visualizations/deckgl/layers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -15,5 +16,6 @@ const layerGenerators = {
deck_scatter,
deck_geojson,
deck_arc,
deck_polygon,
};
export default layerGenerators;
28 changes: 28 additions & 0 deletions superset/assets/visualizations/deckgl/layers/polygon.jsx
Original file line number Diff line number Diff line change
@@ -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),
});
}
2 changes: 2 additions & 0 deletions superset/assets/visualizations/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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;
30 changes: 30 additions & 0 deletions superset/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Binary file added superset/data/sf_population.json.gz
Binary file not shown.
36 changes: 35 additions & 1 deletion superset/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -1982,7 +1983,6 @@ class DeckPathViz(BaseDeckGLViz):
'json': json.loads,
'polyline': polyline.decode,
}
spatial_control_keys = ['spatial']

def query_obj(self):
d = super(DeckPathViz, self).query_obj()
Expand Down Expand Up @@ -2062,6 +2062,40 @@ def get_data(self, df):
}


class DeckPolygon(BaseDeckGLViz):

"""deck.gl's Polygon Layer"""

viz_type = 'deck_polygon'
verbose_name = _('Deck.gl - Polygon')
deser_map = {
'json': json.loads,
'polyline': polyline.decode,
}

def get_position(self, d):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My recent PR got rid of get_position, you may need to rebase and remove this method.

return d[self.form_data.get('line_column')]

def query_obj(self):
d = super(DeckPolygon, self).query_obj()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mistercrunch not sure if you wanted me to move this functionality into the baseClass? I think this is fine since we are still leveraging the line_column controls. Let me know what you think.

Copy link
Member

@mistercrunch mistercrunch Jan 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory we could have some intermediate (or mixin class) in between the base class that would be common to deck_path and deck_polygon, but in general the python philosophy is to keep inheritance schemes simple when possible. I'm fine leaving it as is as it's little code duplication as it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, could we just have DeckPolygon derive DeckPath? Aren't they pretty much identical?

line_col = self.form_data.get('line_column')
if d['metrics']:
d['groupby'].append(line_col)
else:
d['columns'].append(line_col)
return d

def get_properties(self, d):
fd = self.form_data
deser = self.deser_map[fd.get('line_type')]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could save one hash lookup per row (get_properties gets called for every row in the df) by setting self.deser = self.deser_map[fd.get('line_type')] in __init__. (I missed out on this simple optimization in DeckPath...)

polygon = deser(d[fd.get('line_column')])
if fd.get('reverse_long_lat'):
polygon = (polygon[1], polygon[0])
return {
'polygon': polygon,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is pretty much the only difference between DeckPath and DeckPolyline, I'd suggest have DeckPolyline derive DeckPath, and renaming this key to something generic like points. If we wanted to have a different key on each context, you could do it through a class (static) attribute that would be different for each class.

}


class EventFlowViz(BaseViz):

"""A visualization to explore patterns in event sequences"""
Expand Down