diff --git a/common/changes/@visactor/vchart/feat-boxplot-label_2025-12-10-08-17.json b/common/changes/@visactor/vchart/feat-boxplot-label_2025-12-10-08-17.json new file mode 100644 index 0000000000..93f2dd325a --- /dev/null +++ b/common/changes/@visactor/vchart/feat-boxplot-label_2025-12-10-08-17.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vchart", + "comment": "feat: support label of boxPlot", + "type": "none" + } + ], + "packageName": "@visactor/vchart" +} \ No newline at end of file diff --git a/packages/vchart/src/component/label/util.ts b/packages/vchart/src/component/label/util.ts index 46809ace78..d1221c9757 100644 --- a/packages/vchart/src/component/label/util.ts +++ b/packages/vchart/src/component/label/util.ts @@ -4,6 +4,7 @@ import { Direction } from '../../typings/space'; import type { BaseLabelAttrs, LabelItem, OverlapAttrs, Strategy } from '@visactor/vrender-components'; import { SeriesTypeEnum, type ICartesianSeries } from '../../series/interface'; import { isBoolean, isFunction, isObject, isString } from '@visactor/vutils'; +import type { IGlyph, IGraphic, IText } from '@visactor/vrender-core'; import { createText } from '@visactor/vrender-core'; import type { IWaterfallSeriesSpec } from '../../series/waterfall/interface'; import type { ILabelInfo, ILabelSpec } from './interface'; @@ -22,7 +23,8 @@ export const labelRuleMap = { rect3d: barLabel, arc3d: pieLabel, treemap: treemapLabel, - venn: vennLabel + venn: vennLabel, + boxPlot: boxPlotLabel }; export function defaultLabelConfig(rule: string, labelInfo: ILabelInfo) { @@ -463,3 +465,44 @@ function sankeyLabelOverlapStrategy(series: ICartesianSeries) { return strategy; } + +export function boxPlotLabel(labelInfo: ILabelInfo) { + return { + customLayoutFunc: (labels: LabelItem[], texts: IText[], getRelatedGraphic: (item: LabelItem) => IGraphic) => { + for (let i = 0; i < texts.length; i++) { + const text = texts[i]; + const textData = labels[i]; + if (!text || !textData) { + continue; + } + + const baseBoxPlot = getRelatedGraphic(textData) as IGlyph; + const meadianLineIndex = baseBoxPlot.getSubGraphic().findIndex(sub => sub.name === 'median'); + + if (meadianLineIndex !== -1) { + const bbox = baseBoxPlot.getSubGraphic()[meadianLineIndex].AABBBounds; + + if ((labelInfo.series as ICartesianSeries).direction === 'horizontal') { + text.setAttributes({ + x: bbox.x2 + text.AABBBounds.width() / 2, + y: (bbox.y1 + bbox.y2) / 2 + }); + } else { + text.setAttributes({ + x: (bbox.x1 + bbox.x2) / 2, + y: bbox.y2 + text.AABBBounds.height() / 2 + }); + } + } else { + const bbox = baseBoxPlot.AABBBounds; + text.setAttributes({ + x: (bbox.x1 + bbox.x2) / 2, + y: (bbox.y1 + bbox.y2) / 2 + }); + } + } + + return texts; + } + }; +} diff --git a/packages/vchart/src/mark/box-plot.ts b/packages/vchart/src/mark/box-plot.ts index 29e1bfd3ac..280b0cdc6e 100644 --- a/packages/vchart/src/mark/box-plot.ts +++ b/packages/vchart/src/mark/box-plot.ts @@ -317,6 +317,10 @@ export class BoxPlotMark } } + setDataLabelType() { + return 'symbol'; + } + protected _getDefaultStyle() { const defaultStyle: IMarkStyle = { ...super._getDefaultStyle(), diff --git a/packages/vchart/src/series/box-plot/box-plot-transformer.ts b/packages/vchart/src/series/box-plot/box-plot-transformer.ts new file mode 100644 index 0000000000..34e89b87ef --- /dev/null +++ b/packages/vchart/src/series/box-plot/box-plot-transformer.ts @@ -0,0 +1,12 @@ +import { BaseSeriesSpecTransformer } from '../base'; +import { SeriesMarkNameEnum } from '../interface'; +import type { IBoxPlotSeriesSpec, IBoxPlotSeriesTheme } from './interface'; + +export class BoxPlotSeriesSpecTransformer< + T extends IBoxPlotSeriesSpec = IBoxPlotSeriesSpec, + K extends IBoxPlotSeriesTheme = IBoxPlotSeriesTheme +> extends BaseSeriesSpecTransformer { + protected _transformLabelSpec(spec: T): void { + this._addMarkLabelSpec(spec, SeriesMarkNameEnum.boxPlot); + } +} diff --git a/packages/vchart/src/series/box-plot/box-plot.ts b/packages/vchart/src/series/box-plot/box-plot.ts index 1555360b6c..2c0d8a1915 100644 --- a/packages/vchart/src/series/box-plot/box-plot.ts +++ b/packages/vchart/src/series/box-plot/box-plot.ts @@ -25,7 +25,7 @@ import { registerBoxPlotMark } from '../../mark/box-plot'; import { registerSymbolMark } from '../../mark/symbol'; import { boxPlotSeriesMark } from './constant'; import { Factory } from '../../core/factory'; -import type { IBoxPlotMark, IGlyphMark, IMark, ISymbolMark } from '../../mark/interface'; +import type { IBoxPlotMark, IGlyphMark, IMark, ISymbolMark, ITextMark } from '../../mark/interface'; import { merge, isNumber, isValid, isNil, array, last } from '@visactor/vutils'; import { getGroupAnimationParams } from '../util/utils'; import { registerCartesianLinearAxis, registerCartesianBandAxis } from '../../component/axis/cartesian'; @@ -35,6 +35,7 @@ import { registeBoxPlotScaleAnimation } from './animation'; import { boxPlot } from '../../theme/builtin/common/series/box-plot'; import { getActualNumValue } from '../../util/space'; import { isContinuous } from '@visactor/vscale'; +import { BoxPlotSeriesSpecTransformer } from './box-plot-transformer'; const DEFAULT_STROKE_WIDTH = 2; const DEFAULT_SHAFT_FILL_OPACITY = 0.5; @@ -51,6 +52,9 @@ export class BoxPlotSeries ex static readonly builtInTheme = { boxPlot }; static readonly mark: SeriesMarkMap = boxPlotSeriesMark; + static readonly transformerConstructor = BoxPlotSeriesSpecTransformer as any; + readonly transformerConstructor = BoxPlotSeriesSpecTransformer; + protected _bandPosition = 0; protected _minField: string; getMinField() { @@ -272,6 +276,19 @@ export class BoxPlotSeries ex } } + initLabelMarkStyle(textMark: ITextMark) { + if (!textMark) { + return; + } + this.setMarkStyle(textMark, { + fill: this.getColorAttribute(), + text: (datum: Datum) => { + return datum[this.getMedianField()]; + }, + z: this._fieldZ ? this.dataToPositionZ.bind(this) : null + }); + } + initData(): void { super.initData(); if (!this._data) {