mirror of https://github.com/buster-so/buster.git
Merge pull request #1221 from buster-so/big-nate-bus-1985-colorby-tooltip-and-legend-defaults
tooltip adjustments for color by and skip null
This commit is contained in:
commit
e9d86488c6
|
@ -1,4 +1,4 @@
|
||||||
import type { ChartConfigProps, ChartEncodes } from '@buster/server-shared/metrics';
|
import type { BarAndLineAxis, ChartConfigProps, ChartEncodes } from '@buster/server-shared/metrics';
|
||||||
import type { ChartType as ChartJSChartType, PluginChartOptions } from 'chart.js';
|
import type { ChartType as ChartJSChartType, PluginChartOptions } from 'chart.js';
|
||||||
import type { AnnotationPluginOptions } from 'chartjs-plugin-annotation';
|
import type { AnnotationPluginOptions } from 'chartjs-plugin-annotation';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
@ -203,9 +203,10 @@ export const useOptions = ({
|
||||||
const options: ChartProps<ChartJSChartType>['options'] = useMemo(() => {
|
const options: ChartProps<ChartJSChartType>['options'] = useMemo(() => {
|
||||||
const chartAnnotations = chartPlugins?.annotation?.annotations;
|
const chartAnnotations = chartPlugins?.annotation?.annotations;
|
||||||
const isLargeDataset = numberOfDataPoints > LINE_DECIMATION_THRESHOLD;
|
const isLargeDataset = numberOfDataPoints > LINE_DECIMATION_THRESHOLD;
|
||||||
|
const hasColorBy = (selectedAxis as BarAndLineAxis).colorBy?.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
skipNull: true,
|
skipNull: hasColorBy,
|
||||||
indexAxis: isHorizontalBar ? 'y' : 'x',
|
indexAxis: isHorizontalBar ? 'y' : 'x',
|
||||||
backgroundColor: colors,
|
backgroundColor: colors,
|
||||||
borderColor: colors,
|
borderColor: colors,
|
||||||
|
|
|
@ -48,17 +48,37 @@ export const BusterChartJSTooltip: React.FC<{
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [isBar, barGroupType, isLine, lineGroupType]);
|
}, [isBar, barGroupType, isLine, lineGroupType]);
|
||||||
|
|
||||||
|
//@ts-expect-error - skipNull is not typed, only for some charts but yolo
|
||||||
|
const skipNull = chart.options.skipNull === true;
|
||||||
|
|
||||||
|
const hasMultipleShownDatasets = useMemo(() => {
|
||||||
|
const nonHiddenDatasets = datasets.filter((dataset) => !dataset.hidden);
|
||||||
|
if (nonHiddenDatasets.length <= 1) return false;
|
||||||
|
|
||||||
|
if (!skipNull) return nonHiddenDatasets.length > 1; //color by will skip nulls
|
||||||
|
|
||||||
|
// Collect unique yAxisKeys from non-hidden datasets
|
||||||
|
const uniqueYAxisKeys = new Set<string>();
|
||||||
|
|
||||||
|
nonHiddenDatasets.forEach((dataset) => {
|
||||||
|
if (dataset.yAxisKey) {
|
||||||
|
uniqueYAxisKeys.add(dataset.yAxisKey as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return !(uniqueYAxisKeys.size > 1);
|
||||||
|
}, [datasets]);
|
||||||
|
|
||||||
const tooltipItems: ITooltipItem[] = useMemo(() => {
|
const tooltipItems: ITooltipItem[] = useMemo(() => {
|
||||||
if (isBar || isLine || isComboChart) {
|
if (isBar || isLine || isComboChart) {
|
||||||
const hasMultipleShownDatasets = datasets.filter((dataset) => !dataset.hidden).length > 1;
|
|
||||||
|
|
||||||
return barAndLineTooltipHelper(
|
return barAndLineTooltipHelper(
|
||||||
dataPoints,
|
dataPoints,
|
||||||
chart,
|
chart,
|
||||||
columnLabelFormats,
|
columnLabelFormats,
|
||||||
keyToUsePercentage,
|
keyToUsePercentage,
|
||||||
hasMultipleShownDatasets,
|
hasMultipleShownDatasets,
|
||||||
percentageMode
|
percentageMode,
|
||||||
|
skipNull
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,8 @@ describe('barAndLineTooltipHelper', () => {
|
||||||
mockColumnLabelFormats,
|
mockColumnLabelFormats,
|
||||||
[],
|
[],
|
||||||
false, // hasMultipleShownDatasets = false
|
false, // hasMultipleShownDatasets = false
|
||||||
undefined
|
undefined,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -100,7 +101,8 @@ describe('barAndLineTooltipHelper', () => {
|
||||||
mockColumnLabelFormats,
|
mockColumnLabelFormats,
|
||||||
[],
|
[],
|
||||||
true, // hasMultipleShownDatasets = true
|
true, // hasMultipleShownDatasets = true
|
||||||
undefined
|
undefined,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -132,7 +134,8 @@ describe('barAndLineTooltipHelper', () => {
|
||||||
mockColumnLabelFormats,
|
mockColumnLabelFormats,
|
||||||
[],
|
[],
|
||||||
false,
|
false,
|
||||||
undefined
|
undefined,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -166,7 +169,8 @@ describe('barAndLineTooltipHelper', () => {
|
||||||
mockColumnLabelFormats,
|
mockColumnLabelFormats,
|
||||||
[],
|
[],
|
||||||
false,
|
false,
|
||||||
undefined
|
undefined,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -198,7 +202,8 @@ describe('barAndLineTooltipHelper', () => {
|
||||||
mockColumnLabelFormats,
|
mockColumnLabelFormats,
|
||||||
[],
|
[],
|
||||||
false,
|
false,
|
||||||
undefined
|
undefined,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -240,7 +245,8 @@ describe('barAndLineTooltipHelper', () => {
|
||||||
mockColumnLabelFormats,
|
mockColumnLabelFormats,
|
||||||
['percentage_metric'],
|
['percentage_metric'],
|
||||||
true,
|
true,
|
||||||
'stacked' // percentageMode = 'stacked'
|
'stacked', // percentageMode = 'stacked'
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -258,4 +264,60 @@ describe('barAndLineTooltipHelper', () => {
|
||||||
// Verify getPercentage was called for both items
|
// Verify getPercentage was called for both items
|
||||||
expect(mockGetPercentage).toHaveBeenCalledTimes(2);
|
expect(mockGetPercentage).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should filter out data points with null raw values when skipNull is true', () => {
|
||||||
|
// Arrange
|
||||||
|
const mockDataPoints: TooltipItem<keyof ChartTypeRegistry>[] = [
|
||||||
|
{
|
||||||
|
dataset: {
|
||||||
|
label: 'Revenue Dataset',
|
||||||
|
backgroundColor: '#ff0000',
|
||||||
|
tooltipData: [[{ key: 'revenue', value: 1000 }]],
|
||||||
|
yAxisKey: 'revenue',
|
||||||
|
},
|
||||||
|
dataIndex: 0,
|
||||||
|
datasetIndex: 0,
|
||||||
|
raw: 1000, // Valid data point
|
||||||
|
} as unknown as TooltipItem<keyof ChartTypeRegistry>,
|
||||||
|
{
|
||||||
|
dataset: {
|
||||||
|
label: 'Null Dataset',
|
||||||
|
backgroundColor: '#00ff00',
|
||||||
|
tooltipData: [[{ key: 'null_metric', value: null }]],
|
||||||
|
yAxisKey: 'null_metric',
|
||||||
|
},
|
||||||
|
dataIndex: 1,
|
||||||
|
datasetIndex: 1,
|
||||||
|
raw: null, // This should be filtered out
|
||||||
|
} as unknown as TooltipItem<keyof ChartTypeRegistry>,
|
||||||
|
{
|
||||||
|
dataset: {
|
||||||
|
label: 'Another Dataset',
|
||||||
|
backgroundColor: '#0000ff',
|
||||||
|
tooltipData: [[{ key: 'another', value: 500 }]],
|
||||||
|
yAxisKey: 'another',
|
||||||
|
},
|
||||||
|
dataIndex: 2,
|
||||||
|
datasetIndex: 2,
|
||||||
|
raw: 500, // Valid data point
|
||||||
|
} as unknown as TooltipItem<keyof ChartTypeRegistry>,
|
||||||
|
];
|
||||||
|
|
||||||
|
mockFormatLabel.mockReturnValue('formatted');
|
||||||
|
mockGetPercentage.mockReturnValue('25%');
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = barAndLineTooltipHelper(
|
||||||
|
mockDataPoints,
|
||||||
|
mockChart,
|
||||||
|
mockColumnLabelFormats,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
true // skipNull = true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toHaveLength(1); // Should only return 2 items (null one filtered out)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,12 +10,17 @@ export const barAndLineTooltipHelper = (
|
||||||
columnLabelFormats: NonNullable<ChartConfigProps['columnLabelFormats']>,
|
columnLabelFormats: NonNullable<ChartConfigProps['columnLabelFormats']>,
|
||||||
keyToUsePercentage: string[],
|
keyToUsePercentage: string[],
|
||||||
hasMultipleShownDatasets: boolean,
|
hasMultipleShownDatasets: boolean,
|
||||||
percentageMode: undefined | 'stacked'
|
percentageMode: undefined | 'stacked',
|
||||||
|
skipNull: boolean
|
||||||
): ITooltipItem[] => {
|
): ITooltipItem[] => {
|
||||||
if (percentageMode) {
|
if (percentageMode) {
|
||||||
dataPoints.reverse(); //we do this because the data points are in reverse order and it looks better
|
dataPoints.reverse(); //we do this because the data points are in reverse order and it looks better
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skipNull) {
|
||||||
|
dataPoints = dataPoints.filter((dataPoint) => dataPoint.raw !== null);
|
||||||
|
}
|
||||||
|
|
||||||
const tooltipItems = dataPoints.flatMap<ITooltipItem>((dataPoint) => {
|
const tooltipItems = dataPoints.flatMap<ITooltipItem>((dataPoint) => {
|
||||||
const tooltipDataset = dataPoint.dataset;
|
const tooltipDataset = dataPoint.dataset;
|
||||||
const dataPointDataIndex = dataPoint.dataIndex;
|
const dataPointDataIndex = dataPoint.dataIndex;
|
||||||
|
|
Loading…
Reference in New Issue