Skip to content

Commit

Permalink
Multi layers DECK.GL visualization (apache#4096)
Browse files Browse the repository at this point in the history
* Multi layers DECK.GL viz

* Fix tests

* rebasing

* Fix error handling in chartActions

* Addressing comments
  • Loading branch information
mistercrunch authored and michellethomas committed May 23, 2018
1 parent 09fbfd2 commit c8e2ae6
Show file tree
Hide file tree
Showing 21 changed files with 280 additions and 236 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def get_git_sha():
'pyyaml>=3.11',
'requests==2.17.3',
'simplejson==3.10.0',
'six==1.10.0',
'six==1.11.0',
'sqlalchemy==1.1.9',
'sqlalchemy-utils==0.32.16',
'sqlparse==0.2.3',
Expand Down
Binary file added superset/assets/images/viz_thumbnails/multi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 14 additions & 1 deletion superset/assets/javascripts/chart/chartAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,20 @@ export function runQuery(formData, force = false, timeout = 60, key) {
if (err.statusText === 'timeout') {
dispatch(chartUpdateTimeout(err.statusText, timeout, key));
} else if (err.statusText !== 'abort') {
dispatch(chartUpdateFailed(err.responseJSON, key));
let errObject;
if (err.responseJSON) {
errObject = err.responseJSON;
} else if (err.stack) {
errObject = {
error: 'Unexpected error: ' + err.description,
stacktrace: err.stack,
};
} else {
errObject = {
error: 'Unexpected error.',
};
}
dispatch(chartUpdateFailed(errObject, key));
}
});
const annotationLayers = formData.annotation_layers || [];
Expand Down
19 changes: 19 additions & 0 deletions superset/assets/javascripts/explore/stores/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1389,6 +1389,7 @@ export const controls = {
mapbox_style: {
type: 'SelectControl',
label: t('Map Style'),
clearable: false,
renderTrigger: true,
choices: [
['mapbox://styles/mapbox/streets-v9', 'Streets'],
Expand Down Expand Up @@ -1816,5 +1817,23 @@ export const controls = {
and returns a similarly shaped object. {sandboxedEvalInfo}
</p>),
},

deck_slices: {
type: 'SelectAsyncControl',
multi: true,
label: t('deck.gl charts'),
validators: [v.nonEmpty],
default: [],
description: t('Pick a set of deck.gl charts to layer on top of one another'),
dataEndpoint: '/sliceasync/api/read?_flt_0_viz_type=deck_',
placeholder: t('Select charts'),
onAsyncErrorMessage: t('Error while fetching charts'),
mutator: (data) => {
if (!data || !data.result) {
return [];
}
return data.result.map(o => ({ value: o.id, label: o.slice_name }));
},
},
};
export default controls;
17 changes: 16 additions & 1 deletion superset/assets/javascripts/explore/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,21 @@ export const visTypes = {
},
},

deck_multi: {
label: t('Deck.gl - Multiple Layers'),
requiresTime: true,
controlPanelSections: [
{
label: t('Map'),
expanded: true,
controlSetRows: [
['mapbox_style', 'viewport'],
['deck_slices', null],
],
},
],
},

