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": [
{
"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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -39,13 +40,19 @@ export class ConversionFunnelChartSpecTransformer extends FunnelChartSpecTransfo
}

/** Arrow Related */
function addArrowMark(arrowSpec: IConversionFunnelSpec['conversionArrow']) {
interface IArrowTheme {
line?: { style?: Record<string, any> };
symbol?: { style?: Record<string, any> };
text?: { style?: Record<string, any> };
}

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) {
Expand All @@ -60,7 +67,11 @@ function addArrowMark(arrowSpec: IConversionFunnelSpec['conversionArrow']) {
return result;
}

function computeArrows(arrows: Arrow[], style: Omit<IConversionFunnelSpec['conversionArrow'], 'arrows'>) {
function computeArrows(
arrows: Arrow[],
style: Omit<IConversionFunnelSpec['conversionArrow'], 'arrows'>,
theme?: IArrowTheme
) {
if (arrows?.length === 0) {
return null;
}
Expand All @@ -74,23 +85,27 @@ function computeArrows(arrows: Arrow[], style: Omit<IConversionFunnelSpec['conve
zIndex: LayoutZIndex.Mark + 1,
children: []
};
const lineMark = generateArrowLineSpec(line, margin);
const lineMark = generateArrowLineSpec(line, margin, theme?.line?.style);
if (lineMark) {
result.push(lineMark);
}
const arrowMark = generateArrowSymbolSpec(symbol, margin);
const arrowMark = generateArrowSymbolSpec(symbol, margin, theme?.symbol?.style);
if (arrowMark) {
result.push(arrowMark);
}
const textMark = generateArrowTextSpec(text, margin);
const textMark = generateArrowTextSpec(text, margin, theme?.text?.style);
if (textMark) {
result.push(textMark);
}
rootGroup.children = result;
return rootGroup;
}

function generateArrowLineSpec(line: IConversionFunnelSpec['conversionArrow']['line'] = {}, margin?: number) {
function generateArrowLineSpec(
line: IConversionFunnelSpec['conversionArrow']['line'] = {},
margin?: number,
themeStyle: Record<string, any> = {}
) {
const { style = {}, ...rest } = line;
const renderable = (arrow: any, ctx: any) => {
const { from, to } = arrow;
Expand All @@ -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) => {
Expand All @@ -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<string, any> = {}
) {
const { style = {}, ...rest } = symbol;
return {
type: 'symbol',
Expand All @@ -135,6 +155,7 @@ function generateArrowSymbolSpec(symbol: IConversionFunnelSpec['conversionArrow'
...rest,
style: {
...DEFAULT_ARROW_SYMBOL_MARK_STYLE,
...themeStyle,
...style,

x: (arrow: any, ctx: any) => {
Expand All @@ -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<string, any> = {}
) {
const { style = {}, formatMethod, textMargin = 4, ...rest } = text;

return {
Expand All @@ -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<typeof formatMethod> = textContent;
Expand Down
7 changes: 6 additions & 1 deletion packages/vchart-types/types/series/funnel/interface.d.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -49,5 +49,10 @@ export interface IFunnelSeriesTheme {
line?: Partial<IMarkTheme<IRuleMarkSpec>>;
};
[SeriesMarkNameEnum.transformLabel]?: Partial<IMarkTheme<ITextMarkSpec>>;
conversionArrow?: {
line?: Partial<IMarkTheme<IPolygonMarkSpec>>;
symbol?: Partial<IMarkTheme<ISymbolMarkSpec>>;
text?: Partial<IMarkTheme<ITextMarkSpec>>;
};
}
export {};
13 changes: 13 additions & 0 deletions packages/vchart/src/series/funnel/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
IPathMarkSpec,
IPolygonMarkSpec,
IRuleMarkSpec,
ISymbolMarkSpec,
ITextMarkSpec,
IPercent,
IComposedTextMarkSpec,
Expand Down Expand Up @@ -161,4 +162,16 @@ export interface IFunnelSeriesTheme {
line?: Partial<IMarkTheme<IRuleMarkSpec>>;
};
[SeriesMarkNameEnum.transformLabel]?: Partial<IMarkTheme<ITextMarkSpec>>;
/**
* 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<IMarkTheme<IPolygonMarkSpec>>;
/** Style of the arrow head symbol */
symbol?: Partial<IMarkTheme<ISymbolMarkSpec>>;
/** Style of the arrow label text */
text?: Partial<IMarkTheme<ITextMarkSpec>>;
};
}
21 changes: 21 additions & 0 deletions packages/vchart/src/theme/builtin/common/series/funnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
}
}
};

Expand Down
Loading