diff --git a/common/changes/@visactor/vchart-extension/feat-funnel-conversion-arrow-theme_2026-07-14-08-58.json b/common/changes/@visactor/vchart-extension/feat-funnel-conversion-arrow-theme_2026-07-14-08-58.json new file mode 100644 index 0000000000..dd5f2bc427 --- /dev/null +++ b/common/changes/@visactor/vchart-extension/feat-funnel-conversion-arrow-theme_2026-07-14-08-58.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: conversion funnel arrow (line / symbol / text) styles now read from the series.funnel.conversionArrow theme token, so they can be styled at the theme level in addition to the spec", + "type": "minor", + "packageName": "@visactor/vchart-extension" + } + ], + "packageName": "@visactor/vchart-extension", + "email": "e@erlanzhou.com" +} diff --git a/common/changes/@visactor/vchart/feat-funnel-conversion-arrow-theme_2026-07-14-08-58.json b/common/changes/@visactor/vchart/feat-funnel-conversion-arrow-theme_2026-07-14-08-58.json new file mode 100644 index 0000000000..9e6fc98f22 --- /dev/null +++ b/common/changes/@visactor/vchart/feat-funnel-conversion-arrow-theme_2026-07-14-08-58.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: support theming conversion funnel arrow (line / symbol / text) via series.funnel.conversionArrow theme token", + "type": "minor", + "packageName": "@visactor/vchart" + } + ], + "packageName": "@visactor/vchart", + "email": "e@erlanzhou.com" +} diff --git a/packages/vchart-extension/src/charts/conversion-funnel/conversion-funnel-transformer.ts b/packages/vchart-extension/src/charts/conversion-funnel/conversion-funnel-transformer.ts index b185223f06..12bddf0e42 100644 --- a/packages/vchart-extension/src/charts/conversion-funnel/conversion-funnel-transformer.ts +++ b/packages/vchart-extension/src/charts/conversion-funnel/conversion-funnel-transformer.ts @@ -15,7 +15,8 @@ export class ConversionFunnelChartSpecTransformer extends FunnelChartSpecTransfo transformSpec(spec: IConversionFunnelChartSpecBase): void { const { conversionArrow, extensionMark = [], funnelBackground } = spec; if (conversionArrow && conversionArrow.arrows && conversionArrow.arrows.length) { - const marks = addArrowMark(conversionArrow); + const arrowTheme = (this._option?.getTheme?.('series', 'funnel') as any)?.conversionArrow; + const marks = addArrowMark(conversionArrow, arrowTheme); if (marks && marks.length) { extensionMark.push(...marks); } @@ -39,13 +40,19 @@ export class ConversionFunnelChartSpecTransformer extends FunnelChartSpecTransfo } /** Arrow Related */ -function addArrowMark(arrowSpec: IConversionFunnelSpec['conversionArrow']) { +interface IArrowTheme { + line?: { style?: Record }; + symbol?: { style?: Record }; + text?: { style?: Record }; +} + +function addArrowMark(arrowSpec: IConversionFunnelSpec['conversionArrow'], theme?: IArrowTheme) { const { arrows, ...style } = arrowSpec; const leftArrows = arrows.filter(arrow => arrow.position === 'left'); const rightArrows = arrows.filter(arrow => arrow.position === 'right'); - const rightGroup = computeArrows(rightArrows, style); - const leftGroup = computeArrows(leftArrows, style); + const rightGroup = computeArrows(rightArrows, style, theme); + const leftGroup = computeArrows(leftArrows, style, theme); const result = []; if (rightGroup) { @@ -60,7 +67,11 @@ function addArrowMark(arrowSpec: IConversionFunnelSpec['conversionArrow']) { return result; } -function computeArrows(arrows: Arrow[], style: Omit) { +function computeArrows( + arrows: Arrow[], + style: Omit, + theme?: IArrowTheme +) { if (arrows?.length === 0) { return null; } @@ -74,15 +85,15 @@ function computeArrows(arrows: Arrow[], style: Omit = {} +) { const { style = {}, ...rest } = line; const renderable = (arrow: any, ctx: any) => { const { from, to } = arrow; @@ -112,6 +127,7 @@ function generateArrowLineSpec(line: IConversionFunnelSpec['conversionArrow']['l dataKey: arrow => `${arrow.id}`, style: { ...DEFAULT_ARROW_MARK_STYLE, + ...themeStyle, ...style, renderable, points: (arrow: any, ctx: any) => { @@ -126,7 +142,11 @@ function generateArrowLineSpec(line: IConversionFunnelSpec['conversionArrow']['l } as IExtensionMarkSpec<'polygon'>; } -function generateArrowSymbolSpec(symbol: IConversionFunnelSpec['conversionArrow']['symbol'] = {}, margin?: number) { +function generateArrowSymbolSpec( + symbol: IConversionFunnelSpec['conversionArrow']['symbol'] = {}, + margin?: number, + themeStyle: Record = {} +) { const { style = {}, ...rest } = symbol; return { type: 'symbol', @@ -135,6 +155,7 @@ function generateArrowSymbolSpec(symbol: IConversionFunnelSpec['conversionArrow' ...rest, style: { ...DEFAULT_ARROW_SYMBOL_MARK_STYLE, + ...themeStyle, ...style, x: (arrow: any, ctx: any) => { @@ -152,7 +173,11 @@ function generateArrowSymbolSpec(symbol: IConversionFunnelSpec['conversionArrow' } as IExtensionMarkSpec<'symbol'>; } -function generateArrowTextSpec(text: IConversionFunnelSpec['conversionArrow']['text'] = {}, margin?: number) { +function generateArrowTextSpec( + text: IConversionFunnelSpec['conversionArrow']['text'] = {}, + margin?: number, + themeStyle: Record = {} +) { const { style = {}, formatMethod, textMargin = 4, ...rest } = text; return { @@ -163,6 +188,7 @@ function generateArrowTextSpec(text: IConversionFunnelSpec['conversionArrow']['t ...rest, style: { ...DEFAULT_ARROW_TEXT_MARK_STYLE, + ...themeStyle, text: (arrow: any, ctx: any) => { const { text: textContent } = arrow; let displayTextContent: ReturnType = textContent; diff --git a/packages/vchart-types/types/series/funnel/interface.d.ts b/packages/vchart-types/types/series/funnel/interface.d.ts index 14f5d51364..c1259c363c 100644 --- a/packages/vchart-types/types/series/funnel/interface.d.ts +++ b/packages/vchart-types/types/series/funnel/interface.d.ts @@ -1,4 +1,4 @@ -import type { Datum, IMarkSpec, IMarkTheme, ISeriesSpec, IOrientType, IPathMarkSpec, IPolygonMarkSpec, IRuleMarkSpec, ITextMarkSpec, IPercent, IComposedTextMarkSpec, IFormatMethod } from '../../typings'; +import type { Datum, IMarkSpec, IMarkTheme, ISeriesSpec, IOrientType, IPathMarkSpec, IPolygonMarkSpec, IRuleMarkSpec, ISymbolMarkSpec, ITextMarkSpec, IPercent, IComposedTextMarkSpec, IFormatMethod } from '../../typings'; import type { IAnimationSpec } from '../../animation/spec'; import type { SeriesMarkNameEnum } from '../interface/type'; import type { ILabelSpec } from '../../component/label/interface'; @@ -49,5 +49,10 @@ export interface IFunnelSeriesTheme { line?: Partial>; }; [SeriesMarkNameEnum.transformLabel]?: Partial>; + conversionArrow?: { + line?: Partial>; + symbol?: Partial>; + text?: Partial>; + }; } export {}; diff --git a/packages/vchart/src/series/funnel/interface.ts b/packages/vchart/src/series/funnel/interface.ts index d059b3ac52..5da4803b43 100644 --- a/packages/vchart/src/series/funnel/interface.ts +++ b/packages/vchart/src/series/funnel/interface.ts @@ -7,6 +7,7 @@ import type { IPathMarkSpec, IPolygonMarkSpec, IRuleMarkSpec, + ISymbolMarkSpec, ITextMarkSpec, IPercent, IComposedTextMarkSpec, @@ -161,4 +162,16 @@ export interface IFunnelSeriesTheme { line?: Partial>; }; [SeriesMarkNameEnum.transformLabel]?: Partial>; + /** + * Style of the conversion-rate arrow (used by the `conversionFunnel` extension chart) + * @since 2.1.4 + */ + conversionArrow?: { + /** Style of the arrow leader line */ + line?: Partial>; + /** Style of the arrow head symbol */ + symbol?: Partial>; + /** Style of the arrow label text */ + text?: Partial>; + }; } diff --git a/packages/vchart/src/theme/builtin/common/series/funnel.ts b/packages/vchart/src/theme/builtin/common/series/funnel.ts index d6b54cf7f9..29e0cfd2b8 100644 --- a/packages/vchart/src/theme/builtin/common/series/funnel.ts +++ b/packages/vchart/src/theme/builtin/common/series/funnel.ts @@ -26,6 +26,27 @@ export const getFunnelTheme = (is3d?: boolean): IFunnelSeriesTheme => { fill: { type: 'palette', key: 'secondaryFontColor' }, textBaseline: 'middle' } + }, + conversionArrow: { + // Defaults intentionally mirror the original hardcoded constants (non-breaking); they are + // only surfaced to the theme layer so they can be overridden. + // To adapt to dark themes, override them in a custom theme, e.g. { type: 'palette', key: 'axisDomainColor' }. + line: { + style: { + stroke: 'black' + } + }, + symbol: { + style: { + fill: 'black' + } + }, + text: { + style: { + fontSize: 12, + fill: '#606773' + } + } } };