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 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions superset/assets/javascripts/explore/stores/controls.jsx
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'),
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we're assuming JSON formatted string here based on the code bellow. We should be clear about that here. The column the user points to should contain JSON array(N) of array(2).

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we ever use polyline for polygons, seems like it should work for both. We may want to support both as we do for deck_path (optional). That's only immediately useful if we have that input format internally.

mapStateToProps: state => ({
choices: (state.datasource) ? state.datasource.all_cols : [],
}),
},

point_radius_scale: {
type: 'SelectControl',
freeForm: true,
Expand Down
38 changes: 38 additions & 0 deletions superset/assets/javascripts/explore/stores/visTypes.js
Expand Up @@ -517,6 +517,44 @@ export const visTypes = {
],
},

deck_polygon: {
label: t('Deck.gl - Polygon'),
requiresTime: true,
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [
['polygon', 'row_limit'],
Copy link
Member

Choose a reason for hiding this comment

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

We may want to support the reverse control here. Seems like so far I've seen both lat/long long/lat in different places.

],
},
{
label: t('Map'),
controlSetRows: [
['mapbox_style', 'viewport'],
],
},
{
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
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
@@ -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.polygons.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
Expand Up @@ -47,6 +47,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 @@ -94,6 +95,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
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.
22 changes: 22 additions & 0 deletions superset/viz.py
Expand Up @@ -2067,6 +2067,28 @@ def get_data(self, df):
}


class DeckPolygon(BaseDeckGLViz):

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

viz_type = 'deck_polygon'
verbose_name = _('Deck.gl - Polygon')

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

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?

d['columns'] = [self.form_data.get('polygon')]
d['metrics'] = []
d['groupby'] = []
return d

def get_data(self, df):
fd = self.form_data
return {
'polygons': [{'polygon': json.loads(item)} for item in df[fd.get('polygon')]],
'mapboxApiKey': config.get('MAPBOX_API_KEY'),
}


class EventFlowViz(BaseViz):

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