diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/package.json b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/package.json index f10175b43448..03b4bba1dc3f 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/package.json +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/package.json @@ -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", diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/Stories.tsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/Stories.tsx deleted file mode 100644 index 403d09ae5dc6..000000000000 --- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/Stories.tsx +++ /dev/null @@ -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: () => ( - - ), - storyName: 'Basic', - storyPath: 'legacy-|plugin-chart-calendar|CalendarChartPlugin', - }, -]; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/data.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/data.js deleted file mode 100644 index b4a02b996efa..000000000000 --- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/data.js +++ /dev/null @@ -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', -}; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/index.js deleted file mode 100644 index 8a8a7f1a4ea2..000000000000 --- a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-plugins-demo/storybook/stories/legacy-plugin-chart-calendar/index.js +++ /dev/null @@ -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], -}; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/README.md b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/README.md new file mode 100644 index 000000000000..2a009657924e --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/README.md @@ -0,0 +1,43 @@ +## @superset-ui/legacy-preset-chart-nvd3 + +[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-nvd3.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-nvd3.svg?style=flat-square) +[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-legacy-preset-chart-nvd3&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 + +``` \ No newline at end of file diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/package.json b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/package.json new file mode 100644 index 000000000000..7f5a43e5b315 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/package.json @@ -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" + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/AnimatableDeckGLContainer.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/AnimatableDeckGLContainer.jsx new file mode 100644 index 000000000000..fe2c7165db7c --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/AnimatableDeckGLContainer.jsx @@ -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 ( +
+ + {!disabled && + + } + {children} +
+ ); + } +} + +AnimatableDeckGLContainer.propTypes = propTypes; +AnimatableDeckGLContainer.defaultProps = defaultProps; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/CategoricalDeckGLContainer.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/CategoricalDeckGLContainer.jsx new file mode 100644 index 000000000000..2a3e7b3c717a --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/CategoricalDeckGLContainer.jsx @@ -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 ( +
+ + + +
+ ); + } +} + +CategoricalDeckGLContainer.propTypes = propTypes; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/DeckGLContainer.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/DeckGLContainer.jsx new file mode 100644 index 000000000000..ff414bfc0ad9 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/DeckGLContainer.jsx @@ -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 ( + + + + ); + } +} + +DeckGLContainer.propTypes = propTypes; +DeckGLContainer.defaultProps = defaultProps; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/Multi.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/Multi.jsx new file mode 100644 index 000000000000..335ba3a7e02b --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/Multi.jsx @@ -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 ( + + ); + } +} + +DeckMulti.propTypes = propTypes; +DeckMulti.defaultProps = defaultProps; + +export default DeckMulti; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/images/thumbnail.png new file mode 100644 index 000000000000..acedd5ba5145 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/images/thumbnailLarge.png new file mode 100644 index 000000000000..21c27c048997 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/index.js new file mode 100644 index 000000000000..c3cae628ac6c --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/Multi/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/TooltipRow.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/TooltipRow.jsx new file mode 100644 index 000000000000..cc85bfd984ed --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/TooltipRow.jsx @@ -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 ( +
{this.props.label}{this.props.value}
+ ); + } +} + +TooltipRow.propTypes = propTypes; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/factory.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/factory.jsx new file mode 100644 index 000000000000..abbdcca0db43 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/factory.jsx @@ -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 ( + ); + } + } + 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 ( + + ); + } + + Component.propTypes = propTypes; + Component.defaultProps = defaultProps; + + return Component; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/index.js new file mode 100644 index 000000000000..c98f3d1d65ad --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/index.js @@ -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'; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/Arc.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/Arc.jsx new file mode 100644 index 000000000000..2c0a99b096ec --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/Arc.jsx @@ -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 => ( +
+ + + { + formData.dimension && + } +
+ ); +} + +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); diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/images/thumbnail.png new file mode 100644 index 000000000000..02b84b1e7535 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/images/thumbnailLarge.png new file mode 100644 index 000000000000..f79f28349191 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/index.js new file mode 100644 index 000000000000..8f597631da48 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Arc/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/Geojson.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/Geojson.jsx new file mode 100644 index 000000000000..7488a3ddf56a --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/Geojson.jsx @@ -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 && +
+ { + Object.keys(o.object.extraProps).map((prop, index) => + , + ) + } +
+ ); +} + +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 ( + + ); +} + +deckGeoJson.propTypes = propTypes; +deckGeoJson.defaultProps = defaultProps; + +export default deckGeoJson; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/images/thumbnail.png new file mode 100644 index 000000000000..9c1a732cd593 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/images/thumbnailLarge.png new file mode 100644 index 000000000000..acc452cf0325 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/index.js new file mode 100644 index 000000000000..06fded5de738 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Geojson/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/Grid.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/Grid.jsx new file mode 100644 index 000000000000..a0cc8613faff --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/Grid.jsx @@ -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 ( +
+ + +
+ ); +} + +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); diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/images/thumbnail.png new file mode 100644 index 000000000000..2710d9f128db Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/images/thumbnailLarge.png new file mode 100644 index 000000000000..cd9396510633 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/index.js new file mode 100644 index 000000000000..291b967afcdb --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Grid/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/Hex.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/Hex.jsx new file mode 100644 index 000000000000..9901b22628bd --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/Hex.jsx @@ -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 ( +
+ + +
+ ); +} + +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); diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/images/thumbnail.png new file mode 100644 index 000000000000..99149dbc109d Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/images/thumbnailLarge.png new file mode 100644 index 000000000000..31feff5c8fb0 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/index.js new file mode 100644 index 000000000000..940ae5bac0e7 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Hex/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/Path.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/Path.jsx new file mode 100644 index 000000000000..7bf0982419e2 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/Path.jsx @@ -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 && +
+ { + Object.keys(o.object.extraProps).map((prop, index) => + , + ) + } +
+ ); +} + +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); diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/images/thumbnail.png new file mode 100644 index 000000000000..d783a143312d Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/images/thumbnailLarge.png new file mode 100644 index 000000000000..eede9da44ce7 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/index.js new file mode 100644 index 000000000000..5b584c530a7d --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Path/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/Polygon.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/Polygon.jsx new file mode 100644 index 000000000000..891856d04302 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/Polygon.jsx @@ -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 ( +
+ + {formData.metric && } +
+ ); + }; +} + +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 ( +
+ + {formData.metric !== null && + } + +
+ ); + } +} + +DeckGLPolygon.propTypes = propTypes; +DeckGLPolygon.defaultProps = defaultProps; + +export default DeckGLPolygon; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/images/thumbnail.png new file mode 100644 index 000000000000..b32c540e8ba1 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/images/thumbnailLarge.png new file mode 100644 index 000000000000..dfae8616ffac Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/index.js new file mode 100644 index 000000000000..4b5f7c051365 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Polygon/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/Scatter.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/Scatter.jsx new file mode 100644 index 000000000000..42dd7c8df4a8 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/Scatter.jsx @@ -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 => ( +
+ + { + o.object.cat_color && + } + { + o.object.metric && + } +
+ ); +} + +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); diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/images/thumbnail.png new file mode 100644 index 000000000000..a111a158abc7 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/images/thumbnailLarge.png new file mode 100644 index 000000000000..11f38ccc8dbf Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/index.js new file mode 100644 index 000000000000..093a751d5e9d --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Scatter/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/Screengrid.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/Screengrid.jsx new file mode 100644 index 000000000000..d9fba4c70439 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/Screengrid.jsx @@ -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 ( +
+ + +
+ ); +} + +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 ( +
+ +
+ ); + } +} + +DeckGLScreenGrid.propTypes = propTypes; +DeckGLScreenGrid.defaultProps = defaultProps; + +export default DeckGLScreenGrid; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/images/thumbnail.png new file mode 100644 index 000000000000..78a26e67c14f Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/images/thumbnail.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/images/thumbnailLarge.png b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/images/thumbnailLarge.png new file mode 100644 index 000000000000..d5da29c99be9 Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/images/thumbnailLarge.png differ diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/index.js new file mode 100644 index 000000000000..dc96758fca5c --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/Screengrid/index.js @@ -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, + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/common.jsx b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/common.jsx new file mode 100644 index 000000000000..aaee55361500 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/common.jsx @@ -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)); +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/index.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/index.js new file mode 100644 index 000000000000..b77d5bd12c49 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/layers/index.js @@ -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; diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/preset.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/preset.js new file mode 100644 index 000000000000..9360bb44d38e --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/preset.js @@ -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' }), + ], + }); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/transformProps.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/transformProps.js new file mode 100644 index 000000000000..9e7350baed17 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/transformProps.js @@ -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, + }; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils.js b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils.js new file mode 100644 index 000000000000..246e31b46c71 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui-plugins-deckgl/packages/superset-ui-preset-chart-deckgl/src/utils.js @@ -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; +}