Skip to content

Commit dca8419

Browse files
LG-5588 more chart customization points (#3274)
* feat: more chart customization points * docs: add changelog * feat: apply review feedbacks * feat: move common x/y axis properties into a separate file * fix: lint errors * fix: correct props dependency * fixup! fix: lint errors * feat: process review feedback * feat: apply Stephen's feedback * types: export bar-hover-behavior type * more PR review feedback * fix export issue
1 parent 8976ab2 commit dca8419

File tree

23 files changed

+580
-366
lines changed

23 files changed

+580
-366
lines changed

.changeset/rotten-parrots-stare.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@lg-charts/core': minor
3+
---
4+
5+
- ChartTooltip:
6+
- Added `axisPointer` prop supporting 'line', 'shadow', and 'none' options
7+
- Added `className` prop for custom styling
8+
- Bug Fix: renders correctly for values like 0 and empty strings by only checking for null or undefined, not all falsy values
9+
- Bar: `hoverBehavior` prop now accepts 'dim-others' and 'none' options to control hover focus behavior
10+
- XAxis/YAxis: Introduced a new `category` axis type for discrete/categorical datasets (such as for X axes in bar charts).
11+
- It uses a dedicated axis type definition and a `labels` prop for specifying category names.
12+
- Existing continuous axis types (`'log'`, `'time'`, `'value'`) remain unchanged, continuing to support `min`, `max`, and `formatter` for customization.

charts/core/src/Axis/Axis.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { CartesianAxisOption } from 'echarts/types/src/coord/cartesian/AxisModel.js';
2+
3+
import { Theme } from '@leafygreen-ui/lib';
4+
import {
5+
color,
6+
fontFamilies,
7+
fontWeights,
8+
InteractionState,
9+
spacing,
10+
Variant,
11+
} from '@leafygreen-ui/tokens';
12+
13+
import { type AxisProps } from './Axis.types';
14+
15+
export const getAxisOptions = (
16+
theme: Theme,
17+
props: AxisProps,
18+
): CartesianAxisOption => {
19+
const { label, type } = props;
20+
21+
return {
22+
type,
23+
...(type === 'category'
24+
? {
25+
data: props.labels,
26+
}
27+
: {
28+
min: props.min,
29+
max: props.max,
30+
}),
31+
axisLine: {
32+
show: true,
33+
lineStyle: {
34+
color: color[theme].border[Variant.Secondary][InteractionState.Default],
35+
width: 1,
36+
},
37+
},
38+
axisLabel: {
39+
show: true,
40+
fontFamily: fontFamilies.default,
41+
fontWeight: fontWeights.medium,
42+
fontSize: 11,
43+
lineHeight: spacing[400],
44+
color: color[theme].text[Variant.Secondary][InteractionState.Default],
45+
...(type === 'category' ? undefined : { formatter: props.formatter }),
46+
},
47+
axisTick: {
48+
show: false,
49+
},
50+
name: label,
51+
nameLocation: 'middle',
52+
nameTextStyle: {
53+
fontFamily: fontFamilies.default,
54+
fontWeight: fontWeights.medium,
55+
fontSize: 11,
56+
color: color[theme].text[Variant.Secondary][InteractionState.Default],
57+
},
58+
};
59+
};
60+
61+
export const unsetAxisOptions = {
62+
axisLine: {
63+
show: false,
64+
},
65+
axisLabel: {
66+
show: false,
67+
},
68+
};

charts/core/src/Axis/Axis.types.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { AxisLabelValueFormatter } from '../Echart/Echart.types';
2+
3+
/**
4+
* Note: choosing this axis type implicates that the charting library is free to automatically interpolate axis labels and their positions
5+
* which might not exactly align to each provided data point.
6+
*/
7+
const ContinuousAxisTypes = {
8+
Log: 'log',
9+
Time: 'time',
10+
Value: 'value',
11+
} as const;
12+
13+
type ContinuousAxisTypes =
14+
(typeof ContinuousAxisTypes)[keyof typeof ContinuousAxisTypes];
15+
16+
const DiscreteAxisTypes = {
17+
Category: 'category',
18+
} as const;
19+
20+
type DiscreteAxisTypes =
21+
(typeof DiscreteAxisTypes)[keyof typeof DiscreteAxisTypes];
22+
23+
export const AxisType = {
24+
...ContinuousAxisTypes,
25+
...DiscreteAxisTypes,
26+
} as const;
27+
28+
export type AxisType = (typeof AxisType)[keyof typeof AxisType];
29+
30+
interface AxisPropsBase {
31+
/**
32+
* Label name of the axis.
33+
*/
34+
label?: string;
35+
/**
36+
* Type of the axis.
37+
*/
38+
type: AxisType;
39+
}
40+
41+
export interface ContinuousAxisProps extends AxisPropsBase {
42+
type: ContinuousAxisTypes;
43+
44+
/**
45+
*
46+
* Formatter of axis label, which supports string template and callback function.
47+
*
48+
* ```ts
49+
* formatter: (value, index) => `${value}GB`
50+
* ```
51+
*/
52+
formatter?: AxisLabelValueFormatter | string;
53+
54+
/**
55+
* Minimum value of the axis.
56+
*/
57+
min?: number;
58+
59+
/**
60+
* Maximum value of the axis.
61+
*/
62+
max?: number;
63+
}
64+
65+
export interface DiscreteAxisProps extends AxisPropsBase {
66+
type: DiscreteAxisTypes;
67+
68+
/**
69+
* Labels of the data points on the axis.
70+
*/
71+
labels?: Array<string>;
72+
}
73+
74+
export type AxisProps = ContinuousAxisProps | DiscreteAxisProps;

charts/core/src/Axis/XAxis.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useEffect } from 'react';
2+
import merge from 'lodash/merge';
3+
4+
import { useObjectDependency } from '@leafygreen-ui/hooks';
5+
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
6+
import { Theme } from '@leafygreen-ui/lib';
7+
import { spacing } from '@leafygreen-ui/tokens';
8+
9+
import { useChartContext } from '../ChartContext';
10+
import { type EChartOptions } from '../Echart';
11+
12+
import { getAxisOptions, unsetAxisOptions } from './Axis';
13+
import { XAxisProps } from './XAxis.types';
14+
15+
const getXAxisOptions = (
16+
theme: Theme,
17+
props: XAxisProps,
18+
): Omit<EChartOptions, 'series'> => {
19+
const axisOptions = merge(getAxisOptions(theme, props), {
20+
axisLabel: {
21+
align: 'center',
22+
margin: spacing[400],
23+
},
24+
nameTextStyle: {
25+
lineHeight: spacing[400],
26+
padding: [spacing[200], 0, 0, 0],
27+
},
28+
nameGap: spacing[1000],
29+
});
30+
31+
return {
32+
xAxis: axisOptions,
33+
grid: {
34+
bottom: props.label
35+
? spacing[1200] // Pushes out to make room for the label
36+
: spacing[400], // Default bottom spacing
37+
},
38+
};
39+
};
40+
41+
/**
42+
* React component that can render an x-axis on a parent chart.
43+
*
44+
* This is done by updating the parent chart's canvas configuration received via context.
45+
*
46+
* ```
47+
* <Chart>
48+
* <XAxis
49+
* type="time",
50+
* label="My X-Axis Data",
51+
* formatter="{value}GB"
52+
* />
53+
* </Chart>
54+
*/
55+
export function XAxis(props: XAxisProps) {
56+
const {
57+
chart: { ready, updateOptions },
58+
} = useChartContext();
59+
const { theme } = useDarkMode();
60+
const propsDep = useObjectDependency(props);
61+
62+
useEffect(() => {
63+
if (!ready) return;
64+
65+
updateOptions(getXAxisOptions(theme, propsDep));
66+
67+
return () => {
68+
/**
69+
* Hides the axis when the component is unmounted.
70+
*/
71+
updateOptions({
72+
xAxis: { ...unsetAxisOptions },
73+
grid: { bottom: spacing[400] }, // Reset the grid bottom spacing
74+
});
75+
};
76+
}, [ready, theme, updateOptions, propsDep]);
77+
78+
return null;
79+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { AxisProps, AxisType } from './Axis.types';
2+
3+
export const XAxisType = AxisType;
4+
type XAxisType = (typeof XAxisType)[keyof typeof XAxisType];
5+
6+
export type XAxisProps = AxisProps;

charts/core/src/Axis/YAxis.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { useEffect } from 'react';
2+
import merge from 'lodash/merge';
3+
4+
import { useObjectDependency } from '@leafygreen-ui/hooks';
5+
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
6+
import { Theme } from '@leafygreen-ui/lib';
7+
import { spacing } from '@leafygreen-ui/tokens';
8+
9+
import { useChartContext } from '../ChartContext';
10+
import { type EChartOptions } from '../Echart';
11+
12+
import { getAxisOptions, unsetAxisOptions } from './Axis';
13+
import { YAxisProps } from './YAxis.types';
14+
15+
const getYAxisOptions = (
16+
theme: Theme,
17+
props: YAxisProps,
18+
): Omit<EChartOptions, 'series'> => {
19+
const axisOptions = merge(getAxisOptions(theme, props), {
20+
axisLabel: {
21+
align: 'right',
22+
margin: spacing[200],
23+
},
24+
nameTextStyle: {
25+
padding: [0, 0, spacing[800], 0],
26+
},
27+
nameGap: spacing[900],
28+
});
29+
30+
return {
31+
yAxis: axisOptions,
32+
grid: {
33+
left: props.label
34+
? spacing[1200] // Pushes out to make room for the label
35+
: spacing[300], // Default left spacing
36+
},
37+
};
38+
};
39+
40+
/**
41+
* React component that can render an y-axis on a parent chart.
42+
*
43+
* This is done by updating the parent chart's canvas configuration received via context.
44+
*
45+
* ```
46+
* <Chart>
47+
* <YAxis
48+
* type="value",
49+
* label="My Y-Axis Data",
50+
* formatter="{value}GB"
51+
* />
52+
* </Chart>
53+
*/
54+
export function YAxis(props: YAxisProps) {
55+
const {
56+
chart: { ready, updateOptions },
57+
} = useChartContext();
58+
const { theme } = useDarkMode();
59+
const propsDep = useObjectDependency(props);
60+
61+
useEffect(() => {
62+
if (!ready) return;
63+
64+
updateOptions(getYAxisOptions(theme, propsDep));
65+
66+
return () => {
67+
/**
68+
* Hides the axis when the component is unmounted.
69+
*/
70+
updateOptions({
71+
yAxis: { ...unsetAxisOptions },
72+
grid: { left: spacing[300] }, // Reset the grid left spacing
73+
});
74+
};
75+
}, [ready, theme, updateOptions, propsDep]);
76+
77+
return null;
78+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { AxisProps, AxisType } from './Axis.types';
2+
3+
export const YAxisType = AxisType;
4+
type YAxisType = (typeof YAxisType)[keyof typeof YAxisType];
5+
6+
export type YAxisProps = AxisProps;

charts/core/src/Axis/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type {
2+
AxisProps,
3+
AxisType,
4+
ContinuousAxisProps,
5+
DiscreteAxisProps,
6+
} from './Axis.types';
7+
export { XAxis } from './XAxis';
8+
export type { XAxisProps, XAxisType } from './XAxis.types';
9+
export { YAxis } from './YAxis';
10+
export type { YAxisProps, YAxisType } from './YAxis.types';

0 commit comments

Comments
 (0)