Skip to content

Commit 6e8cde5

Browse files
Add sorting function and typing
1 parent d95d57c commit 6e8cde5

File tree

4 files changed

+192
-47
lines changed

4 files changed

+192
-47
lines changed

src/lib/components/barchartv2/Barchart.component.test.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { render, screen, waitFor } from '@testing-library/react';
22
import React from 'react';
3-
import Barchart, { BarchartProps } from './Barchart.component';
3+
import Barchart, { BarchartProps, Point } from './Barchart.component';
44
import { getWrapper } from '../../testUtils';
55

66
const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
@@ -263,4 +263,42 @@ describe('Barchart', () => {
263263
expect(screen.getByText('category2')).toBeInTheDocument();
264264
expect(screen.getByText('category3')).toBeInTheDocument();
265265
});
266+
267+
it('should sort categories using defaultSort function', () => {
268+
const testBars: BarchartProps['bars'] = [
269+
{
270+
label: 'Success',
271+
data: [
272+
['category3', 30],
273+
['category1', 10],
274+
['category2', 20],
275+
],
276+
color: 'green',
277+
},
278+
];
279+
280+
// Sort by total value in descending order
281+
const sortByValueDesc = (pointA: Point, pointB: Point) => {
282+
const totalA = pointA.values.reduce((sum, v) => sum + v.value, 0);
283+
const totalB = pointB.values.reduce((sum, v) => sum + v.value, 0);
284+
return totalB - totalA > 0 ? 1 : totalB - totalA < 0 ? -1 : 0;
285+
};
286+
287+
const { Wrapper } = getWrapper();
288+
render(
289+
<Wrapper>
290+
<Barchart
291+
type="category"
292+
bars={testBars}
293+
defaultSort={sortByValueDesc}
294+
/>
295+
</Wrapper>,
296+
);
297+
298+
// Categories should be rendered in descending order by value
299+
const categories = screen.getAllByText(/category[123]/);
300+
expect(categories[0]).toHaveTextContent('category3'); // 30 (highest)
301+
expect(categories[1]).toHaveTextContent('category2'); // 20 (middle)
302+
expect(categories[2]).toHaveTextContent('category1'); // 10 (lowest)
303+
});
266304
});

