sunburstR icon indicating copy to clipboard operation
sunburstR copied to clipboard

Small segments disappearing- crashes the legend

Open RobW101 opened this issue 6 years ago • 6 comments

Hi - first up, many thanks for the conversion to R - I've added it to a shiny app and everybody who views it loves it :-)

I have two issues however.

Issue1

My requirements are to have consistent ordering and colors for the segments, which is achievable using the parameters, unless the segment is too small. Any idea how to code around this?

Code to reproduce the error - Changing the 70000 to 70 for segment "x-1-b" causes "b" to wink out of existence on the plot (and therefore invalidate the vectors used for coloring / ordering). This causes the legend to disappear. I cannot test for this directly, as the dataframe I provide still contains the value 70 for segment "x-1-b"...

#issue where sunburst is dropping a segment if too small :
dfTest<-data.frame(
  segment=c("x-1-a","x-1-b","x-1-c","x-2-a"),
  score  =c(50000,70000,100000,40000),   #This works
  #score  =c(50000,70,100000,40000),      #This breaks it (segment "b" disappears)
  order  = c(1,2,3,4),
  stringsAsFactors = FALSE)

#Want the segment order to be consistent on the chart
vSegmentOrder<-dfTest[,"order"]   #vector of order for segments

#Want the legend order to be consistent (for the unique set of segments )
vLegendOrder<-c("x","1","2","a","b","c")

#Want the colors to be consistent (for the unique set of segments )
vColors     <-c("grey","orange","pink","red","yellow","green")

#sunburst requires the colours as a list of segment/color pairs
lColors<- list(
  domain=vLegendOrder,
  range=vColors
)

htmlwidgets::onRender(
  sunburst(
    #data=dfTest,
    data=dfTest[vSegmentOrder,],
    colors=lColors,
    legendOrder=vLegendOrder,
    withD3 = T
  ),      
  "
    function(el,x){
    d3.select(el).select('.sunburst-togglelegend').property('checked', true);
    d3.select(el).select('.sunburst-legend').style('visibility', '');
    document.getElementsByClassName('sunburst-sidebar')[0].childNodes[2].nodeValue = 'Segment';
    }
    "
)

Issue2

Not worthy of a separate topic IMHO, but while I'm here !! Can I alter the color of the percentage value in the center of the chart? I've tried to find it with the Document Inspector but failed :-/

Regards,

Rob

RobW101 avatar Apr 25 '18 11:04 RobW101

Segments too small to see are removed here, and I suspect the legend then crashes because you've added too many elements to colors and legendOrder (since one of those segments will ultimately be removed).

You could modify your code so that you don't explicitly set the color and order of the segment that will be removed (i.e. is too small to be seen) and it will work...

library(sunburstR)

dfTest <- data.frame(
  segment = c("x-1-a", "x-1-b", "x-1-c", "x-2-a"),
  score  = c(50000, 70, 100000, 40000),
  order  = c(1, 2, 3, 4),
  stringsAsFactors = FALSE
)

vSegmentOrder <- dfTest[, "order"]
vLegendOrder <- c("x", "1", "2", "a", "c")
vColors <- c("grey", "orange", "pink", "red", "green")

lColors <- list(domain = vLegendOrder,
                range = vColors)

sunburst(
  data = dfTest[vSegmentOrder, ],
  colors = lColors,
  legendOrder = vLegendOrder,
  withD3 = T
)

cjyetman avatar Apr 25 '18 12:04 cjyetman

Ta for the swift reply :-)

The problem is determining when to exclude a segment from the colors / legendOrder vectors. In my actual App the numbers of segments and their "scores" are dynamic. I would need a means to determine, for a particular refresh of the sunburst data, whether any of the segments are too small to plot prior to making the call.

Are you recommending I calculate the excluded segments and modify my vectors accordingly, based upon the snippet of code you highlighted? return (d.x1 - d.x0 > 0.005); // 0.005 radians = 0.29 degrees

RobW101 avatar Apr 25 '18 12:04 RobW101

Are you recommending I calculate the excluded segments and modify my vectors accordingly, based upon the snippet of code you highlighted? return (d.x1 - d.x0 > 0.005); // 0.005 radians = 0.29 degrees

I haven't tried, but yes, that would seem to work.

