diff --git a/common/changes/@visactor/vchart/feat-support-resize-zoom_2025-10-28-06-39.json b/common/changes/@visactor/vchart/feat-support-resize-zoom_2025-10-28-06-39.json new file mode 100644 index 0000000000..9952ec4dab --- /dev/null +++ b/common/changes/@visactor/vchart/feat-support-resize-zoom_2025-10-28-06-39.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: support resize zoom chart plugin\n\n", + "type": "none", + "packageName": "@visactor/vchart" + } + ], + "packageName": "@visactor/vchart", + "email": "lixuef1313@163.com" +} \ No newline at end of file diff --git a/packages/vchart/src/component/geo/geo-coordinate.ts b/packages/vchart/src/component/geo/geo-coordinate.ts index 1395f539eb..8d58acf03e 100644 --- a/packages/vchart/src/component/geo/geo-coordinate.ts +++ b/packages/vchart/src/component/geo/geo-coordinate.ts @@ -399,7 +399,7 @@ export class GeoCoordinate extends BaseComponent implements IGeo const result = super._compareSpec(spec, prevSpec); if (!result.reMake) { result.reMake = ['roam', 'longitudeField', 'latitudeField', 'projection', 'zoomLimit'].some(k => { - return !isEqual(prevSpec?.[k], spec[k]); + return !isEqual(prevSpec?.[k as keyof IGeoRegionSpec], spec[k as keyof IGeoRegionSpec]); }); } diff --git a/packages/vchart/src/core/vchart.ts b/packages/vchart/src/core/vchart.ts index 1177f7256e..bcd8e012e9 100644 --- a/packages/vchart/src/core/vchart.ts +++ b/packages/vchart/src/core/vchart.ts @@ -745,6 +745,7 @@ export class VChart implements IVChart { if (!this._chart || !this._compiler) { return false; } + this._chartPluginApply('onAfterInitChart', this._spec, actionSource); // compile this._option.performanceHook?.beforeCompileToVGrammar?.(this); diff --git a/packages/vchart/src/plugin/chart/index.ts b/packages/vchart/src/plugin/chart/index.ts index 85c004fc23..a0ae2a6b7b 100644 --- a/packages/vchart/src/plugin/chart/index.ts +++ b/packages/vchart/src/plugin/chart/index.ts @@ -2,3 +2,4 @@ export * from './media-query'; export * from './formatter'; export * from './register'; export * from './interface'; +export * from './resize-zoom'; diff --git a/packages/vchart/src/plugin/chart/interface.ts b/packages/vchart/src/plugin/chart/interface.ts index c4715596cf..bd40d805a5 100644 --- a/packages/vchart/src/plugin/chart/interface.ts +++ b/packages/vchart/src/plugin/chart/interface.ts @@ -18,6 +18,7 @@ export interface IChartPlugin extends IBase actionSource: VChartRenderActionSource ) => MaybePromise; onBeforeInitChart?: (service: T, chartSpec: any, actionSource: VChartRenderActionSource) => MaybePromise; + onAfterInitChart?: (service: T, chartSpec: any, actionSource: VChartRenderActionSource) => MaybePromise; } export interface IChartPluginConstructor { @@ -38,4 +39,5 @@ export interface IChartPluginService extends IBase actionSource: VChartRenderActionSource ) => MaybePromise; onBeforeInitChart?: (chartSpec: any, actionSource: VChartRenderActionSource) => MaybePromise; + onAfterInitChart?: (chartSpec: any, actionSource: VChartRenderActionSource) => MaybePromise; } diff --git a/packages/vchart/src/plugin/chart/plugin-service.ts b/packages/vchart/src/plugin/chart/plugin-service.ts index 80e1d5b720..d7375aaffc 100644 --- a/packages/vchart/src/plugin/chart/plugin-service.ts +++ b/packages/vchart/src/plugin/chart/plugin-service.ts @@ -46,6 +46,12 @@ export class ChartPluginService }); } + onAfterInitChart(chartSpec: any, actionSource: VChartRenderActionSource) { + this._plugins.forEach(plugin => { + plugin.onAfterInitChart && plugin.onAfterInitChart(this, chartSpec, actionSource); + }); + } + releaseAll(): void { super.releaseAll(); this.globalInstance = null; diff --git a/packages/vchart/src/plugin/chart/resize-zoom/index.ts b/packages/vchart/src/plugin/chart/resize-zoom/index.ts new file mode 100644 index 0000000000..ab07a0e564 --- /dev/null +++ b/packages/vchart/src/plugin/chart/resize-zoom/index.ts @@ -0,0 +1 @@ +export * from './zoom'; diff --git a/packages/vchart/src/plugin/chart/resize-zoom/zoom.ts b/packages/vchart/src/plugin/chart/resize-zoom/zoom.ts new file mode 100644 index 0000000000..97a579cca8 --- /dev/null +++ b/packages/vchart/src/plugin/chart/resize-zoom/zoom.ts @@ -0,0 +1,102 @@ +import type { IChartPlugin, IChartPluginService } from '../interface'; +import { BasePlugin } from '../../base/base-plugin'; +import { registerChartPlugin } from '../register'; +import type { IPoint } from '../../../typings'; +import { getDefaultTriggerEventByMode } from '../../../component/common/trigger/config'; + +const MIN_ZOOM = 0.1; +const MAX_ZOOM = 10; + +export class ChartResizeZoomPlugin extends BasePlugin implements IChartPlugin { + static readonly pluginType: 'chart'; + static readonly specKey = 'resizeZoom'; + static readonly type: string = 'ChartResizeZoomPlugin'; + readonly type: string = 'ChartResizeZoomPlugin'; + + protected _container?: HTMLElement; + protected _triggerEvent?: string; + protected _minZoom?: number; + protected _maxZoom?: number; + protected _zoom: number = 1; + protected _rate?: number; + + constructor() { + super(ChartResizeZoomPlugin.type); + } + + onAfterInitChart(service: IChartPluginService, chartSpec: any) { + const chart = service.globalInstance; + const spec = chart.getSpec()?.[ChartResizeZoomPlugin.specKey] ?? { enabled: false }; + if (spec.enabled !== true) { + return; + } + this._minZoom = spec.min ?? MIN_ZOOM; + this._maxZoom = spec.max ?? MAX_ZOOM; + this._rate = spec.rate ?? 0.1; + this._container = chart.getContainer(); + this._triggerEvent = getDefaultTriggerEventByMode(service.globalInstance.getChart().getOption().mode).zoom; + if (this._container && this._triggerEvent) { + this._container.addEventListener(this._triggerEvent as keyof HTMLElementEventMap, this._onWheel as EventListener); + } + } + + protected _onWheel = (e: WheelEvent) => { + e.preventDefault(); + e.stopImmediatePropagation(); + + const zoom = Math.pow(1.005, -e.deltaY * Math.pow(16, e.deltaMode) * 0.2 * this._rate); + const center = { x: e.offsetX, y: e.offsetY }; + this.zoom(zoom, center); + }; + + /** + * 缩放图表 + * @param zoom 缩放比例 + * @param pointerPos 缩放中心,即鼠标位置 + */ + zoom(zoom: number, pointerPos?: IPoint) { + const vchart = this.service.globalInstance; + if (!vchart) { + return; + } + const oldZoom = this._zoom; + let tempZoom = this._zoom * zoom; + if ((tempZoom <= this._minZoom && zoom < 1) || (tempZoom >= this._maxZoom && zoom > 1)) { + if (tempZoom <= this._minZoom && this._zoom > this._minZoom) { + tempZoom = this._minZoom; + } else if (tempZoom >= this._maxZoom && this._zoom < this._maxZoom) { + tempZoom = this._maxZoom; + } else { + return; + } + } + + if (tempZoom === oldZoom) { + return; + } + + const actualZoomRatio = tempZoom / oldZoom; + this._zoom = tempZoom; + + vchart.resize(vchart.getCurrentSize().width * this._zoom, vchart.getCurrentSize().height * this._zoom); + // 滚动容器滚动, 保持当前鼠标位置在缩放后不变 + if (pointerPos && this._container) { + const { scrollLeft, scrollTop } = this._container; + this._container.scrollLeft = scrollLeft + pointerPos.x * (actualZoomRatio - 1); + this._container.scrollTop = scrollTop + pointerPos.y * (actualZoomRatio - 1); + } + } + + release(): void { + if (this._container && this._triggerEvent) { + this._container.removeEventListener( + this._triggerEvent as keyof HTMLElementEventMap, + this._onWheel as EventListener + ); + } + } +} + +export const registerChartResizeZoomPlugin = () => { + registerChartPlugin(ChartResizeZoomPlugin); +};