Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
104 changes: 104 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

> 说明:本仓库是 **Rush + PNPM** 管理的 monorepo(见 `rush.json`、`common/config/rush/*`)。大多数开发命令应通过 `rush` 运行。

## 常用命令(开发/构建/测试)

### 安装依赖

```bash
rush install
```

> `rush.json` 指定了 PNPM 版本与 Node 支持范围(`nodeSupportedVersionRange`)。

### 构建

```bash
# 构建所有项目(bulk command)
rush build

# 重新构建所有项目(bulk command)
rush rebuild

# 仅构建某个 package(在该 package 的 scripts 中存在 build 时)
rush run -p @visactor/vrender-core -s build
```

### 启动开发服务

```bash
# 启动 VRender 开发服务(等价于:rush run -p @visactor/vrender -s start)
rush start

# 启动组件库开发服务(等价于:rush run -p @visactor/vrender-components -s start)
rush components

# 启动文档站点开发服务(等价于:rush run -p @internal/docs -s start)
rush docs
```

### Lint / 格式化

```bash
# 运行各 package 的 eslint 脚本(bulk command)
rush eslint

# 预提交相关(仓库自定义全局命令,见 common/config/rush/command-line.json)
rush lint-staged
rush prettier
```

### 测试

```bash
# 运行各 package 的 test 脚本(bulk command)
rush test

# 仅运行某个 package 的测试(前提:该 package 定义了 test 脚本)
rush run -p @visactor/vrender-core -s test

# 运行单个测试文件/用例:
# 本仓库通常使用 Jest(见 share/jest-config),具体参数以对应 package 的 test 脚本为准。
# 常见做法是把 Jest 参数透传给 package 的 test 脚本,例如:
# rush run -p @visactor/vrender-core -s test -- --runTestsByPath <path>
```

## 代码架构(大图景)

### Monorepo 包划分(按职责)

- `packages/vrender`:对外的“整合包”。入口会根据运行环境(browser/node)加载对应 env,并注册常用图元、插件与动画,然后 re-export 各子包能力(见 `packages/vrender/src/index.ts`)。
- `packages/vrender-core`:渲染核心与基础设施(Stage/Layer/Window、渲染管线、事件、拾取、插件系统、资源加载等)。对外导出面很大(见 `packages/vrender-core/src/index.ts`)。
- `packages/vrender-kits`:图元/环境适配与“套件”能力(例如 browser/node 环境加载、各类 registerXXX 图元注册函数等;整合包会调用这些注册)。
- `packages/vrender-animate`:动画系统与自定义动画(ticker、插值、状态动画、组件动画扩展等)。
- `packages/vrender-components`:更高层的可复用组件(如 axis、brush 等),通常依赖 core/kits。
- `packages/react-vrender`、`packages/react-vrender-utils`:React 绑定/宿主配置与工具。

### 核心运行时模型:Window / Stage / Layer

(仓库内也有简短说明:`packages/vrender-core/src/core/README.md`)

- **Window**:对接“原生环境窗口/Canvas 区域”的抽象(浏览器 Canvas、小程序 Canvas 等)。
- **Stage**:逻辑上的“舞台/视口”,不必与单个 Canvas 一一对应;可以是整个窗口,也可以是窗口中的一个区域。Stage 负责组织图形树、驱动渲染与事件系统(见 `packages/vrender-core/src/core/stage.ts`)。
- **Layer**:Stage 内的图层抽象。多图层时通常对应多个底层渲染目标(Canvas / ImageData / WebGL FrameBuffer 等),并支持子图层与混合(见 `packages/vrender-core/src/core/layer.ts`)。

### 图形树与创建入口

- 图形对象以 `Group` 为容器节点,Stage/Layer 也继承自 `Group`,因此“场景图”本质是一棵图形树。
- 常用创建入口:`createStage`(见 `packages/vrender-core/src/create.ts`)。

### 插件与模块注册(“按需注册”思路)

- `@visactor/vrender`(整合包)在入口中会:
1) `preLoadAllModule()` 预加载模块;
2) 根据环境调用 `loadBrowserEnv/loadNodeEnv`;
3) 调用大量 `registerXXX()` 注册图元;
4) 注册内置插件(flex layout、3D view transform、HTML/React attribute 等);
5) 注册动画(`registerCustomAnimate/registerAnimate`)。

