diff --git a/common/changes/@visactor/vrender-core/feat-line-connect_2025-01-10-05-20.json b/common/changes/@visactor/vrender-core/feat-line-connect_2025-01-10-05-20.json new file mode 100644 index 000000000..5bcce79de --- /dev/null +++ b/common/changes/@visactor/vrender-core/feat-line-connect_2025-01-10-05-20.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-core", + "comment": "feat: change effect for connectedType, closed #1660", + "type": "none" + } + ], + "packageName": "@visactor/vrender-core" +} diff --git a/packages/vrender-core/src/common/render-area.ts b/packages/vrender-core/src/common/render-area.ts index ff2133707..743c231a2 100644 --- a/packages/vrender-core/src/common/render-area.ts +++ b/packages/vrender-core/src/common/render-area.ts @@ -24,16 +24,8 @@ export function drawAreaSegments( offsetY?: number; offsetZ?: number; direction?: IDirection; - drawConnect?: boolean; // 是否是绘制connect区域的效果 - mode?: 'none' | 'connect' | 'zero'; - zeroX?: number; - zeroY?: number; } ) { - const { drawConnect = false, mode = 'none' } = params || {}; - if (drawConnect && mode === 'none') { - return; - } // let needMoveTo: boolean = true; const { top, bottom } = segPath; // 如果top和bottom的curves数量不同,那么就跳过 @@ -44,90 +36,26 @@ export function drawAreaSegments( const topList: ICurve[] = []; const bottomList: ICurve[] = []; let lastDefined: boolean = true; - if (drawConnect) { - let defined0 = true; - let lastCurve: ICurve; - let lastBottomCurve: ICurve; - const n = top.curves.length; - top.curves.forEach((curve, i) => { - // step的逻辑 - const bototmCurve = bottom.curves[n - i - 1]; - let currentTopCurve = curve; - let currentBottomCurve = bototmCurve; - if (curve.originP1 === curve.originP2) { - lastCurve = curve; - lastBottomCurve = bototmCurve; - return; - } - if (lastCurve && lastCurve.originP1 === lastCurve.originP2) { - currentTopCurve = lastCurve; - currentBottomCurve = lastBottomCurve; - } - if (curve.defined) { - // 非法变合法需要lineTo,合法变非法需要moveTo,初始非法需要moveTo - if (!defined0) { - topList.push(currentTopCurve); - bottomList.push(currentBottomCurve); - drawAreaConnectBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - defined0 = !defined0; - } + for (let i = 0, n = top.curves.length; i < n; i++) { + const topCurve = top.curves[i]; + if (lastDefined !== topCurve.defined) { + if (lastDefined) { + drawAreaBlock(path, topList, bottomList, params); + topList.length = 0; + bottomList.length = 0; } else { - // 找到合法的点 - const { originP1, originP2 } = curve; - let validTopCurve: ICurve; - let validBottomCurve: ICurve; - if (originP1 && originP1.defined !== false) { - validTopCurve = currentTopCurve; - validBottomCurve = currentBottomCurve; - } else if (originP1 && originP2.defined !== false) { - validTopCurve = curve; - validBottomCurve = bototmCurve; - } - // 合法/(初始)变非法,moveTo - if (defined0) { - defined0 = !defined0; - topList.push(validTopCurve || curve); - bottomList.push(validBottomCurve || bototmCurve); - } else { - // 非法变非法/合法,看情况要不要lineTo - if (validTopCurve) { - // 非法变合法,需要lineTo - defined0 = !defined0; - topList.push(validTopCurve || curve); - bottomList.push(validBottomCurve || bototmCurve); - drawAreaConnectBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - } - } + topList.push(topCurve); + bottomList.push(bottom.curves[n - i - 1]); } - lastCurve = curve; - }); - drawAreaConnectBlock(path, topList, bottomList, params); - } else { - for (let i = 0, n = top.curves.length; i < n; i++) { - const topCurve = top.curves[i]; - if (lastDefined !== topCurve.defined) { - if (lastDefined) { - drawAreaBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - } else { - topList.push(topCurve); - bottomList.push(bottom.curves[n - i - 1]); - } - lastDefined = !lastDefined; - } else { - if (lastDefined) { - topList.push(topCurve); - bottomList.push(bottom.curves[n - i - 1]); - } + lastDefined = !lastDefined; + } else { + if (lastDefined) { + topList.push(topCurve); + bottomList.push(bottom.curves[n - i - 1]); } } - drawAreaBlock(path, topList, bottomList, params); } + drawAreaBlock(path, topList, bottomList, params); return; } @@ -159,7 +87,7 @@ export function drawAreaSegments( let lastDefined: boolean = true; const topList: ICurve[] = []; const bottomList: ICurve[] = []; - let defined0 = true; + const defined0 = true; let lastTopCurve: ICurve; let lastBottomCurve: ICurve; for (let i = 0, n = top.curves.length; i < n; i++) { @@ -171,112 +99,50 @@ export function drawAreaSegments( } drawedLengthUntilLast += curCurveLength; - if (drawConnect) { - // step的逻辑 - const bototmCurve = bottom.curves[n - i - 1]; - let currentTopCurve = topCurve; - let currentBottomCurve = bototmCurve; - if (topCurve.originP1 === topCurve.originP2) { - lastTopCurve = topCurve; - lastBottomCurve = bototmCurve; - continue; - } - if (lastTopCurve && lastTopCurve.originP1 === lastTopCurve.originP2) { - currentTopCurve = lastTopCurve; - currentBottomCurve = lastBottomCurve; - } - if (topCurve.defined) { - // 非法变合法需要lineTo,合法变非法需要moveTo,初始非法需要moveTo - if (!defined0) { - topList.push(currentTopCurve); - bottomList.push(currentBottomCurve); - drawAreaConnectBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - defined0 = !defined0; - } + let tc: ICurve | null = null; + let bc: ICurve | null = null; + if (lastDefined !== topCurve.defined) { + if (lastDefined) { + drawAreaBlock(path, topList, bottomList, params); + topList.length = 0; + bottomList.length = 0; } else { - // 找到合法的点 - const { originP1, originP2 } = topCurve; - let validTopCurve: ICurve; - let validBottomCurve: ICurve; - if (originP1 && originP1.defined !== false) { - validTopCurve = currentTopCurve; - validBottomCurve = currentBottomCurve; - } else if (originP1 && originP2.defined !== false) { - validTopCurve = topCurve; - validBottomCurve = bototmCurve; - } - // 合法/(初始)变非法,moveTo - if (defined0) { - defined0 = !defined0; - topList.push(validTopCurve || topCurve); - bottomList.push(validBottomCurve || bototmCurve); - } else { - // 非法变非法/合法,看情况要不要lineTo - if (validTopCurve) { - // 非法变合法,需要lineTo - defined0 = !defined0; - topList.push(validTopCurve || topCurve); - bottomList.push(validBottomCurve || bototmCurve); - drawAreaConnectBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - } - } + tc = topCurve; + bc = bottom.curves[n - i - 1]; } - lastTopCurve = topCurve; - // drawAreaBlock(path, topList, bottomList, params); + lastDefined = !lastDefined; } else { - let tc: ICurve | null = null; - let bc: ICurve | null = null; - if (lastDefined !== topCurve.defined) { - if (lastDefined) { - drawAreaBlock(path, topList, bottomList, params); - topList.length = 0; - bottomList.length = 0; - } else { - tc = topCurve; - bc = bottom.curves[n - i - 1]; - } - lastDefined = !lastDefined; - } else { - if (lastDefined) { - tc = topCurve; - bc = bottom.curves[n - i - 1]; - } + if (lastDefined) { + tc = topCurve; + bc = bottom.curves[n - i - 1]; } + } - if (tc && bc) { - if (percent < 1) { - if (tc.p2 && tc.p3) { - tc = divideCubic(tc as ICubicBezierCurve, percent)[0]; - } else { - tc = divideLinear(tc as ILineCurve, percent)[0]; - } - if (bc.p2 && bc.p3) { - bc = divideCubic(bc as ICubicBezierCurve, 1 - percent)[1]; - } else { - bc = divideLinear(bc as ILineCurve, 1 - percent)[1]; - } + if (tc && bc) { + if (percent < 1) { + if (tc.p2 && tc.p3) { + tc = divideCubic(tc as ICubicBezierCurve, percent)[0]; + } else { + tc = divideLinear(tc as ILineCurve, percent)[0]; + } + if (bc.p2 && bc.p3) { + bc = divideCubic(bc as ICubicBezierCurve, 1 - percent)[1]; + } else { + bc = divideLinear(bc as ILineCurve, 1 - percent)[1]; } - tc.defined = lastDefined; - bc.defined = lastDefined; - topList.push(tc); - bottomList.push(bc); } - - tc = null; - bc = null; + tc.defined = lastDefined; + bc.defined = lastDefined; + topList.push(tc); + bottomList.push(bc); } - } - if (drawConnect) { - drawAreaConnectBlock(path, topList, bottomList, params); - } else { - drawAreaBlock(path, topList, bottomList, params); + tc = null; + bc = null; } + drawAreaBlock(path, topList, bottomList, params); + // const totalLength = segPath.tryUpdateLength(); // // 直到上次绘制的长度 @@ -303,39 +169,6 @@ export function drawAreaSegments( // } } -function drawAreaConnectBlock( - path: IPath2D, - topList: ICurve[], - bottomList: ICurve[], - params?: { - offsetX?: number; - offsetY?: number; - offsetZ?: number; - mode?: 'none' | 'connect' | 'zero'; - zeroX?: number; - zeroY?: number; - } -) { - if (topList.length < 2) { - return; - } - const { offsetX = 0, offsetY = 0, offsetZ = 0, mode } = params || {}; - let curve = topList[0]; - // mode不支持zero - path.moveTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); - curve = topList[topList.length - 1]; - let end = curve.p3 || curve.p1; - path.lineTo(end.x + offsetX, end.y + offsetY, offsetZ); - - curve = bottomList[bottomList.length - 1]; - path.lineTo(curve.p0.x + offsetX, curve.p0.y + offsetY, offsetZ); - curve = bottomList[0]; - end = curve.p3 || curve.p1; - path.lineTo(end.x + offsetX, end.y + offsetY, offsetZ); - - path.closePath(); -} - function drawAreaBlock( path: IPath2D, topList: ICurve[], diff --git a/packages/vrender-core/src/common/render-curve.ts b/packages/vrender-core/src/common/render-curve.ts index b22abd04e..6c870ab9a 100644 --- a/packages/vrender-core/src/common/render-curve.ts +++ b/packages/vrender-core/src/common/render-curve.ts @@ -82,7 +82,7 @@ export function drawSegments( offsetY?: number; offsetZ?: number; drawConnect?: boolean; // 是否是绘制connect区域的效果 - mode?: 'none' | 'connect' | 'zero'; + mode?: 'none' | 'connect'; zeroX?: number; zeroY?: number; } diff --git a/packages/vrender-core/src/common/segment/step.ts b/packages/vrender-core/src/common/segment/step.ts index 3fd0db4eb..f6981e038 100644 --- a/packages/vrender-core/src/common/segment/step.ts +++ b/packages/vrender-core/src/common/segment/step.ts @@ -87,7 +87,11 @@ export class Step implements ICurvedSegment { this.context.lineTo(x, y, this._lastDefined !== false && p.defined !== false, p); } else { const x1 = this._x * (1 - this._t) + x * this._t; - this.context.lineTo(x1, this._y, this._lastDefined !== false && p.defined !== false, this.lastPoint); + if (this._t === 0.5) { + this.context.lineTo(x1, this._y, this._lastDefined !== false, this.lastPoint); + } else { + this.context.lineTo(x1, this._y, this._lastDefined !== false && p.defined !== false, this.lastPoint); + } this.context.lineTo(x1, y, this._lastDefined !== false && p.defined !== false, p); } break; diff --git a/packages/vrender-core/src/graphic/line.ts b/packages/vrender-core/src/graphic/line.ts index 20cf0be12..9e188355c 100644 --- a/packages/vrender-core/src/graphic/line.ts +++ b/packages/vrender-core/src/graphic/line.ts @@ -82,7 +82,7 @@ export class Line extends Graphic implements ILine { const { points = lineTheme.points, connectedType } = attribute; const b = aabbBounds; points.forEach(p => { - if (p.defined !== false || connectedType === 'zero' || connectedType === 'connect') { + if (p.defined !== false || connectedType === 'connect') { b.add(p.x, p.y); } }); @@ -98,7 +98,7 @@ export class Line extends Graphic implements ILine { const b = aabbBounds; segments.forEach(s => { s.points.forEach(p => { - if (p.defined !== false || connectedType === 'zero' || connectedType === 'connect') { + if (p.defined !== false || connectedType === 'connect') { b.add(p.x, p.y); } }); diff --git a/packages/vrender-core/src/interface/graphic.ts b/packages/vrender-core/src/interface/graphic.ts index cda04af76..2c6ea9e7d 100644 --- a/packages/vrender-core/src/interface/graphic.ts +++ b/packages/vrender-core/src/interface/graphic.ts @@ -198,7 +198,7 @@ export type IConnectedStyle = { /** * 连接,取零或者断开 */ - connectedType: 'connect' | 'zero' | 'none'; + connectedType: 'connect' | 'none'; /** * 连接线的样式配置 */ diff --git a/packages/vrender-core/src/render/contributions/render/area-render.ts b/packages/vrender-core/src/render/contributions/render/area-render.ts index aed87ba04..71748d6f0 100644 --- a/packages/vrender-core/src/render/contributions/render/area-render.ts +++ b/packages/vrender-core/src/render/contributions/render/area-render.ts @@ -195,7 +195,8 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph fillOpacity = areaAttribute.fillOpacity, z = areaAttribute.z, strokeOpacity = areaAttribute.strokeOpacity, - curveTension = areaAttribute.curveTension + curveTension = areaAttribute.curveTension, + connectedType = areaAttribute.connectedType } = area.attribute; const data = this.valid(area, areaAttribute, fillCb, strokeCb); @@ -211,6 +212,13 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph curveType = 'linearClosed'; } + function parsePoint(points: IPointLike[], connectedType: 'none' | 'connect') { + if (connectedType !== 'connect') { + return points; + } + return points.filter(p => p.defined !== false); + } + if (clipRange === 1 && !segments && !points.some(p => p.defined === false) && curveType === 'linear') { return this.drawLinearAreaHighPerformance( area, @@ -250,7 +258,7 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph startPoint.x = lastTopSeg.endX; startPoint.y = lastTopSeg.endY; } - const data = calcLineCache(seg.points, curveType, { + const data = calcLineCache(parsePoint(seg.points, connectedType), curveType, { startPoint, curveTension }); @@ -281,7 +289,7 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph } if (bottomPoints.length > 1) { lastBottomSeg = calcLineCache( - bottomPoints, + parsePoint(bottomPoints, connectedType), curveType === 'stepBefore' ? 'stepAfter' : curveType === 'stepAfter' ? 'stepBefore' : curveType, { curveTension } ); @@ -294,12 +302,12 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph })); } else if (points && points.length) { // 转换points - const topPoints = points; + const topPoints = parsePoint(points, connectedType); const bottomPoints: IPointLike[] = []; - for (let i = points.length - 1; i >= 0; i--) { + for (let i = topPoints.length - 1; i >= 0; i--) { bottomPoints.push({ - x: points[i].x1 ?? points[i].x, - y: points[i].y1 ?? points[i].y + x: points[i].x1 ?? topPoints[i].x, + y: points[i].y1 ?? topPoints[i].y }); } const topCache = calcLineCache(topPoints, curveType, { curveTension }); @@ -456,50 +464,24 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph themeAttribute: IThemeAttribute | IThemeAttribute[] ) => boolean ): boolean { - let ret = false; - ret = - ret || - this._drawSegmentItem( - context, - cache, - fill, - fillOpacity, - stroke, - strokeOpacity, - attribute, - defaultAttribute, - clipRange, - offsetX, - offsetY, - offsetZ, - area, - drawContext, - false, - fillCb, - strokeCb - ); - ret = - ret || - this._drawSegmentItem( - context, - cache, - fill, - fillOpacity, - stroke, - strokeOpacity, - attribute, - defaultAttribute, - clipRange, - offsetX, - offsetY, - offsetZ, - area, - drawContext, - true, - fillCb, - strokeCb - ); - return ret; + return this._drawSegmentItem( + context, + cache, + fill, + fillOpacity, + stroke, + strokeOpacity, + attribute, + defaultAttribute, + clipRange, + offsetX, + offsetY, + offsetZ, + area, + drawContext, + fillCb, + strokeCb + ); } protected _drawSegmentItem( @@ -517,7 +499,6 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph offsetZ: number, area: IArea, drawContext: IDrawContext, - connect: boolean, fillCb?: ( ctx: IContext2d, lineAttribute: Partial, @@ -542,38 +523,6 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph ) { return; } - // 绘制connect区域 - let { connectedType, connectedX, connectedY, connectedStyle } = attribute; - const da: any[] = []; - if (connect) { - if (isArray(defaultAttribute)) { - connectedType = connectedType ?? defaultAttribute[0].connectedType ?? defaultAttribute[1].connectedType; - connectedX = connectedX ?? defaultAttribute[0].connectedX ?? defaultAttribute[1].connectedX; - connectedY = connectedY ?? defaultAttribute[0].connectedY ?? defaultAttribute[1].connectedY; - connectedStyle = connectedStyle ?? defaultAttribute[0].connectedStyle ?? defaultAttribute[1].connectedStyle; - } else { - connectedType = connectedType ?? defaultAttribute.connectedType; - connectedX = connectedX ?? defaultAttribute.connectedX; - connectedY = connectedY ?? defaultAttribute.connectedY; - connectedStyle = connectedStyle ?? defaultAttribute.connectedStyle; - } - - // 如果有非法值就是none - if (connectedType !== 'connect' && connectedType !== 'zero') { - connectedType = 'none'; - } - - if (isArray(defaultAttribute)) { - defaultAttribute.forEach(i => da.push(i)); - } else { - da.push(defaultAttribute); - } - da.push(attribute); - } - - if (connect && connectedType === 'none') { - return false; - } context.beginPath(); @@ -606,11 +555,7 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph offsetX, offsetY, offsetZ, - direction, - drawConnect: connect, - mode: connectedType, - zeroX: connectedX, - zeroY: connectedY + direction }); this.beforeRenderStep( @@ -638,13 +583,7 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph if (fillCb) { fillCb(context, attribute, defaultAttribute); } else if (fillOpacity) { - context.setCommonStyle( - area, - connect ? connectedStyle : attribute, - originX - offsetX, - originY - offsetY, - connect ? da : defaultAttribute - ); + context.setCommonStyle(area, attribute, originX - offsetX, originY - offsetY, defaultAttribute); context.fill(); } } @@ -667,21 +606,11 @@ export class DefaultCanvasAreaRender extends BaseRender implements IGraph { offsetX, offsetY, - offsetZ, - drawConnect: connect, - mode: connectedType, - zeroX: connectedX, - zeroY: connectedY + offsetZ } ); } - context.setStrokeStyle( - area, - connect ? connectedStyle : attribute, - originX - offsetX, - originY - offsetY, - connect ? da : defaultAttribute - ); + context.setStrokeStyle(area, attribute, originX - offsetX, originY - offsetY, defaultAttribute); context.stroke(); } } diff --git a/packages/vrender-core/src/render/contributions/render/line-render.ts b/packages/vrender-core/src/render/contributions/render/line-render.ts index cdbc27e22..ed7730d8d 100644 --- a/packages/vrender-core/src/render/contributions/render/line-render.ts +++ b/packages/vrender-core/src/render/contributions/render/line-render.ts @@ -121,61 +121,6 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph context.stroke(); } } - - // 绘制connect区域 - let { connectedType, connectedX, connectedY, connectedStyle } = attribute; - if (isArray(defaultAttribute)) { - connectedType = connectedType ?? defaultAttribute[0].connectedType ?? defaultAttribute[1].connectedType; - connectedX = connectedX ?? defaultAttribute[0].connectedX ?? defaultAttribute[1].connectedX; - connectedY = connectedY ?? defaultAttribute[0].connectedY ?? defaultAttribute[1].connectedY; - connectedStyle = connectedStyle ?? defaultAttribute[0].connectedStyle ?? defaultAttribute[1].connectedStyle; - } else { - connectedType = connectedType ?? defaultAttribute.connectedType; - connectedX = connectedX ?? defaultAttribute.connectedX; - connectedY = connectedY ?? defaultAttribute.connectedY; - connectedStyle = connectedStyle ?? defaultAttribute.connectedStyle; - } - // 如果有非法值就是none - if (connectedType !== 'connect' && connectedType !== 'zero') { - connectedType = 'none'; - } - if (connectedType !== 'none') { - context.beginPath(); - drawSegments(context.camera ? context : context.nativeContext, cache, clipRange, clipRangeByDimension, { - offsetX, - offsetY, - offsetZ: z, - drawConnect: true, - mode: connectedType, - zeroX: connectedX, - zeroY: connectedY - }); - - const da = []; - if (isArray(defaultAttribute)) { - defaultAttribute.forEach(i => da.push(i)); - } else { - da.push(defaultAttribute); - } - da.push(attribute); - - if (fill !== false) { - if (fillCb) { - fillCb(context, attribute, defaultAttribute); - } else if (fillOpacity) { - context.setCommonStyle(line, connectedStyle, originX - offsetX, originY - offsetY, da); - context.fill(); - } - } - if (stroke !== false) { - if (strokeCb) { - strokeCb(context, attribute, defaultAttribute); - } else if (strokeOpacity) { - context.setStrokeStyle(line, connectedStyle, originX - offsetX, originY - offsetY, da); - context.stroke(); - } - } - } return !!ret; } @@ -266,7 +211,8 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph segments, points, closePath, - curveTension = lineAttribute.curveTension + curveTension = lineAttribute.curveTension, + connectedType = lineAttribute.connectedType } = line.attribute; const data = this.valid(line, lineAttribute, fillCb, strokeCb); @@ -301,6 +247,13 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph } // const { fVisible, sVisible, doFill, doStroke } = data; + function parsePoint(points: IPointLike[], connectedType: 'none' | 'connect') { + if (connectedType === 'none') { + return points; + } + return points.filter(p => p.defined !== false); + } + // 更新cache if (line.shouldUpdateShape()) { const { points, segments } = line.attribute; @@ -335,7 +288,7 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph startPoint.y = lastSeg.endY; startPoint.defined = lastSeg.curves[lastSeg.curves.length - 1].defined; } - const data = calcLineCache(seg.points, curveType, { + const data = calcLineCache(parsePoint(seg.points, connectedType), curveType, { startPoint, curveTension }); @@ -362,7 +315,7 @@ export class DefaultCanvasLineRender extends BaseRender implements IGraph line.cache[line.cache.length - 1] && line.cache[line.cache.length - 1].lineTo(startP.x, startP.y, true); } } else if (points && points.length) { - line.cache = calcLineCache(_points, curveType, { curveTension }); + line.cache = calcLineCache(parsePoint(_points, connectedType), curveType, { curveTension }); } else { line.cache = null; line.clearUpdateShapeTag();