mirror of
https://github.com/apache/superset.git
synced 2024-09-17 11:09:47 -04:00
feat: support polygon tooltip independently from superset app (#12)
* feat: support polygon tooltip independently from superset app * fix: remove unintended changes * fix: minor styling * fix: tooltip * fix: lowercase name * fix: storybook
This commit is contained in:
parent
4f0d4e038b
commit
abc4a04294
@ -42,7 +42,8 @@
|
|||||||
"react-bootstrap-slider": "2.1.5",
|
"react-bootstrap-slider": "2.1.5",
|
||||||
"react-map-gl": "^4.0.10",
|
"react-map-gl": "^4.0.10",
|
||||||
"underscore": "^1.8.3",
|
"underscore": "^1.8.3",
|
||||||
"urijs": "^1.18.10"
|
"urijs": "^1.18.10",
|
||||||
|
"xss": "^1.0.6"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@superset-ui/chart": "^0.12.0",
|
"@superset-ui/chart": "^0.12.0",
|
||||||
|
@ -55,6 +55,14 @@ const defaultProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class AnimatableDeckGLContainer extends React.PureComponent {
|
export default class AnimatableDeckGLContainer extends React.PureComponent {
|
||||||
|
containerRef = React.createRef();
|
||||||
|
|
||||||
|
setTooltip = tooltip => {
|
||||||
|
if (this.containerRef.current) {
|
||||||
|
this.containerRef.current.setTooltip(tooltip);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
start,
|
start,
|
||||||
@ -78,6 +86,7 @@ export default class AnimatableDeckGLContainer extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<DeckGLContainer
|
<DeckGLContainer
|
||||||
|
ref={this.containerRef}
|
||||||
viewport={viewport}
|
viewport={viewport}
|
||||||
layers={layers}
|
layers={layers}
|
||||||
setControlValue={setControlValue}
|
setControlValue={setControlValue}
|
||||||
|
@ -72,6 +72,8 @@ const propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class CategoricalDeckGLContainer extends React.PureComponent {
|
export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||||
|
containerRef = React.createRef();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A Deck.gl container that handles categories.
|
* A Deck.gl container that handles categories.
|
||||||
*
|
*
|
||||||
@ -225,10 +227,17 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
|||||||
this.setState({ categories });
|
this.setState({ categories });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTooltip = tooltip => {
|
||||||
|
if (this.containerRef.current) {
|
||||||
|
this.containerRef.current.setTooltip(tooltip);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<AnimatableDeckGLContainer
|
<AnimatableDeckGLContainer
|
||||||
|
ref={this.containerRef}
|
||||||
getLayers={this.getLayers}
|
getLayers={this.getLayers}
|
||||||
start={this.state.start}
|
start={this.state.start}
|
||||||
end={this.state.end}
|
end={this.state.end}
|
||||||
|
@ -24,6 +24,8 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StaticMap } from 'react-map-gl';
|
import { StaticMap } from 'react-map-gl';
|
||||||
import DeckGL from 'deck.gl';
|
import DeckGL from 'deck.gl';
|
||||||
|
// eslint-disable-next-line import/extensions
|
||||||
|
import Tooltip from './components/Tooltip';
|
||||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||||
import './css/deckgl.css';
|
import './css/deckgl.css';
|
||||||
|
|
||||||
@ -55,6 +57,7 @@ export default class DeckGLContainer extends React.Component {
|
|||||||
// This has to be placed after this.tick is bound to this
|
// This has to be placed after this.tick is bound to this
|
||||||
this.state = {
|
this.state = {
|
||||||
timer: setInterval(this.tick, TICK),
|
timer: setInterval(this.tick, TICK),
|
||||||
|
tooltip: null,
|
||||||
viewState: props.viewport,
|
viewState: props.viewport,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -88,31 +91,38 @@ export default class DeckGLContainer extends React.Component {
|
|||||||
return this.props.layers;
|
return this.props.layers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTooltip = tooltip => {
|
||||||
|
this.setState({ tooltip });
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children, bottomMargin, height, width } = this.props;
|
const { children, bottomMargin, height, width } = this.props;
|
||||||
const { viewState } = this.state;
|
const { viewState, tooltip } = this.state;
|
||||||
const adjustedHeight = height - bottomMargin;
|
const adjustedHeight = height - bottomMargin;
|
||||||
|
|
||||||
const layers = this.layers();
|
const layers = this.layers();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative', width, height: adjustedHeight }}>
|
<>
|
||||||
<DeckGL
|
<div style={{ position: 'relative', width, height: adjustedHeight }}>
|
||||||
initWebGLParameters
|
<DeckGL
|
||||||
controller
|
initWebGLParameters
|
||||||
width={width}
|
controller
|
||||||
height={adjustedHeight}
|
width={width}
|
||||||
layers={layers}
|
height={adjustedHeight}
|
||||||
viewState={viewState}
|
layers={layers}
|
||||||
onViewStateChange={this.onViewStateChange}
|
viewState={viewState}
|
||||||
>
|
onViewStateChange={this.onViewStateChange}
|
||||||
<StaticMap
|
>
|
||||||
mapStyle={this.props.mapStyle}
|
<StaticMap
|
||||||
mapboxApiAccessToken={this.props.mapboxApiAccessToken}
|
mapStyle={this.props.mapStyle}
|
||||||
/>
|
mapboxApiAccessToken={this.props.mapboxApiAccessToken}
|
||||||
</DeckGL>
|
/>
|
||||||
{children}
|
</DeckGL>
|
||||||
</div>
|
{children}
|
||||||
|
</div>
|
||||||
|
<Tooltip tooltip={tooltip} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
import React, { useMemo, CSSProperties } from 'react';
|
||||||
|
import { filterXSS } from 'xss';
|
||||||
|
|
||||||
|
export type TooltipProps = {
|
||||||
|
tooltip:
|
||||||
|
| {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Tooltip(props: TooltipProps) {
|
||||||
|
const { tooltip } = props;
|
||||||
|
if (typeof tooltip === 'undefined' || tooltip === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { x, y, content } = tooltip;
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const style: CSSProperties = useMemo(
|
||||||
|
() => ({
|
||||||
|
position: 'absolute',
|
||||||
|
top: `${y}px`,
|
||||||
|
left: `${x}px`,
|
||||||
|
padding: '8px',
|
||||||
|
margin: '8px',
|
||||||
|
background: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
color: '#fff',
|
||||||
|
maxWidth: '300px',
|
||||||
|
fontSize: '12px',
|
||||||
|
zIndex: 9,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}),
|
||||||
|
[x, y],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const contentHtml = useMemo(
|
||||||
|
() => ({
|
||||||
|
__html: filterXSS(content, { stripIgnoreTag: true }),
|
||||||
|
}),
|
||||||
|
[content],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div style={style}>
|
||||||
|
<div
|
||||||
|
// eslint-disable-next-line react/no-danger
|
||||||
|
dangerouslySetInnerHTML={contentHtml}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div style={style}>{content}</div>;
|
||||||
|
}
|
@ -56,10 +56,13 @@ function setTooltipContent(formData) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="deckgl-tooltip">
|
<div className="deckgl-tooltip">
|
||||||
<TooltipRow
|
{o.object.name && <TooltipRow label="name: " value={`${o.object.name}`} />}
|
||||||
label={`${formData.line_column}: `}
|
{o.object[formData.line_column] && (
|
||||||
value={`${o.object[formData.line_column]}`}
|
<TooltipRow
|
||||||
/>
|
label={`${formData.line_column}: `}
|
||||||
|
value={`${o.object[formData.line_column]}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{formData.metric && (
|
{formData.metric && (
|
||||||
<TooltipRow label={`${metricLabel}: `} value={`${o.object[metricLabel]}`} />
|
<TooltipRow label={`${metricLabel}: `} value={`${o.object[metricLabel]}`} />
|
||||||
)}
|
)}
|
||||||
@ -103,8 +106,9 @@ export function getLayer(formData, payload, onAddFilter, setTooltip, selected, o
|
|||||||
|
|
||||||
return baseColor;
|
return baseColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
const tooltipContentGenerator =
|
const tooltipContentGenerator =
|
||||||
fd.line_column && fd.metric && ['geohash', 'zipcode'].includes(fd.line_type)
|
fd.line_column && fd.metric && ['json', 'geohash', 'zipcode'].includes(fd.line_type)
|
||||||
? setTooltipContent(fd)
|
? setTooltipContent(fd)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
@ -132,17 +136,17 @@ const propTypes = {
|
|||||||
setControlValue: PropTypes.func.isRequired,
|
setControlValue: PropTypes.func.isRequired,
|
||||||
viewport: PropTypes.object.isRequired,
|
viewport: PropTypes.object.isRequired,
|
||||||
onAddFilter: PropTypes.func,
|
onAddFilter: PropTypes.func,
|
||||||
setTooltip: PropTypes.func,
|
|
||||||
width: PropTypes.number.isRequired,
|
width: PropTypes.number.isRequired,
|
||||||
height: PropTypes.number.isRequired,
|
height: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
onAddFilter() {},
|
onAddFilter() {},
|
||||||
setTooltip() {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class DeckGLPolygon extends React.Component {
|
class DeckGLPolygon extends React.Component {
|
||||||
|
containerRef = React.createRef();
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@ -245,7 +249,7 @@ class DeckGLPolygon extends React.Component {
|
|||||||
this.props.formData,
|
this.props.formData,
|
||||||
this.props.payload,
|
this.props.payload,
|
||||||
this.props.onAddFilter,
|
this.props.onAddFilter,
|
||||||
this.props.setTooltip,
|
this.setTooltip,
|
||||||
this.state.selected,
|
this.state.selected,
|
||||||
this.onSelect,
|
this.onSelect,
|
||||||
filters,
|
filters,
|
||||||
@ -254,6 +258,12 @@ class DeckGLPolygon extends React.Component {
|
|||||||
return [layer];
|
return [layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTooltip = tooltip => {
|
||||||
|
if (this.containerRef.current) {
|
||||||
|
this.containerRef.current.setTooltip(tooltip);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { payload, formData, setControlValue } = this.props;
|
const { payload, formData, setControlValue } = this.props;
|
||||||
const { start, end, getStep, values, disabled, viewport } = this.state;
|
const { start, end, getStep, values, disabled, viewport } = this.state;
|
||||||
@ -267,6 +277,7 @@ class DeckGLPolygon extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<AnimatableDeckGLContainer
|
<AnimatableDeckGLContainer
|
||||||
|
ref={this.containerRef}
|
||||||
aggregation
|
aggregation
|
||||||
getLayers={this.getLayers}
|
getLayers={this.getLayers}
|
||||||
start={start}
|
start={start}
|
||||||
|
@ -76,6 +76,6 @@ export default [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
storyName: 'Basic',
|
storyName: 'Basic',
|
||||||
storyPath: 'legacy-|preset-chart-deckgl|ArcChartPlugin',
|
storyPath: 'legacy-preset-chart-deckgl|ArcChartPlugin',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -47,6 +47,6 @@ export default [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
storyName: 'Basic',
|
storyName: 'Basic',
|
||||||
storyPath: 'legacy-|preset-chart-deckgl|GridChartPlugin',
|
storyPath: 'legacy-preset-chart-deckgl|GridChartPlugin',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -48,6 +48,6 @@ export default [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
storyName: 'Basic',
|
storyName: 'Basic',
|
||||||
storyPath: 'legacy-|preset-chart-deckgl|HexChartPlugin',
|
storyPath: 'legacy-preset-chart-deckgl|HexChartPlugin',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -56,6 +56,6 @@ export default [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
storyName: 'Basic',
|
storyName: 'Basic',
|
||||||
storyPath: 'legacy-|preset-chart-deckgl|PathChartPlugin',
|
storyPath: 'legacy-preset-chart-deckgl|PathChartPlugin',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -26,7 +26,7 @@ export default [
|
|||||||
line_column: 'contour',
|
line_column: 'contour',
|
||||||
line_type: 'json',
|
line_type: 'json',
|
||||||
adhoc_filters: [],
|
adhoc_filters: [],
|
||||||
metric: 'count',
|
metric: 'population',
|
||||||
point_radius_fixed: { type: 'fix', value: 1000 },
|
point_radius_fixed: { type: 'fix', value: 1000 },
|
||||||
row_limit: 10000,
|
row_limit: 10000,
|
||||||
reverse_long_lat: false,
|
reverse_long_lat: false,
|
||||||
@ -71,7 +71,7 @@ export default [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
storyName: 'Basic',
|
storyName: 'Basic',
|
||||||
storyPath: 'legacy-|preset-chart-deckgl|PolygonChartPlugin',
|
storyPath: 'legacy-preset-chart-deckgl|PolygonChartPlugin',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
renderStory: () => (
|
renderStory: () => (
|
||||||
@ -119,6 +119,6 @@ export default [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
storyName: 'Single Polygon in geojson format',
|
storyName: 'Single Polygon in geojson format',
|
||||||
storyPath: 'legacy-|preset-chart-deckgl|PolygonChartPlugin',
|
storyPath: 'legacy-preset-chart-deckgl|PolygonChartPlugin',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -82,7 +82,8 @@ export default {
|
|||||||
data: {
|
data: {
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
count: 1,
|
count: 10,
|
||||||
|
name: 'Test',
|
||||||
polygon: {
|
polygon: {
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: {},
|
properties: {},
|
||||||
|
@ -55,6 +55,6 @@ export default [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
storyName: 'Basic',
|
storyName: 'Basic',
|
||||||
storyPath: 'legacy-|preset-chart-deckgl|ScatterChartPlugin',
|
storyPath: 'legacy-preset-chart-deckgl|ScatterChartPlugin',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -46,6 +46,6 @@ export default [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
storyName: 'Basic',
|
storyName: 'Basic',
|
||||||
storyPath: 'legacy-|preset-chart-deckgl|ScreengridChartPlugin',
|
storyPath: 'legacy-preset-chart-deckgl|ScreengridChartPlugin',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -6831,6 +6831,11 @@ cssesc@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||||
|
|
||||||
|
cssfilter@0.0.10:
|
||||||
|
version "0.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae"
|
||||||
|
integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=
|
||||||
|
|
||||||
csso@^4.0.2:
|
csso@^4.0.2:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d"
|
resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d"
|
||||||
@ -17859,6 +17864,14 @@ xmlhttprequest@1:
|
|||||||
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
|
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
|
||||||
integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=
|
integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=
|
||||||
|
|
||||||
|
xss@^1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.6.tgz#eaf11e9fc476e3ae289944a1009efddd8a124b51"
|
||||||
|
integrity sha512-6Q9TPBeNyoTRxgZFk5Ggaepk/4vUOYdOsIUYvLehcsIZTFjaavbVnsuAkLA5lIFuug5hw8zxcB9tm01gsjph2A==
|
||||||
|
dependencies:
|
||||||
|
commander "^2.9.0"
|
||||||
|
cssfilter "0.0.10"
|
||||||
|
|
||||||
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
|
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
|
Loading…
Reference in New Issue
Block a user