Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@visactor/vchart",
"comment": "fix: resolve title width/height/minWidth/maxWidth/minHeight/maxHeight as ILayoutNumber against the chart view rect",
"type": "patch"
}
],
"packageName": "@visactor/vchart",
"email": "chendaxin.tk@bytedance.com"
}
26 changes: 18 additions & 8 deletions docs/assets/option/en/component/title.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Chart title configuration.

Starting from version 2.1.7, `width`, `height`, `minWidth`, `maxWidth`, `minHeight` and `maxHeight` support `ILayoutNumber`. Earlier versions only support numeric values.

### visible(boolean) = true

Whether to display the title.
Expand Down Expand Up @@ -47,21 +49,29 @@ Optional values:
- 'right'
- 'bottom'

### minWidth(number)
### minWidth(ILayoutNumber)

Title's minimum width.

{{ use: common-layout-number }}

### maxWidth(ILayoutNumber)

Title's maximum width. When the text exceeds the maximum width, it will automatically be truncated.

Title's minimum width, in pixels.
{{ use: common-layout-number }}

### maxWidth(number)
### minHeight(ILayoutNumber)

Title's maximum width, in pixels. When the text exceeds the maximum width, it will automatically be truncated.
Title's minimum height.

### minHeight(number)
{{ use: common-layout-number }}

Title's minimum height, in pixels.
### maxHeight(ILayoutNumber)

### maxHeight(number)
Title's maximum height. When the text exceeds the maximum height, it will automatically be truncated.

Title's maximum height, in pixels.
{{ use: common-layout-number }}

### innerPadding(Object|number) = 0

Expand Down
26 changes: 18 additions & 8 deletions docs/assets/option/zh/component/title.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

图表标题配置。

从 2.1.7 版本开始,`width`、`height`、`minWidth`、`maxWidth`、`minHeight` 和 `maxHeight` 支持 `ILayoutNumber`;此前版本仅支持数值。

### visible(boolean) = true

是否显示标题。
Expand Down Expand Up @@ -47,21 +49,29 @@
- 'right'
- 'bottom'

### minWidth(number)
### minWidth(ILayoutNumber)

标题最小宽度。

{{ use: common-layout-number }}

### maxWidth(ILayoutNumber)

标题最大宽度。当文字超过最大宽度时,会自动省略。

标题最小宽度,像素值。
{{ use: common-layout-number }}

### maxWidth(number)
### minHeight(ILayoutNumber)

标题最大宽度,像素值。当文字超过最大宽度时,会自动省略。
标题最小高度。

### minHeight(number)
{{ use: common-layout-number }}

标题最小高度,像素值。
### maxHeight(ILayoutNumber)

### maxHeight(number)
标题最大高度。当文字超过最大高度时,会自动省略。

标题最大高度,像素值。
{{ use: common-layout-number }}

### innerPadding(Object|number) = 0

Expand Down
87 changes: 84 additions & 3 deletions packages/vchart/__tests__/unit/component/title/title.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,21 @@ const ctx: any = {
getChart: () => ({
getSpec: () => ({})
}),
getChartViewRect: () => ({ width: 800, height: 600 }),
getRegionsInIndex: (): any[] => []
};

const layoutRect = { width: 500, height: 500, x: 0, y: 0 };

const createTitle = (spec: any) => {
const title = new Title(spec as any, ctx);
title.created();
title.init({} as any);
return title;
};

const getTitleAttribute = (title: Title) => (title as any)._titleComponent.attribute;

