diff --git a/common/changes/@visactor/vchart/4132-feature-----90------_2025-08-29-04-41.json b/common/changes/@visactor/vchart/4132-feature-----90------_2025-08-29-04-41.json new file mode 100644 index 0000000000..98c28ea067 --- /dev/null +++ b/common/changes/@visactor/vchart/4132-feature-----90------_2025-08-29-04-41.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: support vchart rotate 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/core/factory.ts b/packages/vchart/src/core/factory.ts index eb69de013e..3b5780644e 100644 --- a/packages/vchart/src/core/factory.ts +++ b/packages/vchart/src/core/factory.ts @@ -25,6 +25,7 @@ import type { IBaseTriggerOptions, ITriggerConstructor } from '../interaction/in import type { IComposedEventConstructor } from '../index-harmony-simple'; import type { ITooltipProcessorConstructor } from '../component/tooltip/processor/interface'; import type { ITooltip } from '../component'; +import type { IVChartPluginConstructor } from '../plugin/vchart'; export class Factory { private static _charts: { [key: string]: IChartConstructor } = {}; @@ -42,6 +43,7 @@ export class Factory { private static _animations: { [key: string]: (params?: any, preset?: any) => MarkAnimationSpec } = {}; private static _implements: { [key: string]: (...args: any) => void } = {}; private static _chartPlugin: { [key: string]: IChartPluginConstructor } = {}; + private static _vChartPlugin: { [key: string]: IVChartPluginConstructor } = {}; private static _componentPlugin: { [key: string]: IComponentPluginConstructor } = {}; private static _formatter: ( text: string | number | string[] | number[], @@ -117,6 +119,9 @@ export class Factory { static registerChartPlugin(key: string, plugin: IChartPluginConstructor) { Factory._chartPlugin[key] = plugin; } + static registerVChartPlugin(key: string, plugin: IVChartPluginConstructor) { + Factory._vChartPlugin[key] = plugin; + } static registerComponentPlugin(key: string, plugin: IComponentPluginConstructor) { Factory._componentPlugin[key] = plugin; } @@ -269,6 +274,10 @@ export class Factory { return Object.values(Factory._chartPlugin); } + static getVChartPlugins() { + return Object.values(Factory._vChartPlugin); + } + static getComponentPlugins() { return Object.values(Factory._componentPlugin); } diff --git a/packages/vchart/src/core/interface.ts b/packages/vchart/src/core/interface.ts index 3c1cd41c74..1e8b35a9d1 100644 --- a/packages/vchart/src/core/interface.ts +++ b/packages/vchart/src/core/interface.ts @@ -555,6 +555,12 @@ export interface IVChart { /** 获取当前容器宽高 */ getCurrentSize: () => IContainerSize; + + /** 旋转图表 需要注册插件 */ + rotate90WithTransform?: (rotateDom: HTMLElement) => void; + + /** 取消图表旋转 */ + cancelTransform?: (rotateDom: HTMLElement) => void; } export interface IGlobalConfig { diff --git a/packages/vchart/src/core/vchart.ts b/packages/vchart/src/core/vchart.ts index c21f5cede9..177ad3de7e 100644 --- a/packages/vchart/src/core/vchart.ts +++ b/packages/vchart/src/core/vchart.ts @@ -115,6 +115,8 @@ import type { IGeoCoordinate } from '../component/geo'; import { registerGesturePlugin } from '../plugin/other'; import { registerElementHighlight } from '../interaction/triggers/element-highlight'; import { registerElementSelect } from '../interaction/triggers/element-select'; +import type { IVChartPluginService } from '../plugin/vchart/interface'; +import { VChartPluginService } from '../plugin/vchart/plugin-service'; export class VChart implements IVChart { readonly id = createID(); @@ -357,6 +359,8 @@ export class VChart implements IVChart { private _isReleased: boolean; private _chartPlugin?: IChartPluginService; + private _vChartPlugin?: IVChartPluginService; + private _onResize?: () => void; constructor(spec: ISpec, options: IInitOption) { @@ -2148,6 +2152,14 @@ export class VChart implements IVChart { // 插件生命周期 this._chartPluginApply('onInit', this._spec); } + + const vChartPluginList = Factory.getVChartPlugins(); + if (vChartPluginList.length > 0) { + this._vChartPlugin = new VChartPluginService(this); + this._vChartPlugin.load(vChartPluginList.map(p => new p())); + // 插件生命周期 + this._vChartPlugin.onInit(); + } } private _chartPluginApply(funcName: keyof IChartPluginService, ...args: any[]) { diff --git a/packages/vchart/src/plugin/vchart/index.ts b/packages/vchart/src/plugin/vchart/index.ts new file mode 100644 index 0000000000..a8bca40647 --- /dev/null +++ b/packages/vchart/src/plugin/vchart/index.ts @@ -0,0 +1,2 @@ +export * from './register'; +export * from './interface'; diff --git a/packages/vchart/src/plugin/vchart/interface.ts b/packages/vchart/src/plugin/vchart/interface.ts new file mode 100644 index 0000000000..d19f131124 --- /dev/null +++ b/packages/vchart/src/plugin/vchart/interface.ts @@ -0,0 +1,19 @@ +import type { IVChart } from '../../core/interface'; +import type { IBasePlugin, IBasePluginService, MaybePromise } from '../base/interface'; + +export interface IVChartPlugin extends IBasePlugin { + specKey?: string; + onInit?: (service: T) => MaybePromise; +} + +export interface IVChartPluginConstructor { + readonly pluginType: 'vchart'; + readonly specKey?: string; + readonly type: string; + new (): IVChartPlugin; +} + +export interface IVChartPluginService extends IBasePluginService { + globalInstance: IVChart; + onInit?: () => MaybePromise; +} diff --git a/packages/vchart/src/plugin/vchart/plugin-service.ts b/packages/vchart/src/plugin/vchart/plugin-service.ts new file mode 100644 index 0000000000..3362929076 --- /dev/null +++ b/packages/vchart/src/plugin/vchart/plugin-service.ts @@ -0,0 +1,26 @@ +import type { IVChartPlugin, IVChartPluginService } from './interface'; +import type { IVChart } from '../../core'; +import { BasePluginService } from '../base/base-plugin-service'; + +export class VChartPluginService + extends BasePluginService + implements IVChartPluginService +{ + globalInstance: IVChart; + + constructor(globalInstance: IVChart) { + super(); + this.globalInstance = globalInstance; + } + + onInit() { + this._plugins.forEach(plugin => { + plugin.onInit && plugin.onInit(this); + }); + } + + releaseAll(): void { + super.releaseAll(); + this.globalInstance = null; + } +} diff --git a/packages/vchart/src/plugin/vchart/register.ts b/packages/vchart/src/plugin/vchart/register.ts new file mode 100644 index 0000000000..d6f20a3313 --- /dev/null +++ b/packages/vchart/src/plugin/vchart/register.ts @@ -0,0 +1,6 @@ +import { Factory } from '../../core/factory'; +import type { IVChartPluginConstructor } from './interface'; + +export const registerVChartPlugin = (plugin: IVChartPluginConstructor) => { + Factory.registerVChartPlugin(plugin.type, plugin); +}; diff --git a/packages/vchart/src/plugin/vchart/rotate/index.ts b/packages/vchart/src/plugin/vchart/rotate/index.ts new file mode 100644 index 0000000000..d7ea07a53f --- /dev/null +++ b/packages/vchart/src/plugin/vchart/rotate/index.ts @@ -0,0 +1 @@ +export * from './rotate'; diff --git a/packages/vchart/src/plugin/vchart/rotate/rotate.ts b/packages/vchart/src/plugin/vchart/rotate/rotate.ts new file mode 100644 index 0000000000..f604ffee72 --- /dev/null +++ b/packages/vchart/src/plugin/vchart/rotate/rotate.ts @@ -0,0 +1,158 @@ +import type { IAABBBounds, Matrix } from '@visactor/vutils'; +import { BasePlugin } from '../../base/base-plugin'; +import type { IVChartPlugin, IVChartPluginService } from '../interface'; +import { registerVChartPlugin } from '../register'; +import type { IVChart } from '../../../core/interface'; +import { + matrixAllocate, + transformPointForCanvas, + mapToCanvasPointForCanvas, + registerGlobalEventTransformer, + registerWindowEventTransformer, + vglobal +} from '@visactor/vrender-core'; + +export class RotatePlugin extends BasePlugin implements IVChartPlugin { + static readonly pluginType: 'vchart' = 'vchart'; + + static readonly specKey = 'rotate'; + + static readonly type: string = 'rotatePlugin'; + readonly type: string = 'rotatePlugin'; + + private rotateDegree: number; + private matrix: Matrix; + private vglobal_mapToCanvasPoint: any; // 保存vrender中vglobal的mapToCanvasPoint原方法 + private _vchart: IVChart; + + constructor() { + super(RotatePlugin.type); + } + + onInit(service: IVChartPluginService) { + const { globalInstance: vchart } = service; + if (!vchart) { + return; + } + this._vchart = vchart; + //将函数rotate90WithTransform绑定到table实例上,一般情况下插件不需要将api绑定到table实例上,可以直接自身实现某个api功能 + vchart.rotate90WithTransform = this.rotate90WithTransform; + vchart.cancelTransform = this.cancelTransform; + } + + rotate90WithTransform = (rotateDom: HTMLElement) => { + this.rotateDegree = 90; + const rotateCenter = + rotateDom.clientWidth < rotateDom.clientHeight + ? Math.max(rotateDom.clientWidth, rotateDom.clientHeight) / 2 + : Math.min(rotateDom.clientWidth, rotateDom.clientHeight) / 2; + const domRect = this._vchart.getContainer().getBoundingClientRect(); + const x1 = domRect.left; + const y1 = domRect.top; + const x2 = domRect.right; + const y2 = domRect.bottom; + + rotateDom.style.transform = 'rotate(90deg)'; + rotateDom.style.transformOrigin = `${rotateCenter}px ${rotateCenter}px`; + const getRect = () => { + return { + x1, + y1, + x2, + y2 + } as IAABBBounds; + }; + // 获取视口尺寸的通用方法 + const getViewportDimensions = () => { + // 浏览器环境 + if (typeof window !== 'undefined') { + return { + width: window.innerWidth || document.documentElement.clientWidth, + height: window.innerHeight || document.documentElement.clientHeight + }; + } + // 如果有 vglobal 上的方法可以使用 + if (vglobal && 'getViewportSize' in vglobal && vglobal.getViewportSize) { + // @ts-ignore + return vglobal.getViewportSize(); + } + // 默认使用容器的尺寸 + return rotateDom.getBoundingClientRect(); + }; + + const getMatrix = () => { + const viewPortWidth = getViewportDimensions().width; //获取整个视口的尺寸 + const domRect = this._vchart.getContainer().getBoundingClientRect(); //TODO 这个地方应该获取窗口的宽高 最好能从vglobal上直接获取 + const x1 = domRect.top; + const y1 = viewPortWidth - domRect.right; + + const matrix = matrixAllocate.allocate(1, 0, 0, 1, 0, 0); + matrix.translate(x1, y1); + const centerX = rotateCenter - x1; + const centerY = rotateCenter - y1; + matrix.translate(centerX, centerY); + matrix.rotate(Math.PI / 2); + matrix.translate(-centerX, -centerY); + this.matrix = matrix; + return matrix; + }; + registerGlobalEventTransformer(vglobal, this._vchart.getContainer(), getMatrix, getRect, transformPointForCanvas); + registerWindowEventTransformer( + this._vchart.getStage().window, + this._vchart.getContainer(), + getMatrix, + getRect, + transformPointForCanvas + ); + this.vglobal_mapToCanvasPoint = vglobal.mapToCanvasPoint; + vglobal.mapToCanvasPoint = mapToCanvasPointForCanvas; + //transformPointForCanvas和mapToCanvasPointForCanvas时相对应的 + //具体逻辑在 VRender/packages/vrender-core/src/common/event-transformer.ts中 + // 可以自定义这两个函数 来修改事件属性,transformPointForCanvas中将坐标转换后存放了_canvasX _canvasY,mapToCanvasPointForCanvas中加以利用 + // 在VTable的touch文件中,利用到了_canvasX _canvasY 所以如果自定义上面两个函数也需提供_canvasX _canvasY + }; + cancelTransform = (rotateDom: HTMLElement) => { + this.rotateDegree = 0; + rotateDom.style.transform = 'none'; + rotateDom.style.transformOrigin = 'none'; + const domRect = this._vchart.getContainer().getBoundingClientRect(); + const x1 = domRect.left; + const y1 = domRect.top; + const x2 = domRect.right; + const y2 = domRect.bottom; + + const getRect = () => { + return { + x1, + y1, + x2, + y2 + } as IAABBBounds; + }; + const getMatrix = () => { + const matrix = matrixAllocate.allocate(1, 0, 0, 1, 0, 0); + matrix.translate(x1, y1); + return matrix; + }; + registerGlobalEventTransformer(vglobal, this._vchart.getContainer(), getMatrix, getRect, transformPointForCanvas); + registerWindowEventTransformer( + this._vchart.getStage().window, + this._vchart.getContainer(), + getMatrix, + getRect, + transformPointForCanvas + ); + vglobal.mapToCanvasPoint = this.vglobal_mapToCanvasPoint; + }; + + release() { + this._vchart = null; + this.vglobal_mapToCanvasPoint = null; + this.matrix = null; + super.release(); + } +} + +export const registerRotatePlugin = () => { + registerVChartPlugin(RotatePlugin); +};