Update barSeriesBuilder.ts

This commit is contained in:
Nate Kelley 2025-04-03 11:58:19 -06:00
parent 2221de1836
commit 9a7698596d
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
2 changed files with 61 additions and 83 deletions

View File

@ -9,6 +9,7 @@ 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 { IColumnLabelFormat } from '@/api/asset_interfaces/metric';
export const barSeriesBuilder = ({
selectedDataset,
@ -33,12 +34,6 @@ 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) =>
@ -52,18 +47,8 @@ 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(() => {
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);
}
context.chart.update(); //this is hack because the chart data label almost always overflows
});
hasBeenDrawn = true;
}
@ -115,6 +100,9 @@ declare module 'chart.js' {
}
const TEXT_WIDTH_BUFFER = 4;
const MAX_BAR_HEIGHT = 16;
const MAX_BAR_WIDTH = 13;
const FULL_ROTATION_ANGLE = -90;
export const barBuilder = ({
selectedDataset,
@ -166,91 +154,37 @@ export const barBuilder = ({
}
// 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();
if (index === 0 && context.datasetIndex === 0) {
setGlobalRotation(context);
}
const rawValue = context.dataset.data[context.dataIndex] as number;
if (!showLabels || !rawValue) return false;
const { barWidth, barHeight } = getBarDimensions(context);
if (barWidth < 13) return false;
const formattedValue = formatBarAndLineDataLabel(
rawValue,
context,
if (barWidth < MAX_BAR_WIDTH) return false;
const formattedValue = getFormattedValue(context, {
usePercentage,
columnLabelFormat
);
});
// 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;
const rotation = context.chart.$barDataLabelsGlobalRotation ? FULL_ROTATION_ANGLE : 0;
// Check if this label can be displayed even with rotation
if (rotation === -90 && textWidth > barHeight - TEXT_WIDTH_BUFFER) {
return false;
}
// Store only the formatted value, rotation is handled globally
setBarDataLabelsManager(context, formattedValue);
// Check if the bar height is too small to display the label
if (barHeight < MAX_BAR_HEIGHT) return false;
if (barHeight < 16) return false;
return 'auto';
},
formatter: (_, context) => {
@ -258,7 +192,7 @@ export const barBuilder = ({
},
rotation: (context) => {
// Always use the global rotation setting
return context.chart.$barDataLabelsGlobalRotation ? -90 : 0;
return context.chart.$barDataLabelsGlobalRotation ? FULL_ROTATION_ANGLE : 0;
},
color: dataLabelFontColorContrast,
borderWidth: 0,
@ -299,6 +233,50 @@ const getBarDimensions = (context: Context) => {
return { barWidth, barHeight };
};
const setGlobalRotation = (context: Context) => {
context.chart.$barDataLabelsGlobalRotation = false;
const labels = context.chart.data.datasets
.filter((d) => !d.hidden)
.flatMap((dataset, datasetIndex) => {
return dataset.data.map((value, dataIndex) => {
const currentValue = context.chart.$barDataLabels?.[datasetIndex]?.[dataIndex] || '';
return currentValue || '';
});
});
const labelNeedsToBeRotated = labels.some((label) => {
const { width: textWidth } = context.chart.ctx.measureText(label);
const { barWidth, barHeight } = getBarDimensions(context);
return textWidth > barWidth - TEXT_WIDTH_BUFFER;
});
if (labelNeedsToBeRotated) {
context.chart.$barDataLabelsGlobalRotation = true;
}
};
const getFormattedValue = (
context: Context,
{
usePercentage,
columnLabelFormat
}: {
usePercentage: boolean;
columnLabelFormat: IColumnLabelFormat;
}
) => {
const rawValue = context.dataset.data[context.dataIndex] as number;
const currentValue =
context.chart.$barDataLabels?.[context.datasetIndex]?.[context.dataIndex] || '';
const formattedValue =
currentValue || formatBarAndLineDataLabel(rawValue, context, usePercentage, columnLabelFormat);
// Store only the formatted value, rotation is handled globally
setBarDataLabelsManager(context, formattedValue);
return formattedValue;
};
export const barSeriesBuilder_labels = (props: LabelBuilderProps) => {
const { dataset, columnLabelFormats } = props;

View File

@ -297,7 +297,7 @@ export const WithDataLabelsAndStackTotal: Story = {
showDataLabelsAsPercentage: false
},
units: {
showDataLabels: false,
showDataLabels: true,
showDataLabelsAsPercentage: false
}
},