mirror of https://github.com/apache/superset.git
[deck polygon] add support for geohash (#5712)
* [deck polygon] add support for geohash + improvements: * added autozoom support * support for metric & aggregations (only aggregates if metric is picked) * fixed stroke * fixed opacity * introduced a SliderControl * addressing comments, fixing build * Addressing comments
This commit is contained in:
parent
f0beb3ab80
commit
60ecd72aac
|
@ -61,7 +61,7 @@
|
|||
"d3-tip": "^0.9.1",
|
||||
"datamaps": "^0.5.8",
|
||||
"datatables.net-bs": "^1.10.15",
|
||||
"deck.gl": "^5.1.4",
|
||||
"deck.gl": "^5.3.4",
|
||||
"distributions": "^1.0.0",
|
||||
"dnd-core": "^2.6.0",
|
||||
"dompurify": "^1.0.3",
|
||||
|
@ -72,7 +72,6 @@
|
|||
"jed": "^1.1.1",
|
||||
"jquery": "3.1.1",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"luma.gl": "^5.1.4",
|
||||
"mapbox-gl": "^0.45.0",
|
||||
"mathjs": "^3.20.2",
|
||||
"moment": "^2.20.1",
|
||||
|
|
|
@ -189,7 +189,7 @@ class Chart extends React.PureComponent {
|
|||
className="chart-tooltip"
|
||||
id="chart-tooltip"
|
||||
placement="right"
|
||||
positionTop={this.state.tooltip.y - 10}
|
||||
positionTop={this.state.tooltip.y + 30}
|
||||
positionLeft={this.state.tooltip.x + 30}
|
||||
arrowOffsetTop={10}
|
||||
>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
.BootstrapSliderWrapper .slider-selection {
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
.BootstrapSliderWrapper .slider-handle {
|
||||
background: #b3b3b3;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import ReactBootstrapSlider from 'react-bootstrap-slider';
|
||||
import 'bootstrap-slider/dist/css/bootstrap-slider.min.css';
|
||||
import './BootstrapSliderWrapper.css';
|
||||
|
||||
export default function BootstrapSliderWrapper(props) {
|
||||
return (
|
||||
<span className="BootstrapSliderWrapper">
|
||||
<ReactBootstrapSlider {...props} />
|
||||
</span>
|
||||
);
|
||||
}
|
|
@ -95,46 +95,34 @@ export default function dashboardStateReducer(state = {}, action) {
|
|||
let filters = state.filters;
|
||||
const { chart, col, vals: nextVals, merge, refresh } = action;
|
||||
const sliceId = chart.id;
|
||||
const filterKeys = [
|
||||
'__time_range',
|
||||
'__time_col',
|
||||
'__time_grain',
|
||||
'__time_origin',
|
||||
'__granularity',
|
||||
];
|
||||
if (
|
||||
filterKeys.indexOf(col) >= 0 ||
|
||||
action.chart.formData.groupby.indexOf(col) !== -1
|
||||
) {
|
||||
let newFilter = {};
|
||||
if (!(sliceId in filters)) {
|
||||
// if no filters existed for the slice, set them
|
||||
newFilter = { [col]: nextVals };
|
||||
} else if ((filters[sliceId] && !(col in filters[sliceId])) || !merge) {
|
||||
// If no filters exist for this column, or we are overwriting them
|
||||
newFilter = { ...filters[sliceId], [col]: nextVals };
|
||||
} else if (filters[sliceId][col] instanceof Array) {
|
||||
newFilter[col] = [...filters[sliceId][col], ...nextVals];
|
||||
} else {
|
||||
newFilter[col] = [filters[sliceId][col], ...nextVals];
|
||||
}
|
||||
filters = { ...filters, [sliceId]: newFilter };
|
||||
let newFilter = {};
|
||||
if (!(sliceId in filters)) {
|
||||
// if no filters existed for the slice, set them
|
||||
newFilter = { [col]: nextVals };
|
||||
} else if ((filters[sliceId] && !(col in filters[sliceId])) || !merge) {
|
||||
// If no filters exist for this column, or we are overwriting them
|
||||
newFilter = { ...filters[sliceId], [col]: nextVals };
|
||||
} else if (filters[sliceId][col] instanceof Array) {
|
||||
newFilter[col] = [...filters[sliceId][col], ...nextVals];
|
||||
} else {
|
||||
newFilter[col] = [filters[sliceId][col], ...nextVals];
|
||||
}
|
||||
filters = { ...filters, [sliceId]: newFilter };
|
||||
|
||||
// remove any empty filters so they don't pollute the logs
|
||||
Object.keys(filters).forEach(chartId => {
|
||||
Object.keys(filters[chartId]).forEach(column => {
|
||||
if (
|
||||
!filters[chartId][column] ||
|
||||
filters[chartId][column].length === 0
|
||||
) {
|
||||
delete filters[chartId][column];
|
||||
}
|
||||
});
|
||||
if (Object.keys(filters[chartId]).length === 0) {
|
||||
delete filters[chartId];
|
||||
// remove any empty filters so they don't pollute the logs
|
||||
Object.keys(filters).forEach(chartId => {
|
||||
Object.keys(filters[chartId]).forEach(column => {
|
||||
if (
|
||||
!filters[chartId][column] ||
|
||||
filters[chartId][column].length === 0
|
||||
) {
|
||||
delete filters[chartId][column];
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Object.keys(filters[chartId]).length === 0) {
|
||||
delete filters[chartId];
|
||||
}
|
||||
});
|
||||
return { ...state, filters, refresh };
|
||||
},
|
||||
[SET_UNSAVED_CHANGES]() {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import BootstrapSliderWrapper from '../../../components/BootstrapSliderWrapper';
|
||||
import ControlHeader from '../ControlHeader';
|
||||
|
||||
const propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
};
|
||||
|
||||
export default function SliderControl(props) {
|
||||
// This wouldn't be necessary but might as well
|
||||
return (
|
||||
<div>
|
||||
<ControlHeader {...props} />
|
||||
<BootstrapSliderWrapper
|
||||
{...props}
|
||||
change={(obj) => {
|
||||
props.onChange(obj.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SliderControl.propTypes = propTypes;
|
||||
SliderControl.defaultProps = defaultProps;
|
|
@ -10,6 +10,7 @@ import FixedOrMetricControl from './FixedOrMetricControl';
|
|||
import HiddenControl from './HiddenControl';
|
||||
import SelectAsyncControl from './SelectAsyncControl';
|
||||
import SelectControl from './SelectControl';
|
||||
import SliderControl from './SliderControl';
|
||||
import SpatialControl from './SpatialControl';
|
||||
import TextAreaControl from './TextAreaControl';
|
||||
import TextControl from './TextControl';
|
||||
|
@ -32,6 +33,7 @@ const controlMap = {
|
|||
HiddenControl,
|
||||
SelectAsyncControl,
|
||||
SelectControl,
|
||||
SliderControl,
|
||||
SpatialControl,
|
||||
TextAreaControl,
|
||||
TextControl,
|
||||
|
|
|
@ -1470,10 +1470,10 @@ export const controls = {
|
|||
|
||||
table_filter: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Table Filter'),
|
||||
label: t('Emit Filter Events'),
|
||||
renderTrigger: true,
|
||||
default: false,
|
||||
description: t('Whether to apply filter when table cell is clicked'),
|
||||
description: t('Whether to apply filter when items are clicked'),
|
||||
},
|
||||
|
||||
align_pn: {
|
||||
|
@ -1777,6 +1777,17 @@ export const controls = {
|
|||
'Between 0 and 1.'),
|
||||
},
|
||||
|
||||
opacity: {
|
||||
type: 'SliderControl',
|
||||
label: t('Opacity'),
|
||||
default: 80,
|
||||
step: 1,
|
||||
min: 0,
|
||||
max: 100,
|
||||
renderTrigger: true,
|
||||
description: t('Opacity, expects values between 0 and 100'),
|
||||
},
|
||||
|
||||
viewport: {
|
||||
type: 'ViewportControl',
|
||||
label: t('Viewport'),
|
||||
|
@ -2121,6 +2132,7 @@ export const controls = {
|
|||
choices: [
|
||||
['polyline', 'Polyline'],
|
||||
['json', 'JSON'],
|
||||
['geohash', 'geohash (square)'],
|
||||
],
|
||||
},
|
||||
|
||||
|
@ -2242,7 +2254,7 @@ export const controls = {
|
|||
label: t('Filled'),
|
||||
renderTrigger: true,
|
||||
description: t('Whether to fill the objects'),
|
||||
default: false,
|
||||
default: true,
|
||||
},
|
||||
|
||||
normalized: {
|
||||
|
|
|
@ -605,6 +605,14 @@ export const visTypes = {
|
|||
],
|
||||
},
|
||||
],
|
||||
controlOverrides: {
|
||||
line_type: {
|
||||
choices: [
|
||||
['polyline', 'Polyline'],
|
||||
['json', 'JSON'],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
deck_screengrid: {
|
||||
|
@ -703,25 +711,31 @@ export const visTypes = {
|
|||
label: t('Query'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['line_column', 'line_type'],
|
||||
['row_limit', 'filter_nulls'],
|
||||
['adhoc_filters'],
|
||||
['metric'],
|
||||
['row_limit', null],
|
||||
['line_column', 'line_type'],
|
||||
['reverse_long_lat', 'filter_nulls'],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Map'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['mapbox_style', 'viewport'],
|
||||
['reverse_long_lat', null],
|
||||
['autozoom', null],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Polygon Settings'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['fill_color_picker', 'stroke_color_picker'],
|
||||
['filled', 'stroked'],
|
||||
['extruded', null],
|
||||
['point_radius_scale', null],
|
||||
['line_width', null],
|
||||
['linear_color_scheme', 'opacity'],
|
||||
['table_filter', null],
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -734,6 +748,17 @@ export const visTypes = {
|
|||
],
|
||||
},
|
||||
],
|
||||
controlOverrides: {
|
||||
metric: {
|
||||
validators: [],
|
||||
},
|
||||
line_column: {
|
||||
label: t('Polygon Column'),
|
||||
},
|
||||
line_type: {
|
||||
label: t('Polygon Encoding'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
deck_arc: {
|
||||
|
|
|
@ -525,6 +525,16 @@ export const spectrums = {
|
|||
],
|
||||
};
|
||||
|
||||
export function hexToRGB(hex, alpha = 255) {
|
||||
if (!hex) {
|
||||
return [0, 0, 0, alpha];
|
||||
}
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return [r, g, b, alpha];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a color from a scheme specific palette (scheme)
|
||||
* The function cycles through the palette while memoizing labels
|
||||
|
@ -566,7 +576,7 @@ export const getColorFromScheme = (function () {
|
|||
};
|
||||
}());
|
||||
|
||||
export const colorScalerFactory = function (colors, data, accessor, extents) {
|
||||
export const colorScalerFactory = function (colors, data, accessor, extents, outputRGBA = false) {
|
||||
// Returns a linear scaler our of an array of color
|
||||
if (!Array.isArray(colors)) {
|
||||
/* eslint no-param-reassign: 0 */
|
||||
|
@ -581,15 +591,9 @@ export const colorScalerFactory = function (colors, data, accessor, extents) {
|
|||
}
|
||||
const chunkSize = (ext[1] - ext[0]) / (colors.length - 1);
|
||||
const points = colors.map((col, i) => ext[0] + (i * chunkSize));
|
||||
return d3.scale.linear().domain(points).range(colors).clamp(true);
|
||||
};
|
||||
|
||||
export function hexToRGB(hex, alpha = 255) {
|
||||
if (!hex) {
|
||||
return [0, 0, 0, alpha];
|
||||
const scaler = d3.scale.linear().domain(points).range(colors).clamp(true);
|
||||
if (outputRGBA) {
|
||||
return v => hexToRGB(scaler(v));
|
||||
}
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return [r, g, b, alpha];
|
||||
}
|
||||
return scaler;
|
||||
};
|
||||
|
|
|
@ -5,14 +5,6 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.slider-selection {
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
.slider-handle {
|
||||
background: #b3b3b3;
|
||||
}
|
||||
|
||||
.slider.slider-horizontal {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@ import { Row, Col } from 'react-bootstrap';
|
|||
|
||||
import Mousetrap from 'mousetrap';
|
||||
|
||||
import 'bootstrap-slider/dist/css/bootstrap-slider.min.css';
|
||||
import ReactBootstrapSlider from 'react-bootstrap-slider';
|
||||
import BootrapSliderWrapper from '../components/BootstrapSliderWrapper';
|
||||
import './PlaySlider.css';
|
||||
|
||||
import { t } from '../locales';
|
||||
|
@ -120,7 +119,7 @@ export default class PlaySlider extends React.PureComponent {
|
|||
<i className="fa fa-step-forward fa-lg slider-button " onClick={this.step} />
|
||||
</Col>
|
||||
<Col md={11} className="padded">
|
||||
<ReactBootstrapSlider
|
||||
<BootrapSliderWrapper
|
||||
value={range ? values : values[0]}
|
||||
range={range}
|
||||
formatter={this.formatter}
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'mapbox-gl/dist/mapbox-gl.css';
|
|||
const propTypes = {
|
||||
viewport: PropTypes.object.isRequired,
|
||||
layers: PropTypes.array.isRequired,
|
||||
setControlValue: PropTypes.func.isRequired,
|
||||
setControlValue: PropTypes.func,
|
||||
mapStyle: PropTypes.string,
|
||||
mapboxApiAccessToken: PropTypes.string.isRequired,
|
||||
onViewportChange: PropTypes.func,
|
||||
|
@ -15,6 +15,7 @@ const propTypes = {
|
|||
const defaultProps = {
|
||||
mapStyle: 'light',
|
||||
onViewportChange: () => {},
|
||||
setControlValue: () => {},
|
||||
};
|
||||
|
||||
export default class DeckGLContainer extends React.Component {
|
||||
|
|
|
@ -34,12 +34,18 @@ export function fitViewport(viewport, points, padding = 10) {
|
|||
export function commonLayerProps(formData, slice) {
|
||||
const fd = formData;
|
||||
let onHover;
|
||||
let tooltipContentGenerator;
|
||||
if (fd.js_tooltip) {
|
||||
const jsTooltip = sandboxedEval(fd.js_tooltip);
|
||||
const unsanitizedTooltipGenerator = sandboxedEval(fd.js_tooltip);
|
||||
tooltipContentGenerator = o => dompurify.sanitize(unsanitizedTooltipGenerator(o));
|
||||
} else if (fd.line_column && fd.line_type === 'geohash') {
|
||||
tooltipContentGenerator = o => `${fd.line_column}: ${o.object[fd.line_column]}`;
|
||||
}
|
||||
if (tooltipContentGenerator) {
|
||||
onHover = (o) => {
|
||||
if (o.picked) {
|
||||
slice.setTooltip({
|
||||
content: dompurify.sanitize(jsTooltip(o)),
|
||||
content: tooltipContentGenerator(o),
|
||||
x: o.x,
|
||||
y: o.y,
|
||||
});
|
||||
|
@ -54,6 +60,8 @@ export function commonLayerProps(formData, slice) {
|
|||
const href = sandboxedEval(fd.js_onclick_href)(o);
|
||||
window.open(href);
|
||||
};
|
||||
} else if (fd.table_filter && fd.line_type === 'geohash') {
|
||||
onClick = o => slice.addFilter(fd.line_column, [o.object[fd.line_column]], false);
|
||||
}
|
||||
return {
|
||||
onClick,
|
||||
|
|
|
@ -2,19 +2,36 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { PolygonLayer } from 'deck.gl';
|
||||
import _ from 'underscore';
|
||||
import d3 from 'd3';
|
||||
|
||||
import DeckGLContainer from './../DeckGLContainer';
|
||||
|
||||
import * as common from './common';
|
||||
import { colorScalerFactory } from '../../../modules/colors';
|
||||
import sandboxedEval from '../../../modules/sandbox';
|
||||
|
||||
function getPoints(features) {
|
||||
return _.flatten(features.map(d => d.polygon), true);
|
||||
}
|
||||
|
||||
function getLayer(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],
|
||||
}));
|
||||
const sc = fd.stroke_color_picker;
|
||||
let data = [...payload.data.features];
|
||||
const mainMetric = payload.data.metricLabels.length ? payload.data.metricLabels[0] : null;
|
||||
|
||||
let colorScaler;
|
||||
if (mainMetric) {
|
||||
const ext = d3.extent(data, d => d[mainMetric]);
|
||||
const scaler = colorScalerFactory(fd.linear_color_scheme, null, null, ext, true);
|
||||
colorScaler = (d) => {
|
||||
const c = scaler(d[mainMetric]);
|
||||
c[3] = (fd.opacity / 100.0) * 255;
|
||||
return c;
|
||||
};
|
||||
}
|
||||
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
|
@ -26,19 +43,29 @@ function getLayer(formData, payload, slice) {
|
|||
id: `path-layer-${fd.slice_id}`,
|
||||
data,
|
||||
filled: fd.filled,
|
||||
stroked: fd.stoked,
|
||||
stroked: fd.stroked,
|
||||
getFillColor: colorScaler || [fc.r, fc.g, fc.b, 255 * fc.a],
|
||||
getLineColor: [sc.r, sc.g, sc.b, 255 * sc.a],
|
||||
getLineWidth: fd.line_width,
|
||||
extruded: fd.extruded,
|
||||
fp64: true,
|
||||
...common.commonLayerProps(fd, slice),
|
||||
});
|
||||
}
|
||||
|
||||
function deckPolygon(slice, payload, setControlValue) {
|
||||
const layer = getLayer(slice.formData, payload, slice);
|
||||
const viewport = {
|
||||
const fd = slice.formData;
|
||||
let viewport = {
|
||||
...slice.formData.viewport,
|
||||
width: slice.width(),
|
||||
height: slice.height(),
|
||||
};
|
||||
|
||||
if (fd.autozoom) {
|
||||
viewport = common.fitViewport(viewport, getPoints(payload.data.features));
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<DeckGLContainer
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
|
|
|
@ -230,9 +230,9 @@
|
|||
d3-array "^1.2.0"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
"@deck.gl/core@^5.3.1":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/core/-/core-5.3.1.tgz#acfc1e5fefd3b12e9142419b0aeb77c07885626c"
|
||||
"@deck.gl/core@^5.3.3":
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/core/-/core-5.3.3.tgz#a13c07e5fa3e22297fd450d6da8ab9aac334b1f0"
|
||||
dependencies:
|
||||
luma.gl "^5.3.0"
|
||||
math.gl "^1.2.1"
|
||||
|
@ -241,19 +241,19 @@
|
|||
seer "^0.2.4"
|
||||
viewport-mercator-project "^5.1.0"
|
||||
|
||||
"@deck.gl/layers@^5.3.2":
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/layers/-/layers-5.3.2.tgz#c76b9a7890305a5d6a0fdd56bd0d0d68d4046f6f"
|
||||
"@deck.gl/layers@^5.3.4":
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/layers/-/layers-5.3.4.tgz#ab3de1bf8bb68d67772642acbb4e0f87f4f11300"
|
||||
dependencies:
|
||||
"@deck.gl/core" "^5.3.1"
|
||||
"@deck.gl/core" "^5.3.3"
|
||||
d3-hexbin "^0.2.1"
|
||||
earcut "^2.0.6"
|
||||
|
||||
"@deck.gl/react@^5.3.1":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/react/-/react-5.3.1.tgz#0c16fac59061924eb3509dea06c837bcef8044f2"
|
||||
"@deck.gl/react@^5.3.3":
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/react/-/react-5.3.3.tgz#e7352934f6742d3ce672a394cbff312aab5ccaa0"
|
||||
dependencies:
|
||||
"@deck.gl/core" "^5.3.1"
|
||||
"@deck.gl/core" "^5.3.3"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
"@mapbox/geojson-area@0.2.2":
|
||||
|
@ -830,6 +830,12 @@ acorn@^5.0.0, acorn@^5.5.0, acorn@^5.6.2:
|
|||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
|
||||
|
||||
add-dom-event-listener@1.x:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.0.2.tgz#8faed2c41008721cf111da1d30d995b85be42bed"
|
||||
dependencies:
|
||||
object-assign "4.x"
|
||||
|
||||
ajv-keywords@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
|
||||
|
@ -1951,19 +1957,19 @@ babel-register@^6.24.1, babel-register@^6.26.0, babel-register@^6.9.0:
|
|||
mkdirp "^0.5.1"
|
||||
source-map-support "^0.4.15"
|
||||
|
||||
babel-runtime@^5.6.18:
|
||||
version "5.8.38"
|
||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19"
|
||||
dependencies:
|
||||
core-js "^1.0.0"
|
||||
|
||||
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
|
||||
babel-runtime@6.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
|
||||
dependencies:
|
||||
core-js "^2.4.0"
|
||||
regenerator-runtime "^0.11.0"
|
||||
|
||||
babel-runtime@^5.6.18:
|
||||
version "5.8.38"
|
||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19"
|
||||
dependencies:
|
||||
core-js "^1.0.0"
|
||||
|
||||
babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
|
||||
|
@ -2578,7 +2584,7 @@ class-utils@^0.3.5:
|
|||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5:
|
||||
classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5, classnames@^2.2.6:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||
|
||||
|
@ -2818,10 +2824,20 @@ complex.js@2.0.4:
|
|||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.4.tgz#d8e7cfb9652d1e853e723386421c1a0ca7a48373"
|
||||
|
||||
component-classes@^1.2.5:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/component-classes/-/component-classes-1.2.6.tgz#c642394c3618a4d8b0b8919efccbbd930e5cd691"
|
||||
dependencies:
|
||||
component-indexof "0.0.3"
|
||||
|
||||
component-emitter@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
|
||||
|
||||
component-indexof@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/component-indexof/-/component-indexof-0.0.3.tgz#11d091312239eb8f32c8f25ae9cb002ffe8d3c24"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
@ -3012,6 +3028,13 @@ crypto-random-string@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
||||
|
||||
css-animation@^1.3.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/css-animation/-/css-animation-1.4.1.tgz#5b8813125de0fbbbb0bbe1b472ae84221469b7a8"
|
||||
dependencies:
|
||||
babel-runtime "6.x"
|
||||
component-classes "^1.2.5"
|
||||
|
||||
css-color-names@0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
||||
|
@ -3397,13 +3420,13 @@ decimal.js@9.0.1:
|
|||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-9.0.1.tgz#1cc8b228177da7ab6498c1cc06eb130a290e6e1e"
|
||||
|
||||
deck.gl@^5.1.4:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/deck.gl/-/deck.gl-5.3.2.tgz#2297d820fb8fb02eab95ac6a03261a689500bc11"
|
||||
deck.gl@^5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/deck.gl/-/deck.gl-5.3.4.tgz#35e5a7087ef0d8ca7811d06a721ea289edbe7c24"
|
||||
dependencies:
|
||||
"@deck.gl/core" "^5.3.1"
|
||||
"@deck.gl/layers" "^5.3.2"
|
||||
"@deck.gl/react" "^5.3.1"
|
||||
"@deck.gl/core" "^5.3.3"
|
||||
"@deck.gl/layers" "^5.3.4"
|
||||
"@deck.gl/react" "^5.3.3"
|
||||
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.0"
|
||||
|
@ -3586,6 +3609,10 @@ doctrine@^2.1.0:
|
|||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-align@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.8.0.tgz#c0e89b5b674c6e836cd248c52c2992135f093654"
|
||||
|
||||
"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.2.0, dom-helpers@^3.2.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
|
||||
|
@ -6384,7 +6411,7 @@ lodash.isplainobject@^4.0.6:
|
|||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
|
||||
lodash.keys@^3.0.0:
|
||||
lodash.keys@^3.0.0, lodash.keys@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
||||
dependencies:
|
||||
|
@ -6518,7 +6545,7 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
|
|||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
luma.gl@^5.1.4, luma.gl@^5.3.0:
|
||||
luma.gl@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/luma.gl/-/luma.gl-5.3.0.tgz#a93b2f34489d8230eb6d8c871335800d9b83ee67"
|
||||
dependencies:
|
||||
|
@ -7378,14 +7405,14 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2:
|
|||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||
|
||||
object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
object-assign@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
|
||||
|
||||
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
object-copy@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
|
||||
|
@ -8329,6 +8356,23 @@ randomfill@^1.0.3:
|
|||
randombytes "^2.0.5"
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
rc-align@^2.4.0:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-2.4.3.tgz#b9b3c2a6d68adae71a8e1d041cd5e3b2a655f99a"
|
||||
dependencies:
|
||||
babel-runtime "^6.26.0"
|
||||
dom-align "^1.7.0"
|
||||
prop-types "^15.5.8"
|
||||
rc-util "^4.0.4"
|
||||
|
||||
rc-animate@2.x:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.4.4.tgz#a05a784c747beef140d99ff52b6117711bef4b1e"
|
||||
dependencies:
|
||||
babel-runtime "6.x"
|
||||
css-animation "^1.3.2"
|
||||
prop-types "15.x"
|
||||
|
||||
rc-config-loader@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-config-loader/-/rc-config-loader-2.0.1.tgz#8c8452f59bdd10d448a67762dccf7c1b247db860"
|
||||
|
@ -8341,6 +8385,46 @@ rc-config-loader@^2.0.1:
|
|||
path-exists "^2.1.0"
|
||||
require-from-string "^2.0.1"
|
||||
|
||||
rc-slider@^8.6.1:
|
||||
version "8.6.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-8.6.1.tgz#ee5e0380dbdf4b5de6955a265b0d4ff6196405d1"
|
||||
dependencies:
|
||||
babel-runtime "6.x"
|
||||
classnames "^2.2.5"
|
||||
prop-types "^15.5.4"
|
||||
rc-tooltip "^3.7.0"
|
||||
rc-util "^4.0.4"
|
||||
shallowequal "^1.0.1"
|
||||
warning "^3.0.0"
|
||||
|
||||
rc-tooltip@^3.7.0:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-3.7.2.tgz#3698656d4bacd51b72d9e327bed15d1d5a9f1b27"
|
||||
dependencies:
|
||||
babel-runtime "6.x"
|
||||
prop-types "^15.5.8"
|
||||
rc-trigger "^2.2.2"
|
||||
|
||||
rc-trigger@^2.2.2:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.5.4.tgz#9088a24ba5a811b254f742f004e38a9e2f8843fb"
|
||||
dependencies:
|
||||
babel-runtime "6.x"
|
||||
classnames "^2.2.6"
|
||||
prop-types "15.x"
|
||||
rc-align "^2.4.0"
|
||||
rc-animate "2.x"
|
||||
rc-util "^4.4.0"
|
||||
|
||||
rc-util@^4.0.4, rc-util@^4.4.0:
|
||||
version "4.5.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.5.1.tgz#0e435057174c024901c7600ba8903dd03da3ab39"
|
||||
dependencies:
|
||||
add-dom-event-listener "1.x"
|
||||
babel-runtime "6.x"
|
||||
prop-types "^15.5.10"
|
||||
shallowequal "^0.2.2"
|
||||
|
||||
rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
|
@ -9469,6 +9553,16 @@ shallow-copy@~0.0.1:
|
|||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170"
|
||||
|
||||
shallowequal@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e"
|
||||
dependencies:
|
||||
lodash.keys "^3.1.2"
|
||||
|
||||
shallowequal@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
||||
|
||||
shapefile@0.3:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/shapefile/-/shapefile-0.3.1.tgz#9bb9a429bd6086a0cfb03962d14cfdf420ffba12"
|
||||
|
|
|
@ -11,7 +11,6 @@ import os
|
|||
import re
|
||||
import time
|
||||
import traceback
|
||||
from urllib import parse
|
||||
|
||||
from flask import (
|
||||
flash, g, Markup, redirect, render_template, request, Response, url_for,
|
||||
|
@ -25,6 +24,7 @@ from flask_babel import lazy_gettext as _
|
|||
import pandas as pd
|
||||
import simplejson as json
|
||||
from six import text_type
|
||||
from six.moves.urllib import parse
|
||||
import sqlalchemy as sqla
|
||||
from sqlalchemy import and_, create_engine, or_, update
|
||||
from sqlalchemy.engine.url import make_url
|
||||
|
|
|
@ -10,7 +10,7 @@ from __future__ import division
|
|||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, OrderedDict
|
||||
import copy
|
||||
from datetime import datetime, timedelta
|
||||
import hashlib
|
||||
|
@ -100,7 +100,7 @@ class BaseViz(object):
|
|||
self.process_metrics()
|
||||
|
||||
def process_metrics(self):
|
||||
self.metric_dict = {}
|
||||
self.metric_dict = OrderedDict()
|
||||
fd = self.form_data
|
||||
for mkey in METRIC_KEYS:
|
||||
val = fd.get(mkey)
|
||||
|
@ -2208,6 +2208,7 @@ class BaseDeckGLViz(BaseViz):
|
|||
return {
|
||||
'features': features,
|
||||
'mapboxApiKey': config.get('MAPBOX_API_KEY'),
|
||||
'metricLabels': self.metric_labels,
|
||||
}
|
||||
|
||||
def get_properties(self, d):
|
||||
|
@ -2304,6 +2305,17 @@ class DeckGrid(BaseDeckGLViz):
|
|||
return super(DeckGrid, self).get_data(df)
|
||||
|
||||
|
||||
def geohash_to_json(geohash_code):
|
||||
p = geohash.bbox(geohash_code)
|
||||
return [
|
||||
[p.get('w'), p.get('n')],
|
||||
[p.get('e'), p.get('n')],
|
||||
[p.get('e'), p.get('s')],
|
||||
[p.get('w'), p.get('s')],
|
||||
[p.get('w'), p.get('n')],
|
||||
]
|
||||
|
||||
|
||||
class DeckPathViz(BaseDeckGLViz):
|
||||
|
||||
"""deck.gl's PathLayer"""
|
||||
|
@ -2314,26 +2326,32 @@ class DeckPathViz(BaseDeckGLViz):
|
|||
deser_map = {
|
||||
'json': json.loads,
|
||||
'polyline': polyline.decode,
|
||||
'geohash': geohash_to_json,
|
||||
}
|
||||
|
||||
def query_obj(self):
|
||||
d = super(DeckPathViz, self).query_obj()
|
||||
line_col = self.form_data.get('line_column')
|
||||
if d['metrics']:
|
||||
self.has_metrics = True
|
||||
d['groupby'].append(line_col)
|
||||
else:
|
||||
self.has_metrics = False
|
||||
d['columns'].append(line_col)
|
||||
return d
|
||||
|
||||
def get_properties(self, d):
|
||||
fd = self.form_data
|
||||
deser = self.deser_map[fd.get('line_type')]
|
||||
path = deser(d[fd.get('line_column')])
|
||||
line_type = fd.get('line_type')
|
||||
deser = self.deser_map[line_type]
|
||||
line_column = fd.get('line_column')
|
||||
path = deser(d[line_column])
|
||||
if fd.get('reverse_long_lat'):
|
||||
path = (path[1], path[0])
|
||||
return {
|
||||
self.deck_viz_key: path,
|
||||
}
|
||||
path = [(o[1], o[0]) for o in path]
|
||||
d[self.deck_viz_key] = path
|
||||
if line_type != 'geohash':
|
||||
del d[line_column]
|
||||
return d
|
||||
|
||||
|
||||
class DeckPolygon(DeckPathViz):
|
||||
|
|
Loading…
Reference in New Issue