src/lib/components/barchartv2/Barchart.component.tsx

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,28 @@ export type TimeType = {
2525
interval: number;
2626
};
2727
};
28-
type Point = {
28+
export type Point = {
2929
key: string | number;
3030
values: { label: string; value: number }[];
3131
};
3232

33-
export type BarchartProps = {
33+
export type BarchartProps<
34+
T extends readonly {
35+
readonly label: string;
36+
readonly data: readonly (readonly [number | string, number | string])[];
37+
readonly color: string;
38+
}[],
39+
> = {
3440
type: 'category' | TimeType;
35-
bars: {
36-
label: string;
37-
data: [number | string, number | string][];
38-
color: string;
39-
}[];
41+
bars: T;
4042
tooltip?: (currentPoint: {
4143
key: string | number;
4244
values: { label: string; value: number; isHovered: boolean }[];
4345
}) => React.ReactNode;
44-
defaultSort?: (pointA: Point, pointB: Point) => 1 | -1 | 0;
45-
46+
defaultSort?: (
47+
pointA: Record<T[number]['label'], number> & { category: string | number },
48+
pointB: Record<T[number]['label'], number> & { category: string | number },
49+
) => 1 | -1 | 0;
4650
unitRange?: UnitRange;
4751
helpTooltip?: string;
4852
stacked?: boolean;
@@ -108,15 +112,31 @@ const StyledResponsiveContainer = styled(ResponsiveContainer)`
108112
}
109113
`;
110114

111-
const Barchart = (props: BarchartProps) => {
115+
const Barchart = <
116+
T extends readonly {
117+
readonly label: string;
118+
readonly data: readonly (readonly [number | string, number | string])[];
119+
readonly color: string;
120+
}[],
121+
>(
122+
props: BarchartProps<T>,
123+
) => {
112124
const theme = useTheme();
113125

114-
const { height = 200, bars, type = 'category', unitRange, stacked } = props;
126+
const {
127+
height = 200,
128+
bars,
129+
type = 'category',
130+
unitRange,
131+
stacked,
132+
defaultSort,
133+
} = props;
115134

116135
const { data, rechartsBars } = formatPrometheusDataToChartData(
117136
bars,
118137
type,
119138
stacked,
139+
defaultSort,
120140
);
121141

122142
const maxValue = getMaxBarValue(data, stacked);

src/lib/components/barchartv2/utils.ts

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { BarchartProps } from './Barchart.component';
1+
import { BarchartProps, Point } from './Barchart.component';
2+
23
import { DAY_MONTH_FORMATER, TIME_FORMATER } from '../date/FormattedDateTime';
34

45
export const getRoundReferenceValue = (value: number): number => {
@@ -135,21 +136,23 @@ const findRangeForTimestamp = (
135136
* @param type - The chart type (category or time)
136137
* @returns Recharts data format
137138
*/
138-
export const formatPrometheusDataToChartData = (
139-
bars: BarchartProps['bars'],
140-
type: BarchartProps['type'],
139+
export const formatPrometheusDataToChartData = <
140+
T extends readonly {
141+
readonly label: string;
142+
readonly data: readonly (readonly [number | string, number | string])[];
143+
readonly color: string;
144+
}[],
145+
>(
146+
bars: T,
147+
type: BarchartProps<T>['type'],
141148
stacked?: boolean,
149+
defaultSort?: BarchartProps<T>['defaultSort'],
142150
): {
143-
data: {
144-
[key: string]: string | number;
145-
}[];
146-
rechartsBars: {
147-
dataKey: string;
148-
fill: string;
149-
}[];
151+
data: { [key: string]: string | number }[];
152+
rechartsBars: { dataKey: string; fill: string }[];
150153
} => {
151154
let rechartsBars = bars.map((bar) => ({
152-
dataKey: bar.label.toLowerCase().replace(/\s+/g, ''),
155+
dataKey: bar.label,
153156
fill: bar.color,
154157
}));
155158

@@ -185,7 +188,7 @@ export const formatPrometheusDataToChartData = (
185188

186189
// Process actual data from bars
187190
bars.forEach((bar) => {
188-
const dataKey = bar.label.toLowerCase().replace(/\s+/g, '');
191+
const dataKey = bar.label;
189192

190193
bar.data.forEach(([timestamp, value]) => {
191194
// Find which range this timestamp belongs to
@@ -202,7 +205,7 @@ export const formatPrometheusDataToChartData = (
202205
} else {
203206
// Handle category data
204207
bars.forEach((bar) => {
205-
const dataKey = bar.label.toLowerCase().replace(/\s+/g, '');
208+
const dataKey = bar.label;
206209

207210
bar.data.forEach(([key, value]) => {
208211
const categoryKey = String(key);
@@ -226,7 +229,41 @@ export const formatPrometheusDataToChartData = (
226229
}
227230

228231
// Convert map to array (order is preserved for time ranges)
229-
const data = Array.from(categoryMap.values());
232+
let data = Array.from(categoryMap.values());
233+
234+
// Apply custom sorting for category data only
235+
if (type === 'category' && defaultSort) {
236+
// Convert data to the new record format for sorting
237+
const points = data.map((item) => {
238+
const point: Record<T[number]['label'], number> & {
239+
category: string | number;
240+
} = {
241+
category: item.category,
242+
} as any;
243+
244+
rechartsBars.forEach((bar) => {
245+
(point as any)[bar.dataKey] = Number(item[bar.dataKey]) || 0;
246+
});
247+
248+
return point;
249+
});
250+
251+
// Sort using the provided function
252+
points.sort((pointA, pointB) => {
253+
return defaultSort(pointA, pointB);
254+
});
255+
256+
// Convert back to data format
257+
data = points.map((point) => {
258+
const dataItem: { [key: string]: string | number } = {
259+
category: point.category,
260+
};
261+
rechartsBars.forEach((bar) => {
262+
dataItem[bar.dataKey] = (point as any)[bar.dataKey];
263+
});
264+
return dataItem;
265+
});
266+
}
230267

231268
// Sort bars by their average values in descending order when stacked
232269
// This ensures the largest bars appear at the bottom of the stack

0 commit comments

Comments
 (0)