JavaScript is required

展示更多(分页)

基于分页的节点加载 - 通过"加载更多"功能动态分批加载子节点

基于分页的节点加载

功能概述

本示例演示了在树形关系图中动态加载子节点的高级分页机制。当展开类别节点时,它会加载固定数量的子节点(可通过 pageSize 配置),并提供"加载更多"按钮从服务器获取额外数据。这种方法优化了初始渲染性能,同时允许用户按需渐进式加载大数据集。

核心特性实现

动态数据分页加载

核心分页逻辑在 onNodeExpand 事件处理器和 loadNextPageData 函数中实现:

const onNodeExpand = async (node: RGNode, e: RGUserEvent) => {
    if (node.data.childrenLoaded) {
        console.log('该节点的子节点已经加载过');
        return;
    }
    node.data.childrenLoaded = true;
    const myType = node.data.myType;
    const fromIndex = 0;
    await loadNextPageData(myType, fromIndex);
};

const loadNextPageData = async (myType: string, fromIndex: number) => {
    const typeNode = graphInstance.getNodes().find(n => n.data.myType === myType);
    const { currentPageItems, total } = await fetchMockDataFromRemoteServer(myType, pageSize, fromIndex);

    const currentPageGraphJsonData: RGJsonData = { nodes: [], lines: [] };
    for (const entItem of currentPageItems) {
        currentPageGraphJsonData.nodes.push({
            id: entItem.companyId,
            text: entItem.companyName,
            data: { ownerType: myType }
        });
        currentPageGraphJsonData.lines.push({
            from: typeNodeId,
            to: entItem.companyId
        });
    }
    updateLoadMoreButtonNode(typeNodeId, myType, fromIndex + currentPageItems.length, total, currentPageGraphJsonData);

    // 将新节点定位在父节点位置以实现平滑动画
    currentPageGraphJsonData.nodes.forEach((n: JsonNode) => {
        n.x = typeNode.x;
        n.y = typeNode.y;
    });

    graphInstance.addNodes(currentPageGraphJsonData.nodes);
    graphInstance.addLines(currentPageGraphJsonData.lines);
    await graphInstance.sleep(400); // 等待渲染完成
    await graphInstance.doLayout();
};

关键要点:

  • 延迟加载:子节点仅在父节点展开时加载
  • 服务器分页:使用 pageSizefromIndex 参数分块获取数据
  • 平滑定位:新节点初始定位在父节点坐标,实现自然的展开动画
  • 渲染等待:使用 sleep(400) 确保节点在布局计算前完成渲染

动态"加载更多"按钮管理

updateLoadMoreButtonNode 函数根据剩余项动态添加或移除特殊按钮节点:

const updateLoadMoreButtonNode = (
    typeNodeId: string,
    myType: string,
    fromIndex: number,
    total: number,
    currentPageGraphJsonData: RGJsonData
) => {
    const loadNextPageButtonNodeId = `${myType}-next-button`;
    graphInstance.removeNodeById(loadNextPageButtonNodeId);

    const remainingItemCount = total - fromIndex + 1;
    if (remainingItemCount > 0) {
        currentPageGraphJsonData.nodes.push({
            id: loadNextPageButtonNodeId,
            text: `Load More(${remainingItemCount})`,
            data: { myType: 'more-btn', loadType: myType, fromIndex: (fromIndex + 1) }
        });
        currentPageGraphJsonData.lines.push({
            from: typeNodeId,
            to: loadNextPageButtonNodeId
        });
    }
};

自定义节点插槽实现

NodeSlot 组件使用自定义样式和点击处理器处理不同节点类型:

const NodeSlot: React.FC<RGNodeSlotProps & {loadNextPage: (n: RGNode) => void|Promise<void>}> = ({ node, loadNextPage }) => {
    const myType = node.data.myType;
    const buttonTypes = ['investment-button', 'shareholder-button', 'historical-investment-button', 'historical-shareholder-button'];

    return (
      <>
        {buttonTypes.includes(myType) && (
          <div className="my-node my-button-node">{node.text}</div>
        )}
        {myType === 'root' && (
          <div className="my-node my-root">{node.text}</div>
        )}
        {myType === 'more-btn' && (
          <div className="my-node more-btn px-2" onClick={() => {loadNextPage(node)}}>
            {node.text}
          </div>
        )}
        {!myType && (
          <div className="my-node">{node.text}</div>
        )}
      </>
    );
};

可配置的页面大小

用户可以通过数字输入调整每页加载的项数:

const [pageSize, setPageSize] = React.useState<number>(10);

<SimpleUINumber
    currentValue={pageSize}
    min={1}
    max={200}
    onChange={(newValue: number) => { setPageSize(newValue); }}
/>

树形布局配置

使用带有正交线条的树形布局实现清晰的层级结构:

const graphOptions: RGOptions = {
    defaultExpandHolderPosition: 'right',
    defaultNodeShape: RGNodeShape.rect,
    defaultNodeBorderWidth: 0,
    defaultLineShape: RGLineShape.StandardOrthogonal,
    defaultPolyLineRadius: 5,
    defaultJunctionPoint: RGJunctionPoint.lr,
    reLayoutWhenExpandedOrCollapsed: true,
    layout: {
        layoutName: 'tree',
        from: 'left',
        treeNodeGapH: 100,
        treeNodeGapV: 10,
        alignItemsX: 'start',
        alignParentItemsX: 'end',
    }
};

创意使用场景

企业投资网络分析: 为拥有数千家子公司和投资的大型公司渐进式加载投资关系。首先加载近期投资,然后使用分页按需加载历史数据。

社交网络连接: 分页显示用户的连接,每批加载 50-100 个好友/关注者。"加载更多"按钮显示剩余连接数,允许用户探索大型网络而不会有初始性能开销。

家谱树可视化: 渲染每个祖先可以有数百个后代的家族树。使用分页首先加载直系子女,然后随着用户探索不同分支,渐进式加载孙辈和更远的后代。

电商产品关联: 显示目录商品的基于分类的关联产品关系。每批加载各分类(如"配件"、“相似产品”)的产品,在加载按钮中显示剩余数量。

文档引用图谱: 可视化学术论文或法律文档中的引用网络。首先加载直接引用,然后逐页加载间接引用,允许研究人员探索复杂的文档关系网。