diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index aa3412bf6..3215f8a0f 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -14,7 +14,7 @@ importers: '@types/react-dom': ^18.0.0 '@visactor/vchart': 1.3.0 '@visactor/vgrammar': ~0.5.7 - '@visactor/vrender': workspace:0.21.8 + '@visactor/vrender': workspace:0.21.9 '@visactor/vutils': ~0.19.3 '@vitejs/plugin-react': 3.1.0 axios: ^1.4.0 @@ -71,7 +71,7 @@ importers: '@types/react': ^18.0.0 '@types/react-dom': ^18.0.0 '@types/react-reconciler': ^0.28.2 - '@visactor/vrender': workspace:0.21.8 + '@visactor/vrender': workspace:0.21.9 '@visactor/vutils': ~0.19.3 '@vitejs/plugin-react': 3.1.0 cross-env: ^7.0.3 @@ -111,8 +111,8 @@ importers: '@rushstack/eslint-patch': ~1.1.4 '@types/react': ^18.0.0 '@types/react-dom': ^18.0.0 - '@visactor/react-vrender': workspace:0.21.8 - '@visactor/vrender': workspace:0.21.8 + '@visactor/react-vrender': workspace:0.21.9 + '@visactor/vrender': workspace:0.21.9 '@visactor/vutils': ~0.19.3 '@vitejs/plugin-react': 3.1.0 cross-env: ^7.0.3 @@ -153,8 +153,8 @@ importers: '@types/jest': ^26.0.0 '@types/react': ^18.0.0 '@types/react-dom': ^18.0.0 - '@visactor/vrender-core': workspace:0.21.8 - '@visactor/vrender-kits': workspace:0.21.8 + '@visactor/vrender-core': workspace:0.21.9 + '@visactor/vrender-kits': workspace:0.21.9 '@visactor/vutils': ~0.19.3 '@vitejs/plugin-react': 3.1.0 canvas: 2.11.2 @@ -200,8 +200,8 @@ importers: '@internal/ts-config': workspace:* '@rushstack/eslint-patch': ~1.1.4 '@types/jest': ^26.0.0 - '@visactor/vrender-core': workspace:0.21.8 - '@visactor/vrender-kits': workspace:0.21.8 + '@visactor/vrender-core': workspace:0.21.9 + '@visactor/vrender-kits': workspace:0.21.9 '@visactor/vscale': ~0.19.3 '@visactor/vutils': ~0.19.3 cross-env: ^7.0.3 @@ -287,12 +287,13 @@ importers: '@types/node-fetch': 2.6.4 '@types/react': ^18.0.0 '@types/react-dom': ^18.0.0 - '@visactor/vrender-core': workspace:0.21.8 + '@visactor/vrender-core': workspace:0.21.9 '@visactor/vutils': ~0.19.3 '@vitejs/plugin-react': 3.1.0 canvas: 2.11.2 cross-env: ^7.0.3 eslint: ~8.18.0 + gifuct-js: 2.1.2 node-fetch: 2.6.6 react: ^18.0.0 react-dom: ^18.0.0 @@ -303,6 +304,7 @@ importers: '@resvg/resvg-js': 2.4.1 '@visactor/vrender-core': link:../vrender-core '@visactor/vutils': 0.19.3 + gifuct-js: 2.1.2 roughjs: 4.5.2 devDependencies: '@internal/bundler': link:../../tools/bundler @@ -369,10 +371,10 @@ importers: '@rushstack/eslint-patch': ~1.1.4 '@types/node': '*' '@types/node-fetch': 2.6.4 - '@visactor/vrender': workspace:0.21.8 - '@visactor/vrender-components': workspace:0.21.8 - '@visactor/vrender-core': workspace:0.21.8 - '@visactor/vrender-kits': workspace:0.21.8 + '@visactor/vrender': workspace:0.21.9 + '@visactor/vrender-components': workspace:0.21.9 + '@visactor/vrender-core': workspace:0.21.9 + '@visactor/vrender-kits': workspace:0.21.9 cross-env: ^7.0.3 eslint: ~8.18.0 form-data: ~4.0.0 @@ -6338,6 +6340,12 @@ packages: assert-plus: 1.0.0 dev: true + /gifuct-js/2.1.2: + resolution: {integrity: sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==} + dependencies: + js-binary-schema-parser: 2.0.3 + dev: false + /glob-parent/3.1.0: resolution: {integrity: sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==} dependencies: @@ -8289,6 +8297,10 @@ packages: - ts-node - utf-8-validate + /js-binary-schema-parser/2.0.3: + resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==} + dev: false + /js-string-escape/1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} engines: {node: '>= 0.8'} diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 6632da0a9..e658de8f6 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vrenderMain","version":"0.21.8","nextBump":"patch"}] +[{"definitionName":"lockStepVersion","policyName":"vrenderMain","version":"0.21.9","nextBump":"patch"}] diff --git a/docs/assets/api/en/common/text.md b/docs/assets/api/en/common/text.md index d28e2e8df..c21774b92 100644 --- a/docs/assets/api/en/common/text.md +++ b/docs/assets/api/en/common/text.md @@ -36,3 +36,7 @@ Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui sym #${prefix} lineThrough(number) = 0 中划线线粗 + +#${prefix} disableAutoClipedPoptip(boolean) = false + +禁用省略hover展示poptip \ No newline at end of file diff --git a/docs/assets/api/zh/common/text.md b/docs/assets/api/zh/common/text.md index d28e2e8df..c21774b92 100644 --- a/docs/assets/api/zh/common/text.md +++ b/docs/assets/api/zh/common/text.md @@ -36,3 +36,7 @@ Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui sym #${prefix} lineThrough(number) = 0 中划线线粗 + +#${prefix} disableAutoClipedPoptip(boolean) = false + +禁用省略hover展示poptip \ No newline at end of file diff --git a/docs/assets/option/en/common/text.md b/docs/assets/option/en/common/text.md index d28e2e8df..c21774b92 100644 --- a/docs/assets/option/en/common/text.md +++ b/docs/assets/option/en/common/text.md @@ -36,3 +36,7 @@ Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui sym #${prefix} lineThrough(number) = 0 中划线线粗 + +#${prefix} disableAutoClipedPoptip(boolean) = false + +禁用省略hover展示poptip \ No newline at end of file diff --git a/docs/assets/option/zh/common/text.md b/docs/assets/option/zh/common/text.md index d28e2e8df..c21774b92 100644 --- a/docs/assets/option/zh/common/text.md +++ b/docs/assets/option/zh/common/text.md @@ -36,3 +36,7 @@ Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui sym #${prefix} lineThrough(number) = 0 中划线线粗 + +#${prefix} disableAutoClipedPoptip(boolean) = false + +禁用省略hover展示poptip \ No newline at end of file diff --git a/docs/package.json b/docs/package.json index 7ea72f2f2..b86439163 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,7 +13,7 @@ "@visactor/vchart": "1.3.0", "@visactor/vutils": "~0.19.3", "@visactor/vgrammar": "~0.5.7", - "@visactor/vrender": "workspace:0.21.8", + "@visactor/vrender": "workspace:0.21.9", "markdown-it": "^13.0.0", "highlight.js": "^11.8.0", "axios": "^1.4.0", diff --git a/packages/react-vrender-utils/CHANGELOG.json b/packages/react-vrender-utils/CHANGELOG.json index 54631d1f6..5a84f2c26 100644 --- a/packages/react-vrender-utils/CHANGELOG.json +++ b/packages/react-vrender-utils/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/react-vrender-utils", "entries": [ + { + "version": "0.21.9", + "tag": "@visactor/react-vrender-utils_v0.21.9", + "date": "Mon, 13 Jan 2025 03:23:50 GMT", + "comments": {} + }, { "version": "0.21.8", "tag": "@visactor/react-vrender-utils_v0.21.8", diff --git a/packages/react-vrender-utils/CHANGELOG.md b/packages/react-vrender-utils/CHANGELOG.md index 260e2690d..d7f3504e0 100644 --- a/packages/react-vrender-utils/CHANGELOG.md +++ b/packages/react-vrender-utils/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/react-vrender-utils -This log was last generated on Mon, 06 Jan 2025 11:07:36 GMT and should not be manually modified. +This log was last generated on Mon, 13 Jan 2025 03:23:50 GMT and should not be manually modified. + +## 0.21.9 +Mon, 13 Jan 2025 03:23:50 GMT + +_Version update only_ ## 0.21.8 Mon, 06 Jan 2025 11:07:36 GMT diff --git a/packages/react-vrender-utils/package.json b/packages/react-vrender-utils/package.json index 0b3682226..0d7f68320 100644 --- a/packages/react-vrender-utils/package.json +++ b/packages/react-vrender-utils/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vrender-utils", - "version": "0.21.8", + "version": "0.21.9", "description": "", "sideEffects": false, "main": "cjs/index.js", @@ -24,8 +24,8 @@ "react-dom": "^18.2.0" }, "dependencies": { - "@visactor/vrender": "workspace:0.21.8", - "@visactor/react-vrender": "workspace:0.21.8", + "@visactor/vrender": "workspace:0.21.9", + "@visactor/react-vrender": "workspace:0.21.9", "@visactor/vutils": "~0.19.3", "react-reconciler": "^0.29.0", "tslib": "^2.3.1" diff --git a/packages/react-vrender/CHANGELOG.json b/packages/react-vrender/CHANGELOG.json index c4c829daf..31375da1c 100644 --- a/packages/react-vrender/CHANGELOG.json +++ b/packages/react-vrender/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/react-vrender", "entries": [ + { + "version": "0.21.9", + "tag": "@visactor/react-vrender_v0.21.9", + "date": "Mon, 13 Jan 2025 03:23:50 GMT", + "comments": {} + }, { "version": "0.21.8", "tag": "@visactor/react-vrender_v0.21.8", diff --git a/packages/react-vrender/CHANGELOG.md b/packages/react-vrender/CHANGELOG.md index e3543eaa5..87c3f10df 100644 --- a/packages/react-vrender/CHANGELOG.md +++ b/packages/react-vrender/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/react-vrender -This log was last generated on Mon, 06 Jan 2025 11:07:36 GMT and should not be manually modified. +This log was last generated on Mon, 13 Jan 2025 03:23:50 GMT and should not be manually modified. + +## 0.21.9 +Mon, 13 Jan 2025 03:23:50 GMT + +_Version update only_ ## 0.21.8 Mon, 06 Jan 2025 11:07:36 GMT diff --git a/packages/react-vrender/package.json b/packages/react-vrender/package.json index e45d5de74..09ddbda97 100644 --- a/packages/react-vrender/package.json +++ b/packages/react-vrender/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vrender", - "version": "0.21.8", + "version": "0.21.9", "description": "", "sideEffects": false, "main": "cjs/index.js", @@ -23,7 +23,7 @@ "react": "^18.2.0" }, "dependencies": { - "@visactor/vrender": "workspace:0.21.8", + "@visactor/vrender": "workspace:0.21.9", "@visactor/vutils": "~0.19.3", "react-reconciler": "^0.29.0", "tslib": "^2.3.1" diff --git a/packages/vrender-components/CHANGELOG.json b/packages/vrender-components/CHANGELOG.json index 9364e6029..76072a69f 100644 --- a/packages/vrender-components/CHANGELOG.json +++ b/packages/vrender-components/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@visactor/vrender-components", "entries": [ + { + "version": "0.21.9", + "tag": "@visactor/vrender-components_v0.21.9", + "date": "Mon, 13 Jan 2025 03:23:50 GMT", + "comments": { + "none": [ + { + "comment": "feat: add GifImage component" + }, + { + "comment": "fix: fix duplicate label issue after custom filtering with label dataFilter" + } + ] + } + }, { "version": "0.21.8", "tag": "@visactor/vrender-components_v0.21.8", diff --git a/packages/vrender-components/CHANGELOG.md b/packages/vrender-components/CHANGELOG.md index cffd968aa..ace5b1e23 100644 --- a/packages/vrender-components/CHANGELOG.md +++ b/packages/vrender-components/CHANGELOG.md @@ -1,6 +1,14 @@ # Change Log - @visactor/vrender-components -This log was last generated on Mon, 06 Jan 2025 11:07:36 GMT and should not be manually modified. +This log was last generated on Mon, 13 Jan 2025 03:23:50 GMT and should not be manually modified. + +## 0.21.9 +Mon, 13 Jan 2025 03:23:50 GMT + +### Updates + +- feat: add GifImage component +- fix: fix duplicate label issue after custom filtering with label dataFilter ## 0.21.8 Mon, 06 Jan 2025 11:07:36 GMT diff --git a/packages/vrender-components/package.json b/packages/vrender-components/package.json index 499396d6a..30b9baf67 100644 --- a/packages/vrender-components/package.json +++ b/packages/vrender-components/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender-components", - "version": "0.21.8", + "version": "0.21.9", "description": "components library for dp visualization", "sideEffects": false, "main": "cjs/index.js", @@ -27,8 +27,8 @@ "dependencies": { "@visactor/vutils": "~0.19.3", "@visactor/vscale": "~0.19.3", - "@visactor/vrender-core": "workspace:0.21.8", - "@visactor/vrender-kits": "workspace:0.21.8" + "@visactor/vrender-core": "workspace:0.21.9", + "@visactor/vrender-kits": "workspace:0.21.9" }, "devDependencies": { "@internal/bundler": "workspace:*", diff --git a/packages/vrender-components/src/axis/type.ts b/packages/vrender-components/src/axis/type.ts index 52b816f19..c61919450 100644 --- a/packages/vrender-components/src/axis/type.ts +++ b/packages/vrender-components/src/axis/type.ts @@ -448,7 +448,13 @@ export interface AxisLabelOverlap { overflowLimitLength?: | number | { + /** + * 左侧扩充空间的大小 + */ left?: number; + /** + * 右侧扩充空间的大小 + */ right?: number; // top?: number; // bottom?: number; diff --git a/packages/vrender-components/src/core/type.ts b/packages/vrender-components/src/core/type.ts index d6cc9a13a..7f1c3a1bc 100644 --- a/packages/vrender-components/src/core/type.ts +++ b/packages/vrender-components/src/core/type.ts @@ -52,9 +52,21 @@ export type Padding = | number | number[] | { + /** + * 上边距 + */ top?: number; + /** + * 下边距 + */ bottom?: number; + /** + * 左边距 + */ left?: number; + /** + * 右边距 + */ right?: number; }; @@ -65,7 +77,13 @@ type CommonTextContent = { | number | number[] | { + /** + * 指定文本节点类型为'text' + */ type?: 'text'; + /** + * 设置文本的内容 + */ text: string | string[] | number | number[]; }; }; diff --git a/packages/vrender-components/src/label/base.ts b/packages/vrender-components/src/label/base.ts index 918a90e5e..8e1c79c94 100644 --- a/packages/vrender-components/src/label/base.ts +++ b/packages/vrender-components/src/label/base.ts @@ -182,6 +182,12 @@ export class LabelBase extends AbstractComponent { data = dataFilter(data); } + if (data && data.length) { + // 数据保护,防止重复 id 造成不可预知的问题 + const seenIds = new Set(); + data = data.filter(d => !seenIds.has(d.id) && seenIds.add(d.id)); + } + let labels: (IText | IRichText)[] = this._initText(data); if (isFunction(customLayoutFunc)) { diff --git a/packages/vrender-components/src/label/type.ts b/packages/vrender-components/src/label/type.ts index 3c30021ff..fcef620ce 100644 --- a/packages/vrender-components/src/label/type.ts +++ b/packages/vrender-components/src/label/type.ts @@ -162,7 +162,16 @@ export interface OverlapAttrs { /** * 防重叠的区域大小 */ - size?: { width: number; height: number }; + size?: { + /** + * 防重叠区域的宽度 + */ + width: number; + /** + * 防重叠区域的高度 + */ + height: number; + }; /** * 发生重叠后,是否隐藏标签 @@ -274,6 +283,9 @@ export interface SmartInvertAttrs { } export type ShiftYStrategy = { + /** + * 将防重叠策略设置为 'shiftY' + */ type: 'shiftY'; /** * 布局迭代次数 @@ -294,10 +306,13 @@ export type ShiftYStrategy = { export type PositionStrategy = { /** - * 可选位置策略。 + * 将防重叠的策略设置为'position',即可选位置策略。 * 若默认位置没有足够的空间放置标签,则考虑 position 内的备选位置。 */ type: 'position'; + /** + * 所有的备选位置 + */ position?: Functional; /** * 当 position 内的备选位置依然无法放下标签时,标签是否放回原位。 @@ -310,10 +325,13 @@ export type PositionStrategy = { export type BoundStrategy = { /** - * 标签配置在图形内部时使用。 + * 将防重叠策略设置为'bound',当标签配置在图形内部时使用。 * 当图形大小不足以放下标签,则考虑 position 内的备选位置。 */ type: 'bound'; + /** + * 所有的备选位置 + */ position?: Functional; /** * 当 position 内的备选位置依然无法放下标签时,标签是否放回原位。 @@ -326,7 +344,7 @@ export type BoundStrategy = { export type MoveYStrategy = { /** - * 可选位置策略。 + * 将防重叠策略设置为'moveY' * 若默认位置没有足够的空间放置标签,则根据 offset 在Y方向上寻找位置。 */ type: 'moveY'; @@ -338,7 +356,7 @@ export type MoveYStrategy = { export type MoveXStrategy = { /** - * 可选位置策略。 + * 将防重叠策略设置为'moveX' * 若默认位置没有足够的空间放置标签,则根据 offset 在X方向上寻找位置。 */ type: 'moveX'; @@ -555,26 +573,65 @@ export interface DataLabelAttrs extends IGroupGraphicAttribute { export type Functional = T | ((data: any) => T); +/** + * 标签的离场动画配置 + */ export interface ILabelExitAnimation { + /** + * 动画执行的时长 + */ duration?: number; + /** + * 动画延迟的时长 + */ delay?: number; + /** + * 动画的缓动函数 + */ easing?: EasingType; } +/** + * 标签的入场动画配置 + */ export interface ILabelEnterAnimation extends ILabelExitAnimation { + /** + * 标签动画的模式,支持三种类型 + * - same-time:标签出现动画和关联图形的动画同时进行 + * - after:当关联动图的出场动画结束后,执行标签的出场动画 + * - after-all:当所有关联图元的出场动画结束后,执行标签的出场动画 + */ mode?: 'same-time' | 'after' | 'after-all'; } +/** + * 标签的更新动画配置 + */ export interface ILabelUpdateAnimation extends ILabelExitAnimation { - /** 是否开启 increaseCount 动画 + /** + * 是否开启 increaseCount 动画 * @default true */ increaseEffect?: boolean; } +/** + * 标签更新的时候,动画的通道配置 + */ export interface ILabelUpdateChannelAnimation extends ILabelUpdateAnimation { + /** + * 进行插值动画的视觉通道 + */ channel?: string[]; - options?: { excludeChannels?: string[] }; + /** + * 动画的配置 + */ + options?: { + /** + * 忽略的视觉通道 + */ + excludeChannels?: string[]; + }; } export interface ILabelAnimation extends ILabelEnterAnimation, ILabelExitAnimation, ILabelUpdateAnimation {} diff --git a/packages/vrender-components/src/legend/discrete/type.ts b/packages/vrender-components/src/legend/discrete/type.ts index d31475a3d..4ca0def55 100644 --- a/packages/vrender-components/src/legend/discrete/type.ts +++ b/packages/vrender-components/src/legend/discrete/type.ts @@ -34,6 +34,9 @@ export interface LegendSwitchComponentAttributes { animationEasing?: EasingType; } +/** + * 离散类型的图例组件,当图例项较多的时候,默认使用分页器组件 + */ export type LegendPagerAttributes = Omit & LegendSwitchComponentAttributes & { /** @@ -43,8 +46,15 @@ export type LegendPagerAttributes = Omit & position?: 'start' | 'middle' | 'end'; }; +/** + * 离散类型的图例组件使用滚动条组件的时候对应的配置 + */ export type LegendScrollbarAttributes = Omit & LegendSwitchComponentAttributes & { + /** + * 将翻页器的类型设置为 'scrollbar' + * 申明图例组件使用滚动条进行翻页展示更多的图例项 + */ type: 'scrollbar'; /** * @deprecated since 0.20.13 @@ -209,8 +219,8 @@ export type LegendItem = { */ align?: 'left' | 'right'; /** - * @since 0.21.3 * 水平方向时,一行中多个图例的垂直对齐方式 + * @since 0.21.3 */ verticalAlign?: 'top' | 'middle' | 'bottom'; }; @@ -222,7 +232,10 @@ export type DiscreteLegendAttrs = { select?: | boolean | { - /** @since 0.20.13 */ + /** + * 触发选中交互的事件类型 + * @since 0.20.13 + **/ trigger?: GraphicEventType; }; @@ -232,9 +245,15 @@ export type DiscreteLegendAttrs = { hover?: | boolean | { - /** @since 0.20.13 */ + /** + * 触发hover交互的事件类型 + * @since 0.20.13 + **/ trigger?: GraphicEventType; - /** @since 0.20.13 */ + /** + * 触发取消hover交互的事件类型 + * @since 0.20.13 + **/ triggerOff?: GraphicEventType; }; /** diff --git a/packages/vrender-components/src/marker/type.ts b/packages/vrender-components/src/marker/type.ts index 45b09818e..55605d936 100644 --- a/packages/vrender-components/src/marker/type.ts +++ b/packages/vrender-components/src/marker/type.ts @@ -124,6 +124,9 @@ export type IMarkRef = { }; export type MarkerAttrs = IGroupGraphicAttribute & { + /** + * 设置标注的类型 + */ type?: 'line' | 'arc-line' | 'area' | 'arc-area' | 'point'; /** * 是否支持交互 @@ -154,23 +157,50 @@ export type MarkerAttrs = IGroupGraphicAttribute & { * 组件绘制范围配置 */ limitRect?: { + /** + * 绘制范围的起点x坐标 + */ x: number; + /** + * 绘制范围的起点y坐标 + */ y: number; + /** + * 绘制范围的宽度 + */ width: number; + /** + * 绘制范围的高度 + */ height: number; }; } & BaseMarkerAnimation; /** animation type */ export type BaseMarkerAnimation = { + /** + * 动画公共配置 + */ animation?: MarkerAnimation | boolean; + /** + * 入场动画配置 + */ animationEnter?: MarkerUpdateAnimation; + /** + * 更新动画配置 + */ animationUpdate?: MarkerUpdateAnimation; + /** + * 离场动画配置 + */ animationExit?: MarkerExitAnimation; }; export type MarkerAnimation = MarkerUpdateAnimation | MarkerUpdateAnimation; export type MarkerUpdateAnimation = { + /** + * 设置动画的类型 + */ type: T; } & MarkerExitAnimation; @@ -181,9 +211,21 @@ export type CommonMarkAreaAnimationType = 'fadeIn'; export type MarkPointAnimationType = 'callIn' | 'fadeIn'; export type MarkerExitAnimation = { + /** + * 设置离场动画的类型为fadeOut,即淡出 + */ type: 'fadeOut'; + /** + * 动画的时长 + */ duration?: number; + /** + * 动画延迟的时长 + */ delay?: number; + /** + * 动画的缓动函数 + */ easing?: EasingType; }; @@ -191,29 +233,83 @@ export type MarkerAnimationState = 'enter' | 'update' | 'exit'; /** state type */ export type MarkCommonLineState = { + /** + * 设置线图形的在特定状态下的样式 + */ line?: State; + /** + * 设置线的起点在特定状态下的样式 + */ lineStartSymbol?: State>; + /** + * 设置线的终点在特定状态下的样式 + */ lineEndSymbol?: State>; + /** + * 设置标签在特定状态下的样式 + */ label?: State>; + /** + * 设置标签背景区块在特定状态下的样式 + */ labelBackground?: State>; }; export type CommonMarkAreaState = { + /** + * 设置标注区域在特定状态下的样式 + */ area?: State>; + /** + * 设置标注区域标签在特定状态下的样式 + */ label?: State>; + /** + * 设置标签背景区块在特定状态下的样式 + */ labelBackground?: State>; }; export type MarkPointState = { + /** + * 设置标注点连线在特定状态下的样式 + */ line?: State[]>; + /** + * 设置线起点图形在特定状态下的样式 + */ lineStartSymbol?: State>; + /** + * 设置线终点图形在特定状态下的样式 + */ lineEndSymbol?: State>; + /** + * 设置标注图形在特定状态下的样式 + */ symbol?: State>; + /** + * 设置标注图形在特定状态下的样式 + */ image?: State>; + /** + * 设置标签在特定状态下的样式 + */ text?: State>; + /** + * 设置标签背景区块在特定状态下的样式 + */ textBackground?: State>; + /** + * 设置富文本在特定状态下的样式 + */ richText?: State>; + /** + * 设置自定义标注图形在特定状态下的样式 + */ customMark?: State>; + /** + * 设置目标元素在特定状态下的样式 + */ targetItem?: State>; }; @@ -235,6 +331,9 @@ export type MarkCommonLineAttrs; }; @@ -243,6 +342,9 @@ export type MarkLineAttrs = MarkCommonLineAttrs< keyof typeof IMarkLineLabelPosition, MarkCommonLineAnimationType > & { + /** + * 将辅助线的类型设置为 'line' + */ type?: 'line'; /** * 是否对 points 进行多段处理,默认为 false,即直接将所有的点连接成线。 @@ -255,9 +357,12 @@ export type MarkLineAttrs = MarkCommonLineAttrs< */ mainSegmentIndex?: number; /** - * 构成line的点: 如果是两个点,则为直线;多个点则为曲线 + * 构成line的点: 如果是两个点,则为直线;多个点则为折线 */ points: Point[] | Point[][]; + /** + * 线的样式设置 + */ lineStyle?: ILineGraphicAttribute; }; @@ -266,6 +371,9 @@ export type MarkArcLineAttrs = MarkCommonLineAttrs< keyof typeof IMarkCommonArcLabelPosition, MarkCommonLineAnimationType > & { + /** + * 将辅助线的类型设置为 'arc-line',即弧线 + */ type?: 'arc-line'; /** * 弧线中心位置 @@ -286,6 +394,9 @@ export type MarkArcLineAttrs = MarkCommonLineAttrs< * 弧线终点角度(弧度) */ endAngle: number; + /** + * 设置弧线的样式 + */ lineStyle?: IArcGraphicAttribute; }; @@ -299,6 +410,9 @@ export type MarkAreaAttrs = MarkerAttrs & { * 标签 */ label?: { + /** + * 设置标签的位置 + */ position?: keyof typeof IMarkAreaLabelPosition; /** * 当 mark 配置了 limitRect 之后,label 是否自动调整位置 @@ -310,7 +424,9 @@ export type MarkAreaAttrs = MarkerAttrs & { * area的样式 */ areaStyle?: IPolygonAttribute; - + /** + * 设置标注区域在各种状态下的样式 + */ state?: CommonMarkAreaState; }; @@ -343,6 +459,9 @@ export type MarkArcAreaAttrs = MarkerAttrs & { * 标签 */ label?: { + /** + * 标签的位置 + */ position?: keyof typeof IMarkCommonArcLabelPosition; /** * 当 mark 配置了 limitRect 之后,label 是否自动调整位置 @@ -355,7 +474,9 @@ export type MarkArcAreaAttrs = MarkerAttrs & { * area的样式 */ areaStyle?: IArcGraphicAttribute; - + /** + * 辅助区域这种状态下各个图元的样式设置 + */ state?: CommonMarkAreaState; }; @@ -365,6 +486,9 @@ export type IItemContent = IMarkRef & { * Tips: 保留'richText'与之前的定义做兼容 */ type?: 'symbol' | 'text' | 'image' | 'richText' | 'custom'; + /** + * 设置标注的位置 + */ position?: keyof typeof IMarkPointItemPosition; /** * x 方向偏移量 @@ -405,6 +529,9 @@ export type IItemContent = IMarkRef & { export type IItemLine = { /** TODO:'type-opo' */ type?: 'type-s' | 'type-do' | 'type-po' | 'type-op' | 'type-arc'; + /** + * 是否展示该标注 + */ visible?: boolean; /** * 当type为type-arc时生效, 数值决定曲率, 符号决定法向, 不能等于0 @@ -415,7 +542,13 @@ export type IItemLine = { * 垂直于引导线的装饰线,参考案例: https://observablehq.com/@mikelotis/edmonton-population-history-line-chart */ decorativeLine?: { + /** + * 是否显示引导线的装饰线 + */ visible?: boolean; + /** + * 装饰线的长度 + */ length?: number; }; } & Omit; @@ -454,8 +587,13 @@ export type MarkPointAttrs = Omit, 'labelSty * @default 20 */ size?: number; + /** + * 被标注内容的样式设置 + */ style?: ISymbol; }; - + /** + * 标注点各个状态下的样式 + */ state?: MarkPointState; } & BaseMarkerAnimation; diff --git a/packages/vrender-components/src/poptip/type.ts b/packages/vrender-components/src/poptip/type.ts index e897fd8da..dc580011e 100644 --- a/packages/vrender-components/src/poptip/type.ts +++ b/packages/vrender-components/src/poptip/type.ts @@ -24,21 +24,33 @@ type StateStyle = { }; export type PopTipAttributes = { - /** 位置,参考arco design */ + /** + * 弹出框的方位,有 12 个方位可供选择 + */ position?: 'auto' | 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'left' | 'lt' | 'lb' | 'right' | 'rt' | 'rb'; /** * 标题内容,如果需要进行换行,则使用数组形式,如 ['abc', '123'] */ title?: string | string[] | number | number[]; - /** 标题样式 */ + /** + * 标题样式 + */ titleStyle?: Partial; + /** + * 标题的格式化方法 + */ titleFormatMethod?: (t: string | string[] | number | number[]) => string | string[] | number | number[]; /** * 内容文本,如果需要进行换行,则使用数组形式,如 ['abc', '123'] */ content?: string | string[] | number | number[]; - /** 内容文本样式 */ + /** + * 内容文本样式 + */ contentStyle?: Partial; + /** + * 内容的格式化方法 + */ contentFormatMethod?: (t: string | string[] | number | number[]) => string | string[] | number | number[]; /** * 标题与内容的间距 @@ -63,10 +75,17 @@ export type PopTipAttributes = { */ maxWidth?: number; - // 最大宽度比例 + /** + * 最大宽度比例 + */ maxWidthPercent?: number; - + /** + * 是否展示 + */ visible?: boolean; + /** + * 自定义的展示逻辑 + */ visibleFunc?: (graphic: IGraphic) => boolean; state?: StateStyle; dx?: number; diff --git a/packages/vrender-core/CHANGELOG.json b/packages/vrender-core/CHANGELOG.json index 4b4ed0940..d9c98272d 100644 --- a/packages/vrender-core/CHANGELOG.json +++ b/packages/vrender-core/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@visactor/vrender-core", "entries": [ + { + "version": "0.21.9", + "tag": "@visactor/vrender-core_v0.21.9", + "date": "Mon, 13 Jan 2025 03:23:50 GMT", + "comments": { + "none": [ + { + "comment": "feat: change effect for connectedType, closed #1660 " + }, + { + "comment": "fix: fix issue with animate error when graphic.stage is null" + } + ] + } + }, { "version": "0.21.8", "tag": "@visactor/vrender-core_v0.21.8", diff --git a/packages/vrender-core/CHANGELOG.md b/packages/vrender-core/CHANGELOG.md index 14d8f20a0..7050da819 100644 --- a/packages/vrender-core/CHANGELOG.md +++ b/packages/vrender-core/CHANGELOG.md @@ -1,6 +1,14 @@ # Change Log - @visactor/vrender-core -This log was last generated on Mon, 06 Jan 2025 11:07:36 GMT and should not be manually modified. +This log was last generated on Mon, 13 Jan 2025 03:23:50 GMT and should not be manually modified. + +## 0.21.9 +Mon, 13 Jan 2025 03:23:50 GMT + +### Updates + +- feat: change effect for connectedType, closed #1660 +- fix: fix issue with animate error when graphic.stage is null ## 0.21.8 Mon, 06 Jan 2025 11:07:36 GMT diff --git a/packages/vrender-core/package.json b/packages/vrender-core/package.json index 0c5e24ea7..c4812984a 100644 --- a/packages/vrender-core/package.json +++ b/packages/vrender-core/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender-core", - "version": "0.21.8", + "version": "0.21.9", "description": "", "sideEffects": [ "./src/modules.ts", diff --git a/packages/vrender-core/src/animate/animate.ts b/packages/vrender-core/src/animate/animate.ts index 84d42484c..7edd14f34 100644 --- a/packages/vrender-core/src/animate/animate.ts +++ b/packages/vrender-core/src/animate/animate.ts @@ -200,7 +200,7 @@ export class Animate implements IAnimate { slience?: boolean ) { this.id = id; - this.timeline = timeline; + this.timeline = timeline || defaultTimeline; this.status = AnimateStatus.INITIAL; this.tailAnimate = new SubAnimate(this); this.subAnimates = [this.tailAnimate]; diff --git a/packages/vrender-core/src/common/render-area.ts b/packages/vrender-core/src/common/render-area.ts index ff2133707..743c231a2 100644 --- a/packages/vrender-core/src/common/render-area.ts +++ b/packages/vrender-core/src/common/render-area.ts @@ -24,16 +24,8 @@ export function drawAreaSegments( offsetY?: number; offsetZ?: number; direction?: IDirection; - drawConnect?: boolean; // 是否是绘制connect区域的效果 - mode?: 'none' | 'connect' | 'zero'; - zeroX?: number; - zeroY?: number; } ) { - const { drawConnect = false, mode = 'none' } = params || {}; - if (drawConnect && mode === 'none') { - return; - } // let needMoveTo: boolean = true; const { top, bottom } = segPath; // 如果top和bottom的curves数量不同,那么就跳过 @@ -44,90 +36,26 @@ export function drawAreaSegments( const topList: ICurve[] = []; const bottomList: ICurve[] = []; let lastDefined: boolean = true; - if (drawConnect) { - let defined0 = true; - let lastCurve: ICurve; - let lastBottomCurve: ICurve; - const n = top.curves.length; - top.curves.forEach((curve, i) => { - // step的逻辑 - const bototmCurve = bottom.curves[n - i - 1]; - let currentTopCurve = curve; - let currentBottomCurve = bototmCurve; - if (curve.originP1 === curve.originP2) { - lastCurve = curve; - lastBottomCurve = bototmCurve; - return; - } - if (lastCurve && lastCurve.originP1 === lastCurve.originP2) { - currentTopCurve = lastCurve; - currentBottomCurve = lastBottomCurve; - } - if (curve.defined) { - // 非法变合法需要lineTo,合法变非法需要moveTo,初始非法需要moveTo - if (!defined0) { - topList.push(currentTopCurve); - bottomList.push(currentBottomCurve); - drawAreaConnectBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - defined0 = !defined0; - } + for (let i = 0, n = top.curves.length; i < n; i++) { + const topCurve = top.curves[i]; + if (lastDefined !== topCurve.defined) { + if (lastDefined) { + drawAreaBlock(path, topList, bottomList, params); + topList.length = 0; + bottomList.length = 0; } else { - // 找到合法的点 - const { originP1, originP2 } = curve; - let validTopCurve: ICurve; - let validBottomCurve: ICurve; - if (originP1 && originP1.defined !== false) { - validTopCurve = currentTopCurve; - validBottomCurve = currentBottomCurve; - } else if (originP1 && originP2.defined !== false) { - validTopCurve = curve; - validBottomCurve = bototmCurve; - } - // 合法/(初始)变非法,moveTo - if (defined0) { - defined0 = !defined0; - topList.push(validTopCurve || curve); - bottomList.push(validBottomCurve || bototmCurve); - } else { - // 非法变非法/合法,看情况要不要lineTo - if (validTopCurve) { - // 非法变合法,需要lineTo - defined0 = !defined0; - topList.push(validTopCurve || curve); - bottomList.push(validBottomCurve || bototmCurve); - drawAreaConnectBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - } - } + topList.push(topCurve); + bottomList.push(bottom.curves[n - i - 1]); } - lastCurve = curve; - }); - drawAreaConnectBlock(path, topList, bottomList, params); - } else { - for (let i = 0, n = top.curves.length; i < n; i++) { - const topCurve = top.curves[i]; - if (lastDefined !== topCurve.defined) { - if (lastDefined) { - drawAreaBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - } else { - topList.push(topCurve); - bottomList.push(bottom.curves[n - i - 1]); - } - lastDefined = !lastDefined; - } else { - if (lastDefined) { - topList.push(topCurve); - bottomList.push(bottom.curves[n - i - 1]); - } + lastDefined = !lastDefined; + } else { + if (lastDefined) { + topList.push(topCurve); + bottomList.push(bottom.curves[n - i - 1]); } } - drawAreaBlock(path, topList, bottomList, params); } + drawAreaBlock(path, topList, bottomList, params); return; } @@ -159,7 +87,7 @@ export function drawAreaSegments( let lastDefined: boolean = true; const topList: ICurve[] = []; const bottomList: ICurve[] = []; - let defined0 = true; + const defined0 = true; let lastTopCurve: ICurve; let lastBottomCurve: ICurve; for (let i = 0, n = top.curves.length; i < n; i++) { @@ -171,112 +99,50 @@ export function drawAreaSegments( } drawedLengthUntilLast += curCurveLength; - if (drawConnect) { - // step的逻辑 - const bototmCurve = bottom.curves[n - i - 1]; - let currentTopCurve = topCurve; - let currentBottomCurve = bototmCurve; - if (topCurve.originP1 === topCurve.originP2) { - lastTopCurve = topCurve; - lastBottomCurve = bototmCurve; - continue; - } - if (lastTopCurve && lastTopCurve.originP1 === lastTopCurve.originP2) { - currentTopCurve = lastTopCurve; - currentBottomCurve = lastBottomCurve; - } - if (topCurve.defined) { - // 非法变合法需要lineTo,合法变非法需要moveTo,初始非法需要moveTo - if (!defined0) { - topList.push(currentTopCurve); - bottomList.push(currentBottomCurve); - drawAreaConnectBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - defined0 = !defined0; - } + let tc: ICurve | null = null; + let bc: ICurve | null = null; + if (lastDefined !== topCurve.defined) { + if (lastDefined) { + drawAreaBlock(path, topList, bottomList, params); + topList.length = 0; + bottomList.length = 0; } else { - // 找到合法的点 - const { originP1, originP2 } = topCurve; - let validTopCurve: ICurve; - let validBottomCurve: ICurve; - if (originP1 && originP1.defined !== false) { - validTopCurve = currentTopCurve; - validBottomCurve = currentBottomCurve; - } else if (originP1 && originP2.defined !== false) { - validTopCurve = topCurve; - validBottomCurve = bototmCurve; - } - // 合法/(初始)变非法,moveTo - if (defined0) { - defined0 = !defined0; - topList.push(validTopCurve || topCurve); - bottomList.push(validBottomCurve || bototmCurve); - } else { - // 非法变非法/合法,看情况要不要lineTo - if (validTopCurve) { - // 非法变合法,需要lineTo - defined0 = !defined0; - topList.push(validTopCurve || topCurve); - bottomList.push(validBottomCurve || bototmCurve); - drawAreaConnectBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - } - } + tc = topCurve; + bc = bottom.curves[n - i - 1]; } - lastTopCurve = topCurve; - // drawAreaBlock(path, topList, bottomList, params); + lastDefined = !lastDefined; } else { - let tc: ICurve | null = null; - let bc: ICurve | null = null; - if (lastDefined !== topCurve.defined) { - if (lastDefined) { - drawAreaBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - } else { - tc = topCurve; - bc = bottom.curves[n - i - 1]; - } - lastDefined = !lastDefined; - } else { - if (lastDefined) { - tc = topCurve; - bc = bottom.curves[n - i - 1]; - } + if (lastDefined) { + tc = topCurve; + bc = bottom.curves[n - i - 1]; } + } - if (tc && bc) { - if (percent < 1) { - if (tc.p2 && tc.p3) { - tc = divideCubic(tc as ICubicBezierCurve, percent)[0]; - } else { - tc = divideLinear(tc as ILineCurve, percent)[0]; - } - if (bc.p2 && bc.p3) { - bc = divideCubic(bc as ICubicBezierCurve, 1 - percent)[1]; - } else { - bc = divideLinear(bc as ILineCurve, 1 - percent)[1]; - } + if (tc && bc) { + if (percent < 1) { + if (tc.p2 && tc.p3) { + tc = divideCubic(tc as ICubicBezierCurve, percent)[0]; + } else { + tc = divideLinear(tc as ILineCurve, percent)[0]; + } + if (bc.p2 && bc.p3) { + bc = divideCubic(bc as ICubicBezierCurve, 1 - percent)[1]; + } else { + bc = divideLinear(bc as ILineCurve, 1 - percent)[1]; } - tc.defined = lastDefined; - bc.defined = lastDefined; - topList.push(tc); - bottomList.push(bc); } - - tc = null; - bc = null; + tc.defined = lastDefined; + bc.defined = lastDefined; + topList.push(tc); + bottomList.push(bc); } - } - if (drawConnect) { - drawAreaConnectBlock(path, topList, bottomList, params); - } else { - drawAreaBlock(path, topList, bottomList, params); + tc = null; + bc = null; } + drawAreaBlock(path, topList, bottomList, params); + // const totalLength = segPath.tryUpdateLength(); // // 直到上次绘制的长度 @@ -303,39 +169,6 @@ export function drawAreaSegments( // } } -function drawAreaConnectBlock( - path: IPath2D, - topList: ICurve[], - bottomList: ICurve[], - params?: { - offsetX?: number; - offsetY?: number; - offsetZ?: number; - mode?: 'none' | 'connect' | 'zero'; - zeroX?: number; - zeroY?: number; - } -) { - if (topList.length < 2) { - return; - } - const { offsetX = 0, offsetY = 0, offsetZ = 0, mode } = params || {}; - let curve = topList[0]; - // mode不支持zero - path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); - curve = topList[topList.length - 1]; - let end = curve.p3 || curve.p1; - path.lineTo(end.x + offsetX, end.y + offsetY, offsetZ); - - curve = bottomList[bottomList.length - 1]; - path.lineTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); - curve = bottomList[0]; - end = curve.p3 || curve.p1; - path.lineTo(end.x + offsetX, end.y + offsetY, offsetZ); - - path.closePath(); -} - function drawAreaBlock( path: IPath2D, topList: ICurve[], diff --git a/packages/vrender-core/src/common/render-curve.ts b/packages/vrender-core/src/common/render-curve.ts index b22abd04e..6c870ab9a 100644 --- a/packages/vrender-core/src/common/render-curve.ts +++ b/packages/vrender-core/src/common/render-curve.ts @@ -82,7 +82,7 @@ export function drawSegments( offsetY?: number; offsetZ?: number; drawConnect?: boolean; // 是否是绘制connect区域的效果 - mode?: 'none' | 'connect' | 'zero'; + mode?: 'none' | 'connect'; zeroX?: number; zeroY?: number; } diff --git a/packages/vrender-core/src/common/segment/step.ts b/packages/vrender-core/src/common/segment/step.ts index 3fd0db4eb..f6981e038 100644 --- a/packages/vrender-core/src/common/segment/step.ts +++ b/packages/vrender-core/src/common/segment/step.ts @@ -87,7 +87,11 @@ export class Step implements ICurvedSegment { this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false, p); } else { const x1 = this._x * (1 - this._t) + x * this._t; - this.context.lineTo(x1, this._y, this._lastDefined !== false && p.defined !== false, this.lastPoint); + if (this._t === 0.5) { + this.context.lineTo(x1, this._y, this._lastDefined !== false, this.lastPoint); + } else { + this.context.lineTo(x1, this._y, this._lastDefined !== false && p.defined !== false, this.lastPoint); + } this.context.lineTo(x1, y, this._lastDefined !== false && p.defined !== false, p); } break; diff --git a/packages/vrender-core/src/graphic/line.ts b/packages/vrender-core/src/graphic/line.ts index 20cf0be12..9e188355c 100644 --- a/packages/vrender-core/src/graphic/line.ts +++ b/packages/vrender-core/src/graphic/line.ts @@ -82,7 +82,7 @@ export class Line extends Graphic implements ILine { const { points = lineTheme.points, connectedType } = attribute; const b = aabbBounds; points.forEach(p => { - if (p.defined !== false || connectedType === 'zero' || connectedType === 'connect') { + if (p.defined !== false || connectedType === 'connect') { b.add(p.x, p.y); } }); @@ -98,7 +98,7 @@ export class Line extends Graphic implements ILine { const b = aabbBounds; segments.forEach(s => { s.points.forEach(p => { - if (p.defined !== false || connectedType === 'zero' || connectedType === 'connect') { + if (p.defined !== false || connectedType === 'connect') { b.add(p.x, p.y); } }); diff --git a/packages/vrender-core/src/interface/color.ts b/packages/vrender-core/src/interface/color.ts index e824440d8..960186de9 100644 --- a/packages/vrender-core/src/interface/color.ts +++ b/packages/vrender-core/src/interface/color.ts @@ -1,42 +1,127 @@ interface IGradientStop { + /** + * 颜色偏移量, 0-1的值 + */ offset: number; + /** + * 颜色值 + */ color: string; } export type IGradientColor = ILinearGradient | IRadialGradient | IConicalGradient; +/** + * 线性渐变色 + */ export interface ILinearGradient { + /** + * 渐变色的类型设置为 'linear',即线形渐变 + */ gradient: 'linear'; + /** + * 渐变色的起点x坐标,0-1的值,相对于图形包围盒x方向取值的比例值 + */ x0?: number; + /** + * 渐变色的起点y坐标,0-1的值,相对于图形包围盒y方向取值的比例值 + */ y0?: number; + /** + * 渐变色的终点x坐标,0-1的值,相对于图形包围盒x方向取值的比例值 + */ x1?: number; + /** + * 渐变色的终点y坐标,0-1的值,相对于图形包围盒y方向取值的比例值 + */ y1?: number; + /** + * 渐变色的颜色数组,每个颜色对象包含一个偏移量(0-1)和一个颜色值 + */ stops: IGradientStop[]; } +/** + * 径向渐变色 + */ export interface IRadialGradient { + /** + * 渐变色的类型设置为 'radial',即环形渐变 + */ gradient: 'radial'; + /** + * 径向渐变的起点的x坐标 + */ x0?: number; + /** + * 径向渐变的起点的y坐标 + */ y0?: number; + /** + * 径向渐变的终点的x坐标 + */ x1?: number; + /** + * 径向渐变的终点的x坐标 + */ y1?: number; + /** + * 径向渐变的起点的半径 + */ r0?: number; + /** + * 径向渐变的终点的半径 + */ r1?: number; + /** + * 径向渐变的颜色数组,每个颜色对象包含一个偏移量(0-1)和一个颜色值 + */ stops: IGradientStop[]; } +/** + * 环形渐变色/锥形渐变色 + */ export interface IConicalGradient { + /** + * 渐变色的类型设置为 'conical',即环形渐变 + */ gradient: 'conical'; + /** + * 锥形渐变的开始角度 + */ startAngle?: number; + /** + * 锥形渐变的开始角度 + */ endAngle?: number; + /** + * 锥形渐变的中心点x坐标 + */ x?: number; + /** + * 锥形渐变的中心点y坐标 + */ y?: number; + /** + * 锥形渐变的颜色 + */ stops: IGradientStop[]; } export interface IColorStop { + /** + * 颜色偏移量, 0-1的值 + */ offset: number; + /** + * 颜色值 + */ color: string; } +/** + * 颜色类型, + * 支持字符串、线性渐变、径向渐变、锥形渐变 + */ export type IColor = string | ILinearGradient | IRadialGradient | IConicalGradient; diff --git a/packages/vrender-core/src/interface/graphic.ts b/packages/vrender-core/src/interface/graphic.ts index 6781ff69a..1a57e132f 100644 --- a/packages/vrender-core/src/interface/graphic.ts +++ b/packages/vrender-core/src/interface/graphic.ts @@ -13,10 +13,19 @@ import type { IFace3d } from './graphic/face3d'; import type { IPickerService } from './picker'; type IStrokeSeg = { - start: number; // 百分比 - // end和length二选一 - end: number; // 百分比 - length: number; // 像素长度 + /** + * 百分比 + */ + start: number; + /** + * 百分比 + * end和length二选一 + */ + end: number; + /** + * 像素长度 + */ + length: number; }; // TODO 最后加一个any @@ -38,7 +47,8 @@ export type GraphicType = | 'shadowroot' | 'polygon' | 'pyramid3d' - | 'glyph'; + | 'glyph' + | string; // Cursor style // See: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor @@ -81,57 +91,172 @@ export type Cursor = | 'zoom-out'; export type ITransform = { + /** + * x坐标 + */ x: number; + /** + * y坐标 + */ y: number; + /** + * z坐标 + */ z: number; + /** + * x方向偏移量 + */ dx: number; + /** + * y方向偏移量 + */ dy: number; + /** + * z方向偏移量 + */ dz: number; + /** + * x方向的滚动值 + */ scrollX: number; + /** + * y方向的滚动值 + */ scrollY: number; + /** + * x方向的缩放值 + */ scaleX: number; + /** + * y方向的缩放值 + */ scaleY: number; + /** + * z方向的缩放值 + */ scaleZ: number; + /** + * 绕z轴的转角,即xy平面上的旋转角度 + */ angle: number; + /** + * 绕x轴的转角 + */ alpha: number; + /** + * 绕y轴的转角 + */ beta: number; + /** + * 应用缩放的中心 + */ scaleCenter: [number | string, number | string]; - anchor: [number | string, number | string]; // 基于AABB的锚点位置,用于简单的定位某些path - anchor3d: [number | string, number | string, number] | [number | string, number | string]; // 3d的锚点位置 + /** + * 基于AABB的锚点位置,用于简单的定位某些path + */ + anchor: [number | string, number | string]; + /** + * 3d的锚点位置 + */ + anchor3d: [number | string, number | string, number] | [number | string, number | string]; + /** + * 处理矩阵,在正常计算完变换矩阵之后,会将该矩阵乘到变换矩阵上得到最终的变换矩阵 + */ postMatrix: IMatrix; }; export type IFillType = boolean | string | IColor; export type IFillStyle = { + /** + * 图形的填充透明度 + */ fillOpacity: number; + /** + * 图形模糊效果程度 + */ shadowBlur: number; + /** + * 图形的阴影颜色 + */ shadowColor: string; + /** + * 阴影水平偏移距离 + */ shadowOffsetX: number; + /** + * 阴影垂直偏移距离 + */ shadowOffsetY: number; + /** + * 图形的填充颜色 + */ fill: IFillType; }; export type ILayout = { + /** + * 设置对齐方式 + */ alignSelf: 'auto' | 'flex-start' | 'flex-end' | 'center'; }; export type IBorderStyle = Omit & { + /** + * 边距离边缘的距离 + */ distance: number | string; + /** + * 是否显示边框,默认是不显示的 + */ visible?: boolean; }; export type IStrokeType = boolean | string | IColor | null; export type IStrokeStyle = { + /** + * 外部边框的样式配置,默认不展示外部边框 + */ outerBorder: Partial; + /** + * 内部边框的样式配置 + */ innerBorder: Partial; + /** + * 描边的透明度 + */ strokeOpacity: number; + /** + * 设置线条虚线样式的属性,它通过定义实线和空白的交替长度来创建虚线效果 + */ lineDash: number[]; + + /** + * 设置虚线样式的起始偏移量 + */ lineDashOffset: number; + + /** + * 设置线条的宽度 + */ lineWidth: number; + + /** + * 设置线条末端的样式 + */ lineCap: CanvasLineCap; + + /** + * 设置线条拐角的样式 + */ lineJoin: CanvasLineJoin; + + /** + * 设置线条拐角处的斜接限制 + */ miterLimit: number; - // 描边的boundsBuffer,用于控制bounds的buffer + /** + * 描边的boundsBuffer,用于控制bounds的buffer + */ strokeBoundsBuffer: number; /** * stroke - true 全描边 @@ -156,8 +281,13 @@ export type IStrokeStyle = { type TextureType = 'circle' | 'diamond' | 'rect' | 'vertical-line' | 'horizontal-line' | 'bias-lr' | 'bias-rl' | 'grid'; export type IConnectedStyle = { - // 连接,取零或者断开 - connectedType: 'connect' | 'zero' | 'none'; + /** + * 连接,取零或者断开 + */ + connectedType: 'connect' | 'none'; + /** + * 连接线的样式配置 + */ connectedStyle: { stroke: IStrokeStyle['stroke']; strokeOpacity: IStrokeStyle['strokeOpacity']; @@ -185,8 +315,17 @@ export type IBackgroundConfig = { type IBackgroundType = string | HTMLImageElement | HTMLCanvasElement | IBackgroundConfig; export interface SimpleDomStyleOptions { - width: number; // 容器的宽度 - height: number; // 容器的高度 + /** + * 容器的宽度 + */ + width: number; + /** + * 容器的高度 + */ + height: number; + /** + * 容器的样式设置 + */ style?: | string | Record @@ -198,13 +337,41 @@ export interface SimpleDomStyleOptions { } export interface CommonDomOptions { + /** + * 全局唯一的id + */ id?: string; - container: string | HTMLElement | null; // id或者dom + /** + * 容器元素的id或者dom元素 + */ + container: string | HTMLElement | null; + /** + * 是否显示 + */ visible?: boolean; + /** + * 是否支持事件冒泡 + */ pointerEvents?: boolean | string; - // 可穿透的事件列表 - // @since 0.21.2 + /** + * 可穿透的事件列表 + * @since 0.21.2 + */ penetrateEventList?: string[]; + /** + * 定位类型 + * 'position' - 根据挂载图形节点的坐标也就是x,y进行定位 + * 'boundsLeftTop' - 定位到挂载图形节点bounds的左上角 + * 'left' - 定位到挂载图形节点bounds 的左侧 + * 'right' - 定位到挂载图形节点bounds 的右侧 + * 'bottom' - 定位到挂载图形节点bounds 的底部 + * 'top' - 定位到挂载图形节点bounds 的顶部 + * 'center' - 定位到挂载图形节点bounds 的中心 + * 'top-left' - 定位到挂载图形节点bounds 的左上角 + * 'top-right' - 定位到挂载图形节点bounds 的右上角 + * 'bottom-left' - 定位到挂载图形节点bounds 的左下角 + * 'bottom-right' - 定位到挂载图形节点bounds 的右下角 + */ anchorType?: 'position' | 'boundsLeftTop' | BoundsAnchorType; } @@ -212,50 +379,127 @@ export type IGraphicStyle = ILayout & IFillStyle & IStrokeStyle & IPickStyle & { + /** + * 强制设置的bounds宽度,主要用于使用html或者react展示图形的时候,设置一个固定的宽度 + */ forceBoundsWidth: number | (() => number) | undefined; + /** + * 强制设置的bounds高度,主要用于使用html或者react展示图形的时候,设置一个固定的高度 + */ forceBoundsHeight: number | (() => number) | undefined; + /** + * 透明度,会同时影响填充和描边 + */ opacity: number; + /** + * 影子节点 + */ shadowGraphic?: IGraphic | undefined; - backgroundMode: 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat'; // 填充模式(与具体图元有关) - backgroundFit: boolean; // 是否正好填充,只在repeat-x或者repeat-y以及no-repeat的时候生效 + /** + * 背景填充模式(与具体图元有关) + */ + backgroundMode: 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat'; + /** + * 是否正好填充,只在repeat-x或者repeat-y以及no-repeat的时候生效 + */ + backgroundFit: boolean; + /** + * 背景圆角半径 + */ backgroundCornerRadius: number | number[]; + /** + * 背景透明度 + */ backgroundOpacity: number; + /** + * 背景,支持颜色字符串、html image元素、html canvas元素 + */ background: | IBackgroundType | { + /** + * 背景,支持颜色字符串、html image元素、html canvas元素 + */ background: IBackgroundType; + /** + * 背景的x方向偏移量 + */ dx?: number; + /** + * 背景的y方向偏移量 + */ dy?: number; + /** + * 背景宽度 + */ width?: number; + /** + * 背景高度 + */ height?: number; + /** + * 背景的x坐标 + */ x?: number; + /** + * 背景的y坐标 + */ y?: number; } | null; // 背景,可以与fill同时存在 - texture: TextureType | string; // 纹理 - textureColor: string; // 纹理颜色 - textureSize: number; // 纹理大小 - texturePadding: number; // 纹理间隙 + /** + * 纹理的类型 + */ + texture: TextureType | string; + /** + * 纹理的颜色 + */ + textureColor: string; + /** + * 纹理的大小 + */ + textureSize: number; + /** + * 纹理的间隙 + */ + texturePadding: number; + blur: number; - cursor: Cursor | null; // 鼠标样式 + /** + * 设置图形对应的鼠标样式 + */ + cursor: Cursor | null; renderStyle?: 'default' | 'rough' | any; - // HTML的dom或者string + /** + * HTML的dom或者string + */ html: | ({ - dom: string | HTMLElement; // dom字符串或者dom + /** + * dom字符串或者dom + */ + dom: string | HTMLElement; } & SimpleDomStyleOptions & CommonDomOptions) | null; + /** + * 使用react元素渲染内容 + */ react: | ({ - element: any; // react场景节点 + /** + * react场景节点 + */ + element: any; } & SimpleDomStyleOptions & CommonDomOptions) | null; }; export type IPickStyle = { - // 给stroke模式的pick额外加的buffer,用于外界控制stroke区域的pick范围 + /** + * 给stroke模式的pick额外加的buffer,用于外界控制stroke区域的pick范围 + */ pickStrokeBuffer: number; }; @@ -269,7 +513,9 @@ export type IGraphicAttribute = IDebugType & * stroke百分比 */ strokeSeg: IStrokeSeg | null; - // 包围盒的padding + /** + * 包围盒的padding + */ boundsPadding: number | number[]; /** * 选择模式,精确模式,粗糙模式(包围盒模式),自定义模式 @@ -305,6 +551,9 @@ export type IGraphicAttribute = IDebugType & * @default true */ visible: boolean; + /** + * 分组下的层级,层级越小越先绘制 + */ zIndex: number; layout: any; /** @@ -319,14 +568,26 @@ export type IGraphicAttribute = IDebugType & keepDirIn3d?: boolean; shadowRootIdx: number; shadowPickMode?: 'full' | 'graphic'; + /** + * 全局范围的层级,设置了这个属性的图形,会提取到交互层进行渲染 + */ globalZIndex: number; + /** + * canvas 的合成方式 + */ globalCompositeOperation: CanvasRenderingContext2D['globalCompositeOperation'] | ''; - // 完全支持滚动 | 完全不支持滚动 | 支持x方向的滚动 | 支持y方向的滚动 + /** + * 完全支持滚动 | 完全不支持滚动 | 支持x方向的滚动 | 支持y方向的滚动 + */ overflow: 'scroll' | 'hidden' | 'scroll-x' | 'scroll-y'; - // 绘制fill和stroke的顺序,为0表示fill先绘制,1表示stroke先绘制 + /** + * 绘制fill和stroke的顺序,为0表示fill先绘制,1表示stroke先绘制 + */ fillStrokeOrder: number; - // @since 0.20.15 - // 保持stroke的scale,默认为false,为true的话stroke显示的宽度会随着scale变化 + /** + * @since 0.20.15 + * 保持stroke的scale,默认为false,为true的话stroke显示的宽度会随着scale变化 + */ keepStrokeScale: boolean; }; diff --git a/packages/vrender-core/src/interface/graphic/richText.ts b/packages/vrender-core/src/interface/graphic/richText.ts index 01d788a52..8568e00f9 100644 --- a/packages/vrender-core/src/interface/graphic/richText.ts +++ b/packages/vrender-core/src/interface/graphic/richText.ts @@ -5,20 +5,63 @@ import type { IImage, IImageGraphicAttribute } from './image'; import type { ITextGraphicAttribute } from './text'; export type IRichTextAttribute = { + /** + * 富文本的总宽度 + */ width: number; + /** + * 富文本的总高度 + */ height: number; + /** + * 是否可编辑 + */ editable: boolean; + /** + * 文本超长的时候是否显示省略字符串 + * 1. boolean类型,true 表示将截断后的省略字符串设置为..., false 表示不显示省略字符串 + * 2. string类型,表示显示省略字符串,并将省略字符串设置为指定的值 + */ ellipsis: boolean | string; + /** + * 文字换行类型 + */ wordBreak: RichTextWordBreak; + /** + * 文字垂直方向 + */ verticalDirection: RichTextVerticalDirection; + /** + * 富文本的最大高度,超过这个高度根据ellipsis的配置展示省略字符串或者直接截断 + */ maxHeight: number; + /** + * 富文本的最大宽度,超过这个宽度根据ellipsis的配置展示省略字符串或者直接截断 + */ maxWidth: number; + /** + * 文字对齐方式 + */ textAlign: RichTextGlobalAlignType; + /** + * 文字基线 + */ textBaseline: RichTextGlobalBaselineType; + /** + * 富文本的布局方向 + */ layoutDirection: RichTextLayoutDirectionType; + /** + * 富文本的内容配置 + */ textConfig: IRichTextCharacter[]; - // 是否不自动每行截断 + /** + * 不自动换行,仅当用户设置了换行符的时候才换行 + */ disableAutoWrapLine: boolean; + /** + * 是否强制单行显示 + */ singleLine: boolean; }; @@ -36,81 +79,230 @@ export type RichTextTextDecoration = 'none' | 'underline' | 'line-through'; export type RichTextScript = 'normal' | 'sub' | 'super'; export type IRichTextBasicCharacter = { + /** + * 行高 + */ lineHeight?: number | string; - textAlign?: CanvasTextAlign; // left, right, center + /** + * 文字对齐方式 + * left, right, center + */ + textAlign?: CanvasTextAlign; + /** + * 文字基线 + */ textBaseline?: CanvasTextBaseline; + /** + * 文字方向 + */ direction?: RichTextLayoutDirectionType; }; +/** + * 富文本段落为文本类型时候的配置 + */ export type IRichTextParagraphCharacter = IRichTextBasicCharacter & { + /** + * 文本内容 + */ text: string | number; + /** + * 富文本片段的字体大小 + */ fontSize?: number; + /** + * 富文本片段的字体类型 + */ fontFamily?: string; + /** + * 富文本片段的文字颜色 + */ fill?: IColor | boolean; + /** + * 富文本片段的文字描边颜色 + */ stroke?: IColor | boolean; + /** + * 富文本片段的文字字重 + */ fontWeight?: string; + /** + * 富文本片段的文字描边宽度 + */ lineWidth?: number; // lineHeight?: number; - fontStyle?: RichTextFontStyle; // normal, italic, oblique - textDecoration?: RichTextTextDecoration; // none, underline, line-through + /** + * 富文本片段的文字斜体设置,支持以下属性 + * normal, italic, oblique + */ + fontStyle?: RichTextFontStyle; + /** + * 富文本片段的文字中划线设置,支持以下属性 + * none, underline, line-through + */ + textDecoration?: RichTextTextDecoration; // textAlign?: RichTextTextAlign; // left, right, center script?: RichTextScript; // normal, sub, super + /** + * 富文本片段的文字下划线设置,是否显示下划线 + */ underline?: boolean; + /** + * 富文本片段的文字中划线设置,是否显示中划线 + */ lineThrough?: boolean; + /** + * 富文本片段的透明度 + */ opacity?: number; + /** + * 富文本片段的文字填充透明度 + */ fillOpacity?: number; + /** + * 富文本片段的文字描边透明度 + */ strokeOpacity?: number; // direction?: RichTextLayoutDirectionType; }; export type IRichTextImageCharacter = IRichTextBasicCharacter & { - // 图片基础属性 + /** + * 设置图片的内容, + * 支持三种格式: + * 1. 图片的url + * 2. 图片的Image对象 + * 3. 图片的Canvas对象 + */ image: string | HTMLImageElement | HTMLCanvasElement; + /** + * 图片的宽度 + */ width: number; + /** + * 图片的高度 + */ height: number; // hover相关属性 // backgroundShow?: boolean; // 是否显示background + /** + * 背景的展示模式,支持以下属性 + * always: 一直显示 + * hover: 鼠标hover时显示 + */ backgroundShowMode?: 'always' | 'hover'; - backgroundFill?: boolean | IColor; // 背景矩形填充颜色 - backgroundFillOpacity?: number; // 背景矩形填充透明度 - backgroundStroke?: boolean | IColor; // 背景矩形边框颜色 - backgroundStrokeOpacity?: number; // 背景矩形边框透明度 - backgroundRadius?: number; // 背景矩形圆角 + /** + * 背景矩形填充颜色 + */ + backgroundFill?: boolean | IColor; + /** + * 背景矩形填充透明度 + */ + backgroundFillOpacity?: number; + /** + * 背景矩形边框颜色 + */ + backgroundStroke?: boolean | IColor; + /** + * 背景矩形边框透明度 + */ + backgroundStrokeOpacity?: number; + /** + * 背景矩形圆角 + */ + backgroundRadius?: number; // background size 同时控制了该icon的响应范围 + /** + * 背景矩形的宽度 + */ backgroundWidth?: number; + /** + * 背景矩形的高度 + */ backgroundHeight?: number; - // 唯一标识符 + /** + * 唯一标识符 + */ id?: string; // lineHeight?: number; // textAlign?: RichTextTextAlign; // left, right, center // direction?: RichTextLayoutDirectionType; + /** + * 图片与相邻节点的间距 + */ margin?: number | number[]; funcType?: string; hoverImage?: string | HTMLImageElement | HTMLCanvasElement; }; - +/** + * 富文本的字符类型 + */ export type IRichTextCharacter = IRichTextParagraphCharacter | IRichTextImageCharacter; export type IRichTextIconGraphicAttribute = IImageGraphicAttribute & { + /** + * 唯一id + */ id?: string; + /** + * 背景的展示模式,支持以下属性 + * always: 一直显示 + * hover: 鼠标hover时显示 + * never: 不显示 + */ backgroundShowMode?: 'always' | 'hover' | 'never'; - backgroundFill?: boolean | IColor; // 背景矩形填充颜色 - backgroundFillOpacity?: number; // 背景矩形填充透明度 - backgroundStroke?: boolean | IColor; // 背景矩形边框颜色 - backgroundStrokeOpacity?: number; // 背景矩形边框透明度 - backgroundRadius?: number; // 背景矩形圆角 + /** + * 背景矩形填充颜色 + */ + backgroundFill?: boolean | IColor; + /** + * 背景矩形填充透明度 + */ + backgroundFillOpacity?: number; + /** + * 背景矩形边框颜色 + */ + backgroundStroke?: boolean | IColor; + /** + * 背景矩形边框透明度 + */ + backgroundStrokeOpacity?: number; + /** + * 背景矩形圆角 + */ + backgroundRadius?: number; + /** + * 背景矩形的宽度 + */ backgroundWidth?: number; + /** + * 背景矩形的高度 + */ backgroundHeight?: number; // lineHeight?: number; - textAlign?: CanvasTextAlign; // left, right, center + /** + * 文字对齐方式 + * left, right, center + */ + textAlign?: CanvasTextAlign; + /** + * 文字基线 + * top, middle, bottom + */ textBaseline?: CanvasTextBaseline; + /** + * 文字方向 + * horizontal, vertical + */ direction?: RichTextLayoutDirectionType; - + /** + * 图片与相邻节点的间距 + */ margin?: number | number[]; // backgroundShow?: boolean; diff --git a/packages/vrender-core/src/render/contributions/render/area-render.ts b/packages/vrender-core/src/render/contributions/render/area-render.ts index aed87ba04..71748d6f0 100644 --- a/packages/vrender-core/src/render/contributions/render/area-render.ts +++ b/packages/vrender-core/src/render/contributions/render/area-render.ts @@ -195,7 +195,8 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph fillOpacity = areaAttribute.fillOpacity, z = areaAttribute.z, strokeOpacity = areaAttribute.strokeOpacity, - curveTension = areaAttribute.curveTension + curveTension = areaAttribute.curveTension, + connectedType = areaAttribute.connectedType } = area.attribute; const data = this.valid(area, areaAttribute, fillCb, strokeCb); @@ -211,6 +212,13 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph curveType = 'linearClosed'; } + function parsePoint(points: IPointLike[], connectedType: 'none' | 'connect') { + if (connectedType !== 'connect') { + return points; + } + return points.filter(p => p.defined !== false); + } + if (clipRange === 1 && !segments && !points.some(p => p.defined === false) && curveType === 'linear') { return this.drawLinearAreaHighPerformance( area, @@ -250,7 +258,7 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph startPoint.x = lastTopSeg.endX; startPoint.y = lastTopSeg.endY; } - const data = calcLineCache(seg.points, curveType, { + const data = calcLineCache(parsePoint(seg.points, connectedType), curveType, { startPoint, curveTension }); @@ -281,7 +289,7 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph } if (bottomPoints.length > 1) { lastBottomSeg = calcLineCache( - bottomPoints, + parsePoint(bottomPoints, connectedType), curveType === 'stepBefore' ? 'stepAfter' : curveType === 'stepAfter' ? 'stepBefore' : curveType, { curveTension } ); @@ -294,12 +302,12 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph })); } else if (points && points.length) { // 转换points - const topPoints = points; + const topPoints = parsePoint(points, connectedType); const bottomPoints: IPointLike[] = []; - for (let i = points.length - 1; i >= 0; i--) { + for (let i = topPoints.length - 1; i >= 0; i--) { bottomPoints.push({ - x: points[i].x1 ?? points[i].x, - y: points[i].y1 ?? points[i].y + x: points[i].x1 ?? topPoints[i].x, + y: points[i].y1 ?? topPoints[i].y }); } const topCache = calcLineCache(topPoints, curveType, { curveTension }); @@ -456,50 +464,24 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph themeAttribute: IThemeAttribute | IThemeAttribute[] ) => boolean ): boolean { - let ret = false; - ret = - ret || - this._drawSegmentItem( - context, - cache, - fill, - fillOpacity, - stroke, - strokeOpacity, - attribute, - defaultAttribute, - clipRange, - offsetX, - offsetY, - offsetZ, - area, - drawContext, - false, - fillCb, - strokeCb - ); - ret = - ret || - this._drawSegmentItem( - context, - cache, - fill, - fillOpacity, - stroke, - strokeOpacity, - attribute, - defaultAttribute, - clipRange, - offsetX, - offsetY, - offsetZ, - area, - drawContext, - true, - fillCb, - strokeCb - ); - return ret; + return this._drawSegmentItem( + context, + cache, + fill, + fillOpacity, + stroke, + strokeOpacity, + attribute, + defaultAttribute, + clipRange, + offsetX, + offsetY, + offsetZ, + area, + drawContext, + fillCb, + strokeCb + ); } protected _drawSegmentItem( @@ -517,7 +499,6 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph offsetZ: number, area: IArea, drawContext: IDrawContext, - connect: boolean, fillCb?: ( ctx: IContext2d, lineAttribute: Partial, @@ -542,38 +523,6 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph ) { return; } - // 绘制connect区域 - let { connectedType, connectedX, connectedY, connectedStyle } = attribute; - const da: any[] = []; - if (connect) { - if (isArray(defaultAttribute)) { - connectedType = connectedType ?? defaultAttribute[0].connectedType ?? defaultAttribute[1].connectedType; - connectedX = connectedX ?? defaultAttribute[0].connectedX ?? defaultAttribute[1].connectedX; - connectedY = connectedY ?? defaultAttribute[0].connectedY ?? defaultAttribute[1].connectedY; - connectedStyle = connectedStyle ?? defaultAttribute[0].connectedStyle ?? defaultAttribute[1].connectedStyle; - } else { - connectedType = connectedType ?? defaultAttribute.connectedType; - connectedX = connectedX ?? defaultAttribute.connectedX; - connectedY = connectedY ?? defaultAttribute.connectedY; - connectedStyle = connectedStyle ?? defaultAttribute.connectedStyle; - } - - // 如果有非法值就是none - if (connectedType !== 'connect' && connectedType !== 'zero') { - connectedType = 'none'; - } - - if (isArray(defaultAttribute)) { - defaultAttribute.forEach(i => da.push(i)); - } else { - da.push(defaultAttribute); - } - da.push(attribute); - } - - if (connect && connectedType === 'none') { - return false; - } context.beginPath(); @@ -606,11 +555,7 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph offsetX, offsetY, offsetZ, - direction, - drawConnect: connect, - mode: connectedType, - zeroX: connectedX, - zeroY: connectedY + direction }); this.beforeRenderStep( @@ -638,13 +583,7 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph if (fillCb) { fillCb(context, attribute, defaultAttribute); } else if (fillOpacity) { - context.setCommonStyle( - area, - connect ? connectedStyle : attribute, - originX - offsetX, - originY - offsetY, - connect ? da : defaultAttribute - ); + context.setCommonStyle(area, attribute, originX - offsetX, originY - offsetY, defaultAttribute); context.fill(); } } @@ -667,21 +606,11 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph { offsetX, offsetY, - offsetZ, - drawConnect: connect, - mode: connectedType, - zeroX: connectedX, - zeroY: connectedY + offsetZ } ); } - context.setStrokeStyle( - area, - connect ? connectedStyle : attribute, - originX - offsetX, - originY - offsetY, - connect ? da : defaultAttribute - ); + context.setStrokeStyle(area, attribute, originX - offsetX, originY - offsetY, defaultAttribute); context.stroke(); } } diff --git a/packages/vrender-core/src/render/contributions/render/contributions/image-contribution-render.ts b/packages/vrender-core/src/render/contributions/render/contributions/image-contribution-render.ts index 37a95be2d..dd3309aba 100644 --- a/packages/vrender-core/src/render/contributions/render/contributions/image-contribution-render.ts +++ b/packages/vrender-core/src/render/contributions/render/contributions/image-contribution-render.ts @@ -150,7 +150,7 @@ export class DefaultImageRenderContribution extends DefaultRectRenderContributio useStyle: boolean = true; order: number = 0; drawShape( - rect: any, + image: any, context: IContext2d, x: number, y: number, @@ -172,7 +172,7 @@ export class DefaultImageRenderContribution extends DefaultRectRenderContributio ) => boolean ) { return super.drawShape( - rect, + image, context, x, y, diff --git a/packages/vrender-core/src/render/contributions/render/image-render.ts b/packages/vrender-core/src/render/contributions/render/image-render.ts index 9ba01bdce..50c14622d 100644 --- a/packages/vrender-core/src/render/contributions/render/image-render.ts +++ b/packages/vrender-core/src/render/contributions/render/image-render.ts @@ -169,6 +169,7 @@ export class DefaultCanvasImageRender extends BaseRender implements IGra draw(image: IImage, renderService: IRenderService, drawContext: IDrawContext) { const { image: url } = image.attribute; + if (!url || !image.resources) { return; } diff --git a/packages/vrender-core/src/render/contributions/render/line-render.ts b/packages/vrender-core/src/render/contributions/render/line-render.ts index cdbc27e22..ed7730d8d 100644 --- a/packages/vrender-core/src/render/contributions/render/line-render.ts +++ b/packages/vrender-core/src/render/contributions/render/line-render.ts @@ -121,61 +121,6 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph context.stroke(); } } - - // 绘制connect区域 - let { connectedType, connectedX, connectedY, connectedStyle } = attribute; - if (isArray(defaultAttribute)) { - connectedType = connectedType ?? defaultAttribute[0].connectedType ?? defaultAttribute[1].connectedType; - connectedX = connectedX ?? defaultAttribute[0].connectedX ?? defaultAttribute[1].connectedX; - connectedY = connectedY ?? defaultAttribute[0].connectedY ?? defaultAttribute[1].connectedY; - connectedStyle = connectedStyle ?? defaultAttribute[0].connectedStyle ?? defaultAttribute[1].connectedStyle; - } else { - connectedType = connectedType ?? defaultAttribute.connectedType; - connectedX = connectedX ?? defaultAttribute.connectedX; - connectedY = connectedY ?? defaultAttribute.connectedY; - connectedStyle = connectedStyle ?? defaultAttribute.connectedStyle; - } - // 如果有非法值就是none - if (connectedType !== 'connect' && connectedType !== 'zero') { - connectedType = 'none'; - } - if (connectedType !== 'none') { - context.beginPath(); - drawSegments(context.camera ? context : context.nativeContext, cache, clipRange, clipRangeByDimension, { - offsetX, - offsetY, - offsetZ: z, - drawConnect: true, - mode: connectedType, - zeroX: connectedX, - zeroY: connectedY - }); - - const da = []; - if (isArray(defaultAttribute)) { - defaultAttribute.forEach(i => da.push(i)); - } else { - da.push(defaultAttribute); - } - da.push(attribute); - - if (fill !== false) { - if (fillCb) { - fillCb(context, attribute, defaultAttribute); - } else if (fillOpacity) { - context.setCommonStyle(line, connectedStyle, originX - offsetX, originY - offsetY, da); - context.fill(); - } - } - if (stroke !== false) { - if (strokeCb) { - strokeCb(context, attribute, defaultAttribute); - } else if (strokeOpacity) { - context.setStrokeStyle(line, connectedStyle, originX - offsetX, originY - offsetY, da); - context.stroke(); - } - } - } return !!ret; } @@ -266,7 +211,8 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph segments, points, closePath, - curveTension = lineAttribute.curveTension + curveTension = lineAttribute.curveTension, + connectedType = lineAttribute.connectedType } = line.attribute; const data = this.valid(line, lineAttribute, fillCb, strokeCb); @@ -301,6 +247,13 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph } // const { fVisible, sVisible, doFill, doStroke } = data; + function parsePoint(points: IPointLike[], connectedType: 'none' | 'connect') { + if (connectedType === 'none') { + return points; + } + return points.filter(p => p.defined !== false); + } + // 更新cache if (line.shouldUpdateShape()) { const { points, segments } = line.attribute; @@ -335,7 +288,7 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph startPoint.y = lastSeg.endY; startPoint.defined = lastSeg.curves[lastSeg.curves.length - 1].defined; } - const data = calcLineCache(seg.points, curveType, { + const data = calcLineCache(parsePoint(seg.points, connectedType), curveType, { startPoint, curveTension }); @@ -362,7 +315,7 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph line.cache[line.cache.length - 1] && line.cache[line.cache.length - 1].lineTo(startP.x, startP.y, true); } } else if (points && points.length) { - line.cache = calcLineCache(_points, curveType, { curveTension }); + line.cache = calcLineCache(parsePoint(_points, connectedType), curveType, { curveTension }); } else { line.cache = null; line.clearUpdateShapeTag(); diff --git a/packages/vrender-core/src/resource-loader/loader.ts b/packages/vrender-core/src/resource-loader/loader.ts index 55c779829..f40326744 100644 --- a/packages/vrender-core/src/resource-loader/loader.ts +++ b/packages/vrender-core/src/resource-loader/loader.ts @@ -108,9 +108,9 @@ export class ResourceLoader { let data = ResourceLoader.cache.get(url); if (data) { // 存在缓存 - if (data.loadState === 'init' || data.loadState === 'fail') { + if (data.loadState === 'fail') { return Promise.reject(); - } else if (data.loadState === 'loading') { + } else if (data.loadState === 'init' || data.loadState === 'loading') { return data.dataPromise.then(data => data.data); } return Promise.resolve(data.data); diff --git a/packages/vrender-kits/CHANGELOG.json b/packages/vrender-kits/CHANGELOG.json index 52031d5d6..ebdb7e657 100644 --- a/packages/vrender-kits/CHANGELOG.json +++ b/packages/vrender-kits/CHANGELOG.json @@ -1,6 +1,18 @@ { "name": "@visactor/vrender-kits", "entries": [ + { + "version": "0.21.9", + "tag": "@visactor/vrender-kits_v0.21.9", + "date": "Mon, 13 Jan 2025 03:23:50 GMT", + "comments": { + "none": [ + { + "comment": "fix: fix drawShape function in gif-image render " + } + ] + } + }, { "version": "0.21.8", "tag": "@visactor/vrender-kits_v0.21.8", diff --git a/packages/vrender-kits/CHANGELOG.md b/packages/vrender-kits/CHANGELOG.md index 5b8740064..3a86cdf07 100644 --- a/packages/vrender-kits/CHANGELOG.md +++ b/packages/vrender-kits/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @visactor/vrender-kits -This log was last generated on Mon, 06 Jan 2025 11:07:36 GMT and should not be manually modified. +This log was last generated on Mon, 13 Jan 2025 03:23:50 GMT and should not be manually modified. + +## 0.21.9 +Mon, 13 Jan 2025 03:23:50 GMT + +### Updates + +- fix: fix drawShape function in gif-image render ## 0.21.8 Mon, 06 Jan 2025 11:07:36 GMT diff --git a/packages/vrender-kits/package.json b/packages/vrender-kits/package.json index c83a97f37..9c94d8b4e 100644 --- a/packages/vrender-kits/package.json +++ b/packages/vrender-kits/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender-kits", - "version": "0.21.8", + "version": "0.21.9", "description": "", "sideEffects": false, "main": "cjs/index.js", @@ -21,9 +21,10 @@ }, "dependencies": { "@visactor/vutils": "~0.19.3", - "@visactor/vrender-core": "workspace:0.21.8", + "@visactor/vrender-core": "workspace:0.21.9", "@resvg/resvg-js": "2.4.1", - "roughjs": "4.5.2" + "roughjs": "4.5.2", + "gifuct-js": "2.1.2" }, "devDependencies": { "@internal/bundler": "workspace:*", diff --git a/packages/vrender-kits/src/canvas/contributions/lynx/context.ts b/packages/vrender-kits/src/canvas/contributions/lynx/context.ts index 8629efcc5..30f827541 100644 --- a/packages/vrender-kits/src/canvas/contributions/lynx/context.ts +++ b/packages/vrender-kits/src/canvas/contributions/lynx/context.ts @@ -74,8 +74,15 @@ export class LynxContext2d extends BrowserContext2d implements IContext2d { method: 'native' | 'simple' | 'quick' = application.global.measureTextMethod ): { width: number } { this.setTransform(1, 0, 0, 1, 0, 0, true, application.global.devicePixelRatio); - const data = super.measureText(text, method); - return data; + const data: any = super.measureText(text, method); + // lynx环境中的fontBoundingBoxDescent和fontBoundingBoxAscent有严重偏移,暂时规避 + return { + width: data.width, + fontBoundingBoxDescent: undefined, + fontBoundingBoxAscent: undefined, + actualBoundingBoxAscent: undefined, + actualBoundingBoxDescent: undefined + } as any; } createPattern(image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement, repetition: string): CanvasPattern { diff --git a/packages/vrender-kits/src/graphic/constants.ts b/packages/vrender-kits/src/graphic/constants.ts new file mode 100644 index 000000000..c234ac0eb --- /dev/null +++ b/packages/vrender-kits/src/graphic/constants.ts @@ -0,0 +1,3 @@ +import { Generator } from '@visactor/vrender-core'; + +export const GIFIMAGE_NUMBER_TYPE = Generator.GenAutoIncrementId(); diff --git a/packages/vrender-kits/src/graphic/gif-image.ts b/packages/vrender-kits/src/graphic/gif-image.ts new file mode 100644 index 000000000..2e930233d --- /dev/null +++ b/packages/vrender-kits/src/graphic/gif-image.ts @@ -0,0 +1,156 @@ +import type { IImageGraphicAttribute, ISetAttributeContext } from '@visactor/vrender-core'; +import { application, Image, ResourceLoader } from '@visactor/vrender-core'; +import type { ITimeline } from '@visactor/vrender-core'; +import { isString } from '@visactor/vutils'; +import type { ParsedFrame } from 'gifuct-js'; +import { decompressFrames, parseGIF } from 'gifuct-js'; +import type { IGifImage, IGifImageGraphicAttribute } from '../interface/gif-image'; +import { GIFIMAGE_NUMBER_TYPE } from './constants'; + +export class GifImage extends Image implements IGifImage { + type: any = 'gif-image'; + declare attribute: IGifImageGraphicAttribute; + + frameImageData?: ImageData; + tempCanvas?: HTMLCanvasElement; + tempCtx?: CanvasRenderingContext2D; + gifCanvas?: HTMLCanvasElement; + gifCtx?: CanvasRenderingContext2D; + loadedFrames?: ParsedFrame[]; + frameIndex?: number; + playing?: boolean; + lastTime?: number; + + constructor(params: IGifImageGraphicAttribute) { + super(params); + this.numberType = GIFIMAGE_NUMBER_TYPE; + this.loadGif(); + } + + loadGif() { + if (isString(this.attribute.gifImage)) { + ResourceLoader.GetFile(this.attribute.gifImage, 'arrayBuffer') + .then((res: ArrayBuffer) => { + const gif = parseGIF(res); + const frames = decompressFrames(gif, true); + this.renderGIF(frames); + }) + .catch(e => { + console.error('Gif load error: ', e); + }); + } else if (this.attribute.gifImage instanceof ArrayBuffer) { + const gif = parseGIF(this.attribute.gifImage); + const frames = decompressFrames(gif, true); + this.renderGIF(frames); + } + } + + renderGIF(frames: ParsedFrame[]) { + this.loadedFrames = frames; + this.frameIndex = 0; + + if (!this.tempCanvas) { + this.tempCanvas = application.global.createCanvas({}); + this.tempCtx = this.tempCanvas.getContext('2d'); + } + + if (!this.gifCanvas) { + this.gifCanvas = application.global.createCanvas({}); + this.gifCtx = this.gifCanvas.getContext('2d'); + } + + this.gifCanvas.width = frames[0].dims.width; + this.gifCanvas.height = frames[0].dims.height; + + this.playing = true; + this.lastTime = new Date().getTime(); + const animation = this.animate(); + if (this.attribute.timeline) { + animation.setTimeline(this.attribute.timeline); + } + animation.to({}, 1000, 'linear').loop(Infinity); + } + + renderFrame(context: CanvasRenderingContext2D, x: number, y: number) { + // get the frame + const frame = this.loadedFrames[this.frameIndex || 0]; + + if (frame.disposalType === 2) { + this.gifCtx.clearRect(0, 0, this.gifCanvas.width, this.gifCanvas.height); + } + + // draw image into gifCanvas + this.drawPatch(frame); + + // draw gifCanvas into stage + this.manipulate(context, x, y); + + // update the frame index + const diff = new Date().getTime() - this.lastTime; + if (frame.delay < diff) { + this.frameIndex++; + this.lastTime = new Date().getTime(); + } + if (this.frameIndex >= this.loadedFrames.length) { + this.frameIndex = 0; + } + } + + drawPatch(frame: ParsedFrame) { + const dims = frame.dims; + + if ( + !this.frameImageData || + dims.width !== this.frameImageData.width || + dims.height !== this.frameImageData.height + ) { + this.tempCanvas.width = dims.width; + this.tempCanvas.height = dims.height; + this.frameImageData = this.tempCtx.createImageData(dims.width, dims.height); + } + + // set the patch data as an override + this.frameImageData.data.set(frame.patch); + + // draw the patch back over the canvas + this.tempCtx.putImageData(this.frameImageData, 0, 0); + + this.gifCtx.drawImage(this.tempCanvas, dims.left, dims.top); + } + + manipulate(context: CanvasRenderingContext2D, x: number, y: number) { + context.drawImage( + this.gifCanvas, + 0, + 0, + this.gifCanvas.width, + this.gifCanvas.height, + x, + y, + this.attribute.width, + this.attribute.height + ); + } + + setAttribute(key: string, value: any, forceUpdateTag?: boolean, context?: ISetAttributeContext): void { + super.setAttribute(key, value, forceUpdateTag, context); + if (key === 'gifImage') { + this.loadGif(); + } + } + + setAttributes( + params: Partial, + forceUpdateTag?: boolean, + context?: ISetAttributeContext + ): void { + super.setAttributes(params, forceUpdateTag, context); + if (params.gifImage) { + this.loadGif(); + } + } +} + +export function createGifImage(attributes: IGifImageGraphicAttribute): IGifImage { + return new GifImage(attributes); +} diff --git a/packages/vrender-kits/src/index.ts b/packages/vrender-kits/src/index.ts index 9a24112ec..6b68fef67 100644 --- a/packages/vrender-kits/src/index.ts +++ b/packages/vrender-kits/src/index.ts @@ -51,6 +51,10 @@ export * from './picker/contributions/canvas-picker/arc3d-module'; export * from './picker/contributions/canvas-picker/pyramid3d-module'; +export * from './graphic/gif-image'; +export * from './picker/contributions/canvas-picker/gif-image-module'; +export * from './render/contributions/canvas/gif-image-module'; + export * from './register/register-arc'; export * from './register/register-arc3d'; export * from './register/register-area'; diff --git a/packages/vrender-kits/src/interface/gif-image.ts b/packages/vrender-kits/src/interface/gif-image.ts new file mode 100644 index 000000000..891c3dbde --- /dev/null +++ b/packages/vrender-kits/src/interface/gif-image.ts @@ -0,0 +1,19 @@ +import type { IGraphic, IImageGraphicAttribute, ITimeline } from '@visactor/vrender-core'; +import type { ParsedFrame } from 'gifuct-js'; + +export interface IGifImageGraphicAttribute extends IImageGraphicAttribute { + timeline?: ITimeline; + gifImage?: string | ArrayBuffer; +} + +export interface IGifImage extends IGraphic { + frameImageData?: ImageData; + tempCanvas?: HTMLCanvasElement; + tempCtx?: CanvasRenderingContext2D; + gifCanvas?: HTMLCanvasElement; + gifCtx?: CanvasRenderingContext2D; + loadedFrames?: ParsedFrame[]; + frameIndex?: number; + playing?: boolean; + lastTime?: number; +} diff --git a/packages/vrender-kits/src/jsx/jsx-classic.ts b/packages/vrender-kits/src/jsx/jsx-classic.ts index ad56ff823..f6ea0fdb2 100644 --- a/packages/vrender-kits/src/jsx/jsx-classic.ts +++ b/packages/vrender-kits/src/jsx/jsx-classic.ts @@ -10,7 +10,7 @@ function flatten(list: any, out: any[]): void { } export function jsx(type: string | any, config: Record, ...children: any) { - const { key, name, id, attribute, stateProxy, ...props } = config || {}; + const { key, name, id, attribute, stateProxy, animation, timeline, ...props } = config || {}; let c = type; if (isString(type)) { @@ -29,6 +29,22 @@ export function jsx(type: string | any, config: Record, ...children g.stateProxy = stateProxy; } + if (name) { + g.name = name; + } + + if (isArray(animation)) { + // animation={[ + // ['to', { angle: 2 * Math.PI }, 1000, 'linear'], + // ['loop', Infinity] + // ]} + const animate = g.animate(); + timeline && animate.setTimeline(timeline); + animation.forEach((item: any[]) => { + animate[item[0]](...item.slice(1)); + }); + } + return g; } diff --git a/packages/vrender-kits/src/picker/contributions/canvas-picker/gif-image-module.ts b/packages/vrender-kits/src/picker/contributions/canvas-picker/gif-image-module.ts new file mode 100644 index 000000000..755c5eec0 --- /dev/null +++ b/packages/vrender-kits/src/picker/contributions/canvas-picker/gif-image-module.ts @@ -0,0 +1,14 @@ +import { ContainerModule } from '@visactor/vrender-core'; +import { CanvasGifImagePicker, CanvasPickerContribution } from '../constants'; +import { DefaultCanvasGifImagePicker } from './gif-image-picker'; + +let loadGifImagePick = false; +export const gifImageCanvasPickModule = new ContainerModule((bind, unbind, isBound, rebind) => { + if (loadGifImagePick) { + return; + } + loadGifImagePick = true; + // gifGifImage picker + bind(CanvasGifImagePicker).to(DefaultCanvasGifImagePicker).inSingletonScope(); + bind(CanvasPickerContribution).toService(CanvasGifImagePicker); +}); diff --git a/packages/vrender-kits/src/picker/contributions/canvas-picker/gif-image-picker.ts b/packages/vrender-kits/src/picker/contributions/canvas-picker/gif-image-picker.ts new file mode 100644 index 000000000..4a7634db5 --- /dev/null +++ b/packages/vrender-kits/src/picker/contributions/canvas-picker/gif-image-picker.ts @@ -0,0 +1,31 @@ +import type { IPoint } from '@visactor/vutils'; +import { injectable } from '@visactor/vrender-core'; +import type { IGraphicPicker, IPickParams } from '@visactor/vrender-core'; +import type { IGifImage } from '../../../interface/gif-image'; +import { GIFIMAGE_NUMBER_TYPE } from '../../../graphic/constants'; + +@injectable() +export class DefaultCanvasGifImagePicker implements IGraphicPicker { + type: string = 'gif-image'; + numberType: number = GIFIMAGE_NUMBER_TYPE; + + contains(gifImage: IGifImage, point: IPoint, params?: IPickParams): boolean { + // const { gifImageAttribute } = graphicService.themeService.getCurrentTheme(); + // const { + // x = gifImageAttribute.x, + // y = gifImageAttribute.y, + // } = gifImage.attribute; + + const { pickContext } = params ?? {}; + if (!pickContext) { + return false; + } + + if (!gifImage.AABBBounds.containsPoint(point)) { + return false; + } + + // TODO: 详细形状判断 + return true; + } +} diff --git a/packages/vrender-kits/src/picker/contributions/constants.ts b/packages/vrender-kits/src/picker/contributions/constants.ts index 4ce53d35b..f2c09d01f 100644 --- a/packages/vrender-kits/src/picker/contributions/constants.ts +++ b/packages/vrender-kits/src/picker/contributions/constants.ts @@ -19,6 +19,7 @@ export const CanvasArc3dPicker = Symbol.for('CanvasArc3dPicker'); export const CanvasAreaPicker = Symbol.for('CanvasAreaPicker'); export const CanvasCirclePicker = Symbol.for('CanvasCirclePicker'); export const CanvasImagePicker = Symbol.for('CanvasImagePicker'); +export const CanvasGifImagePicker = Symbol.for('CanvasGifImagePicker'); export const CanvasLinePicker = Symbol.for('CanvasLinePicker'); export const CanvasPathPicker = Symbol.for('CanvasPathPicker'); export const CanvasRectPicker = Symbol.for('CanvasRectPicker'); diff --git a/packages/vrender-kits/src/render/contributions/canvas/gif-image-module.ts b/packages/vrender-kits/src/render/contributions/canvas/gif-image-module.ts new file mode 100644 index 000000000..be568f750 --- /dev/null +++ b/packages/vrender-kits/src/render/contributions/canvas/gif-image-module.ts @@ -0,0 +1,13 @@ +import { ContainerModule, GraphicRender } from '@visactor/vrender-core'; +import { DefaultCanvasGifImageRender } from './gif-image-render'; + +let loadGifImageModule = false; +export const gifImageModule = new ContainerModule(bind => { + if (loadGifImageModule) { + return; + } + loadGifImageModule = true; + // gifImage渲染器 + bind(DefaultCanvasGifImageRender).toSelf().inSingletonScope(); + bind(GraphicRender).toService(DefaultCanvasGifImageRender); +}); diff --git a/packages/vrender-kits/src/render/contributions/canvas/gif-image-render.ts b/packages/vrender-kits/src/render/contributions/canvas/gif-image-render.ts new file mode 100644 index 000000000..47b6cda79 --- /dev/null +++ b/packages/vrender-kits/src/render/contributions/canvas/gif-image-render.ts @@ -0,0 +1,170 @@ +import type { + IContext2d, + IContributionProvider, + IDrawContext, + IGraphicAttribute, + IGraphicRender, + IGraphicRenderDrawParams, + IImageRenderContribution, + IMarkAttribute, + IRenderService, + IThemeAttribute +} from '@visactor/vrender-core'; +import { + BaseRenderContributionTime, + ContributionProvider, + DefaultCanvasImageRender, + DefaultRectRenderContribution, + getTheme, + ImageRenderContribution, + inject, + injectable, + named +} from '@visactor/vrender-core'; +import { GIFIMAGE_NUMBER_TYPE } from '../../../graphic/constants'; +import type { IGifImage } from '../../../interface/gif-image'; + +@injectable() +export class DefaultCanvasGifImageRender extends DefaultCanvasImageRender implements IGraphicRender { + type: 'image'; + numberType: number = GIFIMAGE_NUMBER_TYPE; + + constructor( + @inject(ContributionProvider) + @named(ImageRenderContribution) + protected readonly imageRenderContribitions: IContributionProvider + ) { + super(imageRenderContribitions); + this._renderContribitions = undefined; + this.builtinContributions = [defaultGifImageRenderContribution]; + this.init(imageRenderContribitions); + } + + drawShape( + image: IGifImage, + context: IContext2d, + x: number, + y: number, + drawContext: IDrawContext, + params?: IGraphicRenderDrawParams, + fillCb?: ( + ctx: IContext2d, + markAttribute: Partial, + themeAttribute: IThemeAttribute + ) => boolean, + strokeCb?: ( + ctx: IContext2d, + markAttribute: Partial, + themeAttribute: IThemeAttribute + ) => boolean + ) { + // const imageAttribute = graphicService.themeService.getCurrentTheme().imageAttribute; + const imageAttribute = getTheme(image).image; + const { + x: originX = imageAttribute.x, + y: originY = imageAttribute.y, + fillStrokeOrder = imageAttribute.fillStrokeOrder + } = image.attribute; + + const data = this.valid(image, imageAttribute, fillCb); + if (!data) { + return; + } + const { fVisible, sVisible, doFill, doStroke } = data; + + // deal with cornerRadius + const needRestore = true; + + const _runFill = () => { + if (doFill) { + if (fillCb) { + fillCb(context, image.attribute, imageAttribute); + } else if (fVisible) { + } + } + }; + + const _runStroke = () => { + if (doStroke) { + if (strokeCb) { + strokeCb(context, image.attribute, imageAttribute); + } else if (sVisible) { + context.setStrokeStyle(image, image.attribute, originX - x, originY - y, imageAttribute); + context.stroke(); + } + } + }; + + if (!fillStrokeOrder) { + if (needRestore) { + context.save(); + context.clip(); + } + this.beforeRenderStep(image, context, x, y, doFill, false, fVisible, false, imageAttribute, drawContext, fillCb); + _runFill(); + if (needRestore) { + context.restore(); + } + _runStroke(); + } else { + _runStroke(); + if (needRestore) { + context.save(); + context.clip(); + } + this.beforeRenderStep(image, context, x, y, doFill, false, fVisible, false, imageAttribute, drawContext, fillCb); + _runFill(); + if (needRestore) { + context.restore(); + } + } + + this.afterRenderStep(image, context, x, y, doFill, false, fVisible, false, imageAttribute, drawContext, fillCb); + } + + draw(image: IGifImage, renderService: IRenderService, drawContext: IDrawContext) { + const { context } = renderService.drawParams; + if (!context) { + return; + } + const imageAttribute = getTheme(image).image; + this._draw(image, imageAttribute, false, drawContext); + } +} + +export class DefaultGifImageRenderContribution + extends DefaultRectRenderContribution + implements IImageRenderContribution +{ + time: BaseRenderContributionTime = BaseRenderContributionTime.afterFillStroke; + useStyle: boolean = true; + order: number = 0; + drawShape( + image: any, + context: IContext2d, + x: number, + y: number, + doFill: boolean, + doStroke: boolean, + fVisible: boolean, + sVisible: boolean, + rectAttribute: any, + drawContext: IDrawContext, + fillCb?: ( + ctx: IContext2d, + markAttribute: Partial, + themeAttribute: IThemeAttribute + ) => boolean, + strokeCb?: ( + ctx: IContext2d, + markAttribute: Partial, + themeAttribute: IThemeAttribute + ) => boolean + ) { + if (image.renderFrame && image.playing) { + image.renderFrame(context, x, y); + } + } +} + +export const defaultGifImageRenderContribution = new DefaultGifImageRenderContribution(); diff --git a/packages/vrender/CHANGELOG.json b/packages/vrender/CHANGELOG.json index 6ec53716f..da1eb65b8 100644 --- a/packages/vrender/CHANGELOG.json +++ b/packages/vrender/CHANGELOG.json @@ -1,6 +1,12 @@ { "name": "@visactor/vrender", "entries": [ + { + "version": "0.21.9", + "tag": "@visactor/vrender_v0.21.9", + "date": "Mon, 13 Jan 2025 03:23:50 GMT", + "comments": {} + }, { "version": "0.21.8", "tag": "@visactor/vrender_v0.21.8", diff --git a/packages/vrender/CHANGELOG.md b/packages/vrender/CHANGELOG.md index ead7c60c1..760cd7cf6 100644 --- a/packages/vrender/CHANGELOG.md +++ b/packages/vrender/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @visactor/vrender -This log was last generated on Mon, 06 Jan 2025 11:07:36 GMT and should not be manually modified. +This log was last generated on Mon, 13 Jan 2025 03:23:50 GMT and should not be manually modified. + +## 0.21.9 +Mon, 13 Jan 2025 03:23:50 GMT + +_Version update only_ ## 0.21.8 Mon, 06 Jan 2025 11:07:36 GMT diff --git a/packages/vrender/__tests__/browser/sources/loading-1.gif b/packages/vrender/__tests__/browser/sources/loading-1.gif new file mode 100644 index 000000000..20ac5cb44 Binary files /dev/null and b/packages/vrender/__tests__/browser/sources/loading-1.gif differ diff --git a/packages/vrender/__tests__/browser/sources/loading.gif b/packages/vrender/__tests__/browser/sources/loading.gif new file mode 100644 index 000000000..0c81bcaf8 Binary files /dev/null and b/packages/vrender/__tests__/browser/sources/loading.gif differ diff --git a/packages/vrender/__tests__/browser/src/pages/gif-image.ts b/packages/vrender/__tests__/browser/src/pages/gif-image.ts new file mode 100644 index 000000000..8900d5098 --- /dev/null +++ b/packages/vrender/__tests__/browser/src/pages/gif-image.ts @@ -0,0 +1,64 @@ +import { createStage, container } from '@visactor/vrender-core'; +import { GifImage, IGifImageGraphicAttribute, gifImageModule, gifImageCanvasPickModule } from '@visactor/vrender-kits'; +import { addShapesToStage, colorPools } from '../utils'; + +container.load(gifImageModule); +container.load(gifImageCanvasPickModule); + +export const page = () => { + const shapes = []; + shapes.push( + new GifImage({ + x: 100, + y: 100, + width: 50, + height: 50, + gifImage: './sources/loading.gif' + } as IGifImageGraphicAttribute) + ); + + shapes.push( + new GifImage({ + x: 200, + y: 100, + width: 50, + height: 50, + gifImage: './sources/loading.gif' + } as IGifImageGraphicAttribute) + ); + + shapes.push( + new GifImage({ + x: 100, + y: 200, + width: 50, + height: 50, + gifImage: './sources/loading-1.gif' + } as IGifImageGraphicAttribute) + ); + + shapes.push( + new GifImage({ + x: 200, + y: 200, + width: 50, + height: 50, + gifImage: './sources/loading-1.gif' + } as IGifImageGraphicAttribute) + ); + + const stage = createStage({ + canvas: 'main', + width: 1200, + height: 600, + viewWidth: 1200, + viewHeight: 600 + }); + + addShapesToStage(stage, shapes as any, true); + stage.render(); + + stage.addEventListener('click', e => { + console.log('target', e.target); + }); +}; diff --git a/packages/vrender/__tests__/browser/src/pages/index.ts b/packages/vrender/__tests__/browser/src/pages/index.ts index 8eff30472..2fed4b3de 100644 --- a/packages/vrender/__tests__/browser/src/pages/index.ts +++ b/packages/vrender/__tests__/browser/src/pages/index.ts @@ -190,6 +190,10 @@ export const pages = [ { name: 'react', path: 'react' + }, + { + name: 'gif-image', + path: 'gif-image' } ] }, diff --git a/packages/vrender/package.json b/packages/vrender/package.json index eecfab3db..a158c1dbe 100644 --- a/packages/vrender/package.json +++ b/packages/vrender/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vrender", - "version": "0.21.8", + "version": "0.21.9", "description": "", "sideEffects": true, "main": "cjs/index.js", @@ -24,8 +24,8 @@ "test-watch": "cross-env DEBUG_MODE=1 jest --watch" }, "dependencies": { - "@visactor/vrender-core": "workspace:0.21.8", - "@visactor/vrender-kits": "workspace:0.21.8" + "@visactor/vrender-core": "workspace:0.21.9", + "@visactor/vrender-kits": "workspace:0.21.9" }, "devDependencies": { "@internal/bundler": "workspace:*", diff --git a/tools/bugserver-trigger/package.json b/tools/bugserver-trigger/package.json index 831781f91..60f4eed9d 100644 --- a/tools/bugserver-trigger/package.json +++ b/tools/bugserver-trigger/package.json @@ -8,10 +8,10 @@ "ci": "ts-node --transpileOnly --skipProject ./scripts/trigger-test.ts" }, "dependencies": { - "@visactor/vrender": "workspace:0.21.8", - "@visactor/vrender-core": "workspace:0.21.8", - "@visactor/vrender-kits": "workspace:0.21.8", - "@visactor/vrender-components": "workspace:0.21.8" + "@visactor/vrender": "workspace:0.21.9", + "@visactor/vrender-core": "workspace:0.21.9", + "@visactor/vrender-kits": "workspace:0.21.9", + "@visactor/vrender-components": "workspace:0.21.9" }, "devDependencies": { "@rushstack/eslint-patch": "~1.1.4",