diff --git a/common/changes/@visactor/vrender-components/develop_2024-01-04-08-08.json b/common/changes/@visactor/vrender-components/develop_2024-01-04-08-08.json new file mode 100644 index 000000000..1b9ab7d2a --- /dev/null +++ b/common/changes/@visactor/vrender-components/develop_2024-01-04-08-08.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "fix(marker): fix problem of no render when set visible attr. fix@Visactor/Vchart#1901", + "type": "none" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/common/changes/@visactor/vrender-components/feat-mark-point-confine_2024-01-05-07-32.json b/common/changes/@visactor/vrender-components/feat-mark-point-confine_2024-01-05-07-32.json new file mode 100644 index 000000000..611773cca --- /dev/null +++ b/common/changes/@visactor/vrender-components/feat-mark-point-confine_2024-01-05-07-32.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "feat(marker): mark point support confine. fix @Visactor/VChart#1573", + "type": "none" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-04-13-20.json b/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-04-13-20.json new file mode 100644 index 000000000..0e89cf9e1 --- /dev/null +++ b/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-04-13-20.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "fix(datazoom): adaptive handler text layout. fix@Visactor/VChart#1809", + "type": "none" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-04-14-32.json b/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-04-14-32.json new file mode 100644 index 000000000..d852b7208 --- /dev/null +++ b/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-04-14-32.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "fix(datazoom): set pickable false when zoomLock. fix @Visactor/VChart#1565", + "type": "none" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-05-06-54.json b/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-05-06-54.json new file mode 100644 index 000000000..ee411189e --- /dev/null +++ b/common/changes/@visactor/vrender-components/fix-datazoom-text-layout_2024-01-05-06-54.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-components", + "comment": "fix(datazoom): handler not follow mouse after resize. fix@Visactor/Vchart#1490", + "type": "none" + } + ], + "packageName": "@visactor/vrender-components" +} \ No newline at end of file diff --git a/packages/vrender-components/__tests__/browser/examples/mark-point.ts b/packages/vrender-components/__tests__/browser/examples/mark-point.ts index 322a9a963..983d0d36a 100644 --- a/packages/vrender-components/__tests__/browser/examples/mark-point.ts +++ b/packages/vrender-components/__tests__/browser/examples/mark-point.ts @@ -19,7 +19,8 @@ export function run() { itemRefX: 10, itemRefY: 0, itemRefAngle: 0, - decorativeLineVisible: false + decorativeLineVisible: false, + visible: true }; const styleAttr = { @@ -34,6 +35,7 @@ export function run() { refX: guiObject.itemRefX, refY: guiObject.itemRefY, refAngle: guiObject.itemRefAngle, + confine: true, textStyle: { // text: 'mark point label text' type: 'rich', @@ -108,6 +110,7 @@ export function run() { image: `${window.location.origin}/__tests__/browser/sources/shape_logo.png` } }, + visible: guiObject.visible, clipInRange: false // limitRect: { // x: 50, @@ -195,6 +198,9 @@ export function run() { }) ); }); + gui.add(guiObject, 'visible').onChange(value => { + markPoints.forEach(markPoint => markPoint.setAttribute('visible', value, true)); + }); gui.add(guiObject, 'itemOffsetX').onChange(value => { markPoints.forEach(markPoint => diff --git a/packages/vrender-components/__tests__/unit/marker/point.test.ts b/packages/vrender-components/__tests__/unit/marker/point.test.ts index aa8568aaa..b79887b9f 100644 --- a/packages/vrender-components/__tests__/unit/marker/point.test.ts +++ b/packages/vrender-components/__tests__/unit/marker/point.test.ts @@ -60,8 +60,8 @@ describe('Marker', () => { expect((markPointContainer.children[0] as unknown as Segment).startSymbol?.attribute.y).toBe(250); // tag构造的label - expect((markPointContainer.children[2] as unknown as Tag).attribute.x).toBeCloseTo(209.5782628522115); - expect((markPointContainer.children[2] as unknown as Tag).attribute.y).toBeCloseTo(282.87347885566345); + expect((markPointContainer.children[2] as unknown as Tag).attribute.x).toBeCloseTo(210); + expect((markPointContainer.children[2] as unknown as Tag).attribute.y).toBeCloseTo(280); expect( ((markPointContainer.children[2] as unknown as Tag).getChildByName('tag-content') as any).children[0].attribute .text diff --git a/packages/vrender-components/src/data-zoom/data-zoom.ts b/packages/vrender-components/src/data-zoom/data-zoom.ts index 814f3273b..2b1c9e0a8 100644 --- a/packages/vrender-components/src/data-zoom/data-zoom.ts +++ b/packages/vrender-components/src/data-zoom/data-zoom.ts @@ -1,7 +1,18 @@ -import type { FederatedPointerEvent, IArea, IGroup, ILine, IRect, ISymbol, INode } from '@visactor/vrender-core'; +import type { + FederatedPointerEvent, + IArea, + IGroup, + ILine, + IRect, + ISymbol, + INode, + ITextGraphicAttribute, + IText +} from '@visactor/vrender-core'; // eslint-disable-next-line no-duplicate-imports import { vglobal } from '@visactor/vrender-core'; -import type { IPointLike } from '@visactor/vutils'; +import type { IBoundsLike, IPointLike, ITextMeasureSpec } from '@visactor/vutils'; +import { TextMeasure } from '@visactor/vutils'; // eslint-disable-next-line no-duplicate-imports import { array, clamp, debounce, isFunction, isValid, merge, throttle } from '@visactor/vutils'; import { AbstractComponent } from '../core/base'; @@ -90,30 +101,28 @@ export class DataZoom extends AbstractComponent> { private _statePointToData: (state: number) => any = state => state; private _layoutAttrFromConfig: any; // 用于缓存 - constructor(attributes: DataZoomAttributes, options?: ComponentOptions) { - super(options?.skipDefault ? attributes : merge({}, DataZoom.defaultAttributes, attributes)); - const { - start, - end, - size, - orient, - showDetail, - position, - previewData, - previewPointsX, - previewPointsY, - previewPointsX1, - previewPointsY1, - updateStateCallback - } = this.attribute as DataZoomAttributes; - const { width, height } = size; + setPropsFromAttrs() { + const { start, end, orient, previewData, previewPointsX, previewPointsY, previewPointsX1, previewPointsY1 } = this + .attribute as DataZoomAttributes; start && (this.state.start = start); end && (this.state.end = end); + const { width, height } = this.getLayoutAttrFromConfig(); this._spanCache = this.state.end - this.state.start; this._isHorizontal = orient === 'top' || orient === 'bottom'; this._layoutCache.max = this._isHorizontal ? width : height; this._layoutCache.attPos = this._isHorizontal ? 'x' : 'y'; this._layoutCache.attSize = this._isHorizontal ? 'width' : 'height'; + previewData && (this._previewData = previewData); + isFunction(previewPointsX) && (this._previewPointsX = previewPointsX); + isFunction(previewPointsY) && (this._previewPointsY = previewPointsY); + isFunction(previewPointsX1) && (this._previewPointsX1 = previewPointsX1); + isFunction(previewPointsY1) && (this._previewPointsY1 = previewPointsY1); + } + + constructor(attributes: DataZoomAttributes, options?: ComponentOptions) { + super(options?.skipDefault ? attributes : merge({}, DataZoom.defaultAttributes, attributes)); + const { position, showDetail } = attributes; + // 这些属性在事件交互过程中会改变,所以不能在setAttrs里面动态更改 this._activeCache.startPos = position; this._activeCache.lastPos = position; if (showDetail === 'auto') { @@ -121,11 +130,12 @@ export class DataZoom extends AbstractComponent> { } else { this._showText = showDetail as boolean; } - previewData && (this._previewData = previewData); - isFunction(previewPointsX) && (this._previewPointsX = previewPointsX); - isFunction(previewPointsY) && (this._previewPointsY = previewPointsY); - isFunction(previewPointsX1) && (this._previewPointsX1 = previewPointsX1); - isFunction(previewPointsY1) && (this._previewPointsY1 = previewPointsY1); + this.setPropsFromAttrs(); + } + + setAttributes(params: Partial>, forceUpdateTag?: boolean): void { + super.setAttributes(params, forceUpdateTag); + this.setPropsFromAttrs(); } protected bindEvents(): void { @@ -455,84 +465,155 @@ export class DataZoom extends AbstractComponent> { } } + /** + * 判断文字是否超出datazoom范围 + */ + protected isTextOverflow( + componentBoundsLike: IBoundsLike, + textPosition: IPointLike, + textMeasure: TextMeasure, + textValue: string | number | (string | number)[], + layout: 'start' | 'end' + ) { + const { width: textWidth, height: textHeight } = textMeasure.fullMeasure(textValue); + if (this._isHorizontal) { + if (layout === 'start') { + const x1 = textPosition.x - textWidth; + if (x1 < componentBoundsLike.x1) { + return true; + } + } else { + const x2 = textPosition.x + textWidth; + if (x2 > componentBoundsLike.x2) { + return true; + } + } + } else { + if (layout === 'start') { + const y1 = textPosition.y - textHeight; + if (y1 < componentBoundsLike.y1) { + return true; + } + } else { + const y2 = textPosition.y + textHeight; + if (y2 > componentBoundsLike.y2) { + return true; + } + } + } + return false; + } + protected renderText() { const { startTextStyle, endTextStyle } = this.attribute as DataZoomAttributes; - const { formatMethod: startTextFormat, ...restStartStyle } = startTextStyle; + const { formatMethod: startTextFormat, ...restStartTextStyle } = startTextStyle; const { formatMethod: endTextFormat, ...restEndTextStyle } = endTextStyle; const { start, end } = this.state; this._startValue = this._statePointToData(start); this._endValue = this._statePointToData(end); const { position, width, height } = this.getLayoutAttrFromConfig(); + const startTextValue = startTextFormat ? startTextFormat(this._startValue) : this._startValue; + const endTextValue = endTextFormat ? endTextFormat(this._endValue) : this._endValue; + const startTextMeasure = new TextMeasure({ + defaultFontParams: restStartTextStyle.textStyle as ITextGraphicAttribute + }); + const endTextMeasure = new TextMeasure({ + defaultFontParams: restEndTextStyle.textStyle as ITextGraphicAttribute + }); + const componentBoundsLike = { + x1: position.x, + y1: position.y, + x2: position.x + width, + y2: position.y + height + }; + let startTextPosition: IPointLike; + let endTextPosition: IPointLike; + let startTextAlignStyle: any; + let endTextAlignStyle: any; if (this._isHorizontal) { - // 起始文字 - this._startText = this.maybeAddLabel( - this._container, - merge({}, restStartStyle, { - text: startTextFormat ? startTextFormat(this._startValue) : this._startValue, - x: position.x + start * width, - y: position.y + height / 2, - visible: this._showText, - pickable: false, - childrenPickable: false, - textStyle: { - textAlign: 'right', - textBaseline: 'middle' - } - }), - `data-zoom-start-text-${position}` - ); - this._endText = this.maybeAddLabel( - this._container, - merge({}, restEndTextStyle, { - text: endTextFormat ? endTextFormat(this._endValue) : this._endValue, - x: position.x + end * width, - y: position.y + height / 2, - visible: this._showText, - pickable: false, - childrenPickable: false, - textStyle: { - textAlign: 'left', - textBaseline: 'middle' - } - }), - `data-zoom-end-text-${position}` - ); + startTextPosition = { + x: position.x + start * width, + y: position.y + height / 2 + }; + endTextPosition = { + x: position.x + end * width, + y: position.y + height / 2 + }; + startTextAlignStyle = { + textAlign: this.isTextOverflow( + componentBoundsLike, + startTextPosition, + startTextMeasure, + startTextValue, + 'start' + ) + ? 'left' + : 'right', + textBaseline: 'middle' + }; + endTextAlignStyle = { + textAlign: this.isTextOverflow(componentBoundsLike, endTextPosition, endTextMeasure, endTextValue, 'end') + ? 'right' + : 'left', + textBaseline: 'middle' + }; } else { - // 起始文字 - this._startText = this.maybeAddLabel( - this._container, - merge({}, restStartStyle, { - text: startTextFormat ? startTextFormat(this._startValue) : this._startValue, - x: position.x + width / 2, - y: position.y + start * height, - visible: this._showText, - pickable: false, - childrenPickable: false, - textStyle: { - textAlign: 'center', - textBaseline: 'bottom' - } - }), - `data-zoom-start-text-${position}` - ); - this._endText = this.maybeAddLabel( - this._container, - merge({}, restEndTextStyle, { - text: endTextFormat ? endTextFormat(this._endValue) : this._endValue, - x: position.x + width / 2, - y: position.y + end * height, - visible: this._showText, - pickable: false, - childrenPickable: false, - textStyle: { - textAlign: 'center', - textBaseline: 'top' - } - }), - `data-zoom-end-text-${position}` - ); + startTextPosition = { + x: position.x + width / 2, + y: position.y + start * height + }; + endTextPosition = { + x: position.x + width / 2, + y: position.y + end * height + }; + startTextAlignStyle = { + textAlign: 'center', + textBaseline: this.isTextOverflow( + componentBoundsLike, + startTextPosition, + startTextMeasure, + startTextValue, + 'start' + ) + ? 'top' + : 'bottom' + }; + endTextAlignStyle = { + textAlign: 'center', + textBaseline: this.isTextOverflow(componentBoundsLike, endTextPosition, endTextMeasure, endTextValue, 'end') + ? 'bottom' + : 'top' + }; } + + // 起始文字 + this._startText = this.maybeAddLabel( + this._container, + merge({}, restStartTextStyle, { + text: startTextValue, + x: startTextPosition.x, + y: startTextPosition.y, + visible: this._showText, + pickable: false, + childrenPickable: false, + textStyle: startTextAlignStyle + }), + `data-zoom-start-text-${position}` + ); + this._endText = this.maybeAddLabel( + this._container, + merge({}, restEndTextStyle, { + text: endTextValue, + x: endTextPosition.x, + y: endTextPosition.y, + visible: this._showText, + pickable: false, + childrenPickable: false, + textStyle: endTextAlignStyle + }), + `data-zoom-end-text-${position}` + ); } /** @@ -620,7 +701,8 @@ export class DataZoom extends AbstractComponent> { middleHandlerStyle = {}, startHandlerStyle = {}, endHandlerStyle = {}, - brushSelect + brushSelect, + zoomLock } = this.attribute as DataZoomAttributes; const { start, end } = this.state; const { position, width, height } = this.getLayoutAttrFromConfig(); @@ -636,7 +718,8 @@ export class DataZoom extends AbstractComponent> { width, height, cursor: brushSelect ? 'crosshair' : 'auto', - ...backgroundStyle + ...backgroundStyle, + pickable: !zoomLock }, 'rect' ) as IRect; @@ -659,7 +742,8 @@ export class DataZoom extends AbstractComponent> { width: (end - start) * width, height: height, cursor: brushSelect ? 'crosshair' : 'move', - ...selectedBackgroundStyle + ...selectedBackgroundStyle, + pickable: !zoomLock }, 'rect' ) as IRect; @@ -673,7 +757,8 @@ export class DataZoom extends AbstractComponent> { width, height: (end - start) * height, cursor: brushSelect ? 'crosshair' : 'move', - ...selectedBackgroundStyle + ...selectedBackgroundStyle, + pickable: !zoomLock }, 'rect' ) as IRect; @@ -699,7 +784,8 @@ export class DataZoom extends AbstractComponent> { y: position.y - middleHandlerBackgroundSize, width: (end - start) * width, height: middleHandlerBackgroundSize, - ...middleHandlerStyle.background?.style + ...middleHandlerStyle.background?.style, + pickable: !zoomLock }, 'rect' ) as IRect; @@ -711,7 +797,8 @@ export class DataZoom extends AbstractComponent> { strokeBoundsBuffer: 0, angle: 0, symbolType: middleHandlerStyle.icon?.symbolType ?? 'square', - ...middleHandlerStyle.icon + ...middleHandlerStyle.icon, + pickable: !zoomLock }, 'symbol' ) as ISymbol; @@ -724,7 +811,8 @@ export class DataZoom extends AbstractComponent> { size: height, symbolType: startHandlerStyle.symbolType ?? 'square', ...(DEFAULT_HANDLER_ATTR_MAP.horizontal as any), - ...startHandlerStyle + ...startHandlerStyle, + pickable: !zoomLock }, 'symbol' ) as ISymbol; @@ -736,7 +824,8 @@ export class DataZoom extends AbstractComponent> { size: height, symbolType: endHandlerStyle.symbolType ?? 'square', ...(DEFAULT_HANDLER_ATTR_MAP.horizontal as any), - ...endHandlerStyle + ...endHandlerStyle, + pickable: !zoomLock }, 'symbol' ) as ISymbol; @@ -757,7 +846,8 @@ export class DataZoom extends AbstractComponent> { fill: 'white', fillOpacity: 0, zIndex: 999, - ...(DEFAULT_HANDLER_ATTR_MAP.horizontal as any) + ...(DEFAULT_HANDLER_ATTR_MAP.horizontal as any), + pickable: !zoomLock }, 'rect' ) as IRect; @@ -771,7 +861,8 @@ export class DataZoom extends AbstractComponent> { fill: 'white', fillOpacity: 0, zIndex: 999, - ...(DEFAULT_HANDLER_ATTR_MAP.horizontal as any) + ...(DEFAULT_HANDLER_ATTR_MAP.horizontal as any), + pickable: !zoomLock }, 'rect' ) as IRect; @@ -786,7 +877,8 @@ export class DataZoom extends AbstractComponent> { y: position.y + start * height, width: middleHandlerBackgroundSize, height: (end - start) * height, - ...middleHandlerStyle.background?.style + ...middleHandlerStyle.background?.style, + pickable: !zoomLock }, 'rect' ) as IRect; @@ -802,7 +894,8 @@ export class DataZoom extends AbstractComponent> { angle: 90 * (Math.PI / 180), symbolType: middleHandlerStyle.icon?.symbolType ?? 'square', strokeBoundsBuffer: 0, - ...middleHandlerStyle.icon + ...middleHandlerStyle.icon, + pickable: !zoomLock }, 'symbol' ) as ISymbol; @@ -815,7 +908,8 @@ export class DataZoom extends AbstractComponent> { size: width, symbolType: startHandlerStyle.symbolType ?? 'square', ...(DEFAULT_HANDLER_ATTR_MAP.vertical as any), - ...startHandlerStyle + ...startHandlerStyle, + pickable: !zoomLock }, 'symbol' ) as ISymbol; @@ -828,7 +922,8 @@ export class DataZoom extends AbstractComponent> { size: width, symbolType: endHandlerStyle.symbolType ?? 'square', ...(DEFAULT_HANDLER_ATTR_MAP.vertical as any), - ...endHandlerStyle + ...endHandlerStyle, + pickable: !zoomLock }, 'symbol' ) as ISymbol; @@ -849,7 +944,8 @@ export class DataZoom extends AbstractComponent> { fill: 'white', fillOpacity: 0, zIndex: 999, - ...(DEFAULT_HANDLER_ATTR_MAP.vertical as any) + ...(DEFAULT_HANDLER_ATTR_MAP.vertical as any), + pickable: !zoomLock }, 'rect' ) as IRect; @@ -863,7 +959,8 @@ export class DataZoom extends AbstractComponent> { fill: 'white', fillOpacity: 0, zIndex: 999, - ...(DEFAULT_HANDLER_ATTR_MAP.vertical as any) + ...(DEFAULT_HANDLER_ATTR_MAP.vertical as any), + pickable: !zoomLock }, 'rect' ) as IRect; diff --git a/packages/vrender-components/src/marker/area.ts b/packages/vrender-components/src/marker/area.ts index f18fcd011..a21cfa5c7 100644 --- a/packages/vrender-components/src/marker/area.ts +++ b/packages/vrender-components/src/marker/area.ts @@ -1,6 +1,6 @@ import type { IGroup, INode, IPolygon } from '@visactor/vrender-core'; import { graphicCreator } from '@visactor/vrender-core'; -import { merge } from '@visactor/vutils'; +import { isValidNumber, merge } from '@visactor/vutils'; import type { TagAttributes } from '../tag'; import { Tag } from '../tag'; import { Marker } from './base'; @@ -9,6 +9,7 @@ import type { MarkAreaAttrs } from './type'; import { limitShapeInBounds } from '../util/limit-shape'; import type { ComponentOptions } from '../interface'; import { loadMarkAreaComponent } from './register'; +import type { Point } from '../core/type'; loadMarkAreaComponent(); export class MarkArea extends Marker { @@ -128,6 +129,13 @@ export class MarkArea extends Marker { if (!points || points.length < 3) { return false; } - return true; + let validFlag = true; + points.forEach((point: Point) => { + if (!isValidNumber((point as Point).x) || !isValidNumber((point as Point).y)) { + validFlag = false; + return; + } + }); + return validFlag; } } diff --git a/packages/vrender-components/src/marker/base.ts b/packages/vrender-components/src/marker/base.ts index 1ec3c381b..580bfd291 100644 --- a/packages/vrender-components/src/marker/base.ts +++ b/packages/vrender-components/src/marker/base.ts @@ -16,6 +16,13 @@ export abstract class Marker extends AbstractComponent extends AbstractComponent { @@ -125,6 +127,20 @@ export class MarkLine extends Marker { if (!points || points.length < 2) { return false; } - return true; + let validFlag = true; + points.forEach((point: Point | Point[]) => { + if ((point as any).length) { + (point as Point[]).forEach((p: Point) => { + if (!isValidNumber((p as Point).x) || !isValidNumber((p as Point).y)) { + validFlag = false; + return; + } + }); + } else if (!isValidNumber((point as Point).x) || !isValidNumber((point as Point).y)) { + validFlag = false; + return; + } + }); + return validFlag; } } diff --git a/packages/vrender-components/src/marker/point.ts b/packages/vrender-components/src/marker/point.ts index d89e91f5c..854311514 100644 --- a/packages/vrender-components/src/marker/point.ts +++ b/packages/vrender-components/src/marker/point.ts @@ -13,9 +13,10 @@ import type { import { graphicCreator } from '@visactor/vrender-core'; import type { IPointLike } from '@visactor/vutils'; // eslint-disable-next-line no-duplicate-imports -import { merge } from '@visactor/vutils'; +import { isValidNumber, merge } from '@visactor/vutils'; import { Segment } from '../segment'; 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'; @@ -25,6 +26,7 @@ import { IMarkPointItemPosition } from './type'; import type { Point } from '../core/type'; import type { ComponentOptions } from '../interface'; import { loadMarkPointComponent } from './register'; +import { computeOffsetForlimit } from '../util/limit-shape'; loadMarkPointComponent(); export class MarkPoint extends Marker { @@ -91,16 +93,6 @@ export class MarkPoint extends Marker { y: itemPosition.y + (itemOffsetY || 0), angle: autoRotate && itemAngle + refAngle }); - - // if (this.attribute.limitRect && this.attribute.itemContent?.confine) { - // const { x, y, width, height } = this.attribute.limitRect; - // limitShapeInBounds(item, { - // x1: x, - // y1: y, - // x2: x + width, - // y2: y + height - // }); - // } } protected getItemDx( @@ -251,8 +243,27 @@ export class MarkPoint extends Marker { } } + protected setAllOfItemsAttr(itemPosition: Point) { + const { position, itemLine = {}, itemContent = {}, limitRect } = this.attribute as MarkPointAttrs; + const { type = 'text', confine } = itemContent; + if (limitRect && confine) { + const { x, y, width, height } = limitRect; + const { dx, dy } = computeOffsetForlimit(this._item, { + x1: x, + y1: y, + x2: x + width, + y2: y + height + }); + itemPosition.x = itemPosition.x + dx; + itemPosition.y = itemPosition.y + dy; + } + this.setItemAttributes(this._item, itemContent, itemPosition, type); + this.setItemLineAttr(itemLine, position, itemPosition, itemLine.visible); + this.setDecorativeLineAttr(itemLine, itemPosition, itemLine.decorativeLine?.visible); + } + protected initMarker(container: IGroup) { - const { position, itemLine = {}, itemContent = {} } = this.attribute as MarkPointAttrs; + const { position, itemContent = {} } = this.attribute as MarkPointAttrs; const itemPosition = { x: position.x + (itemContent.offsetX || 0), y: position.y + (itemContent.offsetY || 0) @@ -273,27 +284,35 @@ export class MarkPoint extends Marker { this._decorativeLine = decorativeLine; container.add(decorativeLine as unknown as INode); - this.setItemLineAttr(itemLine, position, itemPosition, itemLine.visible); - this.setDecorativeLineAttr(itemLine, itemPosition, itemLine.decorativeLine?.visible); - + // 为了强制将itemContent限制在limitRect内, 所以需要先绘制item, 然后根据item bounds 动态调整位置 const item = this.initItem(itemContent as any, itemPosition); this._item = item; + + // 由于itemLine的指向也要变化, 所以需要对所有的内容进行渲染 + this.setAllOfItemsAttr(itemPosition); container.add(item as unknown as INode); } protected updateMarker() { - const { position, itemLine = {}, itemContent = {} } = this.attribute as MarkPointAttrs; + const { position, itemContent = {} } = this.attribute as MarkPointAttrs; const { type = 'text' } = itemContent; + const itemPosition = { x: position.x + (itemContent.offsetX || 0), y: position.y + (itemContent.offsetY || 0) }; - this.setItemLineAttr(itemLine, position, itemPosition, itemLine.visible); - this.setDecorativeLineAttr(itemLine, itemPosition, itemLine.decorativeLine?.visible); + + // 为了强制将itemContent限制在limitRect内, 所以需要先绘制item, 然后根据item bounds 动态调整位置 this.setItemAttributes(this._item, itemContent, itemPosition, type); + // 由于itemLine的指向也要变化, 所以需要对所有的内容进行渲染 + this.setAllOfItemsAttr(itemPosition); } protected isValidPoints() { - return true; + const { position } = this.attribute as MarkPointAttrs; + if (isValidNumber(position.x) && isValidNumber(position.y)) { + return true; + } + return false; } } diff --git a/packages/vrender-components/src/marker/type.ts b/packages/vrender-components/src/marker/type.ts index 69bde5e21..56e98ba9a 100644 --- a/packages/vrender-components/src/marker/type.ts +++ b/packages/vrender-components/src/marker/type.ts @@ -129,7 +129,8 @@ export type MarkLineAttrs = MarkerAttrs & */ position?: keyof typeof IMarkLineLabelPosition; /** - * 当 mark 配置了 clip 之后,label 是否自动调整位置 + * 当 mark 配置了 limitRect 之后,label 是否自动调整位置 + * @default false */ confine?: boolean; } & IMarkRef & @@ -148,7 +149,8 @@ export type MarkAreaAttrs = MarkerAttrs & { label?: { position?: keyof typeof IMarkAreaLabelPosition; /** - * 当 mark 配置了 clip 之后,label 是否自动调整位置 + * 当 mark 配置了 limitRect 之后,label 是否自动调整位置 + * @default false */ confine?: boolean; } & IMarkLabel; @@ -194,10 +196,11 @@ export type IItemContent = IMarkRef & { * type为custom时,允许以callback的方式传入需要render的item */ renderCustomCallback?: () => IGroup; - // /** - // * 当 mark 配置了 clip 之后,label 是否自动调整位置 - // */ - // confine?: boolean; + /** + * 当 mark 配置了 limitRect 之后,label 是否自动调整位置 + * @default false + */ + confine?: boolean; }; export type IItemLine = { diff --git a/packages/vrender-components/src/util/limit-shape.ts b/packages/vrender-components/src/util/limit-shape.ts index 823fac572..82e2781fc 100644 --- a/packages/vrender-components/src/util/limit-shape.ts +++ b/packages/vrender-components/src/util/limit-shape.ts @@ -1,10 +1,9 @@ import type { IGraphic } from '@visactor/vrender-core'; import type { IBoundsLike } from '@visactor/vutils'; -export function limitShapeInBounds(shape: IGraphic, bounds: IBoundsLike) { +export function computeOffsetForlimit(shape: IGraphic, bounds: IBoundsLike) { const { x1: regionMinX, y1: regionMinY, x2: regionMaxX, y2: regionMaxY } = bounds; const { x1, y1, x2, y2 } = shape.AABBBounds; - const { dx: originDx = 0, dy: originDy = 0 } = shape.attribute; let dx = 0; let dy = 0; @@ -26,6 +25,14 @@ export function limitShapeInBounds(shape: IGraphic, bounds: IBoundsLike) { // 整体超出顶部 dy = regionMaxY - y2; } + return { + dx, + dy + }; +} +export function limitShapeInBounds(shape: IGraphic, bounds: IBoundsLike) { + const { dx, dy } = computeOffsetForlimit(shape, bounds); + const { dx: originDx = 0, dy: originDy = 0 } = shape.attribute; if (dx) { shape.setAttribute('dx', dx + originDx); }