vasturiano / react-force-graph

React component for 2D, 3D, VR and AR force directed graphs
https://vasturiano.github.io/react-force-graph/example/large-graph/
MIT License
2.27k stars 286 forks source link

Stuttering Animations with custom 2D shapes (300-400 nodes of URL favicons), how to optimize? #436

Open remusris opened 1 year ago

remusris commented 1 year ago

Describe the bug Frame rates begin to drop once I get a couple hundred custom 2D nodes. I'm rendering url favicon PNGs captured from browsing history.

To Reproduce This is the example code snippet. I'm querying from a firebase collection.

const graphData2 = {
  nodes: [
    {
      id: "ougdysjET6QdtTAbVTbj",
      img: "https://s.yimg.com/kw/assets/favicon-32x32.png",
    },
    {
      id: "z65jlGhnUwfZ9fDSRgXu",
      img: "https://s.yimg.com/kw/assets/favicon-32x32.png",
    },
    {
      id: "ougdysjET6QdtTAbVTbL",
      img: "https://s.yimg.com/kw/assets/favicon-32x32.png",
    },
    {
      id: "5PZaDm6sQqtRETxNiMZs",
      img: "https://www.gstatic.com/mobilesdk/160503_mobilesdk/logo/favicon.ico"
    },
    {
      id: "BxMuRkCflReF7Jm5lVNJ",
      img: "https://www.amazon.com/favicon.ico"
    },
    {
      id: "YHDQywfxaS5XZmweLvn4",
      img: "https://www.gstatic.com/mobilesdk/160503_mobilesdk/logo/favicon.ico"
    },
    {
      id: "gZuR86twYmZutQAHQwxS",
      img: "https://www.redditstatic.com/desktop2x/img/favicon/badged-favicon-32x32.png"
    }
  ],
  links: [{ source: "z65jlGhnUwfZ9fDSRgXu", target: "ougdysjET6QdtTAbVTbj" }],
};
const wrapper: React.FC = () => {
  const { user } = useAuth();
  const [items, setItems] = useState<string[]>([]);
  const [graphData, setGraphData] = useState<{ nodes: any[]; links: any[] }>({
    nodes: [],
    links: [],
  });

  const usersRefCollection = collection(db, "users");
  const getUserDoc = doc(usersRefCollection, user?.uid);
  const rawBrowsingHistoryCollection = collection(
    getUserDoc,
    "Raw Browsing History"
  );
  const q = query(rawBrowsingHistoryCollection, orderBy("time", "desc"));

  useEffect(() => {
    const fetchData = async () => {
      const querySnapshot = await getDocs(q);
      const graphData = querySnapshot.docs.map((doc) => doc.data());

      const nodes = graphData
        .map((data) => ({
          id: data.node?.id,
          img: data.node?.img,
        }))
        .filter((node) => node.id && node.img);

      const links = graphData
        .filter(
          (data) =>
            data.link?.source !== undefined && data.link?.target !== undefined
        )
        .map((data) => ({
          source: data.link.source,
          target: data.link.target,
        }));

      setGraphData({ nodes, links });

      console.log(graphData);
    };

    fetchData();
  }, []);

  return (
    <div>
      <div>
        <h1>Dashboard</h1>
        <p>second text</p>
      </div>
      <div>
        {
          <ForceGraph2D
            graphData={graphData}            
            enableNodeDrag={true}
            autoPauseRedraw={false}
            nodeCanvasObject={(
              node: Node,
              ctx: CanvasRenderingContext2D,
              globalScale: number
            ) => {
              console.log(node);
              const size = 12;
              const nodeImg = new Image();
              nodeImg.src = node.img;
              ctx.drawImage(
                nodeImg,
                node.x - size / 2,
                node.y - size / 2,
                size,
                size
              );
            }}
          />
        }
      </div>
    </div>
  );
};

export default wrapper;

graphData2 is there to show how the node data is being structured, the "img" property is a faviconURL link. The firebase query has far more nodes.

Expected behavior There seems to be some stuttering, I'm not sure if it's the way that I'm querying firebase that's causing it causing a bunch of re-renders or some type of optimization in terms of caching.

Screenshots If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information): Google Chrome | 111.0.5563.111 (Official Build) (64-bit) (cohort: Stable) Windows 11 Version 22H2 (Build 22621.1413)

Additional context Add any other context about the problem here.

vasturiano commented 1 year ago

@remusris one optimization that I think it's worth doing is to remove these two instructions from the nodeCanvasObject method:

const nodeImg = new Image();
nodeImg.src = node.img;

You're better off pre-generating those image objects instead of repeatedly at every frame in the nodeCanvasObject method.

If you still have issues after that, it would be better if you can create a simple repro example at https://codesandbox.io/.

remusris commented 1 year ago

@vasturiano I used your suggestion and it significantly improved the performance, thanks for the pointer. If I do run into any more performance issues, I'll follow up once more.

ParthPatel133 commented 7 months ago

@remusris Where did you put const nodeImg = new Image(); nodeImg.src = node.img;

How can I pre-generate outside nodeCanvasObject? @vasturiano

Can you please elaborate further? It will surely help.

Thanks