Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions src/components/ChartContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -416,13 +416,39 @@ export default function ChartContainer({
if (min === Infinity || max === -Infinity) {
return { min: 0, max: 1 };
}

// Always include zero on the axis for better readability
min = Math.min(min, 0);
max = Math.max(max, 0);

if (min === max) {
return { min: min - 1, max: max + 1 };
}
const pad = (max - min) * 0.05;
return { min: min - pad, max: max + pad };
}, [xRange]);

const yDecimalPlaces = useMemo(() => {
let maxDecimals = 0;
parsedData.forEach(file => {
Object.values(file.metricsData).forEach(points => {
points.forEach(p => {
const str = p.y.toString();
const decimalPart = str.split('.')[1];
if (decimalPart) {
const decimals = decimalPart.replace(/e.+/i, '').length;
if (decimals > maxDecimals) {
maxDecimals = decimals;
}
}
});
});
});
Comment on lines +431 to +446

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Handle exponent-formatted values when deriving decimal places

The new yDecimalPlaces logic counts fractional digits by splitting p.y.toString() on a decimal point, but numbers whose string representation is in scientific notation (1e-7, 3.2E-5, etc.) never enter the if (decimalPart) branch. When such values exist in parsedData, maxDecimals remains 0 and both tooltips and tick labels format them with toFixed(0), displaying 0 for every small-magnitude point and making the chart misleading. Consider parsing the exponent and counting digits from the mantissa so that values like 1e-7 retain at least seven decimals.

Useful? React with 👍 / 👎.

return maxDecimals;
}, [parsedData]);

const tickStep = useMemo(() => Math.pow(10, -yDecimalPlaces), [yDecimalPlaces]);

const chartOptions = useMemo(() => ({
responsive: true,
maintainAspectRatio: false,
Expand Down Expand Up @@ -503,7 +529,7 @@ export default function ChartContainer({
return `Step ${context[0].parsed.x}`;
},
label: function (context) {
const value = Number(context.parsed.y.toPrecision(4));
const value = Number(context.parsed.y.toFixed(yDecimalPlaces));
const label = context.dataset?.label || 'Dataset';
return ` ${label}: ${value}`;
},
Expand Down Expand Up @@ -538,14 +564,15 @@ export default function ChartContainer({
title: { display: true, text: 'Value' },
bounds: 'data',
ticks: {
stepSize: tickStep,
callback: function (value) {
return Number(value.toPrecision(2));
return Number(value.toFixed(yDecimalPlaces));
}
}
}
},
elements: { point: { radius: 0 } }
}), [xRange, onXRangeChange]);
}), [xRange, onXRangeChange, yDecimalPlaces, tickStep]);

const buildComparisonChartData = (dataArray) => {
const baselineVal =
Expand Down Expand Up @@ -680,11 +707,13 @@ export default function ChartContainer({
const showComparison = dataArray.length >= 2;

const yRange = calculateYRange(dataArray);
const yMin = Math.floor(yRange.min / tickStep) * tickStep;
const yMax = Math.ceil(yRange.max / tickStep) * tickStep;
const options = {
...chartOptions,
scales: {
...chartOptions.scales,
y: { ...chartOptions.scales.y, min: yRange.min, max: yRange.max }
y: { ...chartOptions.scales.y, min: yMin, max: yMax }
}
};

Expand All @@ -693,12 +722,14 @@ export default function ChartContainer({
if (showComparison) {
const compResult = buildComparisonChartData(dataArray);
stats = compResult.stats.length > 0 ? compResult.stats : null;
const compRange = calculateYRange(compResult.datasets);
const compRange = calculateYRange(compResult.datasets);
const compMin = Math.floor(compRange.min / tickStep) * tickStep;
const compMax = Math.ceil(compRange.max / tickStep) * tickStep;
const compOptions = {
...chartOptions,
scales: {
...chartOptions.scales,
y: { ...chartOptions.scales.y, min: compRange.min, max: compRange.max }
y: { ...chartOptions.scales.y, min: compMin, max: compMax }
}
};
const compActions = (
Expand Down
5 changes: 4 additions & 1 deletion src/components/__tests__/ChartContainer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ describe('ChartContainer', () => {
expect(opts.interaction.axis).toBe('x');
expect(opts.plugins.tooltip.mode).toBe('nearest');
expect(opts.plugins.tooltip.axis).toBe('x');
expect(opts.scales.y.ticks.stepSize).toBe(0.1);

// simulate hover to trigger sync
const hover = __lineProps[0].options.onHover;
Expand Down Expand Up @@ -140,6 +141,7 @@ describe('ChartContainer', () => {
{}
];

const startIndex = __lineProps.length;
render(
<ChartContainer
files={files}
Expand All @@ -156,8 +158,9 @@ describe('ChartContainer', () => {
screen.getByText(/metric3/);

// data range applied (start 1 end 3 => 2 points for loss)
const currentProps = __lineProps.slice(-5);
const currentProps = __lineProps.slice(startIndex);
expect(currentProps[0].data.datasets[0].data).toHaveLength(2);
expect(currentProps[0].options.scales.y.ticks.stepSize).toBe(1);

// trigger container mouse leave
const container = screen.getAllByTestId('line-chart')[0].parentElement;
Expand Down