Skip to content
Merged
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
14 changes: 0 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/lib/components/barchartv2/Barchart.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
{} as Record<string, ChartColors | string>,
);

const { rechartsBars, unitLabel, roundReferenceValue, rechartsData } =
const { rechartsBars, unitLabel, roundReferenceValue, rechartsData, topDomain } =
useChartData(
bars || [],
type,
Expand Down Expand Up @@ -363,7 +363,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {

<YAxis
interval={0}
domain={[0, roundReferenceValue]}
domain={[0, topDomain]}
ticks={getTicks(roundReferenceValue, false)}
tickFormatter={
(value) => new Intl.NumberFormat('fr-FR').format(value) // Add a space as thousand separator
Expand Down
52 changes: 26 additions & 26 deletions src/lib/components/barchartv2/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,41 +507,41 @@ describe('applySortingToData', () => {
describe('getRoundReferenceValue', () => {
it('should return appropriate rounded values with 10% buffer', () => {
// Small values (< 10)
expect(getRoundReferenceValue(0.1)).toBe(0.2); // 0.1 → 0.11 → 0.2
expect(getRoundReferenceValue(1)).toBe(2); // 1.1 → 1.52
expect(getRoundReferenceValue(2)).toBe(5); // 2.2 → 3 → 5
expect(getRoundReferenceValue(3)).toBe(5); // 3.3 → 4 → 5
expect(getRoundReferenceValue(0.1)).toBe(0.1); // 0.1 → 0.11 → 0.1 (magnitude 0.1, remainder 0.01)
expect(getRoundReferenceValue(1)).toBe(1); // 1 → 1.11 (magnitude 1, remainder 0.1)
expect(getRoundReferenceValue(2)).toBe(2); // 2 → 2.2 → 2 (magnitude 1, remainder 0.2)
expect(getRoundReferenceValue(3)).toBe(3); // 3 → 3.3 → 3 (magnitude 1, remainder 0.3)

// Values 5-10 range
expect(getRoundReferenceValue(6)).toBe(10); // 6.6 → 10 (skip 7.5 for values < 10)
expect(getRoundReferenceValue(9)).toBe(10); // 9.9 → 10
expect(getRoundReferenceValue(6)).toBe(6); // 6 → 6.6 → 6 (magnitude 1, remainder 0.6)
expect(getRoundReferenceValue(9)).toBe(9); // 9 → 9.9 → 9 (magnitude 1, remainder 0.9)

// Larger values get 10% buffer applied
expect(getRoundReferenceValue(15)).toBe(20); // 16.5 → 20
expect(getRoundReferenceValue(35)).toBe(40); // 38.5 → 40
expect(getRoundReferenceValue(75)).toBe(100); // 82.5 → 100
expect(getRoundReferenceValue(150)).toBe(200); // 165200
expect(getRoundReferenceValue(350)).toBe(400); // 385400
expect(getRoundReferenceValue(750)).toBe(1000); // 8251000
expect(getRoundReferenceValue(1500)).toBe(2000); // 16502000
expect(getRoundReferenceValue(3500)).toBe(4000); // 38504000
expect(getRoundReferenceValue(7500)).toBe(10000); // 825010000
expect(getRoundReferenceValue(15000)).toBe(20000); // 1650020000
expect(getRoundReferenceValue(15)).toBe(10); // 15 → 16.5, remainder 5, incremented 20 > 16.5, so round down to 10
expect(getRoundReferenceValue(35)).toBe(30); // 35 → 38.5, remainder 5, incremented 40 > 38.5, so round down to 30
expect(getRoundReferenceValue(75)).toBe(80); // 75 → 82.5, remainder 5, incremented 80 <= 82.5, so round up to 80
expect(getRoundReferenceValue(150)).toBe(150); // 150165, remainder 0, so return 150
expect(getRoundReferenceValue(350)).toBe(350); // 350385, remainder 0, so return 350
expect(getRoundReferenceValue(750)).toBe(750); // 750825, remainder 0, so return 750
expect(getRoundReferenceValue(1500)).toBe(1500); // 15001650, remainder 0, so return 1500
expect(getRoundReferenceValue(3500)).toBe(3500); // 35003850, remainder 0, so return 3500
expect(getRoundReferenceValue(7500)).toBe(7500); // 75008250, remainder 0, so return 7500
expect(getRoundReferenceValue(15000)).toBe(15000); // 1500016500, remainder 0, so return 15000
});
});

describe('getTicks', () => {
describe('small values (< 10)', () => {
it('should return 2 ticks for non-symmetrical small values', () => {
expect(getTicks(1, false)).toEqual([0, 1]);
expect(getTicks(2, false)).toEqual([0, 2]);
expect(getTicks(5, false)).toEqual([0, 5]);
expect(getTicks(1, false)).toEqual([0, 0.5, 1]); // 1 % 2 != 0, defaults to 3 ticks
expect(getTicks(2, false)).toEqual([0, 1, 2]); // 2 % (3-1) == 0, uses 3 ticks
expect(getTicks(5, false)).toEqual([0, 2.5, 5]); // 5 % 2 != 0 and 5 % 3 != 0, defaults to 3 ticks
});

it('should return 3 ticks for symmetrical small values', () => {
expect(getTicks(1, true)).toEqual([-1, 0, 1]);
expect(getTicks(2, true)).toEqual([-2, 0, 2]);
expect(getTicks(5, true)).toEqual([-5, 0, 5]);
expect(getTicks(1, true)).toEqual([-1, -0.5, 0, 0.5, 1]); // 1 % 2 != 0, defaults to 3 ticks, symmetrical adds negatives
expect(getTicks(2, true)).toEqual([-2, -1, 0, 1, 2]); // 2 % (3-1) == 0, uses 3 ticks, symmetrical adds negatives
expect(getTicks(5, true)).toEqual([-5, -2.5, 0, 2.5, 5]); // 5 % 2 != 0 and 5 % 3 != 0, defaults to 3 ticks, symmetrical adds negatives
});
});

Expand Down Expand Up @@ -738,8 +738,8 @@ describe('computeUnitLabelAndRoundReferenceValue', () => {
);

expect(result.unitLabel).toBe('kB');
// 1680 / 1000 = 1.68, with buffer: 1.848 → rounds to 2
expect(result.roundReferenceValue).toBe(2);
// 1680 / 1000 = 1.68, with buffer: 1.848 → rounds to 1 (magnitude 1, remainder 0.848)
expect(result.roundReferenceValue).toBe(1);
expect(result.rechartsData).toEqual([
{
category: 'category1',
Expand Down Expand Up @@ -772,8 +772,8 @@ describe('computeUnitLabelAndRoundReferenceValue', () => {
);

expect(result.unitLabel).toBe('B');
// 680 with buffer: 748 → rounds to 800 (8 * 100, value > 10)
expect(result.roundReferenceValue).toBe(800);
// 680 with buffer: 748 → rounds to 680 (value >= 10, remainder 0, rounds down)
expect(result.roundReferenceValue).toBe(680);
expect(result.rechartsData).toEqual([
{ category: 'category1', success: 680 },
]);
Expand Down
63 changes: 28 additions & 35 deletions src/lib/components/barchartv2/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,39 @@ import { useChartLegend } from '../chartlegend/ChartLegendWrapper';

export const getRoundReferenceValue = (value: number): number => {
if (value <= 0) return 1; // Default for zero or negative values

// Buffer the value by 10% to avoid being too close to the edge of the chart
const bufferedValue = value * 1.1;

if (value >= 10) {
const remainder = value % 10;
const incremented = value + (10 - remainder);

// If the remainder is less than 5, round down to the nearest 10
if (remainder < 5) {
return value - remainder;
}

// If incrementing would exceed the buffered max, also round down
if (incremented > bufferedValue) {
return value - remainder;
}

// Otherwise, round up to the next 10
return incremented;
}

// Get the magnitude (10^n where n is the number of digits - 1)
const magnitude = Math.pow(10, Math.floor(Math.log10(value)));

// Buffer the value by 10% to avoid being too close to the edge of the chart
const bufferedValue = value * 1.1;
const remainder = bufferedValue % magnitude;

// Normalized value between 1 and 10
const normalized = bufferedValue / magnitude;

// Round to nice numbers based on normalized value
// skip 1.5, 3, 4, 7.5 as top value for better chart
// appearance for small values
let result: number;

if (normalized <= 1) result = magnitude;
else if (value > 10 && normalized <= 1.5) result = 1.5 * magnitude;
else if (normalized <= 2) result = 2 * magnitude;
else if (value > 10 && normalized <= 2.5) result = 2.5 * magnitude;
else if (value > 10 && normalized <= 3) result = 3 * magnitude;
else if (value > 10 && normalized <= 4) result = 4 * magnitude;
else if (normalized <= 5) result = 5 * magnitude;
else if (value > 10 && normalized <= 6) result = 6 * magnitude;
else if (value > 10 && normalized <= 8) result = 8 * magnitude;
else if (normalized <= 10) result = 10 * magnitude;
else result = 10 * magnitude;

return result;
return remainder === 0 ? bufferedValue : bufferedValue - remainder;
};

export const getTicks = (topValue: number, isSymmetrical: boolean) => {
if (topValue < 10) {
if (isSymmetrical) {
return [-topValue, 0, topValue];
} else {
return [0, topValue];
}
}
const possibleTickNumbers = [4, 3];
const numberOfTicks =
possibleTickNumbers.find((number) => topValue % (number - 1) === 0) || 2; // Default to 2 ticks if no match
possibleTickNumbers.find((number) => topValue % (number - 1) === 0) || 3; // Default to 2 ticks if no match
const tickInterval = topValue / (numberOfTicks - 1);
const ticks = Array.from(
{ length: numberOfTicks },
Expand Down Expand Up @@ -339,7 +331,7 @@ export const computeUnitLabelAndRoundReferenceValue = (
) => {
if (!unitRange) {
const roundReferenceValue = getRoundReferenceValue(maxValue);
return { unitLabel: undefined, roundReferenceValue, rechartsData: data };
return { unitLabel: undefined, roundReferenceValue, rechartsData: data, topDomain: maxValue * 1.1 };
}

const { valueBase, unitLabel } = getUnitLabel(unitRange, maxValue);
Expand All @@ -354,7 +346,7 @@ export const computeUnitLabelAndRoundReferenceValue = (
});
return normalizedDataPoint;
});
return { unitLabel, roundReferenceValue, rechartsData };
return { unitLabel, roundReferenceValue, rechartsData, topDomain: topValue * 1.1 };
};

/**
Expand Down Expand Up @@ -534,12 +526,13 @@ export const useChartData = <T extends BarchartBars>(

const maxValue = getMaxBarValue(filteredData, stacked);

const { unitLabel, roundReferenceValue, rechartsData } =
const { unitLabel, roundReferenceValue, rechartsData, topDomain } =
computeUnitLabelAndRoundReferenceValue(filteredData, maxValue, unitRange);

return {
rechartsBars: filteredRechartsBars,
unitLabel,
topDomain,
roundReferenceValue,
rechartsData,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,13 +360,7 @@ export function LineTimeSerieChart({
}, [chartData]);

// 3. Transform the data base on the valuebase
const { topValue, unitLabel, rechartsData } = useMemo(() => {
if (yAxisType === 'percentage')
return {
topValue: 100,
unitLabel: '%',
rechartsData: chartData,
};
const { topValue, unitLabel, rechartsData, topDomain } = useMemo(() => {

const values = chartData.flatMap((dataPoint) =>
Object.entries(dataPoint)
Expand All @@ -383,19 +377,20 @@ export function LineTimeSerieChart({
if (values.length === 0) {
return {
topValue: 100, // Default value for empty charts
unitLabel: '',
unitLabel: yAxisType === 'percentage' ? '%' : '',
rechartsData: [],
topDomain: 100,
};
}

const top = Math.abs(Math.max(...values));
const bottom = Math.abs(Math.min(...values));
const maxValue = Math.max(top, bottom);

const { valueBase, unitLabel } = getUnitLabel(unitRange ?? [], maxValue);
const { valueBase, unitLabel } = yAxisType === 'percentage' ? { valueBase: 1, unitLabel: '%' } : getUnitLabel(unitRange ?? [], maxValue);
// Use round reference value to add extra padding to the top value
const topValue = getRoundReferenceValue(maxValue / valueBase);

const basedValue = maxValue / valueBase
const topDomain = basedValue * 1.1;
const topValue = getRoundReferenceValue(basedValue);
const rechartsData = chartData.map((dataPoint) => {
const normalizedDataPoint = { ...dataPoint };
Object.entries(dataPoint).forEach(([key, value]) => {
Expand All @@ -406,7 +401,7 @@ export function LineTimeSerieChart({
return normalizedDataPoint;
});

return { topValue, unitLabel, rechartsData };
return { topValue, unitLabel, rechartsData, topDomain };
}, [chartData, yAxisType, unitRange]);

// Group series by resource and create color mapping
Expand Down Expand Up @@ -522,10 +517,10 @@ export function LineTimeSerieChart({
}}
domain={
yAxisType === 'percentage'
? [0, 100]
? [0, topDomain]
: yAxisType === 'symmetrical'
? [-topValue, topValue]
: [0, topValue]
? [-topDomain, topDomain]
: [0, topDomain]
}
axisLine={{ stroke: theme.border }}
tick={{
Expand Down
Loading