From 70e57b7a34c0d2a47df81cbb5525f2a7bb7b3f4c Mon Sep 17 00:00:00 2001 From: "chendaxin.tk" Date: Fri, 19 Jun 2026 12:41:45 +0800 Subject: [PATCH] feat: support function type for `maxRow` / `maxCol` of discrete legend Allow a discrete legend's `maxRow` / `maxCol` to be a callback `(ctx: { rect, orient, id }) => number`, evaluated during layout in `getLegendAttributes` against the legend's allocated rect, so the row / column count can adapt to the available space instead of a layout-blind static number. Numeric values keep working unchanged. --- ...end-maxrow-maxcol-fn_2026-06-19-00-47.json | 11 +++++ .../component/legend/discrete-legend.test.ts | 47 +++++++++++++++++++ .../component/legend/discrete/interface.ts | 37 ++++++++++++++- .../src/component/legend/discrete/util.ts | 10 ++++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 common/changes/@visactor/vchart/feat-discrete-legend-maxrow-maxcol-fn_2026-06-19-00-47.json create mode 100644 packages/vchart/__tests__/unit/component/legend/discrete-legend.test.ts diff --git a/common/changes/@visactor/vchart/feat-discrete-legend-maxrow-maxcol-fn_2026-06-19-00-47.json b/common/changes/@visactor/vchart/feat-discrete-legend-maxrow-maxcol-fn_2026-06-19-00-47.json new file mode 100644 index 0000000000..f290803977 --- /dev/null +++ b/common/changes/@visactor/vchart/feat-discrete-legend-maxrow-maxcol-fn_2026-06-19-00-47.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: support function type for `maxRow` / `maxCol` of the discrete legend, evaluated during layout against the legend's allocated rect, so the row / column count can adapt to the available space", + "type": "minor", + "packageName": "@visactor/vchart" + } + ], + "packageName": "@visactor/vchart", + "email": "chendaxin.tk@bytedance.com" +} diff --git a/packages/vchart/__tests__/unit/component/legend/discrete-legend.test.ts b/packages/vchart/__tests__/unit/component/legend/discrete-legend.test.ts new file mode 100644 index 0000000000..fbda6961aa --- /dev/null +++ b/packages/vchart/__tests__/unit/component/legend/discrete-legend.test.ts @@ -0,0 +1,47 @@ +import { getLegendAttributes } from '../../../../src/component/legend/discrete/util'; + +describe('Discrete legend getLegendAttributes maxRow/maxCol', () => { + const rect = { x: 0, y: 0, width: 200, height: 80 }; + + test('should evaluate function `maxRow` against the layout rect', () => { + const attrs = getLegendAttributes( + { + type: 'discrete', + orient: 'bottom', + id: 'l1', + maxRow: (ctx: any) => Math.floor(ctx.rect.height / 20) + } as any, + rect as any + ); + + expect(attrs.maxRow).toBe(4); + }); + + test('should evaluate function `maxCol` and pass rect / orient / id in the context', () => { + let received: any; + const attrs = getLegendAttributes( + { + type: 'discrete', + orient: 'right', + id: 'l2', + maxCol: (ctx: any) => { + received = ctx; + return 3; + } + } as any, + rect as any + ); + + expect(attrs.maxCol).toBe(3); + expect(received.rect).toBe(rect); + expect(received.orient).toBe('right'); + expect(received.id).toBe('l2'); + }); + + test('should keep numeric `maxRow` / `maxCol` unchanged', () => { + const attrs = getLegendAttributes({ type: 'discrete', maxRow: 2, maxCol: 1 } as any, rect as any); + + expect(attrs.maxRow).toBe(2); + expect(attrs.maxCol).toBe(1); + }); +}); diff --git a/packages/vchart/src/component/legend/discrete/interface.ts b/packages/vchart/src/component/legend/discrete/interface.ts index e971b5d3e4..66aba985bc 100644 --- a/packages/vchart/src/component/legend/discrete/interface.ts +++ b/packages/vchart/src/component/legend/discrete/interface.ts @@ -8,6 +8,8 @@ import type { } from '@visactor/vrender-components'; import type { ILegendCommonSpec, NoVisibleMarkStyle } from '../interface'; import type { IFormatMethod, StringOrNumber } from '../../../typings'; +import type { ILayoutRect } from '../../../typings/layout'; +import type { IOrientType } from '../../../typings/space'; import type { IBaseScale } from '@visactor/vscale'; import type { IGlobalScale } from '../../../scale/interface'; import type { ComponentThemeWithDirection } from '../../interface'; @@ -193,6 +195,26 @@ export type ILegendScrollbar = { } & Omit; /** spec */ +/** + * The layout context passed to a `maxRow` / `maxCol` callback. The callback is evaluated during + * layout, so the row / column count can be derived from the space actually allocated to the legend. + * @since 2.0.23 + */ +export interface IDiscreteLegendArrangeContext { + /** the layout rect allocated to the legend in the current layout */ + rect: ILayoutRect; + /** the orient of the legend */ + orient?: IOrientType; + /** the id of the legend */ + id?: StringOrNumber; +} + +/** + * The max row / col count of a discrete legend. Either a fixed number, or a callback that returns a + * number, evaluated during layout against {@link IDiscreteLegendArrangeContext}. + */ +export type DiscreteLegendArrangeCount = number | ((ctx: IDiscreteLegendArrangeContext) => number); + export type IDiscreteLegendSpec = ILegendCommonSpec & { type?: 'discrete'; /** @@ -231,7 +253,20 @@ export type IDiscreteLegendSpec = ILegendCommonSpec & { * 默认筛选的数据范围 */ defaultSelected?: string[]; -} & Omit; + /** + * The maximum number of rows displayed (for horizontal legend). Besides a fixed number, a + * callback `(ctx) => number` is also supported, which is evaluated during layout so the row + * count can adapt to the space allocated to the legend. + * @since 2.0.23 + */ + maxRow?: DiscreteLegendArrangeCount; + /** + * The maximum number of columns displayed (for vertical legend). Besides a fixed number, a + * callback `(ctx) => number` is also supported (see {@link maxRow}). + * @since 2.0.23 + */ + maxCol?: DiscreteLegendArrangeCount; +} & Omit; // theme 主题相关配置 export type IDiscreteLegendCommonTheme = Omit< diff --git a/packages/vchart/src/component/legend/discrete/util.ts b/packages/vchart/src/component/legend/discrete/util.ts index e761324b52..3a9da77af1 100644 --- a/packages/vchart/src/component/legend/discrete/util.ts +++ b/packages/vchart/src/component/legend/discrete/util.ts @@ -45,6 +45,16 @@ export function getLegendAttributes(spec: IDiscreteLegendSpec, rect: ILayoutRect const attrs: any = restSpec; + // `maxRow` / `maxCol` may be a callback, evaluated here during layout against the legend's + // available `rect` so the row / column count can adapt to the space (e.g. allow more rows on + // a tall-and-narrow legend). The callback receives the layout context and returns a number. + if (typeof attrs.maxRow === 'function') { + attrs.maxRow = attrs.maxRow({ rect, orient, id }); + } + if (typeof attrs.maxCol === 'function') { + attrs.maxCol = attrs.maxCol({ rect, orient, id }); + } + // transform title if (title.visible) { attrs.title = transformLegendTitleAttributes(title);