scatter x axis formatter

This commit is contained in:
Nate Kelley 2025-05-09 14:54:12 -06:00
parent e66f26f7a2
commit 3656ee586c
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 160 additions and 9 deletions

View File

@ -284,4 +284,132 @@ describe('useXAxis', () => {
expect(result.current?.title?.display).toBe(false);
expect(result.current?.title?.text).toBe('');
});
it('should correctly set xAxisColumnFormats and firstXColumnLabelFormat', () => {
// Test with default category column
const { result } = renderHook(() => useXAxis(defaultProps));
// Get the internal state to verify xAxisColumnFormats and firstXColumnLabelFormat
// We need to access the useXAxis hook results
expect(result.current).toBeDefined();
// For scatter chart
const scatterProps = {
...defaultProps,
selectedChartType: ChartType.Scatter,
selectedAxis: {
x: ['numeric_column'],
y: ['numeric_column']
} as ChartEncodes
};
const { result: scatterResult } = renderHook(() => useXAxis(scatterProps));
expect(scatterResult.current).toBeDefined();
expect(scatterResult.current?.type).toBe('linear');
// For multiple x-axis columns
const multipleColumnsProps = {
...defaultProps,
selectedAxis: {
x: ['category_column', 'numeric_column'],
y: ['numeric_column']
} as ChartEncodes
};
const { result: multiResult } = renderHook(() => useXAxis(multipleColumnsProps));
expect(multiResult.current).toBeDefined();
expect(multiResult.current?.type).toBe('category');
// For unsupported chart type (pie)
const pieProps = {
...defaultProps,
selectedChartType: ChartType.Pie
};
const { result: pieResult } = renderHook(() => useXAxis(pieProps));
expect(pieResult.current).toBeUndefined();
});
it('should correctly handle firstXColumnLabelFormat for scatter chart', () => {
// Specifically test the firstXColumnLabelFormat logic for scatter charts
const scatterProps = {
...defaultProps,
selectedChartType: ChartType.Scatter,
selectedAxis: {
x: ['numeric_column'],
y: ['numeric_column']
} as ChartEncodes
};
const { result } = renderHook(() => useXAxis(scatterProps));
// Verify that scatter charts get proper formatting with minimumFractionDigits and maximumFractionDigits set to 0
expect(result.current).toBeDefined();
expect(result.current?.type).toBe('linear');
// Test the tick callback formatting
// We can't directly access the firstXColumnLabelFormat from outside the hook,
// but we can test its effects through the tick callback behavior
expect(result.current?.ticks?.callback).toBeDefined();
// Verify custom column label formats are applied
const customFormatProps = {
...scatterProps,
columnLabelFormats: {
...defaultProps.columnLabelFormats,
numeric_column: {
...DEFAULT_COLUMN_LABEL_FORMAT,
columnType: 'number' as SimplifiedColumnType,
style: 'number' as const,
minimumFractionDigits: 3,
maximumFractionDigits: 3
}
}
};
const { result: customResult } = renderHook(() => useXAxis(customFormatProps));
expect(customResult.current).toBeDefined();
expect(customResult.current?.type).toBe('linear');
});
it('should apply correct column formats for date columns with different formats', () => {
// Test with custom date format
const customDateFormatProps = {
...defaultProps,
selectedChartType: ChartType.Line,
selectedAxis: {
x: ['date_column'],
y: ['numeric_column']
} as ChartEncodes,
columnLabelFormats: {
...defaultProps.columnLabelFormats,
date_column: {
...defaultProps.columnLabelFormats.date_column,
dateFormat: 'YYYY-MM-DD'
}
}
};
const { result } = renderHook(() => useXAxis(customDateFormatProps));
expect(result.current).toBeDefined();
expect(result.current?.type).toBe('time');
expect(result.current?.ticks?.callback).toBeDefined();
// Test with auto date format
const autoDateFormatProps = {
...customDateFormatProps,
columnLabelFormats: {
...defaultProps.columnLabelFormats,
date_column: {
...defaultProps.columnLabelFormats.date_column,
dateFormat: 'auto'
}
}
};
const { result: autoResult } = renderHook(() => useXAxis(autoDateFormatProps));
expect(autoResult.current).toBeDefined();
expect(autoResult.current?.type).toBe('time');
expect(autoResult.current?.ticks?.callback).toBeDefined();
});
});

