From 19aa940fb0f23384e210ab68c483141edd64949e Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Mon, 24 Jul 2023 19:26:52 +0800 Subject: [PATCH 01/26] refactor: rename global and window to vglobal and vwindow, closed #56 --- ...global-window-rename_2023-07-24-11-31.json | 10 + ...global-window-rename_2023-07-24-11-31.json | 10 + .../env-canvas/window-contribution.ts | 4 +- docs/demos/src/core/stage.ts | 2 +- .../pages/gallery/pages/gallary.ts | 438 ++++++++++-------- .../vrender-components/src/brush/brush.ts | 17 +- .../src/data-zoom/data-zoom.ts | 17 +- .../src/player/continuous-player.ts | 14 +- .../src/player/discrete-player.ts | 14 +- .../src/scrollbar/scrollbar.ts | 15 +- packages/vrender/src/constants.ts | 2 +- .../layerHandler/canvas2d-contribution.ts | 4 +- .../layerHandler/offscreen2d-contribution.ts | 4 +- .../window/browser-contribution.ts | 4 +- .../window/feishu-contribution.ts | 4 +- .../contributions/window/lynx-contribution.ts | 4 +- .../window/native-contribution.ts | 4 +- .../contributions/window/node-contribution.ts | 4 +- .../contributions/window/taro-contribution.ts | 4 +- .../contributions/window/wx-contribution.ts | 4 +- packages/vrender/src/core/core-modules.ts | 10 +- packages/vrender/src/core/global-module.ts | 34 +- packages/vrender/src/core/graphic-utils.ts | 4 +- packages/vrender/src/core/layer-service.ts | 4 +- packages/vrender/src/core/stage.ts | 10 +- packages/vrender/src/core/window.ts | 6 +- packages/vrender/src/modules.ts | 6 +- .../src/picker/canvas-picker-service.ts | 4 +- .../src/picker/global-picker-service.ts | 4 +- .../vrender/src/picker/math-picker-service.ts | 4 +- packages/vrender/src/picker/picker-service.ts | 4 +- .../render/incremental-draw-contribution.ts | 4 +- 32 files changed, 367 insertions(+), 306 deletions(-) create mode 100644 common/changes/@visactor/vrender-components/refactor-global-window-rename_2023-07-24-11-31.json create mode 100644 common/changes/@visactor/vrender/refactor-global-window-rename_2023-07-24-11-31.json diff --git a/common/changes/@visactor/vrender-components/refactor-global-window-rename_2023-07-24-11-31.json b/common/changes/@visactor/vrender-components/refactor-global-window-rename_2023-07-24-11-31.json new file mode 100644 index 000000000..9228ca8f2 --- /dev/null +++ b/common/changes/@visactor/vrender-components/refactor-global-window-rename_2023-07-24-11-31.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "refactor: rename global and window to vglobal and vwindow", + "type": "minor" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/common/changes/@visactor/vrender/refactor-global-window-rename_2023-07-24-11-31.json b/common/changes/@visactor/vrender/refactor-global-window-rename_2023-07-24-11-31.json new file mode 100644 index 000000000..a24d6178d --- /dev/null +++ b/common/changes/@visactor/vrender/refactor-global-window-rename_2023-07-24-11-31.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender", + "comment": "refactor: rename global and window to vglobal and vwindow", + "type": "minor" + } + ], + "packageName": "@visactor/vrender" +} \ No newline at end of file diff --git a/docs/demos/src/contribution/env-canvas/window-contribution.ts b/docs/demos/src/contribution/env-canvas/window-contribution.ts index c41f4c499..995d9130d 100644 --- a/docs/demos/src/contribution/env-canvas/window-contribution.ts +++ b/docs/demos/src/contribution/env-canvas/window-contribution.ts @@ -8,7 +8,7 @@ import type { ICanvas, IDomRectLike } from '@visactor/vrender'; -import { Generator, Global, BaseWindowHandlerContribution } from '@visactor/vrender'; +import { Generator, VGlobal, BaseWindowHandlerContribution } from '@visactor/vrender'; import { NodeCanvas } from './canvas'; import type { IBoundsLike } from '@visactor/vutils'; @@ -21,7 +21,7 @@ export class NodeWindowHandlerContribution extends BaseWindowHandlerContribution return null; } - constructor(@inject(Global) private readonly global: IGlobal) { + constructor(@inject(VGlobal) private readonly global: IGlobal) { super(); } diff --git a/docs/demos/src/core/stage.ts b/docs/demos/src/core/stage.ts index 0f47ca968..c084dc25c 100644 --- a/docs/demos/src/core/stage.ts +++ b/docs/demos/src/core/stage.ts @@ -1,7 +1,7 @@ import { createStage, container } from '@visactor/vrender'; export function createAStage() { - console.log(container.get(Symbol.for('Global'))); + console.log(container.get(Symbol.for('VGlobal'))); const stage = createStage({}); console.log(stage); // const stage = new Stage({ diff --git a/multi-platform/wx-vrender/miniprogram/pages/gallery/pages/gallary.ts b/multi-platform/wx-vrender/miniprogram/pages/gallery/pages/gallary.ts index 08e4729dc..299ca3d6e 100644 --- a/multi-platform/wx-vrender/miniprogram/pages/gallery/pages/gallary.ts +++ b/multi-platform/wx-vrender/miniprogram/pages/gallery/pages/gallary.ts @@ -3,14 +3,14 @@ // import { formatTime } from '../../utils/util' import VRender from './index'; -const { global, createStage, createRect, createSymbol, createText, createArc, createLine, createArea } = VRender; +const { vglobal, createStage, createRect, createSymbol, createText, createArc, createLine, createArea } = VRender; Page({ data: { - logs: [], + logs: [] }, stage: undefined, - onLoad(options: {name: string}) { + onLoad(options: { name: string }) { console.log(options); this.init(options.name); // this.setData({ @@ -26,35 +26,40 @@ Page({ wx.createSelectorQuery() .select('#draw') // 在 WXML 中填入的 id .fields({ node: true, size: true }) - .exec(async (res) => { - const canvas = res[0].node; - const canvasIdLists = ['draw']; - console.log(canvas.width, canvas.height); - const domref = { width: res[0].width, height: res[0].height } - await global.setEnv('wx', { domref, force: true, canvasIdLists, freeCanvasIdx: 0 }); - const stage = createStage({ canvas: global.getElementById('draw'), width: res[0].width, height: res[0].height, autoRender: true }); - this.stage = stage as any; - switch(name) { - case 'rect': - return this.drawRect(stage); - case 'symbol': - return this.drawSymbol(stage); - case 'text': - return this.drawText(stage); - case 'arc': - return this.drawArc(stage); - case 'line': - return this.drawLine(stage); - case 'area': - return this.drawArea(stage); - } - - // const ctx = canvas.getContext('2d'); - // canvas.width = 500; - // canvas.height = 500; - // ctx.fillStyle = 'red'; - // ctx.fillRect(0, 0, 100, 100); - }) + .exec(async res => { + const canvas = res[0].node; + const canvasIdLists = ['draw']; + console.log(canvas.width, canvas.height); + const domref = { width: res[0].width, height: res[0].height }; + await vglobal.setEnv('wx', { domref, force: true, canvasIdLists, freeCanvasIdx: 0 }); + const stage = createStage({ + canvas: vglobal.getElementById('draw'), + width: res[0].width, + height: res[0].height, + autoRender: true + }); + this.stage = stage as any; + switch (name) { + case 'rect': + return this.drawRect(stage); + case 'symbol': + return this.drawSymbol(stage); + case 'text': + return this.drawText(stage); + case 'arc': + return this.drawArc(stage); + case 'line': + return this.drawLine(stage); + case 'area': + return this.drawArea(stage); + } + + // const ctx = canvas.getContext('2d'); + // canvas.width = 500; + // canvas.height = 500; + // ctx.fillStyle = 'red'; + // ctx.fillRect(0, 0, 100, 100); + }); }, bindEvent(event: any) { // const { type } = event; @@ -90,141 +95,168 @@ Page({ r.setAttribute('fill', 'green'); }); graphics.push(r); - - graphics.push(createRect({ - x: 100, - y: 30, - width: 100, - height: 100, - fill: { - gradient: 'linear', - x0: 0, - y0: 0, - x1: 1, - y1: 0, - stops: [ - { offset: 0, color: 'green' }, - { offset: 0.5, color: 'orange' }, - { offset: 1, color: 'red' } - ] - }, - cornerRadius: [5, 10, 15, 20], - lineWidth: 5 - })); - + + graphics.push( + createRect({ + x: 100, + y: 30, + width: 100, + height: 100, + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 1, + y1: 0, + stops: [ + { offset: 0, color: 'green' }, + { offset: 0.5, color: 'orange' }, + { offset: 1, color: 'red' } + ] + }, + cornerRadius: [5, 10, 15, 20], + lineWidth: 5 + }) + ); + graphics.forEach(g => { stage.defaultLayer.add(g); - }) + }); // stage.render(); }, drawSymbol(stage: any) { const symbolList = [ - 'circle', 'cross', 'diamond', 'square', 'arrow', 'arrow2Left', 'arrow2Right', 'wedge', 'thinTriangle', 'triangle', 'triangleUp', 'triangleDown', 'triangleRight', 'triangleLeft', 'stroke', 'star', 'wye', 'rect', + 'circle', + 'cross', + 'diamond', + 'square', + 'arrow', + 'arrow2Left', + 'arrow2Right', + 'wedge', + 'thinTriangle', + 'triangle', + 'triangleUp', + 'triangleDown', + 'triangleRight', + 'triangleLeft', + 'stroke', + 'star', + 'wye', + 'rect', 'M -2 2 L 4 -5 L 7 -6 L 6 -3 L -1 3 C 0 4 0 5 1 4 C 1 5 2 6 1 6 A 1.42 1.42 0 0 1 0 7 A 5 5 0 0 0 -2 4 Q -2.5 3.9 -2.5 4.5 T -4 5.8 T -4.8 5 T -3.5 3.5 T -3 3 A 5 5 90 0 0 -6 1 A 1.42 1.42 0 0 1 -5 0 C -5 -1 -4 0 -3 0 C -4 1 -3 1 -2 2 M 4 -5 L 4 -3 L 6 -3 L 5 -4 L 4 -5' - ] + ]; const graphics = []; - + symbolList.forEach((st, i) => { const symbol = createSymbol({ symbolType: st, - x: (i * 30) % 150 + 30, - y: (Math.floor(i * 30 / 150) + 1) * 30, + x: ((i * 30) % 150) + 30, + y: (Math.floor((i * 30) / 150) + 1) * 30, size: 20, fill: 'red', stroke: 'green' }); symbol.addEventListener('mouseenter', () => { symbol.setAttribute('fill', 'blue'); - }) + }); graphics.push(symbol); - }) - + }); + graphics.forEach(g => { stage.defaultLayer.add(g); - }) + }); }, drawText(stage: any) { const graphics: any[] = []; - - graphics.push(createText({ - x: 0, - y: 100, - fill: 'green', - stroke: 'red', - text: 'Testabcdefg', - fontSize: 20, - textBaseline: 'top' - })); - - graphics.push(createText({ - x: 100, - y: 20, - fill: { - gradient: 'linear', - x0: 0, - y0: 0, - x1: 1, - y1: 1, - stops: [ - { offset: 0, color: 'green' }, - { offset: 0.5, color: 'orange' }, - { offset: 1, color: 'red' } - ] - }, - text: ['这'], - fontSize: 180, - textBaseline: 'top' - })); - + + graphics.push( + createText({ + x: 0, + y: 100, + fill: 'green', + stroke: 'red', + text: 'Testabcdefg', + fontSize: 20, + textBaseline: 'top' + }) + ); + + graphics.push( + createText({ + x: 100, + y: 20, + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 1, + y1: 1, + stops: [ + { offset: 0, color: 'green' }, + { offset: 0.5, color: 'orange' }, + { offset: 1, color: 'red' } + ] + }, + text: ['这'], + fontSize: 180, + textBaseline: 'top' + }) + ); + graphics.forEach(g => { stage.defaultLayer.add(g); - }) + }); }, drawArc(stage: any) { const graphics: any[] = []; - - graphics.push(createArc({ - innerRadius: 60, - outerRadius: 137.8, - startAngle: -1.5707963267948966, - endAngle: -0.3141592653589793, - x: 100, - y: 200, - cornerRadius: 6, - stroke: 'green', - lineWidth: 2, - cap: false - })); - - graphics.push(createArc({ - innerRadius: 20, - outerRadius: 60, - startAngle: 0, - endAngle: Math.PI * 2, - x: 100, - y: 120, - fill: { - gradient: 'linear', - x0: 0, - y0: 0, - x1: 1, - y1: 0, - stops: [ - { offset: 0, color: 'green' }, - { offset: 0.5, color: 'orange' }, - { offset: 1, color: 'red' } - ] - }, - fillOpacity: 0.3, - stroke: 'green', - lineWidth: 2, - cap: false - })); - + + graphics.push( + createArc({ + innerRadius: 60, + outerRadius: 137.8, + startAngle: -1.5707963267948966, + endAngle: -0.3141592653589793, + x: 100, + y: 200, + cornerRadius: 6, + stroke: 'green', + lineWidth: 2, + cap: false + }) + ); + + graphics.push( + createArc({ + innerRadius: 20, + outerRadius: 60, + startAngle: 0, + endAngle: Math.PI * 2, + x: 100, + y: 120, + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 1, + y1: 0, + stops: [ + { offset: 0, color: 'green' }, + { offset: 0.5, color: 'orange' }, + { offset: 1, color: 'red' } + ] + }, + fillOpacity: 0.3, + stroke: 'green', + lineWidth: 2, + cap: false + }) + ); + graphics.forEach(g => { stage.defaultLayer.add(g); - }) + }); }, drawLine(stage: any) { const graphics: any[] = []; @@ -236,7 +268,7 @@ Page({ [60, 20], [70, 30] ].map(item => ({ x: item[0] / 5, y: item[1] / 5, defined: item[0] !== 70 })); - + const subP2 = [ [80, 80], [120, 60], @@ -244,7 +276,7 @@ Page({ [200, 20], [240, 50] ].map(item => ({ x: item[0] / 5, y: item[1] / 5 })); - + const points = [ [0, 100], [20, 40], @@ -257,35 +289,39 @@ Page({ [200, 20], [240, 50] ].map(item => ({ x: item[0] / 5, y: item[1] / 5, defined: item[0] !== 70 })); - + ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { - graphics.push(createLine({ - points, - curveType: type as any, - x: ((i * 300) % 900 + 100) / 5, - y: ((Math.floor(i * 300 / 900)) * 200) / 5, - stroke: 'red' - })); + graphics.push( + createLine({ + points, + curveType: type as any, + x: (((i * 300) % 900) + 100) / 5, + y: (Math.floor((i * 300) / 900) * 200) / 5, + stroke: 'red' + }) + ); }); - + ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { i += 7; - graphics.push(createLine({ - points, - curveType: type as any, - x: ((i * 300) % 900 + 100) / 5, - y: ((Math.floor(i * 300 / 900)) * 200) / 5, - segments: [ - { points: subP1, stroke: 'blue', lineWidth: 6 }, - { points: subP2, stroke: 'pink', lineWidth: 2, lineDash: [3, 3] } - ], - stroke: 'red' - })); + graphics.push( + createLine({ + points, + curveType: type as any, + x: (((i * 300) % 900) + 100) / 5, + y: (Math.floor((i * 300) / 900) * 200) / 5, + segments: [ + { points: subP1, stroke: 'blue', lineWidth: 6 }, + { points: subP2, stroke: 'pink', lineWidth: 2, lineDash: [3, 3] } + ], + stroke: 'red' + }) + ); }); - + graphics.forEach(g => { stage.defaultLayer.add(g); - }) + }); }, drawArea(stage: any) { const graphics: any[] = []; @@ -297,7 +333,7 @@ Page({ [60, 20], [70, 30] ].map(item => ({ x: item[0] / 5, y: item[1] / 5, y1: 120 / 5, defined: item[0] !== 70 })); - + const subP2 = [ [80, 80], [120, 60], @@ -305,7 +341,7 @@ Page({ [200, 20], [240, 50] ].map(item => ({ x: item[0] / 5, y1: 120 / 5, y: item[1] / 5 })); - + const points = [ [0, 100], [20, 40], @@ -318,48 +354,52 @@ Page({ [200, 20], [240, 50] ].map(item => ({ x: item[0] / 5, y: item[1] / 5, y1: 120 / 5, defined: item[0] !== 70 })); - + ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { - graphics.push(createArea({ - points, - curveType: type as any, - x: ((i * 300) % 900 + 100) / 5, - y: ((Math.floor(i * 300 / 900)) * 200) / 5, - fill: { - gradient: 'linear', - x0: 0, - y0: 0, - x1: 1, - y1: 0, - stops: [ - { offset: 0, color: 'green' }, - { offset: 0.5, color: 'orange' }, - { offset: 1, color: 'red' } - ] - }, - })); + graphics.push( + createArea({ + points, + curveType: type as any, + x: (((i * 300) % 900) + 100) / 5, + y: (Math.floor((i * 300) / 900) * 200) / 5, + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 1, + y1: 0, + stops: [ + { offset: 0, color: 'green' }, + { offset: 0.5, color: 'orange' }, + { offset: 1, color: 'red' } + ] + } + }) + ); }); - + ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { i += 7; - graphics.push(createArea({ - points, - curveType: type as any, - x: ((i * 300) % 900 + 100) / 5, - y: ((Math.floor(i * 300 / 900)) * 200) / 5, - segments: [ - { points: subP1, fill: 'red' }, - { - points: subP2, - fill: 'pink' - } - ], - fill: true - })); + graphics.push( + createArea({ + points, + curveType: type as any, + x: (((i * 300) % 900) + 100) / 5, + y: (Math.floor((i * 300) / 900) * 200) / 5, + segments: [ + { points: subP1, fill: 'red' }, + { + points: subP2, + fill: 'pink' + } + ], + fill: true + }) + ); }); - + graphics.forEach(g => { stage.defaultLayer.add(g); - }) + }); } -}) +}); diff --git a/packages/vrender-components/src/brush/brush.ts b/packages/vrender-components/src/brush/brush.ts index 6886b52bb..383ef4195 100644 --- a/packages/vrender-components/src/brush/brush.ts +++ b/packages/vrender-components/src/brush/brush.ts @@ -1,19 +1,12 @@ /** * @description 框选组件 */ -import { FederatedPointerEvent, IGroup, IPolygon, global, createPolygon, Polygon, point } from '@visactor/vrender'; -import { - cloneDeep, - debounce, - IBounds, - IPointLike, - isFunction, - merge, - polygonContainPoint, - throttle -} from '@visactor/vutils'; +import type { FederatedPointerEvent, IGroup, IPolygon } from '@visactor/vrender'; +import { createPolygon, Polygon, point } from '@visactor/vrender'; +import type { IBounds, IPointLike } from '@visactor/vutils'; +import { cloneDeep, debounce, isFunction, merge, polygonContainPoint, throttle } from '@visactor/vutils'; import { AbstractComponent } from '../core/base'; -import { BrushAttributes } from './type'; +import type { BrushAttributes } from './type'; import { DEFAULT_BRUSH_ATTRIBUTES } from './config'; const delayMap = { diff --git a/packages/vrender-components/src/data-zoom/data-zoom.ts b/packages/vrender-components/src/data-zoom/data-zoom.ts index 777a14658..8f8743923 100644 --- a/packages/vrender-components/src/data-zoom/data-zoom.ts +++ b/packages/vrender-components/src/data-zoom/data-zoom.ts @@ -1,9 +1,12 @@ -import { FederatedPointerEvent, IArea, IGroup, ILine, IRect, ISymbol, global, INode } from '@visactor/vrender'; -import { IPointLike, array, clamp, isFunction, isValid, merge } from '@visactor/vutils'; +import type { FederatedPointerEvent, IArea, IGroup, ILine, IRect, ISymbol, INode } from '@visactor/vrender'; +import { vglobal } from '@visactor/vrender'; +import type { IPointLike } from '@visactor/vutils'; +import { array, clamp, isFunction, isValid, merge } from '@visactor/vutils'; import { AbstractComponent } from '../core/base'; -import { Tag, TagAttributes } from '../tag'; +import type { TagAttributes } from '../tag'; +import { Tag } from '../tag'; import { DataZoomActiveTag, DEFAULT_DATA_ZOOM_ATTRIBUTES } from './config'; -import { DataZoomAttributes } from './type'; +import type { DataZoomAttributes } from './type'; export class DataZoom extends AbstractComponent> { name = 'dataZoom'; @@ -162,11 +165,11 @@ export class DataZoom extends AbstractComponent> { (e: FederatedPointerEvent) => this._onHandlerPointerDown(e, selectedTag) as unknown as EventListener ); } - if (global.env === 'browser') { + if (vglobal.env === 'browser') { // 拖拽时 - global.addEventListener('pointermove', this._onHandlerPointerMove.bind(this) as EventListener); + vglobal.addEventListener('pointermove', this._onHandlerPointerMove.bind(this) as EventListener); // 拖拽结束 - global.addEventListener('pointerup', this._onHandlerPointerUp.bind(this) as EventListener); + vglobal.addEventListener('pointerup', this._onHandlerPointerUp.bind(this) as EventListener); } // 拖拽时 (this as unknown as IGroup).addEventListener('pointermove', this._onHandlerPointerMove as EventListener); diff --git a/packages/vrender-components/src/player/continuous-player.ts b/packages/vrender-components/src/player/continuous-player.ts index 858ee10c8..44785085a 100644 --- a/packages/vrender-components/src/player/continuous-player.ts +++ b/packages/vrender-components/src/player/continuous-player.ts @@ -1,6 +1,8 @@ -import { FederatedPointerEvent, global } from '@visactor/vrender'; +import type { FederatedPointerEvent } from '@visactor/vrender'; +import { vglobal } from '@visactor/vrender'; import { BasePlayer } from './base-player'; -import { ContinuousPlayerAttributes, PlayerEventEnum } from './type'; +import type { ContinuousPlayerAttributes } from './type'; +import { PlayerEventEnum } from './type'; import { ControllerEventEnum } from './controller/constant'; export interface IContinuousPlayer { @@ -161,7 +163,7 @@ export class ContinuousPlayer extends BasePlayer imp // 事件 this.dispatchCustomEvent(PlayerEventEnum.OnPlay); // 开始播放动画 - this._rafId = global.getRequestAnimationFrame()(this._play.bind(this)); + this._rafId = vglobal.getRequestAnimationFrame()(this._play.bind(this)); }; /** @@ -182,7 +184,7 @@ export class ContinuousPlayer extends BasePlayer imp } // 持续播放 - this._rafId = global.getRequestAnimationFrame()(this._play.bind(this)); + this._rafId = vglobal.getRequestAnimationFrame()(this._play.bind(this)); }; /** @@ -192,7 +194,7 @@ export class ContinuousPlayer extends BasePlayer imp // 播放状态更新 this._isPlaying = false; // 取消播放动画 - global.getCancelAnimationFrame()(this._rafId); + vglobal.getCancelAnimationFrame()(this._rafId); // 切换按钮 this._controller.togglePlay(); // 事件 @@ -209,7 +211,7 @@ export class ContinuousPlayer extends BasePlayer imp this._isPlaying = false; // 计算已流逝的时间, 需要记录下来 this._elapsed = Date.now() - this._startTime; - global.getCancelAnimationFrame()(this._rafId); + vglobal.getCancelAnimationFrame()(this._rafId); this._controller.togglePlay(); this.dispatchCustomEvent(PlayerEventEnum.OnPause); diff --git a/packages/vrender-components/src/player/discrete-player.ts b/packages/vrender-components/src/player/discrete-player.ts index b86ab1f0d..f348811ab 100644 --- a/packages/vrender-components/src/player/discrete-player.ts +++ b/packages/vrender-components/src/player/discrete-player.ts @@ -1,7 +1,9 @@ import { isNil, merge } from '@visactor/vutils'; -import { FederatedPointerEvent, global } from '@visactor/vrender'; +import type { FederatedPointerEvent } from '@visactor/vrender'; +import { vglobal } from '@visactor/vrender'; import { BasePlayer } from './base-player'; -import { DirectionEnum, DirectionType, DiscretePlayerAttributes, PlayerAttributes, PlayerEventEnum } from './type'; +import type { DirectionType, DiscretePlayerAttributes, PlayerAttributes } from './type'; +import { DirectionEnum, PlayerEventEnum } from './type'; import { forwardStep, isReachEnd, isReachStart } from './utils'; import { ControllerEventEnum } from './controller/constant'; @@ -138,7 +140,7 @@ export class DiscretePlayer extends BasePlayer impleme // 重置tick时间, 暂停后重新播放也会重新计时 this._tickTime = Date.now(); // 开启动画 - this._rafId = global.getRequestAnimationFrame()(this._play.bind(this, true)); + this._rafId = vglobal.getRequestAnimationFrame()(this._play.bind(this, true)); }; /** @@ -176,7 +178,7 @@ export class DiscretePlayer extends BasePlayer impleme this._isReachEnd = true; } - this._rafId = global.getRequestAnimationFrame()(this._play.bind(this, false)); + this._rafId = vglobal.getRequestAnimationFrame()(this._play.bind(this, false)); }; /** @@ -196,7 +198,7 @@ export class DiscretePlayer extends BasePlayer impleme // 图标切换 this._controller.togglePlay(); // 取消播放动画 - global.getCancelAnimationFrame()(this._rafId); + vglobal.getCancelAnimationFrame()(this._rafId); // 重置ActiveIndex this._activeIndex = -1; // 播放结束时并且到达终点 @@ -211,7 +213,7 @@ export class DiscretePlayer extends BasePlayer impleme return; } this._isPlaying = false; - global.getCancelAnimationFrame()(this._rafId); + vglobal.getCancelAnimationFrame()(this._rafId); this._controller.togglePlay(); this.dispatchCustomEvent(PlayerEventEnum.OnPause); diff --git a/packages/vrender-components/src/scrollbar/scrollbar.ts b/packages/vrender-components/src/scrollbar/scrollbar.ts index 0383a81fd..0d8620bcc 100644 --- a/packages/vrender-components/src/scrollbar/scrollbar.ts +++ b/packages/vrender-components/src/scrollbar/scrollbar.ts @@ -1,7 +1,8 @@ /** * @description 滚动条组件 */ -import { IRectGraphicAttribute, FederatedPointerEvent, CustomEvent, global, IGroup, IRect } from '@visactor/vrender'; +import type { IRectGraphicAttribute, FederatedPointerEvent, IGroup, IRect } from '@visactor/vrender'; +import { CustomEvent, vglobal } from '@visactor/vrender'; import { merge, normalizePadding, clamp, clampRange } from '@visactor/vutils'; import { AbstractComponent } from '../core/base'; @@ -265,9 +266,9 @@ export class ScrollBar extends AbstractComponent> e.stopPropagation(); const { direction } = this.attribute as ScrollBarAttributes; this._prePos = direction === 'horizontal' ? e.clientX : e.clientY; - if (global.env === 'browser') { - global.addEventListener('pointermove', this._onSliderPointerMove); - global.addEventListener('pointerup', this._onSliderPointerUp); + if (vglobal.env === 'browser') { + vglobal.addEventListener('pointermove', this._onSliderPointerMove); + vglobal.addEventListener('pointerup', this._onSliderPointerUp); } else { this._slider.addEventListener('pointermove', this._onSliderPointerMove); this._slider.addEventListener('pointerup', this._onSliderPointerUp); @@ -299,9 +300,9 @@ export class ScrollBar extends AbstractComponent> private _onSliderPointerUp = (e: any) => { e.preventDefault(); - if (global.env === 'browser') { - global.removeEventListener('pointermove', this._onSliderPointerMove); - global.removeEventListener('pointerup', this._onSliderPointerUp); + if (vglobal.env === 'browser') { + vglobal.removeEventListener('pointermove', this._onSliderPointerMove); + vglobal.removeEventListener('pointerup', this._onSliderPointerUp); } else { this._slider.removeEventListener('pointermove', this._onSliderPointerMove); this._slider.removeEventListener('pointerup', this._onSliderPointerUp); diff --git a/packages/vrender/src/constants.ts b/packages/vrender/src/constants.ts index 87a8bdabb..888462379 100644 --- a/packages/vrender/src/constants.ts +++ b/packages/vrender/src/constants.ts @@ -1,2 +1,2 @@ export const EnvContribution = Symbol.for('EnvContribution'); -export const Global = Symbol.for('Global'); +export const VGlobal = Symbol.for('VGlobal'); diff --git a/packages/vrender/src/core/contributions/layerHandler/canvas2d-contribution.ts b/packages/vrender/src/core/contributions/layerHandler/canvas2d-contribution.ts index b34586908..b6d1b535c 100644 --- a/packages/vrender/src/core/contributions/layerHandler/canvas2d-contribution.ts +++ b/packages/vrender/src/core/contributions/layerHandler/canvas2d-contribution.ts @@ -14,7 +14,7 @@ import type { IDrawContext } from '../../../interface'; import type { IBounds } from '@visactor/vutils'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; @injectable() export class CanvasLayerHandlerContribution implements ILayerHandlerContribution { @@ -23,7 +23,7 @@ export class CanvasLayerHandlerContribution implements ILayerHandlerContribution context: IContext2d; offscreen: boolean; - constructor(@inject(Global) public readonly global: IGlobal) { + constructor(@inject(VGlobal) public readonly global: IGlobal) { this.offscreen = false; } diff --git a/packages/vrender/src/core/contributions/layerHandler/offscreen2d-contribution.ts b/packages/vrender/src/core/contributions/layerHandler/offscreen2d-contribution.ts index f97cfd8b4..e0f751ed5 100644 --- a/packages/vrender/src/core/contributions/layerHandler/offscreen2d-contribution.ts +++ b/packages/vrender/src/core/contributions/layerHandler/offscreen2d-contribution.ts @@ -13,7 +13,7 @@ import type { ILayerHandlerDrawParams } from '../../../interface'; import type { IBoundsLike } from '@visactor/vutils'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; @injectable() export class OffscreenLayerHandlerContribution implements ILayerHandlerContribution { @@ -22,7 +22,7 @@ export class OffscreenLayerHandlerContribution implements ILayerHandlerContribut context: IContext2d; offscreen: boolean; - constructor(@inject(Global) public readonly global: IGlobal) { + constructor(@inject(VGlobal) public readonly global: IGlobal) { this.offscreen = true; } diff --git a/packages/vrender/src/core/contributions/window/browser-contribution.ts b/packages/vrender/src/core/contributions/window/browser-contribution.ts index 76d9aabcb..b5e7757ba 100644 --- a/packages/vrender/src/core/contributions/window/browser-contribution.ts +++ b/packages/vrender/src/core/contributions/window/browser-contribution.ts @@ -12,7 +12,7 @@ import type { IWindowParams } from '../../../interface'; import type { IBoundsLike } from '@visactor/vutils'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; @injectable() export class BrowserWindowHandlerContribution @@ -31,7 +31,7 @@ export class BrowserWindowHandlerContribution return this.canvas.nativeCanvas.parentElement; } - constructor(@inject(Global) private readonly global: IGlobal) { + constructor(@inject(VGlobal) private readonly global: IGlobal) { super(); } diff --git a/packages/vrender/src/core/contributions/window/feishu-contribution.ts b/packages/vrender/src/core/contributions/window/feishu-contribution.ts index e81a2dfd7..e55325a00 100644 --- a/packages/vrender/src/core/contributions/window/feishu-contribution.ts +++ b/packages/vrender/src/core/contributions/window/feishu-contribution.ts @@ -12,7 +12,7 @@ import type { IWindowParams } from '../../../interface'; import type { IBoundsLike } from '@visactor/vutils'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; class MiniAppEventManager { addEventListener(type: string, func: EventListenerOrEventListenerObject) { @@ -57,7 +57,7 @@ export class FeishuWindowHandlerContribution return null; } - constructor(@inject(Global) private readonly global: IGlobal) { + constructor(@inject(VGlobal) private readonly global: IGlobal) { super(); } diff --git a/packages/vrender/src/core/contributions/window/lynx-contribution.ts b/packages/vrender/src/core/contributions/window/lynx-contribution.ts index 67a224294..3251d7c22 100644 --- a/packages/vrender/src/core/contributions/window/lynx-contribution.ts +++ b/packages/vrender/src/core/contributions/window/lynx-contribution.ts @@ -12,7 +12,7 @@ import type { IWindowHandlerContribution } from '../../../interface'; import type { IBoundsLike } from '@visactor/vutils'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; class MiniAppEventManager { addEventListener(type: string, func: EventListenerOrEventListenerObject) { @@ -54,7 +54,7 @@ export class LynxWindowHandlerContribution extends BaseWindowHandlerContribution return null; } - constructor(@inject(Global) private readonly global: IGlobal) { + constructor(@inject(VGlobal) private readonly global: IGlobal) { super(); } diff --git a/packages/vrender/src/core/contributions/window/native-contribution.ts b/packages/vrender/src/core/contributions/window/native-contribution.ts index b4f33d37d..873e3bca5 100644 --- a/packages/vrender/src/core/contributions/window/native-contribution.ts +++ b/packages/vrender/src/core/contributions/window/native-contribution.ts @@ -1,6 +1,6 @@ // import { createCanvas, createImageData, loadImage } from 'canvas'; // import { inject, injectable } from 'inversify'; -// import { Global, IWindow, EnvType, IGlobal, IWindowHandlerContribution, IWindowParams } from '../..'; +// import { VGlobal, IWindow, EnvType, IGlobal, IWindowHandlerContribution, IWindowParams } from '../..'; // import { BaseWindowHandlerContribution } from './base-contribution'; // type NodePkg = { @@ -27,7 +27,7 @@ // return null; // } -// constructor(@inject(Global) private readonly global: IGlobal) { +// constructor(@inject(VGlobal) private readonly global: IGlobal) { // super(); // } diff --git a/packages/vrender/src/core/contributions/window/node-contribution.ts b/packages/vrender/src/core/contributions/window/node-contribution.ts index c660ed29f..fd69d2770 100644 --- a/packages/vrender/src/core/contributions/window/node-contribution.ts +++ b/packages/vrender/src/core/contributions/window/node-contribution.ts @@ -12,7 +12,7 @@ import type { IWindowHandlerContribution, IWindowParams } from '../../../interface'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; @injectable() export class NodeWindowHandlerContribution extends BaseWindowHandlerContribution implements IWindowHandlerContribution { @@ -24,7 +24,7 @@ export class NodeWindowHandlerContribution extends BaseWindowHandlerContribution return null; } - constructor(@inject(Global) private readonly global: IGlobal) { + constructor(@inject(VGlobal) private readonly global: IGlobal) { super(); } diff --git a/packages/vrender/src/core/contributions/window/taro-contribution.ts b/packages/vrender/src/core/contributions/window/taro-contribution.ts index 9bad8f9b1..5abde4a21 100644 --- a/packages/vrender/src/core/contributions/window/taro-contribution.ts +++ b/packages/vrender/src/core/contributions/window/taro-contribution.ts @@ -12,7 +12,7 @@ import type { IWindowHandlerContribution } from '../../../interface'; import type { IBoundsLike } from '@visactor/vutils'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; class MiniAppEventManager { addEventListener(type: string, func: EventListenerOrEventListenerObject) { @@ -54,7 +54,7 @@ export class TaroWindowHandlerContribution extends BaseWindowHandlerContribution return null; } - constructor(@inject(Global) private readonly global: IGlobal) { + constructor(@inject(VGlobal) private readonly global: IGlobal) { super(); } diff --git a/packages/vrender/src/core/contributions/window/wx-contribution.ts b/packages/vrender/src/core/contributions/window/wx-contribution.ts index 2db87154f..095b9d026 100644 --- a/packages/vrender/src/core/contributions/window/wx-contribution.ts +++ b/packages/vrender/src/core/contributions/window/wx-contribution.ts @@ -12,7 +12,7 @@ import type { IWindowParams } from '../../../interface'; import type { IBoundsLike } from '@visactor/vutils'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; class MiniAppEventManager { addEventListener(type: string, func: EventListenerOrEventListenerObject) { @@ -54,7 +54,7 @@ export class WxWindowHandlerContribution extends BaseWindowHandlerContribution i return null; } - constructor(@inject(Global) private readonly global: IGlobal) { + constructor(@inject(VGlobal) private readonly global: IGlobal) { super(); } diff --git a/packages/vrender/src/core/core-modules.ts b/packages/vrender/src/core/core-modules.ts index 4fc9fa4ff..94501693f 100644 --- a/packages/vrender/src/core/core-modules.ts +++ b/packages/vrender/src/core/core-modules.ts @@ -2,17 +2,17 @@ import { ContainerModule } from 'inversify'; import { DefaultGlobal } from './global'; import { DefaultGraphicUtil, DefaultTransformUtil } from './graphic-utils'; import { DefaultLayerService } from './layer-service'; -import { DefaultWindow, Window } from './window'; +import { DefaultWindow, VWindow } from './window'; import { GraphicUtil, LayerService, TransformUtil } from './constants'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; export default new ContainerModule(bind => { // global对象,全局单例模式 bind(DefaultGlobal).toSelf().inSingletonScope(); - bind(Global).toService(DefaultGlobal); + bind(VGlobal).toService(DefaultGlobal); bind(DefaultWindow).to(DefaultWindow); - bind(Window).toService(DefaultWindow); + bind(VWindow).toService(DefaultWindow); bind(DefaultGraphicUtil).toSelf().inSingletonScope(); bind(GraphicUtil).toService(DefaultGraphicUtil); bind(DefaultTransformUtil).toSelf().inSingletonScope(); @@ -24,7 +24,7 @@ export default new ContainerModule(bind => { // bind<(params: Partial) => IStage>(StageFactory).toFactory((context: interface.Context) => { // return (params: Partial) => { // const g = context.container.get(Global); - // const ws = context.container.get(Window); + // const ws = context.container.get(VWindow); // const rs = context.container.get(RenderService); // const layer = context.container.get(Layer); // return new DefaultStage(params, g, ws, rs, layer); diff --git a/packages/vrender/src/core/global-module.ts b/packages/vrender/src/core/global-module.ts index 8232c8bcc..f3ece3c7b 100644 --- a/packages/vrender/src/core/global-module.ts +++ b/packages/vrender/src/core/global-module.ts @@ -1,21 +1,21 @@ -import { ContainerModule } from 'inversify'; -import type { IGlobal } from '../interface'; -import { container } from '../container'; -import { DefaultGlobal } from './global'; -import envModules from './contributions/env/modules'; -import { Global } from '../constants'; +// import { ContainerModule } from 'inversify'; +// import type { IGlobal } from '../interface'; +// import { container } from '../container'; +// import { DefaultGlobal } from './global'; +// import envModules from './contributions/env/modules'; +// import { Global } from '../constants'; -const globalModule = new ContainerModule((bind, unbind, isBound) => { - // global对象,全局单例模式 - if (!isBound(Global)) { - bind(DefaultGlobal).toSelf().inSingletonScope(); - bind(Global).toService(DefaultGlobal); - } -}); +// const globalModule = new ContainerModule((bind, unbind, isBound) => { +// // global对象,全局单例模式 +// if (!isBound(Global)) { +// bind(DefaultGlobal).toSelf().inSingletonScope(); +// bind(Global).toService(DefaultGlobal); +// } +// }); -container.load(globalModule); -container.load(envModules); +// container.load(globalModule); +// container.load(envModules); -export const global = container.get(Global); +// export const global = container.get(Global); -export default globalModule; +// export default globalModule; diff --git a/packages/vrender/src/core/graphic-utils.ts b/packages/vrender/src/core/graphic-utils.ts index 5bb6c06fe..d05da9dbd 100644 --- a/packages/vrender/src/core/graphic-utils.ts +++ b/packages/vrender/src/core/graphic-utils.ts @@ -9,7 +9,7 @@ import { DefaultTextStyle } from '../graphic/config'; import type { IMatrix, IPointLike, ITextMeasureOption } from '@visactor/vutils'; import { Matrix, TextMeasure } from '@visactor/vutils'; import type { IGraphicUtil, ITransformUtil, TransformType } from '../interface/core'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; @injectable() export class DefaultGraphicUtil implements IGraphicUtil { @@ -22,7 +22,7 @@ export class DefaultGraphicUtil implements IGraphicUtil { @inject(ContributionProvider) @named(TextMeasureContribution) protected readonly contributions: IContributionProvider, - @inject(Global) public readonly global: IGlobal + @inject(VGlobal) public readonly global: IGlobal ) { this.configured = false; this.global.hooks.onSetEnv.tap('graphic-util', (lastEnv, env, global) => { diff --git a/packages/vrender/src/core/layer-service.ts b/packages/vrender/src/core/layer-service.ts index 6a689a408..4a979f2bc 100644 --- a/packages/vrender/src/core/layer-service.ts +++ b/packages/vrender/src/core/layer-service.ts @@ -2,7 +2,7 @@ import { inject, injectable } from 'inversify'; import type { ILayer, IStage, IGlobal, ILayerParams } from '../interface'; import { Layer } from './layer'; import type { ILayerService } from '../interface/core'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; @injectable() export class DefaultLayerService implements ILayerService { @@ -10,7 +10,7 @@ export class DefaultLayerService implements ILayerService { declare staticLayerCountInEnv: number; declare dynamicLayerCountInEnv: number; declare inited: boolean; - constructor(@inject(Global) public readonly global: IGlobal) { + constructor(@inject(VGlobal) public readonly global: IGlobal) { this.layerMap = new Map(); } diff --git a/packages/vrender/src/core/stage.ts b/packages/vrender/src/core/stage.ts index e16e22dab..d71960def 100644 --- a/packages/vrender/src/core/stage.ts +++ b/packages/vrender/src/core/stage.ts @@ -24,7 +24,7 @@ import type { IContributionProvider, ILayerService } from '../interface'; -import { Window } from './window'; +import { VWindow } from './window'; import type { Layer } from './layer'; import { EventSystem } from '../event'; import { container } from '../container'; @@ -40,7 +40,7 @@ import { defaultTicker } from '../animate/default-ticker'; import { SyncHook } from '../tapable'; import { DirectionalLight } from './light'; import { OrthoCamera } from './camera'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; import { LayerService } from './constants'; const DefaultConfig = { @@ -184,8 +184,8 @@ export class Stage extends Group implements IStage { beforeRender: new SyncHook(['stage']), afterRender: new SyncHook(['stage']) }; - this.global = container.get(Global); - this.window = container.get(Window); + this.global = container.get(VGlobal); + this.window = container.get(VWindow); this.renderService = container.get(RenderService); this.pickerService = container.get(PickerService); this.pluginService = container.get(PluginService); @@ -718,7 +718,7 @@ export class Stage extends Group implements IStage { * @returns */ renderToNewWindow(fullImage: boolean = true): IWindow { - const window = container.get(Window); + const window = container.get(VWindow); if (fullImage) { window.create({ width: this.viewWidth, diff --git a/packages/vrender/src/core/window.ts b/packages/vrender/src/core/window.ts index 15413861e..a7c3107d2 100644 --- a/packages/vrender/src/core/window.ts +++ b/packages/vrender/src/core/window.ts @@ -12,9 +12,9 @@ import type { } from '../interface'; import { container } from '../container'; import { SyncHook } from '../tapable'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; -export const Window = Symbol.for('Window'); +export const VWindow = Symbol.for('VWindow'); export const WindowHandlerContribution = Symbol.for('WindowHandlerContribution'); @@ -74,7 +74,7 @@ export class DefaultWindow implements IWindow { } constructor( - @inject(Global) public readonly global: IGlobal // @inject(ContributionProvider) // @named(WindowHandlerContribution) // protected readonly contributions: ContributionProvider + @inject(VGlobal) public readonly global: IGlobal // @inject(ContributionProvider) // @named(WindowHandlerContribution) // protected readonly contributions: ContributionProvider ) { this._uid = Generator.GenAutoIncrementId(); } diff --git a/packages/vrender/src/modules.ts b/packages/vrender/src/modules.ts index b6031ecb0..d00cd7148 100644 --- a/packages/vrender/src/modules.ts +++ b/packages/vrender/src/modules.ts @@ -18,7 +18,7 @@ import type { IGraphicUtil, ILayerService, ITransformUtil } from './interface/co import { GraphicService } from './graphic/constants'; import { GraphicUtil, TransformUtil } from './core/constants'; import { container } from './container'; -import { Global } from './constants'; +import { VGlobal } from './constants'; container.load(coreModule); container.load(graphicModule); @@ -32,8 +32,8 @@ loadPickContributions(container); loadCanvasContributions(container); // 全局变量 -export const global = container.get(Global); -application.global = global; +export const vglobal = container.get(VGlobal); +application.global = vglobal; export const graphicUtil = container.get(GraphicUtil); application.graphicUtil = graphicUtil; export const transformUtil = container.get(TransformUtil); diff --git a/packages/vrender/src/picker/canvas-picker-service.ts b/packages/vrender/src/picker/canvas-picker-service.ts index cbcdef712..91c3ff1c8 100644 --- a/packages/vrender/src/picker/canvas-picker-service.ts +++ b/packages/vrender/src/picker/canvas-picker-service.ts @@ -33,7 +33,7 @@ import { import { DefaultPickService } from './picker-service'; import { DrawContribution } from '../render'; import { PickItemInterceptor } from './pick-interceptor'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; // 默认的pick-service,提供基本的最优选中策略,尽量不需要用户自己实现contribution // 用户可以写plugin @@ -63,7 +63,7 @@ export class DefaultCanvasPickerService extends DefaultPickService implements IP @inject(DrawContribution) public readonly drawContribution: IDrawContribution, - @inject(Global) public readonly global: IGlobal, + @inject(VGlobal) public readonly global: IGlobal, // 拦截器 @inject(ContributionProvider) @named(PickItemInterceptor) diff --git a/packages/vrender/src/picker/global-picker-service.ts b/packages/vrender/src/picker/global-picker-service.ts index 6ac03754c..8c7b21802 100644 --- a/packages/vrender/src/picker/global-picker-service.ts +++ b/packages/vrender/src/picker/global-picker-service.ts @@ -11,7 +11,7 @@ import type { IPickParams, PickResult } from '../interface'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; export const BoundsPicker = Symbol.for('BoundsPicker'); @@ -29,7 +29,7 @@ export class DefaultGlobalPickerService implements IPickerService { // @inject(ContributionProvider) // @named(PickerContribution) // protected readonly contributions: ContributionProvider, - @inject(Global) public readonly global: IGlobal + @inject(VGlobal) public readonly global: IGlobal ) { this.global.hooks.onSetEnv.tap('global-picker-service', (lastEnv, env, global) => { this.configure(global, env); diff --git a/packages/vrender/src/picker/math-picker-service.ts b/packages/vrender/src/picker/math-picker-service.ts index 8d4bc7ebc..a7b07199a 100644 --- a/packages/vrender/src/picker/math-picker-service.ts +++ b/packages/vrender/src/picker/math-picker-service.ts @@ -17,7 +17,7 @@ import type { import { DefaultPickService } from './picker-service'; import { EmptyContext2d } from '../canvas'; import { MathPickerContribution } from './contributions/constants'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; import { PickItemInterceptor } from './pick-interceptor'; // 默认的pick-service,提供基本的最优选中策略,尽量不需要用户自己实现contribution @@ -34,7 +34,7 @@ export class DefaultMathPickerService extends DefaultPickService implements IPic @inject(ContributionProvider) @named(MathPickerContribution) protected readonly contributions: IContributionProvider, - @inject(Global) public readonly global: IGlobal, + @inject(VGlobal) public readonly global: IGlobal, // 拦截器 @inject(ContributionProvider) @named(PickItemInterceptor) diff --git a/packages/vrender/src/picker/picker-service.ts b/packages/vrender/src/picker/picker-service.ts index bcaa851ab..fdbd0470e 100644 --- a/packages/vrender/src/picker/picker-service.ts +++ b/packages/vrender/src/picker/picker-service.ts @@ -20,7 +20,7 @@ import type { import { DefaultAttribute, getTheme, mat3Tomat4, multiplyMat4Mat4 } from '../graphic'; import { mat4Allocate, matrixAllocate } from '../allocator/matrix-allocate'; import { PickItemInterceptor } from './pick-interceptor'; -import { Global } from '../constants'; +import { VGlobal } from '../constants'; export const GraphicPicker = Symbol.for('GraphicPicker'); @@ -34,7 +34,7 @@ export abstract class DefaultPickService implements IPickerService { declare InterceptorContributions: IPickItemInterceptorContribution[]; constructor( - @inject(Global) public readonly global: IGlobal, + @inject(VGlobal) public readonly global: IGlobal, // 拦截器 @inject(ContributionProvider) @named(PickItemInterceptor) diff --git a/packages/vrender/src/render/contributions/render/incremental-draw-contribution.ts b/packages/vrender/src/render/contributions/render/incremental-draw-contribution.ts index 294f83e68..f01391ddc 100644 --- a/packages/vrender/src/render/contributions/render/incremental-draw-contribution.ts +++ b/packages/vrender/src/render/contributions/render/incremental-draw-contribution.ts @@ -23,7 +23,7 @@ import { DrawItemInterceptor } from './draw-interceptor'; import { ContributionProvider } from '../../../common/contribution-provider'; import { foreachAsync } from '../../../common/sort'; import type { ILayerService } from '../../../interface/core'; -import { Global } from '../../../constants'; +import { VGlobal } from '../../../constants'; enum STATUS { NORMAL = 0, @@ -47,7 +47,7 @@ export class DefaultIncrementalDrawContribution extends DefaultDrawContribution protected lastRenderService: IRenderService; protected lastDrawContext: IDrawContext; protected count: number; - @inject(Global) global: IGlobal; + @inject(VGlobal) global: IGlobal; constructor( // @inject(ContributionProvider) From 378111c8f0eb3dad5aa8e6ec724d028decee5529 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Mon, 24 Jul 2023 19:44:49 +0800 Subject: [PATCH 02/26] refactor: rename global to vglobal --- .../vrender-components/src/slider/slider.ts | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/vrender-components/src/slider/slider.ts b/packages/vrender-components/src/slider/slider.ts index 013bc8da8..634d91f86 100644 --- a/packages/vrender-components/src/slider/slider.ts +++ b/packages/vrender-components/src/slider/slider.ts @@ -5,22 +5,17 @@ import { isNil, merge, clamp, isValid, array, isObject, isArray } from '@visacto * 3. step 功能开发 * 4. tooltip 功能开发 */ -import { - createGroup, - createText, +import type { IGroup, ISymbol, IGraphic, - global, - createRect, - createSymbol, ISymbolGraphicAttribute, IText, ITextGraphicAttribute, FederatedPointerEvent, - CustomEvent, Cursor } from '@visactor/vrender'; +import { createGroup, createText, vglobal, createRect, createSymbol, CustomEvent } from '@visactor/vrender'; import { AbstractComponent } from '../core/base'; import { SLIDER_ELEMENT_NAME } from './constant'; @@ -498,9 +493,9 @@ export class Slider extends AbstractComponent> { e.stopPropagation(); this._currentHandler = e.target as unknown as IGraphic; this._prePos = this._isHorizontal ? e.clientX : e.clientY; - if (global.env === 'browser') { - global.addEventListener('pointermove', this._onHandlerPointerMove as EventListenerOrEventListenerObject); - global.addEventListener('pointerup', this._onHandlerPointerUp as EventListenerOrEventListenerObject); + if (vglobal.env === 'browser') { + vglobal.addEventListener('pointermove', this._onHandlerPointerMove as EventListenerOrEventListenerObject); + vglobal.addEventListener('pointerup', this._onHandlerPointerUp as EventListenerOrEventListenerObject); } else { this._currentHandler.addEventListener( 'pointermove', @@ -555,9 +550,9 @@ export class Slider extends AbstractComponent> { private _onHandlerPointerUp = (e: FederatedPointerEvent) => { e.preventDefault(); this._currentHandler = null; - if (global.env === 'browser') { - global.removeEventListener('pointermove', this._onHandlerPointerMove as EventListenerOrEventListenerObject); - global.removeEventListener('pointerup', this._onHandlerPointerUp as EventListenerOrEventListenerObject); + if (vglobal.env === 'browser') { + vglobal.removeEventListener('pointermove', this._onHandlerPointerMove as EventListenerOrEventListenerObject); + vglobal.removeEventListener('pointerup', this._onHandlerPointerUp as EventListenerOrEventListenerObject); } else { const currentTarget = e.target; currentTarget.removeEventListener( @@ -575,9 +570,9 @@ export class Slider extends AbstractComponent> { private _onTrackPointerdown = (e: FederatedPointerEvent) => { e.stopPropagation(); this._prePos = this._isHorizontal ? e.clientX : e.clientY; - if (global.env === 'browser') { - global.addEventListener('pointermove', this._onTrackPointerMove as EventListenerOrEventListenerObject); - global.addEventListener('pointerup', this._onTrackPointerUp as EventListenerOrEventListenerObject); + if (vglobal.env === 'browser') { + vglobal.addEventListener('pointermove', this._onTrackPointerMove as EventListenerOrEventListenerObject); + vglobal.addEventListener('pointerup', this._onTrackPointerUp as EventListenerOrEventListenerObject); } else { this._track.addEventListener('pointermove', this._onTrackPointerMove as EventListenerOrEventListenerObject); this._track.addEventListener('pointerup', this._onTrackPointerUp as EventListenerOrEventListenerObject); @@ -638,9 +633,9 @@ export class Slider extends AbstractComponent> { private _onTrackPointerUp = (e: FederatedPointerEvent) => { e.preventDefault(); - if (global.env === 'browser') { - global.removeEventListener('pointermove', this._onTrackPointerMove as EventListenerOrEventListenerObject); - global.removeEventListener('pointerup', this._onTrackPointerUp as EventListenerOrEventListenerObject); + if (vglobal.env === 'browser') { + vglobal.removeEventListener('pointermove', this._onTrackPointerMove as EventListenerOrEventListenerObject); + vglobal.removeEventListener('pointerup', this._onTrackPointerUp as EventListenerOrEventListenerObject); } else { this._track.removeEventListener('pointermove', this._onTrackPointerMove as EventListenerOrEventListenerObject); this._track.removeEventListener('pointerup', this._onTrackPointerUp as EventListenerOrEventListenerObject); From 9d08f00a15ebbd323b9079aa3dd78254aa4f61eb Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Tue, 1 Aug 2023 20:13:30 +0800 Subject: [PATCH 03/26] feat: add basic logic of connect line and area, closed #109 --- ...-connected-line-area_2023-08-01-12-30.json | 10 ++ docs/demos/src/pages/area.ts | 50 ++++--- docs/demos/src/pages/line.ts | 64 ++++++--- packages/vrender/src/common/render-area.ts | 91 +++++++++--- packages/vrender/src/common/render-curve.ts | 123 +++++++++++++--- packages/vrender/src/graphic/config.ts | 23 ++- packages/vrender/src/interface/graphic.ts | 18 +++ .../vrender/src/interface/graphic/area.ts | 6 +- .../vrender/src/interface/graphic/line.ts | 6 +- .../contributions/render/area-render.ts | 131 +++++++++++++++++- .../contributions/render/line-render.ts | 51 +++++++ 11 files changed, 478 insertions(+), 95 deletions(-) create mode 100644 common/changes/@visactor/vrender/feat-connected-line-area_2023-08-01-12-30.json diff --git a/common/changes/@visactor/vrender/feat-connected-line-area_2023-08-01-12-30.json b/common/changes/@visactor/vrender/feat-connected-line-area_2023-08-01-12-30.json new file mode 100644 index 000000000..52e6fd146 --- /dev/null +++ b/common/changes/@visactor/vrender/feat-connected-line-area_2023-08-01-12-30.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender", + "comment": "feat: add connect type to line and area", + "type": "minor" + } + ], + "packageName": "@visactor/vrender" +} \ No newline at end of file diff --git a/docs/demos/src/pages/area.ts b/docs/demos/src/pages/area.ts index f6a8812c4..430dc88a9 100644 --- a/docs/demos/src/pages/area.ts +++ b/docs/demos/src/pages/area.ts @@ -32,29 +32,33 @@ const points = [ export const page = () => { const graphics: IGraphic[] = []; - // ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { - // graphics.push( - // createArea({ - // points, - // curveType: type as any, - // x: ((i * 300) % 900) + 100, - // y: Math.floor((i * 300) / 900) * 200, - // stroke: ['black', false], - // fill: { - // gradient: 'linear', - // x0: 0, - // y0: 0, - // x1: 1, - // y1: 0, - // stops: [ - // { offset: 0, color: 'green' }, - // { offset: 0.5, color: 'orange' }, - // { offset: 1, color: 'red' } - // ] - // } - // }) - // ); - // }); + ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { + graphics.push( + createArea({ + points, + curveType: type as any, + x: ((i * 300) % 900) + 100, + y: Math.floor((i * 300) / 900) * 200, + stroke: ['black', false], + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 1, + y1: 0, + stops: [ + { offset: 0, color: 'green' }, + { offset: 0.5, color: 'orange' }, + { offset: 1, color: 'red' } + ] + }, + connectedType: 'connect', + connectedStyle: { + fill: 'grey' + } + }) + ); + }); ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { i += 7; diff --git a/docs/demos/src/pages/line.ts b/docs/demos/src/pages/line.ts index 280611144..dbd4381bb 100644 --- a/docs/demos/src/pages/line.ts +++ b/docs/demos/src/pages/line.ts @@ -9,8 +9,9 @@ const subP1 = [ [20, 40], [40, 60], [60, 20], - [70, 30] -].map(item => ({ x: item[0], y: item[1], defined: item[0] !== 70 })); + [70, 30], + [75, 60] +].map(item => ({ x: item[0], y: item[1], defined: item[0] !== 60 })); const subP2 = [ [80, 80], @@ -36,30 +37,51 @@ const points = [ export const page = () => { const graphics: IGraphic[] = []; ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { - graphics.push(createLine({ - points, - curveType: type as any, - x: (i * 300) % 900 + 100, - y: (Math.floor(i * 300 / 900)) * 200, - stroke: 'red' - })); + graphics.push( + createLine({ + points, + curveType: type as any, + x: ((i * 300) % 900) + 100, + y: Math.floor((i * 300) / 900) * 200, + stroke: 'red', + connectedType: 'zero', + connectedStyle: { + stroke: 'green' + }, + connectedX: null, + connectedY: 100 + }) + ); }); ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { i += 7; - graphics.push(createLine({ - points, - curveType: type as any, - x: (i * 300) % 900 + 100, - y: (Math.floor(i * 300 / 900)) * 200, - segments: [ - { points: subP1, stroke: colorPools[3], lineWidth: 6 }, - { points: subP2, stroke: colorPools[2], lineWidth: 2, lineDash: [3, 3] } - ], - stroke: 'red' - })); + graphics.push( + createLine({ + points, + curveType: type as any, + x: ((i * 300) % 900) + 100, + y: Math.floor((i * 300) / 900) * 200, + segments: [ + { + points: subP1, + stroke: colorPools[3], + lineWidth: 6, + connectedType: 'connect', + connectedStyle: { + stroke: 'green' + } + }, + { points: subP2, stroke: colorPools[2], lineWidth: 2, lineDash: [3, 3] } + ], + stroke: 'red' + }) + ); }); + graphics.forEach(item => { + item.animate().to({ clipRange: 0 }, 0, 'linear').to({ clipRange: 1 }, 1000, 'linear'); + }); const stage = createStage({ canvas: 'main', @@ -68,5 +90,5 @@ export const page = () => { graphics.forEach(g => { stage.defaultLayer.add(g); - }) + }); }; diff --git a/packages/vrender/src/common/render-area.ts b/packages/vrender/src/common/render-area.ts index 02b5234ce..4cbe701fb 100644 --- a/packages/vrender/src/common/render-area.ts +++ b/packages/vrender/src/common/render-area.ts @@ -23,35 +23,65 @@ export function drawAreaSegments( offsetY?: number; offsetZ?: number; direction?: IDirection; + drawConnect?: boolean; // 是否是绘制connect区域的效果 + mode?: 'none' | 'connect' | 'zero'; + zeroX?: number; + zeroY?: number; } ) { - // const { offsetX = 0, offsetY = 0 } = params || {}; + const { drawConnect = false, mode = 'none' } = params || {}; + if (drawConnect && mode === 'none') { + return; + } // let needMoveTo: boolean = true; const { top, bottom } = segPath; if (percent >= 1) { const topList: ICurve[] = []; const bottomList: ICurve[] = []; let lastDefined: boolean = true; - 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; + if (drawConnect) { + for (let i = 0, n = top.curves.length; i < n; i++) { + const topCurve = top.curves[i]; + if (lastDefined !== topCurve.defined) { + if (!lastDefined) { + drawAreaConnectBlock(path, topList, bottomList, params); + topList.length = 0; + bottomList.length = 0; + } else { + topList.push(topCurve); + bottomList.push(bottom.curves[n - i - 1]); + } + lastDefined = !lastDefined; } else { - topList.push(topCurve); - bottomList.push(bottom.curves[n - i - 1]); + 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); + } 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]); + } } } + drawAreaBlock(path, topList, bottomList, params); } - drawAreaBlock(path, topList, bottomList, params); return; } @@ -160,6 +190,35 @@ export function drawAreaSegments( // } } +function drawAreaConnectBlock( + path: IPath2D, + topList: ICurve[], + bottomList: ICurve[], + params?: { + offsetX?: number; + offsetY?: number; + offsetZ?: number; + } +) { + if (topList.length < 2) { + return; + } + const { offsetX = 0, offsetY = 0, offsetZ = 0 } = params || {}; + let curve = topList[0]; + 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/src/common/render-curve.ts b/packages/vrender/src/common/render-curve.ts index 823f31637..88a6cd24d 100644 --- a/packages/vrender/src/common/render-curve.ts +++ b/packages/vrender/src/common/render-curve.ts @@ -1,4 +1,5 @@ -import { min, IPoint, IPointLike } from '@visactor/vutils'; +import type { IPoint, IPointLike } from '@visactor/vutils'; +import { min } from '@visactor/vutils'; import type { IAreaSegment, @@ -31,24 +32,75 @@ export function drawSegments( offsetX?: number; offsetY?: number; offsetZ?: number; + drawConnect?: boolean; // 是否是绘制connect区域的效果 + mode?: 'none' | 'connect' | 'zero'; + zeroX?: number; + zeroY?: number; } ) { - const { offsetX = 0, offsetY = 0, offsetZ = 0 } = params || {}; + const { + offsetX = 0, + offsetY = 0, + offsetZ = 0, + mode = 'none', + drawConnect = false, + zeroX = 0, + zeroY = 0 + } = params || {}; + // none的connect不需要draw + if (drawConnect && mode === 'none') { + return; + } let needMoveTo: boolean = true; const { curves } = segPath; if (percent >= 1) { - curves.forEach(curve => { - // 跳过这个点 - if (!curve.defined) { - needMoveTo = true; - return; - } - if (needMoveTo) { - path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); - } - drawSegItem(path, curve, 1, params); - needMoveTo = false; - }); + if (drawConnect) { + curves.forEach((curve, i) => { + if (curve.defined) { + // connect段结束,封闭 + if (needMoveTo && i !== 0) { + path.lineTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + } else if (!needMoveTo) { + // 持续moveTo + // if (curve.p2 && curve.p3) { + // path.moveTo(curve.p3.x + offsetX, curve.p3.y + offsetY, offsetZ); + // } else { + // path.moveTo(curve.p1.x + offsetX, curve.p1.y + offsetY, offsetZ); + // } + } + needMoveTo = false; + } else { + // connect段开始 + if (!needMoveTo) { + path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + } else { + // 如果是zero,那么每一段都要绘制一下(第一段不需要绘制) + if (mode === 'zero') { + path.lineTo( + (isFinite(zeroX) ? zeroX : curve.p0.x) + offsetX, + (isFinite(zeroY) ? zeroY : curve.p0.y) + offsetY, + offsetZ + ); + } + } + needMoveTo = true; + } + }); + } else { + curves.forEach(curve => { + // 跳过这个点 + if (!curve.defined) { + needMoveTo = true; + return; + } + if (needMoveTo) { + path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + } + drawSegItem(path, curve, 1, params); + needMoveTo = false; + }); + } + return; } if (percent <= 0) { @@ -80,16 +132,41 @@ export function drawSegments( break; } - // 跳过这个点 - if (!curve.defined) { - needMoveTo = true; - continue; - } - if (needMoveTo) { - path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + if (drawConnect) { + if (curve.defined) { + // connect段结束,封闭 + if (needMoveTo && i !== 0) { + path.lineTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + } + needMoveTo = false; + } else { + // connect段开始 + if (!needMoveTo) { + path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + } else { + // 如果是zero,那么每一段都要绘制一下(第一段不需要绘制) + if (mode === 'zero') { + path.lineTo( + (isFinite(zeroX) ? zeroX : curve.p0.x) + offsetX, + (isFinite(zeroY) ? zeroY : curve.p0.y) + offsetY, + offsetZ + ); + } + } + needMoveTo = true; + } + } else { + // 跳过这个点 + if (!curve.defined) { + needMoveTo = true; + continue; + } + if (needMoveTo) { + path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + } + drawSegItem(path, curve, min(_p, 1), params); + needMoveTo = false; } - drawSegItem(path, curve, min(_p, 1), params); - needMoveTo = false; } } diff --git a/packages/vrender/src/graphic/config.ts b/packages/vrender/src/graphic/config.ts index 14a1acbc4..f6fbe4103 100644 --- a/packages/vrender/src/graphic/config.ts +++ b/packages/vrender/src/graphic/config.ts @@ -26,7 +26,8 @@ import type { RichTextVerticalDirection, RichTextGlobalAlignType, RichTextGlobalBaselineType, - IRichTextIconGraphicAttribute + IRichTextIconGraphicAttribute, + IConnectedStyle } from '../interface'; export const DefaultTransform: ITransform = { @@ -109,6 +110,24 @@ export const DefaultStyle: IGraphicStyle = { ...DefaultStrokeStyle }; +export const DefaultConnectAttribute: Required = { + connectedType: 'none', + // connectedStyle: { + // stroke: DefaultStrokeStyle.stroke, + // strokeOpacity: DefaultStrokeStyle.strokeOpacity, + // lineDash: DefaultStrokeStyle.lineDash, + // lineDashOffset: DefaultStrokeStyle.lineDashOffset, + // lineCap: DefaultStrokeStyle.lineCap, + // lineJoin: DefaultStrokeStyle.lineJoin, + // lineWidth: DefaultStrokeStyle.lineWidth, + // fill: DefaultFillStyle.fill, + // fillOpacity: DefaultFillStyle.fillOpacity + // }, + connectedStyle: {}, // 默认全都继承父属性 + connectedX: NaN, + connectedY: NaN +} as IConnectedStyle; + export const DefaultAttribute: Required = { strokeSeg: null, pickable: true, @@ -149,6 +168,7 @@ export const DefaultArcAttribute: Required = { export const DefaultAreaAttribute: Required = { ...DefaultAttribute, + ...DefaultConnectAttribute, points: [], segments: [], curveType: 'linear', @@ -183,6 +203,7 @@ export const DefaultGlyphAttribute: Required = { export const DefaultLineAttribute: Required = { ...DefaultAttribute, + ...DefaultConnectAttribute, points: [], segments: [], curveType: 'linear', diff --git a/packages/vrender/src/interface/graphic.ts b/packages/vrender/src/interface/graphic.ts index 8942ee1d9..4191583a4 100644 --- a/packages/vrender/src/interface/graphic.ts +++ b/packages/vrender/src/interface/graphic.ts @@ -148,6 +148,24 @@ export type IStrokeStyle = { type TextureType = 'circle' | 'diamond' | 'rect' | 'vertical-line' | 'horizontal-line' | 'bias-lr' | 'bias-rl' | 'grid'; +export type IConnectedStyle = { + // 连接,取零或者断开 + connectedType: 'connect' | 'zero' | 'none'; + connectedStyle: { + stroke: IStrokeStyle['stroke']; + strokeOpacity: IStrokeStyle['strokeOpacity']; + lineDash: IStrokeStyle['lineDash']; + lineDashOffset: IStrokeStyle['lineDashOffset']; + lineCap: IStrokeStyle['lineCap']; + lineJoin: IStrokeStyle['lineJoin']; + lineWidth: IStrokeStyle['lineWidth']; + fill: IFillStyle['fill']; + fillOpacity: IFillStyle['fillOpacity']; + }; + connectedX: number; + connectedY: number; +}; + export type IGraphicStyle = IFillStyle & IStrokeStyle & { opacity: number; diff --git a/packages/vrender/src/interface/graphic/area.ts b/packages/vrender/src/interface/graphic/area.ts index c7f219242..35520c3fd 100644 --- a/packages/vrender/src/interface/graphic/area.ts +++ b/packages/vrender/src/interface/graphic/area.ts @@ -1,6 +1,6 @@ import type { IPointLike } from '@visactor/vutils'; import type { ISegPath2D } from '../curve'; -import type { IGraphic, IGraphicAttribute } from '../graphic'; +import type { IConnectedStyle, IGraphic, IGraphicAttribute } from '../graphic'; import type { ICurveType } from '../common'; export type IAreaAttribute = { @@ -15,7 +15,7 @@ export type IAreaCacheItem = { bottom: ISegPath2D; }; -export type IAreaGraphicAttribute = Partial & Partial; +export type IAreaGraphicAttribute = Partial & Partial & Partial; export interface IArea extends IGraphic { cacheArea?: IAreaCacheItem | IAreaCacheItem[]; @@ -26,6 +26,6 @@ type ISegmentStyle = Pick< 'fill' | 'fillOpacity' | 'background' | 'texture' | 'textureColor' | 'textureSize' | 'texturePadding' >; -export interface IAreaSegment extends Partial { +export interface IAreaSegment extends Partial, Partial { points: IPointLike[]; } diff --git a/packages/vrender/src/interface/graphic/line.ts b/packages/vrender/src/interface/graphic/line.ts index 556043e07..023f1f6d6 100644 --- a/packages/vrender/src/interface/graphic/line.ts +++ b/packages/vrender/src/interface/graphic/line.ts @@ -1,5 +1,5 @@ import type { IPointLike } from '@visactor/vutils'; -import type { IGraphicAttribute, IGraphic } from '../graphic'; +import type { IGraphicAttribute, IGraphic, IConnectedStyle } from '../graphic'; import type { ICurveType } from '../common'; import type { ISegPath2D } from '../curve'; @@ -17,7 +17,7 @@ export type ILineAttribute = { clipRangeByDimension: IClipRangeByDimensionType; }; -export type ILineGraphicAttribute = Partial & Partial; +export type ILineGraphicAttribute = Partial & Partial & Partial; export interface ILine extends IGraphic { cache?: ISegPath2D | ISegPath2D[]; @@ -28,7 +28,7 @@ type ISegmentStyle = Pick< 'stroke' | 'strokeOpacity' | 'lineDash' | 'lineDashOffset' | 'lineCap' | 'lineJoin' | 'lineWidth' | 'miterLimit' >; -export interface ISegment extends Partial { +export interface ISegment extends Partial, Partial { points: IPointLike[]; simplify?: boolean; } diff --git a/packages/vrender/src/render/contributions/render/area-render.ts b/packages/vrender/src/render/contributions/render/area-render.ts index 659bc18b9..a15ce8b36 100644 --- a/packages/vrender/src/render/contributions/render/area-render.ts +++ b/packages/vrender/src/render/contributions/render/area-render.ts @@ -194,7 +194,7 @@ export class DefaultCanvasAreaRender implements IGraphicRender { area.cacheArea = { top: topCache, bottom: bottomCache }; } else { - area.cache = null; + area.cacheArea = null; area.clearUpdateShapeTag(); return; } @@ -361,13 +361,118 @@ export class DefaultCanvasAreaRender implements IGraphicRender { 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; + } + + protected _drawSegmentItem( + context: IContext2d, + cache: IAreaCacheItem, + fill: boolean, + fillOpacity: number, + stroke: boolean, + strokeOpacity: number, + attribute: Partial, + defaultAttribute: Required | Partial[], + clipRange: number, + offsetX: number, + offsetY: number, + offsetZ: number, + area: IArea, + drawContext: IDrawContext, + connect: boolean, + fillCb?: ( + ctx: IContext2d, + lineAttribute: Partial, + themeAttribute: IThemeAttribute | IThemeAttribute[] + ) => boolean, + strokeCb?: ( + ctx: IContext2d, + lineAttribute: Partial, + themeAttribute: IThemeAttribute | IThemeAttribute[] + ) => boolean + ) { + // 绘制connect区域 + let { connectedType, connectedX, connectedY, connectedStyle } = attribute; + const da = []; + 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; + } + + if (isArray(defaultAttribute)) { + defaultAttribute.forEach(i => da.push(i)); + } else { + da.push(defaultAttribute); + } + da.push(attribute); + } + + if (connect && connectedType === 'none') { + return false; + } + context.beginPath(); const ret: boolean = false; drawAreaSegments(context.camera ? context : context.nativeContext, cache, clipRange, { offsetX, offsetY, - offsetZ + offsetZ, + drawConnect: connect, + mode: connectedType, + zeroX: connectedX, + zeroY: connectedY }); if (!this._areaRenderContribitions) { @@ -403,7 +508,13 @@ export class DefaultCanvasAreaRender implements IGraphicRender { if (fillCb) { fillCb(context, attribute, defaultAttribute); } else if (fillOpacity) { - context.setCommonStyle(area, attribute, originX - offsetX, originY - offsetY, defaultAttribute); + context.setCommonStyle( + area, + connect ? connectedStyle : attribute, + originX - offsetX, + originY - offsetY, + connect ? da : defaultAttribute + ); context.fill(); } } @@ -446,11 +557,21 @@ export class DefaultCanvasAreaRender implements IGraphicRender { { offsetX, offsetY, - offsetZ + offsetZ, + drawConnect: connect, + mode: connectedType, + zeroX: connectedX, + zeroY: connectedY } ); } - context.setStrokeStyle(area, attribute, originX - offsetX, originY - offsetY, defaultAttribute); + context.setStrokeStyle( + area, + connect ? connectedStyle : attribute, + originX - offsetX, + originY - offsetY, + connect ? da : defaultAttribute + ); context.stroke(); } } diff --git a/packages/vrender/src/render/contributions/render/line-render.ts b/packages/vrender/src/render/contributions/render/line-render.ts index 093490016..34e0a65be 100644 --- a/packages/vrender/src/render/contributions/render/line-render.ts +++ b/packages/vrender/src/render/contributions/render/line-render.ts @@ -137,6 +137,57 @@ 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; + } + 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; } From 8ef1f3032eb86060a5edb0564d8f5e95bd5f0b65 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Wed, 2 Aug 2023 20:35:55 +0800 Subject: [PATCH 04/26] feat: support vertical text layout insingle line --- docs/demos/src/pages/richtext.ts | 42 +++---- docs/demos/src/pages/text.ts | 10 +- .../contributions/textMeasure/AtextMeasure.ts | 85 ++++++++++++++ packages/vrender/src/graphic/config.ts | 3 +- packages/vrender/src/graphic/text.ts | 105 ++++++++++++++++++ packages/vrender/src/graphic/tools.ts | 65 +++++++++++ .../vrender/src/interface/graphic/text.ts | 2 + packages/vrender/src/interface/text.ts | 11 ++ .../contributions/render/text-render.ts | 105 +++++++++++++----- 9 files changed, 375 insertions(+), 53 deletions(-) diff --git a/docs/demos/src/pages/richtext.ts b/docs/demos/src/pages/richtext.ts index ab2d88c6f..e84d05020 100644 --- a/docs/demos/src/pages/richtext.ts +++ b/docs/demos/src/pages/richtext.ts @@ -19,7 +19,7 @@ export const page = () => { text: 'Mapbox', fontWeight: 'bold', fontSize: 30, - fill: '#3f51b5', + fill: '#3f51b5' }, { text: '公司成立于2010年,创立目标是为Google Map提供一个', @@ -28,7 +28,7 @@ export const page = () => { { text: '替代方案', fontStyle: 'italic', - + fill: '#3f51b5' }, { @@ -53,7 +53,7 @@ export const page = () => { { // "lineHeight": 30, text: '。\n', - + fill: '#30ff05' }, { @@ -102,7 +102,7 @@ export const page = () => { fontSize: 30, textAlign: 'center', textDecoration: 'underline', - + fill: '#0f51b5' }, @@ -346,34 +346,34 @@ export const page = () => { // { // // lineHeight: 30, // text: '中', - // + // // textAlign: 'left' // }, // { // // lineHeight: 30, // text: '文', - // + // // textAlign: 'left' // // rotate: -Math.PI / 3, // }, // { // // lineHeight: 30, // text: '字', - // + // // textAlign: 'left' // // rotate: Math.PI / 2, // }, // { // // lineHeight: 30, // text: '符', - // + // // textAlign: 'left' // // rotate: Math.PI, // }, // { // // lineHeight: 30, // text: 'English', - // + // // textAlign: 'left', // // rotate: Math.PI / 2, // direction: 'vertical' @@ -382,21 +382,21 @@ export const page = () => { // { // // lineHeight: 30, // text: 'n', - // + // // textAlign: 'left' // // rotate: Math.PI / 3, // }, // { // // lineHeight: 30, // text: 'g', - // + // // textAlign: 'left' // // rotate: Math.PI / 2, // } { text: '这', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', @@ -411,7 +411,7 @@ export const page = () => { }, { text: '是', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', @@ -426,7 +426,7 @@ export const page = () => { }, { text: '一', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', @@ -441,7 +441,7 @@ export const page = () => { }, { text: '个', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', @@ -456,7 +456,7 @@ export const page = () => { }, { text: '汉', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', @@ -472,7 +472,7 @@ export const page = () => { { text: '【', direction: 'vertical', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', @@ -487,7 +487,7 @@ export const page = () => { }, { text: '放', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', @@ -502,7 +502,7 @@ export const page = () => { }, { text: '大', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', @@ -516,9 +516,9 @@ export const page = () => { stroke: 'black' }, { - text: 'a0', + text: 'a0这是什么', direction: 'vertical', - + // stroke: false, fontFamily: 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol', diff --git a/docs/demos/src/pages/text.ts b/docs/demos/src/pages/text.ts index 00f190e97..e7af6fa3d 100644 --- a/docs/demos/src/pages/text.ts +++ b/docs/demos/src/pages/text.ts @@ -55,14 +55,18 @@ export const page = () => { y: 200, fill: colorPools[5], // text: ['Tffg'], - text: 'Tffgggaaaa', + text: '这是垂aaa直布局的文字(abc】', + maxLineWidth: 200, + // ellipsis: '', + direction: 'vertical', fontSize: 20, stroke: 'green', - lineWidth: 100, + // lineWidth: 100, // lineHeight: 30, // lineThrough: 1, // underline: 1, - textBaseline: 'alphabetic' + textBaseline: 'top', + textAlign: 'left' // scaleX: 2, // scaleY: 2 }); diff --git a/packages/vrender/src/core/contributions/textMeasure/AtextMeasure.ts b/packages/vrender/src/core/contributions/textMeasure/AtextMeasure.ts index 13aab61d2..33b72bef0 100644 --- a/packages/vrender/src/core/contributions/textMeasure/AtextMeasure.ts +++ b/packages/vrender/src/core/contributions/textMeasure/AtextMeasure.ts @@ -89,6 +89,51 @@ export class ATextMeasure implements ITextMeasure { return this.context.measureText(text); } + clipTextVertical( + verticalList: { text: string; width?: number; direction: number }[], + options: TextOptionsType, + width: number + ): { + verticalList: { text: string; width?: number; direction: number }[]; + width: number; + } { + if (verticalList.length === 0) { + return { verticalList, width: 0 }; + } + const { fontSize = 12 } = options; + // 计算每一个区域的width + verticalList.forEach(item => { + item.width = item.direction === 0 ? fontSize : this.measureTextWidth(item.text, options); + }); + const out: { text: string; width?: number; direction: number }[] = []; + let length = 0; + let i = 0; + for (; i < verticalList.length; i++) { + if (length + verticalList[i].width < width) { + length += verticalList[i].width; + out.push(verticalList[i]); + } else { + break; + } + } + if (verticalList[i] && verticalList[i].text.length > 1) { + const clipedData = this._clipText( + verticalList[i].text, + options, + width - length, + 0, + verticalList[i].text.length - 1 + ); + out.push({ ...verticalList[i], text: clipedData.str }); + length += clipedData.width; + } + + return { + verticalList: out, + width: length + }; + } + /** * 将文本裁剪到width宽 * @param text @@ -162,6 +207,46 @@ export class ATextMeasure implements ITextMeasure { return { str: subText, width: strWidth }; } + clipTextWithSuffixVertical( + verticalList: { text: string; width?: number; direction: number }[], + options: TextOptionsType, + width: number, + suffix: string + ): { + verticalList: { text: string; width?: number; direction: number }[]; + width: number; + } { + if (suffix === '') { + return this.clipTextVertical(verticalList, options, width); + } + if (verticalList.length === 0) { + return { verticalList, width: 0 }; + } + + const output = this.clipTextVertical(verticalList, options, width); + if ( + output.verticalList.length === verticalList.length && + output.verticalList[output.verticalList.length - 1].width === verticalList[verticalList.length - 1].width + ) { + return output; + } + + const suffixWidth = this.measureTextWidth(suffix, options); + if (suffixWidth > width) { + return output; + } + + width -= suffixWidth; + + const out = this.clipTextVertical(verticalList, options, width); + out.width += suffixWidth; + out.verticalList.push({ + text: suffix, + direction: 1, + width: suffixWidth + }); + return out; + } clipTextWithSuffix( text: string, options: TextOptionsType, diff --git a/packages/vrender/src/graphic/config.ts b/packages/vrender/src/graphic/config.ts index 14a1acbc4..a3609f4b3 100644 --- a/packages/vrender/src/graphic/config.ts +++ b/packages/vrender/src/graphic/config.ts @@ -92,7 +92,8 @@ export const DefaultTextStyle: Required = { lineHeight: undefined, underline: 0, lineThrough: 0, - scaleIn3d: false + scaleIn3d: false, + direction: 'horizontal' }; export const DefaultStyle: IGraphicStyle = { diff --git a/packages/vrender/src/graphic/text.ts b/packages/vrender/src/graphic/text.ts index 2d455bb6c..9bb59158f 100644 --- a/packages/vrender/src/graphic/text.ts +++ b/packages/vrender/src/graphic/text.ts @@ -7,6 +7,7 @@ import { Graphic, GRAPHIC_UPDATE_TAG_KEY } from './graphic'; import { getTheme } from './theme'; import { parsePadding } from '../common/utils'; import { TEXT_NUMBER_TYPE } from './constants'; +import { TextDirection, verticalLayout } from './tools'; const TEXT_UPDATE_TAG_KEY = [ 'text', @@ -116,6 +117,19 @@ export class Text extends Graphic implements IText { * @param text */ updateSingallineAABBBounds(text: number | string): AABBBounds { + const textTheme = getTheme(this).text; + const { direction = textTheme.direction } = this.attribute; + + return direction === 'horizontal' + ? this.updateHorizontalSingallineAABBBounds(text) + : this.updateVerticalSingallineAABBBounds(text); + } + + /** + * 计算单行文字的bounds,可以缓存长度以及截取的文字 + * @param text + */ + updateHorizontalSingallineAABBBounds(text: number | string): AABBBounds { const textTheme = getTheme(this).text; const textMeasure = application.graphicUtil.textMeasure; let width: number; @@ -181,6 +195,97 @@ export class Text extends Graphic implements IText { return this._AABBBounds; } + /** + * 计算垂直布局的单行文字的bounds,可以缓存长度以及截取的文字 + * @param text + */ + updateVerticalSingallineAABBBounds(text: number | string): AABBBounds { + const textTheme = getTheme(this).text; + const textMeasure = application.graphicUtil.textMeasure; + let width: number; + let str: string; + const buf = 2; + const attribute = this.attribute; + const { + maxLineWidth = textTheme.maxLineWidth, + ellipsis = textTheme.ellipsis, + textAlign = textTheme.textAlign, + textBaseline = textTheme.textBaseline, + fontSize = textTheme.fontSize, + fontWeight = textTheme.fontWeight, + stroke = textTheme.stroke, + lineHeight = attribute.lineHeight ?? (attribute.fontSize || textTheme.fontSize) + buf, + lineWidth = textTheme.lineWidth + } = attribute; + if (!this.shouldUpdateShape() && this.cache) { + width = this.cache.clipedWidth; + const dx = textDrawOffsetX(textAlign, width); + const dy = textLayoutOffsetY(textBaseline, lineHeight, fontSize); + this._AABBBounds.set(dy, dx, dy + lineHeight, dx + width); + if (stroke) { + this._AABBBounds.expand(lineWidth / 2); + } + return this._AABBBounds; + } + + let verticalList: { text: string; width?: number; direction: TextDirection }[][] = [ + verticalLayout(text.toString()) + ]; + if (Number.isFinite(maxLineWidth)) { + if (ellipsis) { + const strEllipsis = (ellipsis === true ? textTheme.ellipsis : ellipsis) as string; + const data = textMeasure.clipTextWithSuffixVertical( + verticalList[0], + { fontSize, fontWeight }, + maxLineWidth, + strEllipsis + ); + verticalList = [data.verticalList]; + width = data.width; + + // const data = textMeasure.clipTextWithSuffix( + // text.toString(), + // { fontSize, fontWeight }, + // maxLineWidth, + // strEllipsis + // ); + // str = data.str; + // width = data.width; + } else { + const data = textMeasure.clipTextVertical(verticalList[0], { fontSize, fontWeight }, maxLineWidth); + verticalList = [data.verticalList]; + width = data.width; + } + this.cache.verticalList = verticalList; + this.cache.clipedWidth = width; + // todo 计算原本的宽度 + } else { + width = 0; + verticalList[0].forEach(t => { + const w = + t.direction === TextDirection.HORIZONTAL + ? fontSize + : textMeasure.measureTextWidth(t.text, { fontSize, fontWeight }); + + width += w; + t.width = w; + }); + this.cache.verticalList = verticalList; + this.cache.clipedWidth = width; + } + this.clearUpdateShapeTag(); + + const dx = textDrawOffsetX(textAlign, width); + const dy = textLayoutOffsetY(textBaseline, lineHeight, fontSize); + this._AABBBounds.set(dy, dx, dy + lineHeight, dx + width); + + if (stroke) { + this._AABBBounds.expand(lineWidth / 2); + } + + return this._AABBBounds; + } + /** * 计算多行文字的bounds,缓存每行文字的布局位置 * @param text diff --git a/packages/vrender/src/graphic/tools.ts b/packages/vrender/src/graphic/tools.ts index e4fc43bc5..90f18f4c6 100644 --- a/packages/vrender/src/graphic/tools.ts +++ b/packages/vrender/src/graphic/tools.ts @@ -67,3 +67,68 @@ let NUMBER_TYPE: number = 0; export function genNumberType() { return NUMBER_TYPE++; } + +export enum TextDirection { + HORIZONTAL = 0, + VERTICAL = 1 +} +export function verticalLayout(text: string) { + const nextCharacter: { text: string; direction: TextDirection }[] = []; + let flag = 0; // 0: 竖排,1: 旋转 + let currStr = ''; + for (let i = 0; i < text.length; i++) { + if (rotateText(text[i])) { + if (flag) { + currStr += text[i]; + } else { + flag = 1; + currStr = text[i]; + } + } else { + if (flag) { + nextCharacter.push({ + text: currStr, + direction: TextDirection.VERTICAL + }); + currStr = ''; + flag = 0; + } + nextCharacter.push({ + text: text[i], + direction: TextDirection.HORIZONTAL + }); + } + } + + if (currStr) { + nextCharacter.push({ + text: currStr, + direction: TextDirection.VERTICAL + }); + } + return nextCharacter; +} + +// ——, ……, (, ) +const rotateCharList = ['…', '(', ')', '—', '【', '】', '「', '」', '《', '》']; +const rotateCharMap = new Map(); +rotateCharList.forEach(c => rotateCharMap.set(c, true)); +const noRotateCharList = ['']; +const noRotateCharMap = new Map(); +noRotateCharList.forEach(c => noRotateCharMap.set(c, true)); + +function rotateText(c: string) { + if (rotateCharMap.has(c)) { + return true; + } + if (noRotateCharMap.has(c)) { + return false; + } + const cp = c.codePointAt(0); + let rotate = false; + // 默认ascii码就旋转 + if (cp < 256) { + rotate = true; + } + return rotate; +} diff --git a/packages/vrender/src/interface/graphic/text.ts b/packages/vrender/src/interface/graphic/text.ts index a246a7ba4..52be30cd2 100644 --- a/packages/vrender/src/interface/graphic/text.ts +++ b/packages/vrender/src/interface/graphic/text.ts @@ -45,6 +45,7 @@ export type ITextAttribute = { underline: number; lineThrough: number; scaleIn3d: boolean; + direction: 'horizontal' | 'vertical'; // textDecoration: number; // textDecorationWidth: number; // padding?: number | number[]; @@ -55,6 +56,7 @@ export type ITextCache = { clipedWidth?: number; // 多行文本的布局缓存 layoutData?: LayoutType; + verticalList?: { text: string; width?: number; direction: number }[][]; }; export type ITextGraphicAttribute = Partial & Partial; diff --git a/packages/vrender/src/interface/text.ts b/packages/vrender/src/interface/text.ts index c722391c0..3df42c161 100644 --- a/packages/vrender/src/interface/text.ts +++ b/packages/vrender/src/interface/text.ts @@ -14,11 +14,22 @@ export interface ITextMeasure extends IContribution { measureTextPixelHeight: (text: string, options: TextOptionsType) => number; measureTextBoundHieght: (text: string, options: TextOptionsType) => number; clipText: (text: string, options: TextOptionsType, width: number) => { str: string; width: number }; + clipTextVertical: ( + verticalList: { text: string; width?: number; direction: number }[], + options: TextOptionsType, + width: number + ) => { verticalList: { text: string; width?: number; direction: number }[]; width: number }; clipTextWithSuffix: ( text: string, options: TextOptionsType, width: number, suffix: string ) => { str: string; width: number }; + clipTextWithSuffixVertical: ( + verticalList: { text: string; width?: number; direction: number }[], + options: TextOptionsType, + width: number, + suffix: string + ) => { verticalList: { text: string; width?: number; direction: number }[]; width: number }; measureText: (text: string, options: TextOptionsType) => { width: number }; } diff --git a/packages/vrender/src/render/contributions/render/text-render.ts b/packages/vrender/src/render/contributions/render/text-render.ts index b225fbca9..fcfa117da 100644 --- a/packages/vrender/src/render/contributions/render/text-render.ts +++ b/packages/vrender/src/render/contributions/render/text-render.ts @@ -20,6 +20,7 @@ import { BaseRender } from './base-render'; import { ContributionProvider } from '../../../common/contribution-provider'; import { TextRenderContribution } from './contributions/constants'; import { BaseRenderContributionTime } from '../../../common/enums'; +import { matrixAllocate } from '../../../allocator/matrix-allocate'; @injectable() export class DefaultCanvasTextRender extends BaseRender implements IGraphicRender { @@ -68,9 +69,11 @@ export class DefaultCanvasTextRender extends BaseRender implements IGraph underline = textAttribute.underline, lineThrough = textAttribute.lineThrough, keepDirIn3d = textAttribute.keepDirIn3d, + direction = textAttribute.direction, // lineHeight = textAttribute.lineHeight, fontSize = textAttribute.fontSize, textBaseline = textAttribute.textBaseline, + textAlign = textAttribute.textAlign, x: originX = textAttribute.x, y: originY = textAttribute.y } = text.attribute; @@ -171,37 +174,83 @@ export class DefaultCanvasTextRender extends BaseRender implements IGraph } } } else { - context.setTextStyle(text.attribute, textAttribute, z); - const t = text.clipedText as string; - let dy = 0; - if (lineHeight !== fontSize) { - if (textBaseline === 'top') { - dy = (lineHeight - fontSize) / 2; - } else if (textBaseline === 'middle') { - // middle do nothing - } else if (textBaseline === 'bottom') { - dy = -(lineHeight - fontSize) / 2; - } else { - // alphabetic do nothing - // dy = (lineHeight - fontSize) / 2 - fontSize * 0.79; + const cache = text.cache; + const drawText = (t: string, offsetX: number, offsetY: number, direction: number) => { + let _x = x + offsetX; + const _y = y + offsetY; + if (direction) { + context.highPerformanceSave(); + _x += fontSize; + const matrix = matrixAllocate.allocate(1, 0, 0, 1, 0, 0); + // matrix.translate(fontSize, 0); + matrix.rotateByCenter(Math.PI / 2, _x, _y); + context.transformFromMatrix(matrix, true); + matrixAllocate.free(matrix); } - } - if (doStroke) { - if (strokeCb) { - strokeCb(context, text.attribute, textAttribute); - } else if (sVisible) { - context.setStrokeStyle(text, text.attribute, originX - x, originY - y, textAttribute); - context.strokeText(t, x, y + dy, z); + + if (doStroke) { + if (strokeCb) { + strokeCb(context, text.attribute, textAttribute); + } else if (sVisible) { + context.setStrokeStyle(text, text.attribute, originX - x, originY - y, textAttribute); + context.strokeText(t, _x, _y, z); + } } - } - if (doFill) { - if (fillCb) { - fillCb(context, text.attribute, textAttribute); - } else if (fVisible) { - context.setCommonStyle(text, text.attribute, originX - x, originY - y, textAttribute); - context.fillText(t, x, y + dy, z); - this.drawUnderLine(underline, lineThrough, text, x, y + dy, z, textAttribute, context); + if (doFill) { + if (fillCb) { + fillCb(context, text.attribute, textAttribute); + } else if (fVisible) { + context.setCommonStyle(text, text.attribute, originX - x, originY - y, textAttribute); + context.fillText(t, _x, _y, z); + this.drawUnderLine(underline, lineThrough, text, _x, _y, z, textAttribute, context); + } + } + + if (direction) { + context.highPerformanceRestore(); + context.setTransformForCurrent(); + } + }; + if (direction === 'horizontal') { + context.setTextStyle(text.attribute, textAttribute, z); + const t = text.clipedText as string; + let dy = 0; + if (lineHeight !== fontSize) { + if (textBaseline === 'top') { + dy = (lineHeight - fontSize) / 2; + } else if (textBaseline === 'middle') { + // middle do nothing + } else if (textBaseline === 'bottom') { + dy = -(lineHeight - fontSize) / 2; + } else { + // alphabetic do nothing + // dy = (lineHeight - fontSize) / 2 - fontSize * 0.79; + } + } + drawText(t, 0, dy, 0); + } else if (cache) { + context.setTextStyleWithoutAlignBaseline(text.attribute, textAttribute, z); + const { verticalList } = cache; + let offsetY = 0; + const totalW = verticalList[0].reduce((a, b) => a + (b.width || 0), 0); + let offsetX = 0; + if (textBaseline === 'bottom') { + offsetX = -lineHeight; + } else if (textBaseline === 'middle') { + offsetX = -lineHeight / 2; + } + if (textAlign === 'center') { + offsetY -= totalW / 2; + } else if (textAlign === 'right') { + offsetY -= totalW; } + context.textAlign = 'left'; + context.textBaseline = 'top'; + verticalList[0].forEach(item => { + const { text, width, direction } = item; + drawText(text, offsetX, offsetY, direction); + offsetY += width; + }); } } transform3dMatrixToContextMatrix && this.restoreTransformUseContext2d(text, textAttribute, z, context); From 95cf91fd08dd2fa4ff7011b55a418ab56e4a9960 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Wed, 2 Aug 2023 20:39:09 +0800 Subject: [PATCH 05/26] ci: github workflow add dev/** ci --- .github/workflows/bug-server.yml | 4 ++-- .github/workflows/unit-test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bug-server.yml b/.github/workflows/bug-server.yml index ca739a9ef..2bbf9d33a 100644 --- a/.github/workflows/bug-server.yml +++ b/.github/workflows/bug-server.yml @@ -5,7 +5,7 @@ on: push: branches: ['main'] pull_request: - branches: ['main', 'develop'] + branches: ['main', 'develop', 'dev/**'] jobs: build: @@ -27,7 +27,7 @@ jobs: - name: Print All Github Environment Variables run: env - + - name: Update rush run: node common/scripts/install-run-rush.js update --bypass-policy diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 966ca5899..371e228cd 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -5,7 +5,7 @@ name: Unit test CI on: pull_request: - branches: ['main', 'develop'] + branches: ['main', 'develop', 'dev/**'] jobs: build: From 7b43b976d08228de7d4afa5f727ebcbbb9cbdf8b Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Thu, 3 Aug 2023 19:20:24 +0800 Subject: [PATCH 06/26] feat: support multiline vertical text, closed #151 --- .../feat-vertical-text_2023-08-03-11-21.json | 10 ++ docs/demos/src/pages/text.ts | 6 +- packages/vrender/src/graphic/text.ts | 126 ++++++++++++++-- .../contributions/render/text-render.ts | 141 ++++++++++++------ 4 files changed, 218 insertions(+), 65 deletions(-) create mode 100644 common/changes/@visactor/vrender/feat-vertical-text_2023-08-03-11-21.json diff --git a/common/changes/@visactor/vrender/feat-vertical-text_2023-08-03-11-21.json b/common/changes/@visactor/vrender/feat-vertical-text_2023-08-03-11-21.json new file mode 100644 index 000000000..4ab4d1167 --- /dev/null +++ b/common/changes/@visactor/vrender/feat-vertical-text_2023-08-03-11-21.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender", + "comment": "feat: support for vertical layout of text graphic", + "type": "minor" + } + ], + "packageName": "@visactor/vrender" +} \ No newline at end of file diff --git a/docs/demos/src/pages/text.ts b/docs/demos/src/pages/text.ts index e7af6fa3d..f6eba5a70 100644 --- a/docs/demos/src/pages/text.ts +++ b/docs/demos/src/pages/text.ts @@ -55,7 +55,7 @@ export const page = () => { y: 200, fill: colorPools[5], // text: ['Tffg'], - text: '这是垂aaa直布局的文字(abc】', + text: ['这是垂aaa直布局的文字(abc】', '这是垂ccc直布局的文字(abc】'], maxLineWidth: 200, // ellipsis: '', direction: 'vertical', @@ -65,8 +65,8 @@ export const page = () => { // lineHeight: 30, // lineThrough: 1, // underline: 1, - textBaseline: 'top', - textAlign: 'left' + textBaseline: 'middle', + textAlign: 'center' // scaleX: 2, // scaleY: 2 }); diff --git a/packages/vrender/src/graphic/text.ts b/packages/vrender/src/graphic/text.ts index 9bb59158f..5502cffb8 100644 --- a/packages/vrender/src/graphic/text.ts +++ b/packages/vrender/src/graphic/text.ts @@ -1,4 +1,4 @@ -import type { AABBBounds, OBBBounds } from '@visactor/vutils'; +import { max, type AABBBounds, type OBBBounds } from '@visactor/vutils'; import { getContextFont, textDrawOffsetX, textLayoutOffsetY } from '../common/text'; import { CanvasTextLayout } from '../core/contributions/textMeasure/layout'; import { application } from '../application'; @@ -121,15 +121,28 @@ export class Text extends Graphic implements IText { const { direction = textTheme.direction } = this.attribute; return direction === 'horizontal' - ? this.updateHorizontalSingallineAABBBounds(text) - : this.updateVerticalSingallineAABBBounds(text); + ? this.updateHorizontalSinglelineAABBBounds(text) + : this.updateVerticalSinglelineAABBBounds(text); } /** * 计算单行文字的bounds,可以缓存长度以及截取的文字 * @param text */ - updateHorizontalSingallineAABBBounds(text: number | string): AABBBounds { + updateMultilineAABBBounds(text: (number | string)[]): AABBBounds { + const textTheme = getTheme(this).text; + const { direction = textTheme.direction } = this.attribute; + + return direction === 'horizontal' + ? this.updateHorizontalMultilineAABBBounds(text) + : this.updateVerticalMultilineAABBBounds(text); + } + + /** + * 计算单行文字的bounds,可以缓存长度以及截取的文字 + * @param text + */ + updateHorizontalSinglelineAABBBounds(text: number | string): AABBBounds { const textTheme = getTheme(this).text; const textMeasure = application.graphicUtil.textMeasure; let width: number; @@ -199,7 +212,7 @@ export class Text extends Graphic implements IText { * 计算垂直布局的单行文字的bounds,可以缓存长度以及截取的文字 * @param text */ - updateVerticalSingallineAABBBounds(text: number | string): AABBBounds { + updateVerticalSinglelineAABBBounds(text: number | string): AABBBounds { const textTheme = getTheme(this).text; const textMeasure = application.graphicUtil.textMeasure; let width: number; @@ -242,15 +255,6 @@ export class Text extends Graphic implements IText { ); verticalList = [data.verticalList]; width = data.width; - - // const data = textMeasure.clipTextWithSuffix( - // text.toString(), - // { fontSize, fontWeight }, - // maxLineWidth, - // strEllipsis - // ); - // str = data.str; - // width = data.width; } else { const data = textMeasure.clipTextVertical(verticalList[0], { fontSize, fontWeight }, maxLineWidth); verticalList = [data.verticalList]; @@ -290,7 +294,7 @@ export class Text extends Graphic implements IText { * 计算多行文字的bounds,缓存每行文字的布局位置 * @param text */ - updateMultilineAABBBounds(text: (number | string)[]): AABBBounds { + updateHorizontalMultilineAABBBounds(text: (number | string)[]): AABBBounds { const textTheme = getTheme(this).text; const attribute = this.attribute; const { @@ -338,6 +342,98 @@ export class Text extends Graphic implements IText { return this._AABBBounds; } + /** + * 计算垂直布局的多行文字的bounds,可以缓存长度以及截取的文字 + * @param text + */ + updateVerticalMultilineAABBBounds(text: (number | string)[]): AABBBounds { + const textTheme = getTheme(this).text; + const textMeasure = application.graphicUtil.textMeasure; + let width: number; + const buf = 2; + const attribute = this.attribute; + const { + maxLineWidth = textTheme.maxLineWidth, + ellipsis = textTheme.ellipsis, + textAlign = textTheme.textAlign, + textBaseline = textTheme.textBaseline, + fontSize = textTheme.fontSize, + fontWeight = textTheme.fontWeight, + stroke = textTheme.stroke, + lineHeight = attribute.lineHeight ?? (attribute.fontSize || textTheme.fontSize) + buf, + lineWidth = textTheme.lineWidth + } = attribute; + width = 0; + if (!this.shouldUpdateShape() && this.cache) { + this.cache.verticalList.forEach(item => { + const w = item.reduce((a, b) => a + b.width, 0); + width = max(w, width); + }); + const dx = textDrawOffsetX(textAlign, width); + const height = this.cache.verticalList.length * lineHeight; + const dy = textLayoutOffsetY(textBaseline, height, fontSize); + this._AABBBounds.set(dy, dx, dy + height, dx + width); + if (stroke) { + this._AABBBounds.expand(lineWidth / 2); + } + return this._AABBBounds; + } + + const verticalLists: { text: string; width?: number; direction: TextDirection }[][] = text.map(str => { + return verticalLayout(str.toString()); + }); + verticalLists.forEach((verticalData, i) => { + if (Number.isFinite(maxLineWidth)) { + if (ellipsis) { + const strEllipsis = (ellipsis === true ? textTheme.ellipsis : ellipsis) as string; + const data = textMeasure.clipTextWithSuffixVertical( + verticalData, + { fontSize, fontWeight }, + maxLineWidth, + strEllipsis + ); + verticalLists[i] = data.verticalList; + width = data.width; + } else { + const data = textMeasure.clipTextVertical(verticalData, { fontSize, fontWeight }, maxLineWidth); + verticalLists[i] = data.verticalList; + width = data.width; + } + // this.cache.clipedWidth = width; + // todo 计算原本的宽度 + } else { + width = 0; + verticalData.forEach(t => { + const w = + t.direction === TextDirection.HORIZONTAL + ? fontSize + : textMeasure.measureTextWidth(t.text, { fontSize, fontWeight }); + + width += w; + t.width = w; + }); + } + }); + this.cache.verticalList = verticalLists; + this.clearUpdateShapeTag(); + + this.cache.verticalList.forEach(item => { + const w = item.reduce((a, b) => a + b.width, 0); + width = max(w, width); + }); + + const dx = textDrawOffsetX(textAlign, width); + const height = this.cache.verticalList.length * lineHeight; + const dy = textLayoutOffsetY(textBaseline, height, fontSize); + this._AABBBounds.set(dy, dx, dy + height, dx + width); + + if (stroke) { + this._AABBBounds.expand(lineWidth / 2); + } + + return this._AABBBounds; + } + protected tryUpdateOBBBounds(): OBBBounds { throw new Error('暂不支持'); } diff --git a/packages/vrender/src/render/contributions/render/text-render.ts b/packages/vrender/src/render/contributions/render/text-render.ts index fcfa117da..f1efae9da 100644 --- a/packages/vrender/src/render/contributions/render/text-render.ts +++ b/packages/vrender/src/render/contributions/render/text-render.ts @@ -21,6 +21,7 @@ import { ContributionProvider } from '../../../common/contribution-provider'; import { TextRenderContribution } from './contributions/constants'; import { BaseRenderContributionTime } from '../../../common/enums'; import { matrixAllocate } from '../../../allocator/matrix-allocate'; +import { max } from '@visactor/vutils'; @injectable() export class DefaultCanvasTextRender extends BaseRender implements IGraphicRender { @@ -134,22 +135,26 @@ export class DefaultCanvasTextRender extends BaseRender implements IGraph context.setShadowStyle && context.setShadowStyle(text, text.attribute, textAttribute); transform3dMatrixToContextMatrix && this.transformUseContext2d(text, textAttribute, z, context); - if (Array.isArray(str)) { - context.setTextStyleWithoutAlignBaseline(text.attribute, textAttribute, z); - const { multilineLayout } = text; - if (!multilineLayout) { - context.highPerformanceRestore(); - return; - } // 如果不存在的话,需要render层自行布局 - const { xOffset, yOffset } = multilineLayout.bbox; + + const drawText = (t: string, offsetX: number, offsetY: number, direction: number) => { + let _x = x + offsetX; + const _y = y + offsetY; + if (direction) { + context.highPerformanceSave(); + _x += fontSize; + const matrix = matrixAllocate.allocate(1, 0, 0, 1, 0, 0); + // matrix.translate(fontSize, 0); + matrix.rotateByCenter(Math.PI / 2, _x, _y); + context.transformFromMatrix(matrix, true); + matrixAllocate.free(matrix); + } + if (doStroke) { if (strokeCb) { strokeCb(context, text.attribute, textAttribute); } else if (sVisible) { context.setStrokeStyle(text, text.attribute, originX - x, originY - y, textAttribute); - multilineLayout.lines.forEach(line => { - context.strokeText(line.str, (line.leftOffset || 0) + xOffset + x, (line.topOffset || 0) + yOffset + y, z); - }); + context.strokeText(t, _x, _y, z); } } if (doFill) { @@ -157,43 +162,38 @@ export class DefaultCanvasTextRender extends BaseRender implements IGraph fillCb(context, text.attribute, textAttribute); } else if (fVisible) { context.setCommonStyle(text, text.attribute, originX - x, originY - y, textAttribute); - multilineLayout.lines.forEach(line => { - context.fillText(line.str, (line.leftOffset || 0) + xOffset + x, (line.topOffset || 0) + yOffset + y, z); - this.drawMultiUnderLine( - underline, - lineThrough, - text, - (line.leftOffset || 0) + x, // 中下划线都是从文字左侧开始,因此不需要+xOffset - (line.topOffset || 0) + yOffset + y, - z, - line.width, - textAttribute, - context - ); - }); + context.fillText(t, _x, _y, z); + this.drawUnderLine(underline, lineThrough, text, _x, _y, z, textAttribute, context); } } - } else { - const cache = text.cache; - const drawText = (t: string, offsetX: number, offsetY: number, direction: number) => { - let _x = x + offsetX; - const _y = y + offsetY; - if (direction) { - context.highPerformanceSave(); - _x += fontSize; - const matrix = matrixAllocate.allocate(1, 0, 0, 1, 0, 0); - // matrix.translate(fontSize, 0); - matrix.rotateByCenter(Math.PI / 2, _x, _y); - context.transformFromMatrix(matrix, true); - matrixAllocate.free(matrix); - } + if (direction) { + context.highPerformanceRestore(); + context.setTransformForCurrent(); + } + }; + if (Array.isArray(str)) { + context.setTextStyleWithoutAlignBaseline(text.attribute, textAttribute, z); + if (direction === 'horizontal') { + const { multilineLayout } = text; + if (!multilineLayout) { + context.highPerformanceRestore(); + return; + } // 如果不存在的话,需要render层自行布局 + const { xOffset, yOffset } = multilineLayout.bbox; if (doStroke) { if (strokeCb) { strokeCb(context, text.attribute, textAttribute); } else if (sVisible) { context.setStrokeStyle(text, text.attribute, originX - x, originY - y, textAttribute); - context.strokeText(t, _x, _y, z); + multilineLayout.lines.forEach(line => { + context.strokeText( + line.str, + (line.leftOffset || 0) + xOffset + x, + (line.topOffset || 0) + yOffset + y, + z + ); + }); } } if (doFill) { @@ -201,16 +201,63 @@ export class DefaultCanvasTextRender extends BaseRender implements IGraph fillCb(context, text.attribute, textAttribute); } else if (fVisible) { context.setCommonStyle(text, text.attribute, originX - x, originY - y, textAttribute); - context.fillText(t, _x, _y, z); - this.drawUnderLine(underline, lineThrough, text, _x, _y, z, textAttribute, context); + multilineLayout.lines.forEach(line => { + context.fillText(line.str, (line.leftOffset || 0) + xOffset + x, (line.topOffset || 0) + yOffset + y, z); + this.drawMultiUnderLine( + underline, + lineThrough, + text, + (line.leftOffset || 0) + x, // 中下划线都是从文字左侧开始,因此不需要+xOffset + (line.topOffset || 0) + yOffset + y, + z, + line.width, + textAttribute, + context + ); + }); } } - - if (direction) { - context.highPerformanceRestore(); - context.setTransformForCurrent(); + } else { + const cache = text.cache; + const { verticalList } = cache; + context.textAlign = 'left'; + context.textBaseline = 'top'; + const totalHeight = lineHeight * verticalList.length; + let totalW = 0; + verticalList.forEach(verticalData => { + const _w = verticalData.reduce((a, b) => a + (b.width || 0), 0); + totalW = max(_w, totalW); + }); + let offsetY = 0; + let offsetX = 0; + if (textBaseline === 'bottom') { + offsetX = -totalHeight; + } else if (textBaseline === 'middle') { + offsetX = -totalHeight / 2; } - }; + if (textAlign === 'center') { + offsetY -= totalW / 2; + } else if (textAlign === 'right') { + offsetY -= totalW; + } + verticalList.forEach((verticalData, i) => { + const currentW = verticalData.reduce((a, b) => a + (b.width || 0), 0); + const dw = totalW - currentW; + let currentOffsetY = offsetY; + if (textAlign === 'center') { + currentOffsetY += dw / 2; + } else if (textAlign === 'right') { + currentOffsetY += dw; + } + verticalData.forEach(item => { + const { text, width, direction } = item; + drawText(text, totalHeight - (i + 1) * lineHeight + offsetX, currentOffsetY, direction); + currentOffsetY += width; + }); + }); + } + } else { + const cache = text.cache; if (direction === 'horizontal') { context.setTextStyle(text.attribute, textAttribute, z); const t = text.clipedText as string; From bf05faeab42ec085edac3fc0bb8116e437ad50c0 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Fri, 4 Aug 2023 11:44:11 +0800 Subject: [PATCH 07/26] feat: set default and invilid connectType to none, closed #109 --- docs/demos/src/pages/area.ts | 4 +++- docs/demos/src/pages/line.ts | 2 +- packages/vrender/src/common/render-area.ts | 6 +++++- .../vrender/src/render/contributions/render/area-render.ts | 5 +++++ .../vrender/src/render/contributions/render/line-render.ts | 4 ++++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/demos/src/pages/area.ts b/docs/demos/src/pages/area.ts index 430dc88a9..1eb3b2014 100644 --- a/docs/demos/src/pages/area.ts +++ b/docs/demos/src/pages/area.ts @@ -52,7 +52,9 @@ export const page = () => { { offset: 1, color: 'red' } ] }, - connectedType: 'connect', + connectedType: 'zero', + connectedX: null, + connectedY: 100, connectedStyle: { fill: 'grey' } diff --git a/docs/demos/src/pages/line.ts b/docs/demos/src/pages/line.ts index dbd4381bb..65bc6d5e2 100644 --- a/docs/demos/src/pages/line.ts +++ b/docs/demos/src/pages/line.ts @@ -44,7 +44,7 @@ export const page = () => { x: ((i * 300) % 900) + 100, y: Math.floor((i * 300) / 900) * 200, stroke: 'red', - connectedType: 'zero', + connectedType: 'connected', connectedStyle: { stroke: 'green' }, diff --git a/packages/vrender/src/common/render-area.ts b/packages/vrender/src/common/render-area.ts index 4cbe701fb..09fb9c775 100644 --- a/packages/vrender/src/common/render-area.ts +++ b/packages/vrender/src/common/render-area.ts @@ -198,13 +198,17 @@ function drawAreaConnectBlock( 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 } = params || {}; + 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; diff --git a/packages/vrender/src/render/contributions/render/area-render.ts b/packages/vrender/src/render/contributions/render/area-render.ts index 299c2ad9b..173d8238a 100644 --- a/packages/vrender/src/render/contributions/render/area-render.ts +++ b/packages/vrender/src/render/contributions/render/area-render.ts @@ -450,6 +450,11 @@ export class DefaultCanvasAreaRender implements IGraphicRender { connectedStyle = connectedStyle ?? defaultAttribute.connectedStyle; } + // 如果有非法值就是none + if (connectedType !== 'connect' && connectedType !== 'zero') { + connectedType = 'none'; + } + if (isArray(defaultAttribute)) { defaultAttribute.forEach(i => da.push(i)); } else { diff --git a/packages/vrender/src/render/contributions/render/line-render.ts b/packages/vrender/src/render/contributions/render/line-render.ts index 8d40b7229..8e21dc254 100644 --- a/packages/vrender/src/render/contributions/render/line-render.ts +++ b/packages/vrender/src/render/contributions/render/line-render.ts @@ -154,6 +154,10 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph 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, { From 0e06d4b38336358d5c50d7bacae2d06bf416393c Mon Sep 17 00:00:00 2001 From: xiaoluoHe Date: Thu, 3 Aug 2023 10:19:15 +0800 Subject: [PATCH 08/26] feat: label component support avoidMarks --- ...eat-label_avoid_mark_2023-08-03-02-17.json | 10 +++ packages/vrender-components/src/label/base.ts | 67 ++++++++++--------- packages/vrender-components/src/label/type.ts | 6 ++ .../vrender-components/src/util/common.ts | 24 ++++++- 4 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 common/changes/@visactor/vrender-components/feat-label_avoid_mark_2023-08-03-02-17.json diff --git a/common/changes/@visactor/vrender-components/feat-label_avoid_mark_2023-08-03-02-17.json b/common/changes/@visactor/vrender-components/feat-label_avoid_mark_2023-08-03-02-17.json new file mode 100644 index 000000000..c11051126 --- /dev/null +++ b/common/changes/@visactor/vrender-components/feat-label_avoid_mark_2023-08-03-02-17.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "feat(component): label component support avoidMarks", + "type": "patch" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/packages/vrender-components/src/label/base.ts b/packages/vrender-components/src/label/base.ts index db6dcc095..2794a5f49 100644 --- a/packages/vrender-components/src/label/base.ts +++ b/packages/vrender-components/src/label/base.ts @@ -4,11 +4,11 @@ import type { IGroup, Text, IGraphic, IText, FederatedPointerEvent, IColor } from '@visactor/vrender'; import { createText, IncreaseCount, AttributeUpdateType } from '@visactor/vrender'; import type { IBoundsLike } from '@visactor/vutils'; -import { isFunction, isValidNumber, isEmpty, isValid } from '@visactor/vutils'; +import { isFunction, isValidNumber, isEmpty, isValid, isString } from '@visactor/vutils'; import { AbstractComponent } from '../core/base'; import type { PointLocationCfg } from '../core/type'; import { labelSmartInvert } from '../util/labelSmartInvert'; -import { traverseGroup } from '../util'; +import { getMarksByName, getNoneGroupMarksByName, traverseGroup } from '../util'; import { StateValue } from '../constant'; import type { Bitmap } from './overlap'; import { bitmapTool, boundToRange, canPlace, canPlaceInside, clampText, place } from './overlap'; @@ -172,7 +172,7 @@ export abstract class LabelBase extends AbstractCompon } private _prepare() { - const baseMarks = this.getBaseMarks(); + const baseMarks = getMarksByName(this.getRootNode() as IGroup, this.attribute.baseMarkGroupName); const currentBaseMarks: IGraphic[] = []; baseMarks.forEach(mark => { if ((mark as any).releaseStatus !== 'willRelease') { @@ -261,17 +261,31 @@ export abstract class LabelBase extends AbstractCompon return labels; } - const { avoidBaseMark, strategy = [], hideOnHit = true, clampForce = true } = option; + const { avoidBaseMark, strategy = [], hideOnHit = true, clampForce = true, avoidMarks = [] } = option; const bmpTool = this._bmpTool || bitmapTool(size.width, size.height); const bitmap = this._bitmap || bmpTool.bitmap(); const checkBounds = strategy.some(s => s.type === 'bound'); + // 躲避关联的基础图元 if (avoidBaseMark) { this._baseMarks?.forEach(mark => { mark.AABBBounds && bitmap.setRange(boundToRange(bmpTool, mark.AABBBounds, true)); }); } + // 躲避指定图元 + if (avoidMarks.length > 0) { + avoidMarks.forEach(avoid => { + if (isString(avoid)) { + getNoneGroupMarksByName(this.getRootNode() as IGroup, avoid).forEach(avoidMark => { + avoidMark.AABBBounds && bitmap.setRange(boundToRange(bmpTool, avoidMark.AABBBounds, true)); + }); + } else if (avoid.AABBBounds) { + bitmap.setRange(boundToRange(bmpTool, avoid.AABBBounds, true)); + } + }); + } + for (let i = 0; i < labels.length; i++) { if (labels[i].visible === false) { continue; @@ -296,24 +310,6 @@ export abstract class LabelBase extends AbstractCompon } } - // 尝试向内挤压 - if (clampForce) { - const { dx = 0, dy = 0 } = clampText(text, bmpTool.width, bmpTool.height); - if ( - !(dx === 0 && dy === 0) && - canPlace(bmpTool, bitmap, { - x1: text.AABBBounds.x1 + dx, - x2: text.AABBBounds.x2 + dx, - y1: text.AABBBounds.y1 + dy, - y2: text.AABBBounds.y2 + dy - }) - ) { - text.setAttributes({ x: text.attribute.x + dx, y: text.attribute.y + dy }); - result.push(text); - continue; - } - } - let hasPlace: ReturnType = false; // 发生碰撞,根据策略寻找可放置的位置 for (let j = 0; j < strategy.length; j++) { @@ -333,6 +329,25 @@ export abstract class LabelBase extends AbstractCompon } } + // 尝试向内挤压 + if (!hasPlace && clampForce) { + const { dx = 0, dy = 0 } = clampText(text, bmpTool.width, bmpTool.height); + if ( + !(dx === 0 && dy === 0) && + canPlace(bmpTool, bitmap, { + x1: text.AABBBounds.x1 + dx, + x2: text.AABBBounds.x2 + dx, + y1: text.AABBBounds.y1 + dy, + y2: text.AABBBounds.y2 + dy + }) + ) { + text.setAttributes({ x: text.attribute.x + dx, y: text.attribute.y + dy }); + bitmap.setRange(boundToRange(bmpTool, text.AABBBounds, true)); + result.push(text); + continue; + } + } + !hasPlace && !hideOnHit && result.push(text); } @@ -343,14 +358,6 @@ export abstract class LabelBase extends AbstractCompon return result; } - protected getBaseMarks() { - const baseMarkGroup = this.getBaseMarkGroup() as IGroup; - if (!baseMarkGroup) { - return; - } - return baseMarkGroup.getChildren() as IGraphic[]; - } - protected getBaseMarkGroup() { const { baseMarkGroupName } = this.attribute as BaseLabelAttrs; if (!baseMarkGroupName) { diff --git a/packages/vrender-components/src/label/type.ts b/packages/vrender-components/src/label/type.ts index 105273378..e8f5171b7 100644 --- a/packages/vrender-components/src/label/type.ts +++ b/packages/vrender-components/src/label/type.ts @@ -97,6 +97,12 @@ export interface OverlapAttrs { */ avoidBaseMark?: boolean; + /** + * 躲避指定图元 + * @default [] + */ + avoidMarks?: string[] | IGraphic[]; + /** * 发生重叠后的躲避策略 */ diff --git a/packages/vrender-components/src/util/common.ts b/packages/vrender-components/src/util/common.ts index 6c5912846..5b2c8b05b 100644 --- a/packages/vrender-components/src/util/common.ts +++ b/packages/vrender-components/src/util/common.ts @@ -1,7 +1,7 @@ /** * @description 存放工具函数 */ -import { IGraphicAttribute, IGraphic, IGroup } from '@visactor/vrender'; +import type { IGraphicAttribute, IGraphic, IGroup } from '@visactor/vrender'; import { isNil } from '@visactor/vutils'; export function traverseGroup(group: IGraphic, cb: (node: IGraphic) => boolean | void) { @@ -19,3 +19,25 @@ export const isVisible = (obj?: Partial): boolean => { } return obj.visible !== false; }; + +export function getMarksByName(root: IGroup, name: string) { + if (!name) { + return []; + } + const group = root.find(node => node.name === name, true) as IGroup; + if (!group) { + return []; + } + return group.getChildren() as IGraphic[]; +} + +export function getNoneGroupMarksByName(root: IGroup, name: string) { + if (!name) { + return []; + } + const group = root.find(node => node.name === name, true) as IGroup; + if (!group) { + return []; + } + return group.findAll(node => node.type !== 'group', true) as unknown as IGraphic[]; +} From 7cbf7fb7c8a7227df1586270597eb2072cb6de80 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Fri, 4 Aug 2023 15:37:43 +0800 Subject: [PATCH 09/26] feat: support flex layout, closed #152 --- .../vrender/feat-layout_2023-08-04-07-38.json | 10 + docs/demos/src/pages/flex.ts | 42 +++ docs/demos/src/pages/index.ts | 4 + packages/vrender/src/core/stage.ts | 20 ++ packages/vrender/src/graphic/config.ts | 18 +- packages/vrender/src/interface/graphic.ts | 4 + .../vrender/src/interface/graphic/group.ts | 6 + packages/vrender/src/interface/stage.ts | 4 + .../builtin-plugin/flex-layout-plugin.ts | 260 ++++++++++++++++++ 9 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 common/changes/@visactor/vrender/feat-layout_2023-08-04-07-38.json create mode 100644 docs/demos/src/pages/flex.ts create mode 100644 packages/vrender/src/plugins/builtin-plugin/flex-layout-plugin.ts diff --git a/common/changes/@visactor/vrender/feat-layout_2023-08-04-07-38.json b/common/changes/@visactor/vrender/feat-layout_2023-08-04-07-38.json new file mode 100644 index 000000000..6f17c2d84 --- /dev/null +++ b/common/changes/@visactor/vrender/feat-layout_2023-08-04-07-38.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender", + "comment": "feat: support flex layout", + "type": "minor" + } + ], + "packageName": "@visactor/vrender" +} \ No newline at end of file diff --git a/docs/demos/src/pages/flex.ts b/docs/demos/src/pages/flex.ts new file mode 100644 index 000000000..c0d390c97 --- /dev/null +++ b/docs/demos/src/pages/flex.ts @@ -0,0 +1,42 @@ +import { createStage, createGroup, createRect, container, IGraphic, global } from '@visactor/vrender'; + +// container.load(roughModule); + +export const page = () => { + const group = createGroup({ + x: 100, + y: 100, + background: 'red', + width: 300, + height: 400, + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'flex-start', + alignContent: 'center' + }); + + // 添加10个rect + new Array(10).fill(0).forEach(() => { + group.add( + createRect({ + x: 10, + y: 10, + width: 70, + height: 60, + fill: 'green', + boundsPadding: [0, 6, 6, 0] + }) + ); + }); + + const stage = createStage({ + canvas: 'main', + width: 1200, + height: 600, + enableLayout: true + }); + + stage.defaultLayer.add(group); + + stage.render(undefined, { renderStyle: 'rough' }); +}; diff --git a/docs/demos/src/pages/index.ts b/docs/demos/src/pages/index.ts index 940631bd9..8be2fba09 100644 --- a/docs/demos/src/pages/index.ts +++ b/docs/demos/src/pages/index.ts @@ -155,5 +155,9 @@ export const pages = [ { title: 'animate-3d', path: 'animate-3d' + }, + { + title: 'flex布局', + path: 'flex' } ]; diff --git a/packages/vrender/src/core/stage.ts b/packages/vrender/src/core/stage.ts index 0db14affd..fa1e34246 100644 --- a/packages/vrender/src/core/stage.ts +++ b/packages/vrender/src/core/stage.ts @@ -36,6 +36,7 @@ import { AutoRenderPlugin } from '../plugins/builtin-plugin/auto-render-plugin'; import { ViewTransform3dPlugin } from '../plugins/builtin-plugin/3dview-transform-plugin'; import { IncrementalAutoRenderPlugin } from '../plugins/builtin-plugin/incremental-auto-render-plugin'; import { DirtyBoundsPlugin } from '../plugins/builtin-plugin/dirty-bounds-plugin'; +import { FlexLayoutPlugin } from '../plugins/builtin-plugin/flex-layout-plugin'; import { defaultTicker } from '../animate/default-ticker'; import { SyncHook } from '../tapable'; import { DirectionalLight } from './light'; @@ -153,6 +154,7 @@ export class Stage extends Group implements IStage { ticker: ITicker; autoRender: boolean; + _enableLayout: boolean; increaseAutoRender: boolean; view3dTranform: boolean; readonly window: IWindow; @@ -264,6 +266,8 @@ export class Stage extends Group implements IStage { if (params.disableDirtyBounds === false) { this.enableDirtyBounds(); } + + params.enableLayout && this.enableLayout(); this.hooks.beforeRender.tap('constructor', this.beforeRender); this.hooks.afterRender.tap('constructor', this.afterRender); this._beforeRender = params.beforeRender; @@ -437,6 +441,22 @@ export class Stage extends Group implements IStage { plugin.deactivate(this.pluginService); }); } + enableLayout() { + if (this._enableLayout) { + return; + } + this._enableLayout = true; + this.pluginService.register(new FlexLayoutPlugin()); + } + disableLayout() { + if (!this._enableLayout) { + return; + } + this._enableLayout = false; + this.pluginService.findPluginsByName('FlexLayoutPlugin').forEach(plugin => { + plugin.deactivate(this.pluginService); + }); + } // /** // * stage的appendChild,add diff --git a/packages/vrender/src/graphic/config.ts b/packages/vrender/src/graphic/config.ts index e5fa7aa92..6d0866f42 100644 --- a/packages/vrender/src/graphic/config.ts +++ b/packages/vrender/src/graphic/config.ts @@ -27,9 +27,14 @@ import type { RichTextGlobalAlignType, RichTextGlobalBaselineType, IRichTextIconGraphicAttribute, - IConnectedStyle + IConnectedStyle, + ILayout } from '../interface'; +export const DefaultLayout: ILayout = { + alignSelf: 'auto' +}; + export const DefaultTransform: ITransform = { x: 0, y: 0, @@ -108,7 +113,8 @@ export const DefaultStyle: IGraphicStyle = { blur: 0, cursor: null, ...DefaultFillStyle, - ...DefaultStrokeStyle + ...DefaultStrokeStyle, + ...DefaultLayout }; export const DefaultConnectAttribute: Required = { @@ -190,7 +196,13 @@ export const DefaultGroupAttribute: Required = { cornerRadius: 0, path: [], clip: false, - visibleAll: true + visibleAll: true, + display: 'relative', + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'flex-start', + alignItems: 'flex-start', + alignContent: 'flex-start' }; export const DefaultGlyphAttribute: Required = { diff --git a/packages/vrender/src/interface/graphic.ts b/packages/vrender/src/interface/graphic.ts index 4191583a4..fa05b0d0a 100644 --- a/packages/vrender/src/interface/graphic.ts +++ b/packages/vrender/src/interface/graphic.ts @@ -109,6 +109,10 @@ export type IFillStyle = { fill: IFillType; }; +export type ILayout = { + alignSelf: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch'; +}; + export type IBorderStyle = Omit & { distance: number | string; }; diff --git a/packages/vrender/src/interface/graphic/group.ts b/packages/vrender/src/interface/graphic/group.ts index b6d8fd3d2..0703b7fa5 100644 --- a/packages/vrender/src/interface/graphic/group.ts +++ b/packages/vrender/src/interface/graphic/group.ts @@ -10,6 +10,12 @@ export type IGroupAttribute = { cornerRadius: number | number[]; clip: boolean; visibleAll: boolean; + display?: 'relative' | 'flex'; + flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse'; + flexWrap?: 'nowrap' | 'wrap'; + justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around'; + alignItems?: 'flex-start' | 'flex-end' | 'center'; + alignContent?: 'flex-start' | 'center' | 'space-between' | 'space-around'; }; export type IGroupGraphicAttribute = Partial & Partial; diff --git a/packages/vrender/src/interface/stage.ts b/packages/vrender/src/interface/stage.ts index 705a2dc3a..1d1ffdf82 100644 --- a/packages/vrender/src/interface/stage.ts +++ b/packages/vrender/src/interface/stage.ts @@ -39,7 +39,11 @@ export interface IStageParams { // 是否是受控制的canvas,如果不是的话,不会进行resize等操作,也不会修改canvas的样式 canvasControled: boolean; title: string; + // 是否开启自动渲染 autoRender: boolean; + // 是否开启布局支持 + enableLayout: boolean; + // 是否关闭脏矩形检测 disableDirtyBounds: boolean; // 是否支持interactiveLayer,默认为true interactiveLayer: boolean; diff --git a/packages/vrender/src/plugins/builtin-plugin/flex-layout-plugin.ts b/packages/vrender/src/plugins/builtin-plugin/flex-layout-plugin.ts new file mode 100644 index 000000000..8fb38c45d --- /dev/null +++ b/packages/vrender/src/plugins/builtin-plugin/flex-layout-plugin.ts @@ -0,0 +1,260 @@ +import type { IGraphic, IGroup, IGroupAttribute } from '../../interface'; +import { getTheme } from '../../graphic'; +import { graphicService } from '../../modules'; +import type { IPlugin, IPluginService } from '../../interface'; +import { Generator } from '../../common/generator'; + +export class FlexLayoutPlugin implements IPlugin { + name: 'FlexLayoutPlugin' = 'FlexLayoutPlugin'; + activeEvent: 'onRegister' = 'onRegister'; + pluginService: IPluginService; + id: number = Generator.GenAutoIncrementId(); + key: string = this.name + this.id; + + tryLayout(graphic: IGraphic) { + const p = graphic.parent; + if (!p) { + return; + } + const theme = getTheme(p).group; + const { display = theme.display } = p.attribute; + if (display !== 'flex') { + return; + } + const { + width, + height, + flexDirection = theme.flexDirection, + flexWrap = theme.flexWrap, + justifyContent = theme.justifyContent, + alignItems = theme.alignItems, + alignContent = theme.alignContent + } = p.attribute; + if (!(width && height)) { + return; + } + + const result = { + main: { len: width, field: 'x' }, + cross: { len: height, field: 'y' }, + dir: 1 + }; + const main = result.main; + const cross = result.cross; + if (flexDirection === 'row-reverse') { + result.dir = -1; + } else if (flexDirection === 'column') { + main.len = height; + cross.len = width; + main.field = 'y'; + cross.field = 'x'; + } else if (flexDirection === 'column-reverse') { + main.len = height; + cross.len = width; + main.field = 'y'; + cross.field = 'x'; + result.dir = -1; + } + + // 计算宽度 + let mainLen = 0; + let crossLen = 0; + const mianLenArray: { mainLen: number; crossLen: number }[] = []; + p.forEachChildren((c: IGraphic) => { + const b = c.AABBBounds; + const ml = main.field === 'x' ? b.width() : b.height(); + const cl = cross.field === 'x' ? b.width() : b.height(); + mianLenArray.push({ mainLen: ml, crossLen: cl }); + mainLen += ml; + crossLen = Math.max(crossLen, cl); + }); + // 解析main + const mainList: { idx: number; mainLen: number; crossLen: number }[] = []; + if (mainLen > main.len && flexWrap === 'wrap') { + let tempMainL = 0; + let tempCrossL = 0; + mianLenArray.forEach(({ mainLen, crossLen }, i) => { + if (tempMainL + mainLen > main.len) { + if (tempMainL === 0) { + mainList.push({ idx: i, mainLen: tempMainL + mainLen, crossLen }); + tempMainL = 0; + tempCrossL = 0; + } else { + mainList.push({ idx: i - 1, mainLen: tempMainL, crossLen }); + tempMainL = mainLen; + tempCrossL = crossLen; + } + } else { + tempMainL += mainLen; + tempCrossL = Math.max(tempCrossL, crossLen); + } + }); + mainList.push({ idx: mianLenArray.length - 1, mainLen: tempMainL, crossLen: tempCrossL }); + } else { + mainList.push({ idx: mianLenArray.length - 1, mainLen: mainLen, crossLen }); + } + + const children = p.getChildren() as IGraphic[]; + + // 布局main + let lastIdx: number = 0; + mainList.forEach(s => { + this.layoutMain(p, children, justifyContent, main, mianLenArray, lastIdx, s); + lastIdx = s.idx + 1; + }); + + crossLen = mainList.reduce((a, b) => a + b.crossLen, 0); + + // 布局cross + if (mainList.length === 1) { + if (alignItems === 'flex-end') { + const anchorPos = cross.len; + this.layoutCross(children, alignItems, cross, anchorPos, mianLenArray, mainList[0], 0); + } else if (alignItems === 'center') { + const anchorPos = cross.len / 2; + this.layoutCross(children, alignItems, cross, anchorPos, mianLenArray, mainList[0], 0); + } + } else { + if (alignContent === 'flex-start') { + lastIdx = 0; + let anchorPos = 0; + mainList.forEach((s, i) => { + this.layoutCross(children, 'flex-start', cross, anchorPos, mianLenArray, mainList[i], lastIdx); + lastIdx = s.idx + 1; + anchorPos += s.crossLen; + }); + } else if (alignContent === 'center') { + lastIdx = 0; + const padding = Math.max(0, (cross.len - crossLen) / 2); + let anchorPos = padding; + mainList.forEach((s, i) => { + this.layoutCross(children, 'center', cross, anchorPos + s.crossLen / 2, mianLenArray, mainList[i], lastIdx); + lastIdx = s.idx + 1; + anchorPos += s.crossLen; + }); + } else if (alignContent === 'space-around') { + lastIdx = 0; + const padding = Math.max(0, (cross.len - crossLen) / mainList.length / 2); + let anchorPos = padding; + mainList.forEach((s, i) => { + this.layoutCross(children, 'flex-start', cross, anchorPos, mianLenArray, mainList[i], lastIdx); + lastIdx = s.idx + 1; + anchorPos += s.crossLen + padding * 2; + }); + } else if (alignContent === 'space-between') { + lastIdx = 0; + const padding = Math.max(0, (cross.len - crossLen) / (mainList.length * 2 - 2)); + let anchorPos = 0; + mainList.forEach((s, i) => { + this.layoutCross(children, 'flex-start', cross, anchorPos, mianLenArray, mainList[i], lastIdx); + lastIdx = s.idx + 1; + anchorPos += s.crossLen + padding * 2; + }); + } + } + } + + layoutMain( + p: IGroup, + children: IGraphic[], + justifyContent: IGroupAttribute['justifyContent'], + main: { len: number; field: string }, + mianLenArray: { mainLen: number; crossLen: number }[], + lastIdx: number, + currSeg: { idx: number; mainLen: number; crossLen: number } + ) { + if (justifyContent === 'flex-start') { + let pos = 0; + for (let i = lastIdx; i <= currSeg.idx; i++) { + children[i].attribute[main.field] = pos; + pos += mianLenArray[i].mainLen; + } + } else if (justifyContent === 'flex-end') { + let pos = main.len; + for (let i = lastIdx; i <= currSeg.idx; i++) { + pos -= mianLenArray[i].mainLen; + children[i].attribute[main.field] = pos; + } + } else if (justifyContent === 'space-around') { + if (currSeg.mainLen >= main.len) { + let pos = 0; + for (let i = lastIdx; i <= currSeg.idx; i++) { + children[i].attribute[main.field] = pos; + pos += mianLenArray[i].mainLen; + } + } else { + const size = currSeg.idx - lastIdx + 1; + const padding = (main.len - currSeg.mainLen) / size / 2; + let pos = padding; + for (let i = lastIdx; i <= currSeg.idx; i++) { + children[i].attribute[main.field] = pos; + pos += mianLenArray[i].mainLen + padding * 2; + } + } + } else if (justifyContent === 'space-between') { + if (currSeg.mainLen >= main.len) { + let pos = 0; + for (let i = lastIdx; i <= currSeg.idx; i++) { + children[i].attribute[main.field] = pos; + pos += mianLenArray[i].mainLen; + } + } else { + const size = currSeg.idx - lastIdx + 1; + const padding = (main.len - currSeg.mainLen) / (size * 2 - 2); + let pos = 0; + for (let i = lastIdx; i <= currSeg.idx; i++) { + children[i].attribute[main.field] = pos; + pos += mianLenArray[i].mainLen + padding * 2; + } + } + } + } + + layoutCross( + children: IGraphic[], + alignItem: IGroupAttribute['alignItems'], + cross: { len: number; field: string }, + anchorPos: number, + lenArray: { mainLen: number; crossLen: number }[], + currSeg: { idx: number; mainLen: number; crossLen: number }, + lastIdx: number + ) { + if (alignItem === 'flex-end') { + for (let i = lastIdx; i <= currSeg.idx; i++) { + children[i].attribute[cross.field] = anchorPos - lenArray[i].crossLen; + } + } else if (alignItem === 'center') { + for (let i = lastIdx; i <= currSeg.idx; i++) { + children[i].attribute[cross.field] = anchorPos - lenArray[i].crossLen / 2; + } + } else { + for (let i = lastIdx; i <= currSeg.idx; i++) { + children[i].attribute[cross.field] = anchorPos; + } + } + } + + activate(context: IPluginService): void { + this.pluginService = context; + graphicService.hooks.onAttributeUpdate.tap(this.key, graphic => { + if (graphic.glyphHost) { + graphic = graphic.glyphHost; + } + this.tryLayout(graphic); + }); + graphicService.hooks.onSetStage.tap(this.key, graphic => { + if (graphic.glyphHost) { + graphic = graphic.glyphHost; + } + this.tryLayout(graphic); + }); + } + deactivate(context: IPluginService): void { + graphicService.hooks.onAttributeUpdate.taps = graphicService.hooks.onAttributeUpdate.taps.filter(item => { + return item.name !== this.key; + }); + graphicService.hooks.onSetStage.taps = graphicService.hooks.onSetStage.taps.filter(item => { + return item.name !== this.key; + }); + } +} From 370f7764f80a5191ae502655a49f91d7f6dfa03a Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Sun, 6 Aug 2023 17:58:01 +0800 Subject: [PATCH 10/26] chore: update pnpm-lock.yaml --- common/config/rush/pnpm-lock.yaml | 180 +++++++++++++++--------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index cb449e911..41540c81a 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: dependencies: '@visactor/vrender': link:../../packages/vrender devDependencies: - '@antv/g': 5.18.8 + '@antv/g': 5.18.9 '@esbuild-plugins/node-globals-polyfill': 0.1.1 '@esbuild-plugins/node-modules-polyfill': 0.1.4 '@internal/eslint-config': link:../../share/eslint-config @@ -433,7 +433,7 @@ importers: '@types/merge2': 1.4.0 '@types/minimist': 1.2.2 '@types/ms': 0.7.31 - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@types/semver': 7.3.12 '@types/terser': 3.12.0 '@types/through2': 2.0.38 @@ -441,7 +441,7 @@ importers: '@types/vinyl': 2.0.7 '@types/yargs-parser': 21.0.0 eslint: 8.18.0 - ts-node: 10.9.0_gamchj74f4cosewgiaiggc3fvy + ts-node: 10.9.0_7evmrdykb6edw6y6jp3noa77xm typescript: 4.9.5 vitest: 0.30.1_less@4.1.3+terser@5.17.1 @@ -458,23 +458,23 @@ packages: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.18 - /@antv/g-camera-api/1.2.7: - resolution: {integrity: sha512-gshIoy+5slnM4KAuAaVMKdyWaB6jeGDju6CdO30DGrxWsD0Cct2GcfVZt+Opx3bKYB7TJ6ws6H03yzcaWYnFTg==} + /@antv/g-camera-api/1.2.8: + resolution: {integrity: sha512-P6K1Ps/l7bViQ7prKrdIhnTY5uKKP/Pz7vKnlb8WzYNN5cPNWueDJY/cxLTyfQjpvv5mBOdgUOJTjNr4cvBWVw==} dependencies: - '@antv/g-lite': 1.2.7 + '@antv/g-lite': 1.2.8 '@antv/util': 3.3.4 gl-matrix: 3.4.3 tslib: 2.6.1 dev: true - /@antv/g-dom-mutation-observer-api/1.2.7: - resolution: {integrity: sha512-lUTgROvNywkyr4tw8rLVIHfA1rCI6qm17X0sUy6JwjGKG87Yzz0VBZz5AMNv1IRaPIP7nEMtcfJ29HJAu3+X0A==} + /@antv/g-dom-mutation-observer-api/1.2.8: + resolution: {integrity: sha512-2b/KsJufgfsmjMjyQBk6zrbu9D/tvQmwgPU3Px3lex+RIUHL6RmH7MRx2uBmTSJj2/tv7o4ZrVsy7uM6xTH/1g==} dependencies: - '@antv/g-lite': 1.2.7 + '@antv/g-lite': 1.2.8 dev: true - /@antv/g-lite/1.2.7: - resolution: {integrity: sha512-rTToCzfZviOzJeHS0hJvRpEyWv7/6sMe7cyIXLH/BbDYpIOscx0/AV536Zd4I2dVtRiw5wpAU25FQl+Sr7hDHg==} + /@antv/g-lite/1.2.8: + resolution: {integrity: sha512-Kmo6z8g62jv79zaSkrlvFqmBGkUqnhSrw02MPKDHYFoNxbXX7ZAhMYtUTlzTH2GfC7nvJd63uziYHKzsVQ8EYg==} dependencies: '@antv/g-math': 2.0.2 '@antv/util': 3.3.4 @@ -493,21 +493,21 @@ packages: tslib: 2.6.1 dev: true - /@antv/g-web-animations-api/1.2.7: - resolution: {integrity: sha512-lrsZIHtcbUwkw25bV3cI8CJ8phhraewR+AI5mKqKEx+vlngd7TvfGn3aG/jQ1IA01NgkULGNQENJCySVCHtD4w==} + /@antv/g-web-animations-api/1.2.8: + resolution: {integrity: sha512-ktOj1iPTXdxFmzNigpAtLy+ZDiqvyQ/1cFQTMD/c1Rp/7ha2rGslRF1xf4xRIuDhssSNvS5Q6FeEMPjLZM2NnA==} dependencies: - '@antv/g-lite': 1.2.7 + '@antv/g-lite': 1.2.8 '@antv/util': 3.3.4 tslib: 2.6.1 dev: true - /@antv/g/5.18.8: - resolution: {integrity: sha512-kv3AZmbOrPKncfiJ5lVaCQyMVBAxmJUppzBqU+6QplLQFDo73wBGNwHJX4LuucYO7CfcNjN94wIcT0CnX6Ls5A==} + /@antv/g/5.18.9: + resolution: {integrity: sha512-yDTBN3oFTkwpBB6XRSJYS5m2mCvf0+Nl/14WQStx5Bx33Evrz2SxE09lDr5PX4A7K2r24dd+TZ8f16T3++lXwQ==} dependencies: - '@antv/g-camera-api': 1.2.7 - '@antv/g-dom-mutation-observer-api': 1.2.7 - '@antv/g-lite': 1.2.7 - '@antv/g-web-animations-api': 1.2.7 + '@antv/g-camera-api': 1.2.8 + '@antv/g-dom-mutation-observer-api': 1.2.8 + '@antv/g-lite': 1.2.8 + '@antv/g-web-animations-api': 1.2.8 dev: true /@antv/util/3.3.4: @@ -635,7 +635,7 @@ packages: '@babel/helper-plugin-utils': 7.22.5 debug: 4.3.4 lodash.debounce: 4.0.8 - resolve: 1.22.2 + resolve: 1.22.4 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -1915,7 +1915,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 chalk: 4.1.2 jest-message-util: 26.6.2 jest-util: 26.6.2 @@ -1930,7 +1930,7 @@ packages: '@jest/test-result': 26.6.2 '@jest/transform': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 ansi-escapes: 4.3.2 chalk: 4.1.2 exit: 0.1.2 @@ -1970,7 +1970,7 @@ packages: '@jest/test-result': 26.6.2 '@jest/transform': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 ansi-escapes: 4.3.2 chalk: 4.1.2 exit: 0.1.2 @@ -2018,7 +2018,7 @@ packages: dependencies: '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 jest-mock: 26.6.2 /@jest/fake-timers/24.9.0: @@ -2036,7 +2036,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@sinonjs/fake-timers': 6.0.1 - '@types/node': 20.4.5 + '@types/node': 20.4.8 jest-message-util: 26.6.2 jest-mock: 26.6.2 jest-util: 26.6.2 @@ -2223,7 +2223,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@types/yargs': 15.0.15 chalk: 4.1.2 @@ -2500,7 +2500,7 @@ packages: deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 - resolve: 1.22.2 + resolve: 1.22.4 rollup: 3.20.5 dev: false @@ -2547,7 +2547,7 @@ packages: optional: true dependencies: '@rollup/pluginutils': 5.0.2_rollup@3.20.5 - resolve: 1.22.2 + resolve: 1.22.4 rollup: 3.20.5 typescript: 4.9.5 dev: false @@ -2686,7 +2686,7 @@ packages: /@types/clean-css/4.2.6: resolution: {integrity: sha512-Ze1tf+LnGPmG6hBFMi0B4TEB0mhF7EiMM5oyjLDNPE9hxrPU0W+5+bHvO+eFPA+bt0iC1zkQMoU/iGdRVjcRbw==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 source-map: 0.6.1 dev: true @@ -2706,13 +2706,13 @@ packages: /@types/fs-extra/9.0.13: resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 dev: true /@types/glob-stream/8.0.0: resolution: {integrity: sha512-fxTWwdQmX9LWSHD7ZLlv3BHR992mKcVcDnT/2v+l/QZZo7TfDdyasqlSYVzOnMGWhRbrWeWkbj/mgezFjKynhw==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@types/picomatch': 2.3.0 '@types/streamx': 2.9.1 dev: true @@ -2720,18 +2720,18 @@ packages: /@types/glob-watcher/5.0.2: resolution: {integrity: sha512-MZeh2nIzibl/euv5UV0femkGzcKTSE4G2+zv48d6ymeitWwCx52+4X+FqzML9oH2mQnPs+N/JHp3CsBPj1x1Ug==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 dev: true /@types/graceful-fs/4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 /@types/gulp-if/0.0.34: resolution: {integrity: sha512-r2A04hHDC+ZWMRAm+3q6/UeC3ggvl+TZm9P1+2umnp4q9bOlBmUQnR178Io3c0DkZPQAwup8VNtOvmvaWCpP5w==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@types/vinyl': 2.0.7 dev: true @@ -2745,7 +2745,7 @@ packages: /@types/gulp-sourcemaps/0.0.35: resolution: {integrity: sha512-vUBuizwA4CAV3Mke0DJYHQxyN4YOB1aAql284qAO7Et7fe0hmnPi/R9Fhu2UhxMuSxAwFktsJUOQk5dJHOU1eA==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@types/vinyl': 2.0.7 dev: true @@ -2799,7 +2799,7 @@ packages: /@types/merge2/1.4.0: resolution: {integrity: sha512-MRHDvln2ldZELrUC8n1PGaQzZ33aNh8uDcsGehREW0zR1Fr818a4/JTZjO9eloHPPxnpUp8fz/YFTRc5CWm7Xw==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 dev: true /@types/minimatch/5.1.2: @@ -2817,7 +2817,7 @@ packages: /@types/node-fetch/2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 form-data: 3.0.1 dev: true @@ -2825,8 +2825,8 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true - /@types/node/20.4.5: - resolution: {integrity: sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==} + /@types/node/20.4.8: + resolution: {integrity: sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==} /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -2879,7 +2879,7 @@ packages: /@types/streamx/2.9.1: resolution: {integrity: sha512-9bywzhouyedmci7WCIPFwJ8zASDnxt2gaVUy52X0p0Tt085IJSAEP0L6j4SSNeDMSLzpYu6cPz0GrJZ7kPJ6Bg==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 dev: true /@types/terser/3.12.0: @@ -2892,7 +2892,7 @@ packages: /@types/through2/2.0.38: resolution: {integrity: sha512-YFu+nHmjxMurkH1BSzA0Z1WrKDAY8jUKPZctNQn7mc+/KKtp2XxnclHFXxdB1m7Iqnzb5aywgP8TMK283LezGQ==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 dev: true /@types/undertaker-registry/1.0.1: @@ -2902,7 +2902,7 @@ packages: /@types/undertaker/1.2.8: resolution: {integrity: sha512-gW3PRqCHYpo45XFQHJBhch7L6hytPsIe0QeLujlnFsjHPnXLhJcPdN6a9368d7aIQgH2I/dUTPFBlGeSNA3qOg==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@types/undertaker-registry': 1.0.1 async-done: 1.3.2 dev: true @@ -2911,7 +2911,7 @@ packages: resolution: {integrity: sha512-ctNcmmzbMIKooXjRkyyUCOu2Z4AyqibL+RhXoF3pb7K7j+ezItnakmpm31LymkYHSIM5ey0tjIFzTvFOTSBCGw==} dependencies: '@types/glob-stream': 8.0.0 - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@types/vinyl': 2.0.7 dev: true @@ -2919,7 +2919,7 @@ packages: resolution: {integrity: sha512-4UqPv+2567NhMQuMLdKAyK4yzrfCqwaTt6bLhHEs8PFcxbHILsrxaY63n4wgE/BRLDWDQeI+WcTmkXKExh9hQg==} dependencies: '@types/expect': 1.20.4 - '@types/node': 20.4.5 + '@types/node': 20.4.8 /@types/yargs-parser/21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} @@ -3534,7 +3534,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.10 - caniuse-lite: 1.0.30001518 + caniuse-lite: 1.0.30001519 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -3825,8 +3825,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001518 - electron-to-chromium: 1.4.479 + caniuse-lite: 1.0.30001519 + electron-to-chromium: 1.4.485 node-releases: 2.0.13 update-browserslist-db: 1.0.11_browserslist@4.21.10 @@ -3913,8 +3913,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - /caniuse-lite/1.0.30001518: - resolution: {integrity: sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA==} + /caniuse-lite/1.0.30001519: + resolution: {integrity: sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==} /canvas/2.11.2: resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} @@ -4570,8 +4570,8 @@ packages: safer-buffer: 2.1.2 dev: true - /electron-to-chromium/1.4.479: - resolution: {integrity: sha512-ABv1nHMIR8I5n3O3Een0gr6i0mfM+YcTZqjHy3pAYaOjgFG+BMquuKrSyfYf5CbEkLr9uM05RA3pOk4udNB/aQ==} + /electron-to-chromium/1.4.485: + resolution: {integrity: sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w==} /electron/11.5.0: resolution: {integrity: sha512-WjNDd6lGpxyiNjE3LhnFCAk/D9GIj1rU3GSDealVShhkkkPR3Vh4q8ErXGDl1OAO/faomVa10KoFPUN/pLbNxg==} @@ -5902,7 +5902,7 @@ packages: resolution: {integrity: sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==} engines: {node: '>=10'} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@types/vinyl': 2.0.7 istextorbinary: 3.3.0 replacestream: 4.0.3 @@ -6259,8 +6259,8 @@ packages: dependencies: ci-info: 2.0.0 - /is-core-module/2.12.1: - resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} + /is-core-module/2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: has: 1.0.3 @@ -6851,7 +6851,7 @@ packages: '@jest/environment': 26.6.2 '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 jest-mock: 26.6.2 jest-util: 26.6.2 jsdom: 16.7.0 @@ -6881,7 +6881,7 @@ packages: '@jest/environment': 26.6.2 '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 jest-mock: 26.6.2 jest-util: 26.6.2 @@ -6934,7 +6934,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@types/graceful-fs': 4.1.6 - '@types/node': 20.4.5 + '@types/node': 20.4.8 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -6981,7 +6981,7 @@ packages: '@jest/source-map': 26.6.2 '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 chalk: 4.1.2 co: 4.6.0 expect: 26.6.2 @@ -7011,7 +7011,7 @@ packages: '@jest/source-map': 26.6.2 '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 chalk: 4.1.2 co: 4.6.0 expect: 26.6.2 @@ -7115,7 +7115,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 /jest-pnp-resolver/1.2.3_jest-resolve@24.9.0: resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} @@ -7178,7 +7178,7 @@ packages: jest-pnp-resolver: 1.2.3_jest-resolve@26.6.2 jest-util: 26.6.2 read-pkg-up: 7.0.1 - resolve: 1.22.2 + resolve: 1.22.4 slash: 3.0.0 /jest-runner/24.9.0: @@ -7216,7 +7216,7 @@ packages: '@jest/environment': 26.6.2 '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 chalk: 4.1.2 emittery: 0.7.2 exit: 0.1.2 @@ -7248,7 +7248,7 @@ packages: '@jest/environment': 26.6.2 '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 chalk: 4.1.2 emittery: 0.7.2 exit: 0.1.2 @@ -7391,7 +7391,7 @@ packages: resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==} engines: {node: '>= 10.14.2'} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 graceful-fs: 4.2.11 /jest-snapshot/24.9.0: @@ -7457,7 +7457,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 chalk: 4.1.2 graceful-fs: 4.2.11 is-ci: 2.0.0 @@ -7492,7 +7492,7 @@ packages: dependencies: '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.4.5 + '@types/node': 20.4.8 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 26.6.2 @@ -7510,7 +7510,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 merge-stream: 2.0.0 supports-color: 7.2.0 @@ -7839,7 +7839,7 @@ packages: is-plain-object: 2.0.4 object.map: 1.0.1 rechoir: 0.6.2 - resolve: 1.22.2 + resolve: 1.22.4 dev: false /lil-gui/0.17.0: @@ -8013,7 +8013,7 @@ packages: dependencies: findup-sync: 2.0.0 micromatch: 3.1.10 - resolve: 1.22.2 + resolve: 1.22.4 stack-trace: 0.0.10 dev: false @@ -8279,7 +8279,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.2 + resolve: 1.22.4 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -9088,7 +9088,7 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} dependencies: - resolve: 1.22.2 + resolve: 1.22.4 dev: false /regenerate-unicode-properties/10.1.0: @@ -9301,11 +9301,11 @@ packages: resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} dev: true - /resolve/1.22.2: - resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + /resolve/1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true dependencies: - is-core-module: 2.12.1 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -9313,7 +9313,7 @@ packages: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true dependencies: - is-core-module: 2.12.1 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: false @@ -9940,8 +9940,8 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - /strip-literal/1.0.1: - resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} + /strip-literal/1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} dependencies: acorn: 8.10.0 dev: true @@ -10231,7 +10231,7 @@ packages: typescript: 4.9.5 yargs-parser: 20.2.9 - /ts-node/10.9.0_gamchj74f4cosewgiaiggc3fvy: + /ts-node/10.9.0_7evmrdykb6edw6y6jp3noa77xm: resolution: {integrity: sha512-bunW18GUyaCSYRev4DPf4SQpom3pWH29wKl0sDk5zE7ze19RImEVhCW7K4v3hHKkUyfWotU08ToE2RS+Y49aug==} hasBin: true peerDependencies: @@ -10250,7 +10250,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.4.5 + '@types/node': 20.4.8 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -10674,7 +10674,7 @@ packages: replace-ext: 1.0.1 dev: false - /vite-node/0.30.1_c4rv5bqevikf6haxibcjp2765a: + /vite-node/0.30.1_u2lyfyljvjjczcvr4ahwm6zluu: resolution: {integrity: sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==} engines: {node: '>=v14.18.0'} hasBin: true @@ -10684,7 +10684,7 @@ packages: mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 - vite: 3.2.6_c4rv5bqevikf6haxibcjp2765a + vite: 3.2.6_u2lyfyljvjjczcvr4ahwm6zluu transitivePeerDependencies: - '@types/node' - less @@ -10722,13 +10722,13 @@ packages: dependencies: esbuild: 0.15.18 postcss: 8.4.21 - resolve: 1.22.2 + resolve: 1.22.4 rollup: 2.79.1 optionalDependencies: fsevents: 2.3.2 dev: true - /vite/3.2.6_c4rv5bqevikf6haxibcjp2765a: + /vite/3.2.6_u2lyfyljvjjczcvr4ahwm6zluu: resolution: {integrity: sha512-nTXTxYVvaQNLoW5BQ8PNNQ3lPia57gzsQU/Khv+JvzKPku8kNZL6NMUR/qwXhMG6E+g1idqEPanomJ+VZgixEg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -10753,11 +10753,11 @@ packages: terser: optional: true dependencies: - '@types/node': 20.4.5 + '@types/node': 20.4.8 esbuild: 0.15.18 less: 4.1.3 postcss: 8.4.21 - resolve: 1.22.2 + resolve: 1.22.4 rollup: 2.79.1 terser: 5.17.1 optionalDependencies: @@ -10797,7 +10797,7 @@ packages: dependencies: '@types/chai': 4.3.5 '@types/chai-subset': 1.3.3 - '@types/node': 20.4.5 + '@types/node': 20.4.8 '@vitest/expect': 0.30.1 '@vitest/runner': 0.30.1 '@vitest/snapshot': 0.30.1 @@ -10815,11 +10815,11 @@ packages: picocolors: 1.0.0 source-map: 0.6.1 std-env: 3.3.3 - strip-literal: 1.0.1 + strip-literal: 1.3.0 tinybench: 2.5.0 tinypool: 0.4.0 - vite: 3.2.6_c4rv5bqevikf6haxibcjp2765a - vite-node: 0.30.1_c4rv5bqevikf6haxibcjp2765a + vite: 3.2.6_u2lyfyljvjjczcvr4ahwm6zluu + vite-node: 0.30.1_u2lyfyljvjjczcvr4ahwm6zluu why-is-node-running: 2.2.2 transitivePeerDependencies: - less From ec222f47828da0ef779b57a9f33731ff6165f89e Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Sun, 6 Aug 2023 18:01:01 +0800 Subject: [PATCH 11/26] feat(vrender-components): add `verticalMinSize` and `containerAlign` for line type axis, support https://github.com/VisActor/VChart/issues/380 --- packages/vrender-components/src/axis/base.ts | 22 +++- .../vrender-components/src/axis/circle.ts | 26 +++- .../vrender-components/src/axis/constant.ts | 3 +- packages/vrender-components/src/axis/line.ts | 120 +++++++++++++++--- packages/vrender-components/src/axis/type.ts | 30 ++++- packages/vrender-components/src/util/align.ts | 57 +++++++++ 6 files changed, 231 insertions(+), 27 deletions(-) create mode 100644 packages/vrender-components/src/util/align.ts diff --git a/packages/vrender-components/src/axis/base.ts b/packages/vrender-components/src/axis/base.ts index b889024af..6facae437 100644 --- a/packages/vrender-components/src/axis/base.ts +++ b/packages/vrender-components/src/axis/base.ts @@ -75,9 +75,24 @@ export abstract class AxisBase extends AbstractCom protected abstract getTitleAttribute(): TagAttributes; protected abstract getGridAttribute(type: string): GridAttributes; protected abstract getTextBaseline(vector: [number, number], inside?: boolean): TextBaselineType; + protected abstract beforeLabelsOverlap( + labelShapes: IText[], + labelData: AxisItem[], + labelContainer: IGroup, + layer: number, + layerCount: number + ): void; protected abstract handleLabelsOverlap( labelShapes: IText[], labelData: AxisItem[], + labelContainer: IGroup, + layer: number, + layerCount: number + ): void; + protected abstract afterLabelsOverlap( + labelShapes: IText[], + labelData: AxisItem[], + labelContainer: IGroup, layer: number, layerCount: number ): void; @@ -209,9 +224,11 @@ export abstract class AxisBase extends AbstractCom items.forEach((axisItems: AxisItem[], layer: number) => { const layerLabelGroup = this.renderLabels(labelGroup, axisItems, layer); - // handle overlap const labels = layerLabelGroup.getChildren() as IText[]; - this.handleLabelsOverlap(labels, axisItems, layer, items.length); + this.beforeLabelsOverlap(labels, axisItems, layerLabelGroup, layer, items.length); + // handle overlap + this.handleLabelsOverlap(labels, axisItems, layerLabelGroup, layer, items.length); + this.afterLabelsOverlap(labels, axisItems, layerLabelGroup, layer, items.length); }); } @@ -246,7 +263,6 @@ export abstract class AxisBase extends AbstractCom axisContainer.insertBefore(bgRect, axisContainer.firstChild); } } - protected renderTicks(container: IGroup) { const tickLineItems = this.getTickLineItems(); diff --git a/packages/vrender-components/src/axis/circle.ts b/packages/vrender-components/src/axis/circle.ts index 12f136998..f2a2aa023 100644 --- a/packages/vrender-components/src/axis/circle.ts +++ b/packages/vrender-components/src/axis/circle.ts @@ -335,8 +335,32 @@ export class CircleAxis extends AxisBase { return base; } - protected handleLabelsOverlap(labelShapes: IText[], labelData: AxisItem[], layer: number, layerCount: number): void { + protected beforeLabelsOverlap( + labelShapes: IText[], + labelData: AxisItem[], + labelContainer: IGroup, + layer: number, + layerCount: number + ): void { + return; + } + protected handleLabelsOverlap( + labelShapes: IText[], + labelData: AxisItem[], + labelContainer: IGroup, + layer: number, + layerCount: number + ): void { // 暂不支持 return; } + protected afterLabelsOverlap( + labelShapes: IText[], + labelData: AxisItem[], + labelContainer: IGroup, + layer: number, + layerCount: number + ): void { + return; + } } diff --git a/packages/vrender-components/src/axis/constant.ts b/packages/vrender-components/src/axis/constant.ts index 1e06df537..ce7bd7553 100644 --- a/packages/vrender-components/src/axis/constant.ts +++ b/packages/vrender-components/src/axis/constant.ts @@ -11,7 +11,8 @@ export enum AXIS_ELEMENT_NAME { grid = 'axis-grid', gridRegion = 'axis-grid-region', line = 'axis-line', - background = 'axis-background' + background = 'axis-background', + axisLabelBackground = 'axis-label-background' } export enum AxisStateValue { diff --git a/packages/vrender-components/src/axis/line.ts b/packages/vrender-components/src/axis/line.ts index a89b6f675..3586925a9 100644 --- a/packages/vrender-components/src/axis/line.ts +++ b/packages/vrender-components/src/axis/line.ts @@ -12,9 +12,10 @@ import { isNumberClose, isEmpty, isFunction, - isValidNumber + isValidNumber, + isValid } from '@visactor/vutils'; -import type { IGroup, INode, IText, TextBaselineType } from '@visactor/vrender'; +import { createRect, type IGroup, type INode, type IText, type TextBaselineType } from '@visactor/vrender'; import type { SegmentAttributes } from '../segment'; // eslint-disable-next-line no-duplicate-imports import { Segment } from '../segment'; @@ -40,6 +41,7 @@ import { measureTextSize } from '../util'; import { autoHide as autoHideFunc } from './overlap/auto-hide'; import { autoRotate as autoRotateFunc } from './overlap/auto-rotate'; import { autoLimit as autoLimitFunc } from './overlap/auto-limit'; +import { alignAxisLabels } from '../util/align'; function getCirclePoints(center: Point, count: number, radius: number, startAngle: number, endAngle: number) { const points: Point[] = []; @@ -413,25 +415,28 @@ export class LineAxis extends AxisBase { return base; } - protected handleLabelsOverlap(labelShapes: IText[], labelData: AxisItem[], layer: number, layerCount: number): void { + protected beforeLabelsOverlap( + labelShapes: IText[], + labelData: AxisItem[], + labelContainer: IGroup, + layer: number, + layerCount: number + ): void { + return; + } + protected handleLabelsOverlap( + labelShapes: IText[], + labelData: AxisItem[], + labelContainer: IGroup, + layer: number, + layerCount: number + ): void { if (isEmpty(labelShapes)) { return; } - const { verticalLimitSize, label, title, line, tick, orient } = this.attribute; - const labelSpace = label.space ?? 4; - let limitLength = verticalLimitSize; - let titleHeight = 0; - let titleSpacing = 0; - const axisLineWidth = line?.visible ? line.style.lineWidth ?? 1 : 0; - const tickLength = tick?.visible ? tick.length ?? 4 : 0; - if (title?.visible) { - titleHeight = measureTextSize(title.text, title.textStyle).height; - titleSpacing = title.space; - } - if (limitLength) { - limitLength = (limitLength - labelSpace - titleSpacing - titleHeight - axisLineWidth - tickLength) / layerCount; - } + const { verticalLimitSize, label, orient } = this.attribute; + const limitLength = this._getAxisLabelLimitLength(verticalLimitSize, layerCount); const { layoutFunc, @@ -471,4 +476,85 @@ export class LineAxis extends AxisBase { } } } + + protected afterLabelsOverlap( + labelShapes: IText[], + labelData: AxisItem[], + labelContainer: IGroup, + layer: number, + layerCount: number + ) { + const { verticalLimitSize, orient } = this.attribute; + + // 处理 verticalMinSize,根据 verticalMinSize 调整 labelContainer 的大小 + const isHorizontal = orient === 'bottom' || orient === 'top'; + const axisLabelContainerBounds = labelContainer.AABBBounds; + let axisLabelContainerSize = isHorizontal ? axisLabelContainerBounds.height() : axisLabelContainerBounds.width(); + const { verticalMinSize } = this.attribute; + + if (isValidNumber(verticalMinSize) && (!isValidNumber(verticalLimitSize) || verticalMinSize <= verticalLimitSize)) { + const minSize = this._getAxisLabelLimitLength(verticalMinSize, layerCount); + axisLabelContainerSize = Math.max(axisLabelContainerSize, minSize); + + let x; + let y; + if (orient === 'left') { + x = axisLabelContainerBounds.x2 - axisLabelContainerSize; + y = axisLabelContainerBounds.y1; + } else if (orient === 'right') { + x = axisLabelContainerBounds.x1; + y = axisLabelContainerBounds.y1; + } else if (orient === 'top') { + x = axisLabelContainerBounds.x1; + y = axisLabelContainerBounds.y2 - axisLabelContainerSize; + } else if (orient === 'bottom') { + x = axisLabelContainerBounds.x1; + y = axisLabelContainerBounds.y1; + } + const bgRect = createRect({ + x, + y, + width: isHorizontal ? axisLabelContainerBounds.width() : axisLabelContainerSize, + height: isHorizontal ? axisLabelContainerSize : axisLabelContainerBounds.height(), + pickable: false + }); + bgRect.name = AXIS_ELEMENT_NAME.axisLabelBackground; + bgRect.id = this._getNodeId('axis-label-background'); + labelContainer.insertBefore(bgRect, labelContainer.firstChild); + } + + // 处理 align,进行整体的对齐操作 + if (isValid(this.attribute.label.containerAlign)) { + let start; + if (orient === 'left') { + start = axisLabelContainerBounds.x2; + } else if (orient === 'right') { + start = axisLabelContainerBounds.x1; + } else if (orient === 'top') { + start = axisLabelContainerBounds.y2; + } else if (orient === 'bottom') { + start = axisLabelContainerBounds.y1; + } + + alignAxisLabels(labelShapes, start, axisLabelContainerSize, orient, this.attribute.label.containerAlign); + } + } + + private _getAxisLabelLimitLength(limitSize: number, layerCount: number): number { + const { label, title, line, tick } = this.attribute; + const labelSpace = label.space ?? 4; + let limitLength = limitSize; + let titleHeight = 0; + let titleSpacing = 0; + const axisLineWidth = line?.visible ? line.style.lineWidth ?? 1 : 0; + const tickLength = tick?.visible ? tick.length ?? 4 : 0; + if (title?.visible) { + titleHeight = measureTextSize(title.text, title.textStyle).height; + titleSpacing = title.space; + } + if (limitLength) { + limitLength = (limitLength - labelSpace - titleSpacing - titleHeight - axisLineWidth - tickLength) / layerCount; + } + return limitLength; + } } diff --git a/packages/vrender-components/src/axis/type.ts b/packages/vrender-components/src/axis/type.ts index cd9abccaa..cf57635c3 100644 --- a/packages/vrender-components/src/axis/type.ts +++ b/packages/vrender-components/src/axis/type.ts @@ -132,11 +132,6 @@ export interface AxisBaseAttributes extends IGroupGraphicAttribute { * 垂直于坐标轴方向的因子,默认为 1 */ verticalFactor?: number; - /** - * 坐标轴垂直方向的限制空间,该配置会影响文本的显示, - * 即如果超出,文本则会进行自动旋转、自动隐藏等动作。 - */ - verticalLimitSize?: number; /** * 坐标轴的显示位置,用于文本的防重叠处理 */ @@ -259,6 +254,31 @@ export interface LineAxisAttributes extends AxisBaseAttributes { * 网格线配置 */ grid?: LineAxisGridAttributes; + /** + * 坐标轴垂直方向的限制空间,该配置会影响文本的显示, + * 即如果超出,文本则会进行自动旋转、自动隐藏等动作。 + */ + verticalLimitSize?: number; + /** + * 坐标轴垂直方向的最小空间,如果小于该值,则以该值占据显示空间。 + * 如果同时声明了 verticalLimitSize,请保证 verticalMinSize <= verticalLimitSize,否则会以 verticalLimitSize 为准。 + */ + verticalMinSize?: number; + /** + * 轴标签配置 + */ + label?: LabelAttributes & { + /** + * label 相对于容器整体的对齐方式 + * - `top`:整体向上对齐(垂直方向) + * - `middle`:整体居中对齐(垂直方向) + * - `bottom`:整体向下对齐(垂直方向) + * - `left`:整体向左对齐(水平方向) + * - `center`:整体居中对齐(水平方向) + * - `right`:整体向右对齐(水平方向) + */ + containerAlign?: 'left' | 'right' | 'center' | 'top' | 'bottom' | 'middle'; + }; } export interface CircleAxisGridAttributes extends Omit { diff --git a/packages/vrender-components/src/util/align.ts b/packages/vrender-components/src/util/align.ts new file mode 100644 index 000000000..e5278d7dd --- /dev/null +++ b/packages/vrender-components/src/util/align.ts @@ -0,0 +1,57 @@ +import type { IText } from '@visactor/vrender'; + +export function alignAxisLabels(labels: IText[], start: number, containerSize: number, orient: string, align: string) { + if (orient === 'right' || orient === 'left') { + if (align === 'left') { + const flag = orient === 'right' ? 0 : -1; + labels.forEach(label => { + label.setAttributes({ + x: start + containerSize * flag, + textAlign: 'left' + }); + }); + } else if (align === 'right') { + const flag = orient === 'right' ? 1 : 0; + labels.forEach(label => { + label.setAttributes({ + x: start + containerSize * flag, + textAlign: 'right' + }); + }); + } else if (align === 'center') { + const flag = orient === 'right' ? 1 : -1; + labels.forEach(label => { + label.setAttributes({ + x: start + containerSize * 0.5 * flag, + textAlign: 'center' + }); + }); + } + } else if (orient === 'bottom' || orient === 'top') { + if (align === 'top') { + const flag = orient === 'bottom' ? 0 : -1; + labels.forEach(label => { + label.setAttributes({ + y: start + containerSize * flag, + textBaseline: 'top' + }); + }); + } else if (align === 'bottom') { + const flag = orient === 'bottom' ? 1 : 0; + labels.forEach(label => { + label.setAttributes({ + y: start + containerSize * flag, + textBaseline: 'bottom' + }); + }); + } else if (align === 'middle') { + const flag = orient === 'bottom' ? 1 : -1; + labels.forEach(label => { + label.setAttributes({ + y: start + containerSize * 0.5 * flag, + textBaseline: 'middle' + }); + }); + } + } +} From 65ac222f1638ba1fd57fbc6879ed221893e54e56 Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Sun, 6 Aug 2023 18:03:52 +0800 Subject: [PATCH 12/26] chore: update rush change --- ...-containerAlign-for-line-axis_2023-08-06-10-02.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-06-10-02.json diff --git a/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-06-10-02.json b/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-06-10-02.json new file mode 100644 index 000000000..9dd7eb853 --- /dev/null +++ b/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-06-10-02.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "feat: add `verticalMinSize` and `containerAlign` for line type axis, `verticalMinSize` is used to set the minumum vertical size of axis, and `containerAlign` is to set the all labels's alignment in axis container, support https://github.com/VisActor/VChart/issues/380", + "type": "minor" + } + ], + "packageName": "@visactor/vrender-components" +} From 59bb337e7e869f27cc81e8e4b49f18089a83b51c Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Sun, 6 Aug 2023 18:04:42 +0800 Subject: [PATCH 13/26] chore: add line axis containerAlign demo --- .../examples/cartesian-axis-label-align.ts | 165 ++++++++++++++++++ .../__tests__/browser/main.ts | 7 +- 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 packages/vrender-components/__tests__/browser/examples/cartesian-axis-label-align.ts diff --git a/packages/vrender-components/__tests__/browser/examples/cartesian-axis-label-align.ts b/packages/vrender-components/__tests__/browser/examples/cartesian-axis-label-align.ts new file mode 100644 index 000000000..618a8098e --- /dev/null +++ b/packages/vrender-components/__tests__/browser/examples/cartesian-axis-label-align.ts @@ -0,0 +1,165 @@ +import { AABBBounds } from '@visactor/vutils'; +import { PointScale } from '@visactor/vscale'; +import { LineAxis } from '../../../src'; +import { alignAxisLabels } from '../../../src/util/align'; +import render from '../../util/render'; + +const domain = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); +const pointScale = new PointScale().domain(domain).range([0, 1]); +const xItems = domain.map(value => { + return { + id: value, + label: value, + value: pointScale.scale(value), + rawValue: value + }; +}); +const axisBottom = new LineAxis({ + start: { x: 150, y: 350 }, + end: { x: 350, y: 350 }, + items: [xItems], + title: { + visible: true, + position: 'middle', + autoRotate: true, + padding: 4, + maxWidth: 100, + text: 'x 轴 -- bottom', + space: 0 + }, + tick: { + visible: true, + length: 10 + }, + label: { + visible: true, + space: 10, + autoRotate: true, + autoRotateAngle: [-45], + autoHide: true, + autoLimit: true, + containerAlign: 'bottom' + }, + orient: 'bottom', + verticalLimitSize: 120, + verticalMinSize: 120 +}); + +const axisTop = new LineAxis({ + start: { x: 150, y: 150 }, + end: { x: 350, y: 150 }, + items: [xItems], + title: { + visible: true, + position: 'middle', + autoRotate: true, + padding: 4, + maxWidth: 100, + text: 'x 轴 -- bottom', + space: 0 + }, + tick: { + visible: true, + length: 10 + }, + label: { + visible: true, + space: 10, + autoRotate: false, + autoRotateAngle: [45], + autoHide: true, + autoLimit: true, + containerAlign: 'bottom' + // formatMethod: () => 'AAAAAAAAAAAAAA', + }, + orient: 'top', + verticalLimitSize: 100, + verticalMinSize: 100, + verticalFactor: -1 +}); + +const axisLeft = new LineAxis({ + start: { x: 150, y: 150 }, + end: { x: 150, y: 350 }, + items: [xItems], + title: { + visible: true, + position: 'middle', + autoRotate: true, + padding: 4, + maxWidth: 100, + text: 'y 轴 -- left', + space: 0 + }, + tick: { + visible: true, + length: 10 + }, + label: { + visible: true, + autoRotate: false, + autoRotateAngle: [0, 45, 90], + autoHide: true, + autoLimit: true, + containerAlign: 'right', + space: 20 + // formatMethod: () => 'AAAAAAAAAAAAAA' + }, + orient: 'left', + verticalLimitSize: 300, + verticalMinSize: 150, + panel: { + visible: true, + style: { + stroke: 'red' + } + } +}); + +const axisRight = new LineAxis({ + start: { x: 350, y: 150 }, + end: { x: 350, y: 350 }, + items: [xItems], + title: { + visible: true, + position: 'middle', + autoRotate: true, + padding: 10, + maxWidth: 100, + text: 'y 轴 -- Right', + space: 0 + }, + tick: { + visible: true, + length: 10 + }, + label: { + visible: true, + autoRotate: false, + autoRotateAngle: [0, 45, 90], + autoHide: true, + autoLimit: true, + containerAlign: 'left', + formatMethod: (value, datum, index, data) => { + if (value === 'Y') { + return 'AAAAAAAAAAAAAA'; + } + return value; + }, + space: 20 + }, + orient: 'right', + verticalLimitSize: 140, + // verticalMinSize: 100, + verticalFactor: -1, + panel: { + visible: true, + style: { + stroke: 'red' + } + } +}); + +render([axisRight, axisLeft, axisBottom, axisTop], 'main'); + +console.log(axisRight.getElementsByName('axis-container')[0].AABBBounds.width()); diff --git a/packages/vrender-components/__tests__/browser/main.ts b/packages/vrender-components/__tests__/browser/main.ts index a5270dbb6..3e160e9e7 100644 --- a/packages/vrender-components/__tests__/browser/main.ts +++ b/packages/vrender-components/__tests__/browser/main.ts @@ -2,6 +2,10 @@ import './style.css'; const LOCAL_STORAGE_KEY = 'VRENDER_COMPONENTS_DEMOS'; const specs = [ + { + path: 'cartesian-axis-label-align', + name: '轴标签整体对齐' + }, { path: 'axis-overlap', name: '轴标签防重叠' @@ -223,7 +227,8 @@ const handleClick = (e: { target: any }, isInit?: boolean) => { module.run?.(); }) .catch(err => { - // console.log(err); + // eslint-disable-next-line no-console + console.log(err); }); } }; From 1ebe1a1a3d82ff0fa32784c928cc3d7e24a193b7 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Mon, 7 Aug 2023 15:53:38 +0800 Subject: [PATCH 14/26] feat: support text word-break, closed #235 --- .../feat-text-wordBreak_2023-08-07-07-54.json | 10 ++++ docs/demos/src/pages/richtext.ts | 3 +- docs/demos/src/pages/text.ts | 7 ++- .../contributions/textMeasure/AtextMeasure.ts | 60 +++++++++++++++---- packages/vrender/src/graphic/config.ts | 3 +- packages/vrender/src/graphic/text.ts | 39 +++++++++--- .../vrender/src/interface/graphic/text.ts | 1 + packages/vrender/src/interface/text.ts | 16 +++-- 8 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 common/changes/@visactor/vrender/feat-text-wordBreak_2023-08-07-07-54.json diff --git a/common/changes/@visactor/vrender/feat-text-wordBreak_2023-08-07-07-54.json b/common/changes/@visactor/vrender/feat-text-wordBreak_2023-08-07-07-54.json new file mode 100644 index 000000000..01110cd8a --- /dev/null +++ b/common/changes/@visactor/vrender/feat-text-wordBreak_2023-08-07-07-54.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender", + "comment": "feat: support text word-break", + "type": "minor" + } + ], + "packageName": "@visactor/vrender" +} \ No newline at end of file diff --git a/docs/demos/src/pages/richtext.ts b/docs/demos/src/pages/richtext.ts index e84d05020..1da67af67 100644 --- a/docs/demos/src/pages/richtext.ts +++ b/docs/demos/src/pages/richtext.ts @@ -14,6 +14,7 @@ export const page = () => { y: 100, width: 300, height: 0, + wordBreak: 'break-word', textConfig: [ { text: 'Mapbox', @@ -58,7 +59,7 @@ export const page = () => { }, { lineHeight: 30, - text: 'Mapbox的成立旨在改变这种状况,为制图人员和开发人员提供工具来创建他们想要的地图。值得一提的是,目前Mapbox提供的制图工具几乎都是开源的。\nMapbox目前主要提供地理数据、渲染客户端和其他与地图相关的服务。Mapbox GL JS是他们的一个开源客户端库,用于渲染Web端的可交互地图。作为Mapbox生态系统的一部分,它通常与Mapbox提供的其他服务集成在一起,统一对外使用。', + text: 'Mapbox的成立旨在改变这种状况,为制图人员和开发人员提供工具来创建他们想要的地图。值得一提的是,目前Mapbox提供的制图工具几乎都是开源的。\nMapbox目前主要提供地理数据、渲染客户端和其他与地图相关的服务。Mapbox GLabc JS是他们的一个开源客户端库,用于渲染Web端的可交互地图。作为Mapbox生态系统的一部分,它通常与Mapbox提供的其他服务集成在一起,统一对外使用。', fill: '#000' }, { diff --git a/docs/demos/src/pages/text.ts b/docs/demos/src/pages/text.ts index f6eba5a70..cf7f7844b 100644 --- a/docs/demos/src/pages/text.ts +++ b/docs/demos/src/pages/text.ts @@ -21,7 +21,9 @@ export const page = () => { fill: colorPools[5], fontWeight: 'bolder', // stroke: 'red', - text: 'Testabcdefg', + text: 'this is a text', + wordBreak: 'break-word', + maxLineWidth: 110, fontSize: 20, textBaseline: 'top' }); @@ -55,7 +57,8 @@ export const page = () => { y: 200, fill: colorPools[5], // text: ['Tffg'], - text: ['这是垂aaa直布局的文字(abc】', '这是垂ccc直布局的文字(abc】'], + text: ['这是垂aaa直textabc这是什么', '这是阿萨姆abcaaaaa'], + wordBreak: 'break-word', maxLineWidth: 200, // ellipsis: '', direction: 'vertical', diff --git a/packages/vrender/src/core/contributions/textMeasure/AtextMeasure.ts b/packages/vrender/src/core/contributions/textMeasure/AtextMeasure.ts index 33b72bef0..745dd779c 100644 --- a/packages/vrender/src/core/contributions/textMeasure/AtextMeasure.ts +++ b/packages/vrender/src/core/contributions/textMeasure/AtextMeasure.ts @@ -3,6 +3,7 @@ import type { IGraphicUtil } from '../../../interface/core'; import type { ICanvas, IContext2d, EnvType } from '../../../interface'; import type { TextOptionsType, ITextMeasure } from '../../../interface/text'; import { DefaultTextAttribute, DefaultTextStyle } from '../../../graphic/config'; +import { testLetter } from '../../../graphic/richtext/utils'; @injectable() export class ATextMeasure implements ITextMeasure { @@ -92,7 +93,8 @@ export class ATextMeasure implements ITextMeasure { clipTextVertical( verticalList: { text: string; width?: number; direction: number }[], options: TextOptionsType, - width: number + width: number, + wordBreak: boolean ): { verticalList: { text: string; width?: number; direction: number }[]; width: number; @@ -124,7 +126,24 @@ export class ATextMeasure implements ITextMeasure { 0, verticalList[i].text.length - 1 ); - out.push({ ...verticalList[i], text: clipedData.str }); + if (wordBreak && clipedData.str !== verticalList[i].text) { + let text = ''; + let length = 0; + for (let j = 0; j < i; j++) { + const item = verticalList[j]; + text += item.text; + length += item.text.length; + } + text += verticalList[i].text; + const totalLength = length + clipedData.str.length; + let index = testLetter(text, totalLength); + index = index - length; + if (index !== clipedData.str.length - 1) { + clipedData.str = clipedData.str.substring(0, index); + clipedData.width = this.measureTextWidth(clipedData.str, options); + } + } + out.push({ ...verticalList[i], text: clipedData.str, width: clipedData.width }); length += clipedData.width; } @@ -143,7 +162,8 @@ export class ATextMeasure implements ITextMeasure { clipText( text: string, options: TextOptionsType, - width: number + width: number, + wordBreak: boolean ): { str: string; width: number; @@ -159,7 +179,16 @@ export class ATextMeasure implements ITextMeasure { if (length > width) { return { str: '', width: 0 }; } - return this._clipText(text, options, width, 0, text.length - 1); + const data = this._clipText(text, options, width, 0, text.length - 1); + // 如果需要文字截断 + if (wordBreak && data.str !== text) { + const index = testLetter(text, data.str.length); + if (index !== data.str.length) { + data.str = text.substring(0, index); + data.width = this.measureTextWidth(data.str, options); + } + } + return data; } // 二分法找到最佳宽 @@ -211,19 +240,20 @@ export class ATextMeasure implements ITextMeasure { verticalList: { text: string; width?: number; direction: number }[], options: TextOptionsType, width: number, - suffix: string + suffix: string, + wordBreak: boolean ): { verticalList: { text: string; width?: number; direction: number }[]; width: number; } { if (suffix === '') { - return this.clipTextVertical(verticalList, options, width); + return this.clipTextVertical(verticalList, options, width, wordBreak); } if (verticalList.length === 0) { return { verticalList, width: 0 }; } - const output = this.clipTextVertical(verticalList, options, width); + const output = this.clipTextVertical(verticalList, options, width, wordBreak); if ( output.verticalList.length === verticalList.length && output.verticalList[output.verticalList.length - 1].width === verticalList[verticalList.length - 1].width @@ -238,7 +268,7 @@ export class ATextMeasure implements ITextMeasure { width -= suffixWidth; - const out = this.clipTextVertical(verticalList, options, width); + const out = this.clipTextVertical(verticalList, options, width, wordBreak); out.width += suffixWidth; out.verticalList.push({ text: suffix, @@ -251,13 +281,14 @@ export class ATextMeasure implements ITextMeasure { text: string, options: TextOptionsType, width: number, - suffix: string + suffix: string, + wordBreak: boolean ): { str: string; width: number; } { if (suffix === '') { - return this.clipText(text, options, width); + return this.clipText(text, options, width, wordBreak); } if (text.length === 0) { return { str: '', width: 0 }; @@ -272,6 +303,15 @@ export class ATextMeasure implements ITextMeasure { } width -= suffixWidth; const data = this._clipText(text, options, width, 0, text.length - 1); + + // 如果需要文字截断 + if (wordBreak && data.str !== text) { + const index = testLetter(text, data.str.length); + if (index !== data.str.length) { + data.str = text.substring(0, index); + data.width = this.measureTextWidth(data.str, options); + } + } data.str += suffix; data.width += suffixWidth; return data; diff --git a/packages/vrender/src/graphic/config.ts b/packages/vrender/src/graphic/config.ts index 6d0866f42..4f33b410c 100644 --- a/packages/vrender/src/graphic/config.ts +++ b/packages/vrender/src/graphic/config.ts @@ -99,7 +99,8 @@ export const DefaultTextStyle: Required = { underline: 0, lineThrough: 0, scaleIn3d: false, - direction: 'horizontal' + direction: 'horizontal', + wordBreak: 'break-all' }; export const DefaultStyle: IGraphicStyle = { diff --git a/packages/vrender/src/graphic/text.ts b/packages/vrender/src/graphic/text.ts index 5502cffb8..6bccfabe6 100644 --- a/packages/vrender/src/graphic/text.ts +++ b/packages/vrender/src/graphic/text.ts @@ -158,7 +158,8 @@ export class Text extends Graphic implements IText { fontWeight = textTheme.fontWeight, stroke = textTheme.stroke, lineHeight = attribute.lineHeight ?? (attribute.fontSize || textTheme.fontSize) + buf, - lineWidth = textTheme.lineWidth + lineWidth = textTheme.lineWidth, + wordBreak = textTheme.wordBreak } = attribute; if (!this.shouldUpdateShape() && this.cache) { width = this.cache.clipedWidth; @@ -178,12 +179,18 @@ export class Text extends Graphic implements IText { text.toString(), { fontSize, fontWeight }, maxLineWidth, - strEllipsis + strEllipsis, + wordBreak === 'break-word' ); str = data.str; width = data.width; } else { - const data = textMeasure.clipText(text.toString(), { fontSize, fontWeight }, maxLineWidth); + const data = textMeasure.clipText( + text.toString(), + { fontSize, fontWeight }, + maxLineWidth, + wordBreak === 'break-word' + ); str = data.str; width = data.width; } @@ -228,7 +235,8 @@ export class Text extends Graphic implements IText { fontWeight = textTheme.fontWeight, stroke = textTheme.stroke, lineHeight = attribute.lineHeight ?? (attribute.fontSize || textTheme.fontSize) + buf, - lineWidth = textTheme.lineWidth + lineWidth = textTheme.lineWidth, + wordBreak = textTheme.wordBreak } = attribute; if (!this.shouldUpdateShape() && this.cache) { width = this.cache.clipedWidth; @@ -251,12 +259,18 @@ export class Text extends Graphic implements IText { verticalList[0], { fontSize, fontWeight }, maxLineWidth, - strEllipsis + strEllipsis, + wordBreak === 'break-word' ); verticalList = [data.verticalList]; width = data.width; } else { - const data = textMeasure.clipTextVertical(verticalList[0], { fontSize, fontWeight }, maxLineWidth); + const data = textMeasure.clipTextVertical( + verticalList[0], + { fontSize, fontWeight }, + maxLineWidth, + wordBreak === 'break-word' + ); verticalList = [data.verticalList]; width = data.width; } @@ -361,7 +375,8 @@ export class Text extends Graphic implements IText { fontWeight = textTheme.fontWeight, stroke = textTheme.stroke, lineHeight = attribute.lineHeight ?? (attribute.fontSize || textTheme.fontSize) + buf, - lineWidth = textTheme.lineWidth + lineWidth = textTheme.lineWidth, + wordBreak = textTheme.wordBreak } = attribute; width = 0; if (!this.shouldUpdateShape() && this.cache) { @@ -390,12 +405,18 @@ export class Text extends Graphic implements IText { verticalData, { fontSize, fontWeight }, maxLineWidth, - strEllipsis + strEllipsis, + wordBreak === 'break-word' ); verticalLists[i] = data.verticalList; width = data.width; } else { - const data = textMeasure.clipTextVertical(verticalData, { fontSize, fontWeight }, maxLineWidth); + const data = textMeasure.clipTextVertical( + verticalData, + { fontSize, fontWeight }, + maxLineWidth, + wordBreak === 'break-word' + ); verticalLists[i] = data.verticalList; width = data.width; } diff --git a/packages/vrender/src/interface/graphic/text.ts b/packages/vrender/src/interface/graphic/text.ts index 52be30cd2..e780479d4 100644 --- a/packages/vrender/src/interface/graphic/text.ts +++ b/packages/vrender/src/interface/graphic/text.ts @@ -46,6 +46,7 @@ export type ITextAttribute = { lineThrough: number; scaleIn3d: boolean; direction: 'horizontal' | 'vertical'; + wordBreak: 'break-word' | 'break-all'; // textDecoration: number; // textDecorationWidth: number; // padding?: number | number[]; diff --git a/packages/vrender/src/interface/text.ts b/packages/vrender/src/interface/text.ts index 3df42c161..066749db5 100644 --- a/packages/vrender/src/interface/text.ts +++ b/packages/vrender/src/interface/text.ts @@ -13,23 +13,31 @@ export interface ITextMeasure extends IContribution { measureTextWidth: (text: string, options: TextOptionsType) => number; measureTextPixelHeight: (text: string, options: TextOptionsType) => number; measureTextBoundHieght: (text: string, options: TextOptionsType) => number; - clipText: (text: string, options: TextOptionsType, width: number) => { str: string; width: number }; + clipText: ( + text: string, + options: TextOptionsType, + width: number, + wordBreak: boolean + ) => { str: string; width: number }; clipTextVertical: ( verticalList: { text: string; width?: number; direction: number }[], options: TextOptionsType, - width: number + width: number, + wordBreak: boolean ) => { verticalList: { text: string; width?: number; direction: number }[]; width: number }; clipTextWithSuffix: ( text: string, options: TextOptionsType, width: number, - suffix: string + suffix: string, + wordBreak: boolean ) => { str: string; width: number }; clipTextWithSuffixVertical: ( verticalList: { text: string; width?: number; direction: number }[], options: TextOptionsType, width: number, - suffix: string + suffix: string, + wordBreak: boolean ) => { verticalList: { text: string; width?: number; direction: number }[]; width: number }; measureText: (text: string, options: TextOptionsType) => { width: number }; } From b3e4cc8740ecf12b387be203358d728470da64f0 Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Mon, 7 Aug 2023 16:14:11 +0800 Subject: [PATCH 15/26] test: add unit test case for line axis's `containerAlign` and `verticalMinSize` --- .../__tests__/unit/axis/line.test.ts | 267 +++++++++++++++++- 1 file changed, 266 insertions(+), 1 deletion(-) diff --git a/packages/vrender-components/__tests__/unit/axis/line.test.ts b/packages/vrender-components/__tests__/unit/axis/line.test.ts index 024258b09..8050fb068 100644 --- a/packages/vrender-components/__tests__/unit/axis/line.test.ts +++ b/packages/vrender-components/__tests__/unit/axis/line.test.ts @@ -1,5 +1,5 @@ import { PointScale, LinearScale } from '@visactor/vscale'; -import type { IGraphic, Stage, Group, ILine, Text } from '@visactor/vrender'; +import type { IGraphic, Stage, Group, ILine, Text, IGroup, IText } from '@visactor/vrender'; import type { Grid } from '../../../src'; import { LineAxis, Segment } from '../../../src'; import { createCanvas } from '../../util/dom'; @@ -753,4 +753,269 @@ describe('Line Axis', () => { expect(axisTitle.attribute.y).toBe(20); expect(axisTitle.attribute.x).toBe(165.5); }); + + describe("test axis label's containerAlign", () => { + const domain = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + const pointScale = new PointScale().domain(domain).range([0, 1]); + const xItems = domain.map(value => { + return { + id: value, + label: value, + value: pointScale.scale(value), + rawValue: value + }; + }); + + it("should work in `orient: 'left'` axis", () => { + const axis = new LineAxis({ + start: { x: 150, y: 150 }, + end: { x: 150, y: 350 }, + items: [xItems], + title: { + visible: true, + position: 'middle', + autoRotate: true, + padding: 4, + maxWidth: 100, + text: 'y 轴 -- left', + space: 0 + }, + tick: { + visible: true, + length: 10 + }, + label: { + visible: true, + autoRotate: false, + autoRotateAngle: [0, 45, 90], + autoHide: true, + autoLimit: true, + containerAlign: 'left' + // formatMethod: () => 'AAAAAAAAAAAAAA' + }, + orient: 'left', + verticalLimitSize: 300, + verticalMinSize: 100, + panel: { + visible: true, + style: { + stroke: 'red' + } + } + }); + stage.defaultLayer.add(axis as unknown as IGraphic); + stage.render(); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('left'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.x).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x1 + ); + + axis.setAttribute('label', { containerAlign: 'center' }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('center'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.x).toBe( + ((axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x1 + + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x2) * + 0.5 + ); + + axis.setAttribute('label', { containerAlign: 'right' }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('right'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.x).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x2 + ); + + axis.setAttributes({ + label: { + containerAlign: 'left' + }, + verticalMinSize: null + }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('left'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.x).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x1 + ); + expect((axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.width()).toBeCloseTo( + 11.16 + ); + }); + + it("should work in `orient: 'right'` axis", () => { + const axis = new LineAxis({ + start: { x: 350, y: 150 }, + end: { x: 350, y: 350 }, + items: [xItems], + title: { + visible: true, + position: 'middle', + autoRotate: true, + padding: 10, + maxWidth: 100, + text: 'y 轴 -- Right', + space: 0 + }, + tick: { + visible: true, + length: 10 + }, + label: { + visible: true, + autoRotate: false, + autoRotateAngle: [0, 45, 90], + autoHide: true, + autoLimit: true, + containerAlign: 'left', + formatMethod: (value, datum, index, data) => { + if (value === 'Y') { + return 'AAAAAAAAAAAAAA'; + } + return value; + } + }, + orient: 'right', + verticalLimitSize: 130, + verticalMinSize: 100, + verticalFactor: -1, + panel: { + visible: true, + style: { + stroke: 'red' + } + } + }); + stage.defaultLayer.add(axis as unknown as IGraphic); + stage.render(); + + expect(axis.AABBBounds.width()).toBeGreaterThan(100); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('left'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.x).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x1 + ); + + axis.setAttribute('label', { containerAlign: 'center' }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('center'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.x).toBe( + ((axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x1 + + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x2) * + 0.5 + ); + + axis.setAttribute('label', { containerAlign: 'right' }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('right'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.x).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.x2 + ); + + axis.setAttribute('verticalMinSize', 400); + expect(axis.AABBBounds.width()).toBeLessThan(400); + }); + + it("should work in `orient: 'top'` axis", () => { + const axis = new LineAxis({ + start: { x: 150, y: 150 }, + end: { x: 350, y: 150 }, + items: [xItems], + title: { + visible: true, + position: 'middle', + autoRotate: true, + padding: 0, + maxWidth: 100, + text: 'x 轴 -- bottom', + space: 0 + }, + tick: { + visible: true, + length: 10 + }, + label: { + visible: true, + autoRotate: false, + autoRotateAngle: [45], + autoHide: true, + autoLimit: true, + containerAlign: 'bottom' + // formatMethod: () => 'AAAAAAAAAAAAAA', + }, + orient: 'top', + verticalLimitSize: 60, + verticalMinSize: 60, + verticalFactor: -1 + }); + stage.defaultLayer.add(axis as unknown as IGraphic); + stage.render(); + + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textBaseline).toBe('bottom'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.y).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.y2 + ); + + axis.setAttribute('label', { containerAlign: 'middle' }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textBaseline).toBe('middle'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.y).toBe( + ((axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.y1 + + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.y2) * + 0.5 + ); + + axis.setAttribute('label', { containerAlign: 'top' }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textBaseline).toBe('top'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.y).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.y1 + ); + }); + + it("should work in `orient: 'bottom'` axis", () => { + const axis = new LineAxis({ + start: { x: 150, y: 350 }, + end: { x: 350, y: 350 }, + items: [xItems], + title: { + visible: true, + position: 'middle', + autoRotate: true, + padding: 0, + maxWidth: 100, + text: 'x 轴 -- bottom', + space: 0 + }, + tick: { + visible: true, + length: 10 + }, + label: { + visible: true, + space: 10, + // autoRotate: true, + // autoRotateAngle: [-90], + autoHide: true, + autoLimit: true, + containerAlign: 'bottom' + }, + orient: 'bottom', + verticalLimitSize: 120, + verticalMinSize: 120 + }); + stage.defaultLayer.add(axis as unknown as IGraphic); + stage.render(); + + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textBaseline).toBe('bottom'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.y).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.y2 + ); + + axis.setAttribute('label', { containerAlign: 'middle' }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textBaseline).toBe('middle'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.y).toBe( + ((axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.y1 + + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.y2) * + 0.5 + ); + + axis.setAttribute('label', { containerAlign: 'top' }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textBaseline).toBe('top'); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.y).toBe( + (axis.getElementsByName('axis-label-container-layer-0')[0] as IGroup).AABBBounds.y1 + ); + }); + }); }); From 8b043fb52a734a35bc466a7501ac9db9d3112246 Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Mon, 7 Aug 2023 16:17:55 +0800 Subject: [PATCH 16/26] fix: fix the line axis type --- packages/vrender-components/__tests__/unit/axis/line.test.ts | 1 + packages/vrender-components/src/axis/type.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vrender-components/__tests__/unit/axis/line.test.ts b/packages/vrender-components/__tests__/unit/axis/line.test.ts index 8050fb068..0003f35dd 100644 --- a/packages/vrender-components/__tests__/unit/axis/line.test.ts +++ b/packages/vrender-components/__tests__/unit/axis/line.test.ts @@ -826,6 +826,7 @@ describe('Line Axis', () => { axis.setAttributes({ label: { + visible: true, containerAlign: 'left' }, verticalMinSize: null diff --git a/packages/vrender-components/src/axis/type.ts b/packages/vrender-components/src/axis/type.ts index cf57635c3..545b322a7 100644 --- a/packages/vrender-components/src/axis/type.ts +++ b/packages/vrender-components/src/axis/type.ts @@ -241,7 +241,7 @@ export interface IGrid3dType { anchor3d?: [number, number]; } -export interface LineAxisAttributes extends AxisBaseAttributes { +export interface LineAxisAttributes extends Omit { /** * 起始点坐标 */ From 4e39ca528d443d6fdb9d7e3e850f2dca3ef3cfe0 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Mon, 7 Aug 2023 16:22:29 +0800 Subject: [PATCH 17/26] fix: fix type error --- .../vrender/src/core/contributions/textMeasure/layout.ts | 6 ++++-- packages/vrender/src/graphic/text.ts | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/vrender/src/core/contributions/textMeasure/layout.ts b/packages/vrender/src/core/contributions/textMeasure/layout.ts index 71afe25b7..a520a0d3c 100644 --- a/packages/vrender/src/core/contributions/textMeasure/layout.ts +++ b/packages/vrender/src/core/contributions/textMeasure/layout.ts @@ -45,6 +45,7 @@ export class CanvasTextLayout { textBaseline: TextBaselineType, lineHeight: number, suffix: string, + wordBreak: boolean, miniApp: boolean ): LayoutType { // 拆分str @@ -54,7 +55,7 @@ export class CanvasTextLayout { const bboxOffset: vec2 = [0, 0]; while (str.length > 0) { - const { str: clipText } = this.textMeasure.clipTextWithSuffix(str, this.textOptions, width, suffix); + const { str: clipText } = this.textMeasure.clipTextWithSuffix(str, this.textOptions, width, suffix, wordBreak); linesLayout.push({ str: clipText, width: this.textMeasure.measureTextWidth(clipText, this.textOptions) @@ -99,6 +100,7 @@ export class CanvasTextLayout { textBaseline: TextBaselineType, lineHeight: number, suffix: string = '', + wordBreak: boolean, lineWidth?: number ): LayoutType { lines = lines.map(l => l.toString()) as string[]; @@ -111,7 +113,7 @@ export class CanvasTextLayout { for (let i = 0, len = lines.length; i < len; i++) { width = Math.min(this.textMeasure.measureTextWidth(lines[i] as string, this.textOptions), lineWidth); linesLayout.push({ - str: this.textMeasure.clipTextWithSuffix(lines[i] as string, this.textOptions, width, suffix).str, + str: this.textMeasure.clipTextWithSuffix(lines[i] as string, this.textOptions, width, suffix, wordBreak).str, width }); } diff --git a/packages/vrender/src/graphic/text.ts b/packages/vrender/src/graphic/text.ts index 6bccfabe6..4d625df01 100644 --- a/packages/vrender/src/graphic/text.ts +++ b/packages/vrender/src/graphic/text.ts @@ -321,7 +321,8 @@ export class Text extends Graphic implements IText { ellipsis = textTheme.ellipsis, maxLineWidth, stroke = textTheme.stroke, - lineWidth = textTheme.lineWidth + lineWidth = textTheme.lineWidth, + wordBreak = textTheme.wordBreak } = attribute; if (!this.shouldUpdateShape() && this.cache?.layoutData) { @@ -341,6 +342,7 @@ export class Text extends Graphic implements IText { textBaseline as any, lineHeight, ellipsis === true ? (textTheme.ellipsis as string) : ellipsis || undefined, + wordBreak === 'break-word', maxLineWidth ); const { bbox } = layoutData; From 8c7fe5cdb18c225cbc254f024a268d47bd58680f Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Mon, 7 Aug 2023 16:26:00 +0800 Subject: [PATCH 18/26] feat: support animate-bind event, closed #264 --- .../vrender/feat-onAnimateBind_2023-08-07-08-26.json | 10 ++++++++++ packages/vrender/src/graphic/graphic.ts | 4 ++++ 2 files changed, 14 insertions(+) create mode 100644 common/changes/@visactor/vrender/feat-onAnimateBind_2023-08-07-08-26.json diff --git a/common/changes/@visactor/vrender/feat-onAnimateBind_2023-08-07-08-26.json b/common/changes/@visactor/vrender/feat-onAnimateBind_2023-08-07-08-26.json new file mode 100644 index 000000000..d423520b6 --- /dev/null +++ b/common/changes/@visactor/vrender/feat-onAnimateBind_2023-08-07-08-26.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender", + "comment": "feat: support animate-bind event", + "type": "minor" + } + ], + "packageName": "@visactor/vrender" +} \ No newline at end of file diff --git a/packages/vrender/src/graphic/graphic.ts b/packages/vrender/src/graphic/graphic.ts index 368b19d2c..83bebc687 100644 --- a/packages/vrender/src/graphic/graphic.ts +++ b/packages/vrender/src/graphic/graphic.ts @@ -262,6 +262,10 @@ export abstract class Graphic = Partial Date: Mon, 7 Aug 2023 16:55:59 +0800 Subject: [PATCH 19/26] fix: fix the issue of line connect, closed #109 --- packages/vrender/src/common/render-curve.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vrender/src/common/render-curve.ts b/packages/vrender/src/common/render-curve.ts index 3b439e930..592d50ee8 100644 --- a/packages/vrender/src/common/render-curve.ts +++ b/packages/vrender/src/common/render-curve.ts @@ -54,14 +54,14 @@ export function drawSegments( if (!segPath) { return; } - let needMoveTo: boolean = true; + let needMoveTo: boolean = !drawConnect; const { curves } = segPath; if (percent >= 1) { if (drawConnect) { curves.forEach((curve, i) => { if (curve.defined) { // connect段结束,封闭 - if (needMoveTo && i !== 0) { + if (needMoveTo) { path.lineTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); } else if (!needMoveTo) { // 持续moveTo @@ -138,7 +138,7 @@ export function drawSegments( if (drawConnect) { if (curve.defined) { // connect段结束,封闭 - if (needMoveTo && i !== 0) { + if (needMoveTo) { path.lineTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); } needMoveTo = false; From 158ae7b45392652f498b8a163298b7a5d1b8a5aa Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Mon, 7 Aug 2023 22:20:13 +0800 Subject: [PATCH 20/26] fix(vrender-components): the axis label's textAlign and textBaseline should auto adjust when angle is set, releate https://github.com/VisActor/VChart/issues/439 --- .../__tests__/unit/axis/line.test.ts | 190 ++++++++++++++++++ packages/vrender-components/src/axis/line.ts | 10 +- .../src/axis/overlap/auto-rotate.ts | 4 +- 3 files changed, 200 insertions(+), 4 deletions(-) diff --git a/packages/vrender-components/__tests__/unit/axis/line.test.ts b/packages/vrender-components/__tests__/unit/axis/line.test.ts index 0003f35dd..0a14c66fb 100644 --- a/packages/vrender-components/__tests__/unit/axis/line.test.ts +++ b/packages/vrender-components/__tests__/unit/axis/line.test.ts @@ -754,6 +754,196 @@ describe('Line Axis', () => { expect(axisTitle.attribute.x).toBe(165.5); }); + it('should set the correct textAlign and textBaseline when set label angle', () => { + const axis = new LineAxis({ + title: { + space: 10, + padding: 0, + textStyle: { + fontSize: 14, + fill: '#333333', + fontWeight: 'normal', + fillOpacity: 1 + }, + visible: true, + autoRotate: false, + shape: {}, + background: {}, + text: '标题', + maxWidth: 400, + pickable: false, + childrenPickable: false + }, + label: { + visible: true, + inside: false, + space: 10, + autoRotate: true, + style: { + fontSize: 14, + fill: '#89909D', + fontWeight: 'normal', + fillOpacity: 1, + angle: -Math.PI * 0.25 + }, + state: { + hover_reverse: { + fill: 'red' + }, + selected_reverse: { + fill: 'red' + } + }, + // autoHide: true, + autoLimit: true + }, + tick: { + visible: true, + inside: false, + alignWithLabel: true, + length: 4, + style: { + lineWidth: 1, + stroke: '#D9DDE4', + strokeOpacity: 1 + } + }, + subTick: { + visible: false, + inside: false, + count: 4, + length: 2, + style: { + lineWidth: 1, + stroke: '#D9DDE4', + strokeOpacity: 1 + } + }, + line: { + visible: true, + style: { + lineWidth: 1, + stroke: '#D9DDE4', + strokeOpacity: 1 + } + }, + grid: { + style: { + lineWidth: 1, + stroke: '#EBEDF2', + strokeOpacity: 1, + lineDash: [] + }, + visible: false, + length: 424, + type: 'line', + depth: 0 + }, + subGrid: { + visible: false, + style: { + lineWidth: 1, + stroke: '#EBEDF2', + strokeOpacity: 1, + lineDash: [4, 4] + } + }, + x: 50, + y: 436, + start: { + x: 0, + y: 0 + }, + end: { + x: 200, + y: 0 + }, + items: [ + [ + { + id: '1990', + label: '1990', + value: 0.11538461538461535, + rawValue: '1990' + }, + { + id: '1995', + label: '1995', + value: 0.2692307692307692, + rawValue: '1995' + }, + { + id: '2000', + label: '2000', + value: 0.423076923076923, + rawValue: '2000' + }, + { + id: '2005', + label: '2005', + value: 0.5769230769230768, + rawValue: '2005' + }, + { + id: '2010', + label: '2010', + value: 0.7307692307692307, + rawValue: '2010' + }, + { + id: '2015', + label: '2015', + value: 0.8846153846153845, + rawValue: '2015' + } + ] + ], + visible: true, + pickable: true, + orient: 'bottom', + select: true, + hover: true, + panel: { + visible: true, + style: { + fillOpacity: 0 + }, + state: { + hover: { + fillOpacity: 0.65, + fill: '#DDE3E9', + cursor: 'pointer' + }, + selected: { + fillOpacity: 0.65, + fill: '#9CCBDB', + cursor: 'pointer' + } + } + }, + verticalFactor: 1, + verticalLimitSize: 150 + }); + stage.defaultLayer.add(axis as unknown as IGraphic); + stage.render(); + + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('right'); + + axis.setAttribute('label', { + style: { + angle: Math.PI * 0.25 + } + }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('left'); + + axis.setAttribute('label', { + style: { + angle: Math.PI * 0.5 + } + }); + expect((axis.getElementsByName('axis-label')[0] as IText).attribute.textAlign).toBe('left'); + }); + describe("test axis label's containerAlign", () => { const domain = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); const pointScale = new PointScale().domain(domain).range([0, 1]); diff --git a/packages/vrender-components/src/axis/line.ts b/packages/vrender-components/src/axis/line.ts index 3586925a9..dd1b28b24 100644 --- a/packages/vrender-components/src/axis/line.ts +++ b/packages/vrender-components/src/axis/line.ts @@ -39,7 +39,7 @@ import { DEFAULT_AXIS_THEME } from './config'; import { AXIS_ELEMENT_NAME, DEFAULT_STATES } from './constant'; import { measureTextSize } from '../util'; import { autoHide as autoHideFunc } from './overlap/auto-hide'; -import { autoRotate as autoRotateFunc } from './overlap/auto-rotate'; +import { autoRotate as autoRotateFunc, rotateXAxis, rotateYAxis } from './overlap/auto-rotate'; import { autoLimit as autoLimitFunc } from './overlap/auto-limit'; import { alignAxisLabels } from '../util/align'; @@ -422,7 +422,13 @@ export class LineAxis extends AxisBase { layer: number, layerCount: number ): void { - return; + // 调整对齐方式 + const orient = this.attribute.orient; + if (orient === 'left' || orient === 'right') { + rotateYAxis(orient, labelShapes); + } else if (orient === 'bottom' || orient === 'top') { + rotateXAxis(orient, labelShapes); + } } protected handleLabelsOverlap( labelShapes: IText[], diff --git a/packages/vrender-components/src/axis/overlap/auto-rotate.ts b/packages/vrender-components/src/axis/overlap/auto-rotate.ts index 1330be3ba..1cd4215da 100644 --- a/packages/vrender-components/src/axis/overlap/auto-rotate.ts +++ b/packages/vrender-components/src/axis/overlap/auto-rotate.ts @@ -109,7 +109,7 @@ function genRotateBounds(items: IText[]) { }); } -function rotateYAxis(orient: string, items: IText[]) { +export function rotateYAxis(orient: string, items: IText[]) { // 0, 0-90, 90, 90-180, 180, 180-270, 270, 270-360, 360 let align = ['right', 'right', 'center', 'left', 'center', 'left', 'center', 'right', 'right']; let baseline = ['middle', 'middle', 'top', 'top', 'middle', 'middle', 'bottom', 'bottom', 'middle']; @@ -147,7 +147,7 @@ function rotateYAxis(orient: string, items: IText[]) { }); } -function rotateXAxis(orient: string, items: IText[]) { +export function rotateXAxis(orient: string, items: IText[]) { // 0, 0-90, 90, 90-180, 180, 180-270, 270, 270-360, 360 let align = ['center', 'left', 'left', 'left', 'center', 'right', 'right', 'right', 'left']; let baseline = ['top', 'top', 'middle', 'bottom', 'bottom', 'bottom', 'middle', 'top', 'top']; From 554b35183faf22ddf25f3dbfa295e3052160e8fc Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Mon, 7 Aug 2023 22:20:54 +0800 Subject: [PATCH 21/26] chore: update rush change --- ...-containerAlign-for-line-axis_2023-08-07-14-20.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-07-14-20.json diff --git a/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-07-14-20.json b/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-07-14-20.json new file mode 100644 index 000000000..cedd536e3 --- /dev/null +++ b/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-07-14-20.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "fix: the axis label's textAlign and textBaseline should auto adjust when angle is set, releate https://github.com/VisActor/VChart/issues/439", + "type": "patch" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file From 3c293441e06ebf28bba990f2abfcc18845922732 Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Mon, 7 Aug 2023 23:45:12 +0800 Subject: [PATCH 22/26] fix(vrender-components): panel only works for line axis --- packages/vrender-components/src/axis/base.ts | 24 ++------------ packages/vrender-components/src/axis/line.ts | 33 +++++++++++++++++-- packages/vrender-components/src/axis/type.ts | 34 ++++++++++---------- 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/packages/vrender-components/src/axis/base.ts b/packages/vrender-components/src/axis/base.ts index 6facae437..86c1445f3 100644 --- a/packages/vrender-components/src/axis/base.ts +++ b/packages/vrender-components/src/axis/base.ts @@ -192,8 +192,8 @@ export abstract class AxisBase extends AbstractCom } }; - private _renderInner(container: IGroup) { - const { title, label, tick, line, grid, items, panel } = this.attribute; + protected _renderInner(container: IGroup) { + const { title, label, tick, line, grid, items } = this.attribute; const axisContainer = createGroup({ x: 0, y: 0, zIndex: 1 }); axisContainer.name = AXIS_ELEMENT_NAME.axisContainer; @@ -242,26 +242,6 @@ export abstract class AxisBase extends AbstractCom if (title?.visible) { this.renderTitle(axisContainer); } - - // TODO: 目前是通过包围盒绘制,在一些情况下会有那问题,比如圆弧轴、带了箭头的坐标轴等 - // 坐标轴主体 panel - if (panel && panel.visible) { - const axisContainerBounds = axisContainer.AABBBounds; - const bgRect = createRect({ - x: axisContainerBounds.x1, - y: axisContainerBounds.y1, - width: axisContainerBounds.width(), - height: axisContainerBounds.height(), - ...panel.style - }); - bgRect.name = AXIS_ELEMENT_NAME.background; - bgRect.id = this._getNodeId('background'); - - if (!isEmpty(panel.state)) { - bgRect.states = merge({}, DEFAULT_STATES, panel.state); - } - axisContainer.insertBefore(bgRect, axisContainer.firstChild); - } } protected renderTicks(container: IGroup) { const tickLineItems = this.getTickLineItems(); diff --git a/packages/vrender-components/src/axis/line.ts b/packages/vrender-components/src/axis/line.ts index dd1b28b24..5fca7c369 100644 --- a/packages/vrender-components/src/axis/line.ts +++ b/packages/vrender-components/src/axis/line.ts @@ -13,7 +13,8 @@ import { isEmpty, isFunction, isValidNumber, - isValid + isValid, + normalizePadding } from '@visactor/vutils'; import { createRect, type IGroup, type INode, type IText, type TextBaselineType } from '@visactor/vrender'; import type { SegmentAttributes } from '../segment'; @@ -63,6 +64,32 @@ export class LineAxis extends AxisBase { } } + protected _renderInner(container: IGroup) { + super._renderInner(container); + const { panel } = this.attribute; + + // TODO: 目前是通过包围盒绘制,在一些情况下会有那问题,比如圆弧轴、带了箭头的坐标轴等 + // 坐标轴主体 panel + if (panel && panel.visible) { + const axisContainer = this.axisContainer; + const axisContainerBounds = axisContainer.AABBBounds; + const bgRect = createRect({ + x: axisContainerBounds.x1, + y: axisContainerBounds.y1, + width: axisContainerBounds.width(), + height: axisContainerBounds.height(), + ...panel.style + }); + bgRect.name = AXIS_ELEMENT_NAME.background; + bgRect.id = this._getNodeId('background'); + + if (!isEmpty(panel.state)) { + bgRect.states = merge({}, DEFAULT_STATES, panel.state); + } + axisContainer.insertBefore(bgRect, axisContainer.firstChild); + } + } + // TODO: break protected renderLine(container: IGroup): void { const { start, end, line } = this.attribute as LineAxisAttributes; @@ -517,6 +544,7 @@ export class LineAxis extends AxisBase { x = axisLabelContainerBounds.x1; y = axisLabelContainerBounds.y1; } + const bgRect = createRect({ x, y, @@ -556,7 +584,8 @@ export class LineAxis extends AxisBase { const tickLength = tick?.visible ? tick.length ?? 4 : 0; if (title?.visible) { titleHeight = measureTextSize(title.text, title.textStyle).height; - titleSpacing = title.space; + const padding = normalizePadding(title.padding); + titleSpacing = title.space + padding[0] + padding[2]; } if (limitLength) { limitLength = (limitLength - labelSpace - titleSpacing - titleHeight - axisLineWidth - tickLength) / layerCount; diff --git a/packages/vrender-components/src/axis/type.ts b/packages/vrender-components/src/axis/type.ts index 545b322a7..d48c73db7 100644 --- a/packages/vrender-components/src/axis/type.ts +++ b/packages/vrender-components/src/axis/type.ts @@ -166,23 +166,6 @@ export interface AxisBaseAttributes extends IGroupGraphicAttribute { * 子刻度对应网格线配置 */ subGrid?: SubGridAttributesForAxis; - /** - * 坐标轴背景配置 - */ - panel?: { - /** - * 是否绘制坐标轴背景 - */ - visible?: boolean; - /** - * 坐标轴背景配置 - */ - style?: Partial; - /** - * 坐标轴背景交互状态样式配置 - */ - state?: AxisItemStateStyle>; - }; } export type LineGridOfLineAxisAttributes = Omit & { @@ -279,6 +262,23 @@ export interface LineAxisAttributes extends Omit { */ containerAlign?: 'left' | 'right' | 'center' | 'top' | 'bottom' | 'middle'; }; + /** + * 坐标轴背景配置 + */ + panel?: { + /** + * 是否绘制坐标轴背景 + */ + visible?: boolean; + /** + * 坐标轴背景配置 + */ + style?: Partial; + /** + * 坐标轴背景交互状态样式配置 + */ + state?: AxisItemStateStyle>; + }; } export interface CircleAxisGridAttributes extends Omit { From 750c0acee9ce095c049387a30c74a87ac9122418 Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Mon, 7 Aug 2023 23:45:55 +0800 Subject: [PATCH 23/26] chore: update rush change --- ...-containerAlign-for-line-axis_2023-08-07-15-45.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-07-15-45.json diff --git a/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-07-15-45.json b/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-07-15-45.json new file mode 100644 index 000000000..3a4686566 --- /dev/null +++ b/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-07-15-45.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "fix: panel only works for line axis", + "type": "patch" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file From 0fc71a4ffc01dc6358a84f07f70997354de22749 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Tue, 8 Aug 2023 16:17:11 +0800 Subject: [PATCH 24/26] fix: fix the issue of area connect, closed #274 --- docs/demos/src/pages/area.ts | 23 +- docs/demos/src/pages/line.ts | 185 ++++++++++++---- packages/vrender/src/common/render-area.ts | 200 ++++++++++++++---- packages/vrender/src/common/render-curve.ts | 114 ++++++---- packages/vrender/src/common/seg-context.ts | 65 ++++-- packages/vrender/src/common/segment/basis.ts | 23 +- .../src/common/segment/curve/cubic-bezier.ts | 2 + .../vrender/src/common/segment/curve/line.ts | 2 + .../common/segment/curve/quadratic-bezier.ts | 3 + .../src/common/segment/linear-closed.ts | 9 +- packages/vrender/src/common/segment/linear.ts | 11 +- .../vrender/src/common/segment/monotone.ts | 42 +++- packages/vrender/src/common/segment/step.ts | 19 +- packages/vrender/src/interface/curve.ts | 7 +- packages/vrender/src/interface/path.ts | 2 + .../contributions/render/area-render.ts | 28 ++- 16 files changed, 549 insertions(+), 186 deletions(-) diff --git a/docs/demos/src/pages/area.ts b/docs/demos/src/pages/area.ts index 1eb3b2014..ccce1295e 100644 --- a/docs/demos/src/pages/area.ts +++ b/docs/demos/src/pages/area.ts @@ -7,7 +7,7 @@ const subP1 = [ [40, 60], [60, 20], [70, 30] -].map(item => ({ x: item[0], y: item[1], y1: 120, defined: item[0] !== 70 })); +].map(item => ({ x: item[0], y: item[1], y1: 120, defined: item[0] !== 20 })); const subP2 = [ [80, 80], @@ -28,7 +28,7 @@ const points = [ [160, 40], [200, 20], [240, 50] -].map(item => ({ x: item[0], y: item[1], y1: 120, defined: item[0] !== 70 })); +].map(item => ({ x: item[0], y: item[1], y1: 120, defined: item[0] !== 40 && item[0] !== 70 })); export const page = () => { const graphics: IGraphic[] = []; @@ -52,7 +52,8 @@ export const page = () => { { offset: 1, color: 'red' } ] }, - connectedType: 'zero', + clipRange: 0.3, + connectedType: 'connect', connectedX: null, connectedY: 100, connectedStyle: { @@ -71,7 +72,16 @@ export const page = () => { x: ((i * 300) % 900) + 100, y: Math.floor((i * 300) / 900) * 200, segments: [ - { points: subP1, fill: colorPools[3], stroke: ['red', false], lineWidth: 10 }, + { + points: subP1, + fill: colorPools[3], + stroke: ['red', false], + lineWidth: 10, + connectedType: 'connect', + connectedStyle: { + fill: 'grey' + } + }, { points: subP2, stroke: ['red', false], @@ -80,12 +90,17 @@ export const page = () => { textureColor: 'grey' } ], + connectedType: 'connect', fill: true, stroke: true }) ); }); + graphics.forEach(item => { + item.animate().to({ clipRange: 0 }, 0, 'linear').to({ clipRange: 1 }, 10000, 'linear'); + }); + const stage = createStage({ canvas: document.getElementById('main'), autoRender: true diff --git a/docs/demos/src/pages/line.ts b/docs/demos/src/pages/line.ts index 65bc6d5e2..4bc8f485d 100644 --- a/docs/demos/src/pages/line.ts +++ b/docs/demos/src/pages/line.ts @@ -1,6 +1,7 @@ import { createStage, createLine, container, IGraphic } from '@visactor/vrender'; import { roughModule } from '@visactor/vrender-kits'; import { addShapesToStage, colorPools } from '../utils'; +import { createSymbol } from '@visactor/vrender'; // container.load(roughModule); @@ -36,51 +37,161 @@ const points = [ export const page = () => { const graphics: IGraphic[] = []; - ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { - graphics.push( - createLine({ - points, - curveType: type as any, - x: ((i * 300) % 900) + 100, - y: Math.floor((i * 300) / 900) * 200, - stroke: 'red', - connectedType: 'connected', - connectedStyle: { - stroke: 'green' + // ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { + graphics.push( + createLine({ + visible: true, + lineWidth: 2, + lineCap: 'round', + connectedType: 'connect', + stroke: '#1664FF', + defined: true, + z: null, + points: [ + { + x: 41.368421052631575, + y: 112, + context: 0 }, - connectedX: null, - connectedY: 100 - }) - ); - }); + { + x: 96.52631578947368, + y: 224, + context: 1, + defined: false + }, + { + x: 151.68421052631578, + y: 89.59999999999998, + context: 2 + }, + { + x: 206.84210526315786, + y: 78.40000000000002, + context: 3 + }, + { + x: 262, + y: 67.20000000000002, + context: 4 + }, + { + x: 317.1578947368421, + y: 224, + context: 5, + defined: false + }, + { + x: 372.31578947368416, + y: 44.79999999999999, + context: 6 + }, + { + x: 427.4736842105263, + y: 224, + context: 7, + defined: false + }, + { + x: 482.6315789473684, + y: 425.59999999999997, + context: 8 + } + ], + curveType: 'step', + segments: null, + x: 0, + y: 0, + pickable: true + }) + ); - ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { - i += 7; + [ + { + x: 41.368421052631575, + y: 112, + context: 0 + }, + { + x: 96.52631578947368, + y: 224, + context: 1, + defined: false + }, + { + x: 151.68421052631578, + y: 89.59999999999998, + context: 2 + }, + { + x: 206.84210526315786, + y: 78.40000000000002, + context: 3 + }, + { + x: 262, + y: 67.20000000000002, + context: 4 + }, + { + x: 317.1578947368421, + y: 224, + context: 5, + defined: false + }, + { + x: 372.31578947368416, + y: 44.79999999999999, + context: 6 + }, + { + x: 427.4736842105263, + y: 284, + context: 7, + defined: false + }, + { + x: 482.6315789473684, + y: 425.59999999999997, + context: 8 + } + ].forEach(item => { graphics.push( - createLine({ - points, - curveType: type as any, - x: ((i * 300) % 900) + 100, - y: Math.floor((i * 300) / 900) * 200, - segments: [ - { - points: subP1, - stroke: colorPools[3], - lineWidth: 6, - connectedType: 'connect', - connectedStyle: { - stroke: 'green' - } - }, - { points: subP2, stroke: colorPools[2], lineWidth: 2, lineDash: [3, 3] } - ], - stroke: 'red' + createSymbol({ + x: item.x, + y: item.y, + fill: 'red' }) ); }); + // }); + + // ['linear', 'step', 'stepBefore', 'stepAfter', 'basis', 'monotoneX', 'monotoneY'].forEach((type, i) => { + // i += 7; + // graphics.push( + // createLine({ + // points, + // curveType: type as any, + // x: ((i * 300) % 900) + 100, + // y: Math.floor((i * 300) / 900) * 200, + // segments: [ + // { + // points: subP1, + // stroke: colorPools[3], + // lineWidth: 6, + // connectedType: 'connect', + // connectedStyle: { + // stroke: 'green' + // } + // }, + // { points: subP2, stroke: colorPools[2], lineWidth: 2, lineDash: [3, 3] } + // ], + // stroke: 'red' + // }) + // ); + // }); graphics.forEach(item => { - item.animate().to({ clipRange: 0 }, 0, 'linear').to({ clipRange: 1 }, 1000, 'linear'); + item.animate().to({ clipRange: 0 }, 0, 'linear').to({ clipRange: 1 }, 10000, 'linear'); }); const stage = createStage({ diff --git a/packages/vrender/src/common/render-area.ts b/packages/vrender/src/common/render-area.ts index 09fb9c775..4b98560c9 100644 --- a/packages/vrender/src/common/render-area.ts +++ b/packages/vrender/src/common/render-area.ts @@ -1,4 +1,4 @@ -import type { IPoint } from '@visactor/vutils'; +import type { IPoint, IPointLike } from '@visactor/vutils'; import { abs } from '@visactor/vutils'; import type { IAreaCacheItem, ICubicBezierCurve, ICurve, IDirection, ILineCurve, IPath2D } from '../interface'; import { Direction } from './enums'; @@ -40,26 +40,67 @@ export function drawAreaSegments( const bottomList: ICurve[] = []; let lastDefined: boolean = true; if (drawConnect) { - for (let i = 0, n = top.curves.length; i < n; i++) { - const topCurve = top.curves[i]; - if (lastDefined !== topCurve.defined) { - if (!lastDefined) { + 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; - } else { - topList.push(topCurve); - bottomList.push(bottom.curves[n - i - 1]); + defined0 = !defined0; } - lastDefined = !lastDefined; } else { - if (!lastDefined) { - topList.push(topCurve); - bottomList.push(bottom.curves[n - i - 1]); + // 找到合法的点 + 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; + } } } - } - drawAreaBlock(path, topList, bottomList, params); + lastCurve = curve; + }); + drawAreaConnectBlock(path, topList, bottomList, params); } else { for (let i = 0, n = top.curves.length; i < n; i++) { const topCurve = top.curves[i]; @@ -113,6 +154,9 @@ export function drawAreaSegments( let lastDefined: boolean = true; const topList: ICurve[] = []; const bottomList: ICurve[] = []; + let defined0 = true; + let lastTopCurve: ICurve; + let lastBottomCurve: ICurve; for (let i = 0, n = top.curves.length; i < n; i++) { const topCurve = top.curves[i]; const curCurveLength = topCurve.getLength(direction); @@ -121,48 +165,112 @@ export function drawAreaSegments( break; } drawedLengthUntilLast += curCurveLength; - 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]; + + 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; } - lastDefined = !lastDefined; - } else { - if (lastDefined) { - tc = topCurve; - bc = bottom.curves[n - i - 1]; + if (lastTopCurve && lastTopCurve.originP1 === lastTopCurve.originP2) { + currentTopCurve = lastTopCurve; + currentBottomCurve = lastBottomCurve; } - } - - if (tc && bc) { - if (percent < 1) { - if (tc.p2 && tc.p3) { - tc = divideCubic(tc as ICubicBezierCurve, percent)[0]; + 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; + } + } 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 { - tc = divideLinear(tc as ILineCurve, percent)[0]; + // 非法变非法/合法,看情况要不要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; + } } - if (bc.p2 && bc.p3) { - bc = divideCubic(bc as ICubicBezierCurve, 1 - percent)[1]; + } + lastTopCurve = topCurve; + // drawAreaBlock(path, topList, bottomList, params); + } 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 { - bc = divideLinear(bc as ILineCurve, 1 - percent)[1]; + tc = topCurve; + bc = bottom.curves[n - i - 1]; + } + lastDefined = !lastDefined; + } else { + if (lastDefined) { + tc = topCurve; + bc = bottom.curves[n - i - 1]; } } - tc.defined = lastDefined; - bc.defined = lastDefined; - topList.push(tc); - bottomList.push(bc); + + 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 = null; - bc = null; + if (drawConnect) { + drawAreaConnectBlock(path, topList, bottomList, params); + } else { + drawAreaBlock(path, topList, bottomList, params); } - drawAreaBlock(path, topList, bottomList, params); // const totalLength = segPath.tryUpdateLength(); diff --git a/packages/vrender/src/common/render-curve.ts b/packages/vrender/src/common/render-curve.ts index 592d50ee8..8d94f3a04 100644 --- a/packages/vrender/src/common/render-curve.ts +++ b/packages/vrender/src/common/render-curve.ts @@ -1,5 +1,5 @@ import type { IPoint, IPointLike } from '@visactor/vutils'; -import { min } from '@visactor/vutils'; +import { last, min } from '@visactor/vutils'; import type { IAreaSegment, @@ -54,40 +54,55 @@ export function drawSegments( if (!segPath) { return; } - let needMoveTo: boolean = !drawConnect; + let needMoveTo: boolean = true; const { curves } = segPath; if (percent >= 1) { if (drawConnect) { + // return; + let defined0 = true; + let lastCurve: ICurve; curves.forEach((curve, i) => { + // step的逻辑 + let p0 = curve.p0; + if (curve.originP1 === curve.originP2) { + lastCurve = curve; + return; + } + if (lastCurve && lastCurve.originP1 === lastCurve.originP2) { + p0 = lastCurve.p0; + } if (curve.defined) { - // connect段结束,封闭 - if (needMoveTo) { - path.lineTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); - } else if (!needMoveTo) { - // 持续moveTo - // if (curve.p2 && curve.p3) { - // path.moveTo(curve.p3.x + offsetX, curve.p3.y + offsetY, offsetZ); - // } else { - // path.moveTo(curve.p1.x + offsetX, curve.p1.y + offsetY, offsetZ); - // } + // 非法变合法需要lineTo,合法变非法需要moveTo,初始非法需要moveTo + if (!defined0) { + path.lineTo(p0.x + offsetX, p0.y + offsetY, offsetZ); + defined0 = !defined0; } - needMoveTo = false; } else { - // connect段开始 - if (!needMoveTo) { - path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + // 找到合法的点 + const { originP1, originP2 } = curve; + let validP: IPointLike; + if (originP1 && originP1.defined !== false) { + validP = p0; + } else if (originP1 && originP2.defined !== false) { + validP = curve.p3 ?? curve.p1; + } + // 合法/(初始)变非法,moveTo + if (defined0) { + defined0 = !defined0; + const x = validP ? validP.x : curve.p0.x; + const y = validP ? validP.y : curve.p0.y; + path.moveTo(x + offsetX, y + offsetY, offsetZ); } else { - // 如果是zero,那么每一段都要绘制一下(第一段不需要绘制) - if (mode === 'zero') { - path.lineTo( - (isFinite(zeroX) ? zeroX : curve.p0.x) + offsetX, - (isFinite(zeroY) ? zeroY : curve.p0.y) + offsetY, - offsetZ - ); + // 非法变非法/合法,看情况要不要lineTo + if (validP) { + // 非法变合法,需要lineTo + defined0 = !defined0; + path.lineTo(validP.x + offsetX, validP.y + offsetY, offsetZ); } } - needMoveTo = true; } + + lastCurve = curve; }); } else { curves.forEach(curve => { @@ -126,6 +141,8 @@ export function drawSegments( const totalDrawLength = percent * totalLength; // 直到上次绘制的长度 let drawedLengthUntilLast = 0; + let defined0 = true; + let lastCurve: ICurve = null; for (let i = 0, n = curves.length; i < n; i++) { const curve = curves[i]; const curCurveLength = curve.getLength(direction); @@ -136,28 +153,47 @@ export function drawSegments( } if (drawConnect) { + // step的逻辑 + let p0 = curve.p0; + if (curve.originP1 === curve.originP2) { + lastCurve = curve; + continue; + } + if (lastCurve && lastCurve.originP1 === lastCurve.originP2) { + p0 = lastCurve.p0; + } if (curve.defined) { - // connect段结束,封闭 - if (needMoveTo) { - path.lineTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + // 非法变合法需要lineTo,合法变非法需要moveTo,初始非法需要moveTo + if (!defined0) { + path.lineTo(p0.x + offsetX, p0.y + offsetY, offsetZ); + defined0 = !defined0; } - needMoveTo = false; } else { - // connect段开始 - if (!needMoveTo) { - path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); + // 找到合法的点 + const { originP1, originP2 } = curve; + let validP: IPointLike; + if (originP1 && originP1.defined !== false) { + validP = p0; + } else if (originP1 && originP2.defined !== false) { + validP = curve.p3 ?? curve.p1; + } + // 合法/(初始)变非法,moveTo + if (defined0) { + defined0 = !defined0; + const x = validP ? validP.x : curve.p0.x; + const y = validP ? validP.y : curve.p0.y; + path.moveTo(x + offsetX, y + offsetY, offsetZ); } else { - // 如果是zero,那么每一段都要绘制一下(第一段不需要绘制) - if (mode === 'zero') { - path.lineTo( - (isFinite(zeroX) ? zeroX : curve.p0.x) + offsetX, - (isFinite(zeroY) ? zeroY : curve.p0.y) + offsetY, - offsetZ - ); + // 非法变非法/合法,看情况要不要lineTo + if (validP) { + // 非法变合法,需要lineTo + defined0 = !defined0; + path.lineTo(validP.x + offsetX, validP.y + offsetY, offsetZ); } } - needMoveTo = true; } + + lastCurve = curve; } else { // 跳过这个点 if (!curve.defined) { diff --git a/packages/vrender/src/common/seg-context.ts b/packages/vrender/src/common/seg-context.ts index 8eb530292..e26868c38 100644 --- a/packages/vrender/src/common/seg-context.ts +++ b/packages/vrender/src/common/seg-context.ts @@ -1,4 +1,5 @@ -import { abs, IPoint, Point } from '@visactor/vutils'; +import type { IPoint, IPointLike } from '@visactor/vutils'; +import { abs, Point } from '@visactor/vutils'; import type { ICubicBezierCurve, ICurve, ICurveType, IDirection, ILineCurve, ISegPath2D } from '../interface'; import { Direction } from './enums'; import { CubicBezierCurve } from './segment/curve/cubic-bezier'; @@ -29,6 +30,8 @@ export class SegContext implements ISegPath2D { private _lastY: number; private _startX: number; private _startY: number; + private _lastOriginP?: IPointLike; + private _startOriginP?: IPointLike; get endX(): number { return this._lastX; @@ -53,38 +56,53 @@ export class SegContext implements ISegPath2D { this.curves = []; } // @ts-ignore - bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number, defined: boolean): void { + bezierCurveTo( + cp1x: number, + cp1y: number, + cp2x: number, + cp2y: number, + x: number, + y: number, + defined: boolean, + p: IPointLike + ): void { const curve: ICubicBezierCurve = new CubicBezierCurve( new Point(this._lastX, this._lastY), new Point(cp1x, cp1y), new Point(cp2x, cp2y), new Point(x, y) ); + curve.originP1 = this._lastOriginP; + curve.originP2 = p; curve.defined = defined; this.curves.push(curve); this._lastX = x; this._lastY = y; + this._lastOriginP = p; } closePath(): void { if (this.curves.length < 2) { return; } const lastCurve = this.curves[this.curves.length - 1]; - this.lineTo(this._startX, this._startY, lastCurve.defined); + this.lineTo(this._startX, this._startY, lastCurve.defined, this._startOriginP); } // @ts-ignore ellipse(): void { throw new Error('SegContext不支持调用ellipse'); } - lineTo(x: number, y: number, defined: boolean): void { - const curve = this.addLinearCurve(x, y, defined); + lineTo(x: number, y: number, defined: boolean, p: IPointLike): void { + const curve = this.addLinearCurve(x, y, defined, this._lastOriginP, p); this.curves.push(curve); this._lastX = x; this._lastY = y; + this._lastOriginP = p; } - moveTo(x: number, y: number): ISegPath2D { + moveTo(x: number, y: number, p: IPointLike): ISegPath2D { this._lastX = this._startX = x; this._lastY = this._startY = y; + this._lastOriginP = p; + this._startOriginP = p; return this; } // @ts-ignore @@ -102,8 +120,10 @@ export class SegContext implements ISegPath2D { } // linear - protected addLinearCurve(x: number, y: number, defined: boolean): ILineCurve { + protected addLinearCurve(x: number, y: number, defined: boolean, p1: IPointLike, p2: IPointLike): ILineCurve { const curve = new LineCurve(new Point(this._lastX, this._lastY), new Point(x, y)); + curve.originP1 = p1; + curve.originP2 = p2; curve.defined = defined; return curve; } @@ -121,14 +141,16 @@ export class SegContext implements ISegPath2D { } const sc = this.curves[0]; const ec = this.curves[this.curves.length - 1]; - return abs(sc.p0.y - ec.p1.y); + const endP = ec.p3 ?? ec.p1; + return abs(sc.p0.y - endP.y); } else if (direction === Direction.ROW) { if (!this.curves.length) { return 0; } const sc = this.curves[0]; const ec = this.curves[this.curves.length - 1]; - return abs(sc.p0.x - ec.p1.x); + const endP = ec.p3 ?? ec.p1; + return abs(sc.p0.x - endP.x); } if (Number.isFinite(this.length)) { return this.length; @@ -143,14 +165,23 @@ export class SegContext implements ISegPath2D { */ export class ReflectSegContext extends SegContext { // @ts-ignore - bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number, defined: boolean): void { - return super.bezierCurveTo(cp1y, cp1x, cp2y, cp2x, y, x, defined); - } - lineTo(x: number, y: number, defined: boolean): void { - return super.lineTo(y, x, defined); - } - moveTo(x: number, y: number): ISegPath2D { - return super.moveTo(y, x); + bezierCurveTo( + cp1x: number, + cp1y: number, + cp2x: number, + cp2y: number, + x: number, + y: number, + defined: boolean, + p: IPointLike + ): void { + return super.bezierCurveTo(cp1y, cp1x, cp2y, cp2x, y, x, defined, p); + } + lineTo(x: number, y: number, defined: boolean, p: IPointLike): void { + return super.lineTo(y, x, defined, p); + } + moveTo(x: number, y: number, p: IPointLike): ISegPath2D { + return super.moveTo(y, x, p); } clear() { return super.clear(); diff --git a/packages/vrender/src/common/segment/basis.ts b/packages/vrender/src/common/segment/basis.ts index 06f675cb5..305ce6c5a 100644 --- a/packages/vrender/src/common/segment/basis.ts +++ b/packages/vrender/src/common/segment/basis.ts @@ -1,9 +1,10 @@ -import { abs, IPointLike } from '@visactor/vutils'; +import type { IPointLike } from '@visactor/vutils'; +import { abs } from '@visactor/vutils'; import { genLinearSegments } from './linear'; import { genCurveSegments } from './common'; import { SegContext } from '../seg-context'; import { Direction } from '../enums'; -import { ICurvedSegment, IGenSegmentParams, ILinearSegment, ISegPath2D } from '../../interface/curve'; +import type { ICurvedSegment, IGenSegmentParams, ILinearSegment, ISegPath2D } from '../../interface/curve'; /** * 部分源码参考 https://github.com/d3/d3-shape/ @@ -24,7 +25,7 @@ import { ICurvedSegment, IGenSegmentParams, ILinearSegment, ISegPath2D } from '. // 基于d3-shape重构 // https://github.com/d3/d3-shape/blob/main/src/curve/basis.js -export function point(curveClass: Basis, x: number, y: number, defined: boolean) { +export function point(curveClass: Basis, x: number, y: number, defined: boolean, p: IPointLike) { curveClass.context.bezierCurveTo( (2 * curveClass._x0 + curveClass._x1) / 3, (2 * curveClass._y0 + curveClass._y1) / 3, @@ -32,7 +33,8 @@ export function point(curveClass: Basis, x: number, y: number, defined: boolean) (curveClass._y0 + 2 * curveClass._y1) / 3, (curveClass._x0 + 4 * curveClass._x1 + x) / 6, (curveClass._y0 + 4 * curveClass._y1 + y) / 6, - defined + defined, + curveClass.lastPoint1 ); } @@ -42,6 +44,8 @@ export class Basis implements ICurvedSegment { declare context: ISegPath2D; protected startPoint?: IPointLike; + lastPoint0?: IPointLike; + lastPoint1?: IPointLike; constructor(context: ISegPath2D, startPoint?: IPointLike) { this.context = context; @@ -74,7 +78,8 @@ export class Basis implements ICurvedSegment { this, this._x1 * 6 - (this._x0 + 4 * this._x1), this._y1 * 6 - (this._y0 + 4 * this._y1), - this._lastDefined1 !== false && this._lastDefined2 !== false + this._lastDefined1 !== false && this._lastDefined2 !== false, + this.lastPoint1 ); // falls through // case 2: this.context.lineTo(this._x1, this._y1); break; } @@ -90,21 +95,23 @@ export class Basis implements ICurvedSegment { case 0: this._point = 1; this._line - ? this.context.lineTo(x, y, this._lastDefined1 !== false && this._lastDefined2 !== false) - : this.context.moveTo(x, y); + ? this.context.lineTo(x, y, this._lastDefined1 !== false && this._lastDefined2 !== false, p) + : this.context.moveTo(x, y, p); break; case 1: this._point = 2; break; // case 2: this._point = 3; this.context.lineTo((5 * this._x0 + this._x1) / 6, (5 * this._y0 + this._y1) / 6, i, defined1, defined2); // falls through default: - point(this, x, y, this._lastDefined1 !== false && this._lastDefined2 !== false); + point(this, x, y, this._lastDefined1 !== false && this._lastDefined2 !== false, p); break; } (this._x0 = this._x1), (this._x1 = x); (this._y0 = this._y1), (this._y1 = y); this._lastDefined1 = this._lastDefined2; this._lastDefined2 = p.defined; + this.lastPoint0 = this.lastPoint1; + this.lastPoint1 = p; } tryUpdateLength(): number { diff --git a/packages/vrender/src/common/segment/curve/cubic-bezier.ts b/packages/vrender/src/common/segment/curve/cubic-bezier.ts index 6dc4a8c7e..f65298645 100644 --- a/packages/vrender/src/common/segment/curve/cubic-bezier.ts +++ b/packages/vrender/src/common/segment/curve/cubic-bezier.ts @@ -37,6 +37,8 @@ export function divideCubic(curve: ICubicBezierCurve, t: number): ICubicBezierCu export class CubicBezierCurve extends Curve implements ICubicBezierCurve { type: number = CurveTypeEnum.CubicBezierCurve; + declare originP1?: IPointLike; + declare originP2?: IPointLike; declare readonly p0: IPoint; declare readonly p1: IPoint; declare readonly p2: IPoint; diff --git a/packages/vrender/src/common/segment/curve/line.ts b/packages/vrender/src/common/segment/curve/line.ts index 7e6bcbaf4..f90464d53 100644 --- a/packages/vrender/src/common/segment/curve/line.ts +++ b/packages/vrender/src/common/segment/curve/line.ts @@ -19,6 +19,8 @@ export function divideLinear(curve: ILineCurve, t: number): ILineCurve[] { export class LineCurve extends Curve implements ILineCurve { type: number = CurveTypeEnum.LineCurve; + declare originP1?: IPointLike; + declare originP2?: IPointLike; declare p0: IPoint; declare p1: IPoint; declare angle: number; diff --git a/packages/vrender/src/common/segment/curve/quadratic-bezier.ts b/packages/vrender/src/common/segment/curve/quadratic-bezier.ts index 6526e8828..d121934dc 100644 --- a/packages/vrender/src/common/segment/curve/quadratic-bezier.ts +++ b/packages/vrender/src/common/segment/curve/quadratic-bezier.ts @@ -5,6 +5,9 @@ import type { IPoint, IPointLike } from '@visactor/vutils'; export class QuadraticBezierCurve extends Curve implements IQuadraticBezierCurve { type: number = CurveTypeEnum.QuadraticBezierCurve; + declare originP1?: IPointLike; + declare originP2?: IPointLike; + declare readonly p0: IPoint; declare readonly p1: IPoint; declare readonly p2: IPoint; diff --git a/packages/vrender/src/common/segment/linear-closed.ts b/packages/vrender/src/common/segment/linear-closed.ts index b68574fac..9de05c7ce 100644 --- a/packages/vrender/src/common/segment/linear-closed.ts +++ b/packages/vrender/src/common/segment/linear-closed.ts @@ -1,4 +1,5 @@ -import { abs, IPointLike } from '@visactor/vutils'; +import type { IPointLike } from '@visactor/vutils'; +import { abs } from '@visactor/vutils'; import { SegContext } from '../seg-context'; import { genCurveSegments } from './common'; import { Direction } from '../enums'; @@ -62,13 +63,13 @@ export class LinearClosed implements ILinearSegment { case 0: this._point = 1; this._line - ? this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false) - : this.context.moveTo(x, y); + ? this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false, p) + : this.context.moveTo(x, y, p); break; case 1: this._point = 2; // falls through default: - this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false); + this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false, p); break; } diff --git a/packages/vrender/src/common/segment/linear.ts b/packages/vrender/src/common/segment/linear.ts index edf78c984..556c6e627 100644 --- a/packages/vrender/src/common/segment/linear.ts +++ b/packages/vrender/src/common/segment/linear.ts @@ -1,8 +1,9 @@ -import { abs, IPointLike } from '@visactor/vutils'; +import type { IPointLike } from '@visactor/vutils'; +import { abs } from '@visactor/vutils'; import { SegContext } from '../seg-context'; import { genCurveSegments } from './common'; import { Direction } from '../enums'; -import { IGenSegmentParams, ILinearSegment, ISegPath2D } from '../../interface/curve'; +import type { IGenSegmentParams, ILinearSegment, ISegPath2D } from '../../interface/curve'; /** * 部分源码参考 https://github.com/d3/d3-shape/ @@ -65,13 +66,13 @@ export class Linear implements ILinearSegment { case 0: this._point = 1; this._line - ? this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false) - : this.context.moveTo(x, y); + ? this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false, p) + : this.context.moveTo(x, y, p); break; case 1: this._point = 2; // falls through default: - this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false); + this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false, p); break; } diff --git a/packages/vrender/src/common/segment/monotone.ts b/packages/vrender/src/common/segment/monotone.ts index a40279e5c..299a3bdd7 100644 --- a/packages/vrender/src/common/segment/monotone.ts +++ b/packages/vrender/src/common/segment/monotone.ts @@ -1,9 +1,10 @@ -import { IPointLike, abs } from '@visactor/vutils'; +import type { IPointLike } from '@visactor/vutils'; +import { abs } from '@visactor/vutils'; import { genLinearSegments } from './linear'; import { genCurveSegments } from './common'; import { ReflectSegContext, SegContext } from '../seg-context'; import { Direction } from '../enums'; -import { ICurvedSegment, IGenSegmentParams, ISegPath2D } from '../../interface/curve'; +import type { ICurvedSegment, IGenSegmentParams, ISegPath2D } from '../../interface/curve'; /** * 部分源码参考 https://github.com/d3/d3-shape/ @@ -50,13 +51,22 @@ function slope2(curveClass: MonotoneX | MonotoneY, t: number) { // According to https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Representations // "you can express cubic Hermite interpolation in terms of cubic Bézier curves // with respect to the four values p0, p0 + m0 / 3, p1 - m1 / 3, p1". -function point(curveClass: MonotoneX | MonotoneY, t0: number, t1: number, defined: boolean) { +function point(curveClass: MonotoneX | MonotoneY, t0: number, t1: number, defined: boolean, p: IPointLike) { const x0 = curveClass._x0; const y0 = curveClass._y0; const x1 = curveClass._x1; const y1 = curveClass._y1; const dx = (x1 - x0) / 3; - curveClass.context.bezierCurveTo(x0 + dx, y0 + dx * t0, x1 - dx, y1 - dx * t1, x1, y1, defined); + curveClass.context.bezierCurveTo( + x0 + dx, + y0 + dx * t0, + x1 - dx, + y1 - dx * t1, + x1, + y1, + defined, + curveClass.lastPoint1 + ); } export class MonotoneX implements ICurvedSegment { @@ -66,6 +76,8 @@ export class MonotoneX implements ICurvedSegment { declare _t0: number; protected startPoint?: IPointLike; + lastPoint0?: IPointLike; + lastPoint1?: IPointLike; constructor(context: ISegPath2D, startPoint?: IPointLike) { this.context = context; @@ -94,10 +106,10 @@ export class MonotoneX implements ICurvedSegment { lineEnd() { switch (this._point) { case 2: - this.context.lineTo(this._x1, this._y1, this._lastDefined2 !== false); + this.context.lineTo(this._x1, this._y1, this._lastDefined2 !== false, this.lastPoint1); break; case 3: - point(this, this._t0, slope2(this, this._t0), this._lastDefined2 !== false); + point(this, this._t0, slope2(this, this._t0), this._lastDefined2 !== false, this.lastPoint1); break; } if (this._line || (this._line !== 0 && this._point === 1)) { @@ -107,7 +119,6 @@ export class MonotoneX implements ICurvedSegment { } point(p: IPointLike): void { let t1 = NaN; - const x = p.x; const y = p.y; if (x === this._x1 && y === this._y1) { @@ -117,8 +128,8 @@ export class MonotoneX implements ICurvedSegment { case 0: this._point = 1; this._line - ? this.context.lineTo(x, y, this._lastDefined1 !== false && this._lastDefined2 !== false) - : this.context.moveTo(x, y); + ? this.context.lineTo(x, y, this._lastDefined1 !== false && this._lastDefined2 !== false, p) + : this.context.moveTo(x, y, p); break; case 1: this._point = 2; @@ -129,11 +140,18 @@ export class MonotoneX implements ICurvedSegment { this, slope2(this, (t1 = slope3(this, x, y))), t1, - this._lastDefined1 !== false && this._lastDefined2 !== false + this._lastDefined1 !== false && this._lastDefined2 !== false, + p ); break; default: - point(this, this._t0, (t1 = slope3(this, x, y)), this._lastDefined1 !== false && this._lastDefined2 !== false); + point( + this, + this._t0, + (t1 = slope3(this, x, y)), + this._lastDefined1 !== false && this._lastDefined2 !== false, + p + ); break; } @@ -142,6 +160,8 @@ export class MonotoneX implements ICurvedSegment { this._t0 = t1; this._lastDefined1 = this._lastDefined2; this._lastDefined2 = p.defined !== false; + this.lastPoint0 = this.lastPoint1; + this.lastPoint1 = p; } tryUpdateLength(): number { diff --git a/packages/vrender/src/common/segment/step.ts b/packages/vrender/src/common/segment/step.ts index 7286c3c6c..3fd0db4eb 100644 --- a/packages/vrender/src/common/segment/step.ts +++ b/packages/vrender/src/common/segment/step.ts @@ -1,4 +1,5 @@ -import { IPointLike, abs } from '@visactor/vutils'; +import type { IPointLike } from '@visactor/vutils'; +import { abs } from '@visactor/vutils'; import { SegContext } from '../seg-context'; import { genCurveSegments } from './common'; import { Direction } from '../enums'; @@ -29,6 +30,7 @@ export class Step implements ICurvedSegment { private _lastDefined?: boolean; protected startPoint?: IPointLike; + protected lastPoint?: IPointLike; constructor(context: ISegPath2D, t: number = 0.5, startPoint?: IPointLike) { this.context = context; @@ -57,7 +59,7 @@ export class Step implements ICurvedSegment { } lineEnd() { if (0 < this._t && this._t < 1 && this._point === 2) { - this.context.lineTo(this._x, this._y, this._lastDefined !== false); + this.context.lineTo(this._x, this._y, this._lastDefined !== false, this.lastPoint); } if (this._line || (this._line !== 0 && this._point === 1)) { this.context.closePath(); @@ -74,25 +76,26 @@ export class Step implements ICurvedSegment { case 0: this._point = 1; this._line - ? this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false) - : this.context.moveTo(x, y); + ? this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false, p) + : this.context.moveTo(x, y, p); break; case 1: this._point = 2; // falls through default: { if (this._t <= 0) { - this.context.lineTo(this._x, y, this._lastDefined !== false && p.defined !== false); - this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false); + this.context.lineTo(this._x, y, this._lastDefined !== false && p.defined !== false, this.lastPoint); + 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.context.lineTo(x1, y, this._lastDefined !== false && p.defined !== false); + 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; } } this._lastDefined = p.defined; (this._x = x), (this._y = y); + this.lastPoint = p; } tryUpdateLength(): number { diff --git a/packages/vrender/src/interface/curve.ts b/packages/vrender/src/interface/curve.ts index 0d311ad91..6504318bf 100644 --- a/packages/vrender/src/interface/curve.ts +++ b/packages/vrender/src/interface/curve.ts @@ -20,7 +20,8 @@ export interface ISegPath2D extends ICurvePath { cp2y: number, x: number, y: number, - defined: boolean + defined: boolean, + p?: IPointLike // 保存原始点 ) => void; closePath: () => void; ellipse: ( @@ -33,8 +34,8 @@ export interface ISegPath2D extends ICurvePath { endAngle: number, counterclockwise?: boolean ) => void; - lineTo: (x: number, y: number, defined: boolean) => void; - moveTo: (x: number, y: number) => void; + lineTo: (x: number, y: number, defined: boolean, p?: IPointLike) => void; + moveTo: (x: number, y: number, p?: IPointLike) => void; quadraticCurveTo: (cpx: number, cpy: number, x: number, y: number) => void; } diff --git a/packages/vrender/src/interface/path.ts b/packages/vrender/src/interface/path.ts index c6688d211..f6a0a6c51 100644 --- a/packages/vrender/src/interface/path.ts +++ b/packages/vrender/src/interface/path.ts @@ -41,6 +41,8 @@ export type CommandStrType = [ export interface ICurve { type: number; defined: boolean; + originP1?: IPointLike; + originP2?: IPointLike; readonly p0: T; readonly p1?: T; readonly p2?: T; diff --git a/packages/vrender/src/render/contributions/render/area-render.ts b/packages/vrender/src/render/contributions/render/area-render.ts index 173d8238a..f6242503b 100644 --- a/packages/vrender/src/render/contributions/render/area-render.ts +++ b/packages/vrender/src/render/contributions/render/area-render.ts @@ -1,5 +1,5 @@ import type { IPointLike } from '@visactor/vutils'; -import { isArray, min } from '@visactor/vutils'; +import { abs, isArray, min } from '@visactor/vutils'; import { inject, injectable, named } from 'inversify'; import type { IArea, @@ -33,7 +33,7 @@ import { import { getTheme } from '../../../graphic/theme'; import { drawPathProxy, fillVisible, runFill, runStroke, strokeVisible } from './utils'; import { AreaRenderContribution } from './contributions/constants'; -import { BaseRenderContributionTime } from '../../../common/enums'; +import { BaseRenderContributionTime, Direction } from '../../../common/enums'; import { drawAreaSegments } from '../../../common/render-area'; import { AREA_NUMBER_TYPE } from '../../../graphic/constants'; import { drawSegments } from '../../../common/render-curve'; @@ -169,7 +169,10 @@ export class DefaultCanvasAreaRender implements IGraphicRender { y: endPoint.y1 ?? endPoint.y }); } - lastBottomSeg = calcLineCache(bottomPoints, curveType); + lastBottomSeg = calcLineCache( + bottomPoints, + curveType === 'stepBefore' ? 'stepAfter' : curveType === 'stepAfter' ? 'stepBefore' : curveType + ); bottomCaches.unshift(lastBottomSeg); } area.cacheArea = bottomCaches.map((item, index) => ({ @@ -473,10 +476,27 @@ export class DefaultCanvasAreaRender implements IGraphicRender { context.beginPath(); const ret: boolean = false; + const { points, segments } = area.attribute; + let direction = Direction.ROW; + let endP: IPointLike; + let startP: IPointLike; + if (segments) { + const endSeg = segments[segments.length - 1]; + const startSeg = segments[0]; + startP = startSeg.points[0]; + endP = endSeg.points[endSeg.points.length - 1]; + } else { + startP = points[0]; + endP = points[points.length - 1]; + } + const xTotalLength = abs(endP.x - startP.x); + const yTotalLength = abs(endP.y - startP.y); + direction = xTotalLength > yTotalLength ? Direction.ROW : Direction.COLUMN; drawAreaSegments(context.camera ? context : context.nativeContext, cache, clipRange, { offsetX, offsetY, offsetZ, + direction, drawConnect: connect, mode: connectedType, zeroX: connectedX, @@ -561,7 +581,7 @@ export class DefaultCanvasAreaRender implements IGraphicRender { context.camera ? context : context.nativeContext, stroke[0] ? cache.top : cache.bottom, clipRange, - 'auto', + direction === Direction.ROW ? 'x' : 'y', { offsetX, offsetY, From 091c13410377ec36a02b62020e11044361851672 Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Tue, 8 Aug 2023 23:24:53 +0800 Subject: [PATCH 25/26] fix: line axis states should set default value --- .../vrender-components/__tests__/browser/main.ts | 4 ++++ packages/vrender-components/src/axis/base.ts | 6 +++--- packages/vrender-components/src/axis/circle.ts | 12 +++++------- packages/vrender-components/src/axis/line.ts | 16 ++++++---------- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/vrender-components/__tests__/browser/main.ts b/packages/vrender-components/__tests__/browser/main.ts index 3e160e9e7..685fa164d 100644 --- a/packages/vrender-components/__tests__/browser/main.ts +++ b/packages/vrender-components/__tests__/browser/main.ts @@ -2,6 +2,10 @@ import './style.css'; const LOCAL_STORAGE_KEY = 'VRENDER_COMPONENTS_DEMOS'; const specs = [ + { + path: 'axis-interaction', + name: '轴交互' + }, { path: 'cartesian-axis-label-align', name: '轴标签整体对齐' diff --git a/packages/vrender-components/src/axis/base.ts b/packages/vrender-components/src/axis/base.ts index 86c1445f3..2d54e6fd1 100644 --- a/packages/vrender-components/src/axis/base.ts +++ b/packages/vrender-components/src/axis/base.ts @@ -259,7 +259,7 @@ export abstract class AxisBase extends AbstractCom line.id = this._getNodeId(item.id); if (isEmpty(this.attribute.tick?.state)) { - line.states = null; + line.states = DEFAULT_STATES; } else { const data = this.data[index]; const tickLineState = merge({}, DEFAULT_STATES, this.attribute.tick.state); @@ -288,7 +288,7 @@ export abstract class AxisBase extends AbstractCom line.id = this._getNodeId(`${index}`); if (isEmpty(subTick.state)) { - line.states = null; + line.states = DEFAULT_STATES; } else { const subTickLineState = merge({}, DEFAULT_STATES, subTick.state); Object.keys(subTickLineState).forEach(key => { @@ -328,7 +328,7 @@ export abstract class AxisBase extends AbstractCom text.name = AXIS_ELEMENT_NAME.label; text.id = this._getNodeId(`layer${layer}-label-${item.id}`); if (isEmpty(this.attribute.label?.state)) { - text.states = null; + text.states = DEFAULT_STATES; } else { const labelState = merge({}, DEFAULT_STATES, this.attribute.label.state); Object.keys(labelState).forEach(key => { diff --git a/packages/vrender-components/src/axis/circle.ts b/packages/vrender-components/src/axis/circle.ts index f2a2aa023..8b0c86529 100644 --- a/packages/vrender-components/src/axis/circle.ts +++ b/packages/vrender-components/src/axis/circle.ts @@ -150,13 +150,11 @@ export class CircleAxis extends AxisBase { textAlign: 'center', ...textStyle }, - state: isEmpty(state) - ? null - : { - text: state.text, - shape: state.shape, - panel: state.background - } + state: { + text: merge({}, DEFAULT_STATES, state?.text), + shape: merge({}, DEFAULT_STATES, state?.shape), + panel: merge({}, DEFAULT_STATES, state?.background) + } }; const { angle } = restAttrs; // 用户设置的是角度 diff --git a/packages/vrender-components/src/axis/line.ts b/packages/vrender-components/src/axis/line.ts index 5fca7c369..0f35e27f4 100644 --- a/packages/vrender-components/src/axis/line.ts +++ b/packages/vrender-components/src/axis/line.ts @@ -83,9 +83,7 @@ export class LineAxis extends AxisBase { bgRect.name = AXIS_ELEMENT_NAME.background; bgRect.id = this._getNodeId('background'); - if (!isEmpty(panel.state)) { - bgRect.states = merge({}, DEFAULT_STATES, panel.state); - } + bgRect.states = merge({}, DEFAULT_STATES, panel.state ?? {}); axisContainer.insertBefore(bgRect, axisContainer.firstChild); } } @@ -276,13 +274,11 @@ export class LineAxis extends AxisBase { textBaseline, ...textStyle }, - state: isEmpty(state) - ? null - : { - text: state.text, - shape: state.shape, - panel: state.background - } + state: { + text: merge({}, DEFAULT_STATES, state?.text), + shape: merge({}, DEFAULT_STATES, state?.shape), + panel: merge({}, DEFAULT_STATES, state?.background) + } }; attrs.angle = angle; From 3b6c65720b5f8df2472b9dcbb17dda788188974c Mon Sep 17 00:00:00 2001 From: kkxxkk2019 Date: Tue, 8 Aug 2023 23:25:34 +0800 Subject: [PATCH 26/26] chore: update rush change --- ...rAlign-for-line-axis_2023-08-08-15-25.json | 10 ++ .../browser/examples/axis-interaction.ts | 161 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-08-15-25.json create mode 100644 packages/vrender-components/__tests__/browser/examples/axis-interaction.ts diff --git a/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-08-15-25.json b/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-08-15-25.json new file mode 100644 index 000000000..ba08c07e4 --- /dev/null +++ b/common/changes/@visactor/vrender-components/feat-containerAlign-for-line-axis_2023-08-08-15-25.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "fix: line axis states should set default value", + "type": "patch" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/packages/vrender-components/__tests__/browser/examples/axis-interaction.ts b/packages/vrender-components/__tests__/browser/examples/axis-interaction.ts new file mode 100644 index 000000000..e8ebe33c9 --- /dev/null +++ b/packages/vrender-components/__tests__/browser/examples/axis-interaction.ts @@ -0,0 +1,161 @@ +import { LinearScale, PointScale } from '@visactor/vscale'; +import { GroupFadeIn, GroupFadeOut } from '@visactor/vrender'; +import { LineAxis, GroupTransition } from '../../../src'; +import render from '../../util/render'; + +const axis = new LineAxis({ + title: { + space: 20, + padding: 0, + textStyle: { + fontSize: 12, + fill: '#363839', + fontWeight: 'normal', + fillOpacity: 1, + textAlign: 'center', + textBaseline: 'bottom' + }, + visible: false, + autoRotate: false, + angle: -1.5707963267948966, + shape: {}, + background: {}, + state: { + text: null, + shape: null, + background: null + }, + text: '细分', + maxWidth: null + }, + label: { + visible: true, + inside: false, + space: 20, + padding: 0, + style: { + fontSize: 12, + fill: '#6F6F6F', + fontWeight: 'normal', + fillOpacity: 1, + angle: 0, + textAlign: 'center' + }, + formatMethod: null, + state: null, + autoRotate: false, + autoHide: false, + autoLimit: false, + containerAlign: 'center' + }, + tick: { + visible: false, + inside: false, + alignWithLabel: true, + length: 4, + style: { + lineWidth: 1, + stroke: '#D9DDE4', + strokeOpacity: 1 + }, + state: null + }, + subTick: { + visible: false, + inside: false, + count: 4, + length: 2, + style: { + lineWidth: 1, + stroke: '#D9DDE4', + strokeOpacity: 1 + }, + state: null + }, + line: { + visible: true, + style: { + lineWidth: 1, + stroke: '#989999', + strokeOpacity: 1 + }, + startSymbol: {}, + endSymbol: {} + }, + grid: { + style: { + lineWidth: 1, + stroke: '#DADCDD', + strokeOpacity: 1, + lineDash: [4, 2] + }, + visible: false, + length: 461, + type: 'line', + depth: 0 + }, + subGrid: { + visible: false, + style: { + lineWidth: 1, + stroke: '#EBEDF2', + strokeOpacity: 1, + lineDash: [4, 4] + }, + type: 'line' + }, + x: 397, + y: 12, + start: { + x: 0, + y: 0 + }, + end: { + x: 0, + y: 327 + }, + items: [ + [ + { + id: '消费者', + label: '消费者', + value: 0.8333333333333334, + rawValue: '消费者' + }, + { + id: '公司', + label: '公司', + value: 0.4999999999999999, + rawValue: '公司' + }, + { + id: '小型企业', + label: '小型企业', + value: 0.1666666666666666, + rawValue: '小型企业' + } + ] + ], + visible: true, + pickable: true, + orient: 'left', + hover: true, + panel: { + visible: true, + state: { + hover: { + fillOpacity: 0.08, + fill: '#141414' + }, + hover_reverse: { + fillOpacity: 0.08, + fill: '#141414' + } + } + }, + verticalFactor: 1, + verticalLimitSize: 150, + verticalMinSize: 150 +}); + +render([axis], 'main');