mirror of https://github.com/buster-so/buster.git
bar percentage mode updates 🪿
This commit is contained in:
parent
4e068074c7
commit
1f15d0bb6a
|
@ -3,8 +3,6 @@ import { queryKeys } from '@/api/query_keys';
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
export const useGetCurrencies = () => {
|
||||
nextApi;
|
||||
|
||||
return useQuery({
|
||||
...queryKeys.getCurrencies,
|
||||
queryFn: async () => {
|
||||
|
|
|
@ -95,7 +95,8 @@ export const BusterChartJSComponent = React.memo(
|
|||
scatterDotSize,
|
||||
lineGroupType,
|
||||
categoryKeys: (selectedAxis as ScatterAxis).category,
|
||||
trendlineSeries
|
||||
trendlineSeries,
|
||||
barGroupType
|
||||
});
|
||||
|
||||
const { chartPlugins, chartOptions } = useChartSpecificOptions({
|
||||
|
|
|
@ -5,10 +5,10 @@ import type { Context } from 'chartjs-plugin-datalabels';
|
|||
export const formatBarAndLineDataLabel = (
|
||||
value: number,
|
||||
context: Context,
|
||||
usePercentage: boolean | undefined,
|
||||
percentageMode: false | 'stacked' | 'data-label',
|
||||
columnLabelFormat: ColumnLabelFormat
|
||||
) => {
|
||||
if (!usePercentage) {
|
||||
if (!percentageMode) {
|
||||
return formatLabel(value, columnLabelFormat);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,10 @@ export const formatBarAndLineDataLabel = (
|
|||
(dataset) => !dataset.hidden && !dataset.isTrendline
|
||||
);
|
||||
const hasMultipleDatasets = shownDatasets.length > 1;
|
||||
const total: number = hasMultipleDatasets
|
||||
|
||||
const useStackTotal = !hasMultipleDatasets || percentageMode === 'stacked';
|
||||
|
||||
const total: number = useStackTotal
|
||||
? context.chart.$totalizer.stackTotals[context.dataIndex]
|
||||
: context.chart.$totalizer.seriesTotals[context.datasetIndex];
|
||||
const percentage = ((value as number) / total) * 100;
|
||||
|
|
|
@ -16,6 +16,8 @@ export const BusterChartJSTooltip: React.FC<{
|
|||
hasCategoryAxis: boolean;
|
||||
hasMultipleMeasures: boolean;
|
||||
keyToUsePercentage: string[];
|
||||
lineGroupType: BusterChartProps['lineGroupType'];
|
||||
barGroupType: BusterChartProps['barGroupType'];
|
||||
}> = ({
|
||||
chart,
|
||||
dataPoints: dataPointsProp,
|
||||
|
@ -23,7 +25,9 @@ export const BusterChartJSTooltip: React.FC<{
|
|||
selectedChartType,
|
||||
hasCategoryAxis,
|
||||
keyToUsePercentage,
|
||||
hasMultipleMeasures
|
||||
hasMultipleMeasures,
|
||||
lineGroupType,
|
||||
barGroupType
|
||||
}) => {
|
||||
const isPieChart = selectedChartType === ChartType.Pie;
|
||||
const isScatter = selectedChartType === ChartType.Scatter;
|
||||
|
@ -34,6 +38,16 @@ export const BusterChartJSTooltip: React.FC<{
|
|||
const datasets = chart.data.datasets;
|
||||
const dataPoints = dataPointsProp.filter((item) => !item.dataset.isTrendline);
|
||||
|
||||
const percentageMode: undefined | 'stacked' = useMemo(() => {
|
||||
if (isBar) {
|
||||
return barGroupType === 'percentage-stack' ? 'stacked' : undefined;
|
||||
}
|
||||
if (isLine) {
|
||||
return lineGroupType === 'percentage-stack' ? 'stacked' : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}, [isBar, barGroupType, isLine, lineGroupType]);
|
||||
|
||||
const tooltipItems: ITooltipItem[] = useMemo(() => {
|
||||
if (isBar || isLine || isComboChart) {
|
||||
const hasMultipleShownDatasets =
|
||||
|
@ -47,7 +61,8 @@ export const BusterChartJSTooltip: React.FC<{
|
|||
hasMultipleMeasures,
|
||||
keyToUsePercentage,
|
||||
hasCategoryAxis,
|
||||
hasMultipleShownDatasets
|
||||
hasMultipleShownDatasets,
|
||||
percentageMode
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ export const barAndLineTooltipHelper = (
|
|||
hasMultipleMeasures: boolean,
|
||||
keyToUsePercentage: string[],
|
||||
hasCategoryAxis: boolean,
|
||||
hasMultipleShownDatasets: boolean
|
||||
hasMultipleShownDatasets: boolean,
|
||||
percentageMode: undefined | 'stacked'
|
||||
): ITooltipItem[] => {
|
||||
const dataPoint = dataPoints[0];
|
||||
const dataPointDataset = dataPoint.dataset;
|
||||
|
@ -28,7 +29,8 @@ export const barAndLineTooltipHelper = (
|
|||
// }
|
||||
|
||||
const tooltipItems = tooltipDatasets.map<ITooltipItem>((tooltipDataset) => {
|
||||
const usePercentage = keyToUsePercentage.includes(tooltipDataset.label as string);
|
||||
const usePercentage =
|
||||
!!percentageMode || keyToUsePercentage.includes(tooltipDataset.label as string);
|
||||
const assosciatedData = datasets.find((dataset) => dataset.label === tooltipDataset.label);
|
||||
const colorItem = assosciatedData?.backgroundColor as string;
|
||||
const color = assosciatedData
|
||||
|
@ -47,7 +49,8 @@ export const barAndLineTooltipHelper = (
|
|||
tooltipDataset.label as string,
|
||||
columnLabelFormats,
|
||||
chart,
|
||||
hasMultipleShownDatasets
|
||||
hasMultipleShownDatasets,
|
||||
percentageMode
|
||||
)
|
||||
: undefined;
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ export const getPercentage = (
|
|||
datasetKey: string,
|
||||
columnLabelFormats: NonNullable<BusterChartProps['columnLabelFormats']>,
|
||||
chart: Chart,
|
||||
hasMultipleShownDatasets: boolean
|
||||
hasMultipleShownDatasets: boolean,
|
||||
percentageMode: undefined | 'stacked'
|
||||
) => {
|
||||
if (hasMultipleShownDatasets) {
|
||||
if (hasMultipleShownDatasets || percentageMode === 'stacked') {
|
||||
return getStackedPercentage(rawValue, dataIndex, datasetKey, columnLabelFormats, chart);
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,9 @@ export const useTooltipOptions = ({
|
|||
keyToUsePercentage,
|
||||
columnSettings,
|
||||
hasCategoryAxis,
|
||||
hasMultipleMeasures
|
||||
hasMultipleMeasures,
|
||||
barGroupType,
|
||||
lineGroupType
|
||||
);
|
||||
|
||||
if (result) {
|
||||
|
@ -225,7 +227,9 @@ const externalTooltip = (
|
|||
keyToUsePercentage: string[],
|
||||
columnSettings: NonNullable<BusterChartProps['columnSettings']>,
|
||||
hasCategoryAxis: boolean,
|
||||
hasMultipleMeasures: boolean
|
||||
hasMultipleMeasures: boolean,
|
||||
barGroupType: BusterChartProps['barGroupType'],
|
||||
lineGroupType: BusterChartProps['lineGroupType']
|
||||
) => {
|
||||
const { chart, tooltip } = context;
|
||||
const tooltipEl = getOrCreateInitialTooltipContainer(chart)!;
|
||||
|
@ -251,6 +255,8 @@ const externalTooltip = (
|
|||
chart={chart}
|
||||
hasCategoryAxis={hasCategoryAxis}
|
||||
hasMultipleMeasures={hasMultipleMeasures}
|
||||
barGroupType={barGroupType}
|
||||
lineGroupType={lineGroupType}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ export const useXAxis = ({
|
|||
return {
|
||||
display: useGrid && gridLines,
|
||||
offset: true
|
||||
};
|
||||
} satisfies DeepPartial<GridLineOptions>;
|
||||
}, [gridLines, useGrid]);
|
||||
|
||||
const type: DeepPartial<ScaleChartOptions<'bar'>['scales']['x']['type']> = useMemo(() => {
|
||||
|
@ -168,12 +168,18 @@ export const useXAxis = ({
|
|||
return false;
|
||||
}, [type, xAxisTimeInterval]);
|
||||
|
||||
const offset = useMemo(() => {
|
||||
if (isScatterChart) return false;
|
||||
if (isLineChart) return lineGroupType !== 'percentage-stack';
|
||||
return true;
|
||||
}, [isScatterChart, isLineChart, lineGroupType]);
|
||||
|
||||
const memoizedXAxisOptions: DeepPartial<ScaleChartOptions<'bar'>['scales']['x']> | undefined =
|
||||
useMemo(() => {
|
||||
if (isPieChart) return undefined;
|
||||
return {
|
||||
type,
|
||||
offset: !isScatterChart,
|
||||
offset,
|
||||
title: {
|
||||
display: !!title,
|
||||
text: title
|
||||
|
@ -196,6 +202,7 @@ export const useXAxis = ({
|
|||
} satisfies DeepPartial<ScaleChartOptions<'bar'>['scales']['x']>;
|
||||
}, [
|
||||
timeUnit,
|
||||
offset,
|
||||
title,
|
||||
isScatterChart,
|
||||
isPieChart,
|
||||
|
|
|
@ -9,7 +9,11 @@ import { defaultLabelOptionConfig } from '../useChartSpecificOptions/labelOption
|
|||
import type { Options } from 'chartjs-plugin-datalabels/types/options';
|
||||
import { DEFAULT_CHART_LAYOUT } from '../../ChartJSTheme';
|
||||
import { extractFieldsFromChain } from '../../../chartHooks';
|
||||
import { DEFAULT_COLUMN_LABEL_FORMAT, IColumnLabelFormat } from '@/api/asset_interfaces/metric';
|
||||
import {
|
||||
BusterChartProps,
|
||||
DEFAULT_COLUMN_LABEL_FORMAT,
|
||||
IColumnLabelFormat
|
||||
} from '@/api/asset_interfaces/metric';
|
||||
|
||||
export const barSeriesBuilder = ({
|
||||
selectedDataset,
|
||||
|
@ -20,6 +24,7 @@ export const barSeriesBuilder = ({
|
|||
xAxisKeys,
|
||||
barShowTotalAtTop,
|
||||
allY2AxisKeysIndexes,
|
||||
barGroupType,
|
||||
...rest
|
||||
}: SeriesBuilderProps): ChartProps<'bar'>['data']['datasets'] => {
|
||||
const dataLabelOptions: Options['labels'] = {};
|
||||
|
@ -85,7 +90,8 @@ export const barSeriesBuilder = ({
|
|||
yAxisItem,
|
||||
index,
|
||||
xAxisKeys,
|
||||
dataLabelOptions
|
||||
dataLabelOptions,
|
||||
barGroupType
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -116,7 +122,8 @@ export const barBuilder = ({
|
|||
yAxisID,
|
||||
order,
|
||||
xAxisKeys,
|
||||
dataLabelOptions
|
||||
dataLabelOptions,
|
||||
barGroupType
|
||||
}: Pick<
|
||||
SeriesBuilderProps,
|
||||
'selectedDataset' | 'colors' | 'columnSettings' | 'columnLabelFormats'
|
||||
|
@ -127,12 +134,19 @@ export const barBuilder = ({
|
|||
order?: number;
|
||||
xAxisKeys: string[];
|
||||
dataLabelOptions?: Options['labels'];
|
||||
barGroupType: BusterChartProps['barGroupType'];
|
||||
}): ChartProps<'bar'>['data']['datasets'][number] => {
|
||||
const yKey = extractFieldsFromChain(yAxisItem.name).at(-1)?.key!;
|
||||
const columnSetting = columnSettings[yKey];
|
||||
const columnLabelFormat = columnLabelFormats[yKey];
|
||||
const usePercentage = !!columnSetting?.showDataLabelsAsPercentage;
|
||||
const showLabels = !!columnSetting?.showDataLabels;
|
||||
const isPercentageStackedBar = barGroupType === 'percentage-stack';
|
||||
|
||||
const percentageMode = isPercentageStackedBar
|
||||
? 'stacked'
|
||||
: columnSetting?.showDataLabelsAsPercentage
|
||||
? 'data-label'
|
||||
: false;
|
||||
|
||||
return {
|
||||
type: 'bar',
|
||||
|
@ -170,7 +184,7 @@ export const barBuilder = ({
|
|||
if (barWidth < MAX_BAR_WIDTH) return false;
|
||||
|
||||
const formattedValue = getFormattedValue(context, {
|
||||
usePercentage,
|
||||
percentageMode,
|
||||
columnLabelFormat: columnLabelFormat || DEFAULT_COLUMN_LABEL_FORMAT
|
||||
});
|
||||
|
||||
|
@ -286,10 +300,10 @@ const setGlobalRotation = (context: Context) => {
|
|||
const getFormattedValue = (
|
||||
context: Context,
|
||||
{
|
||||
usePercentage,
|
||||
percentageMode,
|
||||
columnLabelFormat
|
||||
}: {
|
||||
usePercentage: boolean;
|
||||
percentageMode: false | 'stacked' | 'data-label';
|
||||
columnLabelFormat: IColumnLabelFormat;
|
||||
}
|
||||
) => {
|
||||
|
@ -297,7 +311,7 @@ const getFormattedValue = (
|
|||
const currentValue =
|
||||
context.chart.$barDataLabels?.[context.datasetIndex]?.[context.dataIndex] || '';
|
||||
const formattedValue =
|
||||
currentValue || formatBarAndLineDataLabel(rawValue, context, usePercentage, columnLabelFormat);
|
||||
currentValue || formatBarAndLineDataLabel(rawValue, context, percentageMode, columnLabelFormat);
|
||||
// Store only the formatted value, rotation is handled globally
|
||||
setBarDataLabelsManager(context, formattedValue);
|
||||
|
||||
|
|
|
@ -30,4 +30,5 @@ export interface SeriesBuilderProps {
|
|||
lineGroupType: BusterChartProps['lineGroupType'];
|
||||
selectedChartType: BusterChartProps['selectedChartType'];
|
||||
barShowTotalAtTop: BusterChartProps['barShowTotalAtTop'];
|
||||
barGroupType: BusterChartProps['barGroupType'];
|
||||
}
|
||||
|
|
|
@ -89,7 +89,11 @@ export const lineBuilder = (
|
|||
const isStackedArea = lineGroupType === 'percentage-stack';
|
||||
const isArea = lineStyle === 'area' || isStackedArea;
|
||||
const fill = isArea ? (index === 0 ? 'origin' : '-1') : false;
|
||||
const usePercentage = isStackedArea;
|
||||
const percentageMode = isStackedArea
|
||||
? 'stacked'
|
||||
: columnSetting.showDataLabelsAsPercentage
|
||||
? 'data-label'
|
||||
: false;
|
||||
|
||||
return {
|
||||
type: 'line',
|
||||
|
@ -127,7 +131,7 @@ export const lineBuilder = (
|
|||
}
|
||||
: false,
|
||||
formatter: (value, context) =>
|
||||
formatBarAndLineDataLabel(value, context, usePercentage, columnLabelFormat),
|
||||
formatBarAndLineDataLabel(value, context, percentageMode, columnLabelFormat),
|
||||
...getLabelPosition(isStackedArea),
|
||||
...defaultLabelOptionConfig
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface UseSeriesOptionsProps {
|
|||
scatterDotSize: BusterChartProps['scatterDotSize'];
|
||||
columnMetadata: ColumnMetaData[];
|
||||
lineGroupType: BusterChartProps['lineGroupType'];
|
||||
barGroupType: BusterChartProps['barGroupType'];
|
||||
trendlineSeries: ChartProps<'line'>['data']['datasets'][number][];
|
||||
barShowTotalAtTop: BusterChartProps['barShowTotalAtTop'];
|
||||
}
|
||||
|
@ -53,7 +54,8 @@ export const useSeriesOptions = ({
|
|||
scatterDotSize,
|
||||
lineGroupType,
|
||||
categoryKeys,
|
||||
barShowTotalAtTop
|
||||
barShowTotalAtTop,
|
||||
barGroupType
|
||||
}: UseSeriesOptionsProps): ChartProps<ChartJSChartType>['data'] => {
|
||||
const selectedDataset = useMemo(() => {
|
||||
return datasetOptions[datasetOptions.length - 1];
|
||||
|
@ -134,7 +136,8 @@ export const useSeriesOptions = ({
|
|||
scatterDotSize,
|
||||
lineGroupType,
|
||||
categoryKeys,
|
||||
selectedChartType
|
||||
selectedChartType,
|
||||
barGroupType
|
||||
});
|
||||
}, [
|
||||
selectedDataset,
|
||||
|
|
|
@ -371,3 +371,69 @@ export const NumericMonthX: Story = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const PercentageStackedLineSingle: Story = {
|
||||
args: {
|
||||
selectedChartType: ChartType.Line,
|
||||
data: generateLineChartData(),
|
||||
lineGroupType: 'percentage-stack',
|
||||
barAndLineAxis: {
|
||||
x: ['date'],
|
||||
y: ['revenue'],
|
||||
category: []
|
||||
},
|
||||
className: 'w-[800px] h-[400px]',
|
||||
columnLabelFormats: {
|
||||
month: {
|
||||
columnType: 'number',
|
||||
style: 'date',
|
||||
dateFormat: 'MMM',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
} satisfies IColumnLabelFormat,
|
||||
sales: {
|
||||
columnType: 'number',
|
||||
style: 'currency',
|
||||
currency: 'USD'
|
||||
} satisfies IColumnLabelFormat,
|
||||
customers: {
|
||||
columnType: 'number',
|
||||
style: 'number',
|
||||
numberSeparatorStyle: ','
|
||||
} satisfies IColumnLabelFormat
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const PercentageStackedLineMultiple: Story = {
|
||||
args: {
|
||||
selectedChartType: ChartType.Line,
|
||||
data: generateLineChartData(),
|
||||
lineGroupType: 'percentage-stack',
|
||||
barAndLineAxis: {
|
||||
x: ['date'],
|
||||
y: ['revenue', 'profit', 'customers'],
|
||||
category: []
|
||||
},
|
||||
className: 'w-[800px] h-[400px]',
|
||||
columnLabelFormats: {
|
||||
date: {
|
||||
columnType: 'number',
|
||||
style: 'date',
|
||||
dateFormat: 'll',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
} satisfies IColumnLabelFormat,
|
||||
sales: {
|
||||
columnType: 'number',
|
||||
style: 'currency',
|
||||
currency: 'USD'
|
||||
} satisfies IColumnLabelFormat,
|
||||
customers: {
|
||||
columnType: 'number',
|
||||
style: 'number',
|
||||
numberSeparatorStyle: ','
|
||||
} satisfies IColumnLabelFormat
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -315,7 +315,7 @@ const LabelSettings: React.FC<{
|
|||
return formatLabel(id, columnLabelFormat, true);
|
||||
}, [displayName]);
|
||||
|
||||
//THIS IS HERE JUST TO PREFETCH THE CURRENCIES
|
||||
//THIS IS HERE JUST TO PREFETCH THE CURRENCIES, I guess I could use prefetch...
|
||||
useGetCurrencies();
|
||||
|
||||
const ComponentsLoop = [
|
||||
|
|
Loading…
Reference in New Issue