-
Notifications
You must be signed in to change notification settings - Fork 71
LG-5588 more chart customization points #3274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
55bbf4a
6e257d7
b3fdac1
7134bf4
961335d
021fba4
b51f729
9f9bcd1
65bd9cd
6ab1097
8f439c4
a297942
489efc5
29f71a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| '@lg-charts/core': minor | ||
| --- | ||
|
|
||
| Chart improvements and refactoring: | ||
| - ChartTooltip: Added `axisPointer` prop supporting 'line', 'shadow', and 'none' options | ||
| - ChartTooltip: Added `className` prop for custom styling | ||
| - Bar: `emphasis` prop now accepts 'self' and 'none' options to control hover focus behavior | ||
| - Axis Types: Added 'category' axis type for discrete data (e.g., bar charts) with properly aligned labels | ||
|
||
| - Bug Fix: Fixed null/undefined check in CustomTooltip to properly handle falsy values like 0 and empty strings | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import { CartesianAxisOption } from 'echarts/types/src/coord/cartesian/AxisModel.js'; | ||
|
|
||
| import { Theme } from '@leafygreen-ui/lib'; | ||
| import { | ||
| color, | ||
| fontFamilies, | ||
| fontWeights, | ||
| InteractionState, | ||
| spacing, | ||
| Variant, | ||
| } from '@leafygreen-ui/tokens'; | ||
|
|
||
| import { type AxisProps } from './Axis.types'; | ||
|
|
||
| export const getAxisOptions = ( | ||
| theme: Theme, | ||
| props: AxisProps, | ||
| ): CartesianAxisOption => { | ||
| const { label, type } = props; | ||
|
|
||
| return { | ||
| type, | ||
| ...(type === 'category' | ||
| ? { | ||
| data: props.labels, | ||
| } | ||
| : { | ||
| min: props.min, | ||
| max: props.max, | ||
| }), | ||
| axisLine: { | ||
| show: true, | ||
| lineStyle: { | ||
| color: color[theme].border[Variant.Secondary][InteractionState.Default], | ||
| width: 1, | ||
| }, | ||
| }, | ||
| axisLabel: { | ||
| show: true, | ||
| fontFamily: fontFamilies.default, | ||
| fontWeight: fontWeights.medium, | ||
| fontSize: 11, | ||
| lineHeight: spacing[400], | ||
| color: color[theme].text[Variant.Secondary][InteractionState.Default], | ||
| formatter: type === 'category' ? undefined : props.formatter, | ||
| }, | ||
| axisTick: { | ||
| show: false, | ||
| }, | ||
| name: label, | ||
| nameLocation: 'middle', | ||
| nameTextStyle: { | ||
| fontFamily: fontFamilies.default, | ||
| fontWeight: fontWeights.medium, | ||
| fontSize: 11, | ||
| color: color[theme].text[Variant.Secondary][InteractionState.Default], | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| export const unsetAxisOptions = { | ||
| axisLine: { | ||
| show: false, | ||
| }, | ||
| axisLabel: { | ||
| show: false, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import type { AxisLabelValueFormatter } from '../Echart/Echart.types'; | ||
|
|
||
| /** | ||
| * Note: choosing this axis type implicates that the charting library is free to automatically interpolate axis labels and their positions | ||
| * which might not exactly align to each provided data point. | ||
| */ | ||
| const ContinuousAxisTypes = { | ||
| Log: 'log', | ||
| Time: 'time', | ||
| Value: 'value', | ||
| } as const; | ||
|
|
||
| type ContinuousAxisTypes = | ||
| (typeof ContinuousAxisTypes)[keyof typeof ContinuousAxisTypes]; | ||
|
|
||
| const DiscreteAxisTypes = { | ||
| Category: 'category', | ||
| } as const; | ||
|
|
||
| type DiscreteAxisTypes = | ||
| (typeof DiscreteAxisTypes)[keyof typeof DiscreteAxisTypes]; | ||
|
|
||
| export const AxisType = { | ||
| ...ContinuousAxisTypes, | ||
| ...DiscreteAxisTypes, | ||
| } as const; | ||
|
|
||
| export type AxisType = (typeof AxisType)[keyof typeof AxisType]; | ||
|
|
||
| interface AxisPropsBase { | ||
| /** | ||
| * Label name of the axis. | ||
| */ | ||
| label?: string; | ||
| /** | ||
| * Type of the axis. | ||
| */ | ||
| type: AxisType; | ||
| } | ||
|
|
||
| export interface ContinuousAxisProps extends AxisPropsBase { | ||
| type: ContinuousAxisTypes; | ||
|
|
||
| /** | ||
| * | ||
| * Formatter of axis label, which supports string template and callback function. | ||
| * | ||
| * ```ts | ||
| * formatter: (value, index) => `${value}GB` | ||
| * ``` | ||
| */ | ||
| formatter?: AxisLabelValueFormatter | string; | ||
|
|
||
| /** | ||
| * Minimum value of the axis. | ||
| */ | ||
| min?: number; | ||
|
|
||
| /** | ||
| * Maximum value of the axis. | ||
| */ | ||
| max?: number; | ||
| } | ||
|
|
||
| export interface DiscreteAxisProps extends AxisPropsBase { | ||
| type: DiscreteAxisTypes; | ||
|
|
||
| /** | ||
| * Labels of the data points on the axis. | ||
| */ | ||
| labels?: Array<string>; | ||
| } | ||
|
|
||
| export type AxisProps = ContinuousAxisProps | DiscreteAxisProps; |
TheSonOfThomp marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { useEffect } from 'react'; | ||
| import merge from 'lodash/merge'; | ||
|
|
||
| import { useObjectDependency } from '@leafygreen-ui/hooks'; | ||
| import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; | ||
| import { Theme } from '@leafygreen-ui/lib'; | ||
| import { spacing } from '@leafygreen-ui/tokens'; | ||
|
|
||
| import { useChartContext } from '../ChartContext'; | ||
| import { type EChartOptions } from '../Echart'; | ||
|
|
||
| import { getAxisOptions, unsetAxisOptions } from './Axis'; | ||
| import { XAxisProps } from './XAxis.types'; | ||
|
|
||
| const getXAxisOptions = ( | ||
| theme: Theme, | ||
| props: XAxisProps, | ||
| ): Omit<EChartOptions, 'series'> => { | ||
| const axisOptions = merge(getAxisOptions(theme, props), { | ||
| axisLabel: { | ||
| align: 'center', | ||
| margin: spacing[400], | ||
| }, | ||
| nameTextStyle: { | ||
| lineHeight: spacing[400], | ||
| padding: [spacing[200], 0, 0, 0], | ||
| }, | ||
| nameGap: spacing[1000], | ||
| }); | ||
|
|
||
| return { | ||
| xAxis: axisOptions, | ||
| grid: { | ||
| bottom: props.label | ||
| ? spacing[1200] // Pushes out to make room for the label | ||
| : spacing[400], // Default bottom spacing | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| /** | ||
| * React component that can render an x-axis on a parent chart. | ||
| * | ||
| * This is done by updating the parent chart's canvas configuration received via context. | ||
| * | ||
| * ``` | ||
| * <Chart> | ||
| * <XAxis | ||
| * type="time", | ||
| * label="My X-Axis Data", | ||
| * formatter="{value}GB" | ||
| * /> | ||
| * </Chart> | ||
| */ | ||
| export function XAxis(props: XAxisProps) { | ||
| const { | ||
| chart: { ready, updateOptions }, | ||
| } = useChartContext(); | ||
| const { theme } = useDarkMode(); | ||
| const propsDep = useObjectDependency(props); | ||
|
|
||
| useEffect(() => { | ||
| if (!ready) return; | ||
|
|
||
| updateOptions(getXAxisOptions(theme, propsDep)); | ||
|
|
||
| return () => { | ||
| /** | ||
| * Hides the axis when the component is unmounted. | ||
| */ | ||
| updateOptions({ | ||
| xAxis: { ...unsetAxisOptions }, | ||
| grid: { bottom: spacing[400] }, // Reset the grid bottom spacing | ||
| }); | ||
| }; | ||
| }, [ready, theme, updateOptions, propsDep]); | ||
|
|
||
| return null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { AxisProps, AxisType } from './Axis.types'; | ||
|
|
||
| export const XAxisType = AxisType; | ||
| type XAxisType = (typeof XAxisType)[keyof typeof XAxisType]; | ||
|
|
||
| export type XAxisProps = AxisProps; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { useEffect } from 'react'; | ||
| import merge from 'lodash/merge'; | ||
|
|
||
| import { useObjectDependency } from '@leafygreen-ui/hooks'; | ||
| import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; | ||
| import { Theme } from '@leafygreen-ui/lib'; | ||
| import { spacing } from '@leafygreen-ui/tokens'; | ||
|
|
||
| import { useChartContext } from '../ChartContext'; | ||
| import { type EChartOptions } from '../Echart'; | ||
|
|
||
| import { getAxisOptions, unsetAxisOptions } from './Axis'; | ||
| import { YAxisProps } from './YAxis.types'; | ||
|
|
||
| const getYAxisOptions = ( | ||
| theme: Theme, | ||
| props: YAxisProps, | ||
| ): Omit<EChartOptions, 'series'> => { | ||
| const axisOptions = merge(getAxisOptions(theme, props), { | ||
| axisLabel: { | ||
| align: 'right', | ||
| margin: spacing[200], | ||
| }, | ||
| nameTextStyle: { | ||
| padding: [0, 0, spacing[800], 0], | ||
| }, | ||
| nameGap: spacing[900], | ||
| }); | ||
|
|
||
| return { | ||
| yAxis: axisOptions, | ||
| grid: { | ||
| left: props.label | ||
| ? spacing[1200] // Pushes out to make room for the label | ||
| : spacing[300], // Default left spacing | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| /** | ||
| * React component that can render an y-axis on a parent chart. | ||
| * | ||
| * This is done by updating the parent chart's canvas configuration received via context. | ||
| * | ||
| * ``` | ||
| * <Chart> | ||
| * <YAxis | ||
| * type="value", | ||
| * label="My Y-Axis Data", | ||
| * formatter="{value}GB" | ||
| * /> | ||
| * </Chart> | ||
| */ | ||
| export function YAxis(props: YAxisProps) { | ||
| const { | ||
| chart: { ready, updateOptions }, | ||
| } = useChartContext(); | ||
| const { theme } = useDarkMode(); | ||
| const propsDep = useObjectDependency(props); | ||
|
|
||
| useEffect(() => { | ||
| if (!ready) return; | ||
|
|
||
| updateOptions(getYAxisOptions(theme, propsDep)); | ||
|
|
||
| return () => { | ||
| /** | ||
| * Hides the axis when the component is unmounted. | ||
| */ | ||
| updateOptions({ | ||
| yAxis: { ...unsetAxisOptions }, | ||
| grid: { left: spacing[300] }, // Reset the grid left spacing | ||
| }); | ||
| }; | ||
| }, [ready, theme, updateOptions, propsDep]); | ||
|
|
||
| return null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { AxisProps, AxisType } from './Axis.types'; | ||
|
|
||
| export const YAxisType = AxisType; | ||
| type YAxisType = (typeof YAxisType)[keyof typeof YAxisType]; | ||
|
|
||
| export type YAxisProps = AxisProps; |
TheSonOfThomp marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export type { | ||
| AxisProps, | ||
| AxisType, | ||
| ContinuousAxisProps, | ||
| DiscreteAxisProps, | ||
| } from './Axis.types'; | ||
| export { XAxis } from './XAxis'; | ||
| export type { XAxisProps, XAxisType } from './XAxis.types'; | ||
| export { YAxis } from './YAxis'; | ||
| export type { YAxisProps, YAxisType } from './YAxis.types'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should update to reflect the consumer API changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π«‘