-
Notifications
You must be signed in to change notification settings - Fork 315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add split treemap algorithm as tile (WIP) #72
Conversation
Well, technically my implementation is in ggraph and not ggforce but you're forgiven😊 |
@thomasp85, fixed |
This looks a lot like the binary treemap algorithm. I don’t have time at the moment to exactly describe the differences, but it looks to me like even if the output is different, we could reuse optimizations from the binary treemap implementation here. Specifically, that implementation computes the cumulative values of the nodes once (in |
I started with binary because it looked closest but then went into get-it-working mode. The sums seems like a very easy optimization, so I will try to implement after focusing on the binary implementation. |
A missing Results on a decent size random hierarchy seem to indicate no success. optimized bl.ock
non-optimized bl.ock
|
However, if we know
|
Regarding the optimization (and please take this with a grain of salt since I’ve only done a cursory look at the code and might not be understanding it correctly): I think you’ll want to avoid calling nodes.slice during each recursive call, too, and instead pass indexes i and j (representing the inclusive start and exclusive end index of the current partition of nodes), as is done in the binary implementation. Also looks like you could compute the initial sum before calling splitTree the first time, rather than checking if sum is null. |
@mbostock, is it ok to assume that all hierarchies passed to In terms of removing the Thanks! |
nodes[0].y0 = y0; | ||
nodes[0].y1 = y1; | ||
if (nodes[0].children && nodes[0].children.length > 0) { | ||
splitTree(nodes[0].children, parent.value, x0, y0, x1, y1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it assumes that parent.value is equal to the sum of the children’s values, which won’t be the case if the parent has a self-value.
return; | ||
} | ||
|
||
splitTree(parent.children, parent.value, x0, y0, x1, y1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it assumes that parent.value is equal to the sum of the children’s values, which won’t be the case if the parent has a self-value. You’ll need to sum parent.children[i].value before calling splitTree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I wondered about this assumption. It sounds like my assumption could easily be violated in which case speed improvements will be dramatically reduced :( I'll revert back to the original sum
handling.
w2 = 0; | ||
|
||
|
||
if (sum === null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would sum ever be null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I introduced the sum
argument to splitTree()
to avoid duplicating the sum calculation especially in the situation of recursing through the split list where we have already calculated the sum
in the prior iteration. However, as you said above, there are cases where we might not know the sum
in which we will call splitTree(nodes, null, ...)
and expect the sum
to be calculated.
I’m confused but it’s probably because I am giving you drive-by feedback without looking too closely at the code. Yes, node.sum must be called on root before invoking the treemap. But no, the |
n = nodes.length; | ||
|
||
for ( ; i < n; ++i) { | ||
sum += nodes[i].value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mbostock, should this sum include self value? I think no since I copied this from binary
.
I think the main thing this is missing is some optimization. I believe that this implementation would benefit from the tricks used by d3.treemapBinary, which are:
(This is the same feedback I gave you in December.) |
I feel like I still don’t have a grasp as to how this algorithm is different from the binary treemap algorithm. The implementations appear pretty similar… Can you give me a quick summary of how they differ? |
So, this does appear to produce nearly identical results to the binary treemap algorithm, except the x and y positions are swapped. I added this to your linked example to compare: data.each(function(d) {
var x0 = d.x0, y0 = d.y0, x1 = d.x1, y1 = d.y1;
d.x0 = y0, d.x1 = y1, d.y0 = x0, d.y1 = x1;
}); Still trying to understand why. |
Okay, I have verified that these two algorithms are in fact identical¹, aside from the flipped x and y! ¹ I discovered a bug in my binary treemap implementation where it uses bisection to find the split point, but sometimes it is off by one because it doesn’t test the adjacent node. The fix will be something like this in binary.js: if (Math.abs(sums[k - 1] - valueTarget) < Math.abs(sums[k] - valueTarget)) --k; |
Okay, the x and y were swapped because the binary treemap uses this test: if ((y1 - y0) > (x1 - x0)) {
// split vertically
} else {
// split horizontally
} Whereas the split treemap uses this test (rewritten slightly for parity): if ((x1 - x0) > (y1 - y0)) {
// split horizontally
} else {
// split vertically
} The difference is when x1 - x0 = y1 - y0: binary was using horizontal, split was using vertical. In our examples the layout size (and root node size) is a square 960×960. This seems like a nice improvement too since it will favor a vertical partition on square nodes. |
Closing as fixed, since these turned out to be the same as binary treemaps. TIL! |
This pull (#71) seeks to add the split algorithm as
d3.treemapSplit
.References:
ggraph
implementation by @thomasp85 https://github.com/thomasp85/ggraph/blob/faf8b2011958ecc9be098122619d946f3aed8171/src/treemap.cppExamples
http://bl.ocks.org/timelyportfolio/2bbd4cedf597eae6a5e90412d9df1c1f
http://bl.ocks.org/timelyportfolio/e480359ea953d96a38a4466187d3d62b
Todo
s(f73f12b)d3
style