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

Charts: Added support for patterns #7390

Merged
merged 4 commits into from
Jun 10, 2022
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
92 changes: 89 additions & 3 deletions packages/react-charts/src/components/Chart/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,18 @@ import { AxesType, VictoryChart, VictoryChartProps } from 'victory-chart';
import { ChartContainer } from '../ChartContainer';
import { ChartLegend, ChartLegendOrientation, ChartLegendPosition } from '../ChartLegend';
import { ChartCommonStyles, ChartThemeDefinition } from '../ChartTheme';
import { getChartTheme, getClassName, getComputedLegend, getLabelTextSize, getPaddingForSide } from '../ChartUtils';
import {
getChartTheme,
getClassName,
getComputedLegend,
getLabelTextSize,
getPaddingForSide,
getPatternId,
getPatternDefs,
getDefaultColorScale,
getDefaultData,
getDefaultPatternScale
} from '../ChartUtils';

/**
* See https://github.com/FormidableLabs/victory/blob/master/packages/victory-core/src/index.d.ts
Expand Down Expand Up @@ -207,6 +218,11 @@ export interface ChartProps extends VictoryChartProps {
* @propType number | Function
*/
innerRadius?: number;
/**
* Generate default pattern defs and populate patternScale
* @beta
*/
isPatternDefs?: boolean;
/**
* Allows legend items to wrap. A value of true allows the legend to wrap onto the next line
* if its container is not wide enough.
Expand Down Expand Up @@ -291,6 +307,26 @@ export interface ChartProps extends VictoryChartProps {
* @propType number | { top: number, bottom: number, left: number, right: number }
*/
padding?: PaddingProps;
/**
* The optional ID to prefix pattern defs
*
* @example patternId="pattern"
* @beta
*/
patternId?: string;
/**
* The patternScale prop is an optional prop that defines a pattern to be applied to the children, where applicable.
* This prop should be given as an array of CSS colors, or as a string corresponding to a URL. Patterns will be
* assigned to children by index, unless they are explicitly specified in styles. Patterns will repeat when there are
* more children than patterns in the provided patternScale. Functionality may be overridden via the `style.data.fill`
* property.
*
* Note: Not all components are supported; for example, ChartLine, ChartBullet, ChartThreshold, etc.
*
* @example patternScale={['url("#pattern:0")', 'url("#pattern:1")', 'url("#pattern:2")']}
* @beta
*/
patternScale?: string[];
/**
* Note: This prop should not be set manually.
*
Expand Down Expand Up @@ -423,6 +459,7 @@ export const Chart: React.FunctionComponent<ChartProps> = ({
ariaDesc,
ariaTitle,
children,
colorScale,
legendAllowWrap = false,
legendComponent = <ChartLegend />,
legendData,
Expand All @@ -432,6 +469,9 @@ export const Chart: React.FunctionComponent<ChartProps> = ({
themeColor,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
themeVariant,
patternId = getPatternId(),
patternScale,
isPatternDefs = false,

// destructure last
theme = getChartTheme(themeColor, showAxis),
Expand All @@ -448,13 +488,36 @@ export const Chart: React.FunctionComponent<ChartProps> = ({
top: getPaddingForSide('top', padding, theme.chart.padding)
};

const defaultColorScale = getDefaultColorScale(colorScale as any, theme.chart.colorScale as string[]);
const defaultPatternScale = getDefaultPatternScale({
colorScale: defaultColorScale,
patternScale,
patternId,
isPatternDefs
});

// Add pattern props for legend tooltip
let labelComponent;
if (
containerComponent.props.labelComponent &&
containerComponent.props.labelComponent.type.displayName === 'ChartLegendTooltip'
) {
labelComponent = React.cloneElement(containerComponent.props.labelComponent, {
patternId,
theme,
...(defaultPatternScale && { patternScale: defaultPatternScale }),
...containerComponent.props.labelComponent.props
});
}

// Clone so users can override container props
const container = React.cloneElement(containerComponent, {
desc: ariaDesc,
title: ariaTitle,
theme,
...containerComponent.props,
className: getClassName({ className: containerComponent.props.className }) // Override VictoryContainer class name
className: getClassName({ className: containerComponent.props.className }), // Override VictoryContainer class name
...(labelComponent && { labelComponent }) // Override label component props
});

const legend = React.cloneElement(legendComponent, {
Expand Down Expand Up @@ -497,29 +560,52 @@ export const Chart: React.FunctionComponent<ChartProps> = ({
return getComputedLegend({
allowWrap: legendAllowWrap,
chartType: 'chart',
colorScale,
dx,
dy,
height,
legendComponent: legend,
padding: defaultPadding,
...(defaultPatternScale && { patternScale: defaultPatternScale }),
position: legendPosition,
theme,
width
});
};

// Render children
const renderChildren = () =>
React.Children.toArray(children).map(child => {
if (React.isValidElement(child)) {
const { ...childProps } = child.props;
return React.cloneElement(child, {
colorScale,
patternId,
theme,
...(defaultPatternScale && { patternScale: defaultPatternScale }),
...childProps,
...((child as any).type.displayName === 'ChartPie' && {
data: getDefaultData(childProps.data, defaultPatternScale)
}) // Override child props
});
}
return child;
});

// Note: containerComponent is required for theme
return (
<VictoryChart
colorScale={colorScale}
containerComponent={container}
height={height}
padding={defaultPadding}
theme={theme}
width={width}
{...rest}
>
{children}
{renderChildren()}
{getLegend()}
{isPatternDefs && getPatternDefs({ patternId, patternScale: defaultColorScale })}
</VictoryChart>
);
};
Expand Down
25 changes: 25 additions & 0 deletions packages/react-charts/src/components/ChartDonut/ChartDonut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ export interface ChartDonutProps extends ChartPieProps {
* @propType number | Function
*/
innerRadius?: NumberOrCallback;
/**
* Generate default pattern defs and populate patternScale
* @beta
*/
isPatternDefs?: boolean;
/**
* The labelComponent prop takes in an entire label component which will be used
* to create a label for the area. The new element created from the passed labelComponent
Expand Down Expand Up @@ -356,6 +361,26 @@ export interface ChartDonutProps extends ChartPieProps {
* @propType number | Function
*/
padAngle?: NumberOrCallback;
/**
* The optional ID to prefix pattern defs
*
* @example patternId="pattern"
* @beta
*/
patternId?: string;
/**
* The patternScale prop is an optional prop that defines a pattern to be applied to the children, where applicable.
* This prop should be given as an array of CSS colors, or as a string corresponding to a URL. Patterns will be
* assigned to children by index, unless they are explicitly specified in styles. Patterns will repeat when there are
* more children than patterns in the provided patternScale. Functionality may be overridden via the `style.data.fill`
* property.
*
* Note: Not all components are supported; for example, ChartLine, ChartBullet, ChartThreshold, etc.
*
* @example patternScale={['url("#pattern:0")', 'url("#pattern:1")', 'url("#pattern:2")']}
* @beta
*/
patternScale?: string[];
/**
* The padding props specifies the amount of padding in number of pixels between
* the edge of the chart and any rendered child components. This prop can be given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ import hoistNonReactStatics from 'hoist-non-react-statics';
import { ChartContainer } from '../ChartContainer';
import { ChartDonut, ChartDonutProps } from '../ChartDonut';
import { ChartDonutStyles, ChartThemeDefinition } from '../ChartTheme';
import { getDonutThresholdDynamicTheme, getDonutThresholdStaticTheme, getPaddingForSide } from '../ChartUtils';
import {
getDefaultColorScale,
getDefaultPatternScale,
getDonutThresholdDynamicTheme,
getDonutThresholdStaticTheme,
getPaddingForSide,
getPatternDefs,
getPatternId
} from '../ChartUtils';

export enum ChartDonutThresholdDonutOrientation {
left = 'left',
Expand Down Expand Up @@ -262,6 +270,11 @@ export interface ChartDonutThresholdProps extends ChartDonutProps {
* Invert the threshold color scale used to represent warnings, errors, etc.
*/
invert?: boolean;
/**
* Generate default pattern defs and populate patternScale
* @beta
*/
isPatternDefs?: boolean;
/**
* 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 @@ -307,6 +320,26 @@ export interface ChartDonutThresholdProps extends ChartDonutProps {
* @propType number | { top: number, bottom: number, left: number, right: number }
*/
padding?: PaddingProps;
/**
* The optional ID to prefix pattern defs
*
* @example patternId="pattern"
* @beta
*/
patternId?: string;
/**
* The patternScale prop is an optional prop that defines a pattern to be applied to the children, where applicable.
* This prop should be given as an array of CSS colors, or as a string corresponding to a URL. Patterns will be
* assigned to children by index, unless they are explicitly specified in styles. Patterns will repeat when there are
* more children than patterns in the provided patternScale. Functionality may be overridden via the `style.data.fill`
* property.
*
* Note: Not all components are supported; for example, ChartLine, ChartBullet, ChartThreshold, etc.
*
* @example patternScale={['url("#pattern:0")', 'url("#pattern:1")', 'url("#pattern:2")']}
* @beta
*/
patternScale?: string[];
/**
* Specifies the radius of the chart. If this property is not provided it is computed
* from width, height, and padding props
Expand Down Expand Up @@ -440,18 +473,22 @@ export const ChartDonutThreshold: React.FunctionComponent<ChartDonutThresholdPro
ariaDesc,
ariaTitle,
children,
colorScale,
constrainToVisibleArea = false,
containerComponent = <ChartContainer />,
data = [],
invert = false,
labels = [], // Don't show any tooltip labels by default, let consumer override if needed
padding,
patternId = getPatternId(),
patternScale,
radius,
standalone = true,
subTitlePosition = ChartDonutStyles.label.subTitlePosition as ChartDonutThresholdSubTitlePosition,
themeColor,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
themeVariant,
isPatternDefs = false,
x,
y,

Expand All @@ -475,6 +512,14 @@ export const ChartDonutThreshold: React.FunctionComponent<ChartDonutThresholdPro
padding: defaultPadding
});

const defaultColorScale = getDefaultColorScale(colorScale, theme.pie.colorScale as string[]);
const defaultPatternScale = getDefaultPatternScale({
colorScale: defaultColorScale,
patternScale,
patternId,
isPatternDefs
});

// Returns computed data representing pie chart slices
const getComputedData = () => {
// Format and sort data. Sorting ensures thresholds are displayed in the correct order and simplifies calculations.
Expand Down Expand Up @@ -513,17 +558,20 @@ export const ChartDonutThreshold: React.FunctionComponent<ChartDonutThresholdPro

return React.cloneElement(child, {
constrainToVisibleArea,
colorScale,
data: childData,
endAngle: 360 * (datum[0]._y ? datum[0]._y / 100 : 0),
height,
invert,
key: `pf-chart-donut-threshold-child-${index}`,
padding: defaultPadding,
...(!childProps.isPatternDefs && defaultPatternScale && { patternScale: [null, ...defaultPatternScale] }),
radius: chartRadius - 14, // Donut utilization radius is threshold radius minus 14px spacing
showStatic: false,
standalone: false,
subTitlePosition: childProps.subTitlePosition || subTitlePosition,
theme: dynamicTheme,
hasPatternDefs: isPatternDefs,
width,
...childProps
});
Expand All @@ -535,12 +583,15 @@ export const ChartDonutThreshold: React.FunctionComponent<ChartDonutThresholdPro
const chart = (
<ChartDonut
allowTooltip={allowTooltip}
colorScale={colorScale}
constrainToVisibleArea={constrainToVisibleArea}
data={getComputedData()}
height={height}
key="pf-chart-donut-threshold"
labels={labels}
padding={defaultPadding}
patternId={patternId}
patternScale={defaultPatternScale ? defaultPatternScale : undefined}
standalone={false}
theme={theme}
width={width}
Expand All @@ -559,7 +610,11 @@ export const ChartDonutThreshold: React.FunctionComponent<ChartDonutThresholdPro
theme,
...containerComponent.props
},
[chart, renderChildren()]
[
chart,
renderChildren(),
isPatternDefs && getPatternDefs({ offset: 1, patternId, patternScale: defaultColorScale })
]
);

return standalone ? (
Expand All @@ -568,6 +623,7 @@ export const ChartDonutThreshold: React.FunctionComponent<ChartDonutThresholdPro
<React.Fragment>
{chart}
{renderChildren()}
{isPatternDefs && getPatternDefs({ offset: 1, patternId, patternScale: defaultColorScale })}
</React.Fragment>
);
};
Expand Down
Loading