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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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 @@
{
Copy link
Collaborator

@fabricteam fabricteam Jan 24, 2025

Choose a reason for hiding this comment

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

🕵🏾‍♀️ visual regressions to review in the fluentuiv8 Visual Regression Report

Keytip 1 screenshots
Image Name Diff(in Pixels) Image Type
Keytip.Offset.chromium.png 121 Changed
react-charting-AreaChart 1 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-AreaChart.Custom Accessibility.chromium.png 11 Changed
react-charting-LineChart 1 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-LineChart.Gaps.chromium.png 1 Changed

"type": "patch",
"comment": "feat: make charts responsive",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
18 changes: 18 additions & 0 deletions packages/charts/react-charting/etc/react-charting.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,21 @@ export interface IRefArrayData {
refElement?: SVGGElement;
}

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

// @public
export interface ISankeyChartAccessibilityProps {
emptyAriaLabel?: string;
Expand Down Expand Up @@ -1637,6 +1652,9 @@ export enum NodesComposition {
// @public
export const PieChart: React_2.FunctionComponent<IPieChartProps>;

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

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

Expand Down
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
@@ -0,0 +1,22 @@
import { IResponsiveContainerStyles } from './ResponsiveContainer.types';

export const getStyles = (): IResponsiveContainerStyles => {
return {
root: {
width: '100%',
height: '100%',

'& [class^="chartWrapper-"]': {
width: '100%', // optional
// To prevent chart height from collapsing while resizing
height: '100%', // optional

'> svg': {
// This overrides the pixel width and height of svg allowing it to resize properly within flexbox
width: '100%',
height: '100%',
},
},
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as React from 'react';
import { classNamesFunction, getWindow } from '@fluentui/react';
import { IResponsiveContainerProps, IResponsiveContainerStyles } from './ResponsiveContainer.types';
import { getStyles } from './ResponsiveContainer.styles';

const getClassNames = classNamesFunction<{}, IResponsiveContainerStyles>();

export const ResponsiveContainer: React.FC<IResponsiveContainerProps> = props => {
const containerRef = React.useRef<HTMLDivElement>(null);
const onResizeRef = React.useRef<IResponsiveContainerProps['onResize']>();
const classNames = React.useMemo(() => getClassNames(getStyles()), []);

const [size, setSize] = React.useState<{ containerWidth?: number; containerHeight?: number }>({});

onResizeRef.current = props.onResize;

React.useEffect(() => {
const _window = getWindow(containerRef.current) as (Window & typeof globalThis) | undefined;
let animationFrameId: number | undefined;
let resizeObserver: ResizeObserver | undefined;

const resizeCallback = (entries: ResizeObserverEntry[]) => {
const { width: containerWidth, height: containerHeight } = entries[0].contentRect;
// rAF is an alternative to the throttle function. For more info, see:
// https://css-tricks.com/debouncing-throttling-explained-examples/#aa-requestanimationframe-raf
animationFrameId = _window?.requestAnimationFrame(() => {
setSize(prevSize => {
const roundedWidth = Math.floor(containerWidth);
const roundedHeight = Math.floor(containerHeight);
if (prevSize.containerWidth === roundedWidth && prevSize.containerHeight === roundedHeight) {
return prevSize;
}

return { containerWidth: roundedWidth, containerHeight: roundedHeight };
});
});
onResizeRef.current?.(containerWidth, containerHeight);
};

if (_window && _window.ResizeObserver) {
resizeObserver = new _window.ResizeObserver(resizeCallback);
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
}

return () => {
if (animationFrameId) {
_window?.cancelAnimationFrame(animationFrameId);
}

resizeObserver?.disconnect();
};
}, []);

return (
<div ref={containerRef} className={classNames.root} style={{ width: props.width, height: props.height }}>
{React.cloneElement(props.children, {
width: size.containerWidth,
height: size.containerHeight,
})}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';
import { IStyle } from '@fluentui/react/lib/Styling';

export interface IResponsiveContainerProps {
/**
*
*/
width?: number | string;

/**
*
*/
height?: number | string;

/**
*
*/
onResize?: (width: number, height: number) => void;

/**
*
*/
children: React.ReactElement<{ width?: number; height?: number }>;
}

export interface IResponsiveContainerStyles {
/**
*
*/
root: IStyle;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ResponsiveContainer';
export * from './ResponsiveContainer.types';
2 changes: 2 additions & 0 deletions packages/charts/react-charting/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,7 @@ export type { IGaugeChartProps, IGaugeChartSegment, IGaugeChartStyleProps, IGaug
export { GaugeChart, GaugeChartVariant, GaugeValueFormat } from './GaugeChart';
export type { DeclarativeChartProps, Schema, IDeclarativeChart, IImageExportOptions } from './DeclarativeChart';
export { DeclarativeChart } from './DeclarativeChart';
export type { IResponsiveContainerProps } from './ResponsiveContainer';
export { ResponsiveContainer } from './ResponsiveContainer';

import './version';
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import * as React from 'react';
import {
VerticalBarChart,
IVerticalBarChartDataPoint,
DataVizPalette,
getColorFromToken,
ILineChartLineOptions,
ResponsiveContainer,
} from '@fluentui/react-charting';
import { classNamesFunction, DefaultPalette, IStyle } from '@fluentui/react';

interface IVBCResponsiveExampleStyles {
resizableArea: IStyle;
}

const getStyles = (): IVBCResponsiveExampleStyles => {
return {
resizableArea: {
display: 'flex',
flexWrap: 'nowrap',
overflow: 'hidden',

minWidth: '200px',
maxWidth: '800px',
border: `2px solid ${DefaultPalette.blue}`,
padding: '20px 10px 10px 10px',
position: 'relative',
resize: 'horizontal',
'::after': {
content: `'Resizable Area'`,
position: 'absolute',
padding: '1px 4px 1px',
top: '-2px',
left: '-2px',
fontFamily: 'monospace',
fontSize: '15px',
fontWeight: 900,
letterSpacing: '1px',
color: DefaultPalette.white,
backgroundColor: DefaultPalette.blue,
},
},
};
};
const points: IVerticalBarChartDataPoint[] = [
{
x: 0,
y: 10000,
legend: 'Oranges',
color: DefaultPalette.accent,
lineData: {
y: 7000,
},
},
{
x: 10000,
y: 50000,
legend: 'Dogs',
color: DefaultPalette.blueDark,
lineData: {
y: 30000,
},
},
{
x: 25000,
y: 30000,
legend: 'Apples',
color: DefaultPalette.blueMid,
lineData: {
y: 3000,
},
},
{
x: 40000,
y: 13000,
legend: 'Bananas',
color: getColorFromToken(DataVizPalette.color6),
},
{
x: 52000,
y: 43000,
legend: 'Giraffes',
color: getColorFromToken(DataVizPalette.color11),
lineData: {
y: 30000,
},
},
{
x: 68000,
y: 30000,
legend: 'Cats',
color: DefaultPalette.blueDark,
lineData: {
y: 5000,
},
},
{
x: 80000,
y: 20000,
legend: 'Elephants',
color: getColorFromToken(DataVizPalette.color11),
lineData: {
y: 16000,
},
},
{
x: 92000,
y: 45000,
legend: 'Monkeys',
color: getColorFromToken(DataVizPalette.color6),
lineData: {
y: 40000,
},
},
];
const lineOptions: ILineChartLineOptions = { lineBorderWidth: '2' };

const getClassNames = classNamesFunction<{}, IVBCResponsiveExampleStyles>();

export class VerticalBarChartResponsiveExample extends React.Component {
private _classNames = getClassNames(getStyles());

public render(): JSX.Element {
return (
<div className={this._classNames.resizableArea} style={{ height: '350px' }}>
<ResponsiveContainer>
<VerticalBarChart data={points} lineLegendText={'Line'} lineLegendColor={'brown'} lineOptions={lineOptions} />
</ResponsiveContainer>
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { VerticalBarChartRotatedLabelExample } from './VerticalBarChart.RotateLa
import { VerticalBarChartDateAxisExample } from './VerticalBarChart.DateAxis.Example';
import { VerticalBarChartNegativeExample } from './VerticalBarChart.Negative.Example';
import { VerticalBarChartAllNegativeExample } from './VerticalBarChart.AllNegative.Example';
import { VerticalBarChartResponsiveExample } from './VerticalBarChart.Responsive.Example';

const VerticalBarChartBasicExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/VerticalBarChart.Basic.Example.tsx') as string;
Expand All @@ -30,6 +31,8 @@ const VerticalBarChartNegativeExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/VerticalBarChart.Negative.Example.tsx') as string;
const VerticalBarChartAllNegativeExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/VerticalBarChart.AllNegative.Example.tsx') as string;
const VerticalBarChartResponsiveExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/VerticalBarChart.Responsive.Example.tsx') as string;

export const VerticalBarChartPageProps: IDocPageProps = {
title: 'VerticalBarChart',
Expand Down Expand Up @@ -82,6 +85,11 @@ export const VerticalBarChartPageProps: IDocPageProps = {
code: VerticalBarChartAllNegativeExampleCode,
view: <VerticalBarChartAllNegativeExample />,
},
{
title: 'VerticalBarChart responsive',
code: VerticalBarChartResponsiveExampleCode,
view: <VerticalBarChartResponsiveExample />,
},
],
overview: require<string>('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/docs/VerticalBarChartOverview.md'),
bestPractices: require<string>('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/docs/VerticalBarChartBestPractices.md'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { VerticalBarChartRotatedLabelExample } from './VerticalBarChart.RotateLa
import { VerticalBarChartDateAxisExample } from './VerticalBarChart.DateAxis.Example';
import { VerticalBarChartNegativeExample } from './VerticalBarChart.Negative.Example';
import { VerticalBarChartAllNegativeExample } from './VerticalBarChart.AllNegative.Example';
import { VerticalBarChartResponsiveExample } from './VerticalBarChart.Responsive.Example';

const VerticalBarChartBasicExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/VerticalBarChart.Basic.Example.tsx') as string;
Expand All @@ -36,6 +37,8 @@ const VerticalBarChartNegativeExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/VerticalBarChart.Negative.Example.tsx') as string;
const VerticalBarChartAllNegativeExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/VerticalBarChart.AllNegative.Example.tsx') as string;
const VerticalBarChartResponsiveExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react-charting/VerticalBarChart/VerticalBarChart.Responsive.Example.tsx') as string;

export class VerticalBarChartPage extends React.Component<IComponentDemoPageProps, {}> {
public render(): JSX.Element {
Expand Down Expand Up @@ -75,6 +78,9 @@ export class VerticalBarChartPage extends React.Component<IComponentDemoPageProp
<ExampleCard title="VerticalBarChart All Negative Y Values" code={VerticalBarChartAllNegativeExampleCode}>
<VerticalBarChartAllNegativeExample />
</ExampleCard>
<ExampleCard title="VerticalBarChart responsive" code={VerticalBarChartResponsiveExampleCode}>
<VerticalBarChartResponsiveExample />
</ExampleCard>
</div>
}
propertiesTables={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { VerticalBarChartRotatedLabelExample } from './VerticalBarChart.RotateLa
import { VerticalBarChartStyledExample } from './VerticalBarChart.Styled.Example';
import { VerticalBarChartNegativeExample } from './VerticalBarChart.Negative.Example';
import { VerticalBarChartAllNegativeExample } from './VerticalBarChart.AllNegative.Example';
import { VerticalBarChartResponsiveExample } from './VerticalBarChart.Responsive.Example';

export const Basic = () => <VerticalBarChartBasicExample />;

Expand All @@ -28,6 +29,8 @@ export const Negative = () => <VerticalBarChartNegativeExample />;

export const AllNegative = () => <VerticalBarChartAllNegativeExample />;

export const Responsive = () => <VerticalBarChartResponsiveExample />;

export default {
title: 'Components/VerticalBarChart',
};
Loading