describe('Title Component Repro', () => {
it('should not throw error when only subtext is set', () => {
const spec = {
Expand All @@ -35,11 +47,80 @@ describe('Title Component Repro', () => {
title.created();
title.init({});

// Simulate layout
const layoutRect = { width: 500, height: 500, x: 0, y: 0 };

expect(() => {
title.getBoundsInRect(layoutRect);
}).not.toThrow();
});
});

describe('Title size spec as ILayoutNumber', () => {
it('should resolve percent `maxHeight` against the chart view rect', () => {
const title = createTitle({ text: 'title', maxHeight: '50%' });
title.getBoundsInRect(layoutRect as any);

expect(getTitleAttribute(title).maxHeight).toBe(300);
});

it('should evaluate function `maxHeight` with the chart view rect', () => {
let received: any;
const title = createTitle({
text: 'title',
maxHeight: (rect: any) => {
received = rect;
return 72;
}
});
title.getBoundsInRect(layoutRect as any);

expect(getTitleAttribute(title).maxHeight).toBe(72);
expect(received).toEqual({ width: 800, height: 600 });
});

it('should resolve percent `minHeight` against the chart view rect', () => {
const title = createTitle({ text: 'title', minHeight: '10%' });
title.getBoundsInRect(layoutRect as any);

expect(getTitleAttribute(title).minHeight).toBe(60);
});

it('should take percent `height` as the layout height', () => {
const title = createTitle({ text: 'title', height: '25%' });
const bounds = title.getBoundsInRect(layoutRect as any);

expect(getTitleAttribute(title).height).toBe(150);
expect(bounds.y2 - bounds.y1).toBe(150);
});

it('should fold percent `maxWidth` once, against the chart view rect', () => {
const title = createTitle({ text: 'title', maxWidth: '30%' });
title.getBoundsInRect(layoutRect as any);

expect(getTitleAttribute(title).maxWidth).toBe(240);
});

it('should still clamp `maxWidth` by the layout rect', () => {
const title = createTitle({ text: 'title', maxWidth: '90%' });
title.getBoundsInRect(layoutRect as any);

expect(getTitleAttribute(title).maxWidth).toBe(500);
});

it('should keep numeric size config unchanged', () => {
const title = createTitle({ text: 'title', maxHeight: 60, minWidth: 100 });
title.getBoundsInRect(layoutRect as any);

expect(getTitleAttribute(title).maxHeight).toBe(60);
expect(getTitleAttribute(title).minWidth).toBe(100);
});

it('should leave unset size config undefined', () => {
const title = createTitle({ text: 'title' });
title.getBoundsInRect(layoutRect as any);

const attribute = getTitleAttribute(title);
expect(attribute.width).toBeUndefined();
expect(attribute.height).toBeUndefined();
expect(attribute.minHeight).toBeUndefined();
expect(attribute.maxHeight).toBeUndefined();
});
});
26 changes: 13 additions & 13 deletions packages/vchart/src/component/title/interface/spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IComponent } from '../../interface';
import type { ITextGraphicAttribute, IRichTextCharacter, ITextAttribute } from '@visactor/vrender-core';
import type { IOrientType, IPadding } from '../../../typings';
import type { ILayoutNumber, IOrientType, IPadding } from '../../../typings';
import type { IComponentSpec } from '../../base/interface';

interface ITitleSpecWithoutText extends Omit<IComponentSpec, 'orient'> {
Expand All @@ -22,29 +22,29 @@ interface ITitleSpecWithoutText extends Omit<IComponentSpec, 'orient'> {
*/
y?: number;
/**
* 标题宽度
* 标题宽度,支持像素值、百分比与回调,百分比与回调的基准是图表视图区域
*/
width?: number;
width?: ILayoutNumber;
/**
* 标题高度
* 标题高度,支持像素值、百分比与回调,百分比与回调的基准是图表视图区域
*/
height?: number;
height?: ILayoutNumber;
/**
* 最小宽度,像素值
* 最小宽度,支持像素值、百分比与回调
*/
minWidth?: number;
minWidth?: ILayoutNumber;
/**
* 最大宽度,像素值。当文字超过最大宽度时,会自动省略。
* 最大宽度,支持像素值、百分比与回调。当文字超过最大宽度时,会自动省略。
*/
maxWidth?: number;
maxWidth?: ILayoutNumber;
/**
* 最小高度,像素值
* 最小高度,支持像素值、百分比与回调
*/
minHeight?: number;
minHeight?: ILayoutNumber;
/**
* 最大高度,像素值
* 最大高度,支持像素值、百分比与回调。当文字超过最大高度时,会自动省略。
*/
maxHeight?: number;
maxHeight?: ILayoutNumber;
/**
* 标题的边距留白
*/
Expand Down
44 changes: 29 additions & 15 deletions packages/vchart/src/component/title/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { LayoutLevel, LayoutZIndex } from '../../constant/layout';
import { Factory } from '../../core/factory';
import type { IModelSpecInfo } from '../../model/interface';
import type { IRegion } from '../../region/interface';
import type { IPoint, IOrientType, ILayoutType, ILayoutRect } from '../../typings';
import type { IPoint, IOrientType, ILayoutType, ILayoutRect, ILayoutNumber } from '../../typings';
import { calcLayoutNumber, isValidOrient } from '../../util/space';
import { BaseComponent } from '../base/base-component';
// eslint-disable-next-line no-duplicate-imports
Expand All @@ -15,7 +15,7 @@ import type { TitleAttrs } from '@visactor/vrender-components';
import type { IGraphic, IGroup, INode } from '@visactor/vrender-core';
import type { Maybe } from '@visactor/vutils';
// eslint-disable-next-line no-duplicate-imports
import { isEqual, isValidNumber, pickWithout, isValid } from '@visactor/vutils';
import { isEqual, isValidNumber, pickWithout, isValid, isNil } from '@visactor/vutils';
import { getSpecInfo } from '../util';
import { title } from '../../theme/builtin/common/component/title';

Expand Down Expand Up @@ -110,14 +110,24 @@ export class Title<T extends ITitleSpec = ITitleSpec> extends BaseComponent<T> i
};
}

/**
* 尺寸配置按 ILayoutNumber 契约解析,百分比与回调都以图表视图区域为基准,与 layout-item 一致。
* 未配置时返回 undefined,避免把 0 当作用户设定的尺寸。
*/
private _calcSpecSize(value: ILayoutNumber, isHorizontal: boolean): number | undefined {
if (isNil(value)) {
return undefined;
}
const chartViewRect = this._option.getChartViewRect();
return calcLayoutNumber(value, isHorizontal ? chartViewRect.width : chartViewRect.height, chartViewRect);
}

private _getTitleLayoutRect() {
const titleBounds = this._titleComponent.AABBBounds;
const width = this._spec.width ? this._spec.width : isValidNumber(titleBounds.width()) ? titleBounds.width() : 0;
const height = this._spec.height
? this._spec.height
: isValidNumber(titleBounds.height())
? titleBounds.height()
: 0;
const specWidth = this._calcSpecSize(this._spec.width, true);
const specHeight = this._calcSpecSize(this._spec.height, false);
const width = specWidth ? specWidth : isValidNumber(titleBounds.width()) ? titleBounds.width() : 0;
const height = specHeight ? specHeight : isValidNumber(titleBounds.height()) ? titleBounds.height() : 0;
return {
width,
height
Expand All @@ -133,9 +143,12 @@ export class Title<T extends ITitleSpec = ITitleSpec> extends BaseComponent<T> i
return { visible: false };
}
const layoutRect = this.getLayoutRect();
const titleWidth = calcLayoutNumber(this._spec.width, layoutRect.width, null, layoutRect.width);
const titleMaxWidth = calcLayoutNumber(this._spec.maxWidth, layoutRect.width, null, layoutRect.width);
const maxWidth = Math.max(Math.min(titleWidth, titleMaxWidth, layoutRect.width), 0);
const titleWidth = this._calcSpecSize(this._spec.width, true);
const titleMaxWidth = this._calcSpecSize(this._spec.maxWidth, true);
const maxWidth = Math.max(
Math.min(titleWidth ?? layoutRect.width, titleMaxWidth ?? layoutRect.width, layoutRect.width),
0
);
const hasText = isValid(this._spec.text) && this._spec.text !== '';
const hasSubtext = isValid(this._spec.subtext) && this._spec.subtext !== '';

Expand All @@ -147,11 +160,12 @@ export class Title<T extends ITitleSpec = ITitleSpec> extends BaseComponent<T> i
subtext: hasSubtext ? this._spec.subtext : undefined,
x: this._spec.x ?? 0,
y: this._spec.y ?? 0,
height: this._spec.height,
minWidth: this._spec.minWidth,
width: titleWidth,
height: this._calcSpecSize(this._spec.height, false),
minWidth: this._calcSpecSize(this._spec.minWidth, true),
maxWidth,
minHeight: this._spec.minHeight,
maxHeight: this._spec.maxHeight,
minHeight: this._calcSpecSize(this._spec.minHeight, false),
maxHeight: this._calcSpecSize(this._spec.maxHeight, false),
padding: this._spec.innerPadding,
align: this._spec.align ?? 'left',
verticalAlign: this._spec.verticalAlign ?? 'top',
Expand Down
Loading