diff --git a/src/component/brush/selector.ts b/src/component/brush/selector.ts index 50ce3446e0..d06f8c2b07 100644 --- a/src/component/brush/selector.ts +++ b/src/component/brush/selector.ts @@ -87,6 +87,51 @@ export function makeBrushCommonSelectorForSeries( const selector: Record = { lineX: getLineSelectors(0), lineY: getLineSelectors(1), + + circle: { + point: function (itemLayout, selectors, area) { + if (!itemLayout) { + return false; + } + const range = area.range as number[]; // [cx, cy, r] + const cx = range[0]; + const cy = range[1]; + const r = range[2]; + + const dx = itemLayout[0] - cx; + const dy = itemLayout[1] - cy; + + // First pass: quick bounding box rejection + // Second pass: precise Euclidean squared distance + return area.boundingRect.contain(itemLayout[0], itemLayout[1]) + && (dx * dx + dy * dy <= r * r); + }, + rect: function (itemLayout, selectors, area) { + if (!itemLayout) { + return false; + } + const range = area.range as number[]; + const cx = range[0]; + const cy = range[1]; + const r = range[2]; + + const x = itemLayout.x; + const y = itemLayout.y; + const width = itemLayout.width; + const height = itemLayout.height; + + // Find the closest point to the circle's center within the rectangle bounds + const closestX = Math.max(x, Math.min(cx, x + width)); + const closestY = Math.max(y, Math.min(cy, y + height)); + + const dx = cx - closestX; + const dy = cy - closestY; + + // Check if the distance from the closest point to the center is <= radius + return dx * dx + dy * dy <= r * r; + } + }, + rect: { point: function (itemLayout, selectors, area) { return itemLayout && area.boundingRect.contain(itemLayout[0], itemLayout[1]); diff --git a/src/component/helper/BrushController.ts b/src/component/helper/BrushController.ts index f394491862..a87e83e151 100644 --- a/src/component/helper/BrushController.ts +++ b/src/component/helper/BrushController.ts @@ -40,7 +40,7 @@ import tokens from '../../visual/tokens'; */ -export type BrushType = 'polygon' | 'rect' | 'lineX' | 'lineY'; +export type BrushType = 'polygon' | 'rect' | 'lineX' | 'lineY' | 'circle'; /** * Only for drawing (after enabledBrush). * 'line', 'rect', 'polygon' or false @@ -744,6 +744,22 @@ function getGlobalDirection2( return globalDir.join('') as keyof typeof CURSOR_MAP; } +function driftCircle( + controller: BrushController, + cover: BrushCover, + dx: number, + dy: number +): void { + const range = cover.__brushOption.range as BrushDimensionMinMax; // [cx, cy, radius] + const localDelta = toLocalDelta(controller, dx, dy); + + range[0] += localDelta[0]; + range[1] += localDelta[1]; + + updateCoverAfterCreation(controller, cover); + trigger(controller, {isEnd: false}); +} + function driftRect( rectRangeConverter: RectRangeConverter, controller: BrushController, @@ -1033,6 +1049,44 @@ const coverRenderers: Record = { lineY: getLineRenderer(1), + circle: { + createCover: function (controller, brushOption) { + const cover = new graphic.Group() as BrushCover; + + cover.add(new graphic.Circle({ + name: 'main', + style: makeStyle(brushOption), + silent: true, + draggable: true, + cursor: 'move', + drift: curry(driftCircle, controller, cover), + ondragend: curry(trigger, controller, {isEnd: true}) + })); + + return cover; + }, + getCreatingRange: function (localTrack) { + const ends = getTrackEnds(localTrack); + const cx = ends[0][0]; + const cy = ends[0][1]; + const dx = ends[1][0] - cx; + const dy = ends[1][1] - cy; + const r = mathPow(dx * dx + dy * dy, 0.5); + + // The range structure passed to selector.ts: [centerX, centerY, radius] + return [cx, cy, r]; + }, + updateCoverShape: function (controller, cover, localRange: BrushDimensionMinMax, brushOption) { + (cover.childAt(0) as graphic.Circle).setShape({ + cx: localRange[0], + cy: localRange[1], + r: localRange[2] + }); + }, + updateCommon: updateCommon, + contain: mainShapeContain + }, + rect: { createCover: function (controller, brushOption) { function returnInput(range: BrushDimensionMinMax[]): BrushDimensionMinMax[] { diff --git a/src/component/helper/BrushTargetManager.ts b/src/component/helper/BrushTargetManager.ts index ccaf86f8c3..ffda98d1d9 100644 --- a/src/component/helper/BrushTargetManager.ts +++ b/src/component/helper/BrushTargetManager.ts @@ -454,6 +454,9 @@ const coordConvert: Record = { return p; }); return {values: values, xyMinMax: xyMinMax}; + }, + circle: function (to, coordSys, rangeOrCoordRange: any) { + return { values: rangeOrCoordRange, xyMinMax: [] }; } }; @@ -514,6 +517,9 @@ const diffProcessor: Record = { return map(values, function (item, idx) { return [item[0] - scales[0] * refer[idx][0], item[1] - scales[1] * refer[idx][1]]; }); + }, + circle: function (values: any, refer: any, scales: any) { + return values; } }; diff --git a/src/component/toolbox/feature/Brush.ts b/src/component/toolbox/feature/Brush.ts index 7c4b7f2fab..61a107cc68 100644 --- a/src/component/toolbox/feature/Brush.ts +++ b/src/component/toolbox/feature/Brush.ts @@ -28,7 +28,7 @@ import ExtensionAPI from '../../../core/ExtensionAPI'; import BrushModel from '../../brush/BrushModel'; import { BrushTypeUncertain } from '../../helper/BrushController'; -const ICON_TYPES = ['rect', 'polygon', 'lineX', 'lineY', 'keep', 'clear'] as const; +const ICON_TYPES = ['rect', 'polygon', 'circle', 'lineX', 'lineY', 'keep', 'clear'] as const; type IconType = typeof ICON_TYPES[number]; @@ -136,6 +136,7 @@ class BrushFeature extends ToolboxFeature { /* eslint-disable */ rect: 'M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13', // jshint ignore:line polygon: 'M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2', // jshint ignore:line + circle: 'M35,10 A25,25 0 1,0 35,60 A25,25 0 1,0 35,10 Z', lineX: 'M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4', // jshint ignore:line lineY: 'M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4', // jshint ignore:line keep: 'M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z', // jshint ignore:line