You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/05/15 01:57:06 UTC
(superset) branch feat/glyph-single-file updated (5b0d8953224 -> dd1226f8e59)
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a change to branch feat/glyph-single-file
in repository https://gitbox.apache.org/repos/asf/superset.git
from 5b0d8953224 feat(glyph): consolidate deckgl Hex layer to defineChart()
new 46555d12cbc feat(glyph): consolidate deckgl Path layer to defineChart()
new dd1226f8e59 feat(glyph): consolidate deckgl Polygon layer to defineChart()
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../src/layers/Path/buildQuery.ts | 95 ----
.../src/layers/Path/controlPanel.ts | 107 -----
.../preset-chart-deckgl/src/layers/Path/index.ts | 51 --
.../preset-chart-deckgl/src/layers/Path/index.tsx | 365 +++++++++++++++
.../src/layers/Path/transformProps.ts | 166 -------
.../src/layers/Polygon/buildQuery.ts | 129 -----
.../src/layers/Polygon/controlPanel.ts | 225 ---------
.../src/layers/Polygon/index.ts | 53 ---
.../src/layers/Polygon/index.tsx | 521 +++++++++++++++++++++
.../src/layers/Polygon/transformProps.ts | 186 --------
10 files changed, 886 insertions(+), 1012 deletions(-)
delete mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/buildQuery.ts
delete mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/controlPanel.ts
delete mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/index.ts
create mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/index.tsx
delete mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/transformProps.ts
delete mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/buildQuery.ts
delete mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/controlPanel.ts
delete mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/index.ts
create mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/index.tsx
delete mode 100644 superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/transformProps.ts
(superset) 01/02: feat(glyph): consolidate deckgl Path layer to defineChart()
Posted by ru...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch feat/glyph-single-file
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 46555d12cbc4fbfb9c309c494542123e781f18de
Author: Evan Rusackas <ev...@rusackas.com>
AuthorDate: Thu May 14 18:54:06 2026 -0700
feat(glyph): consolidate deckgl Path layer to defineChart()
Collapse multi-file plugin into single index.tsx. Path keeps its
custom buildQuery (line_column required, optional metric/groupby
handling) and transformProps (decoders for json/polyline/geohash line
formats, reverse_long_lat support). Path.tsx component stays as
sibling for Multi.
Co-Authored-By: Claude Sonnet 4.6 <no...@anthropic.com>
---
.../src/layers/Path/buildQuery.ts | 95 ------
.../src/layers/Path/controlPanel.ts | 107 ------
.../preset-chart-deckgl/src/layers/Path/index.ts | 51 ---
.../preset-chart-deckgl/src/layers/Path/index.tsx | 365 +++++++++++++++++++++
.../src/layers/Path/transformProps.ts | 166 ----------
5 files changed, 365 insertions(+), 419 deletions(-)
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/buildQuery.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/buildQuery.ts
deleted file mode 100644
index b22ef0b6eea..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/buildQuery.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * 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 {
- buildQueryContext,
- ensureIsArray,
- SqlaFormData,
- QueryFormColumn,
-} from '@superset-ui/core';
-import { addNullFilters, addTooltipColumnsToQuery } from '../buildQueryUtils';
-
-export interface DeckPathFormData extends SqlaFormData {
- line_column?: string;
- line_type?: 'polyline' | 'json' | 'geohash';
- metric?: string;
- reverse_long_lat?: boolean;
- js_columns?: string[];
- tooltip_contents?: unknown[];
- tooltip_template?: string;
-}
-
-export default function buildQuery(formData: DeckPathFormData) {
- const { line_column, metric, js_columns, tooltip_contents } = formData;
-
- if (!line_column) {
- throw new Error('Line column is required for Path charts');
- }
-
- return buildQueryContext(formData, {
- buildQuery: baseQueryObject => {
- const columns = ensureIsArray(
- baseQueryObject.columns || [],
- ) as QueryFormColumn[];
- const metrics = ensureIsArray(baseQueryObject.metrics || []);
- const groupby = ensureIsArray(
- baseQueryObject.groupby || [],
- ) as QueryFormColumn[];
- const jsColumns = ensureIsArray(js_columns || []);
-
- if (baseQueryObject.metrics?.length || metric) {
- if (metric && !metrics.includes(metric)) {
- metrics.push(metric);
- }
- if (!groupby.includes(line_column)) {
- groupby.push(line_column);
- }
- } else if (!columns.includes(line_column)) {
- columns.push(line_column);
- }
-
- jsColumns.forEach(col => {
- if (!columns.includes(col) && !groupby.includes(col)) {
- columns.push(col);
- }
- });
-
- const finalColumns = addTooltipColumnsToQuery(columns, tooltip_contents);
- const finalGroupby = addTooltipColumnsToQuery(groupby, tooltip_contents);
-
- const filters = addNullFilters(
- ensureIsArray(baseQueryObject.filters || []),
- [line_column],
- );
-
- const isTimeseries = Boolean(formData.time_grain_sqla);
-
- return [
- {
- ...baseQueryObject,
- columns: finalColumns,
- metrics,
- groupby: finalGroupby,
- filters,
- is_timeseries: isTimeseries,
- row_limit: baseQueryObject.row_limit,
- },
- ];
- },
- });
-}
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/controlPanel.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/controlPanel.ts
deleted file mode 100644
index 14673499680..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/controlPanel.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * 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 } from '@superset-ui/chart-controls';
-import { t } from '@apache-superset/core/translation';
-import {
- filterNulls,
- autozoom,
- jsColumns,
- jsDataMutator,
- jsTooltip,
- jsOnclickHref,
- viewport,
- lineWidth,
- lineType,
- reverseLongLat,
- mapboxStyle,
- maplibreStyle,
- mapProvider,
- tooltipContents,
- tooltipTemplate,
-} from '../../utilities/Shared_DeckGL';
-import { dndLineColumn } from '../../utilities/sharedDndControls';
-
-const config: ControlPanelConfig = {
- controlPanelSections: [
- {
- label: t('Query'),
- expanded: true,
- controlSetRows: [
- [dndLineColumn],
- [
- {
- ...lineType,
- config: {
- ...lineType.config,
- choices: [
- ['polyline', t('Polyline')],
- ['json', t('JSON')],
- ],
- },
- },
- ],
- ['row_limit'],
- [filterNulls],
- ['adhoc_filters'],
- [tooltipContents],
- [tooltipTemplate],
- ],
- },
- {
- label: t('Map'),
- expanded: true,
- controlSetRows: [
- [mapProvider],
- [mapboxStyle],
- [maplibreStyle],
- [viewport],
- ['color_picker'],
- [lineWidth],
- [
- {
- name: 'line_width_unit',
- config: {
- type: 'SelectControl',
- label: t('Line width unit'),
- default: 'meters',
- choices: [
- ['meters', t('meters')],
- ['pixels', t('pixels')],
- ],
- renderTrigger: true,
- },
- },
- ],
- [reverseLongLat],
- [autozoom],
- ],
- },
- {
- label: t('Advanced'),
- controlSetRows: [
- [jsColumns],
- [jsDataMutator],
- [jsTooltip],
- [jsOnclickHref],
- ],
- },
- ],
-};
-
-export default config;
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/index.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/index.ts
deleted file mode 100644
index e2e64d553f8..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/index.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * 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 '@apache-superset/core/translation';
-import { ChartMetadata, ChartPlugin, Behavior } from '@superset-ui/core';
-import thumbnail from './images/thumbnail.png';
-import thumbnailDark from './images/thumbnail-dark.png';
-import example from './images/example.png';
-import exampleDark from './images/example-dark.png';
-import buildQuery from './buildQuery';
-import transformProps from './transformProps';
-import controlPanel from './controlPanel';
-
-const metadata = new ChartMetadata({
- category: t('Map'),
- credits: ['https://uber.github.io/deck.gl'],
- description: t('Visualizes connected points, which form a path, on a map.'),
- name: t('deck.gl Path'),
- thumbnail,
- thumbnailDark,
- exampleGallery: [{ url: example, urlDark: exampleDark }],
- tags: [t('deckGL'), t('Web')],
- behaviors: [Behavior.InteractiveChart],
-});
-
-export default class PathChartPlugin extends ChartPlugin {
- constructor() {
- super({
- buildQuery,
- loadChart: () => import('./Path'),
- controlPanel,
- metadata,
- transformProps,
- });
- }
-}
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/index.tsx b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/index.tsx
new file mode 100644
index 00000000000..5b67cf2fdb9
--- /dev/null
+++ b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/index.tsx
@@ -0,0 +1,365 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import {
+ Behavior,
+ buildQueryContext,
+ ChartProps,
+ DTTM_ALIAS,
+ ensureIsArray,
+ QueryFormColumn,
+ SqlaFormData,
+} from '@superset-ui/core';
+import { defineChart } from '@superset-ui/glyph-core';
+import PathComponent from './Path';
+import { addJsColumnsToExtraProps, DataRecord } from '../spatialUtils';
+import {
+ createBaseTransformResult,
+ getRecordsFromQuery,
+ getMetricLabelFromFormData,
+ parseMetricValue,
+ addPropertiesToFeature,
+} from '../transformUtils';
+import { addNullFilters, addTooltipColumnsToQuery } from '../buildQueryUtils';
+import {
+ filterNulls,
+ autozoom,
+ jsColumns,
+ jsDataMutator,
+ jsTooltip,
+ jsOnclickHref,
+ viewport,
+ lineWidth,
+ lineType,
+ reverseLongLat,
+ mapboxStyle,
+ maplibreStyle,
+ mapProvider,
+ tooltipContents,
+ tooltipTemplate,
+} from '../../utilities/Shared_DeckGL';
+import { dndLineColumn } from '../../utilities/sharedDndControls';
+import thumbnail from './images/thumbnail.png';
+import thumbnailDark from './images/thumbnail-dark.png';
+import example from './images/example.png';
+import exampleDark from './images/example-dark.png';
+
+// ─── Types ───────────────────────────────────────────────────────────────────
+
+export interface DeckPathFormData extends SqlaFormData {
+ line_column?: string;
+ line_type?: 'polyline' | 'json' | 'geohash';
+ metric?: string;
+ reverse_long_lat?: boolean;
+ js_columns?: string[];
+ tooltip_contents?: unknown[];
+ tooltip_template?: string;
+}
+
+export interface DeckPathTransformPropsFormData extends DeckPathFormData {
+ js_data_mutator?: string;
+ js_tooltip?: string;
+ js_onclick_href?: string;
+}
+
+interface PathFeature {
+ path: [number, number][];
+ metric?: number;
+ timestamp?: unknown;
+ extraProps?: Record<string, unknown>;
+ [key: string]: unknown;
+}
+
+declare global {
+ interface Window {
+ polyline?: {
+ decode: (data: string) => [number, number][];
+ };
+ geohash?: {
+ decode: (data: string) => { longitude: number; latitude: number };
+ };
+ }
+}
+
+// ─── buildQuery ──────────────────────────────────────────────────────────────
+
+export function buildQuery(formData: DeckPathFormData) {
+ const { line_column, metric, js_columns, tooltip_contents } = formData;
+
+ if (!line_column) {
+ throw new Error('Line column is required for Path charts');
+ }
+
+ return buildQueryContext(formData, {
+ buildQuery: baseQueryObject => {
+ const columns = ensureIsArray(
+ baseQueryObject.columns || [],
+ ) as QueryFormColumn[];
+ const metrics = ensureIsArray(baseQueryObject.metrics || []);
+ const groupby = ensureIsArray(
+ baseQueryObject.groupby || [],
+ ) as QueryFormColumn[];
+ const jsCols = ensureIsArray(js_columns || []);
+
+ if (baseQueryObject.metrics?.length || metric) {
+ if (metric && !metrics.includes(metric)) {
+ metrics.push(metric);
+ }
+ if (!groupby.includes(line_column)) {
+ groupby.push(line_column);
+ }
+ } else if (!columns.includes(line_column)) {
+ columns.push(line_column);
+ }
+
+ jsCols.forEach(col => {
+ if (!columns.includes(col) && !groupby.includes(col)) {
+ columns.push(col);
+ }
+ });
+
+ const finalColumns = addTooltipColumnsToQuery(columns, tooltip_contents);
+ const finalGroupby = addTooltipColumnsToQuery(groupby, tooltip_contents);
+
+ const filters = addNullFilters(
+ ensureIsArray(baseQueryObject.filters || []),
+ [line_column],
+ );
+
+ const isTimeseries = Boolean(formData.time_grain_sqla);
+
+ return [
+ {
+ ...baseQueryObject,
+ columns: finalColumns,
+ metrics,
+ groupby: finalGroupby,
+ filters,
+ is_timeseries: isTimeseries,
+ row_limit: baseQueryObject.row_limit,
+ },
+ ];
+ },
+ });
+}
+
+// ─── transformProps ──────────────────────────────────────────────────────────
+
+const decoders = {
+ json: (data: string): [number, number][] => {
+ try {
+ const parsed = JSON.parse(data);
+ return Array.isArray(parsed) ? parsed : [];
+ } catch (error) {
+ return [];
+ }
+ },
+ polyline: (data: string): [number, number][] => {
+ try {
+ if (typeof window !== 'undefined' && window.polyline) {
+ return window.polyline.decode(data);
+ }
+ return [];
+ } catch (error) {
+ return [];
+ }
+ },
+ geohash: (data: string): [number, number][] => {
+ try {
+ if (typeof window !== 'undefined' && window.geohash) {
+ const decoded = window.geohash.decode(data);
+ return [[decoded.longitude, decoded.latitude]];
+ }
+ return [];
+ } catch (error) {
+ return [];
+ }
+ },
+};
+
+export function processPathData(
+ records: DataRecord[],
+ lineColumn: string,
+ ltype: 'polyline' | 'json' | 'geohash' = 'json',
+ reverseLongLatFlag: boolean = false,
+ metricLabel?: string,
+ jsCols?: string[],
+): PathFeature[] {
+ if (!records.length || !lineColumn) {
+ return [];
+ }
+
+ const decoder = decoders[ltype] || decoders.json;
+ const excludeKeys = new Set(
+ [
+ ltype !== 'geohash' ? lineColumn : undefined,
+ 'timestamp',
+ DTTM_ALIAS,
+ metricLabel,
+ ...(jsCols || []),
+ ].filter(Boolean) as string[],
+ );
+
+ return records.map(record => {
+ const lineData = record[lineColumn];
+ let path: [number, number][] = [];
+
+ if (lineData) {
+ path = decoder(String(lineData));
+ if (reverseLongLatFlag && path.length > 0) {
+ path = path.map(([lng, lat]) => [lat, lng]);
+ }
+ }
+
+ let feature: PathFeature = {
+ path,
+ timestamp: record[DTTM_ALIAS],
+ extraProps: {},
+ };
+
+ if (metricLabel && record[metricLabel] != null) {
+ const metricValue = parseMetricValue(record[metricLabel]);
+ if (metricValue !== undefined) {
+ feature.metric = metricValue;
+ }
+ }
+
+ feature = addJsColumnsToExtraProps(feature, record, jsCols);
+ feature = addPropertiesToFeature(feature, record, excludeKeys);
+ return feature;
+ });
+}
+
+function transformProps(chartProps: ChartProps) {
+ const { rawFormData: formData } = chartProps;
+ const {
+ line_column,
+ line_type = 'json',
+ metric,
+ reverse_long_lat = false,
+ js_columns,
+ } = formData as DeckPathTransformPropsFormData;
+
+ const metricLabel = getMetricLabelFromFormData(metric);
+ const records = getRecordsFromQuery(chartProps.queriesData);
+ const features = processPathData(
+ records,
+ line_column || '',
+ line_type,
+ reverse_long_lat,
+ metricLabel,
+ js_columns,
+ ).reverse();
+
+ return createBaseTransformResult(
+ chartProps,
+ features,
+ metricLabel ? [metricLabel] : [],
+ );
+}
+
+// ─── Plugin definition ───────────────────────────────────────────────────────
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export default defineChart<Record<string, never>, any>({
+ metadata: {
+ name: t('deck.gl Path'),
+ description: t('Visualizes connected points, which form a path, on a map.'),
+ category: t('Map'),
+ credits: ['https://uber.github.io/deck.gl'],
+ behaviors: [Behavior.InteractiveChart],
+ tags: [t('deckGL'), t('Web')],
+ thumbnail,
+ thumbnailDark,
+ exampleGallery: [{ url: example, urlDark: exampleDark }],
+ },
+ arguments: {},
+ suppressQuerySection: true,
+ prependSections: [
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ [dndLineColumn],
+ [
+ {
+ ...lineType,
+ config: {
+ ...lineType.config,
+ choices: [
+ ['polyline', t('Polyline')],
+ ['json', t('JSON')],
+ ],
+ },
+ },
+ ],
+ ['row_limit'],
+ [filterNulls],
+ ['adhoc_filters'],
+ [tooltipContents],
+ [tooltipTemplate],
+ ],
+ },
+ {
+ label: t('Map'),
+ expanded: true,
+ controlSetRows: [
+ [mapProvider],
+ [mapboxStyle],
+ [maplibreStyle],
+ [viewport],
+ ['color_picker'],
+ [lineWidth],
+ [
+ {
+ name: 'line_width_unit',
+ config: {
+ type: 'SelectControl',
+ label: t('Line width unit'),
+ default: 'meters',
+ choices: [
+ ['meters', t('meters')],
+ ['pixels', t('pixels')],
+ ],
+ renderTrigger: true,
+ },
+ },
+ ],
+ [reverseLongLat],
+ [autozoom],
+ ],
+ },
+ {
+ label: t('Advanced'),
+ controlSetRows: [
+ [jsColumns],
+ [jsDataMutator],
+ [jsTooltip],
+ [jsOnclickHref],
+ ],
+ },
+ ],
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ buildQuery: (formData: any) => buildQuery(formData as DeckPathFormData),
+ transform: chartProps => transformProps(chartProps),
+ render: ({ transformedProps }) => (
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ <PathComponent {...(transformedProps as any)} />
+ ),
+});
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/transformProps.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/transformProps.ts
deleted file mode 100644
index 702ca92b4ae..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Path/transformProps.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
- * 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 { ChartProps, DTTM_ALIAS } from '@superset-ui/core';
-import { addJsColumnsToExtraProps, DataRecord } from '../spatialUtils';
-import {
- createBaseTransformResult,
- getRecordsFromQuery,
- getMetricLabelFromFormData,
- parseMetricValue,
- addPropertiesToFeature,
-} from '../transformUtils';
-import { DeckPathFormData } from './buildQuery';
-
-declare global {
- interface Window {
- polyline?: {
- decode: (data: string) => [number, number][];
- };
- geohash?: {
- decode: (data: string) => { longitude: number; latitude: number };
- };
- }
-}
-
-export interface DeckPathTransformPropsFormData extends DeckPathFormData {
- js_data_mutator?: string;
- js_tooltip?: string;
- js_onclick_href?: string;
-}
-
-interface PathFeature {
- path: [number, number][];
- metric?: number;
- timestamp?: unknown;
- extraProps?: Record<string, unknown>;
- [key: string]: unknown;
-}
-
-const decoders = {
- json: (data: string): [number, number][] => {
- try {
- const parsed = JSON.parse(data);
- return Array.isArray(parsed) ? parsed : [];
- } catch (error) {
- return [];
- }
- },
- polyline: (data: string): [number, number][] => {
- try {
- if (typeof window !== 'undefined' && window.polyline) {
- return window.polyline.decode(data);
- }
- return [];
- } catch (error) {
- return [];
- }
- },
- geohash: (data: string): [number, number][] => {
- try {
- if (typeof window !== 'undefined' && window.geohash) {
- const decoded = window.geohash.decode(data);
- return [[decoded.longitude, decoded.latitude]];
- }
- return [];
- } catch (error) {
- return [];
- }
- },
-};
-
-function processPathData(
- records: DataRecord[],
- lineColumn: string,
- lineType: 'polyline' | 'json' | 'geohash' = 'json',
- reverseLongLat: boolean = false,
- metricLabel?: string,
- jsColumns?: string[],
-): PathFeature[] {
- if (!records.length || !lineColumn) {
- return [];
- }
-
- const decoder = decoders[lineType] || decoders.json;
- const excludeKeys = new Set(
- [
- lineType !== 'geohash' ? lineColumn : undefined,
- 'timestamp',
- DTTM_ALIAS,
- metricLabel,
- ...(jsColumns || []),
- ].filter(Boolean) as string[],
- );
-
- return records.map(record => {
- const lineData = record[lineColumn];
- let path: [number, number][] = [];
-
- if (lineData) {
- path = decoder(String(lineData));
- if (reverseLongLat && path.length > 0) {
- path = path.map(([lng, lat]) => [lat, lng]);
- }
- }
-
- let feature: PathFeature = {
- path,
- timestamp: record[DTTM_ALIAS],
- extraProps: {},
- };
-
- if (metricLabel && record[metricLabel] != null) {
- const metricValue = parseMetricValue(record[metricLabel]);
- if (metricValue !== undefined) {
- feature.metric = metricValue;
- }
- }
-
- feature = addJsColumnsToExtraProps(feature, record, jsColumns);
- feature = addPropertiesToFeature(feature, record, excludeKeys);
- return feature;
- });
-}
-
-export default function transformProps(chartProps: ChartProps) {
- const { rawFormData: formData } = chartProps;
- const {
- line_column,
- line_type = 'json',
- metric,
- reverse_long_lat = false,
- js_columns,
- } = formData as DeckPathTransformPropsFormData;
-
- const metricLabel = getMetricLabelFromFormData(metric);
- const records = getRecordsFromQuery(chartProps.queriesData);
- const features = processPathData(
- records,
- line_column || '',
- line_type,
- reverse_long_lat,
- metricLabel,
- js_columns,
- ).reverse();
-
- return createBaseTransformResult(
- chartProps,
- features,
- metricLabel ? [metricLabel] : [],
- );
-}
(superset) 02/02: feat(glyph): consolidate deckgl Polygon layer to defineChart()
Posted by ru...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch feat/glyph-single-file
in repository https://gitbox.apache.org/repos/asf/superset.git
commit dd1226f8e59aac252ef2faca3d29a3d9002cd616
Author: Evan Rusackas <ev...@rusackas.com>
AuthorDate: Thu May 14 18:56:52 2026 -0700
feat(glyph): consolidate deckgl Polygon layer to defineChart()
Collapse multi-file plugin into single index.tsx. Polygon keeps its
custom buildQuery (line_column required, optional metric/elevation
metric handling, null filter) and transformProps (json/geohash/zipcode
polygon decoders, fixed/metric elevation, reverse_long_lat). Largest
deckgl layer (500+ lines) but follows the same pattern. Polygon.tsx
component stays as sibling for Multi.
Co-Authored-By: Claude Sonnet 4.6 <no...@anthropic.com>
---
.../src/layers/Polygon/buildQuery.ts | 129 -----
.../src/layers/Polygon/controlPanel.ts | 225 ---------
.../src/layers/Polygon/index.ts | 53 ---
.../src/layers/Polygon/index.tsx | 521 +++++++++++++++++++++
.../src/layers/Polygon/transformProps.ts | 186 --------
5 files changed, 521 insertions(+), 593 deletions(-)
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/buildQuery.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/buildQuery.ts
deleted file mode 100644
index ec319a9ae2c..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/buildQuery.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * 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 {
- buildQueryContext,
- ensureIsArray,
- SqlaFormData,
- getMetricLabel,
- QueryObjectFilterClause,
- QueryObject,
- QueryFormColumn,
- QueryFormMetric,
-} from '@superset-ui/core';
-import { addTooltipColumnsToQuery } from '../buildQueryUtils';
-
-export interface DeckPolygonFormData extends SqlaFormData {
- line_column?: string;
- line_type?: string;
- metric?: string;
- point_radius_fixed?:
- | {
- value?: string;
- }
- | {
- type: 'fix';
- value: string;
- }
- | {
- type: 'metric';
- value: QueryFormMetric;
- };
- reverse_long_lat?: boolean;
- filter_nulls?: boolean;
- js_columns?: string[];
- tooltip_contents?: unknown[];
- tooltip_template?: string;
-}
-
-export default function buildQuery(formData: DeckPolygonFormData) {
- const {
- line_column,
- metric,
- point_radius_fixed,
- filter_nulls = true,
- js_columns,
- tooltip_contents,
- } = formData;
-
- if (!line_column) {
- throw new Error('Polygon column is required for Polygon charts');
- }
-
- return buildQueryContext(formData, (baseQueryObject: QueryObject) => {
- let columns: QueryFormColumn[] = [
- ...ensureIsArray(baseQueryObject.columns || []),
- line_column,
- ];
-
- const jsColumns = ensureIsArray(js_columns || []);
- jsColumns.forEach((col: string) => {
- if (!columns.includes(col)) {
- columns.push(col);
- }
- });
-
- columns = addTooltipColumnsToQuery(columns, tooltip_contents);
-
- const metrics = [];
- if (metric) {
- metrics.push(metric);
- }
-
- if (point_radius_fixed) {
- if ('type' in point_radius_fixed) {
- if (
- point_radius_fixed.type === 'metric' &&
- point_radius_fixed.value != null
- ) {
- metrics.push(point_radius_fixed.value);
- }
- }
- }
-
- const filters = ensureIsArray(baseQueryObject.filters || []);
- if (filter_nulls) {
- const nullFilters: QueryObjectFilterClause[] = [
- {
- col: line_column,
- op: 'IS NOT NULL',
- },
- ];
-
- if (metric) {
- nullFilters.push({
- col: getMetricLabel(metric),
- op: 'IS NOT NULL',
- });
- }
-
- filters.push(...nullFilters);
- }
-
- return [
- {
- ...baseQueryObject,
- columns,
- metrics,
- filters,
- is_timeseries: false,
- row_limit: baseQueryObject.row_limit,
- },
- ];
- });
-}
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/controlPanel.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/controlPanel.ts
deleted file mode 100644
index 010e079d00f..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/controlPanel.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-/**
- * 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,
-} from '@superset-ui/chart-controls';
-import { t } from '@apache-superset/core/translation';
-import timeGrainSqlaAnimationOverrides from '../../utilities/controls';
-import { COLOR_SCHEME_TYPES, formatSelectOptions } from '../../utilities/utils';
-import {
- filterNulls,
- autozoom,
- jsColumns,
- jsDataMutator,
- jsTooltip,
- jsOnclickHref,
- legendFormat,
- legendPosition,
- fillColorPicker,
- strokeColorPicker,
- filled,
- stroked,
- extruded,
- viewport,
- pointRadiusFixed,
- multiplier,
- lineWidth,
- lineType,
- reverseLongLat,
- mapboxStyle,
- maplibreStyle,
- mapProvider,
- deckGLCategoricalColorSchemeTypeSelect,
- deckGLLinearColorSchemeSelect,
- deckGLColorBreakpointsSelect,
- breakpointsDefaultColor,
- tooltipContents,
- tooltipTemplate,
-} from '../../utilities/Shared_DeckGL';
-import { dndLineColumn } from '../../utilities/sharedDndControls';
-
-const config: ControlPanelConfig = {
- controlPanelSections: [
- {
- label: t('Query'),
- expanded: true,
- controlSetRows: [
- [
- {
- ...dndLineColumn,
- config: {
- ...dndLineColumn.config,
- label: t('Polygon Column'),
- },
- },
- ],
- [
- {
- ...lineType,
- config: {
- ...lineType.config,
- label: t('Polygon Encoding'),
- },
- },
- ],
- ['adhoc_filters'],
- ['metric'],
- [
- {
- ...pointRadiusFixed,
- config: {
- ...pointRadiusFixed.config,
- label: t('Elevation'),
- },
- },
- ],
- ['row_limit'],
- [reverseLongLat],
- [filterNulls],
- [tooltipContents],
- [tooltipTemplate],
- ],
- },
- {
- label: t('Map'),
- expanded: true,
- controlSetRows: [
- [mapProvider],
- [mapboxStyle],
- [maplibreStyle],
- [viewport],
- [autozoom],
- ],
- },
- {
- label: t('Polygon Settings'),
- expanded: true,
- controlSetRows: [
- [
- {
- ...deckGLCategoricalColorSchemeTypeSelect,
- config: {
- ...deckGLCategoricalColorSchemeTypeSelect.config,
- choices: [
- [COLOR_SCHEME_TYPES.fixed_color, t('Fixed color')],
- [COLOR_SCHEME_TYPES.linear_palette, t('Linear palette')],
- [COLOR_SCHEME_TYPES.color_breakpoints, t('Color breakpoints')],
- ],
- default: COLOR_SCHEME_TYPES.linear_palette,
- },
- },
- fillColorPicker,
- deckGLLinearColorSchemeSelect,
- breakpointsDefaultColor,
- deckGLColorBreakpointsSelect,
- strokeColorPicker,
- ],
- [filled, stroked],
- [extruded],
- [multiplier],
- [lineWidth],
- [
- {
- name: 'line_width_unit',
- config: {
- type: 'SelectControl',
- label: t('Line width unit'),
- default: 'pixels',
- choices: [
- ['meters', t('meters')],
- ['pixels', t('pixels')],
- ],
- renderTrigger: true,
- },
- },
- ],
- [
- {
- name: 'opacity',
- config: {
- type: 'SliderControl',
- label: t('Opacity'),
- default: 80,
- step: 1,
- min: 0,
- max: 100,
- renderTrigger: true,
- description: t('Opacity, expects values between 0 and 100'),
- },
- },
- ],
- [
- {
- name: 'num_buckets',
- config: {
- type: 'SelectControl',
- multi: false,
- freeForm: true,
- label: t('Number of buckets to group data'),
- default: 5,
- choices: formatSelectOptions([2, 3, 5, 10]),
- description: t('How many buckets should the data be grouped in.'),
- renderTrigger: true,
- },
- },
- ],
- [
- {
- name: 'break_points',
- config: {
- type: 'SelectControl',
- multi: true,
- freeForm: true,
- label: t('Bucket break points'),
- choices: formatSelectOptions([]),
- description: t(
- 'List of n+1 values for bucketing metric into n buckets.',
- ),
- renderTrigger: true,
- },
- },
- ],
- [legendPosition],
- [legendFormat],
- ],
- },
- {
- label: t('Advanced'),
- controlSetRows: [
- [jsColumns],
- [jsDataMutator],
- [jsTooltip],
- [jsOnclickHref],
- ],
- },
- ],
- controlOverrides: {
- metric: {
- validators: [],
- },
- time_grain_sqla: timeGrainSqlaAnimationOverrides,
- },
- formDataOverrides: formData => ({
- ...formData,
- metric: getStandardizedControls().shiftMetric(),
- }),
-};
-
-export default config;
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/index.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/index.ts
deleted file mode 100644
index 0d5a691ab4e..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * 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 '@apache-superset/core/translation';
-import { ChartMetadata, ChartPlugin, Behavior } from '@superset-ui/core';
-import thumbnail from './images/thumbnail.png';
-import thumbnailDark from './images/thumbnail-dark.png';
-import example from './images/example.png';
-import exampleDark from './images/example-dark.png';
-import transformProps from './transformProps';
-import buildQuery from './buildQuery';
-import controlPanel from './controlPanel';
-
-const metadata = new ChartMetadata({
- category: t('Map'),
- credits: ['https://uber.github.io/deck.gl'],
- description: t(
- 'Visualizes geographic areas from your data as polygons on a Mapbox rendered map. Polygons can be colored using a metric.',
- ),
- name: t('deck.gl Polygon'),
- thumbnail,
- thumbnailDark,
- exampleGallery: [{ url: example, urlDark: exampleDark }],
- tags: [t('deckGL'), t('3D'), t('Multi-Dimensions'), t('Geo')],
- behaviors: [Behavior.InteractiveChart],
-});
-
-export default class PolygonChartPlugin extends ChartPlugin {
- constructor() {
- super({
- buildQuery,
- loadChart: () => import('./Polygon'),
- controlPanel,
- metadata,
- transformProps,
- });
- }
-}
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/index.tsx b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/index.tsx
new file mode 100644
index 00000000000..2c6155ec805
--- /dev/null
+++ b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/index.tsx
@@ -0,0 +1,521 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import {
+ Behavior,
+ buildQueryContext,
+ ChartProps,
+ ensureIsArray,
+ getMetricLabel,
+ QueryFormColumn,
+ QueryFormMetric,
+ QueryObject,
+ QueryObjectFilterClause,
+ SqlaFormData,
+} from '@superset-ui/core';
+import { getStandardizedControls } from '@superset-ui/chart-controls';
+import { defineChart } from '@superset-ui/glyph-core';
+import { decode_bbox } from 'ngeohash';
+import PolygonComponent from './Polygon';
+import { addJsColumnsToExtraProps, DataRecord } from '../spatialUtils';
+import {
+ createBaseTransformResult,
+ getRecordsFromQuery,
+ getMetricLabelFromFormData,
+ parseMetricValue,
+ addPropertiesToFeature,
+} from '../transformUtils';
+import { addTooltipColumnsToQuery } from '../buildQueryUtils';
+import timeGrainSqlaAnimationOverrides from '../../utilities/controls';
+import { COLOR_SCHEME_TYPES, formatSelectOptions } from '../../utilities/utils';
+import {
+ filterNulls,
+ autozoom,
+ jsColumns,
+ jsDataMutator,
+ jsTooltip,
+ jsOnclickHref,
+ legendFormat,
+ legendPosition,
+ fillColorPicker,
+ strokeColorPicker,
+ filled,
+ stroked,
+ extruded,
+ viewport,
+ pointRadiusFixed,
+ multiplier,
+ lineWidth,
+ lineType,
+ reverseLongLat,
+ mapboxStyle,
+ maplibreStyle,
+ mapProvider,
+ deckGLCategoricalColorSchemeTypeSelect,
+ deckGLLinearColorSchemeSelect,
+ deckGLColorBreakpointsSelect,
+ breakpointsDefaultColor,
+ tooltipContents,
+ tooltipTemplate,
+} from '../../utilities/Shared_DeckGL';
+import { dndLineColumn } from '../../utilities/sharedDndControls';
+import thumbnail from './images/thumbnail.png';
+import thumbnailDark from './images/thumbnail-dark.png';
+import example from './images/example.png';
+import exampleDark from './images/example-dark.png';
+
+// ─── Types ───────────────────────────────────────────────────────────────────
+
+export interface DeckPolygonFormData extends SqlaFormData {
+ line_column?: string;
+ line_type?: string;
+ metric?: string;
+ point_radius_fixed?:
+ | { value?: string }
+ | { type: 'fix'; value: string }
+ | { type: 'metric'; value: QueryFormMetric };
+ reverse_long_lat?: boolean;
+ filter_nulls?: boolean;
+ js_columns?: string[];
+ tooltip_contents?: unknown[];
+ tooltip_template?: string;
+}
+
+interface PolygonFeature {
+ polygon?: number[][];
+ name?: string;
+ elevation?: number;
+ extraProps?: Record<string, unknown>;
+ metrics?: Record<string, number | string>;
+}
+
+// ─── buildQuery ──────────────────────────────────────────────────────────────
+
+export function buildQuery(formData: DeckPolygonFormData) {
+ const {
+ line_column,
+ metric,
+ point_radius_fixed,
+ filter_nulls = true,
+ js_columns,
+ tooltip_contents,
+ } = formData;
+
+ if (!line_column) {
+ throw new Error('Polygon column is required for Polygon charts');
+ }
+
+ return buildQueryContext(formData, (baseQueryObject: QueryObject) => {
+ let columns: QueryFormColumn[] = [
+ ...ensureIsArray(baseQueryObject.columns || []),
+ line_column,
+ ];
+
+ const jsCols = ensureIsArray(js_columns || []);
+ jsCols.forEach((col: string) => {
+ if (!columns.includes(col)) {
+ columns.push(col);
+ }
+ });
+
+ columns = addTooltipColumnsToQuery(columns, tooltip_contents);
+
+ const metrics = [];
+ if (metric) {
+ metrics.push(metric);
+ }
+
+ if (point_radius_fixed) {
+ if ('type' in point_radius_fixed) {
+ if (
+ point_radius_fixed.type === 'metric' &&
+ point_radius_fixed.value != null
+ ) {
+ metrics.push(point_radius_fixed.value);
+ }
+ }
+ }
+
+ const filters = ensureIsArray(baseQueryObject.filters || []);
+ if (filter_nulls) {
+ const nullFilters: QueryObjectFilterClause[] = [
+ { col: line_column, op: 'IS NOT NULL' },
+ ];
+
+ if (metric) {
+ nullFilters.push({ col: getMetricLabel(metric), op: 'IS NOT NULL' });
+ }
+
+ filters.push(...nullFilters);
+ }
+
+ return [
+ {
+ ...baseQueryObject,
+ columns,
+ metrics,
+ filters,
+ is_timeseries: false,
+ row_limit: baseQueryObject.row_limit,
+ },
+ ];
+ });
+}
+
+// ─── transformProps ──────────────────────────────────────────────────────────
+
+function parseElevationValue(value: string): number | undefined {
+ const parsed = parseFloat(value);
+ return Number.isNaN(parsed) ? undefined : parsed;
+}
+
+export function processPolygonData(
+ records: DataRecord[],
+ formData: DeckPolygonFormData,
+): PolygonFeature[] {
+ const {
+ line_column,
+ line_type,
+ metric,
+ point_radius_fixed,
+ reverse_long_lat,
+ js_columns,
+ } = formData;
+
+ if (!line_column || !records.length) {
+ return [];
+ }
+
+ const metricLabel = getMetricLabelFromFormData(metric);
+
+ let elevationLabel: string | undefined;
+ let fixedElevationValue: number | undefined;
+
+ if (point_radius_fixed) {
+ if ('type' in point_radius_fixed) {
+ if (
+ point_radius_fixed.type === 'metric' &&
+ point_radius_fixed.value != null
+ ) {
+ elevationLabel = getMetricLabel(point_radius_fixed.value);
+ } else if (
+ point_radius_fixed.type === 'fix' &&
+ point_radius_fixed.value
+ ) {
+ fixedElevationValue = parseElevationValue(point_radius_fixed.value);
+ }
+ } else if (point_radius_fixed.value) {
+ fixedElevationValue = parseElevationValue(point_radius_fixed.value);
+ }
+ }
+
+ const excludeKeys = new Set([line_column, ...(js_columns || [])]);
+
+ return records
+ .map(record => {
+ let feature: PolygonFeature = {
+ extraProps: {},
+ metrics: {},
+ };
+
+ feature = addJsColumnsToExtraProps(feature, record, js_columns);
+ const updatedFeature = addPropertiesToFeature(
+ feature as unknown as Record<string, unknown>,
+ record,
+ excludeKeys,
+ );
+ feature = updatedFeature as unknown as PolygonFeature;
+
+ const rawPolygonData = record[line_column];
+ if (!rawPolygonData) {
+ return null;
+ }
+
+ try {
+ let polygonCoords: number[][];
+
+ switch (line_type) {
+ case 'json': {
+ const parsed =
+ typeof rawPolygonData === 'string'
+ ? JSON.parse(rawPolygonData)
+ : rawPolygonData;
+
+ if (parsed.coordinates) {
+ polygonCoords = parsed.coordinates[0] || parsed.coordinates;
+ } else if (parsed.geometry?.coordinates) {
+ polygonCoords =
+ parsed.geometry.coordinates[0] || parsed.geometry.coordinates;
+ } else if (Array.isArray(parsed)) {
+ polygonCoords = parsed;
+ } else {
+ return null;
+ }
+ break;
+ }
+ case 'geohash':
+ polygonCoords = [];
+ const decoded = decode_bbox(String(rawPolygonData));
+ if (decoded) {
+ polygonCoords.push([decoded[1], decoded[0]]);
+ polygonCoords.push([decoded[1], decoded[2]]);
+ polygonCoords.push([decoded[3], decoded[2]]);
+ polygonCoords.push([decoded[3], decoded[0]]);
+ polygonCoords.push([decoded[1], decoded[0]]);
+ }
+ break;
+ case 'zipcode':
+ default: {
+ polygonCoords = Array.isArray(rawPolygonData) ? rawPolygonData : [];
+ break;
+ }
+ }
+
+ if (reverse_long_lat && polygonCoords.length > 0) {
+ polygonCoords = polygonCoords.map(coord => [coord[1], coord[0]]);
+ }
+
+ feature.polygon = polygonCoords;
+
+ if (fixedElevationValue !== undefined) {
+ feature.elevation = fixedElevationValue;
+ } else if (elevationLabel && record[elevationLabel] != null) {
+ const elevationValue = parseMetricValue(record[elevationLabel]);
+ if (elevationValue !== undefined) {
+ feature.elevation = elevationValue;
+ }
+ }
+
+ if (metricLabel && record[metricLabel] != null) {
+ const metricValue = record[metricLabel];
+ if (
+ typeof metricValue === 'string' ||
+ typeof metricValue === 'number'
+ ) {
+ feature.metrics![metricLabel] = metricValue;
+ }
+ }
+ } catch {
+ return null;
+ }
+
+ return feature;
+ })
+ .filter((feature): feature is PolygonFeature => feature !== null);
+}
+
+function transformProps(chartProps: ChartProps) {
+ const { rawFormData: formData } = chartProps;
+ const records = getRecordsFromQuery(chartProps.queriesData);
+ const features = processPolygonData(records, formData as DeckPolygonFormData);
+
+ return createBaseTransformResult(chartProps, features);
+}
+
+// ─── Plugin definition ───────────────────────────────────────────────────────
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export default defineChart<Record<string, never>, any>({
+ metadata: {
+ name: t('deck.gl Polygon'),
+ description: t(
+ 'Visualizes geographic areas from your data as polygons on a Mapbox rendered map. Polygons can be colored using a metric.',
+ ),
+ category: t('Map'),
+ credits: ['https://uber.github.io/deck.gl'],
+ behaviors: [Behavior.InteractiveChart],
+ tags: [t('deckGL'), t('3D'), t('Multi-Dimensions'), t('Geo')],
+ thumbnail,
+ thumbnailDark,
+ exampleGallery: [{ url: example, urlDark: exampleDark }],
+ },
+ arguments: {},
+ suppressQuerySection: true,
+ prependSections: [
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ [
+ {
+ ...dndLineColumn,
+ config: {
+ ...dndLineColumn.config,
+ label: t('Polygon Column'),
+ },
+ },
+ ],
+ [
+ {
+ ...lineType,
+ config: {
+ ...lineType.config,
+ label: t('Polygon Encoding'),
+ },
+ },
+ ],
+ ['adhoc_filters'],
+ ['metric'],
+ [
+ {
+ ...pointRadiusFixed,
+ config: {
+ ...pointRadiusFixed.config,
+ label: t('Elevation'),
+ },
+ },
+ ],
+ ['row_limit'],
+ [reverseLongLat],
+ [filterNulls],
+ [tooltipContents],
+ [tooltipTemplate],
+ ],
+ },
+ {
+ label: t('Map'),
+ expanded: true,
+ controlSetRows: [
+ [mapProvider],
+ [mapboxStyle],
+ [maplibreStyle],
+ [viewport],
+ [autozoom],
+ ],
+ },
+ {
+ label: t('Polygon Settings'),
+ expanded: true,
+ controlSetRows: [
+ [
+ {
+ ...deckGLCategoricalColorSchemeTypeSelect,
+ config: {
+ ...deckGLCategoricalColorSchemeTypeSelect.config,
+ choices: [
+ [COLOR_SCHEME_TYPES.fixed_color, t('Fixed color')],
+ [COLOR_SCHEME_TYPES.linear_palette, t('Linear palette')],
+ [COLOR_SCHEME_TYPES.color_breakpoints, t('Color breakpoints')],
+ ],
+ default: COLOR_SCHEME_TYPES.linear_palette,
+ },
+ },
+ fillColorPicker,
+ deckGLLinearColorSchemeSelect,
+ breakpointsDefaultColor,
+ deckGLColorBreakpointsSelect,
+ strokeColorPicker,
+ ],
+ [filled, stroked],
+ [extruded],
+ [multiplier],
+ [lineWidth],
+ [
+ {
+ name: 'line_width_unit',
+ config: {
+ type: 'SelectControl',
+ label: t('Line width unit'),
+ default: 'pixels',
+ choices: [
+ ['meters', t('meters')],
+ ['pixels', t('pixels')],
+ ],
+ renderTrigger: true,
+ },
+ },
+ ],
+ [
+ {
+ name: 'opacity',
+ config: {
+ type: 'SliderControl',
+ label: t('Opacity'),
+ default: 80,
+ step: 1,
+ min: 0,
+ max: 100,
+ renderTrigger: true,
+ description: t('Opacity, expects values between 0 and 100'),
+ },
+ },
+ ],
+ [
+ {
+ name: 'num_buckets',
+ config: {
+ type: 'SelectControl',
+ multi: false,
+ freeForm: true,
+ label: t('Number of buckets to group data'),
+ default: 5,
+ choices: formatSelectOptions([2, 3, 5, 10]),
+ description: t('How many buckets should the data be grouped in.'),
+ renderTrigger: true,
+ },
+ },
+ ],
+ [
+ {
+ name: 'break_points',
+ config: {
+ type: 'SelectControl',
+ multi: true,
+ freeForm: true,
+ label: t('Bucket break points'),
+ choices: formatSelectOptions([]),
+ description: t(
+ 'List of n+1 values for bucketing metric into n buckets.',
+ ),
+ renderTrigger: true,
+ },
+ },
+ ],
+ [legendPosition],
+ [legendFormat],
+ ],
+ },
+ {
+ label: t('Advanced'),
+ controlSetRows: [
+ [jsColumns],
+ [jsDataMutator],
+ [jsTooltip],
+ [jsOnclickHref],
+ ],
+ },
+ ],
+ additionalControlOverrides: {
+ metric: {
+ validators: [],
+ },
+ time_grain_sqla: timeGrainSqlaAnimationOverrides,
+ },
+ formDataOverrides: formData => ({
+ ...formData,
+ metric: getStandardizedControls().shiftMetric(),
+ }),
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ buildQuery: (formData: any) => buildQuery(formData as DeckPolygonFormData),
+ transform: chartProps => transformProps(chartProps),
+ render: ({ transformedProps }) => (
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ <PolygonComponent {...(transformedProps as any)} />
+ ),
+});
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/transformProps.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/transformProps.ts
deleted file mode 100644
index 5a9f71b2a53..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/transformProps.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-/**
- * 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 { ChartProps, getMetricLabel } from '@superset-ui/core';
-import { addJsColumnsToExtraProps, DataRecord } from '../spatialUtils';
-import {
- createBaseTransformResult,
- getRecordsFromQuery,
- getMetricLabelFromFormData,
- parseMetricValue,
- addPropertiesToFeature,
-} from '../transformUtils';
-import { DeckPolygonFormData } from './buildQuery';
-import { decode_bbox } from 'ngeohash';
-
-function parseElevationValue(value: string): number | undefined {
- const parsed = parseFloat(value);
- return Number.isNaN(parsed) ? undefined : parsed;
-}
-
-interface PolygonFeature {
- polygon?: number[][];
- name?: string;
- elevation?: number;
- extraProps?: Record<string, unknown>;
- metrics?: Record<string, number | string>;
-}
-
-function processPolygonData(
- records: DataRecord[],
- formData: DeckPolygonFormData,
-): PolygonFeature[] {
- const {
- line_column,
- line_type,
- metric,
- point_radius_fixed,
- reverse_long_lat,
- js_columns,
- } = formData;
-
- if (!line_column || !records.length) {
- return [];
- }
-
- const metricLabel = getMetricLabelFromFormData(metric);
-
- let elevationLabel: string | undefined;
- let fixedElevationValue: number | undefined;
-
- if (point_radius_fixed) {
- if ('type' in point_radius_fixed) {
- if (
- point_radius_fixed.type === 'metric' &&
- point_radius_fixed.value != null
- ) {
- elevationLabel = getMetricLabel(point_radius_fixed.value);
- } else if (
- point_radius_fixed.type === 'fix' &&
- point_radius_fixed.value
- ) {
- fixedElevationValue = parseElevationValue(point_radius_fixed.value);
- }
- } else if (point_radius_fixed.value) {
- fixedElevationValue = parseElevationValue(point_radius_fixed.value);
- }
- }
-
- const excludeKeys = new Set([line_column, ...(js_columns || [])]);
-
- return records
- .map(record => {
- let feature: PolygonFeature = {
- extraProps: {},
- metrics: {},
- };
-
- feature = addJsColumnsToExtraProps(feature, record, js_columns);
- const updatedFeature = addPropertiesToFeature(
- feature as unknown as Record<string, unknown>,
- record,
- excludeKeys,
- );
- feature = updatedFeature as unknown as PolygonFeature;
-
- const rawPolygonData = record[line_column];
- if (!rawPolygonData) {
- return null;
- }
-
- try {
- let polygonCoords: number[][];
-
- switch (line_type) {
- case 'json': {
- const parsed =
- typeof rawPolygonData === 'string'
- ? JSON.parse(rawPolygonData)
- : rawPolygonData;
-
- if (parsed.coordinates) {
- polygonCoords = parsed.coordinates[0] || parsed.coordinates;
- } else if (parsed.geometry?.coordinates) {
- // Non-standard format with nested geometry
- polygonCoords =
- parsed.geometry.coordinates[0] || parsed.geometry.coordinates;
- } else if (Array.isArray(parsed)) {
- polygonCoords = parsed;
- } else {
- return null;
- }
- break;
- }
- case 'geohash':
- polygonCoords = [];
- const decoded = decode_bbox(String(rawPolygonData));
- if (decoded) {
- polygonCoords.push([decoded[1], decoded[0]]); // SW (minLon, minLat)
- polygonCoords.push([decoded[1], decoded[2]]); // NW (minLon, maxLat)
- polygonCoords.push([decoded[3], decoded[2]]); // NE (maxLon, maxLat)
- polygonCoords.push([decoded[3], decoded[0]]); // SE (maxLon, minLat)
- polygonCoords.push([decoded[1], decoded[0]]); // SW (close polygon)
- }
- break;
- case 'zipcode':
- default: {
- polygonCoords = Array.isArray(rawPolygonData) ? rawPolygonData : [];
- break;
- }
- }
-
- if (reverse_long_lat && polygonCoords.length > 0) {
- polygonCoords = polygonCoords.map(coord => [coord[1], coord[0]]);
- }
-
- feature.polygon = polygonCoords;
-
- if (fixedElevationValue !== undefined) {
- feature.elevation = fixedElevationValue;
- } else if (elevationLabel && record[elevationLabel] != null) {
- const elevationValue = parseMetricValue(record[elevationLabel]);
- if (elevationValue !== undefined) {
- feature.elevation = elevationValue;
- }
- }
-
- if (metricLabel && record[metricLabel] != null) {
- const metricValue = record[metricLabel];
- if (
- typeof metricValue === 'string' ||
- typeof metricValue === 'number'
- ) {
- feature.metrics![metricLabel] = metricValue;
- }
- }
- } catch {
- return null;
- }
-
- return feature;
- })
- .filter((feature): feature is PolygonFeature => feature !== null);
-}
-
-export default function transformProps(chartProps: ChartProps) {
- const { rawFormData: formData } = chartProps;
- const records = getRecordsFromQuery(chartProps.queriesData);
- const features = processPolygonData(records, formData as DeckPolygonFormData);
-
- return createBaseTransformResult(chartProps, features);
-}