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): use legend data and config from Legends component for image export #33847

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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 @@
{
Copy link
Collaborator

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.default.chromium.png 121 Changed
react-charting-HeatMapChart 1 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-HeatMapChart.Basic - RTL.default.chromium.png 372 Changed
react-charting-VerticalBarChart 1 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-VerticalBarChart.Basic - Secondary Y Axis.default.chromium.png 4 Changed

"type": "patch",
"comment": "feat: export legend data and config from Legends component",
"packageName": "@fluentui/react-charting",
"email": "110246001+krkshitij@users.noreply.github.com",
"dependentChangeType": "patch"
}
4 changes: 4 additions & 0 deletions packages/charts/react-charting/etc/react-charting.api.md
Original file line number Diff line number Diff line change
@@ -361,6 +361,8 @@ export interface ICartesianChartStyles {
export interface IChart {
// (undocumented)
chartContainer: HTMLElement | null;
// (undocumented)
toImage?: (opts?: IImageExportOptions) => Promise<string>;
}

// @public (undocumented)
@@ -910,6 +912,8 @@ export interface ILegendsProps {
onLegendHoverCardLeave?: VoidFunction;
overflowProps?: Partial<IOverflowSetProps>;
overflowText?: string;
// Warning: (ae-forgotten-export) The symbol "ILegendContainer" needs to be exported by the entry point index.d.ts
ref?: IRefObject<ILegendContainer>;
selectedLegend?: string;
selectedLegends?: string[];
shape?: LegendShape;
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import {
classNamesFunction,
find,
getId,
getRTL,
initializeComponentRef,
memoizeFunction,
} from '@fluentui/react/lib/Utilities';
@@ -43,9 +44,10 @@ import {
getSecureProps,
areArraysEqual,
} from '../../utilities/index';
import { ILegend, Legends } from '../Legends/index';
import { ILegend, ILegendContainer, Legends } from '../Legends/index';
import { DirectionalHint } from '@fluentui/react/lib/Callout';
import { IChart } from '../../types/index';
import { IChart, IImageExportOptions } from '../../types/index';
import { toImage } from '../../utilities/image-export-utils';

const getClassNames = classNamesFunction<IAreaChartStyleProps, IAreaChartStyles>();

@@ -129,6 +131,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
private _firstRenderOptimization: boolean;
private _emptyChartId: string;
private _cartesianChartRef: React.RefObject<IChart>;
private _legendsRef: React.RefObject<ILegendContainer>;

public constructor(props: IAreaChartProps) {
super(props);
@@ -162,6 +165,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
this._firstRenderOptimization = true;
this._emptyChartId = getId('_AreaChart_empty');
this._cartesianChartRef = React.createRef();
this._legendsRef = React.createRef();
}

public componentDidUpdate(prevProps: IAreaChartProps): void {
@@ -274,6 +278,10 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
return this._cartesianChartRef.current?.chartContainer || null;
}

public toImage = (opts?: IImageExportOptions): Promise<string> => {
return toImage(this._cartesianChartRef.current?.chartContainer, this._legendsRef.current?.toSVG, getRTL(), opts);
};

private _getDomainNRangeValues = (
points: ILineChartPoints[],
margins: IMargins,
@@ -659,6 +667,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
focusZonePropsInHoverCard={this.props.focusZonePropsForLegendsInHoverCard}
{...this.props.legendProps}
onChange={this._onLegendSelectionChange.bind(this)}
ref={this._legendsRef}
/>
);
};
Original file line number Diff line number Diff line change
@@ -31,8 +31,7 @@ import { SankeyChart } from '../SankeyChart/SankeyChart';
import { GaugeChart } from '../GaugeChart/index';
import { GroupedVerticalBarChart } from '../GroupedVerticalBarChart/index';
import { VerticalBarChart } from '../VerticalBarChart/index';
import { IImageExportOptions, toImage } from './imageExporter';
import { IChart } from '../../types/index';
import { IChart, IImageExportOptions } from '../../types/index';

/**
* DeclarativeChart schema.
@@ -175,11 +174,20 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
};

const exportAsImage = React.useCallback(
(opts?: IImageExportOptions) => {
return toImage(chartRef.current?.chartContainer, {
background: theme.semanticColors.bodyBackground,
scale: 5,
...opts,
(opts?: IImageExportOptions): Promise<string> => {
return new Promise((resolve, reject) => {
if (!chartRef.current || typeof chartRef.current.toImage !== 'function') {
return reject(Error('Chart cannot be exported as image'));
}

chartRef.current
.toImage({
background: theme.semanticColors.bodyBackground,
scale: 5,
...opts,
})
.then(resolve)
.catch(reject);
});
},
[theme],
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './DeclarativeChart';
export type { IImageExportOptions } from './imageExporter';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { classNamesFunction, getId, initializeComponentRef } from '@fluentui/react/lib/Utilities';
import { classNamesFunction, getId, getRTL, initializeComponentRef } from '@fluentui/react/lib/Utilities';
import { ScaleOrdinal } from 'd3-scale';
import { IProcessedStyleSet } from '@fluentui/react/lib/Styling';
import { Callout, DirectionalHint } from '@fluentui/react/lib/Callout';
@@ -15,7 +15,9 @@ import {
areArraysEqual,
} from '../../utilities/index';
import { convertToLocaleString } from '../../utilities/locale-util';
import { IChart } from '../../types/index';
import { IChart, IImageExportOptions } from '../../types/index';
import { toImage } from '../../utilities/image-export-utils';
import { ILegendContainer } from '../Legends/index';

const getClassNames = classNamesFunction<IDonutChartStyleProps, IDonutChartStyles>();
const LEGEND_CONTAINER_HEIGHT = 40;
@@ -50,6 +52,7 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
private _calloutId: string;
private _calloutAnchorPoint: IChartDataPoint | null;
private _emptyChartId: string | null;
private _legendsRef: React.RefObject<ILegendContainer>;

public static getDerivedStateFromProps(
nextProps: Readonly<IDonutChartProps>,
@@ -92,6 +95,7 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
this._calloutId = getId('callout');
this._uniqText = getId('_Pie_');
this._emptyChartId = getId('_DonutChart_empty');
this._legendsRef = React.createRef();
}

public componentDidMount(): void {
@@ -212,6 +216,10 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
return this._rootElem;
}

public toImage = (opts?: IImageExportOptions): Promise<string> => {
return toImage(this._rootElem, this._legendsRef.current?.toSVG, getRTL(), opts);
};

private _closeCallout = () => {
this.setState({
showHover: false,
@@ -285,6 +293,7 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
{...this.props.legendProps}
// eslint-disable-next-line react/jsx-no-bind
onChange={this._onLegendSelectionChange.bind(this)}
ref={this._legendsRef}
/>
);
return legends;
Original file line number Diff line number Diff line change
@@ -21,13 +21,14 @@ import {
getNextGradient,
pointTypes,
} from '../../utilities/index';
import { ILegend, LegendShape, Legends, Shape } from '../Legends/index';
import { ILegend, ILegendContainer, LegendShape, Legends, Shape } from '../Legends/index';
import { FocusZone, FocusZoneDirection } from '@fluentui/react-focus';
import { Callout, DirectionalHint } from '@fluentui/react/lib/Callout';
import { IYValueHover } from '../../index';
import { SVGTooltipText } from '../../utilities/SVGTooltipText';
import { select as d3Select } from 'd3-selection';
import { IChart } from '../../types/index';
import { IChart, IImageExportOptions } from '../../types/index';
import { toImage } from '../../utilities/image-export-utils';

const GAUGE_MARGIN = 16;
const LABEL_WIDTH = 36;
@@ -134,6 +135,7 @@ export class GaugeChartBase extends React.Component<IGaugeChartProps, IGaugeChar
private _rootElem: HTMLDivElement | null;
private _margins: { left: number; right: number; top: number; bottom: number };
private _legendsHeight: number;
private _legendsRef: React.RefObject<ILegendContainer>;

constructor(props: IGaugeChartProps) {
super(props);
@@ -157,6 +159,7 @@ export class GaugeChartBase extends React.Component<IGaugeChartProps, IGaugeChar

this._isRTL = getRTL(props.theme);
this._calloutAnchor = '';
this._legendsRef = React.createRef();
}

public componentDidMount(): void {
@@ -360,6 +363,10 @@ export class GaugeChartBase extends React.Component<IGaugeChartProps, IGaugeChar
return this._rootElem;
}

public toImage = (opts?: IImageExportOptions): Promise<string> => {
return toImage(this._rootElem, this._legendsRef.current?.toSVG, this._isRTL, opts);
};

private _getMargins = () => {
const { hideMinMax, chartTitle, sublabel } = this.props;

@@ -514,6 +521,7 @@ export class GaugeChartBase extends React.Component<IGaugeChartProps, IGaugeChar
{...this.props.legendProps}
// eslint-disable-next-line react/jsx-no-bind
onChange={this._onLegendSelectionChange.bind(this)}
ref={this._legendsRef}
/>
</div>
);
Original file line number Diff line number Diff line change
@@ -48,7 +48,9 @@ import {
IRefArrayData,
Legends,
} from '../../index';
import { IChart } from '../../types/index';
import { IChart, IImageExportOptions } from '../../types/index';
import { toImage } from '../../utilities/image-export-utils';
import { ILegendContainer } from '../Legends/index';

const COMPONENT_NAME = 'GROUPED VERTICAL BAR CHART';
const getClassNames = classNamesFunction<IGroupedVerticalBarChartStyleProps, IGroupedVerticalBarChartStyles>();
@@ -112,6 +114,7 @@ export class GroupedVerticalBarChartBase
private _xAxisInnerPadding: number;
private _xAxisOuterPadding: number;
private _cartesianChartRef: React.RefObject<IChart>;
private _legendsRef: React.RefObject<ILegendContainer>;

public constructor(props: IGroupedVerticalBarChartProps) {
super(props);
@@ -145,6 +148,7 @@ export class GroupedVerticalBarChartBase
this._emptyChartId = getId('_GVBC_empty');
this._domainMargin = MIN_DOMAIN_MARGIN;
this._cartesianChartRef = React.createRef();
this._legendsRef = React.createRef();
}

public componentDidUpdate(prevProps: IGroupedVerticalBarChartProps): void {
@@ -247,6 +251,10 @@ export class GroupedVerticalBarChartBase
return this._cartesianChartRef.current?.chartContainer || null;
}

public toImage = (opts?: IImageExportOptions): Promise<string> => {
return toImage(this._cartesianChartRef.current?.chartContainer, this._legendsRef.current?.toSVG, this._isRtl, opts);
};

private _getMinMaxOfYAxis = () => {
return { startValue: 0, endValue: 0 };
};
@@ -635,6 +643,7 @@ export class GroupedVerticalBarChartBase
focusZonePropsInHoverCard={this.props.focusZonePropsForLegendsInHoverCard}
{...this.props.legendProps}
onChange={this._onLegendSelectionChange.bind(this)}
ref={this._legendsRef}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { CartesianChart, IChildProps, IModifiedCartesianChartProps } from '../../components/CommonComponents/index';
import { IAccessibilityProps, IChart, IHeatMapChartData, IHeatMapChartDataPoint } from '../../types/IDataPoint';
import {
IAccessibilityProps,
IChart,
IHeatMapChartData,
IHeatMapChartDataPoint,
IImageExportOptions,
} from '../../types/IDataPoint';
import { scaleLinear as d3ScaleLinear } from 'd3-scale';
import { classNamesFunction, getId, initializeComponentRef, memoizeFunction } from '@fluentui/react/lib/Utilities';
import {
classNamesFunction,
getId,
getRTL,
initializeComponentRef,
memoizeFunction,
} from '@fluentui/react/lib/Utilities';
import { FocusZoneDirection } from '@fluentui/react-focus';
import { DirectionalHint } from '@fluentui/react/lib/Callout';
import { IProcessedStyleSet } from '@fluentui/react/lib/Styling';
import * as React from 'react';
import { IHeatMapChartProps, IHeatMapChartStyleProps, IHeatMapChartStyles } from './HeatMapChart.types';
import { ILegend, Legends } from '../Legends/index';
import { ILegend, ILegendContainer, Legends } from '../Legends/index';
import { convertToLocaleString } from '../../utilities/locale-util';
import {
ChartTypes,
@@ -26,6 +38,7 @@ import { Target } from '@fluentui/react';
import { format as d3Format } from 'd3-format';
import { timeFormat as d3TimeFormat } from 'd3-time-format';
import { getColorContrast } from '../../utilities/colors';
import { toImage } from '../../utilities/image-export-utils';

type DataSet = {
dataSet: RectanglesGraphData;
@@ -118,6 +131,7 @@ export class HeatMapChartBase extends React.Component<IHeatMapChartProps, IHeatM
private _emptyChartId: string;
private margins: IMargins;
private _cartesianChartRef: React.RefObject<IChart>;
private _legendsRef: React.RefObject<ILegendContainer>;

public constructor(props: IHeatMapChartProps) {
super(props);
@@ -154,6 +168,7 @@ export class HeatMapChartBase extends React.Component<IHeatMapChartProps, IHeatM
};
this._emptyChartId = getId('_HeatMap_empty');
this._cartesianChartRef = React.createRef();
this._legendsRef = React.createRef();
}

public componentDidUpdate(prevProps: IHeatMapChartProps): void {
@@ -254,6 +269,10 @@ export class HeatMapChartBase extends React.Component<IHeatMapChartProps, IHeatM
return this._cartesianChartRef.current?.chartContainer || null;
}

public toImage = (opts?: IImageExportOptions): Promise<string> => {
return toImage(this._cartesianChartRef.current?.chartContainer, this._legendsRef.current?.toSVG, getRTL(), opts);
};

private _getMinMaxOfYAxis = () => {
return { startValue: 0, endValue: 0 };
};
@@ -527,7 +546,7 @@ export class HeatMapChartBase extends React.Component<IHeatMapChartProps, IHeatM
};
legends.push(legend);
});
return <Legends {...legendProps} legends={legends} />;
return <Legends {...legendProps} legends={legends} ref={this._legendsRef} />;
};

private _getColorScale = () => {
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.