Skip to content
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

feat(charts): add RTL legend support #9570

Merged
merged 6 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion packages/react-charts/src/components/Chart/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import { ChartCommonStyles } from '../ChartTheme/ChartStyles';
import { ChartThemeDefinition } from '../ChartTheme/ChartTheme';
import { getClassName } from '../ChartUtils/chart-helpers';
import { getLabelTextSize } from '../ChartUtils/chart-label';
import { getComputedLegend, getLegendItemsExtraHeight } from '../ChartUtils/chart-legend';
import { getComputedLegend, getLegendItemsExtraHeight, getLegendMaxTextWidth } from '../ChartUtils/chart-legend';
import { getPaddingForSide } from '../ChartUtils/chart-padding';
import { getPatternDefs, mergePatternData, useDefaultPatternProps } from '../ChartUtils/chart-patterns';
import { getChartTheme } from '../ChartUtils/chart-theme-types';
import { useEffect } from 'react';
import { ChartLabel } from '../ChartLabel/ChartLabel';
import { ChartPoint } from '../ChartPoint/ChartPoint';

/**
* Chart is a wrapper component that reconciles the domain for all its children, controls the layout of the chart,
Expand Down Expand Up @@ -284,6 +286,10 @@ export interface ChartProps extends VictoryChartProps {
* cases, the legend may not be visible until enough padding is applied.
*/
legendPosition?: 'bottom' | 'bottom-left' | 'right';
/**
* @beta Text direction of the legend labels.
*/
legendDirection?: 'ltr' | 'rtl';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than a prop, should this auto switch when browser is RTL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per conversation, leaving this PR as is and opened #9656 to investigate adding logic to smartly set the default.

