| /* eslint-disable react/jsx-sort-default-props */ |
| import * as React from 'react'; |
| import { createSelector } from 'reselect'; |
| import getChartComponentRegistry from '../registries/ChartComponentRegistrySingleton'; |
| import getChartTransformPropsRegistry from '../registries/ChartTransformPropsRegistrySingleton'; |
| import ChartProps from '../models/ChartProps'; |
| import createLoadableRenderer from './createLoadableRenderer'; |
| import { ChartType } from '../models/ChartPlugin'; |
| import { PreTransformProps, TransformProps, PostTransformProps } from '../types/TransformFunction'; |
| import { HandlerFunction } from '../types/Base'; |
| |
| function IDENTITY<T>(x: T) { |
| return x; |
| } |
| |
| const EMPTY = () => null; |
| |
| const defaultProps = { |
| id: '', |
| className: '', |
| preTransformProps: IDENTITY, |
| overrideTransformProps: undefined, |
| postTransformProps: IDENTITY, |
| onRenderSuccess() {}, |
| onRenderFailure() {}, |
| }; |
| |
| interface LoadingProps { |
| error: { toString(): string }; |
| } |
| |
| interface LoadedModules { |
| Chart: ChartType; |
| transformProps: TransformProps; |
| } |
| |
| interface RenderProps { |
| chartProps: ChartProps; |
| preTransformProps?: PreTransformProps; |
| postTransformProps?: PostTransformProps; |
| } |
| |
| const BLANK_CHART_PROPS = new ChartProps(); |
| |
| export type Props = { |
| id?: string; |
| className?: string; |
| chartProps?: ChartProps | null; |
| chartType: string; |
| preTransformProps?: PreTransformProps; |
| overrideTransformProps?: TransformProps; |
| postTransformProps?: PostTransformProps; |
| onRenderSuccess?: HandlerFunction; |
| onRenderFailure?: HandlerFunction; |
| }; |
| |
| export default class SuperChartCore extends React.PureComponent<Props, {}> { |
| /** |
| * The HTML element that wraps all chart content |
| */ |
| container?: HTMLElement | null; |
| |
| /** |
| * memoized function so it will not recompute |
| * and return previous value |
| * unless one of |
| * - preTransformProps |
| * - transformProps |
| * - postTransformProps |
| * - chartProps |
| * is changed. |
| */ |
| processChartProps = createSelector( |
| (input: { |
| chartProps: ChartProps; |
| preTransformProps?: PreTransformProps; |
| transformProps?: TransformProps; |
| postTransformProps?: PostTransformProps; |
| }) => input.chartProps, |
| input => input.preTransformProps, |
| input => input.transformProps, |
| input => input.postTransformProps, |
| (chartProps, pre = IDENTITY, transform = IDENTITY, post = IDENTITY) => |
| post(transform(pre(chartProps))), |
| ); |
| |
| /** |
| * memoized function so it will not recompute |
| * and return previous value |
| * unless one of |
| * - chartType |
| * - overrideTransformProps |
| * is changed. |
| */ |
| private createLoadableRenderer = createSelector( |
| (input: { chartType: string; overrideTransformProps?: TransformProps }) => input.chartType, |
| input => input.overrideTransformProps, |
| (chartType, overrideTransformProps) => { |
| if (chartType) { |
| const Renderer = createLoadableRenderer({ |
| loader: { |
| Chart: () => getChartComponentRegistry().getAsPromise(chartType), |
| transformProps: overrideTransformProps |
| ? () => Promise.resolve(overrideTransformProps) |
| : () => getChartTransformPropsRegistry().getAsPromise(chartType), |
| }, |
| loading: (loadingProps: LoadingProps) => this.renderLoading(loadingProps, chartType), |
| render: this.renderChart, |
| }); |
| |
| // Trigger preloading. |
| Renderer.preload(); |
| |
| return Renderer; |
| } |
| |
| return EMPTY; |
| }, |
| ); |
| |
| static defaultProps = defaultProps; |
| |
| private renderChart = (loaded: LoadedModules, props: RenderProps) => { |
| const { Chart, transformProps } = loaded; |
| const { chartProps, preTransformProps, postTransformProps } = props; |
| |
| return ( |
| <Chart |
| {...this.processChartProps({ |
| chartProps, |
| preTransformProps, |
| transformProps, |
| postTransformProps, |
| })} |
| /> |
| ); |
| }; |
| |
| private renderLoading = (loadingProps: LoadingProps, chartType: string) => { |
| const { error } = loadingProps; |
| |
| if (error) { |
| return ( |
| <div className="alert alert-warning" role="alert"> |
| <strong>ERROR</strong> |
| <code>chartType="{chartType}"</code> — |
| {error.toString()} |
| </div> |
| ); |
| } |
| |
| return null; |
| }; |
| |
| private setRef = (container: HTMLElement | null) => { |
| this.container = container; |
| }; |
| |
| render() { |
| const { |
| id, |
| className, |
| preTransformProps, |
| postTransformProps, |
| chartProps = BLANK_CHART_PROPS, |
| onRenderSuccess, |
| onRenderFailure, |
| } = this.props; |
| |
| // Create LoadableRenderer and start preloading |
| // the lazy-loaded Chart components |
| const Renderer = this.createLoadableRenderer(this.props); |
| |
| // Do not render if chartProps is set to null. |
| // but the pre-loading has been started in this.createLoadableRenderer |
| // to prepare for rendering once chartProps becomes available. |
| if (chartProps === null) { |
| return null; |
| } |
| |
| const containerProps: { |
| id?: string; |
| className?: string; |
| } = {}; |
| if (id) { |
| containerProps.id = id; |
| } |
| if (className) { |
| containerProps.className = className; |
| } |
| |
| return ( |
| <div {...containerProps} ref={this.setRef}> |
| <Renderer |
| preTransformProps={preTransformProps} |
| postTransformProps={postTransformProps} |
| chartProps={chartProps} |
| onRenderSuccess={onRenderSuccess} |
| onRenderFailure={onRenderFailure} |
| /> |
| </div> |
| ); |
| } |
| } |