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:
Nate Kelley 2025-09-30 13:47:41 -06:00 committed by GitHub
commit e9d86488c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 100 additions and 12 deletions

View File

@ -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 { AnnotationPluginOptions } from 'chartjs-plugin-annotation';
import { useMemo } from 'react';
@ -203,9 +203,10 @@ export const useOptions = ({
const options: ChartProps<ChartJSChartType>['options'] = useMemo(() => {
const chartAnnotations = chartPlugins?.annotation?.annotations;
const isLargeDataset = numberOfDataPoints > LINE_DECIMATION_THRESHOLD;
const hasColorBy = (selectedAxis as BarAndLineAxis).colorBy?.length > 0;
return {
skipNull: true,
skipNull: hasColorBy,
indexAxis: isHorizontalBar ? 'y' : 'x',
backgroundColor: colors,
borderColor: colors,

View File

@ -48,17 +48,37 @@ export const BusterChartJSTooltip: React.FC<{
return undefined;
}, [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(() => {
if (isBar || isLine || isComboChart) {
const hasMultipleShownDatasets = datasets.filter((dataset) => !dataset.hidden).length > 1;
return barAndLineTooltipHelper(
dataPoints,
chart,
columnLabelFormats,
keyToUsePercentage,
hasMultipleShownDatasets,
percentageMode
percentageMode,
skipNull
);
}

View File

@ -55,7 +55,8 @@ describe('barAndLineTooltipHelper', () => {
mockColumnLabelFormats,
[],
false, // hasMultipleShownDatasets = false
undefined
undefined,
false
);
// Assert
@ -100,7 +101,8 @@ describe('barAndLineTooltipHelper', () => {
mockColumnLabelFormats,
[],
true, // hasMultipleShownDatasets = true
undefined
undefined,
false
);
// Assert
@ -132,7 +134,8 @@ describe('barAndLineTooltipHelper', () => {
mockColumnLabelFormats,
[],
false,
undefined
undefined,
false
);
// Assert
@ -166,7 +169,8 @@ describe('barAndLineTooltipHelper', () => {
mockColumnLabelFormats,
[],
false,
undefined
undefined,
false
);
// Assert
@ -198,7 +202,8 @@ describe('barAndLineTooltipHelper', () => {
mockColumnLabelFormats,
[],
false,
undefined
undefined,
false
);
// Assert
@ -240,7 +245,8 @@ describe('barAndLineTooltipHelper', () => {
mockColumnLabelFormats,
['percentage_metric'],
true,
'stacked' // percentageMode = 'stacked'
'stacked', // percentageMode = 'stacked'
false
);
// Assert
@ -258,4 +264,60 @@ describe('barAndLineTooltipHelper', () => {
// Verify getPercentage was called for both items
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)
});
});

View File

@ -10,12 +10,17 @@ export const barAndLineTooltipHelper = (
columnLabelFormats: NonNullable<ChartConfigProps['columnLabelFormats']>,
keyToUsePercentage: string[],
hasMultipleShownDatasets: boolean,
percentageMode: undefined | 'stacked'
percentageMode: undefined | 'stacked',
skipNull: boolean
): ITooltipItem[] => {
if (percentageMode) {
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 tooltipDataset = dataPoint.dataset;
const dataPointDataIndex = dataPoint.dataIndex;