/**
* The maxDomain prop defines a maximum domain value for a chart. This prop is useful in situations where the maximum
* domain of a chart is static, while the minimum value depends on data or other variable information. If the domain
Expand Down Expand Up @@ -471,6 +477,7 @@ export const Chart: React.FunctionComponent<ChartProps> = ({
legendComponent = <ChartLegend />,
legendData,
legendPosition = ChartCommonStyles.legend.position,
legendDirection = 'ltr',
name,
padding,
patternScale,
Expand Down Expand Up @@ -522,11 +529,30 @@ export const Chart: React.FunctionComponent<ChartProps> = ({
...(labelComponent && { labelComponent }) // Override label component props
});

let legendXOffset = 0;
if (legendDirection === 'rtl') {
legendXOffset = getLegendMaxTextWidth(legendData, theme);
}

const legend = React.cloneElement(legendComponent, {
data: legendData,
...(name && { name: `${name}-${(legendComponent as any).type.displayName}` }),
orientation: legendOrientation,
theme,
...(legendDirection === 'rtl' && {
dataComponent: legendComponent.props.dataComponent ? (
React.cloneElement(legendComponent.props.dataComponent, { transform: `translate(${legendXOffset})` })
) : (
<ChartPoint transform={`translate(${legendXOffset})`} />
)
}),
...(legendDirection === 'rtl' && {
labelComponent: legendComponent.props.labelComponent ? (
React.cloneElement(legendComponent.props.labelComponent, { direction: 'rtl', dx: legendXOffset - 30 })
) : (
<ChartLabel direction="rtl" dx={legendXOffset - 30} />
)
}),
...legendComponent.props
});

Expand Down
37 changes: 36 additions & 1 deletion packages/react-charts/src/components/ChartBullet/ChartBullet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ChartLegend } from '../ChartLegend/ChartLegend';
import { ChartThemeDefinition } from '../ChartTheme/ChartTheme';
import { ChartTooltip } from '../ChartTooltip/ChartTooltip';
import { ChartBulletStyles } from '../ChartTheme/ChartStyles';
import { getComputedLegend, getLegendItemsExtraHeight } from '../ChartUtils/chart-legend';
import { getComputedLegend, getLegendItemsExtraHeight, getLegendMaxTextWidth } from '../ChartUtils/chart-legend';
import { ChartBulletComparativeErrorMeasure } from './ChartBulletComparativeErrorMeasure';
import { ChartBulletComparativeMeasure } from './ChartBulletComparativeMeasure';
import { ChartBulletComparativeWarningMeasure } from './ChartBulletComparativeWarningMeasure';
Expand All @@ -29,6 +29,8 @@ import { getBulletDomain } from './utils/chart-bullet-domain';
import { getBulletThemeWithLegendColorScale } from './utils/chart-bullet-theme';
import { getPaddingForSide } from '../ChartUtils/chart-padding';
import { useEffect } from 'react';
import { ChartPoint } from '../ChartPoint/ChartPoint';
import { ChartLabel } from '../ChartLabel/ChartLabel';

/**
* ChartBullet renders a dataset as a bullet chart.
Expand Down Expand Up @@ -252,6 +254,10 @@ export interface ChartBulletProps {
* cases, the legend may not be visible until enough padding is applied.
*/
legendPosition?: 'bottom' | 'bottom-left' | 'right';
/**
* @beta Text direction of the legend labels.
*/
legendDirection?: 'ltr' | 'rtl';
/**
* The maxDomain prop defines a maximum domain value for a chart. This prop is useful in situations where the maximum
* domain of a chart is static, while the minimum value depends on data or other variable information. If the domain
Expand Down Expand Up @@ -509,6 +515,7 @@ export const ChartBullet: React.FunctionComponent<ChartBulletProps> = ({
legendComponent = <ChartLegend />,
legendItemsPerRow,
legendPosition = 'bottom',
legendDirection = 'ltr',
maxDomain,
minDomain,
name,
Expand Down Expand Up @@ -656,6 +663,20 @@ export const ChartBullet: React.FunctionComponent<ChartBulletProps> = ({
...comparativeZeroMeasureComponent.props
});

let legendXOffset = 0;
if (legendDirection === 'rtl') {
legendXOffset = getLegendMaxTextWidth(
[
...(primaryDotMeasureLegendData ? primaryDotMeasureLegendData : []),
...(primarySegmentedMeasureLegendData ? primarySegmentedMeasureLegendData : []),
...(comparativeWarningMeasureLegendData ? comparativeWarningMeasureLegendData : []),
...(comparativeErrorMeasureLegendData ? comparativeErrorMeasureLegendData : []),
...(qualitativeRangeLegendData ? qualitativeRangeLegendData : [])
],
theme
);
}

// Legend
const legend = React.cloneElement(legendComponent, {
data: [
Expand All @@ -670,6 +691,20 @@ export const ChartBullet: React.FunctionComponent<ChartBulletProps> = ({
orientation: legendOrientation,
position: legendPosition,
theme,
...(legendDirection === 'rtl' && {
dataComponent: legendComponent.props.dataComponent ? (
React.cloneElement(legendComponent.props.dataComponent, { transform: `translate(${legendXOffset})` })
) : (
<ChartPoint transform={`translate(${legendXOffset})`} />
)
}),
...(legendDirection === 'rtl' && {
labelComponent: legendComponent.props.labelComponent ? (
React.cloneElement(legendComponent.props.labelComponent, { direction: 'rtl', dx: legendXOffset - 30 })
) : (
<ChartLabel direction="rtl" dx={legendXOffset - 30} />
)
}),
...legendComponent.props
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ export interface ChartDonutProps extends ChartPieProps {
* cases, the legend may not be visible until enough padding is applied.
*/
legendPosition?: 'bottom' | 'right';
/**
* @beta Text direction of the legend labels.
*/
legendDirection?: 'ltr' | 'rtl';
/**
* The name prop is typically used to reference a component instance when defining shared events. However, this
* optional prop may also be applied to child elements as an ID prefix. This is a workaround to ensure Victory
Expand Down Expand Up @@ -573,6 +577,7 @@ export const ChartDonut: React.FunctionComponent<ChartDonutProps> = ({
containerComponent = <ChartContainer />,
innerRadius,
legendPosition = ChartCommonStyles.legend.position,
legendDirection = 'ltr',
name,
padAngle,
padding,
Expand Down Expand Up @@ -698,6 +703,7 @@ export const ChartDonut: React.FunctionComponent<ChartDonutProps> = ({
innerRadius={chartInnerRadius > 0 ? chartInnerRadius : 0}
key="pf-chart-donut-pie"
legendPosition={legendPosition}
legendDirection={legendDirection}
name={name}
padAngle={padAngle !== undefined ? padAngle : getPadAngle}
padding={padding}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ export interface ChartDonutUtilizationProps extends ChartDonutProps {
* cases, the legend may not be visible until enough padding is applied.
*/
legendPosition?: 'bottom' | 'right';
/**
* @beta Text direction of the legend labels.
*/
legendDirection?: 'ltr' | 'rtl';
/**
* The labelRadius prop defines the radius of the arc that will be used for positioning each slice label.
* If this prop is not set, the label radius will default to the radius of the pie + label padding.
Expand Down Expand Up @@ -589,6 +593,7 @@ export const ChartDonutUtilization: React.FunctionComponent<ChartDonutUtilizatio
invert = false,
isStatic = true,
legendPosition = ChartCommonStyles.legend.position,
legendDirection = 'ltr',
padding,
standalone = true,
themeColor,
Expand Down Expand Up @@ -679,6 +684,7 @@ export const ChartDonutUtilization: React.FunctionComponent<ChartDonutUtilizatio
height={height}
key="pf-chart-donut-utilization"
legendPosition={legendPosition}
legendDirection={legendDirection}
padding={padding}
standalone={false}
theme={getThresholdTheme()}
Expand Down
30 changes: 27 additions & 3 deletions packages/react-charts/src/components/ChartPie/ChartPie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ import { ChartLegend } from '../ChartLegend/ChartLegend';
import { ChartCommonStyles } from '../ChartTheme/ChartStyles';
import { ChartThemeDefinition } from '../ChartTheme/ChartTheme';
import { ChartTooltip } from '../ChartTooltip/ChartTooltip';
import { getComputedLegend, getLegendItemsExtraHeight } from '../ChartUtils/chart-legend';
import { getComputedLegend, getLegendItemsExtraHeight, getLegendMaxTextWidth } from '../ChartUtils/chart-legend';
import { getPaddingForSide } from '../ChartUtils/chart-padding';
import { getPatternDefs, useDefaultPatternProps } from '../ChartUtils/chart-patterns';
import { getTheme } from '../ChartUtils/chart-theme';
import { useEffect } from 'react';
import { ChartPoint } from '../ChartPoint/ChartPoint';
import { ChartLabel } from '../ChartLabel/ChartLabel';

/**
* ChartPie renders a dataset as a pie chart.
Expand Down Expand Up @@ -333,6 +335,10 @@ export interface ChartPieProps extends VictoryPieProps {
* cases, the legend may not be visible until enough padding is applied.
*/
legendPosition?: 'bottom' | 'right';
/**
* @beta Text direction of the legend labels.
*/
legendDirection?: 'ltr' | 'rtl';
/**
* The name prop is typically used to reference a component instance when defining shared events. However, this
* optional prop may also be applied to child elements as an ID prefix. This is a workaround to ensure Victory
Expand Down Expand Up @@ -498,16 +504,15 @@ export const ChartPie: React.FunctionComponent<ChartPieProps> = ({
legendComponent = <ChartLegend />,
legendData,
legendPosition = ChartCommonStyles.legend.position,
legendDirection = 'ltr',
name,
patternScale,
patternUnshiftIndex,

padding,
radius,
standalone = true,
style,
themeColor,

// destructure last
theme = getTheme(themeColor),
labelComponent = allowTooltip ? (
Expand Down Expand Up @@ -576,13 +581,32 @@ export const ChartPie: React.FunctionComponent<ChartPieProps> = ({
/>
);

let legendXOffset = 0;
if (legendDirection === 'rtl') {
legendXOffset = getLegendMaxTextWidth(legendData, theme);
}

const legend = React.cloneElement(legendComponent, {
colorScale,
data: legendData,
...(name && { name: `${name}-${(legendComponent as any).type.displayName}` }),
key: 'pf-chart-pie-legend',
orientation: legendOrientation,
theme,
...(legendDirection === 'rtl' && {
dataComponent: legendComponent.props.dataComponent ? (
React.cloneElement(legendComponent.props.dataComponent, { transform: `translate(${legendXOffset})` })
) : (
<ChartPoint transform={`translate(${legendXOffset})`} />
)
}),
...(legendDirection === 'rtl' && {
labelComponent: legendComponent.props.labelComponent ? (
React.cloneElement(legendComponent.props.labelComponent, { direction: 'rtl', dx: legendXOffset - 30 })
) : (
<ChartLabel direction="rtl" dx={legendXOffset - 30} />
)
}),
...legendComponent.props
});

Expand Down
17 changes: 17 additions & 0 deletions packages/react-charts/src/components/ChartUtils/chart-legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { VictoryLegend } from 'victory-legend';
import { ChartLegendProps } from '../ChartLegend/ChartLegend';
import { ChartCommonStyles } from '../ChartTheme/ChartStyles';
import { ChartThemeDefinition } from '../ChartTheme/ChartTheme';
import { getLabelTextSize } from '../ChartUtils/chart-label';
import { getPieOrigin } from './chart-origin';
import * as React from 'react';

Expand Down Expand Up @@ -49,6 +50,22 @@ interface ChartLegendTextMaxSizeInterface {
theme: ChartThemeDefinition; // The theme that will be applied to the chart
}

/**
* Returns the max text length in a legend data set to calculate the x offset for right aligned legends.
* @private
*/

export const getLegendMaxTextWidth = (legendData: any[], theme: ChartThemeDefinition) => {
let legendXOffset = 0;
legendData.map((data: any) => {
const labelWidth = getLabelTextSize({ text: data.name, theme }).width;
if (labelWidth > legendXOffset) {
legendXOffset = labelWidth;
}
});
return legendXOffset;
};

/**
* Returns a legend which has been positioned per the given chart properties
* @private
Expand Down
Loading