mirror of
https://github.com/apache/superset.git
synced 2026-06-07 16:49:17 +00:00
chore(lint): convert class components to function components
Convert all remaining React class components to function components using hooks (useState, useCallback, useEffect, useRef, useMemo) to satisfy the react-prefer-function-component ESLint rule. Key changes: - Converted components in dashboard, explore, SqlLab, and Chart areas - Updated associated test files with proper typing - Fixed JSX.Element return types for components used as JSX - Added explicit ControlHeader props where needed - Fixed shouldFocus callback signature in WithPopoverMenu usage Notable exceptions (not converted): - ErrorBoundary (uses componentDidCatch) - DragDroppable (react-dnd requires class instances) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,8 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/* eslint-disable react/require-default-props */
|
||||
import { PureComponent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { CanvasOverlay } from 'react-map-gl';
|
||||
import { kmToPixels, MILES_PER_KM } from './utils/geo';
|
||||
import roundDecimal from './utils/roundDecimal';
|
||||
@@ -61,16 +60,10 @@ interface ScatterPlotGlowOverlayProps {
|
||||
isDragging?: boolean;
|
||||
}
|
||||
|
||||
const defaultProps: Partial<ScatterPlotGlowOverlayProps> = {
|
||||
// Same as browser default.
|
||||
compositeOperation: 'source-over',
|
||||
dotRadius: 4,
|
||||
lngLatAccessor: (location: GeoJSONLocation) => [
|
||||
location.geometry.coordinates[0],
|
||||
location.geometry.coordinates[1],
|
||||
],
|
||||
renderWhileDragging: true,
|
||||
};
|
||||
const defaultLngLatAccessor = (location: GeoJSONLocation): [number, number] => [
|
||||
location.geometry.coordinates[0],
|
||||
location.geometry.coordinates[1],
|
||||
];
|
||||
|
||||
const computeClusterLabel = (
|
||||
properties: Record<string, number | string | boolean | null | undefined>,
|
||||
@@ -101,65 +94,293 @@ const computeClusterLabel = (
|
||||
return count;
|
||||
};
|
||||
|
||||
class ScatterPlotGlowOverlay extends PureComponent<ScatterPlotGlowOverlayProps> {
|
||||
static defaultProps = defaultProps;
|
||||
function ScatterPlotGlowOverlay({
|
||||
aggregation,
|
||||
compositeOperation = 'source-over',
|
||||
dotRadius = 4,
|
||||
globalOpacity,
|
||||
lngLatAccessor = defaultLngLatAccessor,
|
||||
locations,
|
||||
pointRadiusUnit,
|
||||
renderWhileDragging = true,
|
||||
rgb,
|
||||
zoom,
|
||||
}: ScatterPlotGlowOverlayProps) {
|
||||
const drawText = useCallback(
|
||||
(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
pixel: [number, number],
|
||||
options: DrawTextOptions = {},
|
||||
) => {
|
||||
const IS_DARK_THRESHOLD = 110;
|
||||
const {
|
||||
fontHeight = 0,
|
||||
label = '',
|
||||
radius = 0,
|
||||
rgb: rgbOption = [0, 0, 0],
|
||||
shadow = false,
|
||||
} = options;
|
||||
const maxWidth = radius * 1.8;
|
||||
const luminance = luminanceFromRGB(
|
||||
rgbOption[1] as number,
|
||||
rgbOption[2] as number,
|
||||
rgbOption[3] as number,
|
||||
);
|
||||
|
||||
constructor(props: ScatterPlotGlowOverlayProps) {
|
||||
super(props);
|
||||
this.redraw = this.redraw.bind(this);
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.fillStyle = luminance <= IS_DARK_THRESHOLD ? 'white' : 'black';
|
||||
ctx.font = `${fontHeight}px sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
if (shadow) {
|
||||
ctx.shadowBlur = 15;
|
||||
ctx.shadowColor = luminance <= IS_DARK_THRESHOLD ? 'black' : '';
|
||||
}
|
||||
|
||||
drawText(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
pixel: [number, number],
|
||||
options: DrawTextOptions = {},
|
||||
) {
|
||||
const IS_DARK_THRESHOLD = 110;
|
||||
const {
|
||||
fontHeight = 0,
|
||||
label = '',
|
||||
radius = 0,
|
||||
rgb = [0, 0, 0],
|
||||
shadow = false,
|
||||
} = options;
|
||||
const maxWidth = radius * 1.8;
|
||||
const luminance = luminanceFromRGB(
|
||||
rgb[1] as number,
|
||||
rgb[2] as number,
|
||||
rgb[3] as number,
|
||||
);
|
||||
const textWidth = ctx.measureText(String(label)).width;
|
||||
if (textWidth > maxWidth) {
|
||||
const scale = fontHeight / textWidth;
|
||||
ctx.font = `${scale * maxWidth}px sans-serif`;
|
||||
}
|
||||
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.fillStyle = luminance <= IS_DARK_THRESHOLD ? 'white' : 'black';
|
||||
ctx.font = `${fontHeight}px sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
if (shadow) {
|
||||
ctx.shadowBlur = 15;
|
||||
ctx.shadowColor = luminance <= IS_DARK_THRESHOLD ? 'black' : '';
|
||||
}
|
||||
ctx.fillText(String(label), pixel[0], pixel[1]);
|
||||
ctx.globalCompositeOperation = (compositeOperation ??
|
||||
'source-over') as GlobalCompositeOperation;
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.shadowColor = '';
|
||||
},
|
||||
[compositeOperation],
|
||||
);
|
||||
|
||||
const textWidth = ctx.measureText(String(label)).width;
|
||||
if (textWidth > maxWidth) {
|
||||
const scale = fontHeight / textWidth;
|
||||
ctx.font = `${scale * maxWidth}px sans-serif`;
|
||||
}
|
||||
const redraw = useCallback(
|
||||
({ width, height, ctx, isDragging, project }: RedrawParams) => {
|
||||
const radius = dotRadius ?? 4;
|
||||
const clusterLabelMap: (number | string)[] = [];
|
||||
|
||||
const { compositeOperation } = this.props;
|
||||
locations.forEach((location, i) => {
|
||||
if (location.properties.cluster) {
|
||||
clusterLabelMap[i] = computeClusterLabel(
|
||||
location.properties,
|
||||
aggregation,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.fillText(String(label), pixel[0], pixel[1]);
|
||||
ctx.globalCompositeOperation = (compositeOperation ??
|
||||
'source-over') as GlobalCompositeOperation;
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.shadowColor = '';
|
||||
}
|
||||
const filteredLabels = clusterLabelMap.filter(
|
||||
v => !Number.isNaN(v),
|
||||
) as number[];
|
||||
// Guard against empty array or zero max to prevent NaN from division
|
||||
const maxLabel =
|
||||
filteredLabels.length > 0 ? Math.max(...filteredLabels) : 1;
|
||||
const safeMaxLabel = maxLabel > 0 ? maxLabel : 1;
|
||||
|
||||
// Modified: https://github.com/uber/react-map-gl/blob/master/overlays/scatterplot.react.js
|
||||
redraw({ width, height, ctx, isDragging, project }: RedrawParams) {
|
||||
const {
|
||||
// Calculate min/max radius values for Pixels mode scaling
|
||||
let minRadiusValue = Infinity;
|
||||
let maxRadiusValue = -Infinity;
|
||||
if (pointRadiusUnit === 'Pixels') {
|
||||
locations.forEach(location => {
|
||||
// Accept both null and undefined as "no value" and coerce potential numeric strings
|
||||
if (
|
||||
!location.properties.cluster &&
|
||||
location.properties.radius != null
|
||||
) {
|
||||
const radiusValueRaw = location.properties.radius;
|
||||
const radiusValue = Number(radiusValueRaw);
|
||||
if (Number.isFinite(radiusValue)) {
|
||||
minRadiusValue = Math.min(minRadiusValue, radiusValue);
|
||||
maxRadiusValue = Math.max(maxRadiusValue, radiusValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.globalCompositeOperation = (compositeOperation ??
|
||||
'source-over') as GlobalCompositeOperation;
|
||||
|
||||
if ((renderWhileDragging || !isDragging) && locations) {
|
||||
locations.forEach((location: GeoJSONLocation, i: number) => {
|
||||
const pixel = project(lngLatAccessor(location)) as [number, number];
|
||||
const pixelRounded: [number, number] = [
|
||||
roundDecimal(pixel[0], 1),
|
||||
roundDecimal(pixel[1], 1),
|
||||
];
|
||||
|
||||
if (
|
||||
pixelRounded[0] + radius >= 0 &&
|
||||
pixelRounded[0] - radius < width &&
|
||||
pixelRounded[1] + radius >= 0 &&
|
||||
pixelRounded[1] - radius < height
|
||||
) {
|
||||
ctx.beginPath();
|
||||
if (location.properties.cluster) {
|
||||
const clusterLabel = clusterLabelMap[i];
|
||||
// Validate clusterLabel is a finite number before using it for radius calculation
|
||||
const numericLabel = Number(clusterLabel);
|
||||
const safeNumericLabel = Number.isFinite(numericLabel)
|
||||
? numericLabel
|
||||
: 0;
|
||||
const scaledRadius = roundDecimal(
|
||||
(safeNumericLabel / safeMaxLabel) ** 0.5 * radius,
|
||||
1,
|
||||
);
|
||||
const fontHeight = roundDecimal(scaledRadius * 0.5, 1);
|
||||
const [x, y] = pixelRounded;
|
||||
const gradient = ctx.createRadialGradient(
|
||||
x,
|
||||
y,
|
||||
scaledRadius,
|
||||
x,
|
||||
y,
|
||||
0,
|
||||
);
|
||||
|
||||
gradient.addColorStop(
|
||||
1,
|
||||
`rgba(${rgb![1]}, ${rgb![2]}, ${rgb![3]}, ${0.8 * (globalOpacity ?? 1)})`,
|
||||
);
|
||||
gradient.addColorStop(
|
||||
0,
|
||||
`rgba(${rgb![1]}, ${rgb![2]}, ${rgb![3]}, 0)`,
|
||||
);
|
||||
ctx.arc(
|
||||
pixelRounded[0],
|
||||
pixelRounded[1],
|
||||
scaledRadius,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fill();
|
||||
|
||||
if (Number.isFinite(safeNumericLabel)) {
|
||||
let label: string | number = clusterLabel;
|
||||
if (safeNumericLabel >= 10000) {
|
||||
label = `${Math.round(safeNumericLabel / 1000)}k`;
|
||||
} else if (safeNumericLabel >= 1000) {
|
||||
label = `${Math.round(safeNumericLabel / 100) / 10}k`;
|
||||
}
|
||||
drawText(ctx, pixelRounded, {
|
||||
fontHeight,
|
||||
label,
|
||||
radius: scaledRadius,
|
||||
rgb,
|
||||
shadow: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const defaultRadius = radius / 6;
|
||||
const rawRadius = location.properties.radius;
|
||||
const radiusProperty =
|
||||
typeof rawRadius === 'number' ? rawRadius : null;
|
||||
const pointMetric = location.properties.metric ?? null;
|
||||
let pointRadius: number = radiusProperty ?? defaultRadius;
|
||||
let pointLabel: string | number | undefined;
|
||||
|
||||
if (radiusProperty != null) {
|
||||
const pointLatitude = lngLatAccessor(location)[1];
|
||||
if (pointRadiusUnit === 'Kilometers') {
|
||||
pointLabel = `${roundDecimal(pointRadius, 2)}km`;
|
||||
pointRadius = kmToPixels(
|
||||
pointRadius,
|
||||
pointLatitude,
|
||||
zoom ?? 0,
|
||||
);
|
||||
} else if (pointRadiusUnit === 'Miles') {
|
||||
pointLabel = `${roundDecimal(pointRadius, 2)}mi`;
|
||||
pointRadius = kmToPixels(
|
||||
pointRadius * MILES_PER_KM,
|
||||
pointLatitude,
|
||||
zoom ?? 0,
|
||||
);
|
||||
} else if (pointRadiusUnit === 'Pixels') {
|
||||
// Scale pixel values to a reasonable range (radius/6 to radius/3)
|
||||
// This ensures points are visible and proportional to their values
|
||||
const MIN_POINT_RADIUS = radius / 6;
|
||||
const MAX_POINT_RADIUS = radius / 3;
|
||||
|
||||
if (
|
||||
Number.isFinite(minRadiusValue) &&
|
||||
Number.isFinite(maxRadiusValue) &&
|
||||
maxRadiusValue > minRadiusValue
|
||||
) {
|
||||
// Normalize the value to 0-1 range, then scale to pixel range
|
||||
const numericPointRadius = Number(pointRadius);
|
||||
if (!Number.isFinite(numericPointRadius)) {
|
||||
// fallback to minimum visible size when the value is not a finite number
|
||||
pointRadius = MIN_POINT_RADIUS;
|
||||
} else {
|
||||
const normalizedValueRaw =
|
||||
(numericPointRadius - minRadiusValue) /
|
||||
(maxRadiusValue - minRadiusValue);
|
||||
const normalizedValue = Math.max(
|
||||
0,
|
||||
Math.min(1, normalizedValueRaw),
|
||||
);
|
||||
pointRadius =
|
||||
MIN_POINT_RADIUS +
|
||||
normalizedValue * (MAX_POINT_RADIUS - MIN_POINT_RADIUS);
|
||||
}
|
||||
pointLabel = `${roundDecimal(radiusProperty, 2)}`;
|
||||
} else if (
|
||||
Number.isFinite(minRadiusValue) &&
|
||||
minRadiusValue === maxRadiusValue
|
||||
) {
|
||||
// All values are the same, use a fixed medium size
|
||||
pointRadius = (MIN_POINT_RADIUS + MAX_POINT_RADIUS) / 2;
|
||||
pointLabel = `${roundDecimal(radiusProperty, 2)}`;
|
||||
} else {
|
||||
// Use raw pixel values if they're already in a reasonable range
|
||||
pointRadius = Math.max(
|
||||
MIN_POINT_RADIUS,
|
||||
Math.min(pointRadius, MAX_POINT_RADIUS),
|
||||
);
|
||||
pointLabel = `${roundDecimal(radiusProperty, 2)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pointMetric !== null) {
|
||||
const numericMetric = parseFloat(String(pointMetric));
|
||||
pointLabel = Number.isFinite(numericMetric)
|
||||
? roundDecimal(numericMetric, 2)
|
||||
: String(pointMetric);
|
||||
}
|
||||
|
||||
// Fall back to default points if pointRadius wasn't a numerical column
|
||||
if (!pointRadius) {
|
||||
pointRadius = defaultRadius;
|
||||
}
|
||||
|
||||
ctx.arc(
|
||||
pixelRounded[0],
|
||||
pixelRounded[1],
|
||||
roundDecimal(pointRadius, 1),
|
||||
0,
|
||||
Math.PI * 2,
|
||||
);
|
||||
ctx.fillStyle = `rgba(${rgb![1]}, ${rgb![2]}, ${rgb![3]}, ${globalOpacity})`;
|
||||
ctx.fill();
|
||||
|
||||
if (pointLabel !== undefined) {
|
||||
drawText(ctx, pixelRounded, {
|
||||
fontHeight: roundDecimal(pointRadius, 1),
|
||||
label: pointLabel,
|
||||
radius: pointRadius,
|
||||
rgb,
|
||||
shadow: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
aggregation,
|
||||
compositeOperation,
|
||||
dotRadius,
|
||||
drawText,
|
||||
globalOpacity,
|
||||
lngLatAccessor,
|
||||
locations,
|
||||
@@ -167,234 +388,10 @@ class ScatterPlotGlowOverlay extends PureComponent<ScatterPlotGlowOverlayProps>
|
||||
renderWhileDragging,
|
||||
rgb,
|
||||
zoom,
|
||||
} = this.props;
|
||||
],
|
||||
);
|
||||
|
||||
const radius = dotRadius ?? 4;
|
||||
const clusterLabelMap: (number | string)[] = [];
|
||||
|
||||
locations.forEach((location, i) => {
|
||||
if (location.properties.cluster) {
|
||||
clusterLabelMap[i] = computeClusterLabel(
|
||||
location.properties,
|
||||
aggregation,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const filteredLabels = clusterLabelMap.filter(
|
||||
v => !Number.isNaN(v),
|
||||
) as number[];
|
||||
// Guard against empty array or zero max to prevent NaN from division
|
||||
const maxLabel =
|
||||
filteredLabels.length > 0 ? Math.max(...filteredLabels) : 1;
|
||||
const safeMaxLabel = maxLabel > 0 ? maxLabel : 1;
|
||||
|
||||
// Calculate min/max radius values for Pixels mode scaling
|
||||
let minRadiusValue = Infinity;
|
||||
let maxRadiusValue = -Infinity;
|
||||
if (pointRadiusUnit === 'Pixels') {
|
||||
locations.forEach(location => {
|
||||
// Accept both null and undefined as "no value" and coerce potential numeric strings
|
||||
if (
|
||||
!location.properties.cluster &&
|
||||
location.properties.radius != null
|
||||
) {
|
||||
const radiusValueRaw = location.properties.radius;
|
||||
const radiusValue = Number(radiusValueRaw);
|
||||
if (Number.isFinite(radiusValue)) {
|
||||
minRadiusValue = Math.min(minRadiusValue, radiusValue);
|
||||
maxRadiusValue = Math.max(maxRadiusValue, radiusValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.globalCompositeOperation = (compositeOperation ??
|
||||
'source-over') as GlobalCompositeOperation;
|
||||
|
||||
if ((renderWhileDragging || !isDragging) && locations) {
|
||||
locations.forEach(function _forEach(
|
||||
this: ScatterPlotGlowOverlay,
|
||||
location: GeoJSONLocation,
|
||||
i: number,
|
||||
) {
|
||||
const pixel = project(lngLatAccessor!(location)) as [number, number];
|
||||
const pixelRounded: [number, number] = [
|
||||
roundDecimal(pixel[0], 1),
|
||||
roundDecimal(pixel[1], 1),
|
||||
];
|
||||
|
||||
if (
|
||||
pixelRounded[0] + radius >= 0 &&
|
||||
pixelRounded[0] - radius < width &&
|
||||
pixelRounded[1] + radius >= 0 &&
|
||||
pixelRounded[1] - radius < height
|
||||
) {
|
||||
ctx.beginPath();
|
||||
if (location.properties.cluster) {
|
||||
const clusterLabel = clusterLabelMap[i];
|
||||
// Validate clusterLabel is a finite number before using it for radius calculation
|
||||
const numericLabel = Number(clusterLabel);
|
||||
const safeNumericLabel = Number.isFinite(numericLabel)
|
||||
? numericLabel
|
||||
: 0;
|
||||
const scaledRadius = roundDecimal(
|
||||
(safeNumericLabel / safeMaxLabel) ** 0.5 * radius,
|
||||
1,
|
||||
);
|
||||
const fontHeight = roundDecimal(scaledRadius * 0.5, 1);
|
||||
const [x, y] = pixelRounded;
|
||||
const gradient = ctx.createRadialGradient(
|
||||
x,
|
||||
y,
|
||||
scaledRadius,
|
||||
x,
|
||||
y,
|
||||
0,
|
||||
);
|
||||
|
||||
gradient.addColorStop(
|
||||
1,
|
||||
`rgba(${rgb![1]}, ${rgb![2]}, ${rgb![3]}, ${0.8 * (globalOpacity ?? 1)})`,
|
||||
);
|
||||
gradient.addColorStop(
|
||||
0,
|
||||
`rgba(${rgb![1]}, ${rgb![2]}, ${rgb![3]}, 0)`,
|
||||
);
|
||||
ctx.arc(
|
||||
pixelRounded[0],
|
||||
pixelRounded[1],
|
||||
scaledRadius,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fill();
|
||||
|
||||
if (Number.isFinite(safeNumericLabel)) {
|
||||
let label: string | number = clusterLabel;
|
||||
if (safeNumericLabel >= 10000) {
|
||||
label = `${Math.round(safeNumericLabel / 1000)}k`;
|
||||
} else if (safeNumericLabel >= 1000) {
|
||||
label = `${Math.round(safeNumericLabel / 100) / 10}k`;
|
||||
}
|
||||
this.drawText(ctx, pixelRounded, {
|
||||
fontHeight,
|
||||
label,
|
||||
radius: scaledRadius,
|
||||
rgb,
|
||||
shadow: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const defaultRadius = radius / 6;
|
||||
const rawRadius = location.properties.radius;
|
||||
const radiusProperty =
|
||||
typeof rawRadius === 'number' ? rawRadius : null;
|
||||
const pointMetric = location.properties.metric ?? null;
|
||||
let pointRadius: number = radiusProperty ?? defaultRadius;
|
||||
let pointLabel: string | number | undefined;
|
||||
|
||||
if (radiusProperty != null) {
|
||||
const pointLatitude = lngLatAccessor!(location)[1];
|
||||
if (pointRadiusUnit === 'Kilometers') {
|
||||
pointLabel = `${roundDecimal(pointRadius, 2)}km`;
|
||||
pointRadius = kmToPixels(pointRadius, pointLatitude, zoom ?? 0);
|
||||
} else if (pointRadiusUnit === 'Miles') {
|
||||
pointLabel = `${roundDecimal(pointRadius, 2)}mi`;
|
||||
pointRadius = kmToPixels(
|
||||
pointRadius * MILES_PER_KM,
|
||||
pointLatitude,
|
||||
zoom ?? 0,
|
||||
);
|
||||
} else if (pointRadiusUnit === 'Pixels') {
|
||||
// Scale pixel values to a reasonable range (radius/6 to radius/3)
|
||||
// This ensures points are visible and proportional to their values
|
||||
const MIN_POINT_RADIUS = radius / 6;
|
||||
const MAX_POINT_RADIUS = radius / 3;
|
||||
|
||||
if (
|
||||
Number.isFinite(minRadiusValue) &&
|
||||
Number.isFinite(maxRadiusValue) &&
|
||||
maxRadiusValue > minRadiusValue
|
||||
) {
|
||||
// Normalize the value to 0-1 range, then scale to pixel range
|
||||
const numericPointRadius = Number(pointRadius);
|
||||
if (!Number.isFinite(numericPointRadius)) {
|
||||
// fallback to minimum visible size when the value is not a finite number
|
||||
pointRadius = MIN_POINT_RADIUS;
|
||||
} else {
|
||||
const normalizedValueRaw =
|
||||
(numericPointRadius - minRadiusValue) /
|
||||
(maxRadiusValue - minRadiusValue);
|
||||
const normalizedValue = Math.max(
|
||||
0,
|
||||
Math.min(1, normalizedValueRaw),
|
||||
);
|
||||
pointRadius =
|
||||
MIN_POINT_RADIUS +
|
||||
normalizedValue * (MAX_POINT_RADIUS - MIN_POINT_RADIUS);
|
||||
}
|
||||
pointLabel = `${roundDecimal(radiusProperty, 2)}`;
|
||||
} else if (
|
||||
Number.isFinite(minRadiusValue) &&
|
||||
minRadiusValue === maxRadiusValue
|
||||
) {
|
||||
// All values are the same, use a fixed medium size
|
||||
pointRadius = (MIN_POINT_RADIUS + MAX_POINT_RADIUS) / 2;
|
||||
pointLabel = `${roundDecimal(radiusProperty, 2)}`;
|
||||
} else {
|
||||
// Use raw pixel values if they're already in a reasonable range
|
||||
pointRadius = Math.max(
|
||||
MIN_POINT_RADIUS,
|
||||
Math.min(pointRadius, MAX_POINT_RADIUS),
|
||||
);
|
||||
pointLabel = `${roundDecimal(radiusProperty, 2)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pointMetric !== null) {
|
||||
const numericMetric = parseFloat(String(pointMetric));
|
||||
pointLabel = Number.isFinite(numericMetric)
|
||||
? roundDecimal(numericMetric, 2)
|
||||
: String(pointMetric);
|
||||
}
|
||||
|
||||
// Fall back to default points if pointRadius wasn't a numerical column
|
||||
if (!pointRadius) {
|
||||
pointRadius = defaultRadius;
|
||||
}
|
||||
|
||||
ctx.arc(
|
||||
pixelRounded[0],
|
||||
pixelRounded[1],
|
||||
roundDecimal(pointRadius, 1),
|
||||
0,
|
||||
Math.PI * 2,
|
||||
);
|
||||
ctx.fillStyle = `rgba(${rgb![1]}, ${rgb![2]}, ${rgb![3]}, ${globalOpacity})`;
|
||||
ctx.fill();
|
||||
|
||||
if (pointLabel !== undefined) {
|
||||
this.drawText(ctx, pixelRounded, {
|
||||
fontHeight: roundDecimal(pointRadius, 1),
|
||||
label: pointLabel,
|
||||
radius: pointRadius,
|
||||
rgb,
|
||||
shadow: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <CanvasOverlay redraw={this.redraw} />;
|
||||
}
|
||||
return <CanvasOverlay redraw={redraw} />;
|
||||
}
|
||||
|
||||
export default ScatterPlotGlowOverlay;
|
||||
export default memo(ScatterPlotGlowOverlay);
|
||||
|
||||
Reference in New Issue
Block a user