From f417744e389888b41fc0177f62b46d911b8b66ed Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Mon, 10 Feb 2025 21:33:48 +0800 Subject: [PATCH] fix: fix issue with segment connect area --- .../fix-segment-connect_2025-02-10-13-33.json | 10 ++ .../vrender-core/src/common/seg-context.ts | 49 +++++- .../src/common/segment/curve/arc.ts | 4 + .../src/common/segment/curve/base.ts | 1 + .../src/common/segment/curve/cubic-bezier.ts | 13 +- .../src/common/segment/curve/ellipse.ts | 4 + .../src/common/segment/curve/line.ts | 5 + .../src/common/segment/curve/move.ts | 4 + .../common/segment/curve/quadratic-bezier.ts | 11 +- .../vrender-core/src/common/segment/index.ts | 12 +- packages/vrender-core/src/interface/path.ts | 1 + .../contributions/render/area-render.ts | 144 ++++++++++++------ .../contributions/render/line-render.ts | 80 +++++----- 13 files changed, 242 insertions(+), 96 deletions(-) create mode 100644 common/changes/@visactor/vrender-core/fix-segment-connect_2025-02-10-13-33.json diff --git a/common/changes/@visactor/vrender-core/fix-segment-connect_2025-02-10-13-33.json b/common/changes/@visactor/vrender-core/fix-segment-connect_2025-02-10-13-33.json new file mode 100644 index 000000000..1582c88a4 --- /dev/null +++ b/common/changes/@visactor/vrender-core/fix-segment-connect_2025-02-10-13-33.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-core", + "comment": "fix: fix issue with segment connect area", + "type": "none" + } + ], + "packageName": "@visactor/vrender-core" +} \ No newline at end of file diff --git a/packages/vrender-core/src/common/seg-context.ts b/packages/vrender-core/src/common/seg-context.ts index ff4bef8f7..c6dbf51e7 100644 --- a/packages/vrender-core/src/common/seg-context.ts +++ b/packages/vrender-core/src/common/seg-context.ts @@ -1,6 +1,6 @@ import type { IPoint, IPointLike } from '@visactor/vutils'; import { abs, Point } from '@visactor/vutils'; -import type { ICubicBezierCurve, ICurve, ICurveType, IDirection, ILineCurve, ISegPath2D } from '../interface'; +import type { ICubicBezierCurve, ICurve, ICurveType, IDirection, ILineCurve, ISegment, ISegPath2D } from '../interface'; import { Direction } from './enums'; import { CubicBezierCurve } from './segment/curve/cubic-bezier'; import { LineCurve } from './segment/curve/line'; @@ -158,6 +158,53 @@ export class SegContext implements ISegPath2D { this.length = this.curves.reduce((l, c) => l + c.getLength(), 0); return this.length; } + + reverse() { + this.curves.reverse(); + [this._startX, this._lastX] = [this._lastX, this._startX]; + [this._startY, this._lastY] = [this._lastY, this._startY]; + [this._startOriginP, this._lastOriginP] = [this._lastOriginP, this._startOriginP]; + this.curves.forEach(c => { + c.reverse(); + }); + } + + splitBySegments(segments: ISegment[]) { + if (segments.length === 1) { + return [this]; + } + const res: SegContext[] = []; + let curveIdx = 0; + segments.forEach(seg => { + const lastP = seg.points[seg.points.length - 1]; + const ctx = new SegContext(this.curveType, this.direction); + res.push(ctx); + for (; curveIdx < this.curves.length; curveIdx++) { + let curve = this.curves[curveIdx]; + if (curve.originP2 === lastP) { + ctx.curves.push(curve); + curveIdx++; + for (; curveIdx < this.curves.length; curveIdx++) { + curve = this.curves[curveIdx]; + if (curve.originP2 === lastP) { + ctx.curves.push(curve); + } else { + break; + } + } + ctx._lastX = lastP.x; + ctx._lastY = lastP.y; + ctx._lastOriginP = lastP; + ctx._startX = ctx.curves[0].p0.x; + ctx._startY = ctx.curves[0].p0.y; + ctx._startOriginP = ctx.curves[0].p0; + break; + } + ctx.curves.push(curve); + } + }); + return res; + } } /** diff --git a/packages/vrender-core/src/common/segment/curve/arc.ts b/packages/vrender-core/src/common/segment/curve/arc.ts index 96bf04d6a..3079971fe 100644 --- a/packages/vrender-core/src/common/segment/curve/arc.ts +++ b/packages/vrender-core/src/common/segment/curve/arc.ts @@ -36,4 +36,8 @@ export class ArcCurve extends Curve implements IArcCurve { includeX(x: number): boolean { throw new Error('ArcCurve暂不支持includeX'); } + + reverse() { + throw new Error('ArcCurve暂不支持reverse'); + } } diff --git a/packages/vrender-core/src/common/segment/curve/base.ts b/packages/vrender-core/src/common/segment/curve/base.ts index be0175eb4..70b6cda52 100644 --- a/packages/vrender-core/src/common/segment/curve/base.ts +++ b/packages/vrender-core/src/common/segment/curve/base.ts @@ -24,4 +24,5 @@ export abstract class Curve implements ICurve { protected abstract calcLength(): number; protected abstract calcProjLength(direction: IDirection): number; abstract draw(path: IPath2D, x: number, y: number, sx: number, sy: number, percent: number): void; + abstract reverse(): void; } diff --git a/packages/vrender-core/src/common/segment/curve/cubic-bezier.ts b/packages/vrender-core/src/common/segment/curve/cubic-bezier.ts index f3494aed6..762618257 100644 --- a/packages/vrender-core/src/common/segment/curve/cubic-bezier.ts +++ b/packages/vrender-core/src/common/segment/curve/cubic-bezier.ts @@ -69,10 +69,10 @@ 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; - declare readonly p3: IPoint; + declare p0: IPoint; + declare p1: IPoint; + declare p2: IPoint; + declare p3: IPoint; constructor(p0: IPoint, p1: IPoint, p2: IPoint, p3: IPoint) { super(); this.p0 = p0; @@ -153,4 +153,9 @@ export class CubicBezierCurve extends Curve implements ICubicBezierCurve { const t = (x - minX) / (maxX - minX); return this.getPointAt(t).y; } + + reverse() { + [this.p0, this.p1, this.p2, this.p3] = [this.p3, this.p2, this.p1, this.p0]; + [this.originP1, this.originP2] = [this.originP2, this.originP1]; + } } diff --git a/packages/vrender-core/src/common/segment/curve/ellipse.ts b/packages/vrender-core/src/common/segment/curve/ellipse.ts index 359a356a0..e0366942e 100644 --- a/packages/vrender-core/src/common/segment/curve/ellipse.ts +++ b/packages/vrender-core/src/common/segment/curve/ellipse.ts @@ -52,4 +52,8 @@ export class EllipseCurve extends Curve implements IEllipseCurve { includeX(x: number): boolean { throw new Error('QuadraticBezierCurve暂不支持includeX'); } + + reverse() { + throw new Error('暂不支持'); + } } diff --git a/packages/vrender-core/src/common/segment/curve/line.ts b/packages/vrender-core/src/common/segment/curve/line.ts index 286207645..0f2fc87e1 100644 --- a/packages/vrender-core/src/common/segment/curve/line.ts +++ b/packages/vrender-core/src/common/segment/curve/line.ts @@ -90,4 +90,9 @@ export class LineCurve extends Curve implements ILineCurve { } return Infinity; } + + reverse() { + [this.p0, this.p1] = [this.p1, this.p0]; + [this.originP1, this.originP2] = [this.originP2, this.originP1]; + } } diff --git a/packages/vrender-core/src/common/segment/curve/move.ts b/packages/vrender-core/src/common/segment/curve/move.ts index 0d23e12cf..0c5e306c8 100644 --- a/packages/vrender-core/src/common/segment/curve/move.ts +++ b/packages/vrender-core/src/common/segment/curve/move.ts @@ -34,4 +34,8 @@ export class MoveCurve extends Curve implements IMoveCurve { getYAt(x: number): number { return Infinity; } + + reverse() { + [this.p0, this.p1] = [this.p1, this.p0]; + } } diff --git a/packages/vrender-core/src/common/segment/curve/quadratic-bezier.ts b/packages/vrender-core/src/common/segment/curve/quadratic-bezier.ts index 0d7834350..27b5f01c8 100644 --- a/packages/vrender-core/src/common/segment/curve/quadratic-bezier.ts +++ b/packages/vrender-core/src/common/segment/curve/quadratic-bezier.ts @@ -10,9 +10,9 @@ export class QuadraticBezierCurve extends Curve implements IQuadraticBezierCurve declare originP1?: IPointLike; declare originP2?: IPointLike; - declare readonly p0: IPoint; - declare readonly p1: IPoint; - declare readonly p2: IPoint; + declare p0: IPoint; + declare p1: IPoint; + declare p2: IPoint; constructor(p0: IPoint, p1: IPoint, p2: IPoint) { super(); this.p0 = p0; @@ -70,4 +70,9 @@ export class QuadraticBezierCurve extends Curve implements IQuadraticBezierCurve includeX(x: number): boolean { throw new Error('QuadraticBezierCurve暂不支持includeX'); } + + reverse() { + [this.p0, this.p1, this.p2] = [this.p2, this.p1, this.p0]; + [this.originP1, this.originP2] = [this.originP2, this.originP1]; + } } diff --git a/packages/vrender-core/src/common/segment/index.ts b/packages/vrender-core/src/common/segment/index.ts index 5cfdaf783..97ecad4fc 100644 --- a/packages/vrender-core/src/common/segment/index.ts +++ b/packages/vrender-core/src/common/segment/index.ts @@ -1,5 +1,5 @@ import type { IPointLike } from '@visactor/vutils'; -import type { ICurveType, IGenSegmentParams, ISegPath2D } from '../../interface'; +import type { ICurveType, ISegment, ISegPath2D } from '../../interface'; import { genLinearSegments } from './linear'; import { genBasisSegments } from './basis'; import { genMonotoneXSegments, genMonotoneYSegments } from './monotone'; @@ -45,3 +45,13 @@ export function calcLineCache( return genLinearSegments(points, params); } } + +export function calcSegLineCache( + segments: ISegment[], + points: IPointLike[], + curveType: ICurveType, + params?: { startPoint?: IPointLike; curveTension?: number } +): ISegPath2D[] { + const cache = calcLineCache(points, curveType, params); + return (cache as any).splitBySegments(segments); +} diff --git a/packages/vrender-core/src/interface/path.ts b/packages/vrender-core/src/interface/path.ts index 57fa48dbd..6c7ce6fa3 100644 --- a/packages/vrender-core/src/interface/path.ts +++ b/packages/vrender-core/src/interface/path.ts @@ -53,6 +53,7 @@ export interface ICurve { draw: (path: IPath2D, x: number, y: number, sx: number, sy: number, percent: number) => void; getYAt: (x: number) => number; includeX: (x: number) => boolean; + reverse: () => void; } export interface ICubicBezierCurve extends ICurve { diff --git a/packages/vrender-core/src/render/contributions/render/area-render.ts b/packages/vrender-core/src/render/contributions/render/area-render.ts index 8eb5f8921..794bbff5d 100644 --- a/packages/vrender-core/src/render/contributions/render/area-render.ts +++ b/packages/vrender-core/src/render/contributions/render/area-render.ts @@ -19,7 +19,7 @@ import type { } from '../../../interface'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import { ContributionProvider } from '../../../common/contribution-provider'; -import { calcLineCache } from '../../../common/segment'; +import { calcLineCache, calcSegLineCache } from '../../../common/segment'; import { getTheme } from '../../../graphic/theme'; import { AreaRenderContribution } from './contributions/constants'; @@ -240,62 +240,106 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph // 更新cache if (area.shouldUpdateShape()) { if (segments && segments.length) { - let startPoint: IPointLike; - let lastTopSeg: { endX: number; endY: number }; - const topCaches = segments - .map((seg, index) => { - if (seg.points.length <= 1) { - // 第一个点的话,直接设置lastTopSeg - if (index === 0) { - seg.points[0] && (lastTopSeg = { endX: seg.points[0].x, endY: seg.points[0].y }); - return null; - } - } - // 添加上一个segment结束的点作为这个segment的起始点 - if (index === 1) { - startPoint = { x: lastTopSeg.endX, y: lastTopSeg.endY }; - } else if (index > 1) { - startPoint.x = lastTopSeg.endX; - startPoint.y = lastTopSeg.endY; - } - const data = calcLineCache(parsePoint(seg.points, connectedType), curveType, { - startPoint, - curveTension - }); - lastTopSeg = data; - return data; - }) - .filter(item => !!item); - let lastBottomSeg: ISegPath2D; - const bottomCaches = []; - for (let i = segments.length - 1; i >= 0; i--) { - const points = segments[i].points; + // let startPoint: IPointLike; + // let lastTopSeg: { endX: number; endY: number }; + // 检测segment是否有定义curveType + const points: IPointLike[] = []; + segments.forEach(seg => { + parsePoint(seg.points, connectedType).forEach(p => { + points.push(p); + }); + }); + const topCaches = calcSegLineCache(segments, points, curveType, { curveTension }); + // const topCaches = segments + // .map((seg, index) => { + // if (seg.points.length <= 1) { + // // 第一个点的话,直接设置lastTopSeg + // if (index === 0) { + // seg.points[0] && (lastTopSeg = { endX: seg.points[0].x, endY: seg.points[0].y }); + // return null; + // } + // } + // // 添加上一个segment结束的点作为这个segment的起始点 + // if (index === 1) { + // startPoint = { x: lastTopSeg.endX, y: lastTopSeg.endY }; + // } else if (index > 1) { + // startPoint.x = lastTopSeg.endX; + // startPoint.y = lastTopSeg.endY; + // } + // const data = calcLineCache(parsePoint(seg.points, connectedType), curveType, { + // startPoint, + // curveTension + // }); + // lastTopSeg = data; + // return data; + // }) + // .filter(item => !!item); + const bottomSegments = []; + for (let i = 0; i < segments.length; i++) { + const points = parsePoint(segments[i].points, connectedType); const bottomPoints: IPointLike[] = []; - for (let i = points.length - 1; i >= 0; i--) { + for (let i = 0; i < points.length; i++) { bottomPoints.push({ x: points[i].x1 ?? points[i].x, y: points[i].y1 ?? points[i].y }); } - // 处理一下bottom的segments,bottom的segments需要手动添加endPoints - if (i !== 0) { - const lastSegmentPoints = segments[i - 1].points; - const endPoint = lastSegmentPoints[lastSegmentPoints.length - 1]; - endPoint && - bottomPoints.push({ - x: endPoint.x1 ?? endPoint.x, - y: endPoint.y1 ?? endPoint.y - }); - } - if (bottomPoints.length > 1) { - lastBottomSeg = calcLineCache( - parsePoint(bottomPoints, connectedType), - curveType === 'stepBefore' ? 'stepAfter' : curveType === 'stepAfter' ? 'stepBefore' : curveType, - { curveTension } - ); - bottomCaches.unshift(lastBottomSeg); - } + bottomSegments.push({ + points: bottomPoints + }); } + // const bottomSegments = segments.map(seg => ({...seg, points: seg.points.map(item => ({ + // x: item.x1 ?? item.x, + // y: item.y1 ?? item.y + // }))})); + const bottomPoints: IPointLike[] = []; + bottomSegments.forEach(seg => { + parsePoint(seg.points, connectedType).forEach(p => { + bottomPoints.push(p); + }); + }); + const bottomCaches = calcSegLineCache( + bottomSegments, + bottomPoints, + curveType === 'stepBefore' ? 'stepAfter' : curveType === 'stepAfter' ? 'stepBefore' : curveType, + { curveTension } + ) as any[]; + // // 翻转点 + bottomCaches.forEach(cache => { + cache.reverse && cache.reverse(); + }); + // let lastBottomSeg: ISegPath2D; + // console.log(bottomCaches); + // bottomCaches = []; + // console.log(bottomCaches); + // for (let i = segments.length - 1; i >= 0; i--) { + // const points = segments[i].points; + // const bottomPoints: IPointLike[] = []; + // for (let i = points.length - 1; i >= 0; i--) { + // bottomPoints.push({ + // x: points[i].x1 ?? points[i].x, + // y: points[i].y1 ?? points[i].y + // }); + // } + // // 处理一下bottom的segments,bottom的segments需要手动添加endPoints + // if (i !== 0) { + // const lastSegmentPoints = segments[i - 1].points; + // const endPoint = lastSegmentPoints[lastSegmentPoints.length - 1]; + // endPoint && + // bottomPoints.push({ + // x: endPoint.x1 ?? endPoint.x, + // y: endPoint.y1 ?? endPoint.y + // }); + // } + // if (bottomPoints.length > 1) { + // lastBottomSeg = calcLineCache( + // parsePoint(bottomPoints, connectedType), + // curveType === 'stepBefore' ? 'stepAfter' : curveType === 'stepAfter' ? 'stepBefore' : curveType, + // { curveTension } + // ); + // bottomCaches.unshift(lastBottomSeg); + // } + // } area.cacheArea = bottomCaches.map((item, index) => ({ top: topCaches[index], bottom: item diff --git a/packages/vrender-core/src/render/contributions/render/line-render.ts b/packages/vrender-core/src/render/contributions/render/line-render.ts index ed7730d8d..46ecc0f02 100644 --- a/packages/vrender-core/src/render/contributions/render/line-render.ts +++ b/packages/vrender-core/src/render/contributions/render/line-render.ts @@ -19,7 +19,7 @@ import { getTheme } from '../../../graphic/theme'; import { LINE_NUMBER_TYPE } from '../../../graphic/constants'; import { BaseRender } from './base-render'; import { drawSegments } from '../../../common/render-curve'; -import { calcLineCache } from '../../../common/segment'; +import { calcLineCache, calcSegLineCache } from '../../../common/segment'; /** * 默认的基于canvas的line渲染器 @@ -260,42 +260,48 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph const _points = points; if (segments && segments.length) { - let startPoint: IPointLike; - let lastSeg: { endX: number; endY: number; curves: Array<{ defined: boolean }> }; - line.cache = segments - .map((seg, index) => { - if (seg.points.length <= 1) { - // 第一个点的话,直接设置lastTopSeg - if (index === 0) { - seg.points[0] && - (lastSeg = { - endX: seg.points[0].x, - endY: seg.points[0].y, - curves: [{ defined: seg.points[0].defined !== false }] - }); - return null; - } - } - // 添加上一个segment结束的点作为这个segment的起始点 - if (index === 1) { - startPoint = { - x: lastSeg.endX, - y: lastSeg.endY, - defined: lastSeg.curves[lastSeg.curves.length - 1].defined - }; - } else if (index > 1) { - startPoint.x = lastSeg.endX; - startPoint.y = lastSeg.endY; - startPoint.defined = lastSeg.curves[lastSeg.curves.length - 1].defined; - } - const data = calcLineCache(parsePoint(seg.points, connectedType), curveType, { - startPoint, - curveTension - }); - lastSeg = data; - return data; - }) - .filter(item => !!item); + // 检测segment是否有定义curveType + const points: IPointLike[] = []; + segments.forEach(seg => { + parsePoint(seg.points, connectedType).forEach(p => { + points.push(p); + }); + }); + line.cache = calcSegLineCache(segments, points, curveType, { curveTension }); + // line.cache = segments + // .map((seg, index) => { + // if (seg.points.length <= 1) { + // // 第一个点的话,直接设置lastTopSeg + // if (index === 0) { + // seg.points[0] && + // (lastSeg = { + // endX: seg.points[0].x, + // endY: seg.points[0].y, + // curves: [{ defined: seg.points[0].defined !== false }] + // }); + // return null; + // } + // } + // // 添加上一个segment结束的点作为这个segment的起始点 + // if (index === 1) { + // startPoint = { + // x: lastSeg.endX, + // y: lastSeg.endY, + // defined: lastSeg.curves[lastSeg.curves.length - 1].defined + // }; + // } else if (index > 1) { + // startPoint.x = lastSeg.endX; + // startPoint.y = lastSeg.endY; + // startPoint.defined = lastSeg.curves[lastSeg.curves.length - 1].defined; + // } + // const data = calcLineCache(parsePoint(seg.points, connectedType), curveType, { + // startPoint, + // curveTension + // }); + // lastSeg = data; + // return data; + // }) + // .filter(item => !!item); // 如果lineClosed,那就绘制到第一个点 if (curveType === 'linearClosed') {