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-map-gl": "^4.0.10",
|
||||
"underscore": "^1.8.3",
|
||||
"urijs": "^1.18.10"
|
||||
"urijs": "^1.18.10",
|
||||
"xss": "^1.0.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart": "^0.12.0",
|
||||
|
@ -55,6 +55,14 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
export default class AnimatableDeckGLContainer extends React.PureComponent {
|
||||
containerRef = React.createRef();
|
||||
|
||||
setTooltip = tooltip => {
|
||||
if (this.containerRef.current) {
|
||||
this.containerRef.current.setTooltip(tooltip);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
start,
|
||||
@ -78,6 +86,7 @@ export default class AnimatableDeckGLContainer extends React.PureComponent {
|
||||
return (
|
||||
<div>
|
||||
<DeckGLContainer
|
||||
ref={this.containerRef}
|
||||
viewport={viewport}
|
||||
layers={layers}
|
||||
setControlValue={setControlValue}
|
||||
|
@ -72,6 +72,8 @@ const propTypes = {
|
||||
};
|
||||
|
||||
export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||
containerRef = React.createRef();
|
||||
|
||||
/*
|
||||
* A Deck.gl container that handles categories.
|
||||
*
|
||||
@ -225,10 +227,17 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||
this.setState({ categories });
|
||||
}
|
||||
|
||||
setTooltip = tooltip => {
|
||||
if (this.containerRef.current) {
|
||||
this.containerRef.current.setTooltip(tooltip);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<AnimatableDeckGLContainer
|
||||
ref={this.containerRef}
|
||||
getLayers={this.getLayers}
|
||||
start={this.state.start}
|
||||
end={this.state.end}
|
||||
|
@ -24,6 +24,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StaticMap } from 'react-map-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 './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.state = {
|
||||
timer: setInterval(this.tick, TICK),
|
||||
tooltip: null,
|
||||
viewState: props.viewport,
|
||||
};
|
||||
}
|
||||
@ -88,14 +91,19 @@ export default class DeckGLContainer extends React.Component {
|
||||
return this.props.layers;
|
||||
}
|
||||
|
||||
setTooltip = tooltip => {
|
||||
this.setState({ tooltip });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, bottomMargin, height, width } = this.props;
|
||||
const { viewState } = this.state;
|
||||
const { viewState, tooltip } = this.state;
|
||||
const adjustedHeight = height - bottomMargin;
|
||||
|
||||
const layers = this.layers();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ position: 'relative', width, height: adjustedHeight }}>
|
||||
<DeckGL
|
||||
initWebGLParameters
|
||||
@ -113,6 +121,8 @@ export default class DeckGLContainer extends React.Component {
|
||||
</DeckGL>
|
||||
{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 (
|
||||
<div className="deckgl-tooltip">
|
||||
{o.object.name && <TooltipRow label="name: " value={`${o.object.name}`} />}
|
||||
{o.object[formData.line_column] && (
|
||||
<TooltipRow
|
||||
label={`${formData.line_column}: `}
|
||||
value={`${o.object[formData.line_column]}`}
|
||||
/>
|
||||
)}
|
||||
{formData.metric && (
|
||||
<TooltipRow label={`${metricLabel}: `} value={`${o.object[metricLabel]}`} />
|
||||
)}
|
||||
@ -103,8 +106,9 @@ export function getLayer(formData, payload, onAddFilter, setTooltip, selected, o
|
||||
|
||||
return baseColor;
|
||||
};
|
||||
|
||||
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)
|
||||
: undefined;
|
||||
|
||||
@ -132,17 +136,17 @@ const propTypes = {
|
||||
setControlValue: PropTypes.func.isRequired,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
onAddFilter: PropTypes.func,
|
||||
setTooltip: PropTypes.func,
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onAddFilter() {},
|
||||
setTooltip() {},
|
||||
};
|
||||
|
||||
class DeckGLPolygon extends React.Component {
|
||||
containerRef = React.createRef();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -245,7 +249,7 @@ class DeckGLPolygon extends React.Component {
|
||||
this.props.formData,
|
||||
this.props.payload,
|
||||
this.props.onAddFilter,
|
||||
this.props.setTooltip,
|
||||
this.setTooltip,
|
||||
this.state.selected,
|
||||
this.onSelect,
|
||||
filters,
|
||||
@ -254,6 +258,12 @@ class DeckGLPolygon extends React.Component {
|
||||
return [layer];
|
||||
}
|
||||
|
||||
setTooltip = tooltip => {
|
||||
if (this.containerRef.current) {
|
||||
this.containerRef.current.setTooltip(tooltip);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { payload, formData, setControlValue } = this.props;
|
||||
const { start, end, getStep, values, disabled, viewport } = this.state;
|
||||
@ -267,6 +277,7 @@ class DeckGLPolygon extends React.Component {
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<AnimatableDeckGLContainer
|
||||
ref={this.containerRef}
|
||||
aggregation
|
||||
getLayers={this.getLayers}
|
||||
start={start}
|
||||
|
@ -76,6 +76,6 @@ export default [
|
||||
/>
|
||||
),
|
||||
storyName: 'Basic',
|
||||
storyPath: 'legacy-|preset-chart-deckgl|ArcChartPlugin',
|
||||
storyPath: 'legacy-preset-chart-deckgl|ArcChartPlugin',
|
||||
},
|
||||
];
|
||||
|
@ -47,6 +47,6 @@ export default [
|
||||
/>
|
||||
),
|
||||
storyName: 'Basic',
|
||||
storyPath: 'legacy-|preset-chart-deckgl|GridChartPlugin',
|
||||
storyPath: 'legacy-preset-chart-deckgl|GridChartPlugin',
|
||||
},
|
||||
];
|
||||
|
@ -48,6 +48,6 @@ export default [
|
||||
/>
|
||||
),
|
||||
storyName: 'Basic',
|
||||
storyPath: 'legacy-|preset-chart-deckgl|HexChartPlugin',
|
||||
storyPath: 'legacy-preset-chart-deckgl|HexChartPlugin',
|
||||
},
|
||||
];
|
||||
|
@ -56,6 +56,6 @@ export default [
|
||||
/>
|
||||
),
|
||||
storyName: 'Basic',
|
||||
storyPath: 'legacy-|preset-chart-deckgl|PathChartPlugin',
|
||||
storyPath: 'legacy-preset-chart-deckgl|PathChartPlugin',
|
||||
},
|
||||
];
|
||||
|
@ -26,7 +26,7 @@ export default [
|
||||
line_column: 'contour',
|
||||
line_type: 'json',
|
||||
adhoc_filters: [],
|
||||
metric: 'count',
|
||||
metric: 'population',
|
||||
point_radius_fixed: { type: 'fix', value: 1000 },
|
||||
row_limit: 10000,
|
||||
reverse_long_lat: false,
|
||||
@ -71,7 +71,7 @@ export default [
|
||||
/>
|
||||
),
|
||||
storyName: 'Basic',
|
||||
storyPath: 'legacy-|preset-chart-deckgl|PolygonChartPlugin',
|
||||
storyPath: 'legacy-preset-chart-deckgl|PolygonChartPlugin',
|
||||
},
|
||||
{
|
||||
renderStory: () => (
|
||||
@ -119,6 +119,6 @@ export default [
|
||||
/>
|
||||
),
|
||||
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: {
|
||||
features: [
|
||||
{
|
||||
count: 1,
|
||||
count: 10,
|
||||
name: 'Test',
|
||||
polygon: {
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
|
@ -55,6 +55,6 @@ export default [
|
||||
/>
|
||||
),
|
||||
storyName: 'Basic',
|
||||
storyPath: 'legacy-|preset-chart-deckgl|ScatterChartPlugin',
|
||||
storyPath: 'legacy-preset-chart-deckgl|ScatterChartPlugin',
|
||||
},
|
||||
];
|
||||
|
@ -46,6 +46,6 @@ export default [
|
||||
/>
|
||||
),
|
||||
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"
|
||||
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:
|
||||
version "4.0.2"
|
||||
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"
|
||||
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:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
|
Loading…
Reference in New Issue
Block a user