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

[Geo] Added DeckGL Arc Layer and Refactor on BaseDeckGL class #4134

Merged
merged 19 commits into from
Jan 12, 2018
Merged
Show file tree
Hide file tree
Changes from 4 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.
29 changes: 29 additions & 0 deletions superset/assets/javascripts/explore/stores/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,26 @@ export const controls = {
}),
},

start_spatial: {
type: 'SpatialControl',
label: t('Start Longitude & Latitude'),
validators: [v.nonEmpty],
description: t('Point to your spatial columns'),
mapStateToProps: state => ({
choices: (state.datasource) ? state.datasource.all_cols : [],
}),
},

end_spatial: {
type: 'SpatialControl',
label: t('End Longitude & Latitude'),
validators: [v.nonEmpty],
description: t('Point to your spatial columns'),
mapStateToProps: state => ({
choices: (state.datasource) ? state.datasource.all_cols : [],
}),
},

longitude: {
type: 'SelectControl',
label: t('Longitude'),
Expand Down Expand Up @@ -540,6 +560,15 @@ export const controls = {
choices: formatSelectOptions([0, 100, 200, 300, 500]),
},

stroke_width: {
type: 'SelectControl',
freeForm: true,
label: t('Stroke Width'),
validators: [v.integer],
default: null,
choices: formatSelectOptions([1, 2, 3, 4, 5]),
},

all_columns_x: {
type: 'SelectControl',
label: 'X',
Expand Down
28 changes: 28 additions & 0 deletions superset/assets/javascripts/explore/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,34 @@ export const visTypes = {
],
},

deck_arc: {
label: t('Deck.gl - Arc'),
requiresTime: true,
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [
['start_spatial', 'end_spatial'],
['row_limit', null],
],
},
{
label: t('Map'),
controlSetRows: [
['mapbox_style', 'viewport'],
],
},
{
label: t('Arc'),
controlSetRows: [
['color_picker', null],
['stroke_width', null],
],
},
],
},

deck_scatter: {
label: t('Deck.gl - Scatter plot'),
requiresTime: true,
Expand Down
16 changes: 16 additions & 0 deletions superset/assets/visualizations/deckgl/layers/arc.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ArcLayer } from 'deck.gl';

export default function arcLayer(formData, payload) {
const fd = formData;
const fc = fd.color_picker;
const data = payload.data.arcs.map(d => ({
...d,
color: [fc.r, fc.g, fc.b, 255 * fc.a],
}));

return new ArcLayer({
id: `path-layer-${fd.slice_id}`,
data,
strokeWidth: (fd.stroke_width) ? fd.stroke_width : 3,
});
}
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 @@ -5,6 +5,7 @@ import deck_path from './path';
import deck_hex from './hex';
import deck_scatter from './scatter';
import deck_geojson from './geojson';
import deck_arc from './arc';

