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 1 commit
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
18 changes: 6 additions & 12 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,10 @@ export interface ICartesianChartStyles {
export interface IChart {
// (undocumented)
chartContainer: HTMLElement | null;
// Warning: (ae-forgotten-export) The symbol "IImageExportOptions" needs to be exported by the entry point index.d.ts
//
// (undocumented)
toImage?: (opts?: IImageExportOptions) => Promise<string>;
}

// @public (undocumented)
Expand Down Expand Up @@ -854,18 +858,6 @@ export interface IHorizontalDataPoint {
y: number;
}

// @public (undocumented)
export interface IImageExportOptions {
// (undocumented)
background?: string;
// (undocumented)
height?: number;
// (undocumented)
scale?: number;
// (undocumented)
width?: number;
}

// @public
export interface ILegend {
action?: VoidFunction;
Expand Down Expand Up @@ -910,6 +902,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,11 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
return this._cartesianChartRef.current?.chartContainer || null;
}

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

private _getDomainNRangeValues = (
points: ILineChartPoints[],
margins: IMargins,
Expand Down Expand Up @@ -659,6 +668,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,11 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
return this._rootElem;
}

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

private _closeCallout = () => {
this.setState({
showHover: false,
Expand Down Expand Up @@ -285,6 +294,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,11 @@ export class GaugeChartBase extends React.Component<IGaugeChartProps, IGaugeChar
return this._rootElem;
}

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

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

Expand Down Expand Up @@ -514,6 +522,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,11 @@ export class GroupedVerticalBarChartBase
return this._cartesianChartRef.current?.chartContainer || null;
}

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

private _getMinMaxOfYAxis = () => {
return { startValue: 0, endValue: 0 };
};
Expand Down Expand Up @@ -635,6 +644,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,11 @@ export class HeatMapChartBase extends React.Component<IHeatMapChartProps, IHeatM
return this._cartesianChartRef.current?.chartContainer || null;
}

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

private _getMinMaxOfYAxis = () => {
return { startValue: 0, endValue: 0 };
};
Expand Down Expand Up @@ -527,7 +547,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