Skip to content
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

Zoomable sunburst and underlaying circle #60

Closed
homer3018 opened this issue Feb 12, 2018 · 30 comments
Closed

Zoomable sunburst and underlaying circle #60

homer3018 opened this issue Feb 12, 2018 · 30 comments

Comments

@homer3018
Copy link

Hi there,
I've been using this package for quite a bit of time now, and along with shiny and some input to manually choose which hierarchy you want to see, you can quickly explore your data and that's great.
However I still miss the zoomable feature which could allow to dig deeper in the chosen hierarchy, allowing for deeper hierarchy.

How hard would it be to add the arcTween function, as seen in action here for instance :
https://bl.ocks.org/WangQiru/effe919b93164bca9f59b73d37577ef1

I also like a lot the underlaying grey circle as seen here :
http://www.brain-map.org/api/examples/examples/sunburst/

I've managed to get it done somewhere in a modified version of sunburst.js but I could not find it. I've added things like :
var arcHighlight = d3.svg.arc() .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) .innerRadius(function(d) { return !isParent(d) ? r - outerRadius : Math.max(0, y(d.y + d.dy)); }) .outerRadius(function(d) { return r; });
and some animations as well to make it smoother with path.transition() .duration(750) in the mouseover function.

I've never been able to make the arcTween function do what it's supposed to do, but I'm not a js guru :)

Any help would be appreciated on both topics.

Keep up the good work !

@timelyportfolio
Copy link
Owner

@homer3018, thanks so much for filing the issue and using sunburstR. Always nice to hear when a project is getting used. As you said, the behavior is fairly straightforward to implement, but I will not have the bandwidth in the near term to complete. I have another option from the really wonderful d2b that almost makes me wonder if I should even spend the time.

quick example

example with shiny

I used vue because I love it, but I could have done without (see example).

Let me know what you think.

@homer3018
Copy link
Author

Hey !
The least i can say is that it looks very promising, I like the breadcrumbs component a lot which gets updated each time you zoom in or out.

I'll try and go through your examples and translate the relevant piece of code in my shiny apps to give it a try.

I understand that you're questionning yourself whether or not give an upgrade of this kind to SunburstR... I'll try it first and i might come back with further insights :)

Thanks for the quick reply ! Stay in touch.

@homer3018
Copy link
Author

homer3018 commented Feb 13, 2018

I like a lot the d2b (with vue, if it makes it easier ?) but I don't quite get how to replace my actual output$sunburst_obj<- renderSunburst({sunburst(data.r())}) and sunburstOutput("sunburst_obj", height = "600px") with the d2b/vue elements in my ui.r / server.r files.

Well actually I do get that I need to use vueOutput and renderVue functions, and vue to generate the actual plot, but this is where I'm confused and do not understand how to generate it.

How do you suggest I go about this ?

Thanks ahead of time for your support.

@timelyportfolio
Copy link
Owner

I will sketch out a htmlwidget implementation of d2b that will make incorporation in Shiny straightforward. Are you creating the sunburst in Shiny dynamically, or do you create once and then update?

@homer3018
Copy link
Author

Dynamically. my data.r() is reactive based on dates (to filter out the original very large dataset) and in my output$sunburst <- renderSunburst({ ...}) I construct my hierarchy manually each time data.r() is updated, and then construct my sunburst object with sunburst(). (basically just a collapse of a few key columns using hyphen as separator, plus some cleaning, and then pass it to table() to get frequencies).

I just need to figure out how to replace my sunburst function with vue(). Haven't had that one figured out just yet :/

I can share more code if needed.

Thank you !

@jstrin
Copy link

jstrin commented Feb 14, 2018

@timelyportfolio I was playing with the d2b sunburst example you posted and noticed that the way the data is aggregated and formatted using the treemap::treemap function, it is causing some of the data to be aggregated resulting in inaccurate counts. For example, if you take a look at the "C" nodes in the example, the raw data (from the JSON) is:

Var Value
C 3.0517
C.1 3.0517
C.1.a. 0.8251
C.1.b. 1.6427
C.1.c 0.5839

C.1.a + C.1.b + C.1.c = C.1 = C

However, the javascript appears to aggregate the data moving down the hierarchy for the sunburst. When you look at the plotted value for C it is displaying as 9; C.1 is displaying as 6. In other words -
C.1.a + C.1.b + C.1.c + C.1 = C.1'
C.1.a + C.1.b + C.1.c + C.1 + C = C'

Where C.1' and C' are the displayed values in the final visualization.

I found that aggregating my data then passing it to the treemap::treepallette function was a quick fix to this:

hier_json <- df %>%
    group_by( index1, index2, index3, index4) %>%
    summarise( size = n( ) ) %>%
    treepalette( ) %>%
    select( index1, index2, index3, index4, size, color = HCL.color) %>%
    d3_nest( value_cols = c( "size", "color"))

