Skip to content

Commit 14d559b

Browse files
[charts] Add minTickLabelGap to x-axis
1 parent 9b0bc3f commit 14d559b

File tree

4 files changed

+49
-11
lines changed

4 files changed

+49
-11
lines changed

packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx

+26-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import useSlotProps from '@mui/utils/useSlotProps';
55
import composeClasses from '@mui/utils/composeClasses';
66
import { useThemeProps, useTheme, Theme, styled } from '@mui/material/styles';
77
import { useTicks, TickItemType } from '../hooks/useTicks';
8-
import { AxisDefaultized, ChartsXAxisProps } from '../models/axis';
8+
import { AxisDefaultized, ChartsXAxisProps, ScaleName } from '../models/axis';
99
import { getAxisUtilityClass } from '../ChartsAxis/axisClasses';
1010
import { AxisRoot } from '../internals/components/AxisSharedComponents';
1111
import { ChartsText, ChartsTextProps } from '../ChartsText';
@@ -37,18 +37,29 @@ type LabelExtraData = { width: number; height: number; skipLabel?: boolean };
3737
function addLabelDimension(
3838
xTicks: TickItemType[],
3939
{
40+
tickLabelClassName: className,
4041
tickLabelStyle: style,
4142
tickLabelInterval,
43+
// FIXME: Define the default value in the correct place
44+
minTickLabelGap = 8,
4245
reverse,
4346
isMounted,
4447
}: Pick<ChartsXAxisProps, 'tickLabelInterval' | 'tickLabelStyle'> &
45-
Pick<AxisDefaultized, 'reverse'> & { isMounted: boolean },
48+
Pick<AxisDefaultized<ScaleName, any, ChartsXAxisProps>, 'reverse' | 'minTickLabelGap'> & {
49+
tickLabelClassName?: string;
50+
isMounted: boolean;
51+
},
4652
): (TickItemType & LabelExtraData)[] {
4753
const withDimension = xTicks.map((tick) => {
4854
if (!isMounted || tick.formattedValue === undefined) {
4955
return { ...tick, width: 0, height: 0 };
5056
}
51-
const tickSizes = getWordsByLines({ style, needsComputation: true, text: tick.formattedValue });
57+
const tickSizes = getWordsByLines({
58+
className,
59+
style,
60+
needsComputation: true,
61+
text: tick.formattedValue,
62+
});
5263
return {
5364
...tick,
5465
width: Math.max(...tickSizes.map((size) => size.width)),
@@ -64,23 +75,24 @@ function addLabelDimension(
6475
}
6576

6677
// Filter label to avoid overlap
67-
let currentTextLimit = 0;
6878
let previousTextLimit = 0;
6979
const direction = reverse ? -1 : 1;
7080
return withDimension.map((item, labelIndex) => {
7181
const { width, offset, labelOffset, height } = item;
7282

7383
const distance = getMinXTranslation(width, height, style?.angle);
7484
const textPosition = offset + labelOffset;
75-
const gapRatio = 1.2; // Ratio applied to the minimal distance to add some margin.
7685

77-
currentTextLimit = textPosition - (direction * (gapRatio * distance)) / 2;
78-
if (labelIndex > 0 && direction * currentTextLimit < direction * previousTextLimit) {
86+
const currentTextLimit = textPosition - (direction * distance) / 2;
87+
if (
88+
labelIndex > 0 &&
89+
direction * currentTextLimit < direction * (previousTextLimit + minTickLabelGap)
90+
) {
7991
// Except for the first label, we skip all label that overlap with the last accepted.
8092
// Notice that the early return prevents `previousTextLimit` from being updated.
8193
return { ...item, skipLabel: true };
8294
}
83-
previousTextLimit = textPosition + (direction * (gapRatio * distance)) / 2;
95+
previousTextLimit = textPosition + (direction * distance) / 2;
8496
return item;
8597
});
8698
}
@@ -157,6 +169,11 @@ function ChartsXAxis(inProps: ChartsXAxisProps) {
157169
externalSlotProps: slotProps?.axisTickLabel,
158170
additionalProps: {
159171
style: {
172+
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
173+
fontWeight: 400,
174+
lineHeight: 1.66,
175+
letterSpacing: '0.03333em',
176+
fill: 'rgba(0, 0, 0, 0.87)',
160177
fontSize: 12,
161178
textAnchor: 'middle',
162179
dominantBaseline: position === 'bottom' ? 'hanging' : 'auto',
@@ -177,6 +194,7 @@ function ChartsXAxis(inProps: ChartsXAxisProps) {
177194
});
178195

179196
const xTicksWithDimension = addLabelDimension(xTicks, {
197+
tickLabelClassName: axisTickLabelProps.className,
180198
tickLabelStyle: axisTickLabelProps.style,
181199
tickLabelInterval,
182200
reverse,

packages/x-charts/src/internals/domUtils.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,18 @@ let domCleanTimeout: NodeJS.Timeout | undefined;
104104
* @param style The style applied
105105
* @returns width and height of the text
106106
*/
107-
export const getStringSize = (text: string | number, style: React.CSSProperties = {}) => {
107+
export const getStringSize = (
108+
text: string | number,
109+
{ className = '', style = {} }: { className?: string; style?: React.CSSProperties },
110+
) => {
108111
if (text === undefined || text === null || isSsr()) {
109112
return { width: 0, height: 0 };
110113
}
111114

112115
const str = `${text}`;
113116
const styleString = getStyleString(style);
117+
// FIXME: How to handle the className in cache? The class name be the same, but the style it's applying can change,
118+
// however this wouldn't cause the cache to be invalidated, resulting in wrong sizes.
114119
const cacheKey = `${str}-${styleString}`;
115120

116121
if (stringCache.widthCache[cacheKey]) {
@@ -129,6 +134,7 @@ export const getStringSize = (text: string | number, style: React.CSSProperties
129134
// https://en.wikipedia.org/wiki/Content_Security_Policy
130135
const measurementSpanStyle: Record<string, any> = { ...SPAN_STYLE, ...style };
131136

137+
measurementSpan.className = className;
132138
Object.keys(measurementSpanStyle).map((styleKey) => {
133139
(measurementSpan!.style as Record<string, any>)[camelToMiddleLine(styleKey)] =
134140
autoCompleteStyle(styleKey, measurementSpanStyle[styleKey]);

packages/x-charts/src/internals/getWordsByLines.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export interface ChartsTextStyle extends React.CSSProperties {
1212
}
1313

1414
export interface GetWordsByLinesParams {
15+
/**
16+
* CSS class applied to text elements.
17+
*/
18+
className?: string;
1519
/**
1620
* Text displayed.
1721
*/
@@ -27,9 +31,14 @@ export interface GetWordsByLinesParams {
2731
needsComputation?: boolean;
2832
}
2933

30-
export function getWordsByLines({ style, needsComputation, text }: GetWordsByLinesParams) {
34+
export function getWordsByLines({
35+
className,
36+
style,
37+
needsComputation,
38+
text,
39+
}: GetWordsByLinesParams) {
3140
return text.split('\n').map((subText) => ({
3241
text: subText,
33-
...(needsComputation ? getStringSize(subText, style) : { width: 0, height: 0 }),
42+
...(needsComputation ? getStringSize(subText, { className, style }) : { width: 0, height: 0 }),
3443
}));
3544
}

packages/x-charts/src/models/axis.ts

+5
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ export interface ChartsYAxisProps extends ChartsAxisProps {
140140
}
141141

142142
export interface ChartsXAxisProps extends ChartsAxisProps {
143+
/**
144+
* The minimum gap in pixels between two tick labels.
145+
* @default 8
146+
*/
147+
minTickLabelGap?: number;
143148
/**
144149
* Position of the axis.
145150
*/

0 commit comments

Comments
 (0)