deck_hex: {
label: t('Deck.gl - Hexagons'),
requiresTime: true,
Expand Down Expand Up @@ -398,7 +413,7 @@ export const visTypes = {
},

deck_path: {
label: t('Deck.gl - Grid'),
label: t('Deck.gl - Paths'),
requiresTime: true,
controlPanelSections: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { PathLayer } from 'deck.gl';

import DeckGLContainer from './DeckGLContainer';
import layerGenerators from './layers';

function deckPath(slice, payload, setControlValue) {
export default function deckglFactory(slice, payload, setControlValue) {
const fd = slice.formData;
const c = fd.color_picker;
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
const data = payload.data.paths.map(path => ({
path,
width: fd.line_width,
color: fixedColor,
}));

const layer = new PathLayer({
id: `path-layer-${slice.containerId}`,
data,
rounded: true,
widthScale: 1,
});
const layer = layerGenerators[fd.viz_type](fd, payload);
const viewport = {
...fd.viewport,
width: slice.width(),
Expand All @@ -36,4 +23,3 @@ function deckPath(slice, payload, setControlValue) {
document.getElementById(slice.containerId),
);
}
module.exports = deckPath;
43 changes: 0 additions & 43 deletions superset/assets/visualizations/deckgl/grid.jsx

This file was deleted.

43 changes: 0 additions & 43 deletions superset/assets/visualizations/deckgl/hex.jsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { GeoJsonLayer } from 'deck.gl';
import { hexToRGB } from '../../javascripts/modules/colors';
import { hexToRGB } from '../../../javascripts/modules/colors';

import DeckGLContainer from './DeckGLContainer';

const propertyMap = {
fillColor: 'fillColor',
Expand All @@ -26,8 +23,8 @@ const convertGeoJsonColorProps = (p, colors) => {
};
};

function DeckGeoJsonLayer(slice, payload, setControlValue) {
const fd = slice.formData;
export default function geoJsonLayer(formData, payload) {
const fd = formData;
const fc = fd.fill_color_picker;
const sc = fd.stroke_color_picker;
const data = payload.data.geojson.features.map(d => ({
Expand All @@ -39,29 +36,12 @@ function DeckGeoJsonLayer(slice, payload, setControlValue) {
}),
}));

const layer = new GeoJsonLayer({
id: 'geojson-layer',
return new GeoJsonLayer({
id: `path-layer-${fd.slice_id}`,
data,
filled: true,
stroked: false,
extruded: true,
pointRadiusScale: fd.point_radius_scale,
});

const viewport = {
...fd.viewport,
width: slice.width(),
height: slice.height(),
};
ReactDOM.render(
<DeckGLContainer
mapboxApiAccessToken={payload.data.mapboxApiKey}
viewport={viewport}
layers={[layer]}
mapStyle={fd.mapbox_style}
setControlValue={setControlValue}
/>,
document.getElementById(slice.containerId),
);
}
module.exports = DeckGeoJsonLayer;
23 changes: 23 additions & 0 deletions superset/assets/visualizations/deckgl/layers/grid.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { GridLayer } from 'deck.gl';

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

return new GridLayer({
id: `grid-layer-${fd.slice_id}`,
data,
pickable: true,
cellSize: fd.grid_size,
minColor: [0, 0, 0, 0],
extruded: fd.extruded,
maxColor: [c.r, c.g, c.b, 255 * c.a],
outline: false,
getElevationValue: points => points.reduce((sum, point) => sum + point.weight, 0),
getColorValue: points => points.reduce((sum, point) => sum + point.weight, 0),
});
}
23 changes: 23 additions & 0 deletions superset/assets/visualizations/deckgl/layers/hex.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { HexagonLayer } from 'deck.gl';

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

return new HexagonLayer({
id: `hex-layer-${fd.slice_id}`,
data,
pickable: true,
radius: fd.grid_size,
minColor: [0, 0, 0, 0],
extruded: fd.extruded,
maxColor: [c.r, c.g, c.b, 255 * c.a],
outline: false,
getElevationValue: points => points.reduce((sum, point) => sum + point.weight, 0),
getColorValue: points => points.reduce((sum, point) => sum + point.weight, 0),
});
}
17 changes: 17 additions & 0 deletions superset/assets/visualizations/deckgl/layers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint camelcase: 0 */
import deck_grid from './grid';
import deck_screengrid from './screengrid';
import deck_path from './path';
import deck_hex from './hex';
import deck_scatter from './scatter';
import deck_geojson from './geojson';

const layerGenerators = {
deck_grid,
deck_screengrid,
deck_path,
deck_hex,
deck_scatter,
deck_geojson,
};
export default layerGenerators;
19 changes: 19 additions & 0 deletions superset/assets/visualizations/deckgl/layers/path.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { PathLayer } from 'deck.gl';

export default function getLayer(formData, payload) {
const fd = formData;
const c = fd.color_picker;
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
const data = payload.data.paths.map(path => ({
path,
width: fd.line_width,
color: fixedColor,
}));

return new PathLayer({
id: `path-layer-${fd.slice_id}`,
data,
rounded: true,
widthScale: 1,
});
}
35 changes: 35 additions & 0 deletions superset/assets/visualizations/deckgl/layers/scatter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ScatterplotLayer } from 'deck.gl';

import { getColorFromScheme, hexToRGB } from '../../../javascripts/modules/colors';
import { unitToRadius } from '../../../javascripts/modules/geo';

export default function getLayer(formData, payload) {
const fd = formData;
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const fixedColor = [c.r, c.g, c.b, 255 * c.a];

const data = payload.data.features.map((d) => {
let radius = unitToRadius(fd.point_unit, d.radius) || 10;
if (fd.multiplier) {
radius *= fd.multiplier;
}
let color;
if (fd.dimension) {
color = hexToRGB(getColorFromScheme(d.cat_color, fd.color_scheme), c.a * 255);
} else {
color = fixedColor;
}
return {
...d,
radius,
color,
};
});
return new ScatterplotLayer({
id: `scatter-layer-${fd.slice_id}`,
data,
pickable: true,
fp64: true,
outline: false,
});
}
Loading

0 comments on commit c8e2ae6

Please sign in to comment.