From 3a37d46305569221af9b4f72903812aa1d7c7406 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Fri, 2 Aug 2024 00:04:38 +0800 Subject: [PATCH 1/5] feat: label line support custom path. feat @VisActor/VChart#3000 --- .../__tests__/browser/examples/label-arc.ts | 138 ++++++++++++++---- packages/vrender-components/src/label/arc.ts | 12 +- packages/vrender-components/src/label/base.ts | 14 +- packages/vrender-components/src/label/type.ts | 11 +- 4 files changed, 138 insertions(+), 37 deletions(-) diff --git a/packages/vrender-components/__tests__/browser/examples/label-arc.ts b/packages/vrender-components/__tests__/browser/examples/label-arc.ts index 40fdc028a..73ccc5a19 100644 --- a/packages/vrender-components/__tests__/browser/examples/label-arc.ts +++ b/packages/vrender-components/__tests__/browser/examples/label-arc.ts @@ -3,6 +3,7 @@ import '@visactor/vrender'; import { createGroup, Stage, createArc } from '@visactor/vrender'; import { createRenderer } from '../../util/render'; import { ArcLabel } from '../../../src'; +import { IPointLike } from '@visactor/vutils'; const pieGenerator = () => { const spec: any = { @@ -1252,6 +1253,48 @@ const latestData = [ // } ]; +function drawRoundedPolyline(ctx, points, radius) { + for (let i = 0; i < points.length - 1; i++) { + const startPoint = points[i]; + const endPoint = points[i + 1]; + + // 计算线段的方向向量 + const dx = endPoint.x - startPoint.x; + const dy = endPoint.y - startPoint.y; + + // 计算转折点处的切线方向 + let tangentDx = -dy; + let tangentDy = dx; + + // 标准化切线方向向量 + const tangentLength = Math.sqrt(tangentDx * tangentDx + tangentDy * tangentDy); + tangentDx /= tangentLength; + tangentDy /= tangentLength; + + // 计算圆角的起始点和结束点 + const startRadiusPoint = { + x: startPoint.x + tangentDx * radius, + y: startPoint.y + tangentDy * radius + }; + + const endRadiusPoint = { + x: endPoint.x - tangentDx * radius, + y: endPoint.y - tangentDy * radius + }; + + // 绘制圆弧 + // ctx.beginPath(); + ctx.arc(startPoint.x, startPoint.y, radius, Math.atan2(tangentDy, tangentDx), Math.atan2(-dy, -dx)); + // ctx.stroke(); + + // 绘制直线部分 + // ctx.beginPath(); + ctx.moveTo(startRadiusPoint.x, startRadiusPoint.y); + ctx.lineTo(endRadiusPoint.x, endRadiusPoint.y); + // ctx.stroke(); + } +} + function createContent(stage: Stage) { const pieSpec = pieGenerator(); const pieGroup = createGroup(pieSpec.attribute); @@ -1266,7 +1309,8 @@ function createContent(stage: Stage) { baseMarkGroupName: pieSpec.name, data: pieSpec.children.map((c, index) => { return { - // // text: 'test122344556778891234550987665544' + text: 'xx' + // text: 'test122344556778891234550987665544', // text: latestData[index] ? latestData[index]?.type : undefined // // text: originData[index].id // // fill: c.attribute.fill, @@ -1276,30 +1320,30 @@ function createContent(stage: Stage) { // // lineWidth: 0 // // ...latestData[index] - textType: 'rich', - text: [ - // { - // text: `NO.${index}🐾`, - // fontSize: 15, - // textAlign: 'right', - // textDecoration: 'underline', - // stroke: '#0f51b5' - // } + // textType: 'rich', + // text: [ + // // { + // // text: `NO.${index}🐾`, + // // fontSize: 15, + // // textAlign: 'right', + // // textDecoration: 'underline', + // // stroke: '#0f51b5' + // // } - { - text: 'Mapbox', - fontWeight: 'bold', - fontSize: 25, - fill: '#3f51b5' - }, + // { + // text: 'Mapbox', + // fontWeight: 'bold', + // fontSize: 25, + // fill: '#3f51b5' + // }, - { - text: '替代方案', - fontStyle: 'italic', - textDecoration: 'underline', - fill: '#3f51b5' - } - ] + // { + // text: '替代方案', + // fontStyle: 'italic', + // textDecoration: 'underline', + // fill: '#3f51b5' + // } + // ] // type: 'html', // text: '