FYI... @timelyportfolio may want to deal with this is a different way, I'm just throwing in my 2 cents

cjyetman avatar Apr 25 '18 13:04 cjyetman

OK - with a fair amount of faff, I think I have this working as required... Thanks to your pointer to the cut-off point, I've added a (slightly conservative) threshold to the sunburst data input. Essentially I'm pre-filtering my data to remove any segments < 0.005 radians.. 0.005 radians = 0.2864788 degrees proportion of circle : 0.0.2864788 / 360 = 0.000796 rounding the above to 0.0008 - allowing a small margin of error.

The relevant lines of code:

#work out which segments are too small for sunburst to plot
dfTestIn$proportion<-dfTestIn$score/sum(dfTestIn$score)

#new dataframe, with the small segments excluded
dfTestTemp<-dfTestIn[(dfTestIn$proportion)>0.0008,]

However, IMHO I shouldn't have to do this workaround. In my app I would prefer all segments from the input dataset to appear in the legend (to reflect my user's selection), but any segments too small just don't appear in the sunburst itself.

And for reference, the full code demonstrating the resolved issue.. The legend no longer breaks if a segment is too small :

#coding around issue where sunburst is dropping a segment if too small
#this broke the legend (when applying order and color)
dfTestIn<-data.frame(
  param1 =c("x","x","x","x"),
  param2 = c("1","1","1","2"),
  param3 = c("a","b","c","a"),
  segment=c("x-1-a","x-1-b","x-1-c","x-2-a"),   
  #score  =c(50000,7000,100000,40000),          #This works
  score  =c(50000,70,100000,40000),             #This no longer breaks the legend (segment "b" is pre-filtered)
  order  = c(1,2,3,4),
  stringsAsFactors = FALSE)

#work out which segments are too small for sunburst to plot
dfTestIn$proportion<-dfTestIn$score/sum(dfTestIn$score)

#new dataframe, with the small segments excluded
dfTestTemp<-dfTestIn[(dfTestIn$proportion)>0.0008,]    #0.005 radian cut-off expressed as a proportion of 360degrees

#Update the segment ordering to reflect excluded segments
dfTestTemp$rank  <- rank(dfTestTemp$order)
vSegmentOrder<-dfTestTemp[,"rank"]

#Want the legend order to be consistent (for the unique set of segments )
vLegendOrder<-c(unique(dfTestTemp[,"param1"]),
                unique(dfTestTemp[,"param2"]),
                unique(dfTestTemp[,"param3"])
)

#Colors 
#lookup function for colours (only required once at top of code)..
fSB_Colors <- function(x) {
  switch(x,
         'x'  ='#a6dfe0',  '1'  ='#ff5151','2' = '#817cff',
         'a' = '#3fb56a', 'b' = '#ffdddd', 'c' = '#899fff',
         'Blue')
}

#looking up the colors (for the unique set of segments )
vColors<-unname(unlist(sapply(vLegendOrder, fSB_Colors)))

#sunburst requires the colours as a list of segment/color pairs
lColors<- list(
  domain=vLegendOrder,
  range=vColors
)

#select the columns required to plot the sunburst
dfTest2Plot<-dfTestTemp[,c("segment","score")]

#Plot the thing!
htmlwidgets::onRender(
  sunburst(
    #data=dfTest2Plot,
    data=dfTest2Plot[vSegmentOrder,],  #TODO
    colors=lColors,
    legendOrder=vLegendOrder,
    withD3 = T
  ),      
  "
    function(el,x){
    d3.select(el).select('.sunburst-togglelegend').property('checked', true);
    d3.select(el).select('.sunburst-legend').style('visibility', '');
    document.getElementsByClassName('sunburst-sidebar')[0].childNodes[2].nodeValue = 'Segment';
    }
    "
)

Thanks for the guidance cjyetman :-)

RobW101 avatar Apr 25 '18 16:04 RobW101

@RobW101, glad you found a solution and thanks to @cjyetman. I have never liked the automatic removal of small slices, since the surprise is most likely unpleasant. I plan to remove in a future version.

timelyportfolio avatar May 12 '18 12:05 timelyportfolio

Just stumbled over this "feature".. dropping the automatic removal of small slices would be great!

ismirsehregal avatar Oct 24 '18 12:10 ismirsehregal