🏠 back to Observable

Sunburst and TreeMap displaying graph-like data

Hello,

I am trying to use a sunburst to display particular hierarchical data which are not exactly a tree because some of the leafs are actually the same object.

It works (https://observablehq.com/@ilogre/zoomable-sunburst) but there is one issue: the value of the inner layer segments is recalculated to be he sum of its children values instead of taking the value given in the json file.

For example, “Hierarchy” should have a value of 3, meaning that 3 widgets of our catalog satisfy it, even though:
• 2 widgets satisfy Hierarchy + Part of a Whole
• 2 satisfy Hierarchy + Proportion
• 1 satisfies Hierarchy + Extremum
• 1 satisfies Hierarchy + Comparison

But some of these children are actually the same widgets, so the value of Hierarchy should be 3, not 9 (its children values + its own value).

I know my problem is due to these non-tree data, but fixing it would open a way to display graph data through a Sunburst, and probably a TreeMap.

Unfortunately, I can’t find the line of code where this value is calculated to customize it.

Any help?

Thank you ! :slight_smile:

PS : I will post a more detailed example below if this first post is not clear enough :wink:

1 Like

It is really more of a amelioration suggestion than a bug. The current implementation seems to be the most common. So unfortunately, I have no working example to attach, but I’d be happy to explain the idea.

At the moment, the sunbursts are designed to visualize tree-like data, i.e. it relies on the hypothesis that each leaf is unique. To allow the visualization of graph-like data would only require to rethink the way the weight are attributed.

Let’s take an example:
If at the first layer, the segment “A” has three children “A1”, “A2”, and “A3”, respectively weighted 1, 2 and 3.
In the current implementation, the weight of A is deduced from the sum of its children weights, so 6.
However, we can imagine that A1 and A3 share a child, we wan it to be rendered under “A1” and under “A3” but we don’t want the weight of that child to be imputed twice to “A”.
So the weight of “A” is not the sum of its children anymore.

One way to implement it is to allow the specification of a non-leaf node weight. Currently, if I specify a weight for A in the json, you will calculated a new weight by adding this value to the sum of its children, resulting in an incoherent layout (example).
Note that it would mean A.weight < (A1.weight + A2.weight + A3.weight).
So to calculate the angle of A children, you could see the weight of its children as a ratio of their parent instead of an absolute value.

For example, if the weight of “A” is specified as 5 in the data, but the weight of its children “A1”, “A2”, and “A3” are still respectively weighted 1, 2 and 3, you would divide the angle attributed to “A” by 6 and share accordingly to the children weight.
So it requires to calculate the angles from the center toward the outer layers instead of using the leafs weight to deduce the parent weight and therefore angle.

By doing so, we would be among the first to offer sunburst and treemap visualizations compatible with graph-like data!

1 Like

I’m seeing a lot of viewers but no answer :frowning:
Let me know if it’s because the problem is not clearly stated, is not interesting, or if you have no idea, I am very open to feedback !

Hi @ILogre, and welcome to the forum!

I think your problem is well stated, and I had the same issue once before on a ‘consultant management system’ that I once tried to build for a company: many consultants each could enter a common project, and we wanted that project to be counted in our ‘staff experience’ as 1 project, rather than as 2.

My context was totally different, but the concept was similar: we were working on a data management utility wherein we could create an entity for each project, identify shared entities, and count unique ones. The outcome was also cool for showing on which projects people had shared experiences and whether their time on the project overlapped. :wink:

The long and short of my anecdote: Just writing in to say that it’s an interesting question, but I have no idea. :man_facepalming:

I keep giving you likes but have refrained from chiming in hoping that others will (I am also eager to learn). However, the answer may be that the nature of the visualization itself might not easily lend itself to the type of manipulation you describe… other than manually injecting a calculation on each node? or writing a function (something like d3.group, now native under d3 6.0) to identify common children and then dividing that total count across ‘parent’ values?

At least it’s good to know I am not alone :wink: Thanks

I was afraid it would come to this. I am not an experienced D3 developer, I use it as a provider of “widget on the shelf”.
But I am surprised that we can’t just indicate a value for a parent node in the json data, to override the current calculation by sum of the children. It would be the simplest solution : replace the current sum by something like
if (parent.value != null) { node.value=parent value; } else { //current sum algorithm }
instead of the current behavior, which is something like
parent.children.foreach( child -> parent.value += child.value );

1 Like

But you can? Replace .sum(d => d.value) in the partition cell with your own callback that returns 0 if the node isn’t linked at its original depth.
Alternatively you can just not call .sum() and instead accumulate/assign each node’s .value as you please. Remember that D3 isn’t a charting library, it’s a collection of tools. :slight_smile:

Also, since the sunburst chart is essentially just a radial partition chart, perhaps it might be easier to visualize that instead until you’ve gotten rid of all the quirks?

Please keep in mind that Observable allows you to review the data at every step, so don’t hesitate to create intermediate cells for your data whenever you feel that you’re working with a blackbox.

And if you want to see the original D3 object names (like "Node" instead of "Lz"), you can replace d3 = require('d3@5') with d3 = import('d3@5'). This will include the unminified source code instead.

2 Likes

Thank you @mootari ! That is exactly the line I was looking for.

I know D3 is a whole language, and I’ve been meaning to take the time to learn more about it, but right now we don’t have the opportunity in our schedule, thus the “widgets on the shelf” vision :wink:

Although, I didn’t get your advice:

What do you mean by visualizing “that” ? Isn’t a sunburst the easiest way to visualize a partition in a radial way ?

About that, the documentation states

Do you think it means that you MUST call sum(and call it before the layout resolution), or that IF you call sum it must be before the layout resolution ?
Do you have an advice on how to avoid using .sum ?

I meant that it might be easier to start from a plain, rectangular partition layout. Since you seem to want a mix of sunburst and treemap, temporarily removing the radial arrangement might allow you to better understand the layout’s behavior and how you need to adapt it.

Please note the second part of the sentence that you quoted:

[…] and instead accumulate/assign each node’s .value as you please.

node.sum() recurses through all children of a hierarchy and adds the sum of their values to the parent node’s value.

d3.partition merely requires that a node’s value isn’t smaller than the sum value of its direct children. Wether you let d3 handle that by calling .sum(), or sum up the values yourself, that’s up to you. :slight_smile: You can base your adaption on the original implementation.

That is exactly the problem :slight_smile:
In graph-like data like ours, the value of a node IS smaller than the sum of its children, cf :

I haven’t find this hypothesis in the documentation yet, do you have a reference for that please ?

(BTW, I wasn’t doubting your previous answer or cutting the sentence to undermine it. I was asking if the quote from the documentation is wrong / ambiguous, i.e. it’s false that you MUST call sum(). I wouldn’t bite the hand trying to help :wink: )

I already did that : https://observablehq.com/@ilogre/zoomable-treemap
But the problem is the very same here. The value of a parent node is wrong (calculated by adding its own value to the sum of it’s children).
I attached the sunburst as an example because it’s easier to see the issue visually, because of the white voids.

I will say it again: I am NOT saying there is a bug, nor that the current behavior is wrong.
Clearly, I am trying to visualize data which should not be visualize that way to push the limit and try things :wink:

So the answer could very well be : It’s not possible.

Oh my, I didn’t want to imply that! I merely thought that you had accidentally read past that part, hence me putting emphasis there. :slight_smile:

Think you could draw up a sketch of what you imagine it should look like?

The sunburst should look like this one: https://observablehq.com/@ilogre/sunburst
And the treemap looks the way it is supposed to.

The problem with these two is not visible at the first glance, but when you use the mouse to hover a node (for example Hierarchy), the value you see is wrong (according to the data semantics, it’s “right” according to the partition logic).
The difference between the sunburst in my first message and this one is very simple : in the first one, ALL nodes have a value in the json file, their proper value. In this one, I removed the parent values to let Partition.sum do it’s usual job, rendering a visually satisfying widget, but essentially wrong.

I may have found a solution, not the cleanest way to do it semantically speaking, but it seems to be working.
I calculate the value of the children as a proportional ratio of the value of their parent, that way I ensure that the value of a node IS the sum of its children.
But I add a new attribute in my data, representing the real “value” semantically speaking (i.e. here the number of satisfied widgets) and I display this attribute instead of value for the tooltip.

I’ll develop a script to calculate all my data accordingly Monday and share the updated Observable of the TreeMap and the Sunburst here, if it helps @aaronkyle :wink:

1 Like

Meanwhile I’ve been trying to get a better grip on your problem. Here’s a d3 force graph with selectable/deselectable nodes:

What I’ve figured so far is that you’re trying to filter down a graph, showing only nodes that share edges with all previously selected nodes.

It would probably help a lot if you could describe the utility of your mode of selection in the sunburst. Right now I can’t quite picture how/why/when I would use it this way. Also, where does the link value come into play? So, I’ve now seen the KZS Lab Showcase, reread your intro in that context, and I probably understand things a bit better now: So basically you want to visualize how requiring a visualization to support various insight properties reduces the space of available visualization strategies?

1 Like

All of this open dialogue is exceptionally helpful! Thank you!

This is a fantastic thread. :slight_smile:

That’s exactly it. The KZS Showcase UI shows one “classical” way to display a configurator (only with insights at the moment), and my current goal is to provide several different visualizations to show other viewpoints on our visualization catalog.

Thanks for you graph suggestion ! I developed this one earlier displaying relations between widgets and insights with an interaction through hovering a node: https://observablehq.com/@ilogre/force-directed-graph
But I like your interaction by selecting insights. I’ll have to think of a way to merge both data types (insights x widgets and insights x insights) to combine both interactions.

I’m just afraid it would be too much of a mess to display ALL relations. Maybe by defining links between insights in the data but not displaying them, and using these links to hide the relevant insights when the user select one (like in your graph) ?

I updated the Sunburst to display everything correctly.
Now the angle of a node is calculated properly, not by the sum of its children, which allows nodes to have a value inferior to the sum of its children :slight_smile:
Plus, by passing your cursor on a node, you will have the true number of widgets satisfying the corresponding insight selection.
Right now a selection is only 1 or 2 insights, but we will calculate the 3rd and other layers later :wink:

Here is our corrected Sunburst. I’ll post the other just after this message (limit of 2 links for new members…)

Thank you for your help !

1 Like

Here are our TreeMap and CirclePacking formatted in the same way.

1 Like

That’s probably what I would have done as well after I understood what the data is about. In its current form the utility of my graph is rather non-existant. :slight_smile:

That is actually what I’m still struggling with: Can you give some examples of viewpoints that are missing? E.g., do you want to highlight bottlenecks where, for a subset of insights, the available visualization techniques are limited and can be expanded? Are you trying to find specific insights that are largely incompatible (i.e., cannot be derived from a single chart)?

So you’re with KZS Labs? Does that also mean that you could use the insight and widget icons in your notebooks?

1 Like