Skip to content

Commit 7ab263e

Browse files
committed
Merge branch 'improvement/barchart-multiline-tick-labels' into q/1.0
2 parents ea40796 + 966c030 commit 7ab263e

4 files changed

Lines changed: 121 additions & 34 deletions

File tree

src/lib/components/charts/common/SharedComponents.tsx

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ const TickContainer = styled.div`
3131
justify-content: center;
3232
`;
3333

34+
// Stacks a multi-line tick label (e.g. time + date on a midnight crossover)
35+
// tightly. The reduced line-height keeps the two lines visually grouped; each
36+
// line stays its own ConstrainedText so it truncates independently.
37+
const MultilineTickContainer = styled.div`
38+
width: 100%;
39+
display: flex;
40+
flex-direction: column;
41+
align-items: center;
42+
& span {
43+
line-height: 1.1;
44+
}
45+
`;
46+
47+
// Extra height granted per wrapped line so a second line is not clipped.
48+
const TICK_LINE_HEIGHT = 12;
49+
3450
interface ChartLoadingOrErrorProps {
3551
height: number;
3652
}
@@ -158,42 +174,57 @@ export const CustomTick = ({
158174
1000
159175
: 0;
160176

177+
const tooltipStyle = {
178+
backgroundColor: theme.backgroundLevel1,
179+
padding: spacing.r10,
180+
borderRadius: spacing.r8,
181+
border: `1px solid ${theme.border}`,
182+
position: 'absolute' as const,
183+
};
184+
185+
const labelLine = (content: React.ReactNode, key?: React.Key) => (
186+
<ConstrainedText
187+
key={key}
188+
color="textSecondary"
189+
text={<Text variant="Smaller">{content}</Text>}
190+
centered
191+
tooltipStyle={tooltipStyle}
192+
/>
193+
);
194+
195+
// A category label may carry a second line (e.g. a date on a midnight
196+
// crossover) separated by "\n"; those lines are stacked tightly.
197+
const lines = type.type === 'time' ? [] : String(payload.value).split('\n');
198+
const isMultiline = lines.length > 1;
199+
200+
const content =
201+
type.type === 'time' ? (
202+
labelLine(
203+
<FormattedDateTime
204+
format={formatXAxisDate(duration)}
205+
value={new Date(payload.value)}
206+
/>,
207+
)
208+
) : isMultiline ? (
209+
<MultilineTickContainer>
210+
{lines.map((line, index) => labelLine(line, index))}
211+
</MultilineTickContainer>
212+
) : (
213+
labelLine(String(payload.value))
214+
);
215+
161216
return (
162217
<foreignObject
163218
x={centerX}
164219
y={numY - 10}
165220
width={tickWidth}
166-
height={30}
221+
height={30 + (isMultiline ? (lines.length - 1) * TICK_LINE_HEIGHT : 0)}
167222
style={{
168223
overflow: 'visible',
169224
pointerEvents: 'none',
170225
}}
171226
>
172-
<TickContainer>
173-
<ConstrainedText
174-
color="textSecondary"
175-
text={
176-
<Text variant="Smaller">
177-
{type.type === 'time' ? (
178-
<FormattedDateTime
179-
format={formatXAxisDate(duration)}
180-
value={new Date(payload.value)}
181-
/>
182-
) : (
183-
String(payload.value)
184-
)}
185-
</Text>
186-
}
187-
centered
188-
tooltipStyle={{
189-
backgroundColor: theme.backgroundLevel1,
190-
padding: spacing.r10,
191-
borderRadius: spacing.r8,
192-
border: `1px solid ${theme.border}`,
193-
position: 'absolute',
194-
}}
195-
/>
196-
</TickContainer>
227+
<TickContainer>{content}</TickContainer>
197228
</foreignObject>
198229
);
199230
};

src/lib/components/date/FormattedDateTime.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ export const DAY_MONTH_ABBREVIATED = Intl.DateTimeFormat('en-GB', {
7474
hour12: false,
7575
});
7676

77+
/**
78+
* @description Day month abbreviated as a plain string, with the locale comma
79+
* stripped and "Sept" normalised to the 3-letter "Sep". Shared by the
80+
* `day-month-abbreviated` formatter case and chart label helpers.
81+
* @example 06 Jun
82+
*/
83+
export const formatDayMonthAbbreviated = (value: Date): string =>
84+
DAY_MONTH_ABBREVIATED.format(value)
85+
.replace(',', '')
86+
.replace(/Sept/g, 'Sep');
87+
7788
/**
7889
* @description Day month abbreviated formatter. Used for describing long term date.
7990
* @example 06 Oct 25
@@ -339,13 +350,7 @@ export const FormattedDateTime = ({
339350
</>
340351
);
341352
case 'day-month-abbreviated':
342-
return (
343-
<>
344-
{DAY_MONTH_ABBREVIATED.format(value)
345-
.replace(',', '')
346-
.replace(/Sept/g, 'Sep')}
347-
</>
348-
);
353+
return <>{formatDayMonthAbbreviated(value)}</>;
349354
case 'chart-long-term-date':
350355
return (
351356
<>

src/lib/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ export { Icon } from './components/icon/Icon.component';
7272
export { StatusWrapper } from './components/statuswrapper/Statuswrapper.component';
7373
export { Stack, Wrap, spacing } from './spacing';
7474
export { Form, FormSection, FormGroup } from './components/form/Form.component';
75-
export { FormattedDateTime } from './components/date/FormattedDateTime';
75+
export {
76+
FormattedDateTime,
77+
TIME_FORMATER,
78+
formatDayMonthAbbreviated,
79+
} from './components/date/FormattedDateTime';
7680
export { getDateDaysDiff } from './components/date/dateDiffer';
7781
export { IconHelp } from './components/iconhelper/IconHelper';
7882
export { Dropzone } from './components/dropzone/Dropzone';

stories/BarChart/barchart.stories.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,53 @@ export const Histogram: Story = {
986986
},
987987
};
988988

989+
/**
990+
* A bucket label may contain a `"\n"` to render on two lines — here the bucket
991+
* that crosses midnight shows the date (`02 Jun`) on a second line. The caller
992+
* supplies these strings; the chart renders them as-is via `CustomTick`'s
993+
* multi-line support.
994+
*/
995+
export const HistogramWithMultilineLabels: Story = {
996+
render: () => {
997+
const theme = useTheme() as CoreUITheme;
998+
const labels = [
999+
'11:00',
1000+
'14:00',
1001+
'17:00',
1002+
'20:00',
1003+
'23:00',
1004+
'02:00\n02 Jun',
1005+
'05:00',
1006+
'08:00',
1007+
];
1008+
const values = [12, 30, 45, 50, 42, 20, 15, 25];
1009+
const histogramData = [
1010+
{
1011+
label: 'Requests',
1012+
data: labels.map(
1013+
(label, i) => [label, values[i]] as [string, number],
1014+
),
1015+
},
1016+
];
1017+
return (
1018+
<div style={{ width: '50%', padding: spacing.r16 }}>
1019+
<ChartLegendWrapper
1020+
colorSet={{
1021+
Requests: theme.statusHealthy,
1022+
}}
1023+
>
1024+
<Barchart
1025+
type={{ type: 'category', gap: 0 }}
1026+
bars={histogramData}
1027+
title="Requests over 24h"
1028+
/>
1029+
<ChartLegend shape="rectangle" />
1030+
</ChartLegendWrapper>
1031+
</div>
1032+
);
1033+
},
1034+
};
1035+
9891036
export const ModernPreset: Story = {
9901037
render: () => {
9911038
const theme = useTheme() as CoreUITheme;

0 commit comments

Comments
 (0)