feat: add deckgl files
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@superset-ui/plugins-monorepo",
|
||||
"name": "@superset-ui/plugins-deckgl-monorepo",
|
||||
"version": "0.0.0-master",
|
||||
"description": "Superset UI Plugins",
|
||||
"description": "Superset UI Plugins - deck.gl",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "yarn build:cjs && yarn build:esm && yarn run type:dts && yarn build:assets",
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"test": "yarn run type && yarn run jest",
|
||||
"test:watch": "yarn run lint:fix && beemo create-config jest --react && jest --watch"
|
||||
},
|
||||
"repository": "https://github.com/apache-superset/superset-ui-plugins.git",
|
||||
"repository": "https://github.com/apache-superset/superset-ui-plugins-deckgl.git",
|
||||
"keywords": [
|
||||
"apache",
|
||||
"superset",
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
/* eslint-disable sort-keys, no-magic-numbers */
|
||||
import React from 'react';
|
||||
import { SuperChart } from '@superset-ui/chart';
|
||||
import data from './data';
|
||||
import dummyDatasource from '../../shared/dummyDatasource';
|
||||
|
||||
export default [
|
||||
{
|
||||
renderStory: () => (
|
||||
<SuperChart
|
||||
chartType="calendar"
|
||||
width={400}
|
||||
height={400}
|
||||
datasource={dummyDatasource}
|
||||
queryData={{ data }}
|
||||
formData={{
|
||||
cellSize: 10,
|
||||
cellPadding: 2,
|
||||
cellRadius: 0,
|
||||
linearColorScheme: 'schemeRdYlBu',
|
||||
steps: 10,
|
||||
yAxisFormat: '.3s',
|
||||
xAxisTimeFormat: 'smart_date',
|
||||
showLegend: true,
|
||||
showValues: false,
|
||||
showMetricName: true,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
storyName: 'Basic',
|
||||
storyPath: 'legacy-|plugin-chart-calendar|CalendarChartPlugin',
|
||||
},
|
||||
];
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
/* eslint-disable sort-keys */
|
||||
export default {
|
||||
data: {
|
||||
count: {
|
||||
'1518652800.0': 3,
|
||||
'1518048000.0': 2,
|
||||
'1518220800.0': 2,
|
||||
'1523145600.0': 2,
|
||||
'1529798400.0': 2,
|
||||
'1534204800.0': 2,
|
||||
'1541289600.0': 2,
|
||||
'1542672000.0': 2,
|
||||
'1543881600.0': 2,
|
||||
'1517616000.0': 1,
|
||||
'1517875200.0': 1,
|
||||
'1517961600.0': 1,
|
||||
'1518307200.0': 1,
|
||||
'1518393600.0': 1,
|
||||
'1519257600.0': 1,
|
||||
'1519516800.0': 1,
|
||||
'1519776000.0': 1,
|
||||
'1520208000.0': 1,
|
||||
'1520294400.0': 1,
|
||||
'1520985600.0': 1,
|
||||
'1521072000.0': 1,
|
||||
'1521244800.0': 1,
|
||||
'1521331200.0': 1,
|
||||
'1521676800.0': 1,
|
||||
'1522108800.0': 1,
|
||||
'1522627200.0': 1,
|
||||
'1522800000.0': 1,
|
||||
'1522972800.0': 1,
|
||||
'1523491200.0': 1,
|
||||
'1524096000.0': 1,
|
||||
'1524268800.0': 1,
|
||||
'1524614400.0': 1,
|
||||
'1524960000.0': 1,
|
||||
'1525305600.0': 1,
|
||||
'1525564800.0': 1,
|
||||
'1525737600.0': 1,
|
||||
'1525824000.0': 1,
|
||||
'1525910400.0': 1,
|
||||
'1526083200.0': 1,
|
||||
'1526256000.0': 1,
|
||||
'1526688000.0': 1,
|
||||
'1527033600.0': 1,
|
||||
'1527292800.0': 1,
|
||||
'1527465600.0': 1,
|
||||
'1527638400.0': 1,
|
||||
'1528070400.0': 1,
|
||||
'1528329600.0': 1,
|
||||
'1529539200.0': 1,
|
||||
'1529625600.0': 1,
|
||||
'1529712000.0': 1,
|
||||
'1529971200.0': 1,
|
||||
'1530144000.0': 1,
|
||||
'1530576000.0': 1,
|
||||
'1531267200.0': 1,
|
||||
'1531353600.0': 1,
|
||||
'1531440000.0': 1,
|
||||
'1532736000.0': 1,
|
||||
'1533081600.0': 1,
|
||||
'1533168000.0': 1,
|
||||
'1533945600.0': 1,
|
||||
'1534377600.0': 1,
|
||||
'1534809600.0': 1,
|
||||
'1535155200.0': 1,
|
||||
'1535328000.0': 1,
|
||||
'1535932800.0': 1,
|
||||
'1536710400.0': 1,
|
||||
'1537056000.0': 1,
|
||||
'1537142400.0': 1,
|
||||
'1537488000.0': 1,
|
||||
'1537660800.0': 1,
|
||||
'1538611200.0': 1,
|
||||
'1538697600.0': 1,
|
||||
'1539475200.0': 1,
|
||||
'1540771200.0': 1,
|
||||
'1541116800.0': 1,
|
||||
'1541376000.0': 1,
|
||||
'1541635200.0': 1,
|
||||
'1542153600.0': 1,
|
||||
'1542931200.0': 1,
|
||||
'1543190400.0': 1,
|
||||
'1545177600.0': 1,
|
||||
'1545436800.0': 1,
|
||||
'1545782400.0': 1,
|
||||
'1545868800.0': 1,
|
||||
'1546300800.0': 1,
|
||||
'1546732800.0': 1,
|
||||
'1547769600.0': 1,
|
||||
'1547942400.0': 1,
|
||||
'1548633600.0': 1,
|
||||
},
|
||||
},
|
||||
start: 1517270400000.0,
|
||||
domain: 'month',
|
||||
range: 13,
|
||||
subdomain: 'day',
|
||||
};
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import CalendarChartPlugin from '../../../../superset-ui-legacy-plugin-chart-calendar';
|
||||
import Stories from './Stories';
|
||||
|
||||
new CalendarChartPlugin().configure({ key: 'calendar' }).register();
|
||||
|
||||
export default {
|
||||
examples: [...Stories],
|
||||
};
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
## @superset-ui/legacy-preset-chart-nvd3
|
||||
|
||||
[](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-nvd3.svg?style=flat-square)
|
||||
[](https://david-dm.org/apache-superset/superset-ui-plugins?path=packages/superset-ui-legacy-preset-chart-nvd3)
|
||||
|
||||
This plugin provides Big Number for Superset.
|
||||
|
||||
### Usage
|
||||
|
||||
Import the preset and register. This will register all the chart plugins under nvd3.
|
||||
|
||||
```js
|
||||
import { NVD3ChartPreset } from '@superset-ui/legacy-preset-chart-nvd3';
|
||||
|
||||
new NVD3ChartPreset().register();
|
||||
```
|
||||
|
||||
or register charts one by one. Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to lookup this chart throughout the app.
|
||||
|
||||
```js
|
||||
import { AreaChartPlugin, LineChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3';
|
||||
|
||||
new AreaChartPlugin()
|
||||
.configure({ key: 'area' })
|
||||
.register();
|
||||
new LineChartPlugin()
|
||||
.configure({ key: 'line' })
|
||||
.register();
|
||||
```
|
||||
|
||||
Then use it via `SuperChart`. See [storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-nvd3) for more details.
|
||||
|
||||
```js
|
||||
<SuperChart
|
||||
chartType="line"
|
||||
width={600}
|
||||
height={600}
|
||||
formData={...}
|
||||
queryData={{
|
||||
data: {...},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "@superset-ui/legacy-preset-chart-deckgl",
|
||||
"version": "0.11.0",
|
||||
"description": "Superset Legacy Chart - deck.gl",
|
||||
"sideEffects": [
|
||||
"*.css"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"files": [
|
||||
"esm",
|
||||
"lib"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/apache-superset/superset-ui-plugins-deckgl.git"
|
||||
},
|
||||
"keywords": [
|
||||
"superset"
|
||||
],
|
||||
"author": "Superset",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache-superset/superset-ui-plugins-deckgl/issues"
|
||||
},
|
||||
"homepage": "https://github.com/apache-superset/superset-ui-plugins-deckgl#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@data-ui/xy-chart": "^0.0.80",
|
||||
"d3": "^3.5.17",
|
||||
"d3-tip": "^0.9.1",
|
||||
"dompurify": "^1.0.3",
|
||||
"fast-safe-stringify": "^2.0.6",
|
||||
"lodash": "^4.17.11",
|
||||
"mathjs": "^3.20.2",
|
||||
"moment": "^2.20.1",
|
||||
"nvd3": "1.8.6",
|
||||
"prop-types": "^15.6.2",
|
||||
"urijs": "^1.18.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart": "^0.12.0",
|
||||
"@superset-ui/color": "^0.12.0",
|
||||
"@superset-ui/core": "^0.12.0",
|
||||
"@superset-ui/dimension": "^0.12.0",
|
||||
"@superset-ui/number-format": "^0.12.0",
|
||||
"@superset-ui/time-format": "^0.12.0",
|
||||
"@superset-ui/translation": "^0.12.0",
|
||||
"react": "^15 || ^16"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* 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 DeckGLContainer from './DeckGLContainer';
|
||||
import PlaySlider from '../PlaySlider';
|
||||
|
||||
const PLAYSLIDER_HEIGHT = 20; // px
|
||||
|
||||
const propTypes = {
|
||||
getLayers: PropTypes.func.isRequired,
|
||||
start: PropTypes.number.isRequired,
|
||||
end: PropTypes.number.isRequired,
|
||||
getStep: PropTypes.func,
|
||||
values: PropTypes.array.isRequired,
|
||||
aggregation: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
children: PropTypes.node,
|
||||
mapStyle: PropTypes.string,
|
||||
mapboxApiAccessToken: PropTypes.string.isRequired,
|
||||
setControlValue: PropTypes.func,
|
||||
onViewportChange: PropTypes.func,
|
||||
onValuesChange: PropTypes.func,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
aggregation: false,
|
||||
disabled: false,
|
||||
mapStyle: 'light',
|
||||
setControlValue: () => {},
|
||||
onViewportChange: () => {},
|
||||
onValuesChange: () => {},
|
||||
};
|
||||
|
||||
export default class AnimatableDeckGLContainer extends React.Component {
|
||||
constructor(props) {
|
||||
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,
|
||||
end,
|
||||
getStep,
|
||||
disabled,
|
||||
aggregation,
|
||||
children,
|
||||
getLayers,
|
||||
values,
|
||||
onValuesChange,
|
||||
viewport,
|
||||
setControlValue,
|
||||
mapStyle,
|
||||
mapboxApiAccessToken,
|
||||
} = this.props;
|
||||
const layers = getLayers(values);
|
||||
|
||||
// leave space for the play slider
|
||||
const modifiedViewport = {
|
||||
...viewport,
|
||||
height: disabled ? viewport.height : viewport.height - PLAYSLIDER_HEIGHT,
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DeckGLContainer
|
||||
viewport={modifiedViewport}
|
||||
layers={layers}
|
||||
setControlValue={setControlValue}
|
||||
mapStyle={mapStyle}
|
||||
mapboxApiAccessToken={mapboxApiAccessToken}
|
||||
onViewportChange={this.onViewportChange}
|
||||
/>
|
||||
{!disabled &&
|
||||
<PlaySlider
|
||||
start={start}
|
||||
end={end}
|
||||
step={getStep(start)}
|
||||
values={values}
|
||||
range={!aggregation}
|
||||
onChange={onValuesChange}
|
||||
/>
|
||||
}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AnimatableDeckGLContainer.propTypes = propTypes;
|
||||
AnimatableDeckGLContainer.defaultProps = defaultProps;
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
/* eslint no-underscore-dangle: ["error", { "allow": ["", "__timestamp"] }] */
|
||||
|
||||
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 { fitViewport } from './layers/common';
|
||||
|
||||
const { getScale } = CategoricalColorNamespace;
|
||||
|
||||
function getCategories(fd, data) {
|
||||
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
|
||||
const colorFn = getScale(fd.color_scheme);
|
||||
const categories = {};
|
||||
data.forEach((d) => {
|
||||
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
|
||||
let color;
|
||||
if (fd.dimension) {
|
||||
color = hexToRGB(colorFn(d.cat_color), c.a * 255);
|
||||
} else {
|
||||
color = fixedColor;
|
||||
}
|
||||
categories[d.cat_color] = { color, enabled: true };
|
||||
}
|
||||
});
|
||||
return categories;
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
formData: PropTypes.object.isRequired,
|
||||
mapboxApiKey: PropTypes.string.isRequired,
|
||||
setControlValue: PropTypes.func.isRequired,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
getLayer: PropTypes.func.isRequired,
|
||||
getPoints: PropTypes.func.isRequired,
|
||||
payload: PropTypes.object.isRequired,
|
||||
onAddFilter: PropTypes.func,
|
||||
setTooltip: PropTypes.func,
|
||||
};
|
||||
|
||||
export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||
/*
|
||||
* A Deck.gl container that handles categories.
|
||||
*
|
||||
* The container will have an interactive legend, populated from the
|
||||
* categories present in the data.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.getStateFromProps(props);
|
||||
|
||||
this.getLayers = this.getLayers.bind(this);
|
||||
this.onValuesChange = this.onValuesChange.bind(this);
|
||||
this.onViewportChange = this.onViewportChange.bind(this);
|
||||
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)],
|
||||
});
|
||||
}
|
||||
onViewportChange(viewport) {
|
||||
this.setState({ viewport });
|
||||
}
|
||||
getStateFromProps(props, state) {
|
||||
const features = props.payload.data.features || [];
|
||||
const timestamps = features.map(f => f.__timestamp);
|
||||
const categories = getCategories(props.formData, features);
|
||||
|
||||
// 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
|
||||
// slider position due to changes in form controls
|
||||
if (state && props.payload.form_data === state.formData) {
|
||||
return { ...state, categories };
|
||||
}
|
||||
|
||||
// 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 {
|
||||
start,
|
||||
end,
|
||||
getStep,
|
||||
values,
|
||||
disabled,
|
||||
} = getPlaySliderParams(timestamps, granularity);
|
||||
|
||||
const viewport = props.formData.autozoom
|
||||
? fitViewport(props.viewport, props.getPoints(features))
|
||||
: props.viewport;
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
getStep,
|
||||
values,
|
||||
disabled,
|
||||
viewport,
|
||||
selected: [],
|
||||
lastClick: 0,
|
||||
formData: props.payload.form_data,
|
||||
categories,
|
||||
};
|
||||
}
|
||||
getLayers(values) {
|
||||
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);
|
||||
|
||||
// Apply user defined data mutator if defined
|
||||
if (fd.js_data_mutator) {
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
features = jsFnMutator(features);
|
||||
}
|
||||
|
||||
// Filter by time
|
||||
if (values[0] === values[1] || values[1] === this.end) {
|
||||
features = features.filter(d => d.__timestamp >= values[0] && d.__timestamp <= values[1]);
|
||||
} else {
|
||||
features = features.filter(d => d.__timestamp >= values[0] && d.__timestamp < values[1]);
|
||||
}
|
||||
|
||||
// Show only categories selected in the legend
|
||||
const cats = this.state.categories;
|
||||
if (fd.dimension) {
|
||||
features = features.filter(d => cats[d.cat_color] && cats[d.cat_color].enabled);
|
||||
}
|
||||
|
||||
const filteredPayload = {
|
||||
...payload,
|
||||
data: { ...payload.data, features },
|
||||
};
|
||||
|
||||
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) => {
|
||||
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 = {
|
||||
...this.state.categories,
|
||||
[category]: {
|
||||
...categoryState,
|
||||
enabled: !categoryState.enabled,
|
||||
},
|
||||
};
|
||||
|
||||
// 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; });
|
||||
}
|
||||
this.setState({ categories });
|
||||
}
|
||||
showSingleCategory(category) {
|
||||
const categories = { ...this.state.categories };
|
||||
/* eslint-disable no-param-reassign */
|
||||
Object.values(categories).forEach((v) => { v.enabled = false; });
|
||||
categories[category].enabled = true;
|
||||
this.setState({ categories });
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<AnimatableDeckGLContainer
|
||||
getLayers={this.getLayers}
|
||||
start={this.state.start}
|
||||
end={this.state.end}
|
||||
getStep={this.state.getStep}
|
||||
values={this.state.values}
|
||||
onValuesChange={this.onValuesChange}
|
||||
disabled={this.state.disabled}
|
||||
viewport={this.state.viewport}
|
||||
onViewportChange={this.onViewportChange}
|
||||
mapboxApiAccessToken={this.props.mapboxApiKey}
|
||||
mapStyle={this.props.formData.mapbox_style}
|
||||
setControlValue={this.props.setControlValue}
|
||||
>
|
||||
<Legend
|
||||
categories={this.state.categories}
|
||||
toggleCategory={this.toggleCategory}
|
||||
showSingleCategory={this.showSingleCategory}
|
||||
position={this.props.formData.legend_position}
|
||||
format={this.props.formData.legend_format}
|
||||
/>
|
||||
</AnimatableDeckGLContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CategoricalDeckGLContainer.propTypes = propTypes;
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* 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 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';
|
||||
|
||||
const TICK = 2000; // milliseconds
|
||||
|
||||
const propTypes = {
|
||||
viewport: PropTypes.object.isRequired,
|
||||
layers: PropTypes.array.isRequired,
|
||||
setControlValue: PropTypes.func,
|
||||
mapStyle: PropTypes.string,
|
||||
mapboxApiAccessToken: PropTypes.string.isRequired,
|
||||
onViewportChange: PropTypes.func,
|
||||
};
|
||||
const defaultProps = {
|
||||
mapStyle: 'light',
|
||||
onViewportChange: () => {},
|
||||
setControlValue: () => {},
|
||||
};
|
||||
|
||||
export default class DeckGLContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.tick = this.tick.bind(this);
|
||||
this.onViewportChange = this.onViewportChange.bind(this);
|
||||
// This has to be placed after this.tick is bound to this
|
||||
this.state = {
|
||||
previousViewport: props.viewport,
|
||||
timer: setInterval(this.tick, TICK),
|
||||
};
|
||||
}
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.viewport !== prevState.viewport) {
|
||||
return {
|
||||
viewport: { ...nextProps.viewport },
|
||||
previousViewport: prevState.viewport,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.state.timer);
|
||||
}
|
||||
onViewportChange(viewport) {
|
||||
const vp = Object.assign({}, viewport);
|
||||
// delete vp.width;
|
||||
// delete vp.height;
|
||||
const newVp = { ...this.state.previousViewport, ...vp };
|
||||
|
||||
// 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
|
||||
// changes id at every change
|
||||
if (this.state && !isEqual(this.state.previousViewport, this.props.viewport)) {
|
||||
const setCV = this.props.setControlValue;
|
||||
const vp = this.props.viewport;
|
||||
if (setCV) {
|
||||
setCV('viewport', vp);
|
||||
}
|
||||
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;
|
||||
}
|
||||
render() {
|
||||
const { viewport } = this.props;
|
||||
return (
|
||||
<MapGL
|
||||
{...viewport}
|
||||
mapStyle={this.props.mapStyle}
|
||||
onViewportChange={this.onViewportChange}
|
||||
mapboxApiAccessToken={this.props.mapboxApiAccessToken}
|
||||
>
|
||||
<DeckGL
|
||||
{...viewport}
|
||||
layers={this.layers()}
|
||||
initWebGLParameters
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DeckGLContainer.propTypes = propTypes;
|
||||
DeckGLContainer.defaultProps = defaultProps;
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
/**
|
||||
* 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 _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SupersetClient } from '@superset-ui/connection';
|
||||
|
||||
import DeckGLContainer from '../DeckGLContainer';
|
||||
import { getExploreLongUrl } from '../../../explore/exploreUtils';
|
||||
import layerGenerators from '../layers';
|
||||
|
||||
const propTypes = {
|
||||
formData: PropTypes.object.isRequired,
|
||||
payload: PropTypes.object.isRequired,
|
||||
setControlValue: PropTypes.func.isRequired,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
onAddFilter: PropTypes.func,
|
||||
setTooltip: PropTypes.func,
|
||||
onSelect: PropTypes.func,
|
||||
};
|
||||
const defaultProps = {
|
||||
onAddFilter() {},
|
||||
setTooltip() {},
|
||||
onSelect() {},
|
||||
};
|
||||
|
||||
class DeckMulti extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { subSlicesLayers: {} };
|
||||
this.onViewportChange = this.onViewportChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { formData, payload } = this.props;
|
||||
this.loadLayers(formData, payload);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
const { formData, payload } = nextProps;
|
||||
const hasChanges = !_.isEqual(this.props.formData.deck_slices, nextProps.formData.deck_slices);
|
||||
if (hasChanges) {
|
||||
this.loadLayers(formData, payload);
|
||||
}
|
||||
}
|
||||
|
||||
onViewportChange(viewport) {
|
||||
this.setState({ viewport });
|
||||
}
|
||||
|
||||
loadLayers(formData, payload, viewport) {
|
||||
this.setState({ subSlicesLayers: {}, viewport });
|
||||
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
|
||||
const filters = [
|
||||
...(subslice.form_data.filters || []),
|
||||
...(formData.filters || []),
|
||||
...(formData.extra_filters || []),
|
||||
];
|
||||
const subsliceCopy = {
|
||||
...subslice,
|
||||
form_data: {
|
||||
...subslice.form_data,
|
||||
filters,
|
||||
},
|
||||
};
|
||||
|
||||
SupersetClient.get({
|
||||
endpoint: getExploreLongUrl(subsliceCopy.form_data, 'json'),
|
||||
})
|
||||
.then(({ json }) => {
|
||||
const layer = layerGenerators[subsliceCopy.form_data.viz_type](
|
||||
subsliceCopy.form_data,
|
||||
json,
|
||||
this.props.onAddFilter,
|
||||
this.props.setTooltip,
|
||||
[],
|
||||
this.props.onSelect,
|
||||
);
|
||||
this.setState({
|
||||
subSlicesLayers: {
|
||||
...this.state.subSlicesLayers,
|
||||
[subsliceCopy.slice_id]: layer,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { payload, formData, setControlValue } = this.props;
|
||||
const { subSlicesLayers } = this.state;
|
||||
|
||||
const layers = Object.values(subSlicesLayers);
|
||||
|
||||
return (
|
||||
<DeckGLContainer
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
viewport={this.state.viewport || this.props.viewport}
|
||||
onViewportChange={this.onViewportChange}
|
||||
layers={layers}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DeckMulti.propTypes = propTypes;
|
||||
DeckMulti.defaultProps = defaultProps;
|
||||
|
||||
export default DeckMulti;
|
||||
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 968 KiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
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'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class MultiChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Multi.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
const propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
||||
export default class TooltipRow extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div>{this.props.label}<strong>{this.props.value}</strong></div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TooltipRow.propTypes = propTypes;
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* 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 { isEqual } from 'lodash';
|
||||
|
||||
import DeckGLContainer from './DeckGLContainer';
|
||||
import CategoricalDeckGLContainer from './CategoricalDeckGLContainer';
|
||||
import { fitViewport } from './layers/common';
|
||||
|
||||
const propTypes = {
|
||||
formData: PropTypes.object.isRequired,
|
||||
payload: PropTypes.object.isRequired,
|
||||
setControlValue: PropTypes.func.isRequired,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
onAddFilter: PropTypes.func,
|
||||
setTooltip: PropTypes.func,
|
||||
};
|
||||
const defaultProps = {
|
||||
onAddFilter() {},
|
||||
setTooltip() {},
|
||||
};
|
||||
|
||||
export function createDeckGLComponent(getLayer, getPoints) {
|
||||
// Higher order component
|
||||
class Component extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const originalViewport = props.viewport;
|
||||
const viewport = props.formData.autozoom
|
||||
? fitViewport(originalViewport, getPoints(props.payload.data.features))
|
||||
: originalViewport;
|
||||
this.state = {
|
||||
viewport,
|
||||
layer: this.computeLayer(props),
|
||||
};
|
||||
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
|
||||
) {
|
||||
this.setState({ layer: this.computeLayer(nextProps) });
|
||||
}
|
||||
}
|
||||
onViewportChange(viewport) {
|
||||
this.setState({ viewport });
|
||||
}
|
||||
computeLayer(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;
|
||||
return (
|
||||
<DeckGLContainer
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
viewport={viewport}
|
||||
layers={[layer]}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
onViewportChange={this.onViewportChange}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
Component.propTypes = propTypes;
|
||||
Component.defaultProps = defaultProps;
|
||||
return Component;
|
||||
}
|
||||
|
||||
export function createCategoricalDeckGLComponent(getLayer, getPoints) {
|
||||
function Component(props) {
|
||||
const {
|
||||
formData,
|
||||
payload,
|
||||
setControlValue,
|
||||
onAddFilter,
|
||||
setTooltip,
|
||||
viewport,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<CategoricalDeckGLContainer
|
||||
formData={formData}
|
||||
mapboxApiKey={payload.data.mapboxApiKey}
|
||||
setControlValue={setControlValue}
|
||||
viewport={viewport}
|
||||
getLayer={getLayer}
|
||||
payload={payload}
|
||||
onAddFilter={onAddFilter}
|
||||
setTooltip={setTooltip}
|
||||
getPoints={getPoints}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Component.propTypes = propTypes;
|
||||
Component.defaultProps = defaultProps;
|
||||
|
||||
return Component;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
export { default as DeckGLChartPreset } from './preset';
|
||||
export { default as ArcChartPlugin } from './layers/Arc';
|
||||
export { default as GeoJsonChartPlugin } from './layers/Geojson';
|
||||
export { default as GridChartPlugin } from './layers/Grid';
|
||||
export { default as HexChartPlugin } from './layers/Hex';
|
||||
export { default as MultiChartPlugin } from './Multi';
|
||||
export { default as PathChartPlugin } from './layers/Path';
|
||||
export { default as PolygonChartPlugin } from './layers/Polygon';
|
||||
export { default as ScatterChartPlugin } from './layers/Scatter';
|
||||
export { default as ScreengridChartPlugin } from './layers/Screengrid';
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* 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 { ArcLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { commonLayerProps } from '../common';
|
||||
import { createCategoricalDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function getPoints(data) {
|
||||
const points = [];
|
||||
data.forEach((d) => {
|
||||
points.push(d.sourcePosition);
|
||||
points.push(d.targetPosition);
|
||||
});
|
||||
return points;
|
||||
}
|
||||
|
||||
function setTooltipContent(formData) {
|
||||
return o => (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Start (Longitude, Latitude)')}: `} value={`${o.object.sourcePosition[0]}, ${o.object.sourcePosition[1]}`} />
|
||||
<TooltipRow label={`${t('End (Longitude, Latitude)')}: `} value={`${o.object.targetPosition[0]}, ${o.object.targetPosition[1]}`} />
|
||||
{
|
||||
formData.dimension && <TooltipRow label={`${formData.dimension}: `} value={`${o.object.cat_color}`} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent(fd)),
|
||||
});
|
||||
}
|
||||
|
||||
export default createCategoricalDeckGLComponent(getLayer, getPoints);
|
||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
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'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class ArcChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Arc.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* 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 { 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 { commonLayerProps } from '../common';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
const propertyMap = {
|
||||
fillColor: 'fillColor',
|
||||
color: 'fillColor',
|
||||
fill: 'fillColor',
|
||||
'fill-color': 'fillColor',
|
||||
strokeColor: 'strokeColor',
|
||||
'stroke-color': 'strokeColor',
|
||||
'stroke-width': 'strokeWidth',
|
||||
};
|
||||
|
||||
const alterProps = (props, propOverrides) => {
|
||||
const newProps = {};
|
||||
Object.keys(props).forEach((k) => {
|
||||
if (k in propertyMap) {
|
||||
newProps[propertyMap[k]] = props[k];
|
||||
} else {
|
||||
newProps[k] = props[k];
|
||||
}
|
||||
});
|
||||
if (typeof props.fillColor === 'string') {
|
||||
newProps.fillColor = hexToRGB(props.fillColor);
|
||||
}
|
||||
if (typeof props.strokeColor === 'string') {
|
||||
newProps.strokeColor = hexToRGB(props.strokeColor);
|
||||
}
|
||||
return {
|
||||
...newProps,
|
||||
...propOverrides,
|
||||
};
|
||||
};
|
||||
let features;
|
||||
const recurseGeoJson = (node, propOverrides, extraProps) => {
|
||||
if (node && node.features) {
|
||||
node.features.forEach((obj) => {
|
||||
recurseGeoJson(obj, propOverrides, node.extraProps || extraProps);
|
||||
});
|
||||
}
|
||||
if (node && node.geometry) {
|
||||
const newNode = {
|
||||
...node,
|
||||
properties: alterProps(node.properties, propOverrides),
|
||||
};
|
||||
if (!newNode.extraProps) {
|
||||
newNode.extraProps = extraProps;
|
||||
}
|
||||
features.push(newNode);
|
||||
}
|
||||
};
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
o.object.extraProps &&
|
||||
<div className="deckgl-tooltip">
|
||||
{
|
||||
Object.keys(o.object.extraProps).map((prop, index) =>
|
||||
<TooltipRow key={`prop-${index}`} label={`${prop}: `} value={`${o.object.extraProps[prop]}`} />,
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const fc = fd.fill_color_picker;
|
||||
const sc = fd.stroke_color_picker;
|
||||
const fillColor = [fc.r, fc.g, fc.b, 255 * fc.a];
|
||||
const strokeColor = [sc.r, sc.g, sc.b, 255 * sc.a];
|
||||
const propOverrides = {};
|
||||
if (fillColor[3] > 0) {
|
||||
propOverrides.fillColor = fillColor;
|
||||
}
|
||||
if (strokeColor[3] > 0) {
|
||||
propOverrides.strokeColor = strokeColor;
|
||||
}
|
||||
|
||||
features = [];
|
||||
recurseGeoJson(payload.data, propOverrides);
|
||||
|
||||
let jsFnMutator;
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
features = jsFnMutator(features);
|
||||
}
|
||||
|
||||
return new GeoJsonLayer({
|
||||
id: `geojson-layer-${fd.slice_id}`,
|
||||
filled: fd.filled,
|
||||
data: features,
|
||||
stroked: fd.stroked,
|
||||
extruded: fd.extruded,
|
||||
pointRadiusScale: fd.point_radius_scale,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
formData: PropTypes.object.isRequired,
|
||||
payload: PropTypes.object.isRequired,
|
||||
setControlValue: PropTypes.func.isRequired,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
onAddFilter: PropTypes.func,
|
||||
setTooltip: PropTypes.func,
|
||||
};
|
||||
const defaultProps = {
|
||||
onAddFilter() {},
|
||||
setTooltip() {},
|
||||
};
|
||||
|
||||
function deckGeoJson(props) {
|
||||
const {
|
||||
formData,
|
||||
payload,
|
||||
setControlValue,
|
||||
onAddFilter,
|
||||
setTooltip,
|
||||
viewport,
|
||||
} = props;
|
||||
|
||||
// TODO get this to work
|
||||
// if (formData.autozoom) {
|
||||
// viewport = common.fitViewport(viewport, geojsonExtent(payload.data.features));
|
||||
// }
|
||||
|
||||
const layer = getLayer(formData, payload, onAddFilter, setTooltip);
|
||||
|
||||
return (
|
||||
<DeckGLContainer
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
viewport={viewport}
|
||||
layers={[layer]}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
deckGeoJson.propTypes = propTypes;
|
||||
deckGeoJson.defaultProps = defaultProps;
|
||||
|
||||
export default deckGeoJson;
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
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'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class GeojsonChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Geojson.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* 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 { GridLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import { commonLayerProps, getAggFunc } from '../common';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { createDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Longitude and Latitude')}: `} value={`${o.object.position[0]}, ${o.object.position[1]}`} />
|
||||
<TooltipRow label={`${t('Height')}: `} value={`${o.object.elevationValue}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const c = fd.color_picker;
|
||||
let data = payload.data.features.map(d => ({
|
||||
...d,
|
||||
color: [c.r, c.g, c.b, 255 * c.a],
|
||||
}));
|
||||
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);
|
||||
return new GridLayer({
|
||||
id: `grid-layer-${fd.slice_id}`,
|
||||
data,
|
||||
pickable: true,
|
||||
cellSize: fd.grid_size,
|
||||
minColor: [0, 0, 0, 0],
|
||||
extruded: fd.extruded,
|
||||
maxColor: [c.r, c.g, c.b, 255 * c.a],
|
||||
outline: false,
|
||||
getElevationValue: aggFunc,
|
||||
getColorValue: aggFunc,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
function getPoints(data) {
|
||||
return data.map(d => d.position);
|
||||
}
|
||||
|
||||
export default createDeckGLComponent(getLayer, getPoints);
|
||||
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
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'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class GridChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Grid.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* 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 { HexagonLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import { commonLayerProps, getAggFunc } from '../common';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { createDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Centroid (Longitude and Latitude)')}: `} value={`(${o.object.centroid[0]}, ${o.object.centroid[1]})`} />
|
||||
<TooltipRow label={`${t('Height')}: `} value={`${o.object.elevationValue}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const c = fd.color_picker;
|
||||
let data = payload.data.features.map(d => ({
|
||||
...d,
|
||||
color: [c.r, c.g, c.b, 255 * c.a],
|
||||
}));
|
||||
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);
|
||||
return new HexagonLayer({
|
||||
id: `hex-layer-${fd.slice_id}`,
|
||||
data,
|
||||
pickable: true,
|
||||
radius: fd.grid_size,
|
||||
minColor: [0, 0, 0, 0],
|
||||
extruded: fd.extruded,
|
||||
maxColor: [c.r, c.g, c.b, 255 * c.a],
|
||||
outline: false,
|
||||
getElevationValue: aggFunc,
|
||||
getColorValue: aggFunc,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
function getPoints(data) {
|
||||
return data.map(d => d.position);
|
||||
}
|
||||
|
||||
export default createDeckGLComponent(getLayer, getPoints);
|
||||
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
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'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class HexChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Hex.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* 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 { PathLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { commonLayerProps } from '../common';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { createDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
o.object.extraProps &&
|
||||
<div className="deckgl-tooltip">
|
||||
{
|
||||
Object.keys(o.object.extraProps).map((prop, index) =>
|
||||
<TooltipRow key={`prop-${index}`} label={`${prop}: `} value={`${o.object.extraProps[prop]}`} />,
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const c = fd.color_picker;
|
||||
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
|
||||
let data = payload.data.features.map(feature => ({
|
||||
...feature,
|
||||
path: feature.path,
|
||||
width: fd.line_width,
|
||||
color: fixedColor,
|
||||
}));
|
||||
|
||||
if (fd.js_data_mutator) {
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
return new PathLayer({
|
||||
id: `path-layer-${fd.slice_id}`,
|
||||
data,
|
||||
rounded: true,
|
||||
widthScale: 1,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
function getPoints(data) {
|
||||
let points = [];
|
||||
data.forEach((d) => {
|
||||
points = points.concat(d.path);
|
||||
});
|
||||
return points;
|
||||
}
|
||||
|
||||
export default createDeckGLComponent(getLayer, getPoints);
|
||||
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 511 KiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
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'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class PathChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Path.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
/* eslint no-underscore-dangle: ["error", { "allow": ["", "__timestamp"] }] */
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { PolygonLayer } from 'deck.gl';
|
||||
|
||||
import AnimatableDeckGLContainer from '../../AnimatableDeckGLContainer';
|
||||
import Legend from '../../../Legend';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
import { getBuckets, getBreakPointColorScaler } from '../../utils';
|
||||
|
||||
import { commonLayerProps, fitViewport } from '../common';
|
||||
import { getPlaySliderParams } from '../../../../modules/time';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
|
||||
const DOUBLE_CLICK_TRESHOLD = 250; // milliseconds
|
||||
|
||||
function getPoints(features) {
|
||||
return features.map(d => d.polygon).flat();
|
||||
}
|
||||
|
||||
function getElevation(d, colorScaler) {
|
||||
/* in deck.gl 5.3.4 (used in Superset as of 2018-10-24), if a polygon has
|
||||
* opacity zero it will make everything behind it have opacity zero,
|
||||
* effectively showing the map layer no matter what other polygons are
|
||||
* behind it.
|
||||
*/
|
||||
return colorScaler(d)[3] === 0
|
||||
? 0
|
||||
: d.elevation;
|
||||
}
|
||||
|
||||
function setTooltipContent(formData) {
|
||||
return (o) => {
|
||||
const metricLabel = formData.metric.label || formData.metric;
|
||||
return (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${formData.line_column}: `} value={`${o.object[formData.line_column]}`} />
|
||||
{formData.metric && <TooltipRow label={`${metricLabel}: `} value={`${o.object[metricLabel]}`} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip, selected, onSelect, filters) {
|
||||
const fd = formData;
|
||||
const fc = fd.fill_color_picker;
|
||||
const sc = fd.stroke_color_picker;
|
||||
let data = [...payload.data.features];
|
||||
|
||||
if (filters != null) {
|
||||
filters.forEach((f) => {
|
||||
data = data.filter(f);
|
||||
});
|
||||
}
|
||||
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// when polygons are selected, reduce the opacity of non-selected polygons
|
||||
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;
|
||||
return new PolygonLayer({
|
||||
id: `path-layer-${fd.slice_id}`,
|
||||
data,
|
||||
pickable: true,
|
||||
filled: fd.filled,
|
||||
stroked: fd.stroked,
|
||||
getPolygon: d => d.polygon,
|
||||
getFillColor: colorScaler,
|
||||
getLineColor: [sc.r, sc.g, sc.b, 255 * sc.a],
|
||||
getLineWidth: fd.line_width,
|
||||
extruded: fd.extruded,
|
||||
getElevation: d => getElevation(d, colorScaler),
|
||||
elevationScale: fd.multiplier,
|
||||
fp64: true,
|
||||
...commonLayerProps(fd, setTooltip, tooltipContentGenerator, onSelect),
|
||||
});
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
formData: PropTypes.object.isRequired,
|
||||
payload: PropTypes.object.isRequired,
|
||||
setControlValue: PropTypes.func.isRequired,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
onAddFilter: PropTypes.func,
|
||||
setTooltip: PropTypes.func,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onAddFilter() {},
|
||||
setTooltip() {},
|
||||
};
|
||||
|
||||
class DeckGLPolygon extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = DeckGLPolygon.getDerivedStateFromProps(props);
|
||||
|
||||
this.getLayers = this.getLayers.bind(this);
|
||||
this.onSelect = this.onSelect.bind(this);
|
||||
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
|
||||
// slider position due to changes in form controls
|
||||
if (state && props.payload.form_data === state.formData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const features = props.payload.data.features || [];
|
||||
const timestamps = features.map(f => f.__timestamp);
|
||||
|
||||
// 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 {
|
||||
start,
|
||||
end,
|
||||
getStep,
|
||||
values,
|
||||
disabled,
|
||||
} = getPlaySliderParams(timestamps, granularity);
|
||||
|
||||
const viewport = props.formData.autozoom
|
||||
? fitViewport(props.viewport, getPoints(features))
|
||||
: props.viewport;
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
getStep,
|
||||
values,
|
||||
disabled,
|
||||
viewport,
|
||||
selected: [],
|
||||
lastClick: 0,
|
||||
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;
|
||||
|
||||
// toggle selected polygons
|
||||
const selected = [...this.state.selected];
|
||||
if (doubleClick) {
|
||||
selected.splice(0, selected.length, polygon);
|
||||
} else if (formData.toggle_polygons) {
|
||||
const i = selected.indexOf(polygon);
|
||||
if (i === -1) {
|
||||
selected.push(polygon);
|
||||
} else {
|
||||
selected.splice(i, 1);
|
||||
}
|
||||
} else {
|
||||
selected.splice(0, 1, polygon);
|
||||
}
|
||||
|
||||
this.setState({ selected, lastClick: now });
|
||||
if (formData.table_filter) {
|
||||
onAddFilter(formData.line_column, selected, false, true);
|
||||
}
|
||||
}
|
||||
onValuesChange(values) {
|
||||
this.setState({
|
||||
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 [];
|
||||
}
|
||||
|
||||
const filters = [];
|
||||
|
||||
// time filter
|
||||
if (values[0] === values[1] || values[1] === this.end) {
|
||||
filters.push(d => d.__timestamp >= values[0] && d.__timestamp <= values[1]);
|
||||
} else {
|
||||
filters.push(d => d.__timestamp >= values[0] && d.__timestamp < values[1]);
|
||||
}
|
||||
|
||||
const layer = getLayer(
|
||||
this.props.formData,
|
||||
this.props.payload,
|
||||
this.props.onAddFilter,
|
||||
this.props.setTooltip,
|
||||
this.state.selected,
|
||||
this.onSelect,
|
||||
filters);
|
||||
|
||||
return [layer];
|
||||
}
|
||||
render() {
|
||||
const { payload, formData, setControlValue } = this.props;
|
||||
const { start, end, getStep, values, disabled, viewport } = this.state;
|
||||
|
||||
const fd = formData;
|
||||
const metricLabel = fd.metric ? fd.metric.label || fd.metric : null;
|
||||
const accessor = d => d[metricLabel];
|
||||
|
||||
const buckets = getBuckets(formData, payload.data.features, accessor);
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<AnimatableDeckGLContainer
|
||||
getLayers={this.getLayers}
|
||||
start={start}
|
||||
end={end}
|
||||
getStep={getStep}
|
||||
values={values}
|
||||
onValuesChange={this.onValuesChange}
|
||||
disabled={disabled}
|
||||
viewport={viewport}
|
||||
onViewportChange={this.onViewportChange}
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
aggregation
|
||||
>
|
||||
{formData.metric !== null &&
|
||||
<Legend
|
||||
categories={buckets}
|
||||
position={formData.legend_position}
|
||||
format={formData.legend_format}
|
||||
/>}
|
||||
</AnimatableDeckGLContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DeckGLPolygon.propTypes = propTypes;
|
||||
DeckGLPolygon.defaultProps = defaultProps;
|
||||
|
||||
export default DeckGLPolygon;
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 433 KiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
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'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class PolygonChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Polygon.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* 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 { ScatterplotLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { commonLayerProps } from '../common';
|
||||
import { createCategoricalDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
import { unitToRadius } from '../../../../modules/geo';
|
||||
|
||||
function getPoints(data) {
|
||||
return data.map(d => d.position);
|
||||
}
|
||||
|
||||
function setTooltipContent(formData) {
|
||||
return o => (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Longitude and Latitude')}: `} value={`${o.object.position[0]}, ${o.object.position[1]}`} />
|
||||
{
|
||||
o.object.cat_color && <TooltipRow label={`${t('Category')}: `} value={`${o.object.cat_color}`} />
|
||||
}
|
||||
{
|
||||
o.object.metric && <TooltipRow label={`${formData.point_radius_fixed.value.label}: `} value={`${o.object.metric}`} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const dataWithRadius = payload.data.features.map((d) => {
|
||||
let radius = unitToRadius(fd.point_unit, d.radius) || 10;
|
||||
if (fd.multiplier) {
|
||||
radius *= fd.multiplier;
|
||||
}
|
||||
if (d.color) {
|
||||
return { ...d, radius };
|
||||
}
|
||||
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 };
|
||||
});
|
||||
|
||||
return new ScatterplotLayer({
|
||||
id: `scatter-layer-${fd.slice_id}`,
|
||||
data: dataWithRadius,
|
||||
fp64: true,
|
||||
radiusMinPixels: fd.min_radius || null,
|
||||
radiusMaxPixels: fd.max_radius || null,
|
||||
outline: false,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent(fd)),
|
||||
});
|
||||
}
|
||||
|
||||
export default createCategoricalDeckGLComponent(getLayer, getPoints);
|
||||
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 777 KiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
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'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class ScatterChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Scatter.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
/* eslint no-underscore-dangle: ["error", { "allow": ["", "__timestamp"] }] */
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScreenGridLayer } from 'deck.gl';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import AnimatableDeckGLContainer from '../../AnimatableDeckGLContainer';
|
||||
import { getPlaySliderParams } from '../../../../modules/time';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { commonLayerProps, fitViewport } from '../common';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function getPoints(data) {
|
||||
return data.map(d => d.position);
|
||||
}
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Longitude and Latitude')}: `} value={`${o.object.position[0]}, ${o.object.position[1]}`} />
|
||||
<TooltipRow label={`${t('Weight')}: `} value={`${o.object.weight}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip, selected, onSelect, filters) {
|
||||
const fd = formData;
|
||||
const c = fd.color_picker;
|
||||
let data = payload.data.features.map(d => ({
|
||||
...d,
|
||||
color: [c.r, c.g, c.b, 255 * c.a],
|
||||
}));
|
||||
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
if (filters != null) {
|
||||
filters.forEach((f) => {
|
||||
data = data.filter(f);
|
||||
});
|
||||
}
|
||||
|
||||
// Passing a layer creator function instead of a layer since the
|
||||
// layer needs to be regenerated at each render
|
||||
return new ScreenGridLayer({
|
||||
id: `screengrid-layer-${fd.slice_id}`,
|
||||
data,
|
||||
pickable: true,
|
||||
cellSizePixels: fd.grid_size,
|
||||
minColor: [c.r, c.g, c.b, 0],
|
||||
maxColor: [c.r, c.g, c.b, 255 * c.a],
|
||||
outline: false,
|
||||
getWeight: d => d.weight || 0,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
formData: PropTypes.object.isRequired,
|
||||
payload: PropTypes.object.isRequired,
|
||||
setControlValue: PropTypes.func.isRequired,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
onAddFilter: PropTypes.func,
|
||||
setTooltip: PropTypes.func,
|
||||
};
|
||||
const defaultProps = {
|
||||
onAddFilter() {},
|
||||
setTooltip() {},
|
||||
};
|
||||
|
||||
class DeckGLScreenGrid extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = DeckGLScreenGrid.getDerivedStateFromProps(props);
|
||||
|
||||
this.getLayers = this.getLayers.bind(this);
|
||||
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
|
||||
// slider position due to changes in form controls
|
||||
if (state && props.payload.form_data === state.formData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const features = props.payload.data.features || [];
|
||||
const timestamps = features.map(f => f.__timestamp);
|
||||
|
||||
// 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 {
|
||||
start,
|
||||
end,
|
||||
getStep,
|
||||
values,
|
||||
disabled,
|
||||
} = getPlaySliderParams(timestamps, granularity);
|
||||
|
||||
const viewport = props.formData.autozoom
|
||||
? fitViewport(props.viewport, getPoints(features))
|
||||
: props.viewport;
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
getStep,
|
||||
values,
|
||||
disabled,
|
||||
viewport,
|
||||
selected: [],
|
||||
lastClick: 0,
|
||||
formData: props.payload.form_data,
|
||||
};
|
||||
}
|
||||
onValuesChange(values) {
|
||||
this.setState({
|
||||
values: Array.isArray(values)
|
||||
? values
|
||||
: [values, values + this.state.getStep(values)],
|
||||
});
|
||||
}
|
||||
onViewportChange(viewport) {
|
||||
this.setState({ viewport });
|
||||
}
|
||||
getLayers(values) {
|
||||
const filters = [];
|
||||
|
||||
// time filter
|
||||
if (values[0] === values[1] || values[1] === this.end) {
|
||||
filters.push(d => d.__timestamp >= values[0] && d.__timestamp <= values[1]);
|
||||
} else {
|
||||
filters.push(d => d.__timestamp >= values[0] && d.__timestamp < values[1]);
|
||||
}
|
||||
|
||||
const layer = getLayer(
|
||||
this.props.formData,
|
||||
this.props.payload,
|
||||
this.props.onAddFilter,
|
||||
this.props.setTooltip,
|
||||
filters);
|
||||
|
||||
return [layer];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formData, payload, setControlValue } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<AnimatableDeckGLContainer
|
||||
getLayers={this.getLayers}
|
||||
start={this.state.start}
|
||||
end={this.state.end}
|
||||
getStep={this.state.getStep}
|
||||
values={this.state.values}
|
||||
onValuesChange={this.onValuesChange}
|
||||
disabled={this.state.disabled}
|
||||
viewport={this.state.viewport}
|
||||
onViewportChange={this.onViewportChange}
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
aggregation
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DeckGLScreenGrid.propTypes = propTypes;
|
||||
DeckGLScreenGrid.defaultProps = defaultProps;
|
||||
|
||||
export default DeckGLScreenGrid;
|
||||
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 578 KiB |
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 { t } from '@superset-ui/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
import thumbnail from './images/thumbnail.png';
|
||||
import transformProps from '../../transformProps';
|
||||
|
||||
const metadata = new ChartMetadata({
|
||||
name: t('deck.gl Screen Grid'),
|
||||
description: '',
|
||||
credits: ['https://uber.github.io/deck.gl'],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class ScreengridChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Screengrid.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* 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 { fitBounds } from 'viewport-mercator-project';
|
||||
import * as d3array from 'd3-array';
|
||||
import sandboxedEval from '../../../modules/sandbox';
|
||||
|
||||
const PADDING = 0.25;
|
||||
const GEO_BOUNDS = {
|
||||
LAT_MIN: -90,
|
||||
LAT_MAX: 90,
|
||||
LNG_MIN: -180,
|
||||
LNG_MAX: 180,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the latitude bounds if latitude is a single coordinate
|
||||
* @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;
|
||||
return [latMin, latMax];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the longitude bounds if longitude is a single coordinate
|
||||
* @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;
|
||||
return [lngMin, lngMax];
|
||||
}
|
||||
|
||||
export function getBounds(points) {
|
||||
const latExt = d3array.extent(points, d => d[1]);
|
||||
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]],
|
||||
];
|
||||
}
|
||||
|
||||
export function fitViewport(viewport, points, padding = 10) {
|
||||
try {
|
||||
const bounds = getBounds(points);
|
||||
return {
|
||||
...viewport,
|
||||
...fitBounds({
|
||||
height: viewport.height,
|
||||
width: viewport.width,
|
||||
padding,
|
||||
bounds,
|
||||
}),
|
||||
};
|
||||
} catch (e) {
|
||||
/* eslint no-console: 0 */
|
||||
console.error('Could not auto zoom', e);
|
||||
return viewport;
|
||||
}
|
||||
}
|
||||
|
||||
export function commonLayerProps(formData, setTooltip, setTooltipContent, onSelect) {
|
||||
const fd = formData;
|
||||
let onHover;
|
||||
let tooltipContentGenerator = setTooltipContent;
|
||||
if (fd.js_tooltip) {
|
||||
tooltipContentGenerator = sandboxedEval(fd.js_tooltip);
|
||||
}
|
||||
if (tooltipContentGenerator) {
|
||||
onHover = (o) => {
|
||||
if (o.picked) {
|
||||
setTooltip({
|
||||
content: tooltipContentGenerator(o),
|
||||
x: o.x,
|
||||
y: o.y + 30,
|
||||
});
|
||||
} else {
|
||||
setTooltip(null);
|
||||
}
|
||||
};
|
||||
}
|
||||
let onClick;
|
||||
if (fd.js_onclick_href) {
|
||||
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,
|
||||
pickable: Boolean(onHover),
|
||||
};
|
||||
}
|
||||
|
||||
const percentiles = {
|
||||
p1: 0.01,
|
||||
p5: 0.05,
|
||||
p95: 0.95,
|
||||
p99: 0.99,
|
||||
};
|
||||
|
||||
/* Get an a stat function that operates on arrays, aligns with control=js_agg_function */
|
||||
export function getAggFunc(type = 'sum', accessor = null) {
|
||||
if (type === 'count') {
|
||||
return arr => arr.length;
|
||||
}
|
||||
let d3func;
|
||||
if (type in percentiles) {
|
||||
d3func = (arr, acc) => {
|
||||
let sortedArr;
|
||||
if (accessor) {
|
||||
sortedArr = arr.sort((o1, o2) => d3array.ascending(accessor(o1), accessor(o2)));
|
||||
} else {
|
||||
sortedArr = arr.sort(d3array.ascending);
|
||||
}
|
||||
return d3array.quantile(sortedArr, percentiles[type], acc);
|
||||
};
|
||||
} else {
|
||||
d3func = d3array[type];
|
||||
}
|
||||
if (!accessor) {
|
||||
return arr => d3func(arr);
|
||||
}
|
||||
return arr => d3func(arr.map(accessor));
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
/* eslint camelcase: 0 */
|
||||
import { getLayer as deck_grid } from './Grid/Grid';
|
||||
import { getLayer as deck_screengrid } from './Screengrid/Screengrid';
|
||||
import { getLayer as deck_path } from './Path/Path';
|
||||
import { getLayer as deck_hex } from './Hex/Hex';
|
||||
import { getLayer as deck_scatter } from './Scatter/Scatter';
|
||||
import { getLayer as deck_geojson } from './Geojson/Geojson';
|
||||
import { getLayer as deck_arc } from './Arc/Arc';
|
||||
import { getLayer as deck_polygon } from './Polygon/Polygon';
|
||||
|
||||
const layerGenerators = {
|
||||
deck_grid,
|
||||
deck_screengrid,
|
||||
deck_path,
|
||||
deck_hex,
|
||||
deck_scatter,
|
||||
deck_geojson,
|
||||
deck_arc,
|
||||
deck_polygon,
|
||||
};
|
||||
|
||||
export default layerGenerators;
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* 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 { Preset } from '@superset-ui/core';
|
||||
import ArcChartPlugin from './layers/Arc';
|
||||
import GeoJsonChartPlugin from './layers/Geojson';
|
||||
import GridChartPlugin from './layers/Grid';
|
||||
import HexChartPlugin from './layers/Hex';
|
||||
import MultiChartPlugin from './Multi';
|
||||
import PathChartPlugin from './layers/Path';
|
||||
import PolygonChartPlugin from './layers/Polygon';
|
||||
import ScatterChartPlugin from './layers/Scatter';
|
||||
import ScreengridChartPlugin from './layers/Screengrid';
|
||||
|
||||
export default class DeckGLChartPreset extends Preset {
|
||||
constructor() {
|
||||
super({
|
||||
name: 'deck.gl charts',
|
||||
plugins: [
|
||||
new ArcChartPlugin().configure({ key: 'deck_arc' }),
|
||||
new GeoJsonChartPlugin().configure({ key: 'deck_geojson' }),
|
||||
new GridChartPlugin().configure({ key: 'deck_grid' }),
|
||||
new HexChartPlugin().configure({ key: 'deck_hex' }),
|
||||
new MultiChartPlugin().configure({ key: 'deck_multi' }),
|
||||
new PathChartPlugin().configure({ key: 'deck_path' }),
|
||||
new PolygonChartPlugin().configure({ key: 'deck_polygon' }),
|
||||
new ScatterChartPlugin().configure({ key: 'deck_scatter' }),
|
||||
new ScreengridChartPlugin().configure({ key: 'deck_screengrid' }),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
const NOOP = () => {};
|
||||
|
||||
export default function transformProps(chartProps) {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
rawFormData,
|
||||
queryData,
|
||||
hooks,
|
||||
} = chartProps;
|
||||
const { onAddFilter = NOOP, setControlValue = NOOP, setTooltip = NOOP } = hooks;
|
||||
|
||||
return {
|
||||
formData: rawFormData,
|
||||
payload: queryData,
|
||||
setControlValue,
|
||||
viewport: {
|
||||
...rawFormData.viewport,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
onAddFilter,
|
||||
setTooltip,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
* 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 { extent } from 'd3-array';
|
||||
import { scaleThreshold } from 'd3-scale';
|
||||
import { getSequentialSchemeRegistry, SequentialScheme } from '@superset-ui/color';
|
||||
import { hexToRGB } from '../../modules/colors';
|
||||
|
||||
const DEFAULT_NUM_BUCKETS = 10;
|
||||
|
||||
export function getBreakPoints(
|
||||
{ break_points: formDataBreakPoints, num_buckets: formDataNumBuckets },
|
||||
features,
|
||||
accessor,
|
||||
) {
|
||||
if (!features) {
|
||||
return [];
|
||||
}
|
||||
if (formDataBreakPoints === undefined || formDataBreakPoints.length === 0) {
|
||||
// compute evenly distributed break points based on number of buckets
|
||||
const numBuckets = formDataNumBuckets ? parseInt(formDataNumBuckets, 10) : DEFAULT_NUM_BUCKETS;
|
||||
const [minValue, maxValue] = extent(features, accessor);
|
||||
if (minValue === undefined) {
|
||||
return [];
|
||||
}
|
||||
const delta = (maxValue - minValue) / numBuckets;
|
||||
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)
|
||||
.fill()
|
||||
.map((_, i) => (minValue + i * delta).toFixed(precision));
|
||||
}
|
||||
|
||||
return formDataBreakPoints.sort((a, b) => parseFloat(a) - parseFloat(b));
|
||||
}
|
||||
|
||||
export function getBreakPointColorScaler(
|
||||
{
|
||||
break_points: formDataBreakPoints,
|
||||
num_buckets: formDataNumBuckets,
|
||||
linear_color_scheme: linearColorScheme,
|
||||
opacity,
|
||||
},
|
||||
features,
|
||||
accessor,
|
||||
) {
|
||||
const breakPoints =
|
||||
formDataBreakPoints || formDataNumBuckets
|
||||
? getBreakPoints(
|
||||
{
|
||||
break_points: formDataBreakPoints,
|
||||
num_buckets: formDataNumBuckets,
|
||||
},
|
||||
features,
|
||||
accessor,
|
||||
)
|
||||
: null;
|
||||
const colorScheme = Array.isArray(linearColorScheme)
|
||||
? new SequentialScheme({
|
||||
id: 'custom',
|
||||
colors: linearColorScheme,
|
||||
})
|
||||
: getSequentialSchemeRegistry().get(linearColorScheme);
|
||||
|
||||
let scaler;
|
||||
let maskPoint;
|
||||
if (breakPoints !== null) {
|
||||
// bucket colors into discrete colors
|
||||
const n = breakPoints.length - 1;
|
||||
const bucketedColors =
|
||||
n > 1 ? colorScheme.getColors(n) : [colorScheme.colors[colorScheme.colors.length - 1]];
|
||||
|
||||
// repeat ends
|
||||
const first = bucketedColors[0];
|
||||
const last = bucketedColors[bucketedColors.length - 1];
|
||||
bucketedColors.unshift(first);
|
||||
bucketedColors.push(last);
|
||||
|
||||
const points = breakPoints.map(p => parseFloat(p));
|
||||
scaler = scaleThreshold()
|
||||
.domain(points)
|
||||
.range(bucketedColors);
|
||||
maskPoint = value => value > breakPoints[n] || value < breakPoints[0];
|
||||
} else {
|
||||
// interpolate colors linearly
|
||||
scaler = colorScheme.createLinearScale(extent(features, accessor));
|
||||
maskPoint = () => false;
|
||||
}
|
||||
|
||||
return d => {
|
||||
const v = accessor(d);
|
||||
const c = hexToRGB(scaler(v));
|
||||
if (maskPoint(v)) {
|
||||
c[3] = 0;
|
||||
} else {
|
||||
c[3] = (opacity / 100.0) * 255;
|
||||
}
|
||||
|
||||
return c;
|
||||
};
|
||||
}
|
||||
|
||||
export function getBuckets(fd, features, accessor) {
|
||||
const breakPoints = getBreakPoints(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 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;
|
||||
buckets[range] = {
|
||||
color: colorScaler({ [metricLabel || fd.metric]: mid }),
|
||||
enabled: true,
|
||||
};
|
||||
});
|
||||
|
||||
return buckets;
|
||||
}
|
||||