antvis / G6

♾ A Graph Visualization Framework in JavaScript.
https://g6.antv.antgroup.com/
MIT License
10.94k stars 1.3k forks source link

highlight node performance v4 vs v5 #5994

Open bartoval opened 3 weeks ago

bartoval commented 3 weeks ago

Describe the bug / 问题描述

When I load 1000 nodes in v4, the highlighting of nodes, neighbors, and edges works smoothly. However, the performance is poor in v5. Despite trying various configurations, I found that the setElementState operation (even in bulk) is less efficient compared to v4, where each state change is iterated individually. I used this behaviour both applying 'hover-activate' and doing that manually.

difference v5 and v4 V5:

https://github.com/antvis/G6/assets/79913332/2a006ae4-44fe-4401-b192-cf0fad28ce0d

v4:

https://github.com/antvis/G6/assets/79913332/0c947a33-7756-46a5-92a0-645aacab1f4d

Reproduction link / 重现链接

No response

Steps to Reproduce the Bug or Issue / 重现步骤

No response

G6 Version / G6 版本

🆕 5.x

Operating System / 操作系统

Linux

Browser / 浏览器

Chrome

Additional context / 补充说明

No response

Aarebecca commented 3 weeks ago

It would be helpful if you could provide an example so we could find the bottleneck faster

bartoval commented 3 weeks ago

I will try to do a complete example. Howver in term of code I can say that this version of v4

      const activateNodeRelations = useCallback(
        ({ currentTarget, item, state }: { currentTarget: Graph; item: Item; state: string }) => {
          isItemHighlightedRef.current = true;

          const node = item as INode;
          const neighbors = node.getNeighbors();
          const neighborsIds = neighbors.map((neighbor) => neighbor.getID());

          currentTarget.getNodes().forEach((n: INode) => {
            currentTarget.clearItemStates(n, state);

            if (node.getID() !== n.getID() && !neighborsIds.includes(n.getID())) {
              currentTarget.setItemState(n, 'hidden', true);
              n.toBack();
            } else {
              currentTarget.clearItemStates(n, 'hidden');
              n.toFront();
            }
          });

          currentTarget.getEdges().forEach((edge: IEdge) => {
            if (node.getID() !== edge.getSource().getID() && node.getID() !== edge.getTarget().getID()) {
              edge.hide();
            } else {
              edge.show();
            }
          });
        },
        []
      );

is faster thatn this one implemented in V5

  activateNodeRelations: (graphInstance: Graph, itemId: ID) => {
    const neighbors = graphInstance.getNeighborNodesData(itemId);
    const neighborsIds = neighbors.map(({ id }) => id);

    const allIdsWithEmptyState: Record<string, []> = {};
    const allHiddenIds: Record<string, 'hidden'> = {};

    graphInstance.getNodeData().forEach(({ id }) => {
      allIdsWithEmptyState[id] = [];

      if (itemId !== id && !neighborsIds.includes(id)) {
        allHiddenIds[id] = 'hidden';
      }
    });

    graphInstance.getEdgeData().forEach(({ id, source, target }) => {
      if (id) {
        if (itemId !== source && itemId !== target) {
          allHiddenIds[id] = 'hidden';
        } else {
          allIdsWithEmptyState[id] = [];
        }
      }
    });

    graphInstance.setElementState(allIdsWithEmptyState, false);
    graphInstance.setElementState(allHiddenIds, false);
    graphInstance.setElementState(itemId, 'activeElement', false);
  },

I debugged just using some console log and I saw that, for example:

 graphInstance.setElementState(elements);

is slower than


    currentTarget.getNodes().forEach((n: INode) => {
              currentTarget.setItemState(....)
Aarebecca commented 3 weeks ago

@bartoval Thank you for your feedback and we will analyze the related performance issues soon