blob: c4fb355759a2f0d9f1bfa8b60214206a5751dd24 [file] [log] [blame]
import React, { ReactNode } from 'react';
import ErrorBoundary, { ErrorBoundaryProps, FallbackProps } from 'react-error-boundary';
import { ParentSize } from '@vx/responsive';
import { createSelector } from 'reselect';
import { parseLength, Dimension } from '../../dimension';
import SuperChartCore, { Props as SuperChartCoreProps } from './SuperChartCore';
import DefaultFallbackComponent from './FallbackComponent';
import ChartProps, { ChartPropsConfig } from '../models/ChartProps';
import NoResultsComponent from './NoResultsComponent';
const defaultProps = {
FallbackComponent: DefaultFallbackComponent,
height: 400 as string | number,
width: '100%' as string | number,
};
export type FallbackPropsWithDimension = FallbackProps & Partial<Dimension>;
export type WrapperProps = Dimension & {
children: ReactNode;
};
export type Props = Omit<SuperChartCoreProps, 'chartProps'> &
Omit<ChartPropsConfig, 'width' | 'height'> & {
/**
* Set this to true to disable error boundary built-in in SuperChart
* and let the error propagate to upper level
* and handle by yourself
*/
disableErrorBoundary?: boolean;
/** debounceTime to check for container resize */
debounceTime?: number;
/** Component to render when there are unexpected errors */
FallbackComponent?: React.ComponentType<FallbackPropsWithDimension>;
/** Event listener for unexpected errors from chart */
onErrorBoundary?: ErrorBoundaryProps['onError'];
/** Chart width */
height?: number | string;
/** Chart height */
width?: number | string;
/**
* Component to wrap the actual chart
* after the dynamic width and height are determined.
* This can be useful for handling tooltip z-index, etc.
* e.g. <div style={{ position: 'fixed' }} />
* You cannot just wrap this same component outside of SuperChart
* when using dynamic width or height
* because it will clash with auto-sizing.
*/
Wrapper?: React.ComponentType<WrapperProps>;
};
type PropsWithDefault = Props & Readonly<typeof defaultProps>;
export default class SuperChart extends React.PureComponent<Props, {}> {
/**
* SuperChart's core
*/
core?: SuperChartCore | null;
private createChartProps = ChartProps.createSelector();
private parseDimension = createSelector(
({ width }: { width: string | number; height: string | number }) => width,
({ height }) => height,
(width, height) => {
// Parse them in case they are % or 'auto'
const widthInfo = parseLength(width);
const heightInfo = parseLength(height);
const boxHeight = heightInfo.isDynamic ? `${heightInfo.multiplier * 100}%` : heightInfo.value;
const boxWidth = widthInfo.isDynamic ? `${widthInfo.multiplier * 100}%` : widthInfo.value;
const style = {
height: boxHeight,
width: boxWidth,
};
// bounding box will ensure that when one dimension is not dynamic
// e.g. height = 300
// the auto size will be bound to that value instead of being 100% by default
// e.g. height: 300 instead of height: '100%'
const BoundingBox =
widthInfo.isDynamic &&
heightInfo.isDynamic &&
widthInfo.multiplier === 1 &&
heightInfo.multiplier === 1
? React.Fragment
: ({ children }: { children: ReactNode }) => <div style={style}>{children}</div>;
return { BoundingBox, heightInfo, widthInfo };
},
);
static defaultProps = defaultProps;
private setRef = (core: SuperChartCore | null) => {
this.core = core;
};
renderChart(width: number, height: number) {
const {
id,
className,
chartType,
preTransformProps,
overrideTransformProps,
postTransformProps,
onRenderSuccess,
onRenderFailure,
disableErrorBoundary,
FallbackComponent,
onErrorBoundary,
Wrapper,
queriesData,
...rest
} = this.props as PropsWithDefault;
const chartProps = this.createChartProps({
...rest,
queriesData,
height,
width,
});
let chart;
// Render the no results component if the query data is null or empty
const noResultQueries =
!queriesData ||
queriesData.every(({ data }) => !data || (Array.isArray(data) && data.length === 0));
if (noResultQueries) {
chart = <NoResultsComponent id={id} className={className} height={height} width={width} />;
} else {
const chartWithoutWrapper = (
<SuperChartCore
ref={this.setRef}
id={id}
className={className}
chartType={chartType}
chartProps={chartProps}
preTransformProps={preTransformProps}
overrideTransformProps={overrideTransformProps}
postTransformProps={postTransformProps}
onRenderSuccess={onRenderSuccess}
onRenderFailure={onRenderFailure}
/>
);
chart = Wrapper ? (
<Wrapper width={width} height={height}>
{chartWithoutWrapper}
</Wrapper>
) : (
chartWithoutWrapper
);
}
// Include the error boundary by default unless it is specifically disabled.
return disableErrorBoundary === true ? (
chart
) : (
<ErrorBoundary
FallbackComponent={(props: FallbackProps) => (
<FallbackComponent width={width} height={height} {...props} />
)}
onError={onErrorBoundary}
>
{chart}
</ErrorBoundary>
);
}
render() {
const { heightInfo, widthInfo, BoundingBox } = this.parseDimension(
this.props as PropsWithDefault,
);
// If any of the dimension is dynamic, get parent's dimension
if (widthInfo.isDynamic || heightInfo.isDynamic) {
const { debounceTime } = this.props;
return (
<BoundingBox>
<ParentSize debounceTime={debounceTime}>
{({ width, height }) =>
width > 0 &&
height > 0 &&
this.renderChart(
widthInfo.isDynamic ? Math.floor(width) : widthInfo.value,
heightInfo.isDynamic ? Math.floor(height) : heightInfo.value,
)
}
</ParentSize>
</BoundingBox>
);
}
return this.renderChart(widthInfo.value, heightInfo.value);
}
}