Skip to content

Commit b2abedb

Browse files
committed
Merge branch 'improvement/ARTESCA-16534-chart-axis-issues' into q/1.0
2 parents df9817b + 82645e9 commit b2abedb

File tree

13 files changed

+1306
-665
lines changed

13 files changed

+1306
-665
lines changed

src/lib/components/charts/barchart/Barchart.tsx

Lines changed: 123 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useRef } from 'react';
1+
import { useState, useRef, useMemo, useCallback } from 'react';
22
import {
33
Bar,
44
BarChart as RechartsBarChart,
@@ -13,7 +13,7 @@ import { Stack } from '../../../spacing';
1313
import { chartColors, ChartColors, fontSize } from '../../../style/theme';
1414
import { useChartLegend } from '../legend/ChartLegendWrapper';
1515
import { BarchartTooltip } from './BarchartTooltip';
16-
import { formatToISONumber, getTicks } from '../common/chartUtils';
16+
import { formatTickValue, getTicks } from '../common/chartUtils';
1717
import { useChartData } from './Barchart.utils';
1818
import {
1919
ChartHeader,
@@ -112,15 +112,19 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
112112
} = props;
113113

114114
// Create colorSet from ChartLegendWrapper
115-
const colorSet = bars?.reduce(
116-
(acc, bar) => {
117-
const color = getColor(bar.label);
118-
if (color) {
119-
acc[bar.label] = color;
120-
}
121-
return acc;
122-
},
123-
{} as Record<string, ChartColors | string>,
115+
const colorSet = useMemo(
116+
() =>
117+
bars?.reduce(
118+
(acc, bar) => {
119+
const color = getColor(bar.label);
120+
if (color) {
121+
acc[bar.label] = color;
122+
}
123+
return acc;
124+
},
125+
{} as Record<string, ChartColors | string>,
126+
),
127+
[bars, getColor],
124128
);
125129

126130
const {
@@ -139,6 +143,113 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
139143
stackedBarSort,
140144
);
141145
const titleWithUnit = unitLabel ? `${title} (${unitLabel})` : title;
146+
147+
const tickFormatter = useCallback(
148+
(value: number) => formatTickValue(value, roundReferenceValue),
149+
[roundReferenceValue],
150+
);
151+
152+
const renderChartContent = () => {
153+
if (isError || (!bars && !isLoading)) {
154+
return <ChartError height={height} />;
155+
}
156+
if (isLoading) {
157+
return <ChartLoading height={height} />;
158+
}
159+
160+
return (
161+
<StyledResponsiveContainer ref={chartRef} width="100%" height={height}>
162+
<RechartsBarChart
163+
data={rechartsData}
164+
accessibilityLayer
165+
barSize={
166+
type.type === 'category'
167+
? type.gap === 0
168+
? undefined
169+
: CHART_CONSTANTS.BAR_SIZE
170+
: CHART_CONSTANTS.BAR_SIZE
171+
}
172+
height={height}
173+
margin={CHART_CONSTANTS.CHART_MARGIN}
174+
barCategoryGap={type.type === 'category' ? type.gap : undefined}
175+
>
176+
<CartesianGrid
177+
vertical={true}
178+
horizontal={true}
179+
verticalPoints={[0]}
180+
horizontalPoints={[0]}
181+
stroke={theme.border}
182+
fill={theme.backgroundLevel4}
183+
strokeWidth={1}
184+
/>
185+
{rechartsBars.map((bar) => {
186+
const { fill, dataKey, stackId } = bar;
187+
return (
188+
<Bar
189+
key={dataKey}
190+
dataKey={dataKey}
191+
fill={chartColors[fill] || fill}
192+
minPointSize={stacked ? 0 : CHART_CONSTANTS.MIN_POINT_SIZE}
193+
stackId={stackId}
194+
isAnimationActive={false}
195+
onMouseOver={() => setHoveredValue(dataKey)}
196+
onMouseLeave={() => setHoveredValue(undefined)}
197+
/>
198+
);
199+
})}
200+
201+
<YAxis
202+
interval={0}
203+
domain={[0, topDomain]}
204+
ticks={getTicks(roundReferenceValue, false)}
205+
tickFormatter={tickFormatter}
206+
axisLine={{ stroke: theme.border }}
207+
tick={{
208+
fill: theme.textSecondary,
209+
fontSize: fontSize.smaller,
210+
}}
211+
orientation="right"
212+
/>
213+
214+
<XAxis
215+
dataKey="category"
216+
tick={(props) => (
217+
<CustomTick
218+
{...props}
219+
type={type}
220+
tickWidthOffset={CHART_CONSTANTS.TICK_WIDTH_OFFSET}
221+
/>
222+
)}
223+
type="category"
224+
interval={0}
225+
allowDataOverflow={true}
226+
tickLine={{
227+
stroke: theme.border,
228+
}}
229+
axisLine={{
230+
stroke: theme.border,
231+
}}
232+
/>
233+
234+
<Tooltip
235+
content={(props: TooltipContentProps<number, string>) => (
236+
<BarchartTooltip
237+
type={type}
238+
colorSet={colorSet}
239+
tooltipProps={props}
240+
hoveredValue={hoveredValue}
241+
tooltip={tooltip}
242+
unitLabel={unitLabel}
243+
chartContainerRef={chartRef}
244+
/>
245+
)}
246+
cursor={false}
247+
/>
248+
</RechartsBarChart>
249+
</StyledResponsiveContainer>
250+
);
251+
};
252+
142253
return (
143254
<Stack direction="vertical" style={{ gap: '0' }}>
144255
<ChartHeader
@@ -147,101 +258,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
147258
helpTooltip={helpTooltip}
148259
rightTitle={rightTitle}
149260
/>
150-
{isError || (!bars && !isLoading) ? (
151-
<ChartError height={height} />
152-
) : isLoading ? (
153-
<ChartLoading height={height} />
154-
) : (
155-
<StyledResponsiveContainer ref={chartRef} width="100%" height={height}>
156-
<RechartsBarChart
157-
data={rechartsData}
158-
accessibilityLayer
159-
barSize={
160-
type.type === 'category'
161-
? type.gap === 0
162-
? undefined
163-
: CHART_CONSTANTS.BAR_SIZE
164-
: CHART_CONSTANTS.BAR_SIZE
165-
}
166-
height={height}
167-
margin={CHART_CONSTANTS.CHART_MARGIN}
168-
barCategoryGap={type.type === 'category' ? type.gap : undefined}
169-
>
170-
<CartesianGrid
171-
vertical={true}
172-
horizontal={true}
173-
verticalPoints={[0]}
174-
horizontalPoints={[0]}
175-
stroke={theme.border}
176-
fill={theme.backgroundLevel4}
177-
strokeWidth={1}
178-
/>
179-
{rechartsBars.map((bar) => {
180-
const { fill, dataKey, stackId } = bar;
181-
return (
182-
<Bar
183-
key={dataKey}
184-
dataKey={dataKey}
185-
fill={chartColors[fill] || fill}
186-
minPointSize={stacked ? 0 : CHART_CONSTANTS.MIN_POINT_SIZE}
187-
stackId={stackId}
188-
isAnimationActive={false}
189-
onMouseOver={() => setHoveredValue(dataKey)}
190-
onMouseLeave={() => setHoveredValue(undefined)}
191-
/>
192-
);
193-
})}
194-
195-
<YAxis
196-
interval={0}
197-
domain={[0, topDomain]}
198-
ticks={getTicks(roundReferenceValue, false)}
199-
tickFormatter={(value) => formatToISONumber(value)}
200-
axisLine={{ stroke: theme.border }}
201-
tick={{
202-
fill: theme.textSecondary,
203-
fontSize: fontSize.smaller,
204-
}}
205-
orientation="right"
206-
/>
207-
208-
<XAxis
209-
dataKey="category"
210-
tick={(props) => (
211-
<CustomTick
212-
{...props}
213-
type={type}
214-
tickWidthOffset={CHART_CONSTANTS.TICK_WIDTH_OFFSET}
215-
/>
216-
)}
217-
type="category"
218-
interval={0}
219-
allowDataOverflow={true}
220-
tickLine={{
221-
stroke: theme.border,
222-
}}
223-
axisLine={{
224-
stroke: theme.border,
225-
}}
226-
/>
227-
228-
<Tooltip
229-
content={(props: TooltipContentProps<number, string>) => (
230-
<BarchartTooltip
231-
type={type}
232-
colorSet={colorSet}
233-
tooltipProps={props}
234-
hoveredValue={hoveredValue}
235-
tooltip={tooltip}
236-
unitLabel={unitLabel}
237-
chartContainerRef={chartRef}
238-
/>
239-
)}
240-
cursor={false}
241-
/>
242-
</RechartsBarChart>
243-
</StyledResponsiveContainer>
244-
)}
261+
{renderChartContent()}
245262
</Stack>
246263
);
247264
};

src/lib/components/charts/common/chartUtils.test.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ describe('getRoundReferenceValue', () => {
2323
expect(getRoundReferenceValue(9)).toBe(9); // 9 → 9.9 → 9 (magnitude 1, remainder 0.9)
2424

2525
// Larger values get 10% buffer applied
26-
expect(getRoundReferenceValue(15)).toBe(10); // 15 → 16.5, remainder 5, incremented 20 > 16.5, so round down to 10
27-
expect(getRoundReferenceValue(35)).toBe(30); // 35 → 38.5, remainder 5, incremented 40 > 38.5, so round down to 30
26+
expect(getRoundReferenceValue(15)).toBe(15); // 15 → increment 5, remainder 0, return 15
27+
expect(getRoundReferenceValue(35)).toBe(35); // 35 → increment 5, remainder 0, return 35
2828
expect(getRoundReferenceValue(75)).toBe(80); // 75 → 82.5, remainder 5, incremented 80 <= 82.5, so round up to 80
29-
expect(getRoundReferenceValue(150)).toBe(150); // 150 → 165, remainder 0, so return 150
30-
expect(getRoundReferenceValue(350)).toBe(350); // 350 → 385, remainder 0, so return 350
31-
expect(getRoundReferenceValue(750)).toBe(750); // 750 → 825, remainder 0, so return 750
32-
expect(getRoundReferenceValue(1500)).toBe(1500); // 1500 → 1650, remainder 0, so return 1500
33-
expect(getRoundReferenceValue(3500)).toBe(3500); // 3500 → 3850, remainder 0, so return 3500
34-
expect(getRoundReferenceValue(7500)).toBe(7500); // 7500 → 8250, remainder 0, so return 7500
35-
expect(getRoundReferenceValue(15000)).toBe(15000); // 15000 → 16500, remainder 0, so return 15000
29+
expect(getRoundReferenceValue(150)).toBe(150); // increment 50, remainder 0
30+
expect(getRoundReferenceValue(350)).toBe(350); // increment 50, remainder 0
31+
expect(getRoundReferenceValue(750)).toBe(800); // increment 100, remainder 50, rounds up
32+
expect(getRoundReferenceValue(1500)).toBe(1500); // increment 500, remainder 0
33+
expect(getRoundReferenceValue(3500)).toBe(3500); // increment 500, remainder 0
34+
expect(getRoundReferenceValue(7500)).toBe(8000); // increment 1000, remainder 500, rounds up
35+
expect(getRoundReferenceValue(15000)).toBe(15000); // increment 5000, remainder 0
3636
});
3737
});
3838

