Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support auto bounds mode in vchart-graphic #131

Merged
merged 2 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class CharacterChart<T extends IChartGraphicAttribute>
panel: {},
ticker: this._ticker,
zIndex: this._config.zIndex ?? 0,
vchartBoundsMode: this._config.options.initOption?.vchartBoundsMode ?? 'auto',
chartInitOptions: mergeChartOption(
{
performanceHook: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export class VChartRender extends DefaultCanvasRectRender implements IGraphicRen
// @ts-ignore
vchartStage._editor_needRender = true;
const matrix = chart.globalTransMatrix.clone();
// auto 模式下,需要将vchart.stage的viewBoxTransform 设置到包含偏移量的位置
matrix.translate(chart.vchartAutoTranslate.x, chart.vchartAutoTranslate.y);
const stageMatrix = chart.stage.window.getViewBoxTransform().clone();
stageMatrix.multiply(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f);
vchartStage.window.setViewBoxTransform(
Expand Down
167 changes: 160 additions & 7 deletions packages/vstory-core/src/character/chart/graphic/vchart-graphic.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { IInitOption, ISpec, IVChart } from '@visactor/vchart';
import type { IInitOption, IVChart } from '@visactor/vchart';
import VChart from '@visactor/vchart';
import type { GraphicType, IRectGraphicAttribute, ITicker } from '@visactor/vrender-core';
import { genNumberType, IGraphicAttribute, Rect } from '@visactor/vrender-core';
import type { IBoundsLike } from '@visactor/vutils';
import { pointInAABB } from '@visactor/vutils';
import type { GraphicType, IGraphic, IGroup, IRectGraphicAttribute, ITicker } from '@visactor/vrender-core';
import { genNumberType, parsePadding, Rect } from '@visactor/vrender-core';
import type { IAABBBounds, IBoundsLike } from '@visactor/vutils';
import { Bounds, pointInAABB, transformBoundsWithMatrix } from '@visactor/vutils';
import { mergeChartOption } from '../../../utils/chart';
import { isBoundsLikeEqual } from '../../../utils/equal';

export interface IChartGraphicAttribute {
renderCanvas: HTMLCanvasElement;
Expand All @@ -31,6 +32,7 @@ export interface IChartGraphicAttribute {
anchor?: [number, number];
zIndex?: number;
panel?: Partial<IRectGraphicAttribute>;
vchartBoundsMode?: 'clip' | 'auto';
}

export const CHART_NUMBER_TYPE = genNumberType();
Expand All @@ -43,8 +45,38 @@ export class VChartGraphic extends Rect {
get vchart() {
return this._vchart;
}
// vchart 的实际绘图绘制位置
// 首先 vchart.stage 会根据 stage.window.viewBoxTransform 变换第一次,这一次变化包括了
// 1. 全局stage的缩放;2. vchart-graphic 的位置定位;3. auto 模式下的自动偏移(这个等同于位置偏移)
// 然后 vchart.stage.defaultLayer 会根据偏移量,将图表绘制内容再偏移回来
// 来回2次偏移的目的是,让 vchart 内容超出原是viewBox的部分,可以正常被viewBox包含并绘制
protected _vchartAutoTranslate: { x: number; y: number } = { x: 0, y: 0 };
get vchartAutoTranslate() {
return this._vchartAutoTranslate;
}

// 实际渲染图表内容的 bounds
// 只在 auto 模式下生效
protected _displayBounds: Bounds;

protected _boundsChangeTag: boolean = true;
doUpdateAABBBounds(full?: boolean): IAABBBounds {
if (!this._displayBounds) {
return super.doUpdateAABBBounds(full);
}
this.updateAABBBoundsStamp++;
const graphicTheme = this.getGraphicTheme();
const bounds = this._displayBounds.clone();
transformBoundsWithMatrix(bounds, bounds, this.transMatrix);
// @ts-ignore
const { boundsPadding = graphicTheme.boundsPadding } = this.attribute;
const paddingArray = parsePadding(boundsPadding);
if (paddingArray) {
bounds.expand(paddingArray as number);
}
this.clearUpdateBoundTag();
this._AABBBounds.copy(bounds);
return bounds;
}

constructor(params: IChartGraphicAttribute) {
const { panel, zIndex } = params;
Expand All @@ -62,8 +94,11 @@ export class VChartGraphic extends Rect {
disableDirtyBounds,
ticker,
chartInitOptions,
viewBox
viewBox,
vchartBoundsMode
} = params;
this.attribute.viewBox = viewBox;
this.attribute.vchartBoundsMode = vchartBoundsMode;
this._vchart = new VChart(
spec,
mergeChartOption(
Expand Down Expand Up @@ -111,6 +146,11 @@ export class VChartGraphic extends Rect {
// stage.pauseTriggerEvent();
}
stage.resumeRender();

if (vchartBoundsMode === 'auto') {
// auto 模式下,需要手动更新一下
this.updateVChartGraphicViewBoxInAuto();
}
}

/**
Expand All @@ -128,4 +168,117 @@ export class VChartGraphic extends Rect {
this._vchart && this._vchart.release();
super.release();
}

setAttribute(key: string, value: any) {
if (key === 'viewBox') {
super.setAttribute('x', value.x1);
super.setAttribute('y', value.y1);
this.updateVChartGraphicViewBox(value);
} else {
super.setAttribute(key, value);
}
}
setAttributes(attrs: IChartGraphicAttribute) {
const lastedViewBox = this.attribute.viewBox;
super.setAttributes(attrs);
if (attrs.viewBox) {
this.attribute.viewBox = lastedViewBox;
this.updateVChartGraphicViewBox(attrs.viewBox);
}
}

private _getVChartGroupActualBounds(bounds: Bounds, _group: IGraphic) {
if (_group.type !== 'group') {
bounds.union(_group.globalAABBBounds);
return;
}
// 以下是 group 的情况
const group = _group as IGroup;
if (group.childrenCount === 0) {
return;
}
if (group.name?.startsWith('seriesGroup_')) {
return bounds.union(group.globalAABBBounds);
}
if (group.attribute.clip === true && (group.attribute.width || group.attribute.height)) {
bounds.union(group.globalAABBBounds);
return;
}
group.forEachChildren(_child => {
this._getVChartGroupActualBounds(bounds, _child as IGraphic);
});
}

/**
* 获取 VChart 图形的实际边界。
* 该方法通过遍历 VChart stage的默认图层中的所有子组,计算并返回它们的边界框。
*
* @returns {Bounds} 返回包含所有子组边界的 Bounds 对象。
*/
getVChartActualBounds() {
const stage = this._vchart.getStage();
// const layer = stage.defaultLayer;
const root = stage.defaultLayer.getChildByName('root') as IGroup;
const bounds = new Bounds();
root.forEachChildren((child: IGroup) => {
this._getVChartGroupActualBounds(bounds, child);
});

bounds.translate(-(stage.defaultLayer.attribute.x ?? 0), -(stage.defaultLayer.attribute.y ?? 0));
return bounds;
}

updateVChartGraphicViewBox(bounds: IBoundsLike) {
if (this.attribute.viewBox && isBoundsLikeEqual(this.attribute.viewBox, bounds)) {
// 没有变化,不需要更新
return;
}
// 先更新 viewBox
this.attribute.viewBox = bounds;
// 不是auto模式
if (this.attribute.vchartBoundsMode !== 'auto') {
// 直接更新
this._vchart.updateViewBox(bounds);
return;
}
this.updateVChartGraphicViewBoxInAuto();
}

updateVChartGraphicViewBoxInAuto() {
// 1. 得到当前设置 viewBox 的实际渲染bounds
const rect = this._vchart.getChart().getCanvasRect();
const viewBoxSize = {
width: this.attribute.viewBox.x2 - this.attribute.viewBox.x1,
height: this.attribute.viewBox.y2 - this.attribute.viewBox.y1
};
// 当尺寸变化时,进行一次 resize
if (rect.width !== viewBoxSize.width || rect.height !== viewBoxSize.height) {
// vchart 使用当前的设置 viewBox 进行 resize
// 这里的 resize 不期望修改viewBox
// 但是 vchart 内 viewBox 优先级更高,所以这里的实现有点hack。
// @ts-ignore
this.vchart._viewBox = this.attribute.viewBox;
// @ts-ignore
this.vchart._option.viewBox = this.attribute.viewBox;
// @ts-ignore
this.vchart.getChart()._option.viewBox = this.attribute.viewBox;
this.vchart.resize(viewBoxSize.width, viewBoxSize.height);
}
const rootBounds = this.getVChartActualBounds();
// 2. 得到需要绘制全部内容时的 vchart 的 viewBox
// 不要小于设置viewBox;
rootBounds.union(this.attribute.viewBox);
// 当前实际绘图内容的 bounds
this._displayBounds = rootBounds.clone();
// 3. 考虑到 vchart 可能会将内容绘制到 -x, -y,记录下这个偏移量
this._vchartAutoTranslate.x = rootBounds.x1 < 0 ? rootBounds.x1 : 0;
this._vchartAutoTranslate.y = rootBounds.y1 < 0 ? rootBounds.y1 : 0;
// 4. 将 bounds 标准化到 0, 0, width, height
rootBounds.translate(-this._vchartAutoTranslate.x, -this._vchartAutoTranslate.y);
// 5. 将绘图 viewBox 更新到 vchart.stage
// 注意不要更新到 vchart,更新到vchart会触发vchart重新布局,但是我们不需要vchart按照 viewBox_display 重新布局
this._vchart.getStage().defaultLayer.translateTo(-this.vchartAutoTranslate.x, -this.vchartAutoTranslate.y);
// @ts-ignore
this._vchart._compiler._view.renderer.setViewBox(rootBounds, true);
}
}
6 changes: 5 additions & 1 deletion packages/vstory-core/src/interface/dsl/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@ export interface IDataGroupStyle {
};
}

export interface IChartCharacterInitOption {
vchartBoundsMode?: 'clip' | 'auto';
}

export interface IChartCharacterConfig extends ICharacterConfigBase {
options: {
// 图表spec
spec?: any;
// 初始化参数
initOption?: IInitOption;
initOption?: IInitOption & IChartCharacterInitOption;
// 边距
padding?: { left: number; top: number; right: number; bottom: number };
// 图表容器
Expand Down
5 changes: 5 additions & 0 deletions packages/vstory-core/src/utils/equal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Adapted from https://github.com/antvis/F2/blob/master/packages/f2/src/base/equal.ts by zengyue
// License: https://github.com/antvis/F2/blob/master/packages/f2/LICENSE

import type { IBoundsLike } from '@visactor/vutils';
import { isArray, isPlainObject } from '@visactor/vutils';

/**
Expand Down Expand Up @@ -72,3 +73,7 @@ export function getDiffedParams(from: any, to: any): any {
}
return obj;
}

export function isBoundsLikeEqual(a: IBoundsLike, b: IBoundsLike) {
return a.x1 === b.x1 && a.x2 === b.x2 && a.y1 === b.y1 && a.y2 === b.y2;
}
11 changes: 11 additions & 0 deletions packages/vstory/demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { VScreen } from './demos/works/vscreen';
import { Lottie } from './demos/component/lottie';
import { Infographic } from './demos/infographic/infographic';

import { BaseChart } from './demos/vchart-editor/base-chart';

type MenusType = (
| {
name: string;
Expand Down Expand Up @@ -152,6 +154,15 @@ const App = () => {
component: Infographic
}
]
},
{
name: 'VChart Editor',
subMenus: [
{
name: 'Base Chart',
component: BaseChart
}
]
}
];
const getSelectedMenu = useCallback<(menus: MenusType) => any>(
Expand Down
Loading
Loading