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/07/01 05:48:50 UTC

(superset) branch master updated: fix(point-cluster-map): guard invalid point radii (#40393)

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 3e88b487b32 fix(point-cluster-map): guard invalid point radii (#40393)
3e88b487b32 is described below

commit 3e88b487b32d2721f7e419225d91572488365a2a
Author: Imamatdin <im...@gmail.com>
AuthorDate: Wed Jul 1 10:48:43 2026 +0500

    fix(point-cluster-map): guard invalid point radii (#40393)
    
    Co-authored-by: Imamatdin <20...@users.noreply.github.com>
---
 .../src/components/ScatterPlotOverlay.tsx          |  8 +++-
 .../test/ScatterPlotOverlay.test.tsx               | 52 +++++++++++++++++++++-
 2 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/superset-frontend/plugins/plugin-chart-point-cluster-map/src/components/ScatterPlotOverlay.tsx b/superset-frontend/plugins/plugin-chart-point-cluster-map/src/components/ScatterPlotOverlay.tsx
index 5c94a69b514..b14da848e45 100644
--- a/superset-frontend/plugins/plugin-chart-point-cluster-map/src/components/ScatterPlotOverlay.tsx
+++ b/superset-frontend/plugins/plugin-chart-point-cluster-map/src/components/ScatterPlotOverlay.tsx
@@ -26,6 +26,9 @@ import luminanceFromRGB from '../utils/luminanceFromRGB';
 export const MIN_CLUSTER_RADIUS_RATIO = 1 / 6;
 export const MAX_POINT_RADIUS_RATIO = 1 / 3;
 
+export const isValidCanvasRadius = (value: number) =>
+  Number.isFinite(value) && value > 0;
+
 interface GeoJSONLocation {
   geometry: {
     coordinates: [number, number];
@@ -352,8 +355,11 @@ function ScatterPlotOverlay({
                   : String(pointMetric);
               }
 
-              if (!pointRadius) {
+              if (!isValidCanvasRadius(pointRadius)) {
                 pointRadius = defaultRadius;
+                if (pointMetric === null) {
+                  pointLabel = undefined;
+                }
               }
 
               ctx.arc(
diff --git a/superset-frontend/plugins/plugin-chart-point-cluster-map/test/ScatterPlotOverlay.test.tsx b/superset-frontend/plugins/plugin-chart-point-cluster-map/test/ScatterPlotOverlay.test.tsx
index cafa91aaeba..634fc8e208b 100644
--- a/superset-frontend/plugins/plugin-chart-point-cluster-map/test/ScatterPlotOverlay.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-point-cluster-map/test/ScatterPlotOverlay.test.tsx
@@ -18,8 +18,8 @@
  */
 
 import { render } from '@testing-library/react';
-import ScatterPlotOverlay from '../src/components/ScatterPlotOverlay';
-import {
+import ScatterPlotOverlay, {
+  isValidCanvasRadius,
   MIN_CLUSTER_RADIUS_RATIO,
   MAX_POINT_RADIUS_RATIO,
 } from '../src/components/ScatterPlotOverlay';
@@ -158,6 +158,18 @@ const MIN_VISIBLE_POINT_RADIUS =
 const MAX_VISIBLE_POINT_RADIUS =
   defaultProps.dotRadius * MAX_POINT_RADIUS_RATIO;
 
+test.each([
+  [1, true],
+  [0.1, true],
+  [0, false],
+  [-1, false],
+  [NaN, false],
+  [Infinity, false],
+  [-Infinity, false],
+])('validates canvas radius value %p', (value, expected) => {
+  expect(isValidCanvasRadius(value)).toBe(expected);
+});
+
 test('renders map with varying radius values in Pixels mode', () => {
   const locations = [
     createLocation([100, 100], { radius: 10, cluster: false }),
@@ -373,6 +385,42 @@ test('renders map with Miles mode', () => {
   }).not.toThrow();
 });
 
+test.each(['Kilometers', 'Miles'])(
+  'falls back to default radius for non-positive %s values',
+  pointRadiusUnit => {
+    const locations = [
+      createLocation([100, 50], { radius: -5, cluster: false }),
+      createLocation([200, 50], { radius: 0, cluster: false }),
+      createLocation([300, 50], { radius: 10, cluster: false }),
+    ];
+
+    render(
+      <ScatterPlotOverlay
+        {...defaultProps}
+        locations={locations}
+        pointRadiusUnit={pointRadiusUnit}
+        zoom={10}
+      />,
+    );
+    const redrawParams = triggerRedraw();
+
+    const arcCalls = redrawParams.ctx.arc.mock.calls;
+
+    arcCalls.forEach(call => {
+      expect(Number.isFinite(call[2])).toBe(true);
+      expect(call[2]).toBeGreaterThan(0);
+    });
+    expect(arcCalls[0][2]).toBe(MIN_VISIBLE_POINT_RADIUS);
+    expect(arcCalls[1][2]).toBe(MIN_VISIBLE_POINT_RADIUS);
+    expect(arcCalls[2][2]).toBeGreaterThan(MIN_VISIBLE_POINT_RADIUS);
+
+    const expectedLabel = pointRadiusUnit === 'Miles' ? '10mi' : '10km';
+    expect(redrawParams.ctx.fillText.mock.calls.map(call => call[0])).toEqual([
+      expectedLabel,
+    ]);
+  },
+);
+
 test('displays metric property labels on points', () => {
   const locations = [
     createLocation([100, 100], { radius: 50, metric: 123.456, cluster: false }),