diff --git a/packages/react-charts/src/components/Chart/Chart.tsx b/packages/react-charts/src/components/Chart/Chart.tsx index d7954a92db9..10803be4699 100644 --- a/packages/react-charts/src/components/Chart/Chart.tsx +++ b/packages/react-charts/src/components/Chart/Chart.tsx @@ -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, @@ -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'; /** * 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 @@ -471,6 +477,7 @@ export const Chart: React.FunctionComponent = ({ legendComponent = , legendData, legendPosition = ChartCommonStyles.legend.position, + legendDirection = 'ltr', name, padding, patternScale, @@ -522,11 +529,30 @@ export const Chart: React.FunctionComponent = ({ ...(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})` }) + ) : ( + + ) + }), + ...(legendDirection === 'rtl' && { + labelComponent: legendComponent.props.labelComponent ? ( + React.cloneElement(legendComponent.props.labelComponent, { direction: 'rtl', dx: legendXOffset - 30 }) + ) : ( + + ) + }), ...legendComponent.props }); diff --git a/packages/react-charts/src/components/ChartBullet/ChartBullet.tsx b/packages/react-charts/src/components/ChartBullet/ChartBullet.tsx index 72d01043bea..42ab60edcd5 100644 --- a/packages/react-charts/src/components/ChartBullet/ChartBullet.tsx +++ b/packages/react-charts/src/components/ChartBullet/ChartBullet.tsx @@ -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'; @@ -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. @@ -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 @@ -509,6 +515,7 @@ export const ChartBullet: React.FunctionComponent = ({ legendComponent = , legendItemsPerRow, legendPosition = 'bottom', + legendDirection = 'ltr', maxDomain, minDomain, name, @@ -656,6 +663,20 @@ export const ChartBullet: React.FunctionComponent = ({ ...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: [ @@ -670,6 +691,20 @@ export const ChartBullet: React.FunctionComponent = ({ orientation: legendOrientation, position: legendPosition, theme, + ...(legendDirection === 'rtl' && { + dataComponent: legendComponent.props.dataComponent ? ( + React.cloneElement(legendComponent.props.dataComponent, { transform: `translate(${legendXOffset})` }) + ) : ( + + ) + }), + ...(legendDirection === 'rtl' && { + labelComponent: legendComponent.props.labelComponent ? ( + React.cloneElement(legendComponent.props.labelComponent, { direction: 'rtl', dx: legendXOffset - 30 }) + ) : ( + + ) + }), ...legendComponent.props }); diff --git a/packages/react-charts/src/components/ChartDonut/ChartDonut.tsx b/packages/react-charts/src/components/ChartDonut/ChartDonut.tsx index 19f323ddc97..d5b3aa74b27 100644 --- a/packages/react-charts/src/components/ChartDonut/ChartDonut.tsx +++ b/packages/react-charts/src/components/ChartDonut/ChartDonut.tsx @@ -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 @@ -573,6 +577,7 @@ export const ChartDonut: React.FunctionComponent = ({ containerComponent = , innerRadius, legendPosition = ChartCommonStyles.legend.position, + legendDirection = 'ltr', name, padAngle, padding, @@ -698,6 +703,7 @@ export const ChartDonut: React.FunctionComponent = ({ innerRadius={chartInnerRadius > 0 ? chartInnerRadius : 0} key="pf-chart-donut-pie" legendPosition={legendPosition} + legendDirection={legendDirection} name={name} padAngle={padAngle !== undefined ? padAngle : getPadAngle} padding={padding} diff --git a/packages/react-charts/src/components/ChartDonutUtilization/ChartDonutUtilization.tsx b/packages/react-charts/src/components/ChartDonutUtilization/ChartDonutUtilization.tsx index 26892f0e747..90b8cf1ee03 100644 --- a/packages/react-charts/src/components/ChartDonutUtilization/ChartDonutUtilization.tsx +++ b/packages/react-charts/src/components/ChartDonutUtilization/ChartDonutUtilization.tsx @@ -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. @@ -589,6 +593,7 @@ export const ChartDonutUtilization: React.FunctionComponent = ({ legendComponent = , legendData, legendPosition = ChartCommonStyles.legend.position, + legendDirection = 'ltr', name, patternScale, patternUnshiftIndex, - padding, radius, standalone = true, style, themeColor, - // destructure last theme = getTheme(themeColor), labelComponent = allowTooltip ? ( @@ -576,6 +581,11 @@ export const ChartPie: React.FunctionComponent = ({ /> ); + let legendXOffset = 0; + if (legendDirection === 'rtl') { + legendXOffset = getLegendMaxTextWidth(legendData, theme); + } + const legend = React.cloneElement(legendComponent, { colorScale, data: legendData, @@ -583,6 +593,20 @@ export const ChartPie: React.FunctionComponent = ({ key: 'pf-chart-pie-legend', orientation: legendOrientation, theme, + ...(legendDirection === 'rtl' && { + dataComponent: legendComponent.props.dataComponent ? ( + React.cloneElement(legendComponent.props.dataComponent, { transform: `translate(${legendXOffset})` }) + ) : ( + + ) + }), + ...(legendDirection === 'rtl' && { + labelComponent: legendComponent.props.labelComponent ? ( + React.cloneElement(legendComponent.props.labelComponent, { direction: 'rtl', dx: legendXOffset - 30 }) + ) : ( + + ) + }), ...legendComponent.props }); diff --git a/packages/react-charts/src/components/ChartUtils/chart-legend.ts b/packages/react-charts/src/components/ChartUtils/chart-legend.ts index 060a683535f..3bbd51bda43 100644 --- a/packages/react-charts/src/components/ChartUtils/chart-legend.ts +++ b/packages/react-charts/src/components/ChartUtils/chart-legend.ts @@ -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'; @@ -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