这是一个html字符串

' @@ -1315,7 +1359,7 @@ function createContent(stage: Stage) { }, width: 800, height: 500, - position: 'inside', + position: 'outside', // position: 'inside-outer', @@ -1323,14 +1367,44 @@ function createContent(stage: Stage) { // // angle: 0 // fontSize: 16 // }, - // line: { - // line1MinLength: 30, - // smooth: true, - // style: { - // lineWidth: 2, - // stroke: 'red' - // } - // }, + line: { + line1MinLength: 40, + line2MinLength: 60, + // smooth: true, + style: { + lineWidth: 1, + stroke: 'red', + customShape: (attrs, path) => { + console.log('attrs', attrs, path); + let points = attrs.points as IPointLike[]; + // 绘制带圆角的折线(暂时用小转折拟合) + const direction = points[points.length - 1].x - points[0].x > 0 ? -1 : 1; + path.moveTo(points[0].x, points[0].y); + for (let i = 1; i < points.length - 1; i++) { + const p1 = points[i - 1]; + const p2 = points[i % points.length]; + const p3 = points[(i + 1) % points.length]; + const { x: x1, y: y1 } = p1; + const { x: x2, y: y2 } = p2; + const { x: x3, y: y3 } = p3; + + const k1 = (y2 - y1) / (x2 - x1); + const k2 = (y3 - y2) / (x3 - x2); + const deltaX = 3; + const deltaY1 = k1 * deltaX; + const deltaY2 = k2 * deltaX; + + path.lineTo(p2.x + direction * deltaX, p2.y + direction * deltaY1); // 到点p1的上方 + path.lineTo(p2.x - direction * deltaX, p2.y - direction * deltaY2); // 绘制圆弧 + // path.quadraticCurveTo(p2.x - deltaX, p2.y - deltaY1, p2.x + deltaX, p2.y + deltaY2) + // path.quadraticCurveTo(p2.x - deltaX, p2.y - deltaY1, p2.x + deltaX, p2.y + deltaY2, 2) + } + + path.lineTo(points[points.length - 1].x, points[points.length - 1].y); + return path; + } + } + }, layout: { // align: 'edge' tangentConstraint: false diff --git a/packages/vrender-components/src/label/arc.ts b/packages/vrender-components/src/label/arc.ts index fa1559010..00c93082e 100644 --- a/packages/vrender-components/src/label/arc.ts +++ b/packages/vrender-components/src/label/arc.ts @@ -1,14 +1,18 @@ import type { IAABBBounds, IBoundsLike } from '@visactor/vutils'; +// eslint-disable-next-line no-duplicate-imports import { merge, isValidNumber, isNil, isLess, isGreater, isNumberClose as isClose } from '@visactor/vutils'; import { LabelBase } from './base'; import type { ArcLabelAttrs, IPoint, Quadrant, BaseLabelAttrs, LabelItem, IArcLabelLineSpec } from './type'; +import type { ILineGraphicAttribute } from '@visactor/vrender-core'; +// eslint-disable-next-line no-duplicate-imports import { type IRichText, type IText, type IArcGraphicAttribute, type IGraphic, type ILine, - graphicCreator + graphicCreator, + CustomPath2D } from '@visactor/vrender-core'; import { circlePoint, @@ -944,6 +948,12 @@ export class ArcLabel extends LabelBase { }) : undefined; if (labelLine) { + if (line.style?.customShape) { + const customShape = line.style.customShape; + labelLine.pathProxy = (attrs: Partial) => { + return customShape(attrs, new CustomPath2D()); + }; + } this._setStatesOfLabelLine(labelLine); } return labelLine; diff --git a/packages/vrender-components/src/label/base.ts b/packages/vrender-components/src/label/base.ts index ee4e0a47b..8aa1743e7 100644 --- a/packages/vrender-components/src/label/base.ts +++ b/packages/vrender-components/src/label/base.ts @@ -10,10 +10,13 @@ import type { IColor, ILine, IArea, - IRichText + IRichText, + ILineGraphicAttribute } from '@visactor/vrender-core'; -import { graphicCreator, AttributeUpdateType, IContainPointMode } from '@visactor/vrender-core'; +// eslint-disable-next-line no-duplicate-imports +import { graphicCreator, AttributeUpdateType, IContainPointMode, CustomPath2D } from '@visactor/vrender-core'; import type { IAABBBounds, IBoundsLike, IPointLike } from '@visactor/vutils'; +// eslint-disable-next-line no-duplicate-imports import { isFunction, isEmpty, @@ -31,6 +34,7 @@ import { labelSmartInvert, contrastAccessibilityChecker, smartInvertStrategy } f import { createTextGraphicByType, getMarksByName, getNoneGroupMarksByName, traverseGroup } from '../util'; import { StateValue } from '../constant'; import type { Bitmap } from './overlap'; +// eslint-disable-next-line no-duplicate-imports import { bitmapTool, boundToRange, canPlace, clampText, place } from './overlap'; import type { BaseLabelAttrs, @@ -126,6 +130,12 @@ export class LabelBase extends AbstractComponent { const line = graphicCreator.line({ points }); + if (line.style?.customShape) { + const customShape = line.style.customShape; + line.pathProxy = (attrs: Partial) => { + return customShape(attrs, new CustomPath2D()); + }; + } if (baseMark && baseMark.attribute.fill) { line.setAttribute('stroke', baseMark.attribute.fill); diff --git a/packages/vrender-components/src/label/type.ts b/packages/vrender-components/src/label/type.ts index 9dc92c79e..34e6885d4 100644 --- a/packages/vrender-components/src/label/type.ts +++ b/packages/vrender-components/src/label/type.ts @@ -10,7 +10,8 @@ import type { ILineGraphicAttribute, IRichTextCharacter, IRichText, - ILine + ILine, + ICustomPath2D } from '@visactor/vrender-core'; import type { BoundsAnchorType, IPointLike, InsideBoundsAnchorType } from '@visactor/vutils'; @@ -423,7 +424,13 @@ export interface ILabelLineSpec { /** * 引导线样式 */ - style?: Partial; + style?: Partial & { + /** + * 自定义路径 + * @since 0.19.21 + */ + customShape?: (attrs: Partial, path: ICustomPath2D) => ICustomPath2D; + }; } export interface IArcLabelLineSpec extends ILabelLineSpec { From 3fadc9177e425597848d583e601b1ffdf56d00a8 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Fri, 2 Aug 2024 00:05:21 +0800 Subject: [PATCH 2/5] chore: rush change --- .../feat-label-line_2024-08-01-16-05.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@visactor/vrender-components/feat-label-line_2024-08-01-16-05.json diff --git a/common/changes/@visactor/vrender-components/feat-label-line_2024-08-01-16-05.json b/common/changes/@visactor/vrender-components/feat-label-line_2024-08-01-16-05.json new file mode 100644 index 000000000..71fff7a1f --- /dev/null +++ b/common/changes/@visactor/vrender-components/feat-label-line_2024-08-01-16-05.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "feat: label line support custom path. feat @VisActor/VChart#3000", + "type": "none" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file From 158c64bb6820a8905256057d715f554343431571 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Fri, 2 Aug 2024 00:18:23 +0800 Subject: [PATCH 3/5] chore: delete unuseful code --- .../__tests__/browser/examples/label-arc.ts | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/packages/vrender-components/__tests__/browser/examples/label-arc.ts b/packages/vrender-components/__tests__/browser/examples/label-arc.ts index 73ccc5a19..352a5c767 100644 --- a/packages/vrender-components/__tests__/browser/examples/label-arc.ts +++ b/packages/vrender-components/__tests__/browser/examples/label-arc.ts @@ -1253,48 +1253,6 @@ const latestData = [ // } ]; -function drawRoundedPolyline(ctx, points, radius) { - for (let i = 0; i < points.length - 1; i++) { - const startPoint = points[i]; - const endPoint = points[i + 1]; - - // 计算线段的方向向量 - const dx = endPoint.x - startPoint.x; - const dy = endPoint.y - startPoint.y; - - // 计算转折点处的切线方向 - let tangentDx = -dy; - let tangentDy = dx; - - // 标准化切线方向向量 - const tangentLength = Math.sqrt(tangentDx * tangentDx + tangentDy * tangentDy); - tangentDx /= tangentLength; - tangentDy /= tangentLength; - - // 计算圆角的起始点和结束点 - const startRadiusPoint = { - x: startPoint.x + tangentDx * radius, - y: startPoint.y + tangentDy * radius - }; - - const endRadiusPoint = { - x: endPoint.x - tangentDx * radius, - y: endPoint.y - tangentDy * radius - }; - - // 绘制圆弧 - // ctx.beginPath(); - ctx.arc(startPoint.x, startPoint.y, radius, Math.atan2(tangentDy, tangentDx), Math.atan2(-dy, -dx)); - // ctx.stroke(); - - // 绘制直线部分 - // ctx.beginPath(); - ctx.moveTo(startRadiusPoint.x, startRadiusPoint.y); - ctx.lineTo(endRadiusPoint.x, endRadiusPoint.y); - // ctx.stroke(); - } -} - function createContent(stage: Stage) { const pieSpec = pieGenerator(); const pieGroup = createGroup(pieSpec.attribute); From fe3b39e8de0d71bcf0b6bb75f624c766f57ce3be Mon Sep 17 00:00:00 2001 From: skie1997 Date: Fri, 2 Aug 2024 10:49:15 +0800 Subject: [PATCH 4/5] chore: reconfig custom shape layer --- .../__tests__/browser/examples/label-arc.ts | 58 +++++++++---------- packages/vrender-components/src/label/arc.ts | 6 +- packages/vrender-components/src/label/base.ts | 6 +- packages/vrender-components/src/label/type.ts | 17 +++--- 4 files changed, 45 insertions(+), 42 deletions(-) diff --git a/packages/vrender-components/__tests__/browser/examples/label-arc.ts b/packages/vrender-components/__tests__/browser/examples/label-arc.ts index 352a5c767..a9ff29116 100644 --- a/packages/vrender-components/__tests__/browser/examples/label-arc.ts +++ b/packages/vrender-components/__tests__/browser/examples/label-arc.ts @@ -1329,38 +1329,38 @@ function createContent(stage: Stage) { line1MinLength: 40, line2MinLength: 60, // smooth: true, - style: { - lineWidth: 1, - stroke: 'red', - customShape: (attrs, path) => { - console.log('attrs', attrs, path); - let points = attrs.points as IPointLike[]; - // 绘制带圆角的折线(暂时用小转折拟合) - const direction = points[points.length - 1].x - points[0].x > 0 ? -1 : 1; - path.moveTo(points[0].x, points[0].y); - for (let i = 1; i < points.length - 1; i++) { - const p1 = points[i - 1]; - const p2 = points[i % points.length]; - const p3 = points[(i + 1) % points.length]; - const { x: x1, y: y1 } = p1; - const { x: x2, y: y2 } = p2; - const { x: x3, y: y3 } = p3; + customShape: (text, attrs, path) => { + console.log('attrs', text, attrs, path); + let points = attrs.points as IPointLike[]; + // 绘制带圆角的折线(暂时用小转折拟合) + const direction = points[points.length - 1].x - points[0].x > 0 ? -1 : 1; + path.moveTo(points[0].x, points[0].y); + for (let i = 1; i < points.length - 1; i++) { + const p1 = points[i - 1]; + const p2 = points[i % points.length]; + const p3 = points[(i + 1) % points.length]; + const { x: x1, y: y1 } = p1; + const { x: x2, y: y2 } = p2; + const { x: x3, y: y3 } = p3; - const k1 = (y2 - y1) / (x2 - x1); - const k2 = (y3 - y2) / (x3 - x2); - const deltaX = 3; - const deltaY1 = k1 * deltaX; - const deltaY2 = k2 * deltaX; + const k1 = (y2 - y1) / (x2 - x1); + const k2 = (y3 - y2) / (x3 - x2); + const deltaX = 3; + const deltaY1 = k1 * deltaX; + const deltaY2 = k2 * deltaX; - path.lineTo(p2.x + direction * deltaX, p2.y + direction * deltaY1); // 到点p1的上方 - path.lineTo(p2.x - direction * deltaX, p2.y - direction * deltaY2); // 绘制圆弧 - // path.quadraticCurveTo(p2.x - deltaX, p2.y - deltaY1, p2.x + deltaX, p2.y + deltaY2) - // path.quadraticCurveTo(p2.x - deltaX, p2.y - deltaY1, p2.x + deltaX, p2.y + deltaY2, 2) - } - - path.lineTo(points[points.length - 1].x, points[points.length - 1].y); - return path; + path.lineTo(p2.x + direction * deltaX, p2.y + direction * deltaY1); // 到点p1的上方 + path.lineTo(p2.x - direction * deltaX, p2.y - direction * deltaY2); // 绘制圆弧 + // path.quadraticCurveTo(p2.x - deltaX, p2.y - deltaY1, p2.x + deltaX, p2.y + deltaY2) + // path.quadraticCurveTo(p2.x - deltaX, p2.y - deltaY1, p2.x + deltaX, p2.y + deltaY2, 2) } + + path.lineTo(points[points.length - 1].x, points[points.length - 1].y); + return path; + }, + style: { + lineWidth: 1, + stroke: 'red' } }, layout: { diff --git a/packages/vrender-components/src/label/arc.ts b/packages/vrender-components/src/label/arc.ts index 00c93082e..b896120e8 100644 --- a/packages/vrender-components/src/label/arc.ts +++ b/packages/vrender-components/src/label/arc.ts @@ -948,10 +948,10 @@ export class ArcLabel extends LabelBase { }) : undefined; if (labelLine) { - if (line.style?.customShape) { - const customShape = line.style.customShape; + if (line?.customShape) { + const customShape = line.customShape; labelLine.pathProxy = (attrs: Partial) => { - return customShape(attrs, new CustomPath2D()); + return customShape(text.attribute.text, attrs, new CustomPath2D()); }; } this._setStatesOfLabelLine(labelLine); diff --git a/packages/vrender-components/src/label/base.ts b/packages/vrender-components/src/label/base.ts index 8aa1743e7..8910c433f 100644 --- a/packages/vrender-components/src/label/base.ts +++ b/packages/vrender-components/src/label/base.ts @@ -130,10 +130,10 @@ export class LabelBase extends AbstractComponent { const line = graphicCreator.line({ points }); - if (line.style?.customShape) { - const customShape = line.style.customShape; + if (line?.customShape) { + const customShape = line.customShape; line.pathProxy = (attrs: Partial) => { - return customShape(attrs, new CustomPath2D()); + return customShape(text.attribute.text, attrs, new CustomPath2D()); }; } diff --git a/packages/vrender-components/src/label/type.ts b/packages/vrender-components/src/label/type.ts index 34e6885d4..02cec5492 100644 --- a/packages/vrender-components/src/label/type.ts +++ b/packages/vrender-components/src/label/type.ts @@ -421,16 +421,19 @@ export interface ILabelLineSpec { * @default true */ visible?: boolean; + /** + * 自定义路径 + * @since 0.19.21 + */ + customShape?: ( + text: string | number | string[] | number[], + attrs: Partial, + path: ICustomPath2D + ) => ICustomPath2D; /** * 引导线样式 */ - style?: Partial & { - /** - * 自定义路径 - * @since 0.19.21 - */ - customShape?: (attrs: Partial, path: ICustomPath2D) => ICustomPath2D; - }; + style?: Partial; } export interface IArcLabelLineSpec extends ILabelLineSpec { From e794ee471ced052a928d7bfb685112c922f221ab Mon Sep 17 00:00:00 2001 From: skie1997 Date: Fri, 2 Aug 2024 11:57:53 +0800 Subject: [PATCH 5/5] feat: change callback params from text to mark attr --- packages/vrender-components/src/interface.ts | 10 ++++++++-- packages/vrender-components/src/label/arc.ts | 2 +- packages/vrender-components/src/label/base.ts | 2 +- packages/vrender-components/src/label/type.ts | 2 +- packages/vrender-components/src/tag/tag.ts | 6 +++--- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/vrender-components/src/interface.ts b/packages/vrender-components/src/interface.ts index fb4854f7b..551d182bd 100644 --- a/packages/vrender-components/src/interface.ts +++ b/packages/vrender-components/src/interface.ts @@ -1,4 +1,10 @@ -import type { ICustomPath2D, IGraphicAttribute, IRectGraphicAttribute } from '@visactor/vrender-core'; +import type { + ICustomPath2D, + IGraphicAttribute, + IRectGraphicAttribute, + IRichTextAttribute, + ITextGraphicAttribute +} from '@visactor/vrender-core'; import type { TextContent } from './core/type'; export type Direction = 'horizontal' | 'vertical'; @@ -15,7 +21,7 @@ export type BackgroundAttributes = { * @since 0.19.19 */ customShape?: ( - text: Pick, + text: ITextGraphicAttribute | IRichTextAttribute, attrs: Partial, path: ICustomPath2D ) => ICustomPath2D; diff --git a/packages/vrender-components/src/label/arc.ts b/packages/vrender-components/src/label/arc.ts index b896120e8..1c5c9dcee 100644 --- a/packages/vrender-components/src/label/arc.ts +++ b/packages/vrender-components/src/label/arc.ts @@ -951,7 +951,7 @@ export class ArcLabel extends LabelBase { if (line?.customShape) { const customShape = line.customShape; labelLine.pathProxy = (attrs: Partial) => { - return customShape(text.attribute.text, attrs, new CustomPath2D()); + return customShape(text.attribute, attrs, new CustomPath2D()); }; } this._setStatesOfLabelLine(labelLine); diff --git a/packages/vrender-components/src/label/base.ts b/packages/vrender-components/src/label/base.ts index 8910c433f..d9c61d3e7 100644 --- a/packages/vrender-components/src/label/base.ts +++ b/packages/vrender-components/src/label/base.ts @@ -133,7 +133,7 @@ export class LabelBase extends AbstractComponent { if (line?.customShape) { const customShape = line.customShape; line.pathProxy = (attrs: Partial) => { - return customShape(text.attribute.text, attrs, new CustomPath2D()); + return customShape(text.attribute, attrs, new CustomPath2D()); }; } diff --git a/packages/vrender-components/src/label/type.ts b/packages/vrender-components/src/label/type.ts index 02cec5492..0e176d9c0 100644 --- a/packages/vrender-components/src/label/type.ts +++ b/packages/vrender-components/src/label/type.ts @@ -426,7 +426,7 @@ export interface ILabelLineSpec { * @since 0.19.21 */ customShape?: ( - text: string | number | string[] | number[], + text: ITextGraphicAttribute, attrs: Partial, path: ICustomPath2D ) => ICustomPath2D; diff --git a/packages/vrender-components/src/tag/tag.ts b/packages/vrender-components/src/tag/tag.ts index 9d766a206..1306afb76 100644 --- a/packages/vrender-components/src/tag/tag.ts +++ b/packages/vrender-components/src/tag/tag.ts @@ -111,7 +111,7 @@ export class Tag extends AbstractComponent> { tagWidth += symbolPlaceWidth; textX += symbolPlaceWidth; - let textShape; + let textShape: IRichText | IText; const isRich = isRichText({ text } as TextContent) || type === 'rich'; if (isRich) { const richTextAttrs = { @@ -144,7 +144,7 @@ export class Tag extends AbstractComponent> { if (backgroundStyle.customShape) { const customShape = backgroundStyle.customShape; bgRect.pathProxy = (attrs: Partial) => { - return customShape(text as Pick, attrs, new CustomPath2D()); + return customShape(textShape.attribute, attrs, new CustomPath2D()); }; } this._bgRect = bgRect; @@ -320,7 +320,7 @@ export class Tag extends AbstractComponent> { if (backgroundStyle.customShape) { const customShape = backgroundStyle.customShape; bgRect.pathProxy = (attrs: Partial) => { - return customShape(text as Pick, attrs, new CustomPath2D()); + return customShape(textShape.attribute, attrs, new CustomPath2D()); }; } this._bgRect = bgRect;