feat(echarts-funnel): Implement % calculation type (#26290)

This commit is contained in:
Kamil Gabryjelski
2023-12-22 13:14:52 +01:00
committed by GitHub
parent 39ac45351b
commit 5400d30b20
5 changed files with 181 additions and 14 deletions

View File

@@ -20,16 +20,20 @@ import React from 'react';
import { t } from '@superset-ui/core';
import {
ControlPanelConfig,
ControlStateMapping,
ControlSubSectionHeader,
D3_FORMAT_DOCS,
D3_FORMAT_OPTIONS,
D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT,
getStandardizedControls,
sections,
sharedControls,
ControlStateMapping,
getStandardizedControls,
D3_FORMAT_DOCS,
} from '@superset-ui/chart-controls';
import { DEFAULT_FORM_DATA, EchartsFunnelLabelTypeType } from './types';
import {
DEFAULT_FORM_DATA,
EchartsFunnelLabelTypeType,
PercentCalcType,
} from './types';
import { legendSection } from '../controls';
const { labelType, numberFormat, showLabels, defaultTooltipLabel } =
@@ -70,6 +74,25 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'percent_calculation_type',
config: {
type: 'SelectControl',
label: t('% calculation'),
description: t(
'Display percents in the label and tooltip as the percent of the total value, from the first step of the funnel, or from the previous step in the funnel.',
),
choices: [
[PercentCalcType.FIRST_STEP, t('Calculate from first step')],
[PercentCalcType.PREV_STEP, t('Calculate from previous step')],
[PercentCalcType.TOTAL, t('Percent of total')],
],
default: PercentCalcType.FIRST_STEP,
renderTrigger: true,
},
},
],
],
},
{

View File

@@ -19,12 +19,12 @@
import {
CategoricalColorNamespace,
DataRecord,
getColumnLabel,
getMetricLabel,
getNumberFormatter,
getValueFormatter,
NumberFormats,
ValueFormatter,
getColumnLabel,
getValueFormatter,
} from '@superset-ui/core';
import { CallbackDataParams } from 'echarts/types/src/util/types';
import { EChartsCoreOption, FunnelSeriesOption } from 'echarts';
@@ -34,6 +34,7 @@ import {
EchartsFunnelFormData,
EchartsFunnelLabelTypeType,
FunnelChartTransformedProps,
PercentCalcType,
} from './types';
import {
extractGroupbyLabel,
@@ -43,7 +44,7 @@ import {
sanitizeHtml,
} from '../utils/series';
import { defaultGrid } from '../defaults';
import { OpacityEnum, DEFAULT_LEGEND_FORM_DATA } from '../constants';
import { DEFAULT_LEGEND_FORM_DATA, OpacityEnum } from '../constants';
import { getDefaultTooltip } from '../utils/tooltip';
import { Refs } from '../types';
@@ -53,17 +54,32 @@ export function formatFunnelLabel({
params,
labelType,
numberFormatter,
percentCalculationType = PercentCalcType.FIRST_STEP,
sanitizeName = false,
}: {
params: Pick<CallbackDataParams, 'name' | 'value' | 'percent'>;
params: Pick<CallbackDataParams, 'name' | 'value' | 'percent' | 'data'>;
labelType: EchartsFunnelLabelTypeType;
numberFormatter: ValueFormatter;
percentCalculationType?: PercentCalcType;
sanitizeName?: boolean;
}): string {
const { name: rawName = '', value, percent } = params;
const { name: rawName = '', value, percent: totalPercent, data } = params;
const name = sanitizeName ? sanitizeHtml(rawName) : rawName;
const formattedValue = numberFormatter(value as number);
const formattedPercent = percentFormatter((percent as number) / 100);
const { firstStepPercent, prevStepPercent } = data as {
firstStepPercent: number;
prevStepPercent: number;
};
let percent;
if (percentCalculationType === PercentCalcType.TOTAL) {
percent = (totalPercent ?? 0) / 100;
} else if (percentCalculationType === PercentCalcType.PREV_STEP) {
percent = prevStepPercent ?? 0;
} else {
percent = firstStepPercent ?? 0;
}
const formattedPercent = percentFormatter(percent);
switch (labelType) {
case EchartsFunnelLabelTypeType.Key:
@@ -119,6 +135,7 @@ export default function transformProps(
showTooltipLabels,
showLegend,
sliceId,
percentCalculationType,
}: EchartsFunnelFormData = {
...DEFAULT_LEGEND_FORM_DATA,
...DEFAULT_FUNNEL_FORM_DATA,
@@ -154,16 +171,24 @@ export default function transformProps(
currencyFormat,
);
const transformedData: FunnelSeriesOption[] = data.map(datum => {
const transformedData: {
value: number;
name: string;
itemStyle: { color: string; opacity: OpacityEnum };
}[] = data.map((datum, index) => {
const name = extractGroupbyLabel({
datum,
groupby: groupbyLabels,
coltypeMapping: {},
});
const value = datum[metricLabel] as number;
const isFiltered =
filterState.selectedValues && !filterState.selectedValues.includes(name);
const firstStepPercent = value / (data[0][metricLabel] as number);
const prevStepPercent =
index === 0 ? 1 : value / (data[index - 1][metricLabel] as number);
return {
value: datum[metricLabel],
value,
name,
itemStyle: {
color: colorFn(name, sliceId),
@@ -171,6 +196,8 @@ export default function transformProps(
? OpacityEnum.SemiTransparent
: OpacityEnum.NonTransparent,
},
firstStepPercent,
prevStepPercent,
};
});
@@ -188,7 +215,12 @@ export default function transformProps(
);
const formatter = (params: CallbackDataParams) =>
formatFunnelLabel({ params, numberFormatter, labelType });
formatFunnelLabel({
params,
numberFormatter,
labelType,
percentCalculationType,
});
const defaultLabel = {
formatter,
@@ -237,6 +269,7 @@ export default function transformProps(
params,
numberFormatter,
labelType: tooltipLabelType,
percentCalculationType,
}),
},
legend: {

View File

@@ -42,6 +42,7 @@ export type EchartsFunnelFormData = QueryFormData &
gap: number;
sort: 'descending' | 'ascending' | 'none' | undefined;
orient: 'vertical' | 'horizontal' | undefined;
percentCalculationType: PercentCalcType;
};
export enum EchartsFunnelLabelTypeType {
@@ -78,3 +79,9 @@ export type FunnelChartTransformedProps =
BaseTransformedProps<EchartsFunnelFormData> &
CrossFilterTransformedProps &
ContextMenuTransformedProps;
export enum PercentCalcType {
TOTAL = 'total',
PREV_STEP = 'prev_step',
FIRST_STEP = 'first_step',
}