feat: add plugin for Kepler (#2)

* feat: add plugin for Kepler

* fix: add dep and update naming for consistency

* fix: lint issues
This commit is contained in:
Maxime Beauchemin 2019-10-21 16:54:33 -07:00 committed by Yongjie Zhao
parent 317f185d36
commit cac4e07bfe
12 changed files with 453 additions and 1 deletions

View File

@ -40,10 +40,10 @@
"license": "Apache-2.0",
"devDependencies": {
"@superset-ui/build-config": "^0.1.0",
"@superset-ui/commit-config": "^0.0.9",
"@superset-ui/chart": "^0.12.1",
"@superset-ui/chart-composition": "^0.12.1",
"@superset-ui/color": "^0.12.1",
"@superset-ui/commit-config": "^0.0.9",
"@superset-ui/connection": "^0.12.0",
"@superset-ui/core": "^0.12.0",
"@superset-ui/dimension": "^0.12.0",
@ -59,6 +59,7 @@
"husky": "^3.0.3",
"lerna": "^3.2.1",
"lint-staged": "^9.2.1",
"luma.gl": "^7.3.0",
"react": "^16.6.0",
"react-dom": "^16.6.0",
"yarn": "^1.9.4"

View File

@ -0,0 +1,40 @@
## @superset-ui/legacy-preset-chart-deckgl
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-deckgl.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-deckgl.svg?style=flat-square)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-legacy-preset-chart-deckgl&style=flat-square)](https://david-dm.org/apache-superset/superset-ui-plugins?path=packages/superset-ui-legacy-preset-chart-deckgl)
This plugin provides `deck.gl` for Superset.
### Usage
Import the preset and register. This will register all the chart plugins under `deck.gl`.
```js
import { DeckGLChartPreset } from '@superset-ui/legacy-preset-chart-deckgl';
new DeckGLChartPreset().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 { ArcChartPlugin } from '@superset-ui/legacy-preset-chart-deckgl';
new ArcChartPlugin()
.configure({ key: 'deck_arc' })
.register();
```
Then use it via `SuperChart`. See [storybook](https://apache-superset.github.io/superset-ui-plugins-deckgl) for more details.
```js
<SuperChart
chartType="deck_arc"
width={600}
height={600}
formData={...}
queryData={{
data: {...},
}}
/>
```

View File

@ -0,0 +1,49 @@
{
"name": "@superset-ui/legacy-preset-chart-kepler",
"version": "0.1.0",
"description": "Superset Chart - kerpler.gl",
"sideEffects": true,
"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": {
"kepler.gl": "1.1.8",
"lodash": "^4.17.15",
"mapbox-gl": "^0.53.0",
"prop-types": "^15.6.0",
"react-palm": "^3.1.2",
"shortid": "^2.2.15",
"styled-components": "^4.4.0",
"viewport-mercator-project": "^6.1.1"
},
"peerDependencies": {
"@superset-ui/chart": "^0.12.0",
"@superset-ui/color": "^0.12.0",
"@superset-ui/connection": "^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"
}
}

View File

@ -0,0 +1,20 @@
/**
* 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.
*/
.kepler {
}

View File

@ -0,0 +1,150 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable prefer-destructuring */
/* eslint-disable react/destructuring-assignment */
/* eslint-disable react/jsx-handler-names */
/* eslint-disable react/no-deprecated */
/* eslint-disable no-negated-condition */
/* eslint-disable react/prop-types */
/* eslint-disable react/require-default-props */
/* eslint-disable sort-keys */
/**
* 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 { connect, Provider } from 'react-redux';
import PropTypes from 'prop-types';
import KeplerGl from 'kepler.gl';
import KeplerGlSchema from 'kepler.gl/schemas';
import { addDataToMap } from 'kepler.gl/actions';
import Processors from 'kepler.gl/processors';
import shortid from 'shortid';
import getKeplerStore from './store';
import './Kepler.css';
const propTypes = {
height: PropTypes.number,
setControlValue: PropTypes.func,
readonly: PropTypes.boolean,
};
class Kepler extends React.PureComponent {
constructor(props) {
super(props);
this.setMapConfig = this.setMapConfig.bind(this);
this.state = {
keplerId: shortid.generate(),
};
}
componentDidMount() {
this.addDataToMap(this.props);
this.setMapConfig();
}
componentWillReceiveProps(nextProps) {
if (nextProps.features !== this.props.features) {
this.addDataToMap(nextProps, false);
this.setMapConfig();
}
}
getCurrentConfig() {
try {
const { keplerGl } = this.props;
return KeplerGlSchema.getConfigToSave(keplerGl[this.state.keplerId]);
} catch (e) {
return null;
}
}
setMapConfig() {
const { setControlValue } = this.props;
const config = this.getCurrentConfig();
if (config) {
setControlValue('config', JSON.stringify(this.getCurrentConfig(), null, 2));
}
}
addDataToMap(props, useControlConfig = true) {
let config = props.config;
if (!config) {
config = {};
} else {
config = useControlConfig ? JSON.parse(config) : this.getCurrentConfig();
}
const data = Processors.processRowObject(props.features);
const datasets = [
{
data,
info: {
id: 'main',
label: 'Superset Data',
},
},
];
const options = { readOnly: this.props.readonly };
if (this.props.autozoom) {
options.centerMap = true;
if (config && config.config) {
config.config.mapState = {};
}
}
props.dispatch(addDataToMap({ datasets, config, options }));
}
render() {
return (
<div>
<KeplerGl
id={this.state.keplerId}
onSaveMap={this.setMapConfig}
theme="light"
{...this.props}
/>
</div>
);
}
}
Kepler.displayName = 'Kepler';
Kepler.propTypes = propTypes;
const mapStateToProps = state => ({ keplerGl: state.keplerGl });
const dispatchToProps = dispatch => ({ dispatch });
const KeplerConnected = connect(
mapStateToProps,
dispatchToProps,
)(Kepler);
// eslint-disable-next-line react/no-multi-comp
export default class SubApp extends React.Component {
constructor(props) {
super(props);
this.store = getKeplerStore(props.setControlValue);
}
render() {
return (
<Provider store={this.store}>
<KeplerConnected {...this.props} />
</Provider>
);
}
}

View File

@ -0,0 +1,40 @@
/* eslint-disable sort-keys */
/**
* 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 transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
const metadata = new ChartMetadata({
name: t('Kepler.gl'),
description: '',
credits: ['https://github.com/uber/kepler.gl'],
thumbnail,
});
export default class KeplerChartPlugin extends ChartPlugin {
constructor() {
super({
metadata,
transformProps,
loadChart: () => import('./Kepler'),
});
}
}

View File

@ -0,0 +1,2 @@
export { default as KeplerChartPreset } from './preset';
export { default as KeplerChartPlugin } from './KeplerChartPlugin';

View File

@ -0,0 +1,29 @@
/**
* 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 KeplerChartPlugin from './KeplerChartPlugin';
export default class KeplerChartPreset extends Preset {
constructor() {
super({
name: 'Kepler charts',
plugins: [new KeplerChartPlugin().configure({ key: 'kepler' })],
});
}
}

View File

@ -0,0 +1,86 @@
/* eslint-disable babel/new-cap */
/* eslint-disable babel/no-invalid-this */
/* eslint-disable compat/compat */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-magic-numbers */
/* eslint-disable sort-keys */
/**
* 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 { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import keplerGlReducer from 'kepler.gl/reducers';
import KeplerGlSchema from 'kepler.gl/schemas';
import { taskMiddleware } from 'react-palm/tasks';
let previousValue = null;
// Rates limit and only runs the latest call
function RateLimit(fn, delay, context) {
let latestCall = null;
let timer = null;
function processLatestCall() {
if (latestCall) {
fn.apply(latestCall.context, latestCall.arguments);
clearInterval(timer);
timer = null;
}
}
return function limited(...args) {
latestCall = {
context: context || this,
arguments: [].slice.call(args),
};
if (timer) {
clearInterval(timer);
}
timer = setInterval(processLatestCall, delay);
};
}
function updateConfigControl(store, setControlValue) {
const keplerState = Object.values(store.getState().keplerGl)[0];
const stateToSave = KeplerGlSchema.getConfigToSave(keplerState);
const config = JSON.stringify(stateToSave, null, 2);
if (previousValue !== config) {
setControlValue('config', config);
previousValue = config;
}
}
const rateLimitedUpdateConfigControl = RateLimit(updateConfigControl, 1000);
export default function getKeplerStore(setControlValue) {
// Using react-palm middleware to intercept changes and
// save the state into the config control as text
const stateChangeMiddleware = store => next => action => {
const returnValue = next(action);
rateLimitedUpdateConfigControl(store, setControlValue);
return returnValue;
};
const reducers = combineReducers({
keplerGl: keplerGlReducer,
readonly: false,
});
const middlewares = [taskMiddleware, stateChangeMiddleware];
const enhancers = [applyMiddleware(...middlewares)];
const store = createStore(reducers, {}, compose(...enhancers));
return store;
}

View File

@ -0,0 +1,35 @@
/* eslint-disable sort-keys */
/**
* 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.
*/
export default function transformProps(chartProps) {
const { formData, height, width, queryData, hooks } = chartProps;
const { mapboxApiAccessToken, features } = queryData.data;
const { config, autozoom, readonly } = formData;
return {
height,
width,
config,
autozoom,
readonly,
features,
setControlValue: hooks.setControlValue,
mapboxApiAccessToken,
};
}