If you are looking for assistance putting together htmlwidgets for the d2b sunburst (and/or the d2b bubble chart) I'm happy to do what I can.

@timelyportfolio
Copy link
Owner

timelyportfolio commented Feb 14, 2018

@jstrin, thanks so much for pointing this out as I have been meaning to clarify, since this can be both confusing and misleading. Your solution is very nice, so I appreciate both taking the time to find a solution and sharing it here.

The problem arises when a tree is pre-summed (generally not the case in my experience, but definitely the case with treemap) and can be traced to this line. My proposed fix would be to only use size at the leaf level, so the sums would be recalculated at all non-leaf nodes.

var root = hierarchy(json)
        .sum(function(d) {
 		// only sum if no children (or is leaf) 
 		if(!(d.children && d.children.length > 0)) return d[x.options.valueField || "size"];
	});

This would be a potentially breaking change, so I'll let this sit for a while and seek input in case someone has an opinion. Please let me know if you see anything amiss.

For testing, we can use the following code.

library(sunburstR)
library(d3r)

df <- data.frame(
  index1 = c(rep("A",3),"B"),
  index2 = c(NA,"A.1","A.1",NA),
  index3 = c(NA, NA, "A.1.1", NA),
  size = c(5,5,5, 10),
  stringsAsFactors = FALSE
)

sunburst(
  d3_nest(df, value_cols="size"),
  count = TRUE
)

... and should expect

image

This would be much easier if we can assume all data passed to sunburst is pre-summed, but I think case this does not fit reality, so I prefer the above approach.

@timelyportfolio
Copy link
Owner

More for testing...

library(sunburstR)
library(d3r)
library(treemap)
library(dplyr)


df <- data.frame(
  index1 = c(rep("A",3),"B"),
  index2 = c(NA,"A.1","A.1",NA),
  index3 = c(NA, NA, "A.1.1", NA),
  size = c(5,5,5, 10),
  stringsAsFactors = FALSE
)

sunburst(
  d3_nest(df, value_cols="size"),
  count = TRUE
)


rhd <- random.hierarchical.data()

sunburst(
  d3_nest(rhd, value_cols="x"),
  valueField = "x"
)

tm <- treemap(
  rhd,
  index = paste0("index", 1:3),
  vSize = "x"
)$tm


sunburst(
  tm %>%
    select(index1, index2, index3, vSize) %>%
    d3_nest(value_cols = "vSize"),
  valueField = "vSize"
)

@homer3018
Copy link
Author

I've noticed this as well and wondered why though it does not affect since i do not use any of the hierarchy function and generate it myself.

@timelyportfolio in your last example all I'd like to do after all is use vue instead of sunburstR::sunburst(). Then to make it work within a Shiny app all I'd need to do would be to add renderVue and vueOutput.

Cheers guys,

@timelyportfolio
Copy link
Owner

@homer3018, so are you saying you got it to work? or are you saying a d2b implementation will do what you want?

@homer3018
Copy link
Author

@timelyportfolio I haven't gotten it to work just yet no, but yes a D2B implemantation is exactly what I need. It'd allow me to plot the stuff i need in a simple R script, and using renderVue and vueOutput will allow me to use it through a Shiny app.

I just don't know how to use vue() in a simple R script... At the end I'm just looking at having the same result obtained from sunburst(), but with zooming ability and nicer breadcrumbs.

@timelyportfolio
Copy link
Owner

@jstrin b8a18d1 implements the proposed change. Would you mind testing with a couple trees on your side to see if it works as expected?

@homer3018
Copy link
Author

In more details, I've got this in my ui.R :

output$sunburst_obj<- renderSunburst({
   sunburst(data.r())
})

and in my server.R :

sunburstOutput("sunburst_obj")

From my understanding, it now needs to be
ui.R :

output$sunburst_obj<- renderVue({
   vue(data.r())
})

and server.R :

vueOutput("sunburst_obj")

It is the vue(data.r()) part that I can't manage to get it to plot something using my data. Hope it's clearer now :)

@timelyportfolio
Copy link
Owner

timelyportfolio commented Feb 20, 2018

@homer3018, how much are you customizing the sunburst? I am trying to decide how much customization I need to provide for what is now a barely working d2b htmlwidget. If you feel like living on the edge and want to try it, then

devtools::install_github("timelyportfolio/sunburstR@d2b")

sequences <- read.csv(
  system.file("examples/visit-sequences.csv",package="sunburstR")
  ,header = FALSE
  ,stringsAsFactors = FALSE
)[1:300,]

sunburst(sequences)
sund2b(sequences)

@homer3018
Copy link
Author

