[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:
Maxime Beauchemin 2018-08-28 11:09:10 -07:00 committed by Beto Dealmeida
parent f0beb3ab80
commit 60ecd72aac
18 changed files with 343 additions and 119 deletions

View File

@ -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",

View File

@ -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}
>

View File

@ -0,0 +1,8 @@
.BootstrapSliderWrapper .slider-selection {
background: #efefef;
}
.BootstrapSliderWrapper .slider-handle {
background: #b3b3b3;
}

View File

@ -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>
);
}

View File

@ -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]() {

View File

@ -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;

View File

@ -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,

View File

@ -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: {

View File

@ -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: {

View File

@ -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;
};

View File

@ -5,14 +5,6 @@
margin: 0;
}
.slider-selection {
background: #efefef;
}
.slider-handle {
background: #b3b3b3;
}
.slider.slider-horizontal {
width: 100% !important;
}

View File

@ -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}

View File

@ -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 {

View File

@ -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,

View File

@ -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}

View File

@ -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"

View File

@ -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

View File

@ -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):