diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/AnimatableDeckGLContainer.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/AnimatableDeckGLContainer.jsx
index fe2c7165db7c..83574cd79f94 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/AnimatableDeckGLContainer.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/AnimatableDeckGLContainer.jsx
@@ -22,7 +22,7 @@ import PropTypes from 'prop-types';
import DeckGLContainer from './DeckGLContainer';
import PlaySlider from '../PlaySlider';
-const PLAYSLIDER_HEIGHT = 20; // px
+const PLAYSLIDER_HEIGHT = 20; // px
const propTypes = {
getLayers: PropTypes.func.isRequired,
@@ -55,12 +55,14 @@ export default class AnimatableDeckGLContainer extends React.Component {
super(props);
this.onViewportChange = this.onViewportChange.bind(this);
}
+
onViewportChange(viewport) {
const originalViewport = this.props.disabled
? { ...viewport }
: { ...viewport, height: viewport.height + PLAYSLIDER_HEIGHT };
this.props.onViewportChange(originalViewport);
}
+
render() {
const {
start,
@@ -95,16 +97,16 @@ export default class AnimatableDeckGLContainer extends React.Component {
mapboxApiAccessToken={mapboxApiAccessToken}
onViewportChange={this.onViewportChange}
/>
- {!disabled &&
-
- }
+ {!disabled && (
+
+ )}
{children}
);
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/CategoricalDeckGLContainer.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/CategoricalDeckGLContainer.jsx
index 2a3e7b3c717a..7ba534879fb5 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/CategoricalDeckGLContainer.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/CategoricalDeckGLContainer.jsx
@@ -22,10 +22,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import { CategoricalColorNamespace } from '@superset-ui/color';
import AnimatableDeckGLContainer from './AnimatableDeckGLContainer';
-import Legend from '../Legend';
-import { hexToRGB } from '../../modules/colors';
-import { getPlaySliderParams } from '../../modules/time';
-import sandboxedEval from '../../modules/sandbox';
+import Legend from './Legend';
+import { hexToRGB } from './utils/colors';
+import { getPlaySliderParams } from './utils/time';
+import sandboxedEval from './utils/sandbox';
import { fitViewport } from './layers/common';
const { getScale } = CategoricalColorNamespace;
@@ -35,7 +35,7 @@ function getCategories(fd, data) {
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
const colorFn = getScale(fd.color_scheme);
const categories = {};
- data.forEach((d) => {
+ data.forEach(d => {
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
let color;
if (fd.dimension) {
@@ -46,6 +46,7 @@ function getCategories(fd, data) {
categories[d.cat_color] = { color, enabled: true };
}
});
+
return categories;
}
@@ -78,21 +79,23 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
this.toggleCategory = this.toggleCategory.bind(this);
this.showSingleCategory = this.showSingleCategory.bind(this);
}
+
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.payload.form_data !== this.state.formData) {
this.setState({ ...this.getStateFromProps(nextProps) });
}
}
+
onValuesChange(values) {
this.setState({
- values: Array.isArray(values)
- ? values
- : [values, values + this.state.getStep(values)],
+ values: Array.isArray(values) ? values : [values, values + this.state.getStep(values)],
});
}
+
onViewportChange(viewport) {
this.setState({ viewport });
}
+
getStateFromProps(props, state) {
const features = props.payload.data.features || [];
const timestamps = features.map(f => f.__timestamp);
@@ -107,19 +110,10 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
// the granularity has to be read from the payload form_data, not the
// props formData which comes from the instantaneous controls state
- const granularity = (
- props.payload.form_data.time_grain_sqla ||
- props.payload.form_data.granularity ||
- 'P1D'
- );
+ const granularity =
+ props.payload.form_data.time_grain_sqla || props.payload.form_data.granularity || 'P1D';
- const {
- start,
- end,
- getStep,
- values,
- disabled,
- } = getPlaySliderParams(timestamps, granularity);
+ const { start, end, getStep, values, disabled } = getPlaySliderParams(timestamps, granularity);
const viewport = props.formData.autozoom
? fitViewport(props.viewport, props.getPoints(features))
@@ -138,17 +132,10 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
categories,
};
}
+
getLayers(values) {
- const {
- getLayer,
- payload,
- formData: fd,
- onAddFilter,
- setTooltip,
- } = this.props;
- let features = payload.data.features
- ? [...payload.data.features]
- : [];
+ const { getLayer, payload, formData: fd, onAddFilter, setTooltip } = this.props;
+ let features = payload.data.features ? [...payload.data.features] : [];
// Add colors from categories or fixed color
features = this.addColor(features, fd);
@@ -179,18 +166,23 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
return [getLayer(fd, filteredPayload, onAddFilter, setTooltip)];
}
+
addColor(data, fd) {
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const colorFn = getScale(fd.color_scheme);
- return data.map((d) => {
+
+ return data.map(d => {
let color;
if (fd.dimension) {
color = hexToRGB(colorFn(d.cat_color), c.a * 255);
+
return { ...d, color };
}
+
return d;
});
}
+
toggleCategory(category) {
const categoryState = this.state.categories[category];
const categories = {
@@ -204,17 +196,23 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
// if all categories are disabled, enable all -- similar to nvd3
if (Object.values(categories).every(v => !v.enabled)) {
/* eslint-disable no-param-reassign */
- Object.values(categories).forEach((v) => { v.enabled = true; });
+ Object.values(categories).forEach(v => {
+ v.enabled = true;
+ });
}
this.setState({ categories });
}
+
showSingleCategory(category) {
const categories = { ...this.state.categories };
/* eslint-disable no-param-reassign */
- Object.values(categories).forEach((v) => { v.enabled = false; });
+ Object.values(categories).forEach(v => {
+ v.enabled = false;
+ });
categories[category].enabled = true;
this.setState({ categories });
}
+
render() {
return (
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/DeckGLContainer.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/DeckGLContainer.jsx
index ff414bfc0ad9..702b5c7ac366 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/DeckGLContainer.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/DeckGLContainer.jsx
@@ -22,9 +22,9 @@ import MapGL from 'react-map-gl';
import DeckGL from 'deck.gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import { isEqual } from 'lodash';
-import '../stylesheets/deckgl.css';
+import './css/deckgl.css';
-const TICK = 2000; // milliseconds
+const TICK = 2000; // milliseconds
const propTypes = {
viewport: PropTypes.object.isRequired,
@@ -51,6 +51,7 @@ export default class DeckGLContainer extends React.Component {
timer: setInterval(this.tick, TICK),
};
}
+
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.viewport !== prevState.viewport) {
return {
@@ -58,11 +59,14 @@ export default class DeckGLContainer extends React.Component {
previousViewport: prevState.viewport,
};
}
+
return null;
}
+
componentWillUnmount() {
clearInterval(this.state.timer);
}
+
onViewportChange(viewport) {
const vp = Object.assign({}, viewport);
// delete vp.width;
@@ -72,6 +76,7 @@ export default class DeckGLContainer extends React.Component {
// this.setState(() => ({ viewport: newVp }));
this.props.onViewportChange(newVp);
}
+
tick() {
// Limiting updating viewport controls through Redux at most 1*sec
// Deep compare is needed as shallow equality doesn't work here, viewport object
@@ -85,15 +90,19 @@ export default class DeckGLContainer extends React.Component {
this.setState(() => ({ previousViewport: this.props.viewport }));
}
}
+
layers() {
// Support for layer factory
if (this.props.layers.some(l => typeof l === 'function')) {
- return this.props.layers.map(l => typeof l === 'function' ? l() : l);
+ return this.props.layers.map(l => (typeof l === 'function' ? l() : l));
}
+
return this.props.layers;
}
+
render() {
const { viewport } = this.props;
+
return (
-
+
);
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Legend.css b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Legend.css
new file mode 100644
index 000000000000..6b6345c0a002
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Legend.css
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+div.legend {
+ font-size: 90%;
+ position: absolute;
+ background: #fff;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
+ margin: 24px;
+ padding: 12px 20px;
+ outline: none;
+ overflow-y: scroll;
+ max-height: 200px;
+}
+
+ul.categories {
+ list-style: none;
+ padding-left: 0;
+ margin: 0;
+}
+
+ul.categories li a {
+ color: rgb(51, 51, 51);
+ text-decoration: none;
+}
+
+ul.categories li a span {
+ margin-right: 10px;
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Legend.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Legend.jsx
new file mode 100644
index 000000000000..355f632d34b9
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Legend.jsx
@@ -0,0 +1,105 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+import { formatNumber } from '@superset-ui/number-format';
+
+import './Legend.css';
+
+const categoryDelimiter = ' - ';
+
+const propTypes = {
+ categories: PropTypes.object,
+ toggleCategory: PropTypes.func,
+ showSingleCategory: PropTypes.func,
+ format: PropTypes.string,
+ position: PropTypes.oneOf([null, 'tl', 'tr', 'bl', 'br']),
+};
+
+const defaultProps = {
+ categories: {},
+ toggleCategory: () => {},
+ showSingleCategory: () => {},
+ format: null,
+ position: 'tr',
+};
+
+export default class Legend extends React.PureComponent {
+ format(value) {
+ if (!this.props.format) {
+ return value;
+ }
+
+ const numValue = parseFloat(value);
+ return formatNumber(this.props.format, numValue);
+
+ }
+
+ formatCategoryLabel(k) {
+ if (!this.props.format) {
+ return k;
+ }
+
+ if (k.includes(categoryDelimiter)) {
+ const values = k.split(categoryDelimiter);
+ return this.format(values[0]) + categoryDelimiter + this.format(values[1]);
+ }
+
+ return this.format(k);
+ }
+
+ render() {
+ if (Object.keys(this.props.categories).length === 0 || this.props.position === null) {
+ return null;
+ }
+
+ const categories = Object.entries(this.props.categories).map(([k, v]) => {
+ const style = { color: 'rgba(' + v.color.join(', ') + ')' };
+ const icon = v.enabled ? '\u25FC' : '\u25FB';
+ return (
+
+ this.props.toggleCategory(k)}
+ onDoubleClick={() => this.props.showSingleCategory(k)}
+ >
+ {icon} {this.formatCategoryLabel(k)}
+
+
+ );
+ });
+
+ const vertical = this.props.position.charAt(0) === 't' ? 'top' : 'bottom';
+ const horizontal = this.props.position.charAt(1) === 'r' ? 'right' : 'left';
+ const style = {
+ position: 'absolute',
+ [vertical]: '0px',
+ [horizontal]: '10px',
+ };
+
+ return (
+
+ );
+ }
+}
+
+Legend.propTypes = propTypes;
+Legend.defaultProps = defaultProps;
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/Multi.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/Multi.jsx
index 335ba3a7e02b..1206b0df99c0 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/Multi.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/Multi.jsx
@@ -66,7 +66,7 @@ class DeckMulti extends React.PureComponent {
loadLayers(formData, payload, viewport) {
this.setState({ subSlicesLayers: {}, viewport });
- payload.data.slices.forEach((subslice) => {
+ payload.data.slices.forEach(subslice => {
// Filters applied to multi_deck are passed down to underlying charts
// note that dashboard contextual information (filter_immune_slices and such) aren't
// taken into consideration here
@@ -84,8 +84,8 @@ class DeckMulti extends React.PureComponent {
};
SupersetClient.get({
- endpoint: getExploreLongUrl(subsliceCopy.form_data, 'json'),
- })
+ endpoint: getExploreLongUrl(subsliceCopy.form_data, 'json'),
+ })
.then(({ json }) => {
const layer = layerGenerators[subsliceCopy.form_data.viz_type](
subsliceCopy.form_data,
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/index.js
index c3cae628ac6c..290df741fa3a 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/index.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/index.js
@@ -22,17 +22,17 @@ import thumbnail from './images/thumbnail.png';
import transformProps from '../transformProps';
const metadata = new ChartMetadata({
- name: t('deck.gl Multiple Layers'),
- description: '',
credits: ['https://uber.github.io/deck.gl'],
+ description: '',
+ name: t('deck.gl Multiple Layers'),
thumbnail,
});
export default class MultiChartPlugin extends ChartPlugin {
constructor() {
super({
+ loadChart: () => import('./Multi'),
metadata,
- loadChart: () => import('./Multi.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/PlaySlider.css b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/PlaySlider.css
new file mode 100644
index 000000000000..0c21b3e8fe0f
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/PlaySlider.css
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+.play-slider {
+ display: flex;
+ height: 40px;
+ width: 100%;
+ margin: 0;
+}
+
+.play-slider-controls {
+ flex: 0 0 80px;
+ text-align: middle;
+}
+
+.play-slider-scrobbler {
+ flex: 1;
+}
+
+.slider.slider-horizontal {
+ width: 100% !important;
+}
+
+.slider-button {
+ color: #b3b3b3;
+ margin-right: 5px;
+}
+
+div.slider > div.tooltip.tooltip-main.top.in {
+ margin-left: 0 !important;
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/PlaySlider.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/PlaySlider.jsx
new file mode 100644
index 000000000000..f6600226f77c
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/PlaySlider.jsx
@@ -0,0 +1,170 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+import Mousetrap from 'mousetrap';
+import { t } from '@superset-ui/translation';
+import BootrapSliderWrapper from '../components/BootstrapSliderWrapper';
+import './PlaySlider.css';
+
+const propTypes = {
+ start: PropTypes.number.isRequired,
+ step: PropTypes.number.isRequired,
+ end: PropTypes.number.isRequired,
+ values: PropTypes.array.isRequired,
+ onChange: PropTypes.func,
+ loopDuration: PropTypes.number,
+ maxFrames: PropTypes.number,
+ orientation: PropTypes.oneOf(['horizontal', 'vertical']),
+ reversed: PropTypes.bool,
+ disabled: PropTypes.bool,
+ range: PropTypes.bool,
+};
+
+const defaultProps = {
+ onChange: () => {},
+ loopDuration: 15000,
+ maxFrames: 100,
+ orientation: 'horizontal',
+ reversed: false,
+ disabled: false,
+ range: true,
+};
+
+export default class PlaySlider extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = { intervalId: null };
+
+ const range = props.end - props.start;
+ const frames = Math.min(props.maxFrames, range / props.step);
+ const width = range / frames;
+ this.intervalMilliseconds = props.loopDuration / frames;
+ this.increment = width < props.step ? props.step : width - (width % props.step);
+
+ this.onChange = this.onChange.bind(this);
+ this.play = this.play.bind(this);
+ this.pause = this.pause.bind(this);
+ this.stepBackward = this.stepBackward.bind(this);
+ this.stepForward = this.stepForward.bind(this);
+ this.getPlayClass = this.getPlayClass.bind(this);
+ this.formatter = this.formatter.bind(this);
+ }
+ componentDidMount() {
+ Mousetrap.bind(['space'], this.play);
+ }
+ componentWillUnmount() {
+ Mousetrap.unbind(['space']);
+ }
+ onChange(event) {
+ this.props.onChange(event.target.value);
+ if (this.state.intervalId != null) {
+ this.pause();
+ }
+ }
+ getPlayClass() {
+ if (this.state.intervalId == null) {
+ return 'fa fa-play fa-lg slider-button';
+ }
+ return 'fa fa-pause fa-lg slider-button';
+ }
+ play() {
+ if (this.props.disabled) {
+ return;
+ }
+ if (this.state.intervalId != null) {
+ this.pause();
+ } else {
+ const id = setInterval(this.stepForward, this.intervalMilliseconds);
+ this.setState({ intervalId: id });
+ }
+ }
+ pause() {
+ clearInterval(this.state.intervalId);
+ this.setState({ intervalId: null });
+ }
+ stepForward() {
+ const { start, end, step, values, disabled } = this.props;
+
+ if (disabled) {
+ return;
+ }
+
+ const currentValues = Array.isArray(values) ? values : [values, values + step];
+ const nextValues = currentValues.map(value => value + this.increment);
+ const carriageReturn = (nextValues[1] > end) ? (nextValues[0] - start) : 0;
+
+ this.props.onChange(nextValues.map(value => value - carriageReturn));
+ }
+ stepBackward() {
+ const { start, end, step, values, disabled } = this.props;
+
+ if (disabled) {
+ return;
+ }
+
+ const currentValues = Array.isArray(values) ? values : [values, values + step];
+ const nextValues = currentValues.map(value => value - this.increment);
+ const carriageReturn = (nextValues[0] < start) ? (end - nextValues[1]) : 0;
+
+ this.props.onChange(nextValues.map(value => value + carriageReturn));
+ }
+ formatter(values) {
+ if (this.props.disabled) {
+ return t('Data has no time steps');
+ }
+
+ let parts = values;
+ if (!Array.isArray(values)) {
+ parts = [values];
+ } else if (values[0] === values[1]) {
+ parts = [values[0]];
+ }
+ return parts.map(value => (new Date(value)).toUTCString()).join(' : ');
+ }
+ render() {
+ const { start, end, step, orientation, reversed, disabled, range, values } = this.props;
+ return (
+
+ );
+ }
+}
+
+PlaySlider.propTypes = propTypes;
+PlaySlider.defaultProps = defaultProps;
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/TooltipRow.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/TooltipRow.jsx
index cc85bfd984ed..0e1138d28002 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/TooltipRow.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/TooltipRow.jsx
@@ -24,11 +24,15 @@ const propTypes = {
value: PropTypes.string.isRequired,
};
-
export default class TooltipRow extends React.PureComponent {
render() {
+ const { label, value } = this.props;
+
return (
-
{this.props.label}{this.props.value}
+
+ {label}
+ {value}
+
);
}
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/css/deckgl.css b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/css/deckgl.css
new file mode 100644
index 000000000000..b085d1c6b2e3
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/css/deckgl.css
@@ -0,0 +1,22 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ .deckgl-tooltip > div {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/factory.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/factory.jsx
index abbdcca0db43..824eabd4454b 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/factory.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/factory.jsx
@@ -52,39 +52,30 @@ export function createDeckGLComponent(getLayer, getPoints) {
};
this.onViewportChange = this.onViewportChange.bind(this);
}
+
UNSAFE_componentWillReceiveProps(nextProps) {
// Only recompute the layer if anything BUT the viewport has changed
const nextFdNoVP = { ...nextProps.formData, viewport: null };
const currFdNoVP = { ...this.props.formData, viewport: null };
- if (
- !isEqual(nextFdNoVP, currFdNoVP) ||
- nextProps.payload !== this.props.payload
- ) {
+ if (!isEqual(nextFdNoVP, currFdNoVP) || nextProps.payload !== this.props.payload) {
this.setState({ layer: this.computeLayer(nextProps) });
}
}
+
onViewportChange(viewport) {
this.setState({ viewport });
}
+
computeLayer(props) {
- const {
- formData,
- payload,
- onAddFilter,
- setTooltip,
- } = props;
+ const { formData, payload, onAddFilter, setTooltip } = props;
+
return getLayer(formData, payload, onAddFilter, setTooltip);
}
+
render() {
- const {
- formData,
- payload,
- setControlValue,
- } = this.props;
- const {
- layer,
- viewport,
- } = this.state;
+ const { formData, payload, setControlValue } = this.props;
+ const { layer, viewport } = this.state;
+
return (
);
+ />
+ );
}
}
Component.propTypes = propTypes;
Component.defaultProps = defaultProps;
+
return Component;
}
export function createCategoricalDeckGLComponent(getLayer, getPoints) {
function Component(props) {
- const {
- formData,
- payload,
- setControlValue,
- onAddFilter,
- setTooltip,
- viewport,
- } = props;
+ const { formData, payload, setControlValue, onAddFilter, setTooltip, viewport } = props;
return (
{
+ data.forEach(d => {
points.push(d.sourcePosition);
points.push(d.targetPosition);
});
+
return points;
}
function setTooltipContent(formData) {
return o => (
-
-
- {
- formData.dimension &&
- }
+
+
+ {formData.dimension && (
+
+ )}
);
}
@@ -48,12 +55,13 @@ export function getLayer(fd, payload, onAddFilter, setTooltip) {
const data = payload.data.features;
const sc = fd.color_picker;
const tc = fd.target_color_picker;
+
return new ArcLayer({
- id: `path-layer-${fd.slice_id}`,
data,
getSourceColor: d => d.sourceColor || d.color || [sc.r, sc.g, sc.b, 255 * sc.a],
getTargetColor: d => d.targetColor || d.color || [tc.r, tc.g, tc.b, 255 * tc.a],
- strokeWidth: (fd.stroke_width) ? fd.stroke_width : 3,
+ id: `path-layer-${fd.slice_id}`,
+ strokeWidth: fd.stroke_width ? fd.stroke_width : 3,
...commonLayerProps(fd, setTooltip, setTooltipContent(fd)),
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/index.js
index 8f597631da48..2ef3543eb123 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/index.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/index.js
@@ -22,17 +22,17 @@ import thumbnail from './images/thumbnail.png';
import transformProps from '../../transformProps';
const metadata = new ChartMetadata({
- name: t('deck.gl Arc'),
- description: '',
credits: ['https://uber.github.io/deck.gl'],
+ description: '',
+ name: t('deck.gl Arc'),
thumbnail,
});
export default class ArcChartPlugin extends ChartPlugin {
constructor() {
super({
+ loadChart: () => import('./Arc'),
metadata,
- loadChart: () => import('./Arc.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/Geojson.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/Geojson.jsx
index 7488a3ddf56a..83bb44211152 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/Geojson.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/Geojson.jsx
@@ -22,8 +22,8 @@ import { GeoJsonLayer } from 'deck.gl';
// TODO import geojsonExtent from 'geojson-extent';
import DeckGLContainer from '../../DeckGLContainer';
-import { hexToRGB } from '../../../../modules/colors';
-import sandboxedEval from '../../../../modules/sandbox';
+import { hexToRGB } from '../../utils/colors';
+import sandboxedEval from '../../utils/sandbox';
import { commonLayerProps } from '../common';
import TooltipRow from '../../TooltipRow';
@@ -39,7 +39,7 @@ const propertyMap = {
const alterProps = (props, propOverrides) => {
const newProps = {};
- Object.keys(props).forEach((k) => {
+ Object.keys(props).forEach(k => {
if (k in propertyMap) {
newProps[propertyMap[k]] = props[k];
} else {
@@ -52,6 +52,7 @@ const alterProps = (props, propOverrides) => {
if (typeof props.strokeColor === 'string') {
newProps.strokeColor = hexToRGB(props.strokeColor);
}
+
return {
...newProps,
...propOverrides,
@@ -60,7 +61,7 @@ const alterProps = (props, propOverrides) => {
let features;
const recurseGeoJson = (node, propOverrides, extraProps) => {
if (node && node.features) {
- node.features.forEach((obj) => {
+ node.features.forEach(obj => {
recurseGeoJson(obj, propOverrides, node.extraProps || extraProps);
});
}
@@ -78,14 +79,17 @@ const recurseGeoJson = (node, propOverrides, extraProps) => {
function setTooltipContent(o) {
return (
- o.object.extraProps &&
-
- {
- Object.keys(o.object.extraProps).map((prop, index) =>
- ,
- )
- }
-
+ o.object.extraProps && (
+
+ {Object.keys(o.object.extraProps).map((prop, index) => (
+
+ ))}
+
+ )
);
}
@@ -138,14 +142,7 @@ const defaultProps = {
};
function deckGeoJson(props) {
- const {
- formData,
- payload,
- setControlValue,
- onAddFilter,
- setTooltip,
- viewport,
- } = props;
+ const { formData, payload, setControlValue, onAddFilter, setTooltip, viewport } = props;
// TODO get this to work
// if (formData.autozoom) {
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/index.js
index 06fded5de738..dbedf53418c3 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/index.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/index.js
@@ -22,17 +22,17 @@ import thumbnail from './images/thumbnail.png';
import transformProps from '../../transformProps';
const metadata = new ChartMetadata({
- name: t('deck.gl Geojson'),
- description: '',
credits: ['https://uber.github.io/deck.gl'],
+ description: '',
+ name: t('deck.gl Geojson'),
thumbnail,
});
export default class GeojsonChartPlugin extends ChartPlugin {
constructor() {
super({
+ loadChart: () => import('./Geojson'),
metadata,
- loadChart: () => import('./Geojson.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/Grid.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/Grid.jsx
index a0cc8613faff..370cb3dfdb1c 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/Grid.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/Grid.jsx
@@ -20,7 +20,7 @@ import { GridLayer } from 'deck.gl';
import React from 'react';
import { t } from '@superset-ui/translation';
-import { commonLayerProps, getAggFunc } from '../common';
+import { commonLayerProps, getAggFunc } from '../common';
import sandboxedEval from '../../../../modules/sandbox';
import { createDeckGLComponent } from '../../factory';
import TooltipRow from '../../TooltipRow';
@@ -28,7 +28,10 @@ import TooltipRow from '../../TooltipRow';
function setTooltipContent(o) {
return (
-
+
);
@@ -49,6 +52,7 @@ export function getLayer(formData, payload, onAddFilter, setTooltip) {
}
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);
+
return new GridLayer({
id: `grid-layer-${fd.slice_id}`,
data,
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/index.js
index 291b967afcdb..33ca75260fe5 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/index.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/index.js
@@ -22,17 +22,17 @@ import thumbnail from './images/thumbnail.png';
import transformProps from '../../transformProps';
const metadata = new ChartMetadata({
- name: t('deck.gl Grid'),
- description: '',
credits: ['https://uber.github.io/deck.gl'],
+ description: '',
+ name: t('deck.gl Grid'),
thumbnail,
});
export default class GridChartPlugin extends ChartPlugin {
constructor() {
super({
+ loadChart: () => import('./Grid'),
metadata,
- loadChart: () => import('./Grid.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/Hex.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/Hex.jsx
index 9901b22628bd..22dd39b6ff97 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/Hex.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/Hex.jsx
@@ -20,7 +20,7 @@ import { HexagonLayer } from 'deck.gl';
import React from 'react';
import { t } from '@superset-ui/translation';
-import { commonLayerProps, getAggFunc } from '../common';
+import { commonLayerProps, getAggFunc } from '../common';
import sandboxedEval from '../../../../modules/sandbox';
import { createDeckGLComponent } from '../../factory';
import TooltipRow from '../../TooltipRow';
@@ -28,7 +28,10 @@ import TooltipRow from '../../TooltipRow';
function setTooltipContent(o) {
return (
-
+
);
@@ -48,6 +51,7 @@ export function getLayer(formData, payload, onAddFilter, setTooltip) {
data = jsFnMutator(data);
}
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);
+
return new HexagonLayer({
id: `hex-layer-${fd.slice_id}`,
data,
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/index.js
index 940ae5bac0e7..03251a3f0bd6 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/index.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/index.js
@@ -22,17 +22,17 @@ import thumbnail from './images/thumbnail.png';
import transformProps from '../../transformProps';
const metadata = new ChartMetadata({
- name: t('deck.gl 3D Hexagon'),
- description: '',
credits: ['https://uber.github.io/deck.gl'],
+ description: '',
+ name: t('deck.gl 3D Hexagon'),
thumbnail,
});
export default class HexChartPlugin extends ChartPlugin {
constructor() {
super({
+ loadChart: () => import('./Hex'),
metadata,
- loadChart: () => import('./Hex.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/Path.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/Path.jsx
index 7bf0982419e2..15794d4ed472 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/Path.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/Path.jsx
@@ -25,14 +25,18 @@ import TooltipRow from '../../TooltipRow';
function setTooltipContent(o) {
return (
- o.object.extraProps &&
-
- {
+ o.object.extraProps && (
+
+ {
Object.keys(o.object.extraProps).map((prop, index) =>
- ,
- )
- }
-
+
+ ))}
+
+ )
);
}
@@ -63,9 +67,10 @@ export function getLayer(formData, payload, onAddFilter, setTooltip) {
function getPoints(data) {
let points = [];
- data.forEach((d) => {
+ data.forEach(d => {
points = points.concat(d.path);
});
+
return points;
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/index.js
index 5b584c530a7d..66e978e5d77d 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/index.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/index.js
@@ -22,17 +22,17 @@ import thumbnail from './images/thumbnail.png';
import transformProps from '../../transformProps';
const metadata = new ChartMetadata({
- name: t('deck.gl Path'),
- description: '',
credits: ['https://uber.github.io/deck.gl'],
+ description: '',
+ name: t('deck.gl Path'),
thumbnail,
});
export default class PathChartPlugin extends ChartPlugin {
constructor() {
super({
+ loadChart: () => import('./Path'),
metadata,
- loadChart: () => import('./Path.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/Polygon.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/Polygon.jsx
index 891856d04302..dc62446efce6 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/Polygon.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/Polygon.jsx
@@ -32,7 +32,7 @@ import { commonLayerProps, fitViewport } from '../common';
import { getPlaySliderParams } from '../../../../modules/time';
import sandboxedEval from '../../../../modules/sandbox';
-const DOUBLE_CLICK_TRESHOLD = 250; // milliseconds
+const DOUBLE_CLICK_TRESHOLD = 250; // milliseconds
function getPoints(features) {
return features.map(d => d.polygon).flat();
@@ -44,18 +44,22 @@ function getElevation(d, colorScaler) {
* effectively showing the map layer no matter what other polygons are
* behind it.
*/
- return colorScaler(d)[3] === 0
- ? 0
- : d.elevation;
+ return colorScaler(d)[3] === 0 ? 0 : d.elevation;
}
function setTooltipContent(formData) {
- return (o) => {
+ return o => {
const metricLabel = formData.metric.label || formData.metric;
+
return (
-
- {formData.metric && }
+
+ {formData.metric && (
+
+ )}
);
};
@@ -68,7 +72,7 @@ export function getLayer(formData, payload, onAddFilter, setTooltip, selected, o
let data = [...payload.data.features];
if (filters != null) {
- filters.forEach((f) => {
+ filters.forEach(f => {
data = data.filter(f);
});
}
@@ -82,21 +86,25 @@ export function getLayer(formData, payload, onAddFilter, setTooltip, selected, o
const metricLabel = fd.metric ? fd.metric.label || fd.metric : null;
const accessor = d => d[metricLabel];
// base color for the polygons
- const baseColorScaler = fd.metric === null
- ? () => [fc.r, fc.g, fc.b, 255 * fc.a]
- : getBreakPointColorScaler(fd, data, accessor);
+ const baseColorScaler =
+ fd.metric === null
+ ? () => [fc.r, fc.g, fc.b, 255 * fc.a]
+ : getBreakPointColorScaler(fd, data, accessor);
// when polygons are selected, reduce the opacity of non-selected polygons
- const colorScaler = (d) => {
+ const colorScaler = d => {
const baseColor = baseColorScaler(d);
if (selected.length > 0 && selected.indexOf(d[fd.line_column]) === -1) {
baseColor[3] /= 2;
}
+
return baseColor;
};
- const tooltipContentGenerator = (fd.line_column && fd.metric && ['geohash', 'zipcode'].indexOf(fd.line_type) >= 0)
- ? setTooltipContent(fd)
- : undefined;
+ const tooltipContentGenerator =
+ fd.line_column && fd.metric && ['geohash', 'zipcode'].indexOf(fd.line_type) >= 0
+ ? setTooltipContent(fd)
+ : undefined;
+
return new PolygonLayer({
id: `path-layer-${fd.slice_id}`,
data,
@@ -140,6 +148,7 @@ class DeckGLPolygon extends React.Component {
this.onValuesChange = this.onValuesChange.bind(this);
this.onViewportChange = this.onViewportChange.bind(this);
}
+
static getDerivedStateFromProps(props, state) {
// the state is computed only from the payload; if it hasn't changed, do
// not recompute state since this would reset selections and/or the play
@@ -153,19 +162,10 @@ class DeckGLPolygon extends React.Component {
// the granularity has to be read from the payload form_data, not the
// props formData which comes from the instantaneous controls state
- const granularity = (
- props.payload.form_data.time_grain_sqla ||
- props.payload.form_data.granularity ||
- 'P1D'
- );
+ const granularity =
+ props.payload.form_data.time_grain_sqla || props.payload.form_data.granularity || 'P1D';
- const {
- start,
- end,
- getStep,
- values,
- disabled,
- } = getPlaySliderParams(timestamps, granularity);
+ const { start, end, getStep, values, disabled } = getPlaySliderParams(timestamps, granularity);
const viewport = props.formData.autozoom
? fitViewport(props.viewport, getPoints(features))
@@ -183,11 +183,12 @@ class DeckGLPolygon extends React.Component {
formData: props.payload.form_data,
};
}
+
onSelect(polygon) {
const { formData, onAddFilter } = this.props;
const now = new Date();
- const doubleClick = (now - this.state.lastClick) <= DOUBLE_CLICK_TRESHOLD;
+ const doubleClick = now - this.state.lastClick <= DOUBLE_CLICK_TRESHOLD;
// toggle selected polygons
const selected = [...this.state.selected];
@@ -209,16 +210,17 @@ class DeckGLPolygon extends React.Component {
onAddFilter(formData.line_column, selected, false, true);
}
}
+
onValuesChange(values) {
this.setState({
- values: Array.isArray(values)
- ? values
- : [values, values + this.state.getStep(values)],
+ values: Array.isArray(values) ? values : [values, values + this.state.getStep(values)],
});
}
+
onViewportChange(viewport) {
this.setState({ viewport });
}
+
getLayers(values) {
if (this.props.payload.data.features === undefined) {
return [];
@@ -240,10 +242,12 @@ class DeckGLPolygon extends React.Component {
this.props.setTooltip,
this.state.selected,
this.onSelect,
- filters);
+ filters,
+ );
return [layer];
}
+
render() {
const { payload, formData, setControlValue } = this.props;
const { start, end, getStep, values, disabled, viewport } = this.state;
@@ -253,6 +257,7 @@ class DeckGLPolygon extends React.Component {
const accessor = d => d[metricLabel];
const buckets = getBuckets(formData, payload.data.features, accessor);
+
return (
- {formData.metric !== null &&
- }
+ {formData.metric !== null && (
+
+ )}
);
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/index.js
index 4b5f7c051365..0106461c2570 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/index.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/index.js
@@ -22,17 +22,17 @@ import thumbnail from './images/thumbnail.png';
import transformProps from '../../transformProps';
const metadata = new ChartMetadata({
- name: t('deck.gl Polygon'),
- description: '',
credits: ['https://uber.github.io/deck.gl'],
+ description: '',
+ name: t('deck.gl Polygon'),
thumbnail,
});
export default class PolygonChartPlugin extends ChartPlugin {
constructor() {
super({
+ loadChart: () => import('./Polygon'),
metadata,
- loadChart: () => import('./Polygon.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/Scatter.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/Scatter.jsx
index 42dd7c8df4a8..78e49f3fda14 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/Scatter.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/Scatter.jsx
@@ -31,20 +31,26 @@ function getPoints(data) {
function setTooltipContent(formData) {
return o => (
-
- {
- o.object.cat_color &&
- }
- {
- o.object.metric &&
- }
+
+ {o.object.cat_color && (
+
+ )}
+ {o.object.metric && (
+
+ )}
);
}
export function getLayer(formData, payload, onAddFilter, setTooltip) {
const fd = formData;
- const dataWithRadius = payload.data.features.map((d) => {
+ const dataWithRadius = payload.data.features.map(d => {
let radius = unitToRadius(fd.point_unit, d.radius) || 10;
if (fd.multiplier) {
radius *= fd.multiplier;
@@ -54,6 +60,7 @@ export function getLayer(formData, payload, onAddFilter, setTooltip) {
}
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const color = [c.r, c.g, c.b, c.a * 255];
+
return { ...d, radius, color };
});
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/index.js
index 093a751d5e9d..b46273f67ef1 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/index.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/index.js
@@ -22,17 +22,17 @@ import thumbnail from './images/thumbnail.png';
import transformProps from '../../transformProps';
const metadata = new ChartMetadata({
- name: t('deck.gl Scatterplot'),
- description: '',
credits: ['https://uber.github.io/deck.gl'],
+ description: '',
+ name: t('deck.gl Scatterplot'),
thumbnail,
});
export default class ScatterChartPlugin extends ChartPlugin {
constructor() {
super({
+ loadChart: () => import('./Scatter'),
metadata,
- loadChart: () => import('./Scatter.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/Screengrid.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/Screengrid.jsx
index d9fba4c70439..8c738a30e33a 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/Screengrid.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/Screengrid.jsx
@@ -35,7 +35,10 @@ function getPoints(data) {
function setTooltipContent(o) {
return (
-
+
);
@@ -56,7 +59,7 @@ export function getLayer(formData, payload, onAddFilter, setTooltip, selected, o
}
if (filters != null) {
- filters.forEach((f) => {
+ filters.forEach(f => {
data = data.filter(f);
});
}
@@ -99,6 +102,7 @@ class DeckGLScreenGrid extends React.PureComponent {
this.onValuesChange = this.onValuesChange.bind(this);
this.onViewportChange = this.onViewportChange.bind(this);
}
+
static getDerivedStateFromProps(props, state) {
// the state is computed only from the payload; if it hasn't changed, do
// not recompute state since this would reset selections and/or the play
@@ -112,19 +116,10 @@ class DeckGLScreenGrid extends React.PureComponent {
// the granularity has to be read from the payload form_data, not the
// props formData which comes from the instantaneous controls state
- const granularity = (
- props.payload.form_data.time_grain_sqla ||
- props.payload.form_data.granularity ||
- 'P1D'
- );
+ const granularity =
+ props.payload.form_data.time_grain_sqla || props.payload.form_data.granularity || 'P1D';
- const {
- start,
- end,
- getStep,
- values,
- disabled,
- } = getPlaySliderParams(timestamps, granularity);
+ const { start, end, getStep, values, disabled } = getPlaySliderParams(timestamps, granularity);
const viewport = props.formData.autozoom
? fitViewport(props.viewport, getPoints(features))
@@ -142,16 +137,17 @@ class DeckGLScreenGrid extends React.PureComponent {
formData: props.payload.form_data,
};
}
+
onValuesChange(values) {
this.setState({
- values: Array.isArray(values)
- ? values
- : [values, values + this.state.getStep(values)],
+ values: Array.isArray(values) ? values : [values, values + this.state.getStep(values)],
});
}
+
onViewportChange(viewport) {
this.setState({ viewport });
}
+
getLayers(values) {
const filters = [];
@@ -167,13 +163,15 @@ class DeckGLScreenGrid extends React.PureComponent {
this.props.payload,
this.props.onAddFilter,
this.props.setTooltip,
- filters);
+ filters,
+ );
return [layer];
}
render() {
const { formData, payload, setControlValue } = this.props;
+
return (
import('./Screengrid'),
metadata,
- loadChart: () => import('./Screengrid.jsx'),
transformProps,
});
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/common.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/common.jsx
index aaee55361500..b23be4ba12f9 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/common.jsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/common.jsx
@@ -22,10 +22,10 @@ import sandboxedEval from '../../../modules/sandbox';
const PADDING = 0.25;
const GEO_BOUNDS = {
- LAT_MIN: -90,
LAT_MAX: 90,
- LNG_MIN: -180,
+ LAT_MIN: -90,
LNG_MAX: 180,
+ LNG_MIN: -180,
};
/**
@@ -33,12 +33,11 @@ const GEO_BOUNDS = {
* @param latExt Latitude range
*/
function getLatBoundsForSingleCoordinate(latExt) {
- const latMin = latExt[0] - PADDING < GEO_BOUNDS.LAT_MIN
- ? GEO_BOUNDS.LAT_MIN
- : latExt[0] - PADDING;
- const latMax = latExt[1] + PADDING > GEO_BOUNDS.LAT_MAX
- ? GEO_BOUNDS.LAT_MAX
- : latExt[1] + PADDING;
+ const latMin =
+ latExt[0] - PADDING < GEO_BOUNDS.LAT_MIN ? GEO_BOUNDS.LAT_MIN : latExt[0] - PADDING;
+ const latMax =
+ latExt[1] + PADDING > GEO_BOUNDS.LAT_MAX ? GEO_BOUNDS.LAT_MAX : latExt[1] + PADDING;
+
return [latMin, latMax];
}
@@ -47,12 +46,11 @@ function getLatBoundsForSingleCoordinate(latExt) {
* @param lngExt Longitude range
*/
function getLngBoundsForSingleCoordinate(lngExt) {
- const lngMin = lngExt[0] - PADDING < GEO_BOUNDS.LNG_MIN
- ? GEO_BOUNDS.LNG_MIN
- : lngExt[0] - PADDING;
- const lngMax = lngExt[1] + PADDING > GEO_BOUNDS.LNG_MAX
- ? GEO_BOUNDS.LNG_MAX
- : lngExt[1] + PADDING;
+ const lngMin =
+ lngExt[0] - PADDING < GEO_BOUNDS.LNG_MIN ? GEO_BOUNDS.LNG_MIN : lngExt[0] - PADDING;
+ const lngMax =
+ lngExt[1] + PADDING > GEO_BOUNDS.LNG_MAX ? GEO_BOUNDS.LNG_MAX : lngExt[1] + PADDING;
+
return [lngMin, lngMax];
}
@@ -61,15 +59,14 @@ export function getBounds(points) {
const lngExt = d3array.extent(points, d => d[0]);
const latBounds = latExt[0] === latExt[1] ? getLatBoundsForSingleCoordinate(latExt) : latExt;
const lngBounds = lngExt[0] === lngExt[1] ? getLngBoundsForSingleCoordinate(lngExt) : lngExt;
- return [
- [lngBounds[0], latBounds[0]],
- [lngBounds[1], latBounds[1]],
- ];
+
+ return [[lngBounds[0], latBounds[0]], [lngBounds[1], latBounds[1]]];
}
export function fitViewport(viewport, points, padding = 10) {
try {
const bounds = getBounds(points);
+
return {
...viewport,
...fitBounds({
@@ -82,6 +79,7 @@ export function fitViewport(viewport, points, padding = 10) {
} catch (e) {
/* eslint no-console: 0 */
console.error('Could not auto zoom', e);
+
return viewport;
}
}
@@ -94,7 +92,7 @@ export function commonLayerProps(formData, setTooltip, setTooltipContent, onSele
tooltipContentGenerator = sandboxedEval(fd.js_tooltip);
}
if (tooltipContentGenerator) {
- onHover = (o) => {
+ onHover = o => {
if (o.picked) {
setTooltip({
content: tooltipContentGenerator(o),
@@ -108,13 +106,14 @@ export function commonLayerProps(formData, setTooltip, setTooltipContent, onSele
}
let onClick;
if (fd.js_onclick_href) {
- onClick = (o) => {
+ onClick = o => {
const href = sandboxedEval(fd.js_onclick_href)(o);
window.open(href);
};
} else if (fd.table_filter && onSelect !== undefined) {
onClick = o => onSelect(o.object[fd.line_column]);
}
+
return {
onClick,
onHover,
@@ -143,6 +142,7 @@ export function getAggFunc(type = 'sum', accessor = null) {
} else {
sortedArr = arr.sort(d3array.ascending);
}
+
return d3array.quantile(sortedArr, percentiles[type], acc);
};
} else {
@@ -151,5 +151,6 @@ export function getAggFunc(type = 'sum', accessor = null) {
if (!accessor) {
return arr => d3func(arr);
}
+
return arr => d3func(arr.map(accessor));
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/transformProps.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/transformProps.js
index 9e7350baed17..02b821a0d72a 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/transformProps.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/transformProps.js
@@ -19,13 +19,7 @@
const NOOP = () => {};
export default function transformProps(chartProps) {
- const {
- width,
- height,
- rawFormData,
- queryData,
- hooks,
- } = chartProps;
+ const { width, height, rawFormData, queryData, hooks } = chartProps;
const { onAddFilter = NOOP, setControlValue = NOOP, setTooltip = NOOP } = hooks;
return {
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils.js
index 246e31b46c71..cc5487c55523 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils.js
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils.js
@@ -19,7 +19,7 @@
import { extent } from 'd3-array';
import { scaleThreshold } from 'd3-scale';
import { getSequentialSchemeRegistry, SequentialScheme } from '@superset-ui/color';
-import { hexToRGB } from '../../modules/colors';
+import { hexToRGB } from './utils/colors';
const DEFAULT_NUM_BUCKETS = 10;
@@ -42,12 +42,12 @@ export function getBreakPoints(
const precision = delta === 0 ? 0 : Math.max(0, Math.ceil(Math.log10(1 / delta)));
const extraBucket = maxValue > maxValue.toFixed(precision) ? 1 : 0;
-return Array(numBuckets + 1 + extraBucket)
+ return Array(numBuckets + 1 + extraBucket)
.fill()
.map((_, i) => (minValue + i * delta).toFixed(precision));
}
-return formDataBreakPoints.sort((a, b) => parseFloat(a) - parseFloat(b));
+ return formDataBreakPoints.sort((a, b) => parseFloat(a) - parseFloat(b));
}
export function getBreakPointColorScaler(
@@ -112,7 +112,7 @@ export function getBreakPointColorScaler(
c[3] = (opacity / 100.0) * 255;
}
-return c;
+ return c;
};
}
@@ -121,7 +121,7 @@ export function getBuckets(fd, features, accessor) {
const colorScaler = getBreakPointColorScaler(fd, features, accessor);
const buckets = {};
breakPoints.slice(1).forEach((value, i) => {
- const range = `${breakPoints[i] } - ${ breakPoints[i + 1]}`;
+ const range = `${breakPoints[i]} - ${breakPoints[i + 1]}`;
const mid = 0.5 * (parseFloat(breakPoints[i]) + parseFloat(breakPoints[i + 1]));
// fix polygon doesn't show
const metricLabel = fd.metric ? fd.metric.label || fd.metric : null;
@@ -131,5 +131,5 @@ export function getBuckets(fd, features, accessor) {
};
});
-return buckets;
+ return buckets;
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/colors.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/colors.js
new file mode 100644
index 000000000000..6b0f72af70ea
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/colors.js
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { rgb } from 'd3-color';
+
+export function hexToRGB(hex, alpha = 255) {
+ if (!hex) {
+ return [0, 0, 0, alpha];
+ }
+ const { r, g, b } = rgb(hex);
+ return [r, g, b, alpha];
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/sandbox.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/sandbox.js
new file mode 100644
index 000000000000..7c2e77b0536b
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/sandbox.js
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+// A safe alternative to JS's eval
+import vm from 'vm';
+import _ from 'underscore';
+import * as d3array from 'd3-array';
+import * as colors from './colors';
+
+// Objects exposed here should be treated like a public API
+// if `underscore` had backwards incompatible changes in a future release, we'd
+// have to be careful about bumping the library as those changes could break user charts
+const GLOBAL_CONTEXT = {
+ console,
+ _,
+ colors,
+ d3array,
+};
+
+// Copied/modified from https://github.com/hacksparrow/safe-eval/blob/master/index.js
+export default function sandboxedEval(code, context, opts) {
+ const sandbox = {};
+ const resultKey = 'SAFE_EVAL_' + Math.floor(Math.random() * 1000000);
+ sandbox[resultKey] = {};
+ const codeToEval = resultKey + '=' + code;
+ const sandboxContext = { ...GLOBAL_CONTEXT, ...context };
+ Object.keys(sandboxContext).forEach(function (key) {
+ sandbox[key] = sandboxContext[key];
+ });
+ try {
+ vm.runInNewContext(codeToEval, sandbox, opts);
+ return sandbox[resultKey];
+ } catch (error) {
+ return () => error;
+ }
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/time.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/time.js
new file mode 100644
index 000000000000..3d4466f3cfe3
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils/time.js
@@ -0,0 +1,124 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import moment from 'moment';
+
+
+// array with the minimum values of each part of a timestamp -- note that
+// months are zero-indexed in Javascript
+const truncatePartTo = [
+ 1, // year
+ 0, // month
+ 1, // day
+ 0, // hour
+ 0, // minute
+ 0, // second
+ 0, // millisecond
+];
+
+
+export function truncate(timestamp, step) {
+ /*
+ * Truncate timestamp down to duration resolution.
+ */
+ const lowerBound = moment(timestamp).subtract(step);
+ const explodedTimestamp = timestamp.toArray();
+ const explodedLowerBound = lowerBound.toArray();
+
+ const firstDiffIndex = explodedTimestamp
+ .map((part, i) => (explodedLowerBound[i] !== part))
+ .indexOf(true);
+ const dateParts = explodedTimestamp.map((part, i) => {
+ if (i === firstDiffIndex) {
+ // truncate down to closest `truncatePartTo[i] + n * step`
+ const difference = part - explodedLowerBound[i];
+ return part - ((part - truncatePartTo[i]) % difference);
+ } else if (i < firstDiffIndex || firstDiffIndex === -1) {
+ return part;
+ }
+ return truncatePartTo[i];
+ });
+
+ return moment(dateParts);
+}
+
+function getStepSeconds(step, start) {
+ /* Return number of seconds in a step.
+ *
+ * The step might be ambigous, eg, "1 month" has a variable number of
+ * seconds, which is why we need to know the start time.
+ */
+ const startMillliseconds = parseInt(moment(start).format('x'), 10);
+ const endMilliseconds = parseInt(moment(start).add(step).format('x'), 10);
+ return endMilliseconds - startMillliseconds;
+}
+
+export const getPlaySliderParams = function (timestamps, timeGrain) {
+ const minTimestamp = moment(Math.min(...timestamps));
+ const maxTimestamp = moment(Math.max(...timestamps));
+ let step;
+ let reference;
+
+ if (timeGrain.indexOf('/') !== -1) {
+ // Here, time grain is a time interval instead of a simple duration, either
+ // `reference/duration` or `duration/reference`. We need to parse the
+ // duration and make sure that start and end are in the right places. For
+ // example, if `reference` is a Saturday and `duration` is 1 week (P1W)
+ // then both start and end should be Saturdays.
+ const parts = timeGrain.split('/', 2);
+ if (parts[0].endsWith('Z')) { // ISO string
+ reference = moment(parts[0]);
+ step = moment.duration(parts[1]);
+ } else {
+ reference = moment(parts[1]);
+ step = moment.duration(parts[0]);
+ }
+ } else {
+ step = moment.duration(timeGrain);
+ reference = truncate(minTimestamp, step);
+ }
+
+ // find the largest `reference + n * step` smaller than the minimum timestamp
+ const start = moment(reference);
+ while (start < minTimestamp) {
+ start.add(step);
+ }
+ while (start > minTimestamp) {
+ start.subtract(step);
+ }
+
+ // find the smallest `reference + n * step` larger than the maximum timestamp
+ const end = moment(reference);
+ while (end > maxTimestamp) {
+ end.subtract(step);
+ }
+ while (end < maxTimestamp) {
+ end.add(step);
+ }
+
+ const values = timeGrain != null ? [start, moment(start).add(step)] : [start, end];
+ const disabled = timestamps.every(timestamp => timestamp === null);
+
+ return {
+ start: parseInt(start.format('x'), 10),
+ end: parseInt(end.format('x'), 10),
+ getStep: getStepSeconds.bind(this, step),
+ values: values.map(v => parseInt(v.format('x'), 10)),
+ disabled,
+ };
+};