diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/Contour.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/Contour.tsx
new file mode 100644
index 0000000000000..2c46bd6fc257f
--- /dev/null
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/Contour.tsx
@@ -0,0 +1,103 @@
+/**
+ * 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 { ContourLayer } from 'deck.gl';
+import React from 'react';
+import { t } from '@superset-ui/core';
+import { commonLayerProps } from '../common';
+import sandboxedEval from '../../utils/sandbox';
+import { createDeckGLComponent, getLayerType } from '../../factory';
+import { ColorType } from '../../types';
+import TooltipRow from '../../TooltipRow';
+
+function setTooltipContent(o: any) {
+ return (
+
+
+
+
+ );
+}
+export const getLayer: getLayerType = function (
+ formData,
+ payload,
+ onAddFilter,
+ setTooltip,
+) {
+ const fd = formData;
+ const {
+ aggregation = 'SUM',
+ js_data_mutator: jsFnMutator,
+ contours: rawContours,
+ cellSize = '200',
+ } = fd;
+ let data = payload.data.features;
+
+ const contours = rawContours?.map(
+ (contour: {
+ color: ColorType;
+ lowerThreshold: number;
+ upperThreshold?: number;
+ strokeWidth?: number;
+ }) => {
+ const { lowerThreshold, upperThreshold, color, strokeWidth } = contour;
+ if (upperThreshold) {
+ // Isoband format
+ return {
+ threshold: [lowerThreshold, upperThreshold],
+ color: [color.r, color.g, color.b],
+ };
+ }
+ // Isoline format
+ return {
+ threshold: lowerThreshold,
+ color: [color.r, color.g, color.b],
+ strokeWidth,
+ };
+ },
+ );
+
+ if (jsFnMutator) {
+ // Applying user defined data mutator if defined
+ const jsFnMutatorFunction = sandboxedEval(fd.js_data_mutator);
+ data = jsFnMutatorFunction(data);
+ }
+
+ return new ContourLayer({
+ id: `contourLayer-${fd.slice_id}`,
+ data,
+ contours,
+ cellSize: Number(cellSize || '200'),
+ aggregation: aggregation.toUpperCase(),
+ getPosition: (d: { position: number[]; weight: number }) => d.position,
+ getWeight: (d: { weight: number }) => d.weight || 0,
+ ...commonLayerProps(fd, setTooltip, setTooltipContent),
+ });
+};
+
+function getPoints(data: any[]) {
+ return data.map(d => d.position);
+}
+
+export default createDeckGLComponent(getLayer, getPoints);
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/controlPanel.ts
new file mode 100644
index 0000000000000..238029aada4b6
--- /dev/null
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/controlPanel.ts
@@ -0,0 +1,133 @@
+/**
+ * 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 {
+ ControlPanelConfig,
+ getStandardizedControls,
+ sections,
+} from '@superset-ui/chart-controls';
+import { t, validateNonEmpty } from '@superset-ui/core';
+import {
+ autozoom,
+ filterNulls,
+ jsColumns,
+ jsDataMutator,
+ jsOnclickHref,
+ jsTooltip,
+ mapboxStyle,
+ spatial,
+ viewport,
+} from '../../utilities/Shared_DeckGL';
+
+const config: ControlPanelConfig = {
+ controlPanelSections: [
+ sections.legacyRegularTime,
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ [spatial],
+ ['row_limit'],
+ ['size'],
+ [filterNulls],
+ ['adhoc_filters'],
+ ],
+ },
+ {
+ label: t('Map'),
+ expanded: true,
+ controlSetRows: [
+ [mapboxStyle, viewport],
+ [autozoom],
+ [
+ {
+ name: 'cellSize',
+ config: {
+ type: 'TextControl',
+ label: t('Cell Size'),
+ default: 300,
+ isInt: true,
+ description: t('The size of each cell in meters'),
+ renderTrigger: true,
+ clearable: false,
+ },
+ },
+ ],
+ [
+ {
+ name: 'aggregation',
+ config: {
+ type: 'SelectControl',
+ label: t('Aggregation'),
+ description: t(
+ 'The function to use when aggregating points into groups',
+ ),
+ default: 'sum',
+ clearable: false,
+ renderTrigger: true,
+ choices: [
+ ['sum', t('sum')],
+ ['min', t('min')],
+ ['max', t('max')],
+ ['mean', t('mean')],
+ ],
+ },
+ },
+ ],
+ [
+ {
+ name: 'contours',
+ config: {
+ type: 'ContourControl',
+ label: t('Contours'),
+ renderTrigger: true,
+ description: t(
+ 'Define contour layers. Isolines represent a collection of line segments that ' +
+ 'serparate the area above and below a given threshold. Isobands represent a ' +
+ 'collection of polygons that fill the are containing values in a given ' +
+ 'threshold range.',
+ ),
+ },
+ },
+ ],
+ ],
+ },
+ {
+ label: t('Advanced'),
+ controlSetRows: [
+ [jsColumns],
+ [jsDataMutator],
+ [jsTooltip],
+ [jsOnclickHref],
+ ],
+ },
+ ],
+ controlOverrides: {
+ size: {
+ label: t('Weight'),
+ description: t("Metric used as a weight for the grid's coloring"),
+ validators: [validateNonEmpty],
+ },
+ },
+ formDataOverrides: formData => ({
+ ...formData,
+ size: getStandardizedControls().shiftMetric(),
+ }),
+};
+
+export default config;
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/images/thumbnail.png b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/images/thumbnail.png
new file mode 100644
index 0000000000000..eb9b541307cf1
Binary files /dev/null and b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/images/thumbnail.png differ
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/images/thumbnailLarge.png
new file mode 100644
index 0000000000000..fef905c95ff79
Binary files /dev/null and b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/images/thumbnailLarge.png differ
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/index.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/index.ts
new file mode 100644
index 0000000000000..01f14467c72ea
--- /dev/null
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/index.ts
@@ -0,0 +1,45 @@
+/**
+ * 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, ChartMetadata, ChartPlugin } from '@superset-ui/core';
+import transformProps from '../../transformProps';
+import controlPanel from './controlPanel';
+import thumbnail from './images/thumbnail.png';
+
+const metadata = new ChartMetadata({
+ category: t('Map'),
+ credits: ['https://uber.github.io/deck.gl'],
+ description: t(
+ 'Uses Gaussian Kernel Density Estimation to visualize spatial distribution of data',
+ ),
+ name: t('deck.gl Countour'),
+ thumbnail,
+ useLegacyApi: true,
+ tags: [t('deckGL'), t('Spatial'), t('Comparison'), t('Experimental')],
+});
+
+export default class ContourChartPlugin extends ChartPlugin {
+ constructor() {
+ super({
+ loadChart: () => import('./Contour'),
+ controlPanel,
+ metadata,
+ transformProps,
+ });
+ }
+}
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/preset.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/preset.ts
index 2f9a1c6b9e1f3..d6e41c6e9b3da 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/preset.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/preset.ts
@@ -27,6 +27,7 @@ import PathChartPlugin from './layers/Path';
import PolygonChartPlugin from './layers/Polygon';
import ScatterChartPlugin from './layers/Scatter';
import ScreengridChartPlugin from './layers/Screengrid';
+import ContourChartPlugin from './layers/Contour';
export default class DeckGLChartPreset extends Preset {
constructor() {
@@ -43,6 +44,7 @@ export default class DeckGLChartPreset extends Preset {
new PolygonChartPlugin().configure({ key: 'deck_polygon' }),
new ScatterChartPlugin().configure({ key: 'deck_scatter' }),
new ScreengridChartPlugin().configure({ key: 'deck_screengrid' }),
+ new ContourChartPlugin().configure({ key: 'deck_contour' }),
],
});
}
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/types.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/types.ts
index 9177da614d6fd..aadd859d775ca 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/types.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/types.ts
@@ -21,3 +21,9 @@
export type Range = [number, number];
export type Point = [number, number];
+export interface ColorType {
+ r: number;
+ g: number;
+ b: number;
+ a: number;
+}
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/types/external.d.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/types/external.d.ts
index 9543307b69d60..7093762a5c76b 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/types/external.d.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/types/external.d.ts
@@ -17,4 +17,55 @@
* under the License.
*/
-declare module '*.png';
+declare module 'deck.gl' {
+ import { Layer, LayerProps } from '@deck.gl/core';
+
+ interface HeatmapLayerProps extends LayerProps {
+ id?: string;
+ data?: T[];
+ getPosition?: (d: T) => number[] | null | undefined;
+ getWeight?: (d: T) => number | null | undefined;
+ radiusPixels?: number;
+ colorRange?: number[][];
+ threshold?: number;
+ intensity?: number;
+ aggregation?: string;
+ }
+
+ interface ContourLayerProps extends LayerProps {
+ id?: string;
+ data?: T[];
+ getPosition?: (d: T) => number[] | null | undefined;
+ getWeight?: (d: T) => number | null | undefined;
+ contours: {
+ color?: ColorType | undefined;
+ lowerThreshold?: any | undefined;
+ upperThreshold?: any | undefined;
+ strokeWidth?: any | undefined;
+ zIndex?: any | undefined;
+ };
+ cellSize: number;
+ colorRange?: number[][];
+ intensity?: number;
+ aggregation?: string;
+ }
+
+ export class HeatmapLayer extends Layer<
+ T,
+ HeatmapLayerProps
+ > {
+ constructor(props: HeatmapLayerProps);
+ }
+
+ export class ContourLayer extends Layer<
+ T,
+ ContourLayerProps
+ > {
+ constructor(props: ContourLayerProps);
+ }
+}
+
+declare module '*.png' {
+ const value: any;
+ export default value;
+}
diff --git a/superset-frontend/src/explore/components/controls/ContourControl/ContourOption.tsx b/superset-frontend/src/explore/components/controls/ContourControl/ContourOption.tsx
new file mode 100644
index 0000000000000..8bc2669038aca
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/ContourControl/ContourOption.tsx
@@ -0,0 +1,107 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with work for additional information
+ * regarding copyright ownership. The ASF licenses file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use 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 { styled, t } from '@superset-ui/core';
+import { ContourOptionProps } from './types';
+import ContourPopoverTrigger from './ContourPopoverTrigger';
+import OptionWrapper from '../DndColumnSelectControl/OptionWrapper';
+
+const StyledOptionWrapper = styled(OptionWrapper)`
+ max-width: 100%;
+ min-width: 100%;
+`;
+
+const StyledListItem = styled.li`
+ display: flex;
+ align-items: center;
+`;
+
+const ColorPatch = styled.div<{ formattedColor: string }>`
+ background-color: ${({ formattedColor }) => formattedColor};
+ height: ${({ theme }) => theme.gridUnit}px;
+ width: ${({ theme }) => theme.gridUnit}px;
+ margin: 0 ${({ theme }) => theme.gridUnit}px;
+`;
+
+const ContourOption = ({
+ contour,
+ index,
+ saveContour,
+ onClose,
+ onShift,
+}: ContourOptionProps) => {
+ const { lowerThreshold, upperThreshold, color, strokeWidth } = contour;
+
+ const isIsoband = upperThreshold;
+
+ const formattedColor = color
+ ? `rgba(${color.r}, ${color.g}, ${color.b}, 1)`
+ : 'undefined';
+
+ const formatIsoline = (threshold: number, width: number) =>
+ `${t('Threshold')}: ${threshold}, ${t('color')}: ${formattedColor}, ${t(
+ 'stroke width',
+ )}: ${width}`;
+
+ const formatIsoband = (threshold: number[]) =>
+ `${t('Threshold')}: [${threshold[0]}, ${
+ threshold[1]
+ }], color: ${formattedColor}`;
+
+ const displayString = isIsoband
+ ? formatIsoband([lowerThreshold || -1, upperThreshold])
+ : formatIsoline(lowerThreshold || -1, strokeWidth);
+
+ const overlay = (
+