1

I am working on a collapsible tree for power bi custom visual. I want to implement a toggling functionality that would allow me to drill up or down a node when I click on it. Below I have provided my current code for rendering a tree and an other method that executes when a node is clicked. What I think is that when a node is clicked, renderTree method is called again from within click method and that results in rendering of whole tree again. And thus I am not able to see the nodes being toggled. What changes or any additionl method would I need for that. Thanks in advance.

Here is what I have tried so far but it does not give me the correct results. In this case the measured data is actuall a json tree and tree() and diagonal() have already been declared in global scope of class Visual just like so:

// Declare an interface for the links/connections
interface LinkCoords {
  x: number;
  y: number;
}

// Declare an interface for a tree node
interface TreeNode {
  name: string;
  children?: TreeNode[];
  // Dynamic addition of any incoming columns from analyze by data role
  [key: string]: number | string | undefined | TreeNode[];
}

// Create a tree layout
  private tree = d3
    .tree()
    // Height and width of the separations
    .nodeSize([50, 300])
    // Wether or not the nodes have same parent, there should be a separation of 2 units between nodes
    .separation(function separation(a, b) {
      return a.parent == b.parent ? 2 : 2;
    });

  // Links properties
  private diagonal = d3
    .linkHorizontal<{}, LinkCoords>()
    .x((d) => d.y)
    .y((d) => d.x);
// Function that would render the tree on the canvas
  private renderTree(measuredData: TreeNode) {
    // Compute the hierarchy
    const root = d3.hierarchy(measuredData);

    // Call the tree function
    this.tree(root);

    // Node variable
    const node = this.treeContainer
      .selectAll(".node")
      .data(root.descendants())
      .enter()
      .append("g")
      .attr("transform", function (d: any) {
        return "translate(" + (d.y + 20) + "," + (d.x + 307) + ")";
      })
      .attr("font-size", 10)
      .on("click", (event, d) => this.click(d.data));

    // Node properties
    node
      .append("rect")
      .attr("width", 150)
      .attr("height", 50)
      .style("stroke", function (d) {
        return d.children ? "#5CCE8A" : "#D45F5F";
      })
      .attr("fill", "grey");

    // Node text properties
    node
      .append("text")
      .attr("x", 70)
      .attr("y", 25)
      .attr("text-anchor", "middle")
      .text(
        (d) => `name: ${d.data.name}, value: ${d.data.V}, import: ${d.data.I}`
      )
      .attr("fill", "white");

    // Create links
    const link = this.treeContainer
      .selectAll(".link")
      .data(root.links())
      .enter();

    // Define the path source and target for the links
    link
      .append("path")
      .attr("class", "link")
      .attr("fill", "none")
      .attr("stroke", "white")
      .attr("d", (d) => {
        // Define the source
        const source: LinkCoords = {
          x: (d.source as any).x + 332,
          y: (d.source as any).y + 170,
        };
        // Define the target
        const target: LinkCoords = {
          x: (d.target as any).x + 332,
          y: (d.target as any).y + 20,
        };
        return this.diagonal({ source, target });
      })
      .attr("stroke-width", "1px")
      .attr("stroke-dasharray", "1, 0");
  }

  // Toggle the node
  private click(d: any) {
    if (d.children) {
      // If the clicked node has children
      d._children = d.children; // Hide its children
      d.children = null;
    } else {
      // If the clicked node does not have children
      d.children = d._children; // Show its children
      d._children = null;
    }
    this.renderTree(d);
  }

1 Answers1

0

The reason why you are nt able to see the nodes being toggled is because every time you click on a node, you are rerendering the whole tree with the updated data. Instead of rerendering the whole tree, you should modify the existing tree by updating the properties of the nodes that need to be toggled.

To achieve this, you can add a "collapsed" property to each node, which will be set to true or false depending on whether the node is collapsed or expanded. Then, in the click function, you can toggle the "collapsed" property of the clicked node and its children, and then update the properties of the corresponding nodes in the SVG.

You could change something like,

private click(d: any) {
    if (d.children || d._children) {
        if (d.collapsed) {
            d.children = d._children;
            d._children = null;
            d.collapsed = false;
        } else {
            // If the clicked node is collapsed, expand it
            d._children = d.children;
            d.children = null;
            d.collapsed = true; // using this property to change the behavior on click
        }

        const descendants = this.treeContainer
            .selectAll(".node")
            .data(this.tree(root).descendants());

        descendants
            .select("rect")
            .style("stroke", function (d) {
                return d.children || d._children ? "#5CCE8A" : "#D45F5F";
            });

        const links = this.treeContainer.selectAll(".link").data(this.tree(root).links());
        links.exit().remove(); // removes the elements for those deleted in data 
        links.enter() 
            .append("path")
            .attr("class", "link")
            .attr("fill", "none")
            .attr("stroke", "white")
            .attr("stroke-width", "1px")
            .attr("stroke-dasharray", "1, 0")
            .merge(links)
            .attr("d", (d) => {
                const source: LinkCoords = {
                    x: (d.source as any).x + 332,
                    y: (d.source as any).y + 170,
            };
                const target: LinkCoords = {
                    x: (d.target as any).x + 332,
                    y: (d.target as any).y + 20,
            };
                return this.diagonal({ source, target });
            });

    }
}
Ganesh Nemade
  • 1,504
  • 12
  • 17