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 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
@@ -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)
@@ -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;
@@ -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;
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,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,
@@ -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}
/>
);
};
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,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,
@@ -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;
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,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;

@@ -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>
);
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,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 };
};
@@ -635,6 +644,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,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 };
};
@@ -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 = () => {
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.