Skip to content

Commit

Permalink
feat(D3 plugin): add chartPerfomance data to onRender callback (#338)
Browse files Browse the repository at this point in the history
  • Loading branch information
kuzmadom authored Oct 30, 2023
1 parent 4d4264a commit d7464df
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 30 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@bem-react/classname": "^1.6.0",
"@gravity-ui/date-utils": "^1.4.1",
"@gravity-ui/yagr": "^3.11.0",
"afterframe": "^1.0.2",
"d3": "^7.8.5",
"lodash": "^4.17.21",
"react-split-pane": "^0.1.92"
Expand Down
75 changes: 75 additions & 0 deletions src/plugins/d3/__stories__/bar-x/PerformanceIssue.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import {StoryObj} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import {Button} from '@gravity-ui/uikit';
import {settings} from '../../../../libs';
import {ChartKit} from '../../../../components/ChartKit';
import type {ChartKitRef, ChartKitWidgetData} from '../../../../types';
import {D3Plugin} from '../..';
import {randomNormal} from 'd3';
import {randomString} from '../../../../utils';

const randomFn = randomNormal(0, 10);
const randomStr = () => randomString(Math.random() * 10, 'absdEFGHIJklmnopqrsTUvWxyz');

const ChartStory = (args: {pointsCount: number; seriesCount: number}) => {
const [shown, setShown] = React.useState(false);
const chartkitRef = React.useRef<ChartKitRef>();

const widgetData: ChartKitWidgetData = React.useMemo(() => {
const points = Array.from({length: args.pointsCount}).map(() =>
Math.ceil(Math.abs(randomFn())),
);
const series = Array.from({length: args.seriesCount}).map(randomStr);

return {
series: {
data: series.map((s) => ({
type: 'bar-x',
stacking: 'normal',
name: s,
data: points.map((p, i) => ({
x: i,
y: p,
})),
})),
},
};
}, [args]);

if (!shown) {
settings.set({plugins: [D3Plugin]});
return <Button onClick={() => setShown(true)}>Show chart</Button>;
}

return (
<div style={{height: '300px', width: '100%'}}>
<ChartKit
ref={chartkitRef}
type="d3"
data={widgetData}
onRender={action('onRender')}
onLoad={action('onLoad')}
onChartLoad={action('onChartLoad')}
/>
</div>
);
};

export const PerformanceIssueScatter: StoryObj<typeof ChartStory> = {
name: 'Performance issue',
args: {
pointsCount: 1000,
seriesCount: 10,
},
argTypes: {
pointsCount: {
control: 'number',
},
},
};

export default {
title: 'Plugins/D3/Bar-X',
component: ChartStory,
};
72 changes: 72 additions & 0 deletions src/plugins/d3/__stories__/line/PerformanceIssue.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import {StoryObj} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import {Button} from '@gravity-ui/uikit';
import {settings} from '../../../../libs';
import {ChartKit} from '../../../../components/ChartKit';
import type {ChartKitRef, ChartKitWidgetData} from '../../../../types';
import {D3Plugin} from '../..';
import {randomNormal} from 'd3';
import {randomString} from '../../../../utils';

const randomFn = randomNormal(0, 10);
const randomStr = () => randomString(Math.random() * 10, 'absdEFGHIJklmnopqrsTUvWxyz');

const ChartStory = (args: {pointsCount: number; seriesCount: number}) => {
const [shown, setShown] = React.useState(false);
const chartkitRef = React.useRef<ChartKitRef>();

const widgetData: ChartKitWidgetData = React.useMemo(() => {
const points = Array.from({length: args.pointsCount}).map(() => Math.abs(randomFn()));
const series = Array.from({length: args.seriesCount}).map(randomStr);

return {
series: {
data: series.map((s) => ({
type: 'line',
name: s,
data: points.map((_, i) => ({
x: i,
y: randomFn(),
})),
})),
},
};
}, [args]);

if (!shown) {
settings.set({plugins: [D3Plugin]});
return <Button onClick={() => setShown(true)}>Show chart</Button>;
}

return (
<div style={{height: '300px', width: '100%'}}>
<ChartKit
ref={chartkitRef}
type="d3"
data={widgetData}
onRender={action('onRender')}
onLoad={action('onLoad')}
onChartLoad={action('onChartLoad')}
/>
</div>
);
};

export const PerformanceIssueScatter: StoryObj<typeof ChartStory> = {
name: 'Performance issue',
args: {
pointsCount: 5000,
seriesCount: 2,
},
argTypes: {
pointsCount: {
control: 'number',
},
},
};

export default {
title: 'Plugins/D3/Line',
component: ChartStory,
};
59 changes: 39 additions & 20 deletions src/plugins/d3/__stories__/scatter/PerformanceIssue.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
import React from 'react';
import {Meta, Story} from '@storybook/react';
import {StoryObj} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import {Button} from '@gravity-ui/uikit';
import {settings} from '../../../../libs';
import {ChartKit} from '../../../../components/ChartKit';
import type {ChartKitRef, ChartKitWidgetData} from '../../../../types';
import {D3Plugin} from '../..';
import {randomNormal} from 'd3';
import {randomString} from '../../../../utils';

const Template: Story = () => {
const randomFn = randomNormal(0, 10);
const randomStr = () => randomString(Math.random() * 10, 'absdEFGHIJklmnopqrsTUvWxyz');

const ChartStory = (args: {categoriesCount: number; seriesCount: number}) => {
const [shown, setShown] = React.useState(false);
const chartkitRef = React.useRef<ChartKitRef>();

const widgetData: ChartKitWidgetData = React.useMemo(() => {
const categories = Array.from({length: 5000}).map((_, i) => String(i));
const randomFn = randomNormal(0, 10);
const categories = Array.from({length: args.categoriesCount}).map(randomStr);
const series = Array.from({length: args.seriesCount}).map(randomStr);

return {
xAxis: {
type: 'category',
categories: categories,
},
series: {
data: [
{
type: 'scatter',
name: 'Series 1',
data: categories.map((_, i) => ({
x: i,
y: randomFn(),
})),
},
],
data: series.map((s) => ({
type: 'scatter',
name: s,
data: categories.map((_, i) => ({
x: i,
y: randomFn(),
})),
})),
},
};
}, []);
}, [args]);

if (!shown) {
settings.set({plugins: [D3Plugin]});
Expand All @@ -43,15 +45,32 @@ const Template: Story = () => {

return (
<div style={{height: '300px', width: '100%'}}>
<ChartKit ref={chartkitRef} type="d3" data={widgetData} onRender={action('onRender')} />
<ChartKit
ref={chartkitRef}
type="d3"
data={widgetData}
onRender={action('onRender')}
onLoad={action('onLoad')}
onChartLoad={action('onChartLoad')}
/>
</div>
);
};

export const PerformanceIssue = Template.bind({});
export const PerformanceIssueScatter: StoryObj<typeof ChartStory> = {
name: 'Performance issue',
args: {
categoriesCount: 5000,
seriesCount: 2,
},
argTypes: {
categoriesCount: {
control: 'number',
},
},
};

const meta: Meta = {
export default {
title: 'Plugins/D3/Scatter',
component: ChartStory,
};

export default meta;
35 changes: 26 additions & 9 deletions src/plugins/d3/renderer/D3Widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React from 'react';
import {select} from 'd3';
import debounce from 'lodash/debounce';
import type {DebouncedFunc} from 'lodash';
import afterFrame from 'afterframe';

import type {ChartKitProps, ChartKitWidgetRef} from '../../../types';
import {getRandomCKId} from '../../../utils';

import {getRandomCKId, measurePerformance} from '../../../utils';
import {Chart} from './components';

type ChartDimensions = {
Expand All @@ -15,21 +15,38 @@ type ChartDimensions = {

const D3Widget = React.forwardRef<ChartKitWidgetRef | undefined, ChartKitProps<'d3'>>(
function D3Widget(props, forwardedRef) {
const {data, onLoad, onRender} = props;
const {data, onLoad, onRender, onChartLoad} = props;
const ref = React.useRef<HTMLDivElement>(null);
const debounced = React.useRef<DebouncedFunc<() => void> | undefined>();
const [dimensions, setDimensions] = React.useState<Partial<ChartDimensions>>();
const performanceMeasure = React.useRef<ReturnType<typeof measurePerformance> | null>(
measurePerformance(),
);

//FIXME: add chartPerfomance data to callbacks;
React.useLayoutEffect(() => {
if (onLoad) {
onLoad({});
if (onChartLoad) {
onChartLoad({});
}
}, [onChartLoad]);

if (onRender) {
onRender({});
React.useLayoutEffect(() => {
if (dimensions?.width) {
if (!performanceMeasure.current) {
performanceMeasure.current = measurePerformance();
}

afterFrame(() => {
const renderTime = performanceMeasure.current?.end();
onRender?.({
renderTime,
});
onLoad?.({
widgetRendering: renderTime,
});
performanceMeasure.current = null;
});
}
}, []);
}, [data, onRender, onLoad, dimensions]);

const handleResize = React.useCallback(() => {
const parentElement = ref.current?.parentElement;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export {getRandomCKId, randomString} from './common';
export {typedMemo} from './react';
export {getChartPerformanceDuration, markChartPerformance} from './performance';
export * from './performance';
10 changes: 10 additions & 0 deletions src/utils/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,13 @@ export const getChartPerformanceDuration = (name: string) => {

return undefined;
};

export function measurePerformance() {
const timestamp = performance.now();

return {
end() {
return performance.now() - timestamp;
},
};
}

0 comments on commit d7464df

Please sign in to comment.