@@ -95,8 +95,23 @@ describe('getUnitLabel', () => {
9595
});
9696

9797
describe('addMissingDataPoint', () => {
98+
it('should generate placeholder timestamps when original data is empty', () => {
99+
const result = addMissingDataPoint([], 0, 100, 10);
100+
expect(result).toEqual([
101+
[0, NAN_STRING],
102+
[10, NAN_STRING],
103+
[20, NAN_STRING],
104+
[30, NAN_STRING],
105+
[40, NAN_STRING],
106+
[50, NAN_STRING],
107+
[60, NAN_STRING],
108+
[70, NAN_STRING],
109+
[80, NAN_STRING],
110+
[90, NAN_STRING],
111+
]);
112+
});
113+
98114
it('should return empty array for invalid inputs', () => {
99-
expect(addMissingDataPoint([], 0, 100, 10)).toEqual([]);
100115
expect(addMissingDataPoint([[10, 5]], undefined, 100, 10)).toEqual([]);
101116
expect(addMissingDataPoint([[10, 5]], 0, 0, 10)).toEqual([]);
102117
expect(addMissingDataPoint([[10, 5]], -1, 100, 10)).toEqual([]);
@@ -264,8 +279,8 @@ describe('normalizeChartDataWithUnits', () => {
264279
);
265280

266281
expect(result.unitLabel).toBe('B');
267-
// 680 / 1 = 680 → getRoundReferenceValue(680) = 680
268-
expect(result.topValue).toBe(680);
282+
// 680 / 1 = 680 → getRoundReferenceValue(680) = 700 (rounds up since 80 >= 50)
283+
expect(result.topValue).toBe(700);
269284
expect(result.rechartsData).toEqual([
270285
{ category: 'category1', success: 680 },
271286
]);

0 commit comments

Comments
 (0)