const layerGenerators = {
deck_grid,
Expand All @@ -13,5 +14,6 @@ const layerGenerators = {
deck_hex,
deck_scatter,
deck_geojson,
deck_arc,
};
export default layerGenerators;
2 changes: 2 additions & 0 deletions superset/assets/visualizations/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const VIZ_TYPES = {
deck_path: 'deck_path',
deck_geojson: 'deck_geojson',
deck_multi: 'deck_multi',
deck_arc: 'deck_arc',
};

const vizMap = {
Expand Down Expand Up @@ -92,6 +93,7 @@ const vizMap = {
[VIZ_TYPES.deck_hex]: deckglFactory,
[VIZ_TYPES.deck_path]: deckglFactory,
[VIZ_TYPES.deck_geojson]: deckglFactory,
[VIZ_TYPES.deck_arc]: deckglFactory,
[VIZ_TYPES.deck_multi]: require('./deckgl/multi.jsx'),
};
export default vizMap;
98 changes: 98 additions & 0 deletions superset/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,58 @@ def get_position(self, d):
d.get('lat'),
]

def process_spatial_query_obj(self, spatial_key):
fd = self.form_data
gb = []
spatial = fd.get(spatial_key)

if spatial is None:
raise Exception(_('Bad spatial key'))
Copy link
Member

Choose a reason for hiding this comment

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

nit: ValueError


if spatial.get('type') == 'latlong':
gb += [spatial.get('lonCol')]
gb += [spatial.get('latCol')]
elif spatial.get('type') == 'delimited':
gb += [spatial.get('lonlatCol')]
elif spatial.get('type') == 'geohash':
gb += [spatial.get('geohashCol')]

return gb

def process_spatial_data_obj(self, df, spatial_key):
fd = self.form_data
spatial = fd.get(spatial_key)
if spatial is None:
raise Exception(_('Bad spatial key'))
Copy link
Member

Choose a reason for hiding this comment

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

nit: ValueError


if spatial.get('type') == 'latlong':
df = df.rename(columns={
spatial.get('lonCol'): 'lon',
Copy link
Member

Choose a reason for hiding this comment

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

Aren't names conflicting here when there are multiple spatial controls? For that reason I think it'd be ideal to use the key as the column name in the dataframe, and store a tuple with lat & long as the value.

spatial.get('latCol'): 'lat'})
elif spatial.get('type') == 'delimited':
cols = ['lon', 'lat']
if spatial.get('reverseCheckbox'):
cols.reverse()
df[cols] = (
df[spatial.get('lonlatCol')]
.str
.split(spatial.get('delimiter'), expand=True)
.astype(np.float64)
)
del df[spatial.get('lonlatCol')]
elif spatial.get('type') == 'geohash':
latlong = df[spatial.get('geohashCol')].map(geohash.decode)
df['lat'] = latlong.apply(lambda x: x[0])
Copy link
Member

Choose a reason for hiding this comment

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

Idea: it may be less complex overall to simply store the lat/long as a tuple in a single column

df['lon'] = latlong.apply(lambda x: x[1])
del df['geohash']

features = []
for d in df.to_dict(orient='records'):
d = dict(position=self.get_position(d), **self.get_properties(d))
features.append(d)

return features

def query_obj(self):
d = super(BaseDeckGLViz, self).query_obj()
fd = self.form_data
Expand Down Expand Up @@ -1847,6 +1899,7 @@ def query_obj(self):

def get_data(self, df):
fd = self.form_data

spatial = fd.get('spatial')
if spatial:
if spatial.get('type') == 'latlong':
Expand Down Expand Up @@ -1998,6 +2051,51 @@ def get_data(self, df):
}


class DeckArc(BaseDeckGLViz):

"""deck.gl's Arc Layer"""

viz_type = 'deck_arc'
verbose_name = _('Deck.gl - Arc')

def query_obj(self):
d = super(DeckArc, self).query_obj()

gb = []
for spatial_key in ['start_spatial', 'end_spatial']:
gb += self.process_spatial_query_obj(spatial_key)

metrics = self.get_metrics()
if metrics:
d['groupby'] = gb
d['metrics'] = self.get_metrics()
else:
d['columns'] = gb

return d

def get_data(self, df):
fd = self.form_data
start_lat = df[fd.get('start_spatial').get('latCol')]
start_lon = df[fd.get('start_spatial').get('lonCol')]
end_lat = df[fd.get('end_spatial').get('latCol')]
end_lon = df[fd.get('end_spatial').get('lonCol')]
source_pos = list(zip(start_lon, start_lat))
target_pos = list(zip(end_lon, end_lat))

data = []
for pos in list(zip(source_pos, target_pos)):
data.append({
'sourcePosition': pos[0],
'targetPosition': pos[1],
})

return {
'arcs': data,
'mapboxApiKey': config.get('MAPBOX_API_KEY'),
}


class EventFlowViz(BaseViz):

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