diff --git a/src/lib/components/barchartv2/Barchart.component.test.tsx b/src/lib/components/barchartv2/Barchart.component.test.tsx
index 7d2fa33a1c..08a2fcae11 100644
--- a/src/lib/components/barchartv2/Barchart.component.test.tsx
+++ b/src/lib/components/barchartv2/Barchart.component.test.tsx
@@ -230,15 +230,37 @@ describe('Barchart', () => {
});
});
- describe('Reference line', () => {
- it('should render with reference line', () => {
- const { Wrapper } = getWrapper();
- render(
-
-
- ,
- );
- expect(screen.getByText('50')).toBeInTheDocument();
- });
+ it('should render stacked bars', () => {
+ const testStackedBars: BarchartProps['bars'] = [
+ {
+ label: 'Success',
+ data: [
+ ['category1', 10],
+ ['category2', 20],
+ ['category3', 30],
+ ],
+ color: 'green',
+ },
+ {
+ label: 'Failed',
+ data: [
+ ['category1', 5],
+ ['category2', 8],
+ ['category3', 12],
+ ],
+ color: 'red',
+ },
+ ];
+
+ const { Wrapper } = getWrapper();
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText('category1')).toBeInTheDocument();
+ expect(screen.getByText('category2')).toBeInTheDocument();
+ expect(screen.getByText('category3')).toBeInTheDocument();
});
});
diff --git a/src/lib/components/barchartv2/Barchart.component.tsx b/src/lib/components/barchartv2/Barchart.component.tsx
index a0a2104ffe..b0e45ae34a 100644
--- a/src/lib/components/barchartv2/Barchart.component.tsx
+++ b/src/lib/components/barchartv2/Barchart.component.tsx
@@ -13,7 +13,7 @@ import styled, { useTheme } from 'styled-components';
import {
computeUnitLabelAndRoundReferenceValue,
formatPrometheusDataToChartData,
- getMaxValue,
+ getMaxBarValue,
UnitRange,
} from './utils';
@@ -111,10 +111,15 @@ const StyledResponsiveContainer = styled(ResponsiveContainer)`
const Barchart = (props: BarchartProps) => {
const theme = useTheme();
- const { height = 200, bars, type = 'category', unitRange } = props;
+ const { height = 200, bars, type = 'category', unitRange, stacked } = props;
- const { data, rechartsBars } = formatPrometheusDataToChartData(bars, type);
- const maxValue = getMaxValue(data);
+ const { data, rechartsBars } = formatPrometheusDataToChartData(
+ bars,
+ type,
+ stacked,
+ );
+
+ const maxValue = getMaxBarValue(data, stacked);
const { unitLabel, roundReferenceValue, rechartsData } =
computeUnitLabelAndRoundReferenceValue(data, maxValue, unitRange);
@@ -128,6 +133,7 @@ const Barchart = (props: BarchartProps) => {
dataKey={bar.dataKey}
fill={bar.fill}
minPointSize={3}
+ stackId={stacked ? 'stacked' : undefined}
/>
))}
diff --git a/src/lib/components/barchartv2/utils.test.ts b/src/lib/components/barchartv2/utils.test.ts
index 47ef413232..ba3ecc985e 100644
--- a/src/lib/components/barchartv2/utils.test.ts
+++ b/src/lib/components/barchartv2/utils.test.ts
@@ -1,8 +1,11 @@
+import { BarchartProps } from './Barchart.component';
+
import {
computeUnitLabelAndRoundReferenceValue,
formatPrometheusDataToChartData,
- getMaxValue,
+ getMaxBarValue,
getRoundReferenceValue,
+ sortStackedBars,
UnitRange,
} from './utils';
@@ -25,19 +28,28 @@ describe('getRoundReferenceValue', () => {
});
});
-describe('getMaxValue', () => {
+describe('getMaxBarValue', () => {
it('should return the maximum value from chart data', () => {
const data = [
{ category: 'A', value1: 10, value2: 5 },
{ category: 'B', value1: 20, value2: 15 },
{ category: 'C', value1: 8, value2: 25 },
];
- expect(getMaxValue(data)).toBe(25);
+ expect(getMaxBarValue(data)).toBe(25);
});
it('should handle single value data', () => {
const data = [{ category: 'A', value: 42 }];
- expect(getMaxValue(data)).toBe(42);
+ expect(getMaxBarValue(data)).toBe(42);
+ });
+
+ it('should return the maximum value from stacked data', () => {
+ const data = [
+ { category: 'A', value1: 10, value2: 5 },
+ { category: 'B', value1: 20, value2: 15 },
+ { category: 'C', value1: 8, value2: 25 },
+ ];
+ expect(getMaxBarValue(data, true)).toBe(35);
});
});
@@ -219,65 +231,150 @@ describe('formatPrometheusDataToChartData', () => {
]);
});
});
- describe('computeUnitLabelAndRoundReferenceValue', () => {
- it('should compute the unit label and round reference value correctly when reaching threshold', () => {
- const data = [
- {
- category: 'category1',
- success: 1680,
- },
- ];
- const maxValue = 1680;
- const unitRange: UnitRange = [
- {
- threshold: 1000,
- label: 'kB',
- },
- ];
- const result = computeUnitLabelAndRoundReferenceValue(
- data,
- maxValue,
- unitRange,
- );
+ describe('Stacked Bar Sorting', () => {
+ const bars: BarchartProps['bars'] = [
+ {
+ label: 'Small Bar',
+ data: [
+ ['category1', 5],
+ ['category2', 10],
+ ['category3', 15],
+ ],
+ color: 'blue',
+ },
+ {
+ label: 'Large Bar',
+ data: [
+ ['category1', 50],
+ ['category2', 60],
+ ['category3', 70],
+ ],
+ color: 'red',
+ },
+ {
+ label: 'Medium Bar',
+ data: [
+ ['category1', 20],
+ ['category2', 25],
+ ['category3', 30],
+ ],
+ color: 'green',
+ },
+ ];
+ const type: BarchartProps['type'] = 'category';
+ it('should sort bars by average values in descending order when stacked is true', () => {
+ const result = formatPrometheusDataToChartData(bars, type, true);
- expect(result.unitLabel).toBe('kB');
- expect(result.roundReferenceValue).toBe(10);
- expect(result.rechartsData).toEqual([
- {
- category: 'category1',
- success: 1.68,
- },
- ]);
+ // Bars should be sorted by average in descending order (largest first)
+ expect(result.rechartsBars[0].dataKey).toBe('largebar'); // Average: 60
+ expect(result.rechartsBars[1].dataKey).toBe('mediumbar'); // Average: 25
+ expect(result.rechartsBars[2].dataKey).toBe('smallbar'); // Average: 10
});
- it('should compute the unit label and round reference value correctly when threshold is 0', () => {
- const data = [
- {
- category: 'category1',
- success: 680,
- },
- ];
- const maxValue = 680;
- const unitRange: UnitRange = [
- {
- threshold: 0,
- label: 'B',
- },
- {
- threshold: 1000,
- label: 'kB',
- },
- ];
- const result = computeUnitLabelAndRoundReferenceValue(
- data,
- maxValue,
- unitRange,
- );
- expect(result.unitLabel).toBe('B');
- expect(result.roundReferenceValue).toBe(1000);
- expect(result.rechartsData).toEqual([
- { category: 'category1', success: 680 },
- ]);
+ it('should not sort bars when stacked is false or undefined', () => {
+ const result = formatPrometheusDataToChartData(bars, type, false);
+
+ // Bars should maintain original order
+ expect(result.rechartsBars[0].dataKey).toBe('smallbar');
+ expect(result.rechartsBars[1].dataKey).toBe('largebar');
});
});
});
+
+describe('computeUnitLabelAndRoundReferenceValue', () => {
+ it('should compute the unit label and round reference value correctly when reaching threshold', () => {
+ const data = [
+ {
+ category: 'category1',
+ success: 1680,
+ },
+ ];
+ const maxValue = 1680;
+ const unitRange: UnitRange = [
+ {
+ threshold: 1000,
+ label: 'kB',
+ },
+ ];
+ const result = computeUnitLabelAndRoundReferenceValue(
+ data,
+ maxValue,
+ unitRange,
+ );
+
+ expect(result.unitLabel).toBe('kB');
+ expect(result.roundReferenceValue).toBe(10);
+ expect(result.rechartsData).toEqual([
+ {
+ category: 'category1',
+ success: 1.68,
+ },
+ ]);
+ });
+ it('should compute the unit label and round reference value correctly when threshold is 0', () => {
+ const data = [
+ {
+ category: 'category1',
+ success: 680,
+ },
+ ];
+ const maxValue = 680;
+ const unitRange: UnitRange = [
+ {
+ threshold: 0,
+ label: 'B',
+ },
+ {
+ threshold: 1000,
+ label: 'kB',
+ },
+ ];
+ const result = computeUnitLabelAndRoundReferenceValue(
+ data,
+ maxValue,
+ unitRange,
+ );
+
+ expect(result.unitLabel).toBe('B');
+ expect(result.roundReferenceValue).toBe(1000);
+ expect(result.rechartsData).toEqual([
+ { category: 'category1', success: 680 },
+ ]);
+ });
+});
+describe('sortStackedBars', () => {
+ const bars = [
+ { dataKey: 'bar1', fill: 'blue' },
+ { dataKey: 'bar2', fill: 'red' },
+ { dataKey: 'bar3', fill: 'green' },
+ ];
+ const data = [
+ { bar1: 10, bar2: 20, bar3: 30 },
+ { bar1: 40, bar2: 50, bar3: 60 },
+ { bar1: 70, bar2: 80, bar3: 90 },
+ ];
+ it('should sort bars by average values in descending order when stacked is true', () => {
+ const result = sortStackedBars(bars, data, true);
+ expect(result).toEqual([
+ { dataKey: 'bar3', fill: 'green' },
+ { dataKey: 'bar2', fill: 'red' },
+ { dataKey: 'bar1', fill: 'blue' },
+ ]);
+ });
+ it('should not sort bars when stacked is false', () => {
+ const result = sortStackedBars(bars, data, false);
+ expect(result).toEqual([
+ { dataKey: 'bar1', fill: 'blue' },
+ { dataKey: 'bar2', fill: 'red' },
+ { dataKey: 'bar3', fill: 'green' },
+ ]);
+ });
+ it('should not sort bars when stacked is undefined', () => {
+ const result = sortStackedBars(bars, data, undefined);
+ expect(result).toEqual([
+ { dataKey: 'bar1', fill: 'blue' },
+ { dataKey: 'bar2', fill: 'red' },
+ { dataKey: 'bar3', fill: 'green' },
+ ]);
+ });
+});
diff --git a/src/lib/components/barchartv2/utils.ts b/src/lib/components/barchartv2/utils.ts
index e8518996fc..9a6f61e25a 100644
--- a/src/lib/components/barchartv2/utils.ts
+++ b/src/lib/components/barchartv2/utils.ts
@@ -27,12 +27,28 @@ export const getMinValue = (data: { [key: string]: string | number }[]) => {
return Math.min(...values);
};
-export const getMaxValue = (data: { [key: string]: string | number }[]) => {
+export const getMaxBarValue = (
+ data: { [key: string]: string | number }[],
+ stacked?: boolean,
+) => {
const values = data.map((item) => {
+ // If stacked, we need to filter out category and sum the values in the same object
+ if (stacked) {
+ // Get objects keys except category
+ const filterOutCategory = Object.keys(item).filter(
+ (key) => key !== 'category',
+ );
+ // Sum the values in the same object (corresponding to one bar) based on the keys
+ const sumValues = filterOutCategory.reduce((acc, curr) => {
+ return acc + Number(item[curr]);
+ }, 0);
+ return sumValues;
+ }
//filter out the category key
const numberValues = Object.keys(item)
.filter((key) => key !== 'category')
.map((key) => Number(item[key]));
+ // Get the max value among the values in the object (corresponding to one bar)
return Math.max(...numberValues);
});
return Math.max(...values);
@@ -122,6 +138,7 @@ const findRangeForTimestamp = (
export const formatPrometheusDataToChartData = (
bars: BarchartProps['bars'],
type: BarchartProps['type'],
+ stacked?: boolean,
): {
data: {
[key: string]: string | number;
@@ -131,7 +148,7 @@ export const formatPrometheusDataToChartData = (
fill: string;
}[];
} => {
- const rechartsBars = bars.map((bar) => ({
+ let rechartsBars = bars.map((bar) => ({
dataKey: bar.label.toLowerCase().replace(/\s+/g, ''),
fill: bar.color,
}));
@@ -211,6 +228,9 @@ export const formatPrometheusDataToChartData = (
// Convert map to array (order is preserved for time ranges)
const data = Array.from(categoryMap.values());
+ // Sort stacked bars
+ rechartsBars = sortStackedBars(rechartsBars, data, stacked);
+
return {
rechartsBars,
data,
@@ -297,3 +317,33 @@ export function getUnitLabel(
unitLabel: unitRange[index - 1].label,
};
}
+
+// Sort stacked bars by their average values in descending order
+// This ensures the largest bars appear at the bottom of the stack
+export const sortStackedBars = (
+ rechartsBars: {
+ dataKey: string;
+ fill: string;
+ }[],
+ data: {
+ [key: string]: string | number;
+ }[],
+ stacked?: boolean,
+) => {
+ if (!stacked) {
+ return rechartsBars;
+ }
+ const barAverages = rechartsBars.map((bar) => {
+ const values = data
+ .map((item) => Number(item[bar.dataKey]) || 0)
+ .filter((value) => !isNaN(value));
+ const average =
+ values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0;
+ return { ...bar, average };
+ });
+
+ // Sort by average in descending order (largest first, which will be at bottom in stack)
+ barAverages.sort((a, b) => b.average - a.average);
+ // Remove the average property and keep only the bar data
+ return barAverages.map(({ average, ...bar }) => bar);
+};
diff --git a/stories/BarChart/barchart.stories.tsx b/stories/BarChart/barchart.stories.tsx
index 9a642a95e1..47bc200214 100644
--- a/stories/BarChart/barchart.stories.tsx
+++ b/stories/BarChart/barchart.stories.tsx
@@ -329,13 +329,33 @@ export const CategoryWithMissingData: Story = {
return ;
},
};
+const capacityDataWithUnitRange: BarchartProps['bars'] = [
+ {
+ label: 'Free',
+ data: [
+ ['category1', 2000000],
+ ['category2', 4000000],
+ ['category3', 6000000],
+ ],
+ color: 'blue',
+ },
+ {
+ label: 'Used',
+ data: [
+ ['category1', 8000000],
+ ['category2', 10000000],
+ ['category3', 12000000],
+ ],
+ color: 'lightblue',
+ },
+];
export const CapacityWithUnitRange: Story = {
render: () => {
return (
{
+ return ;
+ },
+};