From 750eb600d9c52230bacb995498f11eac7b7cdba3 Mon Sep 17 00:00:00 2001 From: xile611 Date: Thu, 11 Dec 2025 14:31:48 +0800 Subject: [PATCH] fix: fix color of outlier in boxPlot --- docs/assets/option/en/mark/box-plot.md | 114 ++++++++++++++--- docs/assets/option/zh/mark/box-plot.md | 114 ++++++++++++++--- .../vchart/src/data/transforms/box-plot.ts | 13 +- packages/vchart/src/mark/box-plot.ts | 6 +- .../vchart/src/series/box-plot/box-plot.ts | 6 +- packages/vchart/src/typings/visual.ts | 121 ++++++++++++++---- 6 files changed, 312 insertions(+), 62 deletions(-) diff --git a/docs/assets/option/en/mark/box-plot.md b/docs/assets/option/en/mark/box-plot.md index 17a880a28b..19b5bf3480 100644 --- a/docs/assets/option/en/mark/box-plot.md +++ b/docs/assets/option/en/mark/box-plot.md @@ -1,29 +1,111 @@ {{ target: mark-box-plot }} - + + +#${prefix} shaftShape(string) = 'line' +The rendering shape of the box plot, controlling its visual presentation + +Optional values: + +- `'line'` - Standard line form with rectangular box and line whiskers +- `'bar'` - Bar form using rectangles to represent data ranges +- `'filled-line'` - Filled line form + +#${prefix} boxWidth(number|string) +The width of the box (for vertical box plot) or height (for horizontal box plot) + +Supports numbers or percentage strings. Adaptive to group width when empty. + +Default: `30` + +#${prefix} boxHeight(number|string) +The height of the box (for vertical box plot) or width (for horizontal box plot) + +Supports numbers or percentage strings. + +#${prefix} shaftWidth(number|string) +The width of whisker caps (for vertical box plot) or height (for horizontal box plot) + +Supports numbers or percentage strings. Adaptive when empty. Only effective when `shaftShape='line'` or `'filled-line'`. + +Default: `20` + +#${prefix} ruleWidth(number|string) +The width of whisker cap lines (for vertical box plot) or height (for horizontal box plot) + +Only effective when `shaftShape='line'` or `'filled-line'`. + +#${prefix} ruleHeight(number|string) +The height of whisker cap lines (for vertical box plot) or width (for horizontal box plot) + +Only effective when `shaftShape='line'` or `'filled-line'`. + +#${prefix} minMaxWidth(number|string) +The following configurations only take effect when `shaftShape='bar'`. +The width of the min-max rectangle (for vertical box plot) or height (for horizontal box plot) + +#${prefix} minMaxHeight(number|string) +The height of the min-max rectangle (for vertical box plot) or width (for horizontal box plot) + +#${prefix} q1q3Width(number|string) +The width of the Q1-Q3 quartile rectangle (for vertical box plot) or height (for horizontal box plot) + +#${prefix} q1q3Height(number|string) +The height of the Q1-Q3 quartile rectangle (for vertical box plot) or width (for horizontal box plot) #${prefix} lineWidth(number) = 2 -Box plot stroke thickness +The stroke width of the box plot -#${prefix} boxWidth(number) -Box plot box width, adaptive if empty +Affects all line widths in `shaftShape='line'` or `'filled-line'` mode. +No stroke is displayed in `shaftShape='bar'` mode. -#${prefix} shaftWidth(number) -Box plot maximum-minimum value width, adaptive if empty +#${prefix} stroke(string) +The stroke color of the box plot -#${prefix} shaftShape(string) = 'line' -Box plot center axis shape connecting maximum and minimum values +#${prefix} boxStroke(string) +The stroke color of the box, can be set separately to distinguish from other parts -Optional values: +{{ use: common-version(version: '2.0.11') }} -- `line` Linear -- `bar` Bar +#${prefix} medianStroke(string) +The stroke color of the median line, can be set separately to highlight the median + +{{ use: common-version(version: '2.0.11') }} #${prefix} boxFill(string) -Box plot box fill color, no fill if empty +The fill color of the box, no fill if empty -#${prefix} stroke(string) -Box plot stroke color, effective only when shaftShape='line' +#${prefix} minMaxFillOpacity(number) +The fill opacity of the min-max rectangle + +Only effective when `shaftShape='bar'`. + +#${prefix} boxCornerRadius(number) +The corner radius of the box, rounded corners will be displayed when set + +{{ use: common-version(version: '2.0.11') }} + +#${prefix} min(Function) +Data mapping function for minimum value + +Type: `(datum: Datum) => number` + +#${prefix} q1(Function) +Data mapping function for first quartile (Q1, 25th percentile) + +Type: `(datum: Datum) => number` + +#${prefix} median(Function) +Data mapping function for median (Q2, 50th percentile) + +Type: `(datum: Datum) => number` + +#${prefix} q3(Function) +Data mapping function for third quartile (Q3, 75th percentile) + +Type: `(datum: Datum) => number` + +#${prefix} max(Function) +Data mapping function for maximum value -#${prefix} shaftFillOpacity(number) = 0.5 -Box plot center axis line opacity connecting maximum and minimum values, effective only when shaftType=bar \ No newline at end of file +Type: `(datum: Datum) => number` diff --git a/docs/assets/option/zh/mark/box-plot.md b/docs/assets/option/zh/mark/box-plot.md index ac0e522612..a3fe74f61e 100644 --- a/docs/assets/option/zh/mark/box-plot.md +++ b/docs/assets/option/zh/mark/box-plot.md @@ -1,29 +1,111 @@ {{ target: mark-box-plot }} - + + +#${prefix} shaftShape(string) = 'line' +箱线图的绘制形状,控制箱线图的视觉呈现方式 + +可选值: + +- `'line'` - 标准线条形式,箱体为矩形,须线为线条 +- `'bar'` - 柱状形式,使用矩形表示数据范围 +- `'filled-line'` - 填充线条形式 + +#${prefix} boxWidth(number|string) +箱体的宽度(垂直箱线图)或高度(水平箱线图) + +支持数字或百分比字符串。为空时自适应分组宽度。 + +默认值:`30` + +#${prefix} boxHeight(number|string) +箱体的高度(垂直箱线图)或宽度(水平箱线图) + +支持数字或百分比字符串。 + +#${prefix} shaftWidth(number|string) +须线端点的宽度(垂直箱线图)或高度(水平箱线图) + +支持数字或百分比字符串。为空时自适应。仅在 `shaftShape='line'` 或 `'filled-line'` 时生效。 + +默认值:`20` + +#${prefix} ruleWidth(number|string) +须线端点横线的宽度(垂直箱线图)或高度(水平箱线图) + +仅在 `shaftShape='line'` 或 `'filled-line'` 时生效。 + +#${prefix} ruleHeight(number|string) +须线端点横线的高度(垂直箱线图)或宽度(水平箱线图) + +仅在 `shaftShape='line'` 或 `'filled-line'` 时生效。 + +#${prefix} minMaxWidth(number|string) +以下配置仅在 `shaftShape='bar'` 时生效。 +最小-最大值矩形的宽度(垂直箱线图)或高度(水平箱线图) + +#${prefix} minMaxHeight(number|string) +最小-最大值矩形的高度(垂直箱线图)或宽度(水平箱线图) + +#${prefix} q1q3Width(number|string) +Q1-Q3 四分位数矩形的宽度(垂直箱线图)或高度(水平箱线图) + +#${prefix} q1q3Height(number|string) +Q1-Q3 四分位数矩形的高度(垂直箱线图)或宽度(水平箱线图) #${prefix} lineWidth(number) = 2 -箱型图箱体描边粗细 +箱线图的描边宽度 -#${prefix} boxWidth(number) -箱型图箱体宽度,为空则自适应 +在 `shaftShape='line'` 或 `'filled-line'` 模式下影响所有线条的宽度。 +在 `shaftShape='bar'` 模式下不显示描边。 -#${prefix} shaftWidth(number) -箱型图最大最小值宽度,为空则自适应 +#${prefix} stroke(string) +箱线图的描边颜色 -#${prefix} shaftShape(string) = 'line' -箱型图连接最大最小值的中轴线形状 +#${prefix} boxStroke(string) +箱体的描边颜色,可单独设置以区别于其他部分 -可选值: +{{ use: common-version(version: '2.0.11') }} -- `line` 线形 -- `bar` 条形 +#${prefix} medianStroke(string) +中位数线的描边颜色,可单独设置以突出显示中位数 + +{{ use: common-version(version: '2.0.11') }} #${prefix} boxFill(string) -箱型图箱体填充颜色,为空则不填充 +箱体的填充颜色,为空则不填充 -#${prefix} stroke(string) -箱型图描边颜色,仅当 shaftShape='line'时生效 +#${prefix} minMaxFillOpacity(number) +最小-最大值矩形的填充透明度 + +仅在 `shaftShape='bar'` 模式下生效。 + +#${prefix} boxCornerRadius(number) +箱体的圆角半径,设置后箱体四角将显示为圆角 + +{{ use: common-version(version: '2.0.11') }} + +#${prefix} min(Function) +最小值的数据映射函数 + +类型:`(datum: Datum) => number` + +#${prefix} q1(Function) +第一四分位数(Q1,25%分位数)的数据映射函数 + +类型:`(datum: Datum) => number` + +#${prefix} median(Function) +中位数(Q2,50%分位数)的数据映射函数 + +类型:`(datum: Datum) => number` + +#${prefix} q3(Function) +第三四分位数(Q3,75%分位数)的数据映射函数 + +类型:`(datum: Datum) => number` + +#${prefix} max(Function) +最大值的数据映射函数 -#${prefix} shaftFillOpacity(number) = 0.5 -箱型图连接最大最小值的中轴线透明度,仅当 shaftType=bar 时生效 +类型:`(datum: Datum) => number` diff --git a/packages/vchart/src/data/transforms/box-plot.ts b/packages/vchart/src/data/transforms/box-plot.ts index 60eb21950c..24cf1fcdd4 100644 --- a/packages/vchart/src/data/transforms/box-plot.ts +++ b/packages/vchart/src/data/transforms/box-plot.ts @@ -1,10 +1,11 @@ -import { isArray } from '@visactor/vutils'; +import { isArray, isValid } from '@visactor/vutils'; import { BOX_PLOT_OUTLIER_VALUE_FIELD } from '../../constant/box-plot'; import type { Datum } from '../../typings'; export interface IBoxPlotOutlierOpt { dimensionField: string[]; outliersField: string; + seriesField?: string; } /** * 将箱型图outlier数组展平 @@ -14,8 +15,9 @@ export interface IBoxPlotOutlierOpt { */ export const foldOutlierData = (data: Array, op: IBoxPlotOutlierOpt) => { const result: any = []; - const { outliersField, dimensionField } = op; + const { outliersField, dimensionField, seriesField } = op; const latestData = (data[0] as any).latestData || []; + latestData.forEach((d: Datum) => { let outlierValues = d[outliersField]; if (!isArray(outlierValues)) { @@ -27,8 +29,13 @@ export const foldOutlierData = (data: Array, op: IBoxPlotOutlierOpt) = [BOX_PLOT_OUTLIER_VALUE_FIELD]: v }; dimensionField.forEach(field => { - resData[field] = d[field]; + (resData as any)[field] = d[field]; }); + + if (isValid(seriesField)) { + (resData as any)[seriesField] = d[seriesField]; + } + return resData; }) ); diff --git a/packages/vchart/src/mark/box-plot.ts b/packages/vchart/src/mark/box-plot.ts index 280b0cdc6e..7265dda07b 100644 --- a/packages/vchart/src/mark/box-plot.ts +++ b/packages/vchart/src/mark/box-plot.ts @@ -171,7 +171,11 @@ export class BoxPlotMark median: { type: 'line', defaultAttributes: { x: 0, y: 0 } } }; this._positionChannels = BOX_PLOT_CHANNELS; - this._channelEncoder = null; + this._channelEncoder = { + boxStroke: (val: string) => ({ box: { stroke: val } }), + medianStroke: (val: string) => ({ median: { stroke: val } }), + boxCornerRadius: (val: number) => ({ box: { cornerRadius: val } }) + }; this._positionEncoder = (glyphAttrs: any, datum: Datum, g: IGlyph) => { const { x = g.attribute.x, diff --git a/packages/vchart/src/series/box-plot/box-plot.ts b/packages/vchart/src/series/box-plot/box-plot.ts index 2c0d8a1915..310581ea52 100644 --- a/packages/vchart/src/series/box-plot/box-plot.ts +++ b/packages/vchart/src/series/box-plot/box-plot.ts @@ -188,7 +188,8 @@ export class BoxPlotSeries ex this.setMarkStyle( outlierMark, { - fill: this._outliersStyle?.fill ?? this.getColorAttribute(), + fill: this._outliersStyle?.fill ?? this._boxFillColor ?? this.getColorAttribute(), + stroke: this.getColorAttribute(), size: isNumber(this._outliersStyle?.size) ? this._outliersStyle.size : DEFAULT_OUTLIER_SIZE }, STATE_VALUE_ENUM.STATE_NORMAL, @@ -307,7 +308,8 @@ export class BoxPlotSeries ex type: 'foldOutlierData', options: { dimensionField: this._direction === Direction.horizontal ? this._fieldY : this._fieldX, - outliersField: this._outliersField + outliersField: this._outliersField, + seriesField: this._seriesField } }); diff --git a/packages/vchart/src/typings/visual.ts b/packages/vchart/src/typings/visual.ts index 8fc79a58fa..11982e4670 100644 --- a/packages/vchart/src/typings/visual.ts +++ b/packages/vchart/src/typings/visual.ts @@ -537,54 +537,127 @@ export interface IRectMarkSpec extends IFillMarkSpec { } export interface IBoxPlotMarkSpec extends ICommonSpec { + // ============ 数据统计值配置 ============ /** - * box描边宽度 + * 最小值(箱线图的下限) + * 用于计算箱线图的下端须线位置 */ - lineWidth?: number; + min?: (datum: Datum) => number; + /** + * 第一四分位数(Q1,25%分位数) + * 用于确定箱体的下边界 + */ + q1?: (datum: Datum) => number; + /** + * 中位数(Q2,50%分位数) + * 在箱体中绘制中位数线 + */ + median?: (datum: Datum) => number; + /** + * 第三四分位数(Q3,75%分位数) + * 用于确定箱体的上边界 + */ + q3?: (datum: Datum) => number; + /** + * 最大值(箱线图的上限) + * 用于计算箱线图的上端须线位置 + */ + max?: (datum: Datum) => number; + + // ============ 样式形态配置 ============ + /** + * 中轴线(须线)的绘制形状 + * - 'line': 标准线条形式,绘制 shaft(连接线)、box(箱体)、max/min/median(线条)五个子图元 + * - 'bar': 柱状形式,绘制 minMaxBox(最小-最大值矩形)、q1q3Box(Q1-Q3矩形)、median(中位数线)三个子图元 + * - 'filled-line': 填充线条形式 + * @default 'line' + */ + shaftShape?: BoxPlotShaftShape; + + // ============ 尺寸配置(shaftShape = 'line' / 'filled-line' 模式)============ /** - * box宽度 + * 箱体的宽度(垂直箱线图)或高度(水平箱线图) + * 在 shaftShape = 'line' / 'filled-line' 模式下,控制 box 子图元的尺寸 + * 支持像素值或百分比字符串(相对于分组宽度) + * @default 30 */ boxWidth?: number | string; /** - * 最大最小值宽度 + * 箱体的高度(垂直箱线图)或宽度(水平箱线图) + * 在 shaftShape = 'line' / 'filled-line' 模式下使用 + */ + boxHeight?: number | string; + /** + * 最大/最小值须线的宽度(垂直箱线图)或高度(水平箱线图) + * 在 shaftShape = 'line' / 'filled-line' 模式下,控制 max/min 子图元的线条长度 + * 支持像素值或百分比字符串 + * @default 20 */ shaftWidth?: number | string; /** - * 中轴线类型 + * 最大/最小值须线端点的宽度(垂直箱线图)或高度(水平箱线图) + * 在 shaftShape = 'line' / 'filled-line' 模式下使用 */ - shaftShape?: BoxPlotShaftShape; + ruleWidth?: number | string; /** - * 盒子填充颜色,为空则不填充 + * 最大/最小值须线端点的高度(垂直箱线图)或宽度(水平箱线图) + * 在 shaftShape = 'line' / 'filled-line' 模式下使用 */ - boxFill?: string; - // /** - // * 描边颜色 - // */ - // stroke?: string; + ruleHeight?: number | string; + + // ============ 尺寸配置(shaftShape = 'bar' 模式)============ /** - * 中轴线透明度,仅当shaftType=bar时生效 + * 最小-最大值矩形的宽度(垂直箱线图)或高度(水平箱线图) + * 在 shaftShape = 'bar' 模式下,控制 minMaxBox 子图元的尺寸 */ - shaftFillOpacity?: number; + minMaxWidth?: number | string; /** - * 最小值 + * 最小-最大值矩形的高度(垂直箱线图)或宽度(水平箱线图) + * 在 shaftShape = 'bar' 模式下使用 */ - min?: (datum: Datum) => number; + minMaxHeight?: number | string; /** - * 25%分位数 + * Q1-Q3 四分位数矩形的宽度(垂直箱线图)或高度(水平箱线图) + * 在 shaftShape = 'bar' 模式下,控制 q1q3Box 子图元的尺寸 */ - q1?: (datum: Datum) => number; + q1q3Width?: number | string; /** - * 中位数 + * Q1-Q3 四分位数矩形的高度(垂直箱线图)或宽度(水平箱线图) + * 在 shaftShape = 'bar' 模式下使用 */ - median?: (datum: Datum) => number; + q1q3Height?: number | string; + + // ============ 样式配置 ============ /** - * 75%分位数 + * 箱线图的描边宽度 + * 在 shaftShape = 'line' / 'filled-line' 模式下影响所有线条的宽度 + * 在 shaftShape = 'bar' 模式下,minMaxBox 和 q1q3Box 的 lineWidth 会被强制设为 0 + * @default 2 */ - q3?: (datum: Datum) => number; + lineWidth?: number; /** - * 最大值 + * 最小-最大值矩形的填充透明度 + * 仅在 shaftShape = 'bar' 模式下生效,控制 minMaxBox 子图元的 fillOpacity */ - max?: (datum: Datum) => number; + minMaxFillOpacity?: number; + /** + * 箱体的描边颜色 + * 在 shaftShape = 'line' / 'filled-line' 模式下,控制 box 子图元的 stroke 属性 + * @since 2.0.11 + */ + boxStroke?: string; + /** + * 中位数线的描边颜色 + * 在 shaftShape = 'line' / 'filled-line' 模式下,控制 median 子图元的 stroke 属性 + * @since 2.0.11 + */ + medianStroke?: string; + /** + * 箱体的圆角半径 + * 在 shaftShape = 'line' / 'filled-line' 模式下,控制 box 子图元的 cornerRadius 属性 + * @since 2.0.11 + */ + boxCornerRadius?: number; } export interface IRippleMarkSpec extends ICommonSpec {