Fix misleading circle sizes calculated by circle packing
Asked Answered
A

0

7

I have created a circle packing layout with a large hierarchical dataset. The image below shows what it looks like. The number in each circle corresponds to the number of leaf nodes.

Each leaf node is given an initial value of 1. The layout then calculates r, x, and y for each node.

let root = d3.stratify()
  .id((d) => d.id)
  .parentId((d) => d.parent)
  (data)

root
  .sum(d => d.children ? 0 : 1)
  .sort((a, b) => b.height - a.height || b.value - a.value)

pack(root)

The problem here is that the size of the largest circle is incorrect. It only has a value of 1271, hence it should be smaller than the circle with a value of 1364. Therefore, the visualisation is misleading.

enter image description here

Now if I prune the hierarchy and remove all the children of the roots children, the size of each circle is correct. But this will not allow me to zoom in and show the children of a node. As their x, y, and r, have not been calculated.

root
  .sum(d => d.children ? 0 : 1)
  .sort((a, b) => b.height - a.height || b.value - a.value)

root.children.forEach( child => {
  delete child.children
})

pack(root)

enter image description here

I understand that the problem stated above is jsut a limitation of the space filling circle packing algorithm. In fact, this post explains it quite well.

Circle packing can only represent either one generation with a constant areal scale factor or all leaf nodes - not both. Either approach can be accomplished with d3.pack

Circle packing can represent diameters proportionally for either leaves or one generation. Again either approach can be accomplished with d3.pack.

This question was posted a could years ago, so I am curious if it is somehow possible to overcome this problem?

This is what I have tried:

  1. Create hierarchy
  2. Remove children of roots children
  3. Calculate the layout
  4. Add children of roots children back
  5. Recalculate layout for Each child
    root
      .sum(d => d.children ? 0 : 1)
      .sort((a, b) => b.height - a.height || b.value - a.value)
    
    root.children.forEach( child => {
      if(child.children) {
        child.temp = child.children
        delete child.children
      }
    })
    
    pack(root)
    
    root.children.forEach(child => {
      if (child.temp) {
        child.children = child.temp
        delete child.temp
    
        let pack = d3.pack()
          .size([child.r * 2, child.r * 2])
        
        pack(child)
    
        child.descendants().forEach(d => {
          //d.x = d.x - child.r // + (child.x - child.r)
          //d.y = d.y - child.r //(child.y - child.r)
        })
      }
    })

Which produces this:

enter image description here

So some calculation is missing to position the nodes correctly.

The second iteration of the pack layout recalculates the childrens positions. So if I add this:

root.children.forEach(child => {
      if (child.temp) {
        child.children = child.temp
        delete child.temp

        let pack = d3.pack()
          .size([child.r * 2, child.r * 2])
        
        let tempX = child.x
        let tempY = child.y

        pack(child)
        
        child.x = tempX
        child.y = tempY
      }
    })

It produces the image below, where the children are in the correct position.

enter image description here

But if I zoom in on a node, then it's children are not positioned correctly. So if I add the following:

root.children.forEach(child => {
      if (child.temp) {
        child.children = child.temp
        delete child.temp

        let pack = d3.pack()
          .size([child.r * 2, child.r * 2])
        
        let tempX = child.x
        let tempY = child.y

        pack(child)
        
        child.x = tempX
        child.y = tempY
        
        child.descendants().forEach(d => {
          if(d.id == child.id) { return }
          d.x = d.x - child.r
          d.y = d.y - child.r
        })
      }
    })

I get this when I zoom in on a node:

enter image description here

The position is off, but only by a small bit.

I am guessing you would need to do this type of this recursively in the final version. I am also curious if anyone has tried something like this before?

Is this a reverse circle packing algorithm? Instead of calculating the size of the parent by the combined size of the nested nodes, we need to calculate the size of the nested nodes by using the parents size and then scaling them accordingly.

The visualisation will not let you compare leaf nodes, but rather compare the structure of a hierarchy?

Agriculture answered 12/10, 2021 at 8:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.