这意味着:
- 如果你在 **core/kits/animate/components** 中新增能力,通常需要提供/更新对应的 `register...` 入口,并确认整合包是否需要默认注册。
- 排查“某图元/能力不可用”时,优先检查是否已注册(入口通常在 `packages/vrender/src/index.ts` 或各包的 `register/` 目录)。
6 changes: 6 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"permissions": {
"defaultMode": "bypassPermissions"
},
"enableAllProjectMcpServers": true
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ report.html
coverage/

*.local.ts
*.local.md
*.local.json

# Dependency directories
node_modules/
Expand Down
12 changes: 2 additions & 10 deletions packages/vrender-animate/src/animate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,17 +263,9 @@ export class Animate implements IAnimate {
// 创建新的step
const step = new Step(AnimateStepType.from, props, duration, easing);

// 如果是第一个step
if (!this._firstStep) {
this._firstStep = step;
this._lastStep = step;
} else {
// 添加到链表末尾
this._lastStep.append(step);
this._lastStep = step;
}
step.bind(this.target, this);

this.updateDuration();
this.updateStepAfterAppend(step);

return this;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/vrender-animate/src/custom/clip-graphic.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IArcGraphicAttribute, IGraphic, IGroup, IRectGraphicAttribute } from '@visactor/vrender-core';
import { application, AttributeUpdateType, type EasingType } from '@visactor/vrender-core';
import { AttributeUpdateType, type EasingType, graphicService } from '@visactor/vrender-core';
import { ACustomAnimate } from './custom-animate';

