diff --git a/common/changes/@visactor/vrender-components/fix-marker-position-and-ref_2024-05-26-09-16.json b/common/changes/@visactor/vrender-components/fix-marker-position-and-ref_2024-05-26-09-16.json new file mode 100644 index 000000000..5f75ea66a --- /dev/null +++ b/common/changes/@visactor/vrender-components/fix-marker-position-and-ref_2024-05-26-09-16.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "fix(marker): fix marker position and ref bad case. fix@visactor/vchart#2721", + "type": "none" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/packages/vrender-components/__tests__/browser/examples/mark-line.ts b/packages/vrender-components/__tests__/browser/examples/mark-line.ts index 3b647a7ae..d51bcdd6e 100644 --- a/packages/vrender-components/__tests__/browser/examples/mark-line.ts +++ b/packages/vrender-components/__tests__/browser/examples/mark-line.ts @@ -120,6 +120,12 @@ export function run() { refY: guiObject.labelRefY, refAngle: degreeToRadian(guiObject.labelRefAngle) }, + limitRect: { + x: 180, + y: 250, + width: 30, + height: 3000 + }, clipInRange: false, interactive: true // limitRect: { @@ -136,7 +142,7 @@ export function run() { y: 350 }, { - x: 200, + x: 300, y: 250 } ], @@ -204,12 +210,20 @@ export function run() { gui .add(guiObject, 'labelPos', [ 'start', - 'middle', - 'end', + 'startTop', + 'startBottom', + 'insideStart', 'insideStartTop', 'insideStartBottom', + + 'middle', 'insideMiddleTop', 'insideMiddleBottom', + + 'end', + 'endTop', + 'endBottom', + 'insideEnd', 'insideEndTop', 'insideEndBottom' ]) diff --git a/packages/vrender-components/__tests__/browser/examples/mark-point.ts b/packages/vrender-components/__tests__/browser/examples/mark-point.ts index 1abc4c024..43bcdb68a 100644 --- a/packages/vrender-components/__tests__/browser/examples/mark-point.ts +++ b/packages/vrender-components/__tests__/browser/examples/mark-point.ts @@ -16,15 +16,22 @@ export function run() { itemLineVisible: true, // itemType: 'text', itemPos: 'middle', - itemOffsetX: 100, - itemOffsetY: 100, + // itemOffsetX: 100, + // itemOffsetY: 100, + offsetLen: 150, + angleOfOffset: 0, itemAutoRotate: false, itemRefX: 10, itemRefY: 0, itemRefAngle: 0, decorativeLineVisible: false, visible: true, - arcRatio: 0.8 + arcRatio: 0.8, + startSymbolRefX: 30, + startSymbolRefY: 0, + endSymbolRefX: 30, + endSymbolRefY: 0, + targetSymbolOffset: 0 }; const styleAttr = { @@ -112,13 +119,17 @@ export function run() { visible: guiObject.decorativeLineVisible }, startSymbol: { - visible: true, - symbolType: 'triangle' + visible: false, + symbolType: 'triangle', + refX: guiObject.startSymbolRefX, + refY: guiObject.startSymbolRefY }, endSymbol: { - visible: true, + visible: false, symbolType: 'triangle', size: 10, + refX: guiObject.endSymbolRefX, + refY: guiObject.endSymbolRefY, style: { fill: 'blue' } @@ -131,8 +142,8 @@ export function run() { } }, itemContent: { - offsetX: guiObject.itemOffsetX, - offsetY: guiObject.itemOffsetY, + offsetX: Math.cos((guiObject.angleOfOffset / 180) * Math.PI) * guiObject.offsetLen, + offsetY: Math.sin((guiObject.angleOfOffset / 180) * Math.PI) * guiObject.offsetLen, refX: guiObject.itemRefX, refY: guiObject.itemRefY, refAngle: guiObject.itemRefAngle, @@ -221,7 +232,7 @@ export function run() { }, targetSymbol: { visible: true, - offset: 10 + offset: guiObject.targetSymbolOffset // style: { // size: 30, // // fill: 'red', @@ -254,8 +265,8 @@ export function run() { const markPoint2 = new MarkPoint({ position: { - x: 200, - y: 200 + x: 250, + y: 250 }, ...(styleAttr as any), itemContent: { @@ -329,22 +340,40 @@ export function run() { markPoints.forEach(markPoint => markPoint.setAttribute('visible', value, true)); }); - gui.add(guiObject, 'itemOffsetX').onChange(value => { + // gui.add(guiObject, 'itemOffsetX').onChange(value => { + // markPoints.forEach(markPoint => + // markPoint.setAttribute('itemContent', { + // offsetX: value + // }) + // ); + // console.log('markpoints', markPoints); + // }); + + // gui.add(guiObject, 'itemOffsetY').onChange(value => { + // markPoints.forEach(markPoint => + // markPoint.setAttribute('itemContent', { + // offsetY: value + // }) + // ); + // console.log('markpoints', markPoints); + // }); + + gui.add(guiObject, 'offsetLen').onChange(value => { markPoints.forEach(markPoint => markPoint.setAttribute('itemContent', { - offsetX: value + offsetX: value * Math.cos((guiObject.angleOfOffset / 180) * Math.PI), + offsetY: value * Math.sin((guiObject.angleOfOffset / 180) * Math.PI) }) ); - console.log('markpoints', markPoints); }); - gui.add(guiObject, 'itemOffsetY').onChange(value => { + gui.add(guiObject, 'angleOfOffset', 0, 360).onChange(value => { markPoints.forEach(markPoint => markPoint.setAttribute('itemContent', { - offsetY: value + offsetX: guiObject.offsetLen * Math.cos((value / 180) * Math.PI), + offsetY: guiObject.offsetLen * Math.sin((value / 180) * Math.PI) }) ); - console.log('markpoints', markPoints); }); gui.add(guiObject, 'itemAutoRotate').onChange(value => { @@ -371,6 +400,46 @@ export function run() { ); }); + gui.add(guiObject, 'startSymbolRefX').onChange(value => { + markPoints.forEach(markPoint => + markPoint.setAttribute('itemLine', { + startSymbol: { + refX: value + } + }) + ); + }); + + gui.add(guiObject, 'startSymbolRefY').onChange(value => { + markPoints.forEach(markPoint => + markPoint.setAttribute('itemLine', { + startSymbol: { + refY: value + } + }) + ); + }); + + gui.add(guiObject, 'endSymbolRefX').onChange(value => { + markPoints.forEach(markPoint => + markPoint.setAttribute('itemLine', { + endSymbol: { + refX: value + } + }) + ); + }); + + gui.add(guiObject, 'endSymbolRefY').onChange(value => { + markPoints.forEach(markPoint => + markPoint.setAttribute('itemLine', { + endSymbol: { + refY: value + } + }) + ); + }); + gui.add(guiObject, 'itemRefAngle').onChange(value => { markPoints.forEach(markPoint => markPoint.setAttribute('itemContent', { @@ -396,4 +465,12 @@ export function run() { }) ); }); + + gui.add(guiObject, 'targetSymbolOffset').onChange(value => { + markPoints.forEach(markPoint => + markPoint.setAttribute('targetSymbol', { + offset: value + }) + ); + }); } diff --git a/packages/vrender-components/src/marker/common-line.ts b/packages/vrender-components/src/marker/common-line.ts index 44337f47a..27ee0ffe7 100644 --- a/packages/vrender-components/src/marker/common-line.ts +++ b/packages/vrender-components/src/marker/common-line.ts @@ -40,7 +40,9 @@ export abstract class MarkCommonLine extends Marker< const { label = {}, limitRect } = this.attribute; const { position, confine, autoRotate } = label; const labelPoint = this.getPointAttrByPosition(position); - const labelAngle = this._line.getEndAngle() || 0; + const labelAngle = position.toString().toLocaleLowerCase().includes('start') + ? this._line.getStartAngle() || 0 + : this._line.getEndAngle() || 0; this._label.setAttributes({ ...labelPoint.position, angle: autoRotate ? this.getRotateByAngle(labelPoint.angle) : 0, diff --git a/packages/vrender-components/src/marker/config.ts b/packages/vrender-components/src/marker/config.ts index 60685642a..73a6df366 100644 --- a/packages/vrender-components/src/marker/config.ts +++ b/packages/vrender-components/src/marker/config.ts @@ -1,6 +1,7 @@ import type { TextAlignType, TextBaselineType } from '@visactor/vrender-core'; import { IMarkAreaLabelPosition, IMarkLineLabelPosition, IMarkCommonArcLabelPosition } from './type'; +export const FUZZY_EQUAL_DELTA = 0.001; export const DEFAULT_MARK_LINE_THEME = { interactive: true, startSymbol: { @@ -55,15 +56,27 @@ export const DEFAULT_CARTESIAN_MARK_LINE_TEXT_STYLE_MAP: { } = { postiveXAxis: { start: { + textAlign: 'left', + textBaseline: 'middle' + }, + startTop: { + textAlign: 'left', + textBaseline: 'bottom' + }, + startBottom: { + textAlign: 'left', + textBaseline: 'top' + }, + insideStart: { textAlign: 'right', textBaseline: 'middle' }, insideStartTop: { - textAlign: 'left', + textAlign: 'right', textBaseline: 'bottom' }, insideStartBottom: { - textAlign: 'left', + textAlign: 'right', textBaseline: 'top' }, @@ -84,6 +97,18 @@ export const DEFAULT_CARTESIAN_MARK_LINE_TEXT_STYLE_MAP: { textAlign: 'left', textBaseline: 'middle' }, + endTop: { + textAlign: 'left', + textBaseline: 'bottom' + }, + endBottom: { + textAlign: 'left', + textBaseline: 'top' + }, + insideEnd: { + textAlign: 'right', + textBaseline: 'middle' + }, insideEndTop: { textAlign: 'right', textBaseline: 'bottom' @@ -95,15 +120,27 @@ export const DEFAULT_CARTESIAN_MARK_LINE_TEXT_STYLE_MAP: { }, negativeXAxis: { start: { + textAlign: 'right', + textBaseline: 'middle' + }, + startTop: { + textAlign: 'right', + textBaseline: 'bottom' + }, + startBottom: { + textAlign: 'right', + textBaseline: 'top' + }, + insideStart: { textAlign: 'left', textBaseline: 'middle' }, insideStartTop: { - textAlign: 'right', + textAlign: 'left', textBaseline: 'bottom' }, insideStartBottom: { - textAlign: 'right', + textAlign: 'left', textBaseline: 'top' }, @@ -124,6 +161,18 @@ export const DEFAULT_CARTESIAN_MARK_LINE_TEXT_STYLE_MAP: { textAlign: 'right', textBaseline: 'middle' }, + endTop: { + textAlign: 'right', + textBaseline: 'bottom' + }, + endBottom: { + textAlign: 'right', + textBaseline: 'top' + }, + insideEnd: { + textAlign: 'left', + textBaseline: 'middle' + }, insideEndTop: { textAlign: 'left', textBaseline: 'bottom' diff --git a/packages/vrender-components/src/marker/line.ts b/packages/vrender-components/src/marker/line.ts index f96a7f775..ba3b9d78e 100644 --- a/packages/vrender-components/src/marker/line.ts +++ b/packages/vrender-components/src/marker/line.ts @@ -10,10 +10,10 @@ import type { ArcSegment } from '../segment'; // eslint-disable-next-line no-duplicate-imports import { Segment } from '../segment'; import { DEFAULT_STATES } from '../constant'; -import { DEFAULT_CARTESIAN_MARK_LINE_TEXT_STYLE_MAP, DEFAULT_MARK_LINE_THEME } from './config'; +import { DEFAULT_CARTESIAN_MARK_LINE_TEXT_STYLE_MAP, DEFAULT_MARK_LINE_THEME, FUZZY_EQUAL_DELTA } from './config'; import type { ILineGraphicAttribute } from '@visactor/vrender-core'; import { markCommonLineAnimate } from './animate/animate'; -import { isPostiveXAxisCartes } from '../util'; +import { fuzzyEqualNumber, getTextAlignAttrOfVerticalDir, isPostiveXAxis } from '../util'; loadMarkLineComponent(); @@ -43,7 +43,7 @@ export class MarkLine extends MarkCommonLine 0 && position.includes('inside')) || (labelAngle < 0 && !position.includes('inside')) - ? 'bottom' - : 'top' - }; + if ( + fuzzyEqualNumber(Math.abs(labelAngle), Math.PI / 2, FUZZY_EQUAL_DELTA) || + fuzzyEqualNumber(Math.abs(labelAngle), (Math.PI * 3) / 2, FUZZY_EQUAL_DELTA) + ) { + return getTextAlignAttrOfVerticalDir(autoRotate, labelAngle, position); } - if (isPostiveXAxisCartes(labelAngle)) { + if (isPostiveXAxis(labelAngle)) { return DEFAULT_CARTESIAN_MARK_LINE_TEXT_STYLE_MAP.postiveXAxis[position]; } return DEFAULT_CARTESIAN_MARK_LINE_TEXT_STYLE_MAP.negativeXAxis[position]; diff --git a/packages/vrender-components/src/marker/point.ts b/packages/vrender-components/src/marker/point.ts index 60959a664..31cd5ba60 100644 --- a/packages/vrender-components/src/marker/point.ts +++ b/packages/vrender-components/src/marker/point.ts @@ -19,7 +19,7 @@ import type { TagAttributes } from '../tag'; // eslint-disable-next-line no-duplicate-imports import { Tag } from '../tag'; import { Marker } from './base'; -import { DEFAULT_MARK_POINT_TEXT_STYLE_MAP, DEFAULT_MARK_POINT_THEME } from './config'; +import { DEFAULT_MARK_POINT_TEXT_STYLE_MAP, DEFAULT_MARK_POINT_THEME, FUZZY_EQUAL_DELTA } from './config'; import type { IItemContent, IItemLine, MarkPointAnimationType, MarkPointAttrs, MarkerAnimationState } from './type'; // eslint-disable-next-line no-duplicate-imports import { IMarkPointItemPosition } from './type'; @@ -29,7 +29,13 @@ import { loadMarkPointComponent } from './register'; import { computeOffsetForlimit } from '../util/limit-shape'; import { DEFAULT_STATES } from '../constant'; import { DefaultExitMarkerAnimation, DefaultUpdateMarkPointAnimation, markPointAnimate } from './animate/animate'; -import { deltaXYToAngle, isPostiveXAxisCartes, isPostiveXAxisPolar, removeRepeatPoint } from '../util'; +import { + deltaXYToAngle, + fuzzyEqualNumber, + getTextAlignAttrOfVerticalDir, + isPostiveXAxis, + removeRepeatPoint +} from '../util'; loadMarkPointComponent(); @@ -56,7 +62,8 @@ export class MarkPoint extends Marker { private _line?: Segment; private _decorativeLine!: ILine; - private _isArcLine: boolean = false; + private _isArcLine: boolean = false; // 用于区分 arc-segment 和 segment + private _isStraightLine: boolean = false; // 用于区分绘制 纯直线 和 折线,(type-do/op/po时, 如果偏移量很小, 视觉无法分辨, 也需要绘制成直线) constructor(attributes: MarkPointAttrs, options?: ComponentOptions) { super(options?.skipDefault ? attributes : merge({}, MarkPoint.defaultAttributes, attributes)); @@ -73,14 +80,15 @@ export class MarkPoint extends Marker { lineEndAngle: number, itemPosition: keyof typeof IMarkPointItemPosition ) { - let isPostiveXAxis = true; - if (this._isArcLine) { - isPostiveXAxis = isPostiveXAxisPolar(lineEndAngle, (this._line as ArcSegment).isReverseArc); - } else { - isPostiveXAxis = isPostiveXAxisCartes(lineEndAngle); + // 垂直方向例外 + if ( + fuzzyEqualNumber(Math.abs(lineEndAngle), Math.PI / 2, FUZZY_EQUAL_DELTA) || + fuzzyEqualNumber(Math.abs(lineEndAngle), (Math.PI * 3) / 2, FUZZY_EQUAL_DELTA) + ) { + return getTextAlignAttrOfVerticalDir(autoRotate, lineEndAngle, itemPosition); } - if (isPostiveXAxis) { + if (isPostiveXAxis(lineEndAngle)) { return DEFAULT_MARK_POINT_TEXT_STYLE_MAP.postiveXAxis[itemPosition]; } return DEFAULT_MARK_POINT_TEXT_STYLE_MAP.negativeXAxis[itemPosition]; @@ -108,8 +116,8 @@ export class MarkPoint extends Marker { } = itemContent; const { state } = this.attribute as MarkPointAttrs; const lineEndAngle = this._line?.getEndAngle() || 0; - const itemRefOffsetX = refX * Math.cos(lineEndAngle) + refY * Math.cos(lineEndAngle); - const itemRefOffsetY = refX * Math.sin(lineEndAngle) + refY * Math.sin(lineEndAngle); + const itemRefOffsetX = refX * Math.cos(lineEndAngle) + refY * Math.cos(lineEndAngle - Math.PI / 2); + const itemRefOffsetY = refX * Math.sin(lineEndAngle) + refY * Math.sin(lineEndAngle - Math.PI / 2); if (itemType === 'text') { const offsetX = newItemPosition.x - newPosition.x; const offsetY = newItemPosition.y - newPosition.y; @@ -144,16 +152,7 @@ export class MarkPoint extends Marker { item.states = merge({}, DEFAULT_STATES, state?.image); } - let isPostiveXAxis = true; - let itemAngle; - if (this._isArcLine) { - isPostiveXAxis = isPostiveXAxisPolar(lineEndAngle, (this._line as ArcSegment).isReverseArc); - // 文字翻转 - itemAngle = isPostiveXAxis ? lineEndAngle : lineEndAngle - Math.PI; - } else { - isPostiveXAxis = isPostiveXAxisCartes(lineEndAngle); - itemAngle = isPostiveXAxis ? lineEndAngle : lineEndAngle - Math.PI; - } + const itemAngle = isPostiveXAxis(lineEndAngle) ? lineEndAngle : lineEndAngle - Math.PI; item.setAttributes({ x: newItemPosition.x + (itemRefOffsetX || 0), @@ -238,6 +237,11 @@ export class MarkPoint extends Marker { let startAngle = 0; let endAngle = 0; const { type = 'type-s', arcRatio = 0.8 } = itemLine; + // confine之后位置会变化,所以这里需要重新check是否是直线 + const itemOffsetX = newItemPosition.x - newPosition.x; + const itemOffsetY = newItemPosition.y - newPosition.y; + this._isStraightLine = + fuzzyEqualNumber(itemOffsetX, 0, FUZZY_EQUAL_DELTA) || fuzzyEqualNumber(itemOffsetY, 0, FUZZY_EQUAL_DELTA); if (this._isArcLine) { const { x: x1, y: y1 } = newPosition; const { x: x2, y: y2 } = newItemPosition; @@ -254,12 +258,26 @@ export class MarkPoint extends Marker { const deltaX = arcRatio * direction * x0; // 数值决定曲率, 符号决定法向, 可通过配置自定义 const centerX = x0 + deltaX; const centerY = line(centerX); - center = { x: centerX, y: centerY }; startAngle = deltaXYToAngle(y1 - centerY, x1 - centerX); endAngle = deltaXYToAngle(y2 - centerY, x2 - centerX); + center = { x: centerX, y: centerY }; + + if (arcRatio > 0) { + // 此时绘制凹圆弧, 顺时针绘制 + // 根据arc图元绘制逻辑, 需要保证endAngle > startAngle, 才能顺时针绘制 + if (endAngle < startAngle) { + endAngle += Math.PI * 2; + } + } else { + // 此时绘制凸圆弧, 顺时针绘制 + // 根据arc图元绘制逻辑, 需要保证endAngle < startAngle, 才能逆时针绘制 + if (startAngle < endAngle) { + startAngle += Math.PI * 2; + } + } radius = Math.sqrt((centerX - x1) * (centerX - x1) + (centerY - y1) * (centerY - y1)); - } else if (type === 'type-do') { + } else if (type === 'type-do' && !this._isStraightLine) { points = [ newPosition, { @@ -268,7 +286,7 @@ export class MarkPoint extends Marker { }, newItemPosition ]; - } else if (type === 'type-po') { + } else if (type === 'type-po' && !this._isStraightLine) { points = [ newPosition, { @@ -277,7 +295,7 @@ export class MarkPoint extends Marker { }, newItemPosition ]; - } else if (type === 'type-op') { + } else if (type === 'type-op' && !this._isStraightLine) { points = [ newPosition, { @@ -327,10 +345,7 @@ export class MarkPoint extends Marker { const { startSymbol, endSymbol, lineStyle, type = 'type-s' } = itemLine; const { state } = this.attribute as MarkPointAttrs; const pointsAttr = this.getItemLineAttr(itemLine, newPosition, newItemPosition); - if ( - (type === 'type-arc' && this._line.key === 'arc-segment') || - (type !== 'type-arc' && this._line.key === 'segment') - ) { + if ((this._isArcLine && this._line.key === 'arc-segment') || (!this._isArcLine && this._line.key === 'segment')) { this._line.setAttributes({ ...pointsAttr, startSymbol, @@ -432,12 +447,12 @@ export class MarkPoint extends Marker { const targetSize = targetItemvisible ? targetSymbolSize || (targetSymbolStyle.size ?? 10) : 0; const targetOffsetAngle = deltaXYToAngle(itemContentOffsetY, itemContentOffsetX); const newPosition: Point = { - x: position.x + (targetSize + targetSymbolOffset) * Math.cos(targetOffsetAngle), - y: position.y + (targetSize + targetSymbolOffset) * Math.sin(targetOffsetAngle) + x: position.x + (targetSize / 2 + targetSymbolOffset) * Math.cos(targetOffsetAngle), + y: position.y + (targetSize / 2 + targetSymbolOffset) * Math.sin(targetOffsetAngle) }; const newItemPosition: Point = { - x: position.x + (targetSize + targetSymbolOffset) * Math.cos(targetOffsetAngle) + itemContentOffsetX, // 偏移量 = targetItem size + targetItem space + 用户配置offset - y: position.y + (targetSize + targetSymbolOffset) * Math.sin(targetOffsetAngle) + itemContentOffsetY // 偏移量 = targetItem size + targetItem space + 用户配置offset + x: position.x + (targetSize / 2 + targetSymbolOffset) * Math.cos(targetOffsetAngle) + itemContentOffsetX, // 偏移量 = targetItem size + targetItem space + 用户配置offset + y: position.y + (targetSize / 2 + targetSymbolOffset) * Math.sin(targetOffsetAngle) + itemContentOffsetY // 偏移量 = targetItem size + targetItem space + 用户配置offset }; return { newPosition, newItemPosition }; @@ -446,16 +461,16 @@ export class MarkPoint extends Marker { protected initMarker(container: IGroup) { const { position, itemContent = {}, itemLine } = this.attribute as MarkPointAttrs; const { type: itemLineType = 'type-s', arcRatio = 0.8 } = itemLine; + const { offsetX = 0, offsetY = 0 } = itemContent; + + this._isStraightLine = + fuzzyEqualNumber(offsetX, 0, FUZZY_EQUAL_DELTA) || fuzzyEqualNumber(offsetY, 0, FUZZY_EQUAL_DELTA); + this._isArcLine = itemLineType === 'type-arc' && arcRatio !== 0 && !this._isStraightLine; /** 根据targetItem计算新的弧线起点 */ const { newPosition, newItemPosition } = this.computeNewPositionAfterTargetItem(position); /** itemline - 连接线 */ - this._isArcLine = - itemLineType === 'type-arc' && - arcRatio !== 0 && - newPosition.x !== newItemPosition.x && - newPosition.y !== newItemPosition.y; const lineConstructor = this._isArcLine ? ArcSegment : Segment; const line = new lineConstructor({ @@ -500,13 +515,14 @@ export class MarkPoint extends Marker { const { position, itemContent = {}, itemLine } = this.attribute as MarkPointAttrs; const { type = 'text' } = itemContent; const { type: itemLineType = 'type-s', arcRatio = 0.8 } = itemLine; + const { offsetX = 0, offsetY = 0 } = itemContent; + + this._isStraightLine = + fuzzyEqualNumber(offsetX, 0, FUZZY_EQUAL_DELTA) || fuzzyEqualNumber(offsetY, 0, FUZZY_EQUAL_DELTA); + const isArcLine = itemLineType === 'type-arc' && arcRatio !== 0 && !this._isStraightLine; /** 根据targetItem计算新的弧线起点 */ const { newPosition, newItemPosition } = this.computeNewPositionAfterTargetItem(position); - const isArcLine = - itemLineType === 'type-arc' && - arcRatio !== 0 && - newPosition.x !== newItemPosition.x && - newPosition.y !== newItemPosition.y; + if (isArcLine !== this._isArcLine) { // 如果曲线和直线相互切换了, 则需要重新绘制line this._isArcLine = isArcLine; diff --git a/packages/vrender-components/src/marker/type.ts b/packages/vrender-components/src/marker/type.ts index b7eba0dad..8fb0350b3 100644 --- a/packages/vrender-components/src/marker/type.ts +++ b/packages/vrender-components/src/marker/type.ts @@ -19,12 +19,20 @@ import type { Point, State } from '../core/type'; export enum IMarkLineLabelPosition { start = 'start', - middle = 'middle', - end = 'end', + startTop = 'startTop', + startBottom = 'startBottom', + insideStart = 'insideStart', insideStartTop = 'insideStartTop', insideStartBottom = 'insideStartBottom', + + middle = 'middle', insideMiddleTop = 'insideMiddleTop', insideMiddleBottom = 'insideMiddleBottom', + + end = 'end', + endTop = 'endTop', + endBottom = 'endBottom', + insideEnd = 'insideEnd', insideEndTop = 'insideEndTop', insideEndBottom = 'insideEndBottom' } diff --git a/packages/vrender-components/src/segment/arc-segment.ts b/packages/vrender-components/src/segment/arc-segment.ts index 435c426b6..d760249cc 100644 --- a/packages/vrender-components/src/segment/arc-segment.ts +++ b/packages/vrender-components/src/segment/arc-segment.ts @@ -26,16 +26,24 @@ export class ArcSegment extends Segment { * 外部获取segment起点切线正方向 */ getStartAngle() { - const startAngle = this.isReverseArc ? this._startAngle + Math.PI / 2 : this._startAngle - Math.PI / 2; - return startAngle > Math.PI * 2 ? startAngle - Math.PI * 2 : startAngle; + // 如果是顺时针弧, start切线方向 = 弧度方向 - Math.PI / 2, 反之相反 + const tangAng = this.isReverseArc ? this._startAngle + Math.PI / 2 : this._startAngle - Math.PI / 2; + + // 经过刚刚的计算角度范围: [0, 360] => [-90, 270] 或 [0, 450] + // 将其规范范围到[0, 360] + return tangAng < 0 ? tangAng + Math.PI * 2 : tangAng > Math.PI * 2 ? tangAng - Math.PI * 2 : tangAng; } /** * 外部获取segment终点切线正方向 */ getEndAngle() { - const endAngle = this.isReverseArc ? this._endAngle - Math.PI / 2 : this._endAngle + Math.PI / 2; - return endAngle > Math.PI * 2 ? endAngle - Math.PI * 2 : endAngle; + // 如果是顺时针弧, end切线方向 = 弧度方向 + Math.PI / 2, 反之相反 + const tangAng = this.isReverseArc ? this._endAngle - Math.PI / 2 : this._endAngle + Math.PI / 2; + + // 经过刚刚的计算角度范围: [0, 360] => [-90, 270] 或 [0, 450] + // 将其规范范围到[0, 360] + return tangAng < 0 ? tangAng + Math.PI * 2 : tangAng > Math.PI * 2 ? tangAng - Math.PI * 2 : tangAng; } getMainSegmentPoints() { @@ -94,6 +102,8 @@ export class ArcSegment extends Segment { const line = graphicCreator.arc({ x: center.x, y: center.y, + // startAngle: Math.PI + 0.5, + // endAngle: 0, startAngle, endAngle, innerRadius: radius, diff --git a/packages/vrender-components/src/segment/segment.ts b/packages/vrender-components/src/segment/segment.ts index 5a1ca7217..1b5cdc761 100644 --- a/packages/vrender-components/src/segment/segment.ts +++ b/packages/vrender-components/src/segment/segment.ts @@ -10,6 +10,7 @@ import type { ILineGraphicWithCornerRadius, SegmentAttributes, SymbolAttributes import type { Point } from '../core/type'; import type { ComponentOptions } from '../interface'; import { loadSegmentComponent } from './register'; +import { normalizeAngle } from '@visactor/vutils'; loadSegmentComponent(); export class Segment extends AbstractComponent> { @@ -25,7 +26,7 @@ export class Segment extends AbstractComponent> { * 外部获取segment起点正方向 */ getStartAngle() { - return this._startAngle; + return normalizeAngle(this._startAngle); } protected _endAngle!: number; @@ -33,7 +34,7 @@ export class Segment extends AbstractComponent> { * 外部获取segment终点正方向 */ getEndAngle() { - return this._endAngle; + return normalizeAngle(this._endAngle); } protected _mainSegmentPoints: Point[]; // 组成主线段的点 @@ -174,8 +175,8 @@ export class Segment extends AbstractComponent> { const { autoRotate = true } = attribute; let symbol; if (attribute && attribute.visible) { - const startAngle = this._startAngle; - const endAngle = this._endAngle; + const startAngle = this.getStartAngle(); + const endAngle = this.getEndAngle(); const { state } = this.attribute as SegmentAttributes; const start = points[0]; const end = points[points.length - 1]; @@ -191,14 +192,14 @@ export class Segment extends AbstractComponent> { start.y + (isValidNumber(startAngle) ? refX * Math.sin(startAngle) + refY * Math.sin(startAngle - Math.PI / 2) : 0) }; - rotate = this._computeStartRotate(startAngle); // @chensiji - 加Math.PI / 2是因为:默认symbol的包围盒垂直于line,所以在做自动旋转时需要在line正方向基础上做90度偏移 + rotate = this._computeStartRotate(this._startAngle); // @chensiji - 加Math.PI / 2是因为:默认symbol的包围盒垂直于line,所以在做自动旋转时需要在line正方向基础上做90度偏移 } else { position = { x: end.x + (isValidNumber(endAngle) ? refX * Math.cos(endAngle) + refY * Math.cos(endAngle - Math.PI / 2) : 0), y: end.y + (isValidNumber(endAngle) ? refX * Math.sin(endAngle) + refY * Math.sin(endAngle - Math.PI / 2) : 0) }; - rotate = this._computeEndRotate(endAngle); + rotate = this._computeEndRotate(this._endAngle); } symbol = graphicCreator.symbol({ diff --git a/packages/vrender-components/src/util/common.ts b/packages/vrender-components/src/util/common.ts index 0aa4f16ec..0df80199a 100644 --- a/packages/vrender-components/src/util/common.ts +++ b/packages/vrender-components/src/util/common.ts @@ -4,6 +4,7 @@ import type { IGraphicAttribute, IGraphic, IGroup } from '@visactor/vrender-core'; import { isNil } from '@visactor/vutils'; import type { Point } from '../core/type'; +import type { IMarkLineLabelPosition, IMarkPointItemPosition } from '../marker'; export function traverseGroup(group: IGraphic, cb: (node: IGraphic) => boolean | void) { group.forEachChildren(node => { @@ -53,13 +54,41 @@ export function removeRepeatPoint(points: Point[]) { return result; } -export function isPostiveXAxisCartes(angle: number) { - return angle > -Math.PI / 2 && angle < Math.PI / 2; +export function isPostiveXAxis(angle: number) { + return (angle >= 0 && angle < Math.PI / 2) || (angle > (Math.PI * 3) / 2 && angle <= Math.PI * 2); } -export function isPostiveXAxisPolar(angle: number, isReverse: boolean) { - if (isReverse) { - return (angle > 0 && angle < Math.PI / 2) || (angle < 0 && angle > -Math.PI * 2); +export function fuzzyEqualNumber(a: number, b: number, delta: number): boolean { + return Math.abs(a - b) < delta; +} + +export function getTextAlignAttrOfVerticalDir( + autoRotate: boolean, + lineEndAngle: number, + itemPosition: IMarkLineLabelPosition | keyof typeof IMarkPointItemPosition +) { + if (autoRotate) { + return { + textAlign: 'right', + textBaseline: 'middle' + }; } - return (angle > 0 && angle < Math.PI / 2) || (angle > (Math.PI * 3) / 2 && angle < Math.PI * 2); + return { + textAlign: + // left: 90度方向, 即笛卡尔坐标系y轴负方向 + top 或 270度方向, 即笛卡尔坐标系y轴正方向 + bottom + (lineEndAngle < Math.PI && itemPosition.toLocaleLowerCase().includes('top')) || + (lineEndAngle > Math.PI && itemPosition.toLocaleLowerCase().includes('bottom')) + ? 'left' + : // right: 90度方向, 即笛卡尔坐标系y轴负方向 + bottom 或 270度方向, 即笛卡尔坐标系y轴正方向 + top + (lineEndAngle < Math.PI && itemPosition.toLocaleLowerCase().includes('bottom')) || + (lineEndAngle > Math.PI && itemPosition.toLocaleLowerCase().includes('top')) + ? 'right' + : 'center', + textBaseline: + // bottom: 90度方向, 即笛卡尔坐标系y轴负方向 + inside 或 270度方向, 即笛卡尔坐标系y轴正方向 + outside + (lineEndAngle < Math.PI && itemPosition.includes('inside')) || + (lineEndAngle > Math.PI && !itemPosition.includes('inside')) + ? 'bottom' + : 'top' + }; }