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(react-charting): make charts responsive #33723

Merged
merged 17 commits into from
Feb 24, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "feat: make charts responsive",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ exports[`vertical bar chart with numeric x-axis data Should render the vertical
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone5"
data-tabster="{\\"uncontrolled\\": {}}"
>
Expand Down
35 changes: 35 additions & 0 deletions packages/charts/react-charting/etc/react-charting.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export interface ICartesianChartProps {
export interface ICartesianChartStyleProps {
className?: string;
color?: string;
enableReflow?: boolean;
height?: number;
href?: string;
isRtl?: boolean;
Expand Down Expand Up @@ -1216,6 +1217,32 @@ export interface IRefArrayData {
refElement?: SVGGElement;
}

// @public
export interface IResponsiveChildProps {
// (undocumented)
height?: number;
// (undocumented)
shouldResize?: number;
// (undocumented)
styles?: IStyleFunctionOrObject_2<{}, {
root: IStyle_2;
}>;
// (undocumented)
width?: number;
}

// @public
export interface IResponsiveContainerProps {
aspect?: number;
children: React_2.ReactElement<IResponsiveChildProps>;
height?: number | string;
maxHeight?: number;
minHeight?: number | string;
minWidth?: number | string;
onResize?: (width: number, height: number) => void;
width?: number | string;
}

// @public
export interface ISankeyChartAccessibilityProps {
emptyAriaLabel?: string;
Expand Down Expand Up @@ -1262,6 +1289,8 @@ export interface ISankeyChartStyleProps {
// (undocumented)
className?: string;
// (undocumented)
enableReflow?: boolean;
// (undocumented)
height: number;
// (undocumented)
pathColor?: string;
Expand Down Expand Up @@ -1641,6 +1670,9 @@ export enum NodesComposition {
// @public
export const PieChart: React_2.FunctionComponent<IPieChartProps>;

// @public
export const ResponsiveContainer: React_2.FC<IResponsiveContainerProps>;

// @public
export const SankeyChart: React_2.FunctionComponent<ISankeyChartProps>;

Expand Down Expand Up @@ -1687,6 +1719,9 @@ export const VerticalBarChart: React_2.FunctionComponent<IVerticalBarChartProps>
// @public
export const VerticalStackedBarChart: React_2.FunctionComponent<IVerticalStackedBarChartProps>;

// @public
export function withResponsiveContainer<TProps extends Omit<IResponsiveContainerProps, 'children'>>(WrappedComponent: React_2.ComponentType<TProps>): React_2.FC<TProps>;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/ResponsiveContainer/index';
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ exports[`AreaChart - mouse events Should render callout correctly on mouseover 1
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
onFocus={[Function]}
Expand Down Expand Up @@ -599,9 +597,7 @@ exports[`AreaChart - mouse events Should render customized callout on mouseover
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
onFocus={[Function]}
Expand Down Expand Up @@ -1067,9 +1063,7 @@ exports[`AreaChart - mouse events Should render customized callout per stack on
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
onFocus={[Function]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ exports[`Area chart rendering Should render the Area Chart with negative y value
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
>
Expand Down Expand Up @@ -1170,9 +1168,7 @@ exports[`Area chart rendering Should render the Area Chart with tozeroy mode 1`]
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
>
Expand Down Expand Up @@ -6169,9 +6165,7 @@ exports[`Area chart rendering Should render the Area chart with secondary Y axis
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
>
Expand Down Expand Up @@ -9782,9 +9776,7 @@ exports[`Area chart rendering Should render the area chart with numeric x-axis d
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
>
Expand Down Expand Up @@ -10937,9 +10929,7 @@ exports[`AreaChart - Theme Should reflect theme change 1`] = `
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone11"
data-tabster="{\\"uncontrolled\\": {}}"
>
Expand Down Expand Up @@ -12080,9 +12070,7 @@ exports[`Screen resolution Should remain unchanged on zoom in 1`] = `
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
>
Expand Down Expand Up @@ -13222,9 +13210,7 @@ exports[`Screen resolution Should remain unchanged on zoom out 1`] = `
&:focus {
outline: none;
}
{
overflow: auto;
}

data-focuszone-id="FocusZone8"
data-tabster="{\\"uncontrolled\\": {}}"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ export class CartesianChartBase
height: this.state._height,
className: this.props.className,
isRtl: this._isRtl,
enableReflow: this.props.enableReflow,
});

const svgDimensions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ import { NeutralColors, isIE11 } from '@fluentui/react';
const isIE11Var: boolean = isIE11();

export const getStyles = (props: ICartesianChartStyleProps): ICartesianChartStyles => {
const { className, theme, isRtl, shouldHighlight, href, lineColor = 'transparent', toDrawShape } = props;
const {
className,
theme,
isRtl,
shouldHighlight,
href,
lineColor = 'transparent',
toDrawShape,
enableReflow,
} = props;
const { fonts } = theme!;
return {
root: [
Expand All @@ -20,7 +29,7 @@ export const getStyles = (props: ICartesianChartStyleProps): ICartesianChartStyl
className,
],
chartWrapper: {
overflow: 'auto',
...(enableReflow ? { overflow: 'auto' } : {}),
},
axisTitle: [
theme.fonts.xSmall,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ export interface ICartesianChartStyleProps {
* boolean flag which determines if shape is drawn in callout
*/
toDrawShape?: boolean;

/**
* Prop to disable shrinking of the chart beyond a certain limit and enable scrolling when the chart overflows
*/
enableReflow?: boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ import { GroupedVerticalBarChart } from '../GroupedVerticalBarChart/index';
import { VerticalBarChart } from '../VerticalBarChart/index';
import { IImageExportOptions, toImage } from './imageExporter';
import { IChart } from '../../types/index';
import { withResponsiveContainer } from '../ResponsiveContainer/withResponsiveContainer';

const ResponsiveDonutChart = withResponsiveContainer(DonutChart);
const ResponsiveVerticalStackedBarChart = withResponsiveContainer(VerticalStackedBarChart);
const ResponsiveLineChart = withResponsiveContainer(LineChart);
const ResponsiveHorizontalBarChartWithAxis = withResponsiveContainer(HorizontalBarChartWithAxis);
const ResponsiveAreaChart = withResponsiveContainer(AreaChart);
const ResponsiveHeatMapChart = withResponsiveContainer(HeatMapChart);
const ResponsiveSankeyChart = withResponsiveContainer(SankeyChart);
const ResponsiveGaugeChart = withResponsiveContainer(GaugeChart);
const ResponsiveGroupedVerticalBarChart = withResponsiveContainer(GroupedVerticalBarChart);
const ResponsiveVerticalBarChart = withResponsiveContainer(VerticalBarChart);

/**
* DeclarativeChart schema.
Expand Down Expand Up @@ -167,7 +179,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
// Unsupported schema, render as VerticalStackedBarChart
fallbackVSBC = true;
return (
<VerticalStackedBarChart
<ResponsiveVerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme, fallbackVSBC)}
{...commonProps}
/>
Expand Down Expand Up @@ -195,12 +207,17 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =

switch (plotlyInput.data[0].type) {
case 'pie':
return <DonutChart {...transformPlotlyJsonToDonutProps(plotlySchema, colorMap, isDarkTheme)} {...commonProps} />;
return (
<ResponsiveDonutChart
{...transformPlotlyJsonToDonutProps(plotlySchema, colorMap, isDarkTheme)}
{...commonProps}
/>
);
case 'bar':
const orientation = plotlyInput.data[0].orientation;
if (orientation === 'h' && isNumberArray((plotlyInput.data[0] as PlotData).x)) {
return (
<HorizontalBarChartWithAxis
<ResponsiveHorizontalBarChartWithAxis
{...transformPlotlyJsonToHorizontalBarWithAxisProps(plotlySchema, colorMap, isDarkTheme)}
{...commonProps}
/>
Expand All @@ -211,14 +228,14 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
);
if (['group', 'overlay'].includes(plotlySchema?.layout?.barmode) && !containsLines) {
return (
<GroupedVerticalBarChart
<ResponsiveGroupedVerticalBarChart
{...transformPlotlyJsonToGVBCProps(plotlySchema, colorMap, isDarkTheme)}
{...commonProps}
/>
);
}
return (
<VerticalStackedBarChart
<ResponsiveVerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)}
{...commonProps}
/>
Expand All @@ -233,35 +250,50 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
);
const renderChartJsx = (chartProps: ILineChartProps | IAreaChartProps) => {
if (isAreaChart) {
return <AreaChart {...chartProps} />;
return <ResponsiveAreaChart {...chartProps} />;
}
return <LineChart {...chartProps} />;
return <ResponsiveLineChart {...chartProps} />;
};
return checkAndRenderChart(renderChartJsx, isAreaChart);
case 'heatmap':
return <HeatMapChart {...transformPlotlyJsonToHeatmapProps(plotlySchema)} {...commonProps} legendProps={{}} />;
return (
<ResponsiveHeatMapChart
{...transformPlotlyJsonToHeatmapProps(plotlySchema)}
{...commonProps}
legendProps={{}}
/>
);
case 'sankey':
return (
<SankeyChart {...transformPlotlyJsonToSankeyProps(plotlySchema, colorMap, isDarkTheme)} {...commonProps} />
<ResponsiveSankeyChart
{...transformPlotlyJsonToSankeyProps(plotlySchema, colorMap, isDarkTheme)}
{...commonProps}
/>
);
case 'indicator':
case 'gauge':
if (plotlyInput.data?.[0]?.mode?.includes('gauge') || plotlyInput.data?.[0]?.type === 'gauge') {
return (
<GaugeChart {...transformPlotlyJsonToGaugeProps(plotlySchema, colorMap, isDarkTheme)} {...commonProps} />
<ResponsiveGaugeChart
{...transformPlotlyJsonToGaugeProps(plotlySchema, colorMap, isDarkTheme)}
{...commonProps}
/>
);
}
throw new Error(`Unsupported chart - type: ${plotlyInput.data[0]?.type}, mode: ${plotlyInput.data[0]?.mode}`);
case 'histogram':
return (
<VerticalBarChart {...transformPlotlyJsonToVBCProps(plotlySchema, colorMap, isDarkTheme)} {...commonProps} />
<ResponsiveVerticalBarChart
{...transformPlotlyJsonToVBCProps(plotlySchema, colorMap, isDarkTheme)}
{...commonProps}
/>
);
default:
const xValues = (plotlyInput.data[0] as PlotData).x;
const yValues = (plotlyInput.data[0] as PlotData).y;
if (xValues && yValues && xValues.length > 0 && yValues.length > 0) {
const renderLineChartJsx = (chartProps: ILineChartProps) => {
return <LineChart {...chartProps} />;
return <ResponsiveLineChart {...chartProps} />;
};
return checkAndRenderChart(renderLineChartJsx);
}
Expand Down
Loading
Loading