Skip to content

Commit

Permalink
fix(Highcharts plugin): fix onRender trigger behaviour (#502)
Browse files Browse the repository at this point in the history
* fix(Highcharts plugin): fix onRender trigger behaviour

* fix: handle nullable entries
  • Loading branch information
korvin89 authored Jul 9, 2024
1 parent 8fd0d83 commit f910063
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const Template: Story = () => {
type="highcharts"
data={widgetData}
onLoad={action('onLoad')}
onRender={action('onRender')}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,7 @@ export class HighchartsComponent extends React.PureComponent<Props, State> {
}

componentDidUpdate() {
const needRenderCallback =
this.props.onRender && !this.state.isError && !this.props.splitTooltip;
if (needRenderCallback) {
this.props.onRender?.({
renderTime: getChartPerformanceDuration(this.getId()),
});

if (this.needRenderCallback()) {
const widget = this.chartComponent.current ? this.chartComponent.current.chart : null;

if (this.state.callback && widget) {
Expand Down Expand Up @@ -169,6 +163,7 @@ export class HighchartsComponent extends React.PureComponent<Props, State> {
constructorType={options?.useHighStock ? 'stockChart' : 'chart'}
containerProps={{className: 'chartkit-graph'}}
ref={this.chartComponent}
onRender={this.needRenderCallback() && this.props.onRender}
/>
);
}
Expand Down Expand Up @@ -226,4 +221,10 @@ export class HighchartsComponent extends React.PureComponent<Props, State> {
window.requestAnimationFrame(this.reflow);
}
}

private needRenderCallback() {
const {splitTooltip, onRender} = this.props;
const {isError} = this.state;
return !splitTooltip && onRender && !isError;
}
}
28 changes: 28 additions & 0 deletions src/plugins/highcharts/renderer/components/HighchartsReact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@

import React from 'react';

import afterFrame from 'afterframe';
import Highcharts from 'highcharts';

import type {ChartKitProps} from '../../../../types';
import {measurePerformance} from '../../../../utils';

import {useElementSize} from './useElementSize';

interface HighchartsReactRefObject {
chart: Highcharts.Chart | null | undefined;
container: React.RefObject<HTMLDivElement | undefined>;
Expand All @@ -16,6 +22,7 @@ interface HighchartsReactProps {
highcharts?: typeof Highcharts;
options: Highcharts.Options;
callback?: Highcharts.ChartCallbackFunction;
onRender?: ChartKitProps<any>['onRender'];
}

const useIsomorphicLayoutEffect =
Expand All @@ -25,8 +32,13 @@ export const HighchartsReact: React.ForwardRefExoticComponent<
React.PropsWithoutRef<HighchartsReactProps> & React.RefAttributes<HighchartsReactRefObject>
> = React.memo(
React.forwardRef(function HighchartsReact(props: HighchartsReactProps, ref) {
const {onRender} = props;
const containerRef = React.useRef<HTMLDivElement | null>(null);
const chartRef = React.useRef<Highcharts.Chart | null>();
const {width, height} = useElementSize(containerRef);
const performanceMeasure = React.useRef<ReturnType<typeof measurePerformance> | null>(
measurePerformance(),
);

useIsomorphicLayoutEffect(() => {
function createChart() {
Expand Down Expand Up @@ -83,6 +95,22 @@ export const HighchartsReact: React.ForwardRefExoticComponent<
[],
);

React.useLayoutEffect(() => {
if (width && height) {
if (!performanceMeasure.current) {
performanceMeasure.current = measurePerformance();
}

afterFrame(() => {
const renderTime = performanceMeasure.current?.end();
if (typeof renderTime === 'number') {
onRender?.({renderTime});
}
performanceMeasure.current = null;
});
}
}, [width, height, onRender]);

return <div {...props.containerProps} ref={containerRef} />;
}),
);
Expand Down
67 changes: 67 additions & 0 deletions src/plugins/highcharts/renderer/components/useElementSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';

import debounce from 'lodash/debounce';
import round from 'lodash/round';

const RESIZE_DEBOUNCE = 200;
const ROUND_PRESICION = 2;

export interface UseElementSizeResult {
width: number;
height: number;
}

export function useElementSize<T extends HTMLElement = HTMLDivElement>(
ref: React.MutableRefObject<T | null> | null,
// can be used, when it is needed to force reassign observer to element
// in order to get correct measures. might be related to below
// https://github.com/WICG/resize-observer/issues/65
key?: string,
) {
const [size, setSize] = React.useState<UseElementSizeResult>({
width: 0,
height: 0,
});

React.useLayoutEffect(() => {
if (!ref?.current) {
return undefined;
}

const handleResize: ResizeObserverCallback = (entries) => {
if (!Array.isArray(entries)) {
return;
}

const entry = entries[0];

if (entry && entry.borderBoxSize) {
const borderBoxSize = entry.borderBoxSize[0]
? entry.borderBoxSize[0]
: (entry.borderBoxSize as unknown as ResizeObserverSize);
// ...but old versions of Firefox treat it as a single item
// https://github.com/mdn/dom-examples/blob/main/resize-observer/resize-observer-text.html#L88

setSize({
width: round(borderBoxSize.inlineSize, ROUND_PRESICION),
height: round(borderBoxSize.blockSize, ROUND_PRESICION),
});
} else if (entry) {
const target = entry.target as HTMLElement;
setSize({
width: round(target.offsetWidth, ROUND_PRESICION),
height: round(target.offsetHeight, ROUND_PRESICION),
});
}
};

const observer = new ResizeObserver(debounce(handleResize, RESIZE_DEBOUNCE));
observer.observe(ref.current);

return () => {
observer.disconnect();
};
}, [ref, key]);

return size;
}

0 comments on commit f910063

Please sign in to comment.