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 02:01:42 UTC
(superset) branch feat/glyph-single-file updated: feat(glyph): consolidate deckgl Scatter layer to defineChart()
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
The following commit(s) were added to refs/heads/feat/glyph-single-file by this push:
new 857ba7f855e feat(glyph): consolidate deckgl Scatter layer to defineChart()
857ba7f855e is described below
commit 857ba7f855e8cfd44e770aa5b43ff18846ef66d9
Author: Evan Rusackas <ev...@rusackas.com>
AuthorDate: Thu May 14 19:01:33 2026 -0700
feat(glyph): consolidate deckgl Scatter layer to defineChart()
Collapse multi-file plugin into single index.tsx. Scatter keeps its
custom buildQuery (spatial config required, metric-or-fixed radius
handling) and transformProps (radius from metric/fix value, category
column, js columns).
Also wires `onInit?: ControlPanelConfig['onInit']` through to
defineChart so Scatter can clear time_grain_sqla and granularity on
chart initialization. The onInit hook is now an optional metadata-level
override surfaced in the generated ControlPanelConfig.
Scatter.tsx component stays as sibling for Multi.
Co-Authored-By: Claude Sonnet 4.6 <no...@anthropic.com>
---
.../superset-ui-glyph-core/src/defineChart.tsx | 15 +
.../src/layers/Scatter/buildQuery.ts | 134 -------
.../src/layers/Scatter/controlPanel.ts | 165 --------
.../src/layers/Scatter/index.ts | 61 ---
.../src/layers/Scatter/index.tsx | 438 +++++++++++++++++++++
.../src/layers/Scatter/transformProps.ts | 133 -------
6 files changed, 453 insertions(+), 493 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-glyph-core/src/defineChart.tsx b/superset-frontend/packages/superset-ui-glyph-core/src/defineChart.tsx
index 7814e97a938..4cf1c35647a 100644
--- a/superset-frontend/packages/superset-ui-glyph-core/src/defineChart.tsx
+++ b/superset-frontend/packages/superset-ui-glyph-core/src/defineChart.tsx
@@ -265,6 +265,14 @@ export interface ChartDefinition<
*/
formDataOverrides?: (formData: QueryFormData) => QueryFormData;
+ /**
+ * onInit hook - called once when the chart's controls are first initialized.
+ * Use to reset/override control values that should not persist from the
+ * dataset's defaults (e.g., clearing time_grain_sqla on charts that don't
+ * use it).
+ */
+ onInit?: ControlPanelConfig['onInit'];
+
/**
* Custom buildQuery function - use for charts that need post-processing operators
* If not provided, a default query builder is generated from arguments
@@ -627,6 +635,7 @@ function generateControlPanel<TArgs extends ChartArguments>(
controlOverrides?: ChartDefinition<TArgs>['controlOverrides'],
additionalControlOverrides?: ChartDefinition<TArgs>['additionalControlOverrides'],
formDataOverrides?: ChartDefinition<TArgs>['formDataOverrides'],
+ onInit?: ChartDefinition<TArgs>['onInit'],
additionalSections?: ControlPanelSectionConfig[],
prependSections?: ControlPanelSectionConfig[],
chartOptionsTabOverride?: 'customize' | 'data',
@@ -753,6 +762,10 @@ function generateControlPanel<TArgs extends ChartArguments>(
config.formDataOverrides = formDataOverrides;
}
+ if (onInit) {
+ config.onInit = onInit;
+ }
+
// Store raw glyph args for native rendering (bypasses expandControlConfig pipeline)
config._glyphArgs = args;
@@ -973,6 +986,7 @@ export function defineChart<
controlOverrides,
additionalControlOverrides,
formDataOverrides,
+ onInit,
buildQuery: customBuildQuery,
transform,
render,
@@ -989,6 +1003,7 @@ export function defineChart<
controlOverrides,
additionalControlOverrides,
formDataOverrides,
+ onInit,
additionalSections,
prependSections,
chartOptionsTabOverride,
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/buildQuery.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/buildQuery.ts
deleted file mode 100644
index 231e2bf1f79..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/buildQuery.ts
+++ /dev/null
@@ -1,134 +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,
- getMetricLabel,
- QueryFormMetric,
- QueryFormOrderBy,
- SqlaFormData,
- QueryFormColumn,
- QueryObject,
-} from '@superset-ui/core';
-import {
- getSpatialColumns,
- addSpatialNullFilters,
- SpatialFormData,
-} from '../spatialUtils';
-import {
- addJsColumnsToColumns,
- addTooltipColumnsToQuery,
-} from '../buildQueryUtils';
-import { isMetricValue } from '../utils/metricUtils';
-
-export interface DeckScatterFormData
- extends Omit<SpatialFormData, 'color_picker'>, SqlaFormData {
- // Can be a string (legacy format) or an object with type and value
- point_radius_fixed?:
- | string // Legacy format: metric name directly
- | {
- type?: 'fix' | 'metric';
- value?: QueryFormMetric | number;
- };
- multiplier?: number;
- point_unit?: string;
- min_radius?: number;
- max_radius?: number;
- color_picker?: { r: number; g: number; b: number; a: number };
- dimension?: string;
-}
-
-export default function buildQuery(formData: DeckScatterFormData) {
- const {
- spatial,
- point_radius_fixed,
- dimension,
- js_columns,
- tooltip_contents,
- } = formData;
-
- if (!spatial) {
- throw new Error('Spatial configuration is required for Scatter charts');
- }
-
- return buildQueryContext(formData, {
- buildQuery: (baseQueryObject: QueryObject) => {
- const spatialColumns = getSpatialColumns(spatial);
- let columns = [...(baseQueryObject.columns || []), ...spatialColumns];
-
- if (dimension) {
- columns.push(dimension);
- }
-
- const columnStrings = columns.map(col =>
- typeof col === 'string' ? col : col.label || col.sqlExpression || '',
- );
- const withJsColumns = addJsColumnsToColumns(columnStrings, js_columns);
-
- columns = withJsColumns as QueryFormColumn[];
- columns = addTooltipColumnsToQuery(columns, tooltip_contents);
-
- // Only add metric if point_radius_fixed is a metric type
- const isMetric = isMetricValue(point_radius_fixed);
- // Extract metric value: legacy string format or object with metric value
- const rawValue =
- typeof point_radius_fixed === 'string'
- ? point_radius_fixed
- : point_radius_fixed?.value;
- const metricValue: QueryFormMetric | null =
- isMetric && rawValue !== undefined && typeof rawValue !== 'number'
- ? (rawValue as QueryFormMetric)
- : null;
-
- // Preserve existing metrics and only add radius metric if it's metric-based
- const existingMetrics = baseQueryObject.metrics || [];
- // Deduplicate metrics using getMetricLabel for comparison
- const existingLabels = new Set(
- existingMetrics.map(m => getMetricLabel(m)),
- );
- const metrics: QueryFormMetric[] =
- metricValue && !existingLabels.has(getMetricLabel(metricValue))
- ? [...existingMetrics, metricValue]
- : existingMetrics;
-
- const filters = addSpatialNullFilters(
- spatial,
- ensureIsArray(baseQueryObject.filters || []),
- );
-
- // orderby needs string label, not the full metric object
- const orderby =
- isMetric && metricValue
- ? ([[getMetricLabel(metricValue), false]] as QueryFormOrderBy[])
- : (baseQueryObject.orderby as QueryFormOrderBy[]) || [];
-
- return [
- {
- ...baseQueryObject,
- columns,
- metrics,
- filters,
- orderby,
- is_timeseries: false,
- row_limit: baseQueryObject.row_limit,
- },
- ];
- },
- });
-}
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/controlPanel.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/controlPanel.ts
deleted file mode 100644
index 20a788c82a3..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/controlPanel.ts
+++ /dev/null
@@ -1,165 +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 { validateNonEmpty } from '@superset-ui/core';
-import timeGrainSqlaAnimationOverrides from '../../utilities/controls';
-import {
- filterNulls,
- autozoom,
- jsColumns,
- jsDataMutator,
- jsTooltip,
- jsOnclickHref,
- legendFormat,
- legendPosition,
- viewport,
- spatial,
- pointRadiusFixed,
- multiplier,
- mapboxStyle,
- maplibreStyle,
- mapProvider,
- generateDeckGLColorSchemeControls,
- tooltipContents,
- tooltipTemplate,
-} from '../../utilities/Shared_DeckGL';
-
-const config: ControlPanelConfig = {
- onInit: controlState => ({
- ...controlState,
- time_grain_sqla: {
- ...controlState.time_grain_sqla,
- value: null,
- },
- granularity: {
- ...controlState.granularity,
- value: null,
- },
- }),
- controlPanelSections: [
- {
- label: t('Query'),
- expanded: true,
- controlSetRows: [
- [spatial, null],
- ['row_limit', filterNulls],
- ['adhoc_filters'],
- [tooltipContents],
- [tooltipTemplate],
- ],
- },
- {
- label: t('Map'),
- expanded: true,
- controlSetRows: [
- [mapProvider],
- [mapboxStyle],
- [maplibreStyle],
- [autozoom, viewport],
- ],
- },
- {
- label: t('Point Size'),
- controlSetRows: [
- [pointRadiusFixed],
- [
- {
- name: 'point_unit',
- config: {
- type: 'SelectControl',
- label: t('Point Unit'),
- default: 'square_m',
- clearable: false,
- choices: [
- ['square_m', t('Square meters')],
- ['square_km', t('Square kilometers')],
- ['square_miles', t('Square miles')],
- ['radius_m', t('Radius in meters')],
- ['radius_km', t('Radius in kilometers')],
- ['radius_miles', t('Radius in miles')],
- ],
- description: t(
- 'The unit of measure for the specified point radius',
- ),
- },
- },
- ],
- [
- {
- name: 'min_radius',
- config: {
- type: 'TextControl',
- label: t('Minimum Radius'),
- isFloat: true,
- validators: [validateNonEmpty],
- renderTrigger: true,
- default: 2,
- description: t(
- 'Minimum radius size of the circle, in pixels. As the zoom level changes, this ' +
- 'insures that the circle respects this minimum radius.',
- ),
- },
- },
- {
- name: 'max_radius',
- config: {
- type: 'TextControl',
- label: t('Maximum Radius'),
- isFloat: true,
- validators: [validateNonEmpty],
- renderTrigger: true,
- default: 250,
- description: t(
- 'Maximum radius size of the circle, in pixels. As the zoom level changes, this ' +
- 'insures that the circle respects this maximum radius.',
- ),
- },
- },
- ],
- [multiplier, null],
- ],
- },
- {
- label: t('Point Color'),
- controlSetRows: [
- [legendPosition],
- [legendFormat],
- ...generateDeckGLColorSchemeControls({}),
- ],
- },
- {
- label: t('Advanced'),
- controlSetRows: [
- [jsColumns],
- [jsDataMutator],
- [jsTooltip],
- [jsOnclickHref],
- ],
- },
- ],
- controlOverrides: {
- size: {
- validators: [],
- },
- time_grain_sqla: timeGrainSqlaAnimationOverrides,
- },
-};
-
-export default config;
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/index.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/index.ts
deleted file mode 100644
index ee43b4a2749..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/index.ts
+++ /dev/null
@@ -1,61 +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(
- 'A map that takes rendering circles with a variable radius at latitude/longitude coordinates',
- ),
- name: t('deck.gl Scatterplot'),
- thumbnail,
- thumbnailDark,
- exampleGallery: [{ url: example, urlDark: exampleDark }],
- tags: [
- t('deckGL'),
- t('Comparison'),
- t('Scatter'),
- t('2D'),
- t('Geo'),
- t('Intensity'),
- t('Density'),
- ],
- behaviors: [Behavior.InteractiveChart],
-});
-
-export default class ScatterChartPlugin extends ChartPlugin {
- constructor() {
- super({
- buildQuery,
- loadChart: () => import('./Scatter'),
- controlPanel,
- metadata,
- transformProps,
- });
- }
-}
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/index.tsx b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/index.tsx
new file mode 100644
index 00000000000..596da9f8c48
--- /dev/null
+++ b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/index.tsx
@@ -0,0 +1,438 @@
+/**
+ * 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,
+ QueryFormOrderBy,
+ QueryObject,
+ SqlaFormData,
+ validateNonEmpty,
+} from '@superset-ui/core';
+import { defineChart } from '@superset-ui/glyph-core';
+import ScatterComponent from './Scatter';
+import {
+ processSpatialData,
+ getSpatialColumns,
+ addSpatialNullFilters,
+ SpatialFormData,
+ DataRecord,
+} from '../spatialUtils';
+import {
+ createBaseTransformResult,
+ getRecordsFromQuery,
+ getMetricLabelFromFormData,
+ parseMetricValue,
+ addPropertiesToFeature,
+} from '../transformUtils';
+import {
+ addJsColumnsToColumns,
+ addTooltipColumnsToQuery,
+} from '../buildQueryUtils';
+import {
+ isMetricValue,
+ isFixedValue,
+ getFixedValue,
+} from '../utils/metricUtils';
+import timeGrainSqlaAnimationOverrides from '../../utilities/controls';
+import {
+ filterNulls,
+ autozoom,
+ jsColumns,
+ jsDataMutator,
+ jsTooltip,
+ jsOnclickHref,
+ legendFormat,
+ legendPosition,
+ viewport,
+ spatial,
+ pointRadiusFixed,
+ multiplier,
+ mapboxStyle,
+ maplibreStyle,
+ mapProvider,
+ generateDeckGLColorSchemeControls,
+ tooltipContents,
+ tooltipTemplate,
+} from '../../utilities/Shared_DeckGL';
+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 DeckScatterFormData
+ extends Omit<SpatialFormData, 'color_picker'>,
+ SqlaFormData {
+ // Can be a string (legacy format) or an object with type and value
+ point_radius_fixed?:
+ | string // Legacy format: metric name directly
+ | {
+ type?: 'fix' | 'metric';
+ value?: QueryFormMetric | number;
+ };
+ multiplier?: number;
+ point_unit?: string;
+ min_radius?: number;
+ max_radius?: number;
+ color_picker?: { r: number; g: number; b: number; a: number };
+ dimension?: string;
+}
+
+interface ScatterPoint {
+ position: [number, number];
+ radius?: number;
+ color?: [number, number, number, number];
+ cat_color?: string;
+ metric?: number;
+ extraProps?: Record<string, unknown>;
+ [key: string]: unknown;
+}
+
+// ─── buildQuery ──────────────────────────────────────────────────────────────
+
+export function buildQuery(formData: DeckScatterFormData) {
+ const {
+ spatial: spatialCfg,
+ point_radius_fixed,
+ dimension,
+ js_columns,
+ tooltip_contents,
+ } = formData;
+
+ if (!spatialCfg) {
+ throw new Error('Spatial configuration is required for Scatter charts');
+ }
+
+ return buildQueryContext(formData, {
+ buildQuery: (baseQueryObject: QueryObject) => {
+ const spatialColumns = getSpatialColumns(spatialCfg);
+ let columns = [...(baseQueryObject.columns || []), ...spatialColumns];
+
+ if (dimension) {
+ columns.push(dimension);
+ }
+
+ const columnStrings = columns.map(col =>
+ typeof col === 'string' ? col : col.label || col.sqlExpression || '',
+ );
+ const withJsColumns = addJsColumnsToColumns(columnStrings, js_columns);
+
+ columns = withJsColumns as QueryFormColumn[];
+ columns = addTooltipColumnsToQuery(columns, tooltip_contents);
+
+ const isMetric = isMetricValue(point_radius_fixed);
+ const rawValue =
+ typeof point_radius_fixed === 'string'
+ ? point_radius_fixed
+ : point_radius_fixed?.value;
+ const metricValue: QueryFormMetric | null =
+ isMetric && rawValue !== undefined && typeof rawValue !== 'number'
+ ? (rawValue as QueryFormMetric)
+ : null;
+
+ const existingMetrics = baseQueryObject.metrics || [];
+ const existingLabels = new Set(
+ existingMetrics.map(m => getMetricLabel(m)),
+ );
+ const metrics: QueryFormMetric[] =
+ metricValue && !existingLabels.has(getMetricLabel(metricValue))
+ ? [...existingMetrics, metricValue]
+ : existingMetrics;
+
+ const filters = addSpatialNullFilters(
+ spatialCfg,
+ ensureIsArray(baseQueryObject.filters || []),
+ );
+
+ const orderby =
+ isMetric && metricValue
+ ? ([[getMetricLabel(metricValue), false]] as QueryFormOrderBy[])
+ : (baseQueryObject.orderby as QueryFormOrderBy[]) || [];
+
+ return [
+ {
+ ...baseQueryObject,
+ columns,
+ metrics,
+ filters,
+ orderby,
+ is_timeseries: false,
+ row_limit: baseQueryObject.row_limit,
+ },
+ ];
+ },
+ });
+}
+
+// ─── transformProps ──────────────────────────────────────────────────────────
+
+export function processScatterData(
+ records: DataRecord[],
+ spatialCfg: DeckScatterFormData['spatial'],
+ radiusMetricLabel?: string,
+ categoryColumn?: string,
+ jsCols?: string[],
+ fixedRadiusValue?: number | string | null,
+): ScatterPoint[] {
+ if (!spatialCfg || !records.length) {
+ return [];
+ }
+
+ const spatialFeatures = processSpatialData(records, spatialCfg);
+ const excludeKeys = new Set([
+ 'position',
+ 'weight',
+ 'extraProps',
+ ...(spatialCfg
+ ? [
+ spatialCfg.lonCol,
+ spatialCfg.latCol,
+ spatialCfg.lonlatCol,
+ spatialCfg.geohashCol,
+ ].filter(Boolean)
+ : []),
+ radiusMetricLabel,
+ categoryColumn,
+ ...(jsCols || []),
+ ]);
+
+ return spatialFeatures.map(feature => {
+ let scatterPoint: ScatterPoint = {
+ position: feature.position,
+ extraProps: feature.extraProps || {},
+ };
+
+ if (fixedRadiusValue != null) {
+ const parsedFixedRadius = parseMetricValue(fixedRadiusValue);
+ if (parsedFixedRadius !== undefined) {
+ scatterPoint.radius = parsedFixedRadius;
+ }
+ } else if (radiusMetricLabel && feature[radiusMetricLabel] != null) {
+ const radiusValue = parseMetricValue(feature[radiusMetricLabel]);
+ if (radiusValue !== undefined) {
+ scatterPoint.radius = radiusValue;
+ scatterPoint.metric = radiusValue;
+ }
+ }
+
+ if (categoryColumn && feature[categoryColumn] != null) {
+ scatterPoint.cat_color = String(feature[categoryColumn]);
+ }
+
+ scatterPoint = addPropertiesToFeature(
+ scatterPoint,
+ feature as DataRecord,
+ excludeKeys,
+ );
+ return scatterPoint;
+ });
+}
+
+function transformProps(chartProps: ChartProps) {
+ const { rawFormData: formData } = chartProps;
+ const {
+ spatial: spatialCfg,
+ point_radius_fixed,
+ dimension,
+ js_columns,
+ } = formData as DeckScatterFormData;
+
+ const fixedRadiusValue = isFixedValue(point_radius_fixed)
+ ? getFixedValue(point_radius_fixed)
+ : null;
+
+ const radiusMetricLabel = getMetricLabelFromFormData(point_radius_fixed);
+ const records = getRecordsFromQuery(chartProps.queriesData);
+
+ const features = processScatterData(
+ records,
+ spatialCfg,
+ radiusMetricLabel,
+ dimension,
+ js_columns,
+ fixedRadiusValue,
+ );
+
+ return createBaseTransformResult(
+ chartProps,
+ features,
+ radiusMetricLabel ? [radiusMetricLabel] : [],
+ );
+}
+
+// ─── Plugin definition ───────────────────────────────────────────────────────
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export default defineChart<Record<string, never>, any>({
+ metadata: {
+ name: t('deck.gl Scatterplot'),
+ description: t(
+ 'A map that takes rendering circles with a variable radius at latitude/longitude coordinates',
+ ),
+ category: t('Map'),
+ credits: ['https://uber.github.io/deck.gl'],
+ behaviors: [Behavior.InteractiveChart],
+ tags: [
+ t('deckGL'),
+ t('Comparison'),
+ t('Scatter'),
+ t('2D'),
+ t('Geo'),
+ t('Intensity'),
+ t('Density'),
+ ],
+ thumbnail,
+ thumbnailDark,
+ exampleGallery: [{ url: example, urlDark: exampleDark }],
+ },
+ arguments: {},
+ suppressQuerySection: true,
+ onInit: controlState => ({
+ ...controlState,
+ time_grain_sqla: {
+ ...controlState.time_grain_sqla,
+ value: null,
+ },
+ granularity: {
+ ...controlState.granularity,
+ value: null,
+ },
+ }),
+ prependSections: [
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ [spatial, null],
+ ['row_limit', filterNulls],
+ ['adhoc_filters'],
+ [tooltipContents],
+ [tooltipTemplate],
+ ],
+ },
+ {
+ label: t('Map'),
+ expanded: true,
+ controlSetRows: [
+ [mapProvider],
+ [mapboxStyle],
+ [maplibreStyle],
+ [autozoom, viewport],
+ ],
+ },
+ {
+ label: t('Point Size'),
+ controlSetRows: [
+ [pointRadiusFixed],
+ [
+ {
+ name: 'point_unit',
+ config: {
+ type: 'SelectControl',
+ label: t('Point Unit'),
+ default: 'square_m',
+ clearable: false,
+ choices: [
+ ['square_m', t('Square meters')],
+ ['square_km', t('Square kilometers')],
+ ['square_miles', t('Square miles')],
+ ['radius_m', t('Radius in meters')],
+ ['radius_km', t('Radius in kilometers')],
+ ['radius_miles', t('Radius in miles')],
+ ],
+ description: t(
+ 'The unit of measure for the specified point radius',
+ ),
+ },
+ },
+ ],
+ [
+ {
+ name: 'min_radius',
+ config: {
+ type: 'TextControl',
+ label: t('Minimum Radius'),
+ isFloat: true,
+ validators: [validateNonEmpty],
+ renderTrigger: true,
+ default: 2,
+ description: t(
+ 'Minimum radius size of the circle, in pixels. As the zoom level changes, this ' +
+ 'insures that the circle respects this minimum radius.',
+ ),
+ },
+ },
+ {
+ name: 'max_radius',
+ config: {
+ type: 'TextControl',
+ label: t('Maximum Radius'),
+ isFloat: true,
+ validators: [validateNonEmpty],
+ renderTrigger: true,
+ default: 250,
+ description: t(
+ 'Maximum radius size of the circle, in pixels. As the zoom level changes, this ' +
+ 'insures that the circle respects this maximum radius.',
+ ),
+ },
+ },
+ ],
+ [multiplier, null],
+ ],
+ },
+ {
+ label: t('Point Color'),
+ controlSetRows: [
+ [legendPosition],
+ [legendFormat],
+ ...generateDeckGLColorSchemeControls({}),
+ ],
+ },
+ {
+ label: t('Advanced'),
+ controlSetRows: [
+ [jsColumns],
+ [jsDataMutator],
+ [jsTooltip],
+ [jsOnclickHref],
+ ],
+ },
+ ],
+ additionalControlOverrides: {
+ size: {
+ validators: [],
+ },
+ time_grain_sqla: timeGrainSqlaAnimationOverrides,
+ },
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ buildQuery: (formData: any) => buildQuery(formData as DeckScatterFormData),
+ transform: chartProps => transformProps(chartProps),
+ render: ({ transformedProps }) => (
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ <ScatterComponent {...(transformedProps as any)} />
+ ),
+});
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/transformProps.ts b/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/transformProps.ts
deleted file mode 100644
index a168bd821eb..00000000000
--- a/superset-frontend/plugins/preset-chart-deckgl/src/layers/Scatter/transformProps.ts
+++ /dev/null
@@ -1,133 +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 } from '@superset-ui/core';
-import { processSpatialData, DataRecord } from '../spatialUtils';
-import {
- createBaseTransformResult,
- getRecordsFromQuery,
- getMetricLabelFromFormData,
- parseMetricValue,
- addPropertiesToFeature,
-} from '../transformUtils';
-import { DeckScatterFormData } from './buildQuery';
-import { isFixedValue, getFixedValue } from '../utils/metricUtils';
-
-interface ScatterPoint {
- position: [number, number];
- radius?: number;
- color?: [number, number, number, number];
- cat_color?: string;
- metric?: number;
- extraProps?: Record<string, unknown>;
- [key: string]: unknown;
-}
-
-function processScatterData(
- records: DataRecord[],
- spatial: DeckScatterFormData['spatial'],
- radiusMetricLabel?: string,
- categoryColumn?: string,
- jsColumns?: string[],
- fixedRadiusValue?: number | string | null,
-): ScatterPoint[] {
- if (!spatial || !records.length) {
- return [];
- }
-
- const spatialFeatures = processSpatialData(records, spatial);
- const excludeKeys = new Set([
- 'position',
- 'weight',
- 'extraProps',
- ...(spatial
- ? [
- spatial.lonCol,
- spatial.latCol,
- spatial.lonlatCol,
- spatial.geohashCol,
- ].filter(Boolean)
- : []),
- radiusMetricLabel,
- categoryColumn,
- ...(jsColumns || []),
- ]);
-
- return spatialFeatures.map(feature => {
- let scatterPoint: ScatterPoint = {
- position: feature.position,
- extraProps: feature.extraProps || {},
- };
-
- // Handle radius: either from metric or fixed value
- if (fixedRadiusValue != null) {
- // Use fixed radius value for all points
- const parsedFixedRadius = parseMetricValue(fixedRadiusValue);
- if (parsedFixedRadius !== undefined) {
- scatterPoint.radius = parsedFixedRadius;
- }
- } else if (radiusMetricLabel && feature[radiusMetricLabel] != null) {
- // Use metric value for radius
- const radiusValue = parseMetricValue(feature[radiusMetricLabel]);
- if (radiusValue !== undefined) {
- scatterPoint.radius = radiusValue;
- scatterPoint.metric = radiusValue;
- }
- }
-
- if (categoryColumn && feature[categoryColumn] != null) {
- scatterPoint.cat_color = String(feature[categoryColumn]);
- }
-
- scatterPoint = addPropertiesToFeature(
- scatterPoint,
- feature as DataRecord,
- excludeKeys,
- );
- return scatterPoint;
- });
-}
-
-export default function transformProps(chartProps: ChartProps) {
- const { rawFormData: formData } = chartProps;
- const { spatial, point_radius_fixed, dimension, js_columns } =
- formData as DeckScatterFormData;
-
- // Check if this is a fixed value or metric
- const fixedRadiusValue = isFixedValue(point_radius_fixed)
- ? getFixedValue(point_radius_fixed)
- : null;
-
- const radiusMetricLabel = getMetricLabelFromFormData(point_radius_fixed);
- const records = getRecordsFromQuery(chartProps.queriesData);
-
- const features = processScatterData(
- records,
- spatial,
- radiusMetricLabel,
- dimension,
- js_columns,
- fixedRadiusValue,
- );
-
- return createBaseTransformResult(
- chartProps,
- features,
- radiusMetricLabel ? [radiusMetricLabel] : [],
- );
-}