export class ClipGraphicAnimate extends ACustomAnimate<any> {
Expand Down Expand Up @@ -102,7 +102,7 @@ export class ClipAngleAnimate extends ClipGraphicAnimate {
arcStartAngle = startAngle;
arcEndAngle = animationType === 'out' ? startAngle + Math.PI * 2 : startAngle;
}
const arc = application.graphicService.creator.arc({
const arc = graphicService.creator.arc({
x: params?.center?.x ?? width / 2,
y: params?.center?.y ?? height / 2,
outerRadius: params?.radius ?? (width + height) / 2,
Expand Down Expand Up @@ -152,7 +152,7 @@ export class ClipRadiusAnimate extends ClipGraphicAnimate {
const startRadius = params?.startRadius ?? 0;
const endRadius = params?.endRadius ?? Math.sqrt((width / 2) ** 2 + (height / 2) ** 2);

const arc = application.graphicService.creator.arc({
const arc = graphicService.creator.arc({
x: params?.center?.x ?? width / 2,
y: params?.center?.y ?? height / 2,
outerRadius: animationType === 'out' ? endRadius : startRadius,
Expand Down Expand Up @@ -196,7 +196,7 @@ export class ClipDirectionAnimate extends ClipGraphicAnimate {
const direction = params?.direction ?? 'x';
const orient = params?.orient ?? 'positive';

const rect = application.graphicService.creator.rect({
const rect = graphicService.creator.rect({
x: 0,
y: 0,
width: animationType === 'in' && direction === 'x' ? 0 : width,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { EasingType } from '@visactor/vrender-core';
import { vglobal } from '@visactor/vrender-core';
import { VGlobal, vglobal } from '@visactor/vrender-core';
import { AStageAnimate } from '../../custom-animate';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { vglobal } from '@visactor/vrender-core';
import { VGlobal, vglobal } from '@visactor/vrender-core';

/**
* 图像处理工具类,公共的图像处理逻辑
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { EasingType } from '@visactor/vrender-core';
import { vglobal } from '@visactor/vrender-core';
import { VGlobal, vglobal } from '@visactor/vrender-core';
import { AStageAnimate } from '../custom-animate';

// 模糊效果配置接口
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { EasingType } from '@visactor/vrender-core';
import { vglobal } from '@visactor/vrender-core';
import { VGlobal, vglobal } from '@visactor/vrender-core';
import { DisappearAnimateBase } from './base/DisappearAnimateBase';

export interface PixelationConfig {
Expand Down
42 changes: 13 additions & 29 deletions packages/vrender-animate/src/custom/morphing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
splitArea,
splitPath,
CustomPath2D,
application,
graphicService,
interpolateColor,
ColorStore,
ColorType,
Expand Down Expand Up @@ -377,7 +377,7 @@ export const oneToMultiMorph = (
}

const childGraphics: IGraphic[] = (
animationConfig?.splitPath === 'clone' ? cloneGraphic : (animationConfig?.splitPath ?? splitGraphic)
animationConfig?.splitPath === 'clone' ? cloneGraphic : animationConfig?.splitPath ?? splitGraphic
)(fromGraphic, validateToGraphics.length, false);

const oldOnEnd = animationConfig?.onEnd;
Expand Down Expand Up @@ -541,7 +541,7 @@ const appendShadowChildrenToGraphic = (graphic: IGraphic, children: IGraphic[],
rect: childAttrs
});
new Array(count).fill(0).forEach(el => {
const child = application.graphicService.creator.rect({
const child = graphicService.creator.rect({
x: 0,
y: 0,
width,
Expand Down Expand Up @@ -571,9 +571,7 @@ export const cloneGraphic = (graphic: IGraphic, count: number, needAppend?: bool
path: new CustomPath2D().fromCustomPath2D(path)
};

children.push(
application.graphicService.creator.path(needAppend ? element : Object.assign({}, childAttrs, element))
);
children.push(graphicService.creator.path(needAppend ? element : Object.assign({}, childAttrs, element)));
}

if (needAppend) {
Expand All @@ -597,60 +595,46 @@ export const splitGraphic = (graphic: IGraphic, count: number, needAppend?: bool
if (graphic.type === 'rect') {
const childrenAttrs = splitRect(graphic as IRect, count);
childrenAttrs.forEach(element => {
children.push(
application.graphicService.creator.rect(needAppend ? element : Object.assign({}, childAttrs, element))
);
children.push(graphicService.creator.rect(needAppend ? element : Object.assign({}, childAttrs, element)));
});
} else if (graphic.type === 'arc') {
const childrenAttrs = splitArc(graphic as IArc, count);
childrenAttrs.forEach(element => {
children.push(
application.graphicService.creator.arc(needAppend ? element : Object.assign({}, childAttrs, element))
);
children.push(graphicService.creator.arc(needAppend ? element : Object.assign({}, childAttrs, element)));
});
} else if (graphic.type === 'circle') {
const childrenAttrs = splitCircle(graphic as ICircle, count);
childrenAttrs.forEach(element => {
children.push(
application.graphicService.creator.arc(needAppend ? element : Object.assign({}, childAttrs, element))
);
children.push(graphicService.creator.arc(needAppend ? element : Object.assign({}, childAttrs, element)));
});
} else if (graphic.type === 'line') {
const childrenAttrs = splitLine(graphic as ILine, count);
const defaultSymbol = { size: 10, symbolType: 'circle' };

childrenAttrs.forEach(element => {
children.push(
application.graphicService.creator.symbol(
graphicService.creator.symbol(
needAppend ? Object.assign({}, element, defaultSymbol) : Object.assign({}, childAttrs, element, defaultSymbol)
)
);
});
} else if (graphic.type === 'polygon') {
const childrenAttrs = splitPolygon(graphic as IPolygon, count);
childrenAttrs.forEach(element => {
children.push(
application.graphicService.creator.polygon(needAppend ? element : Object.assign({}, childAttrs, element))
);
children.push(graphicService.creator.polygon(needAppend ? element : Object.assign({}, childAttrs, element)));
});
} else if (graphic.type === 'area') {
const childrenAttrs = splitArea(graphic as IArea, count);
childrenAttrs.forEach(element => {
children.push(
application.graphicService.creator.polygon(needAppend ? element : Object.assign({}, childAttrs, element))
);
children.push(graphicService.creator.polygon(needAppend ? element : Object.assign({}, childAttrs, element)));
});
} else if (graphic.type === 'path') {
const childrenAttrs = splitPath(graphic as IPath, count);
childrenAttrs.forEach(element => {
if ('path' in element) {
children.push(
application.graphicService.creator.path(needAppend ? element : Object.assign({}, childAttrs, element))
);
children.push(graphicService.creator.path(needAppend ? element : Object.assign({}, childAttrs, element)));
} else {
children.push(
application.graphicService.creator.polygon(needAppend ? element : Object.assign({}, childAttrs, element))
);
children.push(graphicService.creator.polygon(needAppend ? element : Object.assign({}, childAttrs, element)));
}
});
}
Expand Down Expand Up @@ -687,7 +671,7 @@ export const multiToOneMorph = (
}

const childGraphics: IGraphic[] = (
animationConfig?.splitPath === 'clone' ? cloneGraphic : (animationConfig?.splitPath ?? splitGraphic)
animationConfig?.splitPath === 'clone' ? cloneGraphic : animationConfig?.splitPath ?? splitGraphic
)(toGraphic, validateFromGraphics.length, true);

const toAttrs = toGraphic.attribute;
Expand Down
6 changes: 3 additions & 3 deletions packages/vrender-animate/src/custom/streamLight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
IRect,
IRectAttribute
} from '@visactor/vrender-core';
import { application, AttributeUpdateType, CustomPath2D, divideCubic } from '@visactor/vrender-core';
import { AttributeUpdateType, CustomPath2D, divideCubic, graphicService } from '@visactor/vrender-core';
import { ACustomAnimate } from './custom-animate';
import type { IPoint } from '@visactor/vutils';
import { PointService } from '@visactor/vutils';
Expand Down Expand Up @@ -52,7 +52,7 @@ export class StreamLight extends ACustomAnimate<any> {

onStartLineOrArea(type: 'line' | 'area') {
const root = this.target.attachShadow();
const line = application.graphicService.creator[type]({
const line = graphicService.creator[type]({
...this.params?.attribute
});
this[type] = line;
Expand All @@ -69,7 +69,7 @@ export class StreamLight extends ACustomAnimate<any> {
const size = this.target.AABBBounds[sizeAttr]();
const y = isHorizontal ? 0 : this.target.AABBBounds.y1;

const rect = application.graphicService.creator.rect({
const rect = graphicService.creator.rect({
[sizeAttr]: size,
fill: '#bcdeff',
shadowBlur: 30,
Expand Down
4 changes: 3 additions & 1 deletion packages/vrender-animate/src/register.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Graphic } from '@visactor/vrender-core';
import { Graphic, Stage } from '@visactor/vrender-core';
import { mixin } from '@visactor/vutils';
import { GraphicStateExtension } from './state/graphic-extension';
import { AnimateExtension } from './animate-extension';
Expand All @@ -7,4 +7,6 @@ export function registerAnimate() {
// Mix in animation state methods to Graphic prototype
mixin(Graphic, GraphicStateExtension);
mixin(Graphic, AnimateExtension);
// Mix in animation methods to Stage prototype (for createTimeline and createTicker)
mixin(Stage, AnimateExtension);
}
6 changes: 3 additions & 3 deletions packages/vrender-animate/src/ticker/default-ticker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EventEmitter } from '@visactor/vutils';
import type { IStage, ITimeline } from '@visactor/vrender-core';
import { application, PerformanceRAF, type ITickHandler, type ITicker, STATUS } from '@visactor/vrender-core';
import { PerformanceRAF, type ITickHandler, type ITicker, STATUS, vglobal } from '@visactor/vrender-core';

const performanceRAF = new PerformanceRAF();

Expand Down Expand Up @@ -70,10 +70,10 @@ export class DefaultTicker extends EventEmitter implements ITicker {
init(): void {
this.interval = 16;
this.status = STATUS.INITIAL;
application.global.hooks.onSetEnv.tap('graph-ticker', () => {
vglobal.hooks.onSetEnv.tap('graph-ticker', () => {
this.initHandler(false);
});
if (application.global.env) {
if (vglobal.env) {
this.initHandler(false);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createStage } from '@visactor/vrender-core';
import { initBrowserEnv } from '@visactor/vrender-kits';
initBrowserEnv();
import { loadBrowserEnv } from '@visactor/vrender-kits';
loadBrowserEnv();
import render from '../../util/render';
import { LineAxis } from '../../../src';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createStage } from '@visactor/vrender-core';
import { initBrowserEnv } from '@visactor/vrender-kits';
initBrowserEnv();
import { loadBrowserEnv } from '@visactor/vrender-kits';
loadBrowserEnv();
import { PointScale } from '@visactor/vscale';
import { LineAxis } from '../../../src';

Expand Down
Loading
Loading