bar series updater

This commit is contained in:
Nate Kelley 2025-04-03 11:33:34 -06:00
parent 624452b8f2
commit 2221de1836
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
1 changed files with 104 additions and 40 deletions

View File

@ -33,6 +33,12 @@ export const barSeriesBuilder = ({
dataLabelOptions.stackTotal = {
display: function (context) {
// Reset the global rotation flag when processing the first data point
if (context.dataIndex === 0 && context.datasetIndex === 0) {
context.chart.$barDataLabelsGlobalRotation = false;
context.chart.$barDataLabelsUpdateInProgress = false;
}
const chart = context.chart;
const shownDatasets = context.chart.data.datasets.filter(
(dataset, index) =>
@ -46,8 +52,18 @@ export const barSeriesBuilder = ({
const chartLayout = context.chart.options.layout;
const padding = { ...DEFAULT_CHART_LAYOUT.padding, top: 24 };
context.chart.options.layout = { ...chartLayout, padding };
//use setTimeout to ensure that the chart data label almost always overflows.
requestAnimationFrame(() => {
context.chart.update(); //this is hack because the chart data label almost always overflows
if (!context.chart.$barDataLabelsUpdateInProgress) {
context.chart.$barDataLabelsUpdateInProgress = true;
console.log('updating');
context.chart.update('none'); //this is hack because the chart data label almost always overflows
// Reset the flag after the update completes
setTimeout(() => {
context.chart.$barDataLabelsUpdateInProgress = false;
}, 100);
}
});
hasBeenDrawn = true;
}
@ -92,19 +108,14 @@ export const barSeriesBuilder = ({
declare module 'chart.js' {
interface Chart {
$barDataLabels: Record<
number,
Record<
number,
{
formattedValue: string;
rotation: number;
}
>
>;
$barDataLabels: Record<number, Record<number, string>>;
$barDataLabelsGlobalRotation: boolean;
$barDataLabelsUpdateInProgress: boolean;
}
}
const TEXT_WIDTH_BUFFER = 4;
export const barBuilder = ({
selectedDataset,
colors,
@ -133,8 +144,6 @@ export const barBuilder = ({
const usePercentage = !!columnSetting?.showDataLabelsAsPercentage;
const showLabels = !!columnSetting?.showDataLabels;
const textWidthBuffer = 4;
return {
type: 'bar',
label: yAxisItem.name,
@ -150,38 +159,106 @@ export const barBuilder = ({
labels: {
barTotal: {
display: (context) => {
// Initialize the global rotation flag if it doesn't exist
if (context.chart.$barDataLabelsGlobalRotation === undefined) {
context.chart.$barDataLabelsGlobalRotation = false;
context.chart.$barDataLabelsUpdateInProgress = false;
}
// First dataset - analyze all data points to determine if any need rotation
if (index === 0 && context.dataIndex === 0) {
// Reset the rotation flag at the start of each render cycle
context.chart.$barDataLabelsGlobalRotation = false;
// Analyze all datasets and datapoints in this first call
const checkAllLabelsForRotation = () => {
const datasets = context.chart.data.datasets;
const needsGlobalRotation = datasets.some((dataset, datasetIndex) => {
if (dataset.type !== 'bar') return false;
return Array.from({ length: dataset.data.length }).some((_, dataIndex) => {
const value = dataset.data[dataIndex] as number;
if (!value) return false;
// Get dimensions for this data point
const meta = context.chart.getDatasetMeta(datasetIndex);
if (!meta || !meta.data[dataIndex]) return false;
const barElement = meta.data[dataIndex] as BarElement;
const { width: barWidth } = barElement.getProps(['width'], true);
// Only proceed if bar is visible and has reasonable width
if (barWidth < 13) return false;
// Check if formatted value would need rotation
const formattedValue = formatBarAndLineDataLabel(
value,
{ ...context, datasetIndex, dataIndex } as Context,
false, // We're just checking width, not actual formatting
columnLabelFormat
);
const { width: textWidth } = context.chart.ctx.measureText(formattedValue);
return textWidth > barWidth - TEXT_WIDTH_BUFFER;
});
});
if (needsGlobalRotation) {
context.chart.$barDataLabelsGlobalRotation = true;
// Schedule update after all display calculations
if (!context.chart.$barDataLabelsUpdateInProgress) {
context.chart.$barDataLabelsUpdateInProgress = true;
setTimeout(() => {
context.chart.update('none');
setTimeout(() => {
context.chart.$barDataLabelsUpdateInProgress = false;
}, 100);
}, 0);
}
}
};
// Run rotation analysis immediately
checkAllLabelsForRotation();
}
const rawValue = context.dataset.data[context.dataIndex] as number;
if (!showLabels || !rawValue) return false;
const { barWidth, barHeight } = getBarDimensions(context);
const { barWidth, barHeight } = getBarDimensions(context);
if (barWidth < 13) return false;
const formattedValue = formatBarAndLineDataLabel(
rawValue,
context,
usePercentage,
columnLabelFormat
);
const { width: widthOfFormattedValue } = context.chart.ctx.measureText(formattedValue);
const rotation = widthOfFormattedValue > barWidth - textWidthBuffer ? -90 : 0;
if (rotation === -90 && widthOfFormattedValue > barHeight - textWidthBuffer) {
// Get text width for this specific label
const { width: textWidth } = context.chart.ctx.measureText(formattedValue);
// Use the global rotation setting
const rotation = context.chart.$barDataLabelsGlobalRotation ? -90 : 0;
// Check if this label can be displayed even with rotation
if (rotation === -90 && textWidth > barHeight - TEXT_WIDTH_BUFFER) {
return false;
}
setBarDataLabelsManager(context, formattedValue, rotation);
// Store only the formatted value, rotation is handled globally
setBarDataLabelsManager(context, formattedValue);
if (barHeight < 16) return false;
return 'auto';
},
formatter: (_, context) => {
const formattedValue = getBarDataLabelsManager(context).formattedValue;
return formattedValue;
return context.chart.$barDataLabels?.[context.datasetIndex]?.[context.dataIndex] || '';
},
rotation: (context) => {
const { rotation } = getBarDataLabelsManager(context);
return rotation;
// Always use the global rotation setting
return context.chart.$barDataLabelsGlobalRotation ? -90 : 0;
},
color: dataLabelFontColorContrast,
borderWidth: 0,
@ -200,17 +277,15 @@ export const barBuilder = ({
} as ChartProps<'bar'>['data']['datasets'][number];
};
const setBarDataLabelsManager = (context: Context, formattedValue: string, rotation: number) => {
const setBarDataLabelsManager = (context: Context, formattedValue: string) => {
const dataIndex = context.dataIndex;
const datasetIndex = context.datasetIndex;
context.chart.$barDataLabels = {
...context.chart.$barDataLabels,
[datasetIndex]: {
...context.chart.$barDataLabels?.[datasetIndex],
[dataIndex]: {
formattedValue,
rotation
}
[dataIndex]: formattedValue
}
};
};
@ -224,17 +299,6 @@ const getBarDimensions = (context: Context) => {
return { barWidth, barHeight };
};
const getBarDataLabelsManager = (context: Context) => {
const dataIndex = context.dataIndex;
const datasetIndex = context.datasetIndex;
const values = context.chart.$barDataLabels?.[datasetIndex]?.[dataIndex];
return {
formattedValue: values?.formattedValue,
rotation: values?.rotation
};
};
export const barSeriesBuilder_labels = (props: LabelBuilderProps) => {
const { dataset, columnLabelFormats } = props;