为了账号安全,请及时绑定邮箱和手机立即绑定

D3 Selection 总是返回 Enter() 之后的所有项目,无论它们是否是新的

D3 Selection 总是返回 Enter() 之后的所有项目,无论它们是否是新的

POPMUISE 2023-07-14 10:41:30
对于上下文:我在 React 应用程序中运行 D3,并将 D3 更新模式绑定到 React 生命周期方法。问题: 当我const selection = select(".svg-circle-node-class").data(nodeArray)运行该函数时,D3 总是为我提供数组中的每个节点,无论新节点是否已添加到数组中。我希望该函数selection.enter()为我提供新的节点,并selection.exit()为我提供不存在的节点以供删除,并且对于直接在此选择上完成的任何操作而不使用,enter()应该为我提供所有剩余的节点,如下所示:这个问题导致我无法区分新旧项目,这导致我总是重新附加新的 SVG 元素,因此每次状态发生变化时都会复制所有内容。我只想更新这些元素的值。我的具体情况如下:我有一个图的实现,其结构如下:class Graph {  nodes: Node[];}class DirectedEdge {  from: Node;  to: Node;}class Node {  x: number;  y: number;  edges: DirectedEdge[];  id: string; // unique for each node}在我的反应组件中,我通过在组件状态中保存图形的实例来跟踪图形的状态,如下所示:const n1, n2: Node = ...;interface IState {  g: Graph = new Graph(n1, n2), // constructor connects two graphs with an edge  degree: number // a parameter to change some shapes}我将图表的初始化与componentDidMount生命周期方法联系起来,这是执行此操作的函数:    /** Once HTML elements have loaded, this method is run to initialize the SVG elements using D3. */    private initializeGraph(): void {        const mainGroup = select(this.svgElement.current)            .append("g")            .attr("id", "main");        // append nodes svg group        this.nodeElements = mainGroup.append("g")            .attr("id", "nodes")            .selectAll<SVGCircleElement, Node>(".directed-graph-node")            .data<Node>(this.state.g.nodes, _ => _.id);        // append edges svg group        this.edgeElements = mainGroup.append("g")            .attr("id", "edges")            .selectAll<SVGPathElement, DirectedEdge>(".directed-graph-edge")            .data<DirectedEdge>(this.state.g.nodes.flatMap(_ => _.edges), _ => _.id);    }我将一个updateGraph函数与componentDidUpdate生命周期函数绑定在一起,导致每次状态发生变化时都会调用它(即,在本例中,是由参数“ Degree”变化引起的。但我希望能够更新(x ,y) 每次更新时每个节点的位置)。
查看完整描述

1 回答

?
阿晨1998

TA贡献2037条经验 获得超6个赞

在D3中,如果您想获得新的进入/更新/退出选择,则必须再次执行数据连接。您的代码中发生的情况是:

  • 您在函数中执行一次数据连接initialize(对于每个图表元素)。该数据连接将每个节点标记为新节点并返回每个节点,然后您可以缓存这些结果。

  • 在您的update函数中,您每次都使用这些缓存的结果。

update相反,每次图形更改时,请在 上执行数据连接,而不是在 上执行initialize。一个例子nodeElements

private initializeGraph(): void {

    const mainGroup = select(this.svgElement.current)

        .append("g")

        .attr("id", "main");


    // append nodes svg group

    this.nodeElements = mainGroup.append("g")

        .attr("id", "nodes")

}


private updateGraph(): void {

    // select nodes & edges

    const graphNodes = this.nodeElements

        .selectAll<SVGCircleElement, Node>(".directed-graph-node")

        .data<Node>(this.state.g.nodes, _ => _.id);


    // update nodes with their current position

    graphNodes.attr("cx", node => node.x)

        .attr("cy", node => node.y);


    // add newly added nodes if any

    graphNodes.enter()

        .append("circle")

        .attr("class", ".directed-graph-node")

        .attr("stroke", "steelblue")

        .attr("cx", node => node.x)

        .attr("cy", node => node.y)

        .attr("r", 2.5)

        .call(drag<SVGCircleElement, Node>());


    // remove nodes that don't exist anymore

    graphNodes.exit().remove();

}

正如您所看到的,这种模式相当严厉。我们可以使用Selection.join()来代替。它允许我们删除enter和上的重复代码update并减轻重量。


private updateGraph(): void {

    const graphNodes = this.nodeElements

        .selectAll<SVGCircleElement, Node>(".directed-graph-node")

        // data() join()

        .data<Node>(this.state.g.nodes, _ => _.id)

        .join(

        enter => enter.append("circle")

            .attr("class", ".directed-graph-node")

            .attr("stroke", "steelblue")

            .attr("r", 2.5)

            .call(drag<SVGCircleElement, Node>()),

        update => update,

        exit => exit.remove();

        )

        // enter + update past this point

        .attr("cx", node => node.x) 

        .attr("cy", node => node.y)

}


查看完整回答
反对 回复 2023-07-14
  • 1 回答
  • 0 关注
  • 69 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信