View File

@ -67,6 +67,17 @@ export const useXAxis = ({
}, {});
}, [selectedAxis.x, columnLabelFormats, isSupportedType]);
const firstXColumnLabelFormat = useMemo(() => {
if (isScatterChart) {
return {
...xAxisColumnFormats[selectedAxis.x[0]],
minimumFractionDigits: 0,
maximumFractionDigits: 0
};
}
return xAxisColumnFormats[selectedAxis.x[0]];
}, [isScatterChart, xAxisColumnFormats, selectedAxis.x]);
const stacked = useIsStacked({ selectedChartType, lineGroupType, barGroupType });
const grid: DeepPartial<GridLineOptions> | undefined = useMemo(() => {
@ -79,10 +90,9 @@ export const useXAxis = ({
const type: DeepPartial<ScaleChartOptions<'bar'>['scales']['x']['type']> = useMemo(() => {
const xAxisKeys = Object.keys(xAxisColumnFormats);
const xAxisKeysLength = xAxisKeys.length;
const firstXKey = xAxisKeys[0];
if (xAxisKeysLength === 1) {
const xIsDate = xAxisColumnFormats[firstXKey].columnType === 'date';
const xIsDate = firstXColumnLabelFormat.columnType === 'date';
if ((isLineChart || isScatterChart) && xIsDate) {
return 'time';
@ -101,17 +111,24 @@ export const useXAxis = ({
}
if (isScatterChart && xAxisKeysLength === 1) {
const isNumeric = isNumericColumnType(xAxisColumnFormats[firstXKey]?.columnType);
const isNumeric = isNumericColumnType(firstXColumnLabelFormat?.columnType);
if (isNumeric) return 'linear';
}
return 'category';
}, [isScatterChart, isComboChart, isLineChart, columnSettings, xAxisColumnFormats]);
}, [
isScatterChart,
isComboChart,
isLineChart,
columnSettings,
xAxisColumnFormats,
firstXColumnLabelFormat
]);
const derivedTimeUnit = useMemo(() => {
if (type !== 'time') return false;
const fmt = xAxisColumnFormats[selectedAxis.x[0]].dateFormat;
const fmt = firstXColumnLabelFormat.dateFormat;
if (!fmt || fmt === 'auto') return false;
// look for patterns in your DATE_FORMATS keys
@ -122,7 +139,7 @@ export const useXAxis = ({
if (/H{1,2}/.test(fmt)) return 'hour';
// fall back
return false;
}, [xAxisColumnFormats, selectedAxis.x]);
}, [firstXColumnLabelFormat]);
const title = useXAxisTitle({
xAxis: selectedAxis.x,
@ -141,8 +158,7 @@ export const useXAxis = ({
const rawValue = this.getLabelForValue(value as number);
if (type === 'time' || isDate(rawValue)) {
const xKey = selectedAxis.x[0];
const xColumnLabelFormat = xAxisColumnFormats[xKey];
const xColumnLabelFormat = firstXColumnLabelFormat;
const isAutoFormat = xColumnLabelFormat.dateFormat === 'auto';
if (isAutoFormat) {
const unit = (this.chart.scales['x'] as TimeScale)._unit as
@ -162,6 +178,11 @@ export const useXAxis = ({
return truncateText(res, 24);
}
if (isScatterChart) {
//raw value does not work for scatter charts, it returns the value as a string
return formatLabel(value, firstXColumnLabelFormat);
}
return DEFAULT_X_AXIS_TICK_CALLBACK.call(this, value, index, this.getLabels() as any);
});

View File

@ -81,7 +81,9 @@ export const formatLabel = (
if (style === 'currency') {
formattedText = formatNumber(roundedNumber, {
currency,
compact: compactNumbers
compact: compactNumbers,
minimumFractionDigits: Math.min(minimumFractionDigits, maximumFractionDigits),
maximumFractionDigits: Math.max(minimumFractionDigits, maximumFractionDigits)
});
} else {
formattedText = formatNumber(roundedNumber, {