@timelyportfolio Sorry for the late reply. Pretty busy these days...
Anyhow I tried the previous code, and can't find function sund2b but instead I've got d2bsun... Maybe you adjusted things meanwhile ?
It does not crash but provide an empty white frame, while sunburst() still works the same.

Let's make this piece of code working first before talking customization.

Thanks for the hard work !

@timelyportfolio
Copy link
Owner

@homer3018 sorry I did not push the most recent commit. I will do tonight.

@jstrin
Copy link

jstrin commented Feb 22, 2018

@timelyportfolio sorry for the delayed response. I ran through a handful of tests including using data with truncated paths (e.g., NA values for one of the indices) and it looks good on my end. I'll re-run my test scripts after you push the updated d2b code.

@timelyportfolio
Copy link
Owner

@jstrin, @homer3018 I pushed the very rough first draft prototype of sund2b. Let me know if it works for you, and then we can start working on a full set of arguments and customizations.

@homer3018
Copy link
Author

Huge thank you @timelyportfolio !! Just what I was chasing around ! The zoom is working just fine, and I like a lot better this breadcrumb.

As for arguments and customizations maybe the colors arrangements can be a little better e.g. for a blue category make all the children kinda blue as well ?
Not sure how difficult this is, but I've seen it before : http://www.brain-map.org/api/examples/examples/sunburst/index.html
The code is available as well.

I must admit that I also like in this example the underlaying grey circle, bigger than the outside diameter which make it easier to grasp (from my perspective at least) how much weight this or that category has. The counterpart of this is they have disabled the greyed parts that or not being hovered. Maybe this can be an option ? Also I think they've set the inner diameter to 0, which makes the root a small circle in the middle. But that's all aesthetic stuff. I think really the color management should be a bit better instead of random (is it ?)

@homer3018
Copy link
Author

@timelyportfolio I just tested to put the sund2b object in a shiny app using renderSund2b() and sund2bOutput() but it does not work :

Warning: Error in structure: object 'd2bOutput' not found
Stack trace (innermost first):
    46: structure
    45: shiny::markRenderFunction
    44: htmlwidgets::shinyRenderWidget
    43: renderSund2b
    42: server [C:\R\WorkingTemp\BAP\R-Scipts\Shiny_BAP/server.R#185]
     1: runApp
Error in structure(renderFunc, class = c("shiny.render.function", "function"),  : 
  object 'd2bOutput' not found

@timelyportfolio
Copy link
Owner

@homer3018 Shiny should now be fixed. I changed color default to d3.schemeCategory20 to match sunburst, and I also added a color argument similar to sunburst() for complete customization. Will you install newest from Github and let me know how it works? Some examples are in ?sund2b.

@homer3018
Copy link
Author

That's great, thank you very much ! I'll try and test it out later today and of course I'll come back here with feedback :)

@jstrin
Copy link

jstrin commented Feb 23, 2018

@timelyportfolio I ran through a handful of different data structures both pre-summed and and and it looks good to me

@homer3018
Copy link
Author

@timelyportfolio Sorry again for the late response.
Shiny works fine indeed except a small icon centering issue within the breadcrumbs : there are two icons displayed, small house for Home and a pointed finger that do not appear to be centered for some reason.

See the screenshot here : https://imgur.com/a/vKhix

@homer3018
Copy link
Author

And said icons do not appear when I use it outside shiny e.g. using sund2b()

@timelyportfolio
Copy link
Owner

@homer3018 I see it now, and I am glad that you found that. The issue is that d2b requires font-family: FontAwesome for the icons. I would rather not add a FontAwesome dependency, so I'm not sure how to handle at this point. Icons appear in your Shiny app since you are including bootstrap and FontAwesome. I'll try to think of a solution or show how to fix if a user would like to add the dependency themselves.

As to why the icons aren't centered in Shiny, bootstrap css conflicts with ::after box-sizing.

image

I have added css to override.

@homer3018
Copy link
Author

@timelyportfolio sorry again for the delayed response. Somehow the css does not override and I'm still getting an offset for the icons.

Other than that everything is fine with me as I don't bother not getting the icons when not using Shiny.

Thanks a lot

@homer3018
Copy link
Author

@timelyportfolio hey, did you actually go ahead and deploy this code or is this just a dev version ? In other words will i get the function sund2b() if I run install.packages("sunburstR) ?

The icon thing is not too much of an issue although it would be nice.

@timelyportfolio
Copy link
Owner

timelyportfolio commented Mar 22, 2018

@homer3018, yes this is on CRAN now. I will warn that currently d2b requires that children sum to parents, or restated the sum of values in parent should be entirely accounted for by children. Something like #60 (comment) will not work correctly with d2b. Unfortunately there is no easy way around this.

@homer3018
Copy link
Author

Happy enough with the results. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants