Skip to content

Commit

Permalink
feat(charts): Added support for patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
dlabrecq committed Jun 10, 2022
1 parent fd4459f commit 4a4e8e3
Show file tree
Hide file tree
Showing 19 changed files with 1,597 additions and 43 deletions.
89 changes: 86 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 @@ -291,6 +302,24 @@ 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"
*/
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")']}
*/
patternScale?: string[];
/**
* Note: This prop should not be set manually.
*
Expand Down Expand Up @@ -407,6 +436,10 @@ export interface ChartProps extends VictoryChartProps {
* @deprecated Use PatternFly's pf-theme-dark CSS selector
*/
themeVariant?: string;
/**
* Generate default pattern defs and populate patternScale
*/
usePatternDefs?: boolean;
/**
* Specifies the width of the svg viewBox of the chart container. This value should be given as a
* number of pixels.
Expand All @@ -423,6 +456,7 @@ export const Chart: React.FunctionComponent<ChartProps> = ({
ariaDesc,
ariaTitle,
children,
colorScale,
legendAllowWrap = false,
legendComponent = <ChartLegend />,
legendData,
Expand All @@ -432,6 +466,9 @@ export const Chart: React.FunctionComponent<ChartProps> = ({
themeColor,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
themeVariant,
patternId = getPatternId(),
patternScale,
usePatternDefs = false,

// destructure last
theme = getChartTheme(themeColor, showAxis),
Expand All @@ -448,13 +485,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,
usePatternDefs
});

// 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 +557,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()}
{usePatternDefs && getPatternDefs({ patternId, patternScale: defaultColorScale })}
</VictoryChart>
);
};
Expand Down
22 changes: 22 additions & 0 deletions packages/react-charts/src/components/ChartDonut/ChartDonut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,24 @@ export interface ChartDonutProps extends ChartPieProps {
* @propType number | Function
*/
padAngle?: NumberOrCallback;
/**
* The optional ID to prefix pattern defs
*
* @example patternId="pattern"
*/
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")']}
*/
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 Expand Up @@ -504,6 +522,10 @@ export interface ChartDonutProps extends ChartPieProps {
* Note: Default label properties may be applied
*/
titleComponent?: React.ReactElement<any>;
/**
* Generate default pattern defs and populate patternScale
*/
usePatternDefs?: boolean;
/**
* Specifies the width of the svg viewBox of the chart container. This value should be given as a number of pixels.
*
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 @@ -307,6 +315,24 @@ 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"
*/
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")']}
*/
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 @@ -398,6 +424,10 @@ export interface ChartDonutThresholdProps extends ChartDonutProps {
* The title for the donut chart
*/
title?: string;
/**
* Generate default pattern defs and populate patternScale
*/
usePatternDefs?: boolean;
/**
* Specifies the width of the svg viewBox of the chart container. This value should be given as a number of pixels.
*
Expand Down Expand Up @@ -440,18 +470,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,
usePatternDefs = false,
x,
y,

Expand All @@ -475,6 +509,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,
usePatternDefs
});

// 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 +555,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.usePatternDefs && 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,
_usePatternDefs: usePatternDefs,
width,
...childProps
});
Expand All @@ -535,12 +580,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 +607,11 @@ export const ChartDonutThreshold: React.FunctionComponent<ChartDonutThresholdPro
theme,
...containerComponent.props
},
[chart, renderChildren()]
[
chart,
renderChildren(),
usePatternDefs && getPatternDefs({ offset: 1, patternId, patternScale: defaultColorScale })
]
);

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

0 comments on commit 4a4e8e3

Please sign in to comment.