Skip to content

Commit

Permalink
Added graph benchmark with a naive fps gauge
Browse files Browse the repository at this point in the history
- Avoiding sub-pixel rendering
- Toggled `onlyRenderVisibleElements` to improve performance
  • Loading branch information
kfirpeled committed Dec 23, 2024
1 parent 2ba3247 commit f1950ad
Show file tree
Hide file tree
Showing 8 changed files with 10,814 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React from 'react';
import React, { memo } from 'react';
import { BaseEdge, getSmoothStepPath } from '@xyflow/react';
import { useEuiTheme } from '@elastic/eui';
import type { EdgeProps, EdgeViewModel } from '../types';
Expand All @@ -14,55 +14,68 @@ import { getMarkerStart, getMarkerEnd } from './markers';

type EdgeColor = EdgeViewModel['color'];

export function DefaultEdge({
id,
label,
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
data,
}: EdgeProps) {
const { euiTheme } = useEuiTheme();
const color: EdgeColor = data?.color ?? 'primary';
const dashedStyle = {
strokeDasharray: '2 2',
};

const [edgePath] = getSmoothStepPath({
// sourceX and targetX are adjusted to account for the shape handle position
sourceX: sourceX - getShapeHandlePosition(data?.sourceShape),
export const DefaultEdge = memo(
({
id,
label,
sourceX,
sourceY,
sourcePosition,
targetX: targetX + getShapeHandlePosition(data?.targetShape),
targetX,
targetY,
targetPosition,
borderRadius: 15,
offset: 0,
});
data,
}: EdgeProps) => {
const { euiTheme } = useEuiTheme();
const color: EdgeColor = data?.color ?? 'primary';
const sourceMargin = getShapeHandlePosition(data?.sourceShape);
const targetMargin = getShapeHandlePosition(data?.targetShape);
const markerStart =
data?.sourceShape !== 'label' && data?.sourceShape !== 'group'
? getMarkerStart(color)
: undefined;
const markerEnd =
data?.targetShape !== 'label' && data?.targetShape !== 'group'
? getMarkerEnd(color)
: undefined;

return (
<>
<BaseEdge
path={edgePath}
style={{
stroke: euiTheme.colors[color],
}}
css={
(!data?.type || data?.type === 'dashed') && {
strokeDasharray: '2,2',
}
}
markerStart={
data?.sourceShape !== 'label' && data?.sourceShape !== 'group'
? getMarkerStart(color)
: undefined
}
markerEnd={
data?.targetShape !== 'label' && data?.targetShape !== 'group'
? getMarkerEnd(color)
: undefined
}
/>
</>
);
}
const sX = Math.round(sourceX - sourceMargin);
const sY = Math.round(sourceY);
const tX = Math.round(targetX + targetMargin);
const tY = Math.round(targetY);

const [edgePath] = getSmoothStepPath({
// sourceX and targetX are adjusted to account for the shape handle position
sourceX: sX,
sourceY: sY,
sourcePosition,
targetX: tX,
targetY: tY,
targetPosition,
borderRadius: 15,
offset: 0,
});

return (
<>
<BaseEdge
id={id}
path={edgePath}
interactionWidth={0}
style={{
stroke: euiTheme.colors[color],
...(!data?.type || data?.type === 'dashed' ? dashedStyle : {}),
}}
markerStart={markerStart}
markerEnd={markerEnd}
/>
</>
);
}
);

DefaultEdge.displayName = 'DefaultEdge';
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useEffect, useRef, useState } from 'react';
import { css } from '@emotion/react';
import { FpsTrendline } from './fps_trendline';

export const FpsMonitor: React.FC = () => {
const [fps, setFps] = useState(0);
const frameCount = useRef(0);
const lastTimestamp = useRef(performance.now());

useEffect(() => {
let animationFrameId: number;

const calculateFPS = (timestamp: DOMHighResTimeStamp) => {
frameCount.current += 1;
const delta = timestamp - lastTimestamp.current;

// Update FPS every second
if (delta >= 1000) {
setFps((frameCount.current * 1000) / delta);
frameCount.current = 0;
lastTimestamp.current = timestamp;
}

animationFrameId = requestAnimationFrame(calculateFPS);
};

animationFrameId = requestAnimationFrame(calculateFPS);

return () => cancelAnimationFrame(animationFrameId);
}, []);

return (
<div
style={{
padding: '10px',
position: 'fixed',
}}
>
<strong>{'FPS:'}</strong> {Math.round(fps)} <br />
<strong>{'Nodes:'}</strong> {document.getElementsByClassName('react-flow__node').length}{' '}
<strong>{'Edges:'}</strong> {document.getElementsByClassName('react-flow__edge').length}{' '}
<FpsTrendline
css={css`
width: 300px;
`}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useEffect, useRef, useState } from 'react';
import { CommonProps } from '@elastic/eui';

export const FpsTrendline: React.FC<CommonProps> = (props: CommonProps) => {
const [fpsSamples, setFpsSamples] = useState<number[]>([]);
const frameCount = useRef(0);
const lastTimestamp = useRef(performance.now());

useEffect(() => {
let animationFrameId: number;

const calculateFPS = (timestamp: number) => {
frameCount.current += 1;
const delta = timestamp - lastTimestamp.current;

if (delta >= 1000) {
const fps = (frameCount.current * 1000) / delta;
setFpsSamples((prevSamples) => {
const updatedSamples = [...prevSamples, fps];
return updatedSamples.slice(-20);
});
frameCount.current = 0;
lastTimestamp.current = timestamp;
}

animationFrameId = requestAnimationFrame(calculateFPS);
};

animationFrameId = requestAnimationFrame(calculateFPS);

return () => cancelAnimationFrame(animationFrameId);
}, []);

const getBarColor = (fps: number): string => {
if (fps >= 50) return '#4caf50'; // Green
if (fps >= 30) return '#ffeb3b'; // Yellow
return '#f44336'; // Red
};

return (
<div {...props}>
<div
style={{
display: 'flex',
alignItems: 'flex-end',
height: '30px',
padding: '5px',
}}
>
{fpsSamples.map((fps, index) => (
<div
key={index}
style={{
height: `${Math.min(fps, 60) * (100 / 60)}%`,
width: '5%',
backgroundColor: getBarColor(fps),
marginRight: '2px',
}}
title={`${fps.toFixed(2)} FPS`}
>
<div
style={{
fontSize: '8px',
padding: '2px',
left: `${index * 5 + 5}%`,
}}
>
{fps.toFixed(0)}
</div>
</div>
))}
</div>
</div>
);
};
Loading

0 comments on commit f1950ad

Please sign in to comment.