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
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 @@
{
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": "[email protected]",
"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
Expand Up @@ -361,6 +361,8 @@ export interface ICartesianChartStyles {
export interface IChart {
// (undocumented)
chartContainer: HTMLElement | null;
// (undocumented)
toImage?: (opts?: IImageExportOptions) => Promise<string>;
}

// @public (undocumented)
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
classNamesFunction,
find,
getId,
getRTL,
initializeComponentRef,
memoizeFunction,
} from '@fluentui/react/lib/Utilities';
Expand Down Expand Up @@ -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>();

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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],
Expand Down
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';
Expand All @@ -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;
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 };
};
Expand Down Expand Up @@ -635,6 +643,7 @@ export class GroupedVerticalBarChartBase
focusZonePropsInHoverCard={this.props.focusZonePropsForLegendsInHoverCard}
{...this.props.legendProps}
onChange={this._onLegendSelectionChange.bind(this)}
ref={this._legendsRef}
/>
);
};
Expand Down
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,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 };
};
Expand Down Expand Up @@ -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 = () => {
Expand Down
Loading
Loading