Add Sunburst Chart to highcharter Package

Add Sunburst Chart to highcharter Package

The highcharter Package

The highcharter package link by Joshua Kunst has long been my favorite data visualization package in R. It created a wonderful API to the famous JS Highcharts library. link. Although Highcharts is not free for commercial use, but I found many functionality of it is unparalleled with other visualization packages like ploty or sunburstR or r2d3.

For example, you can easily create an interactive scatterplot with ggplot2 like syntax:

data(diamonds, economics_long, mpg, package = "ggplot2")
library(tidyverse)
library(highcharter)
library(widgetframe)

l <- hchart(mpg, "scatter", hcaes(x = displ, y = hwy, group = class))
frameWidget(l, height = '400')

Or to be more flexible, use the API functions to “build” the chart and fine tuning almost any specs of your charts:

hc <- highchart() %>% 
  hc_xAxis(categories = citytemp$month) %>% 
  hc_add_series(name = "Tokyo", data = citytemp$tokyo) %>% 
  hc_add_series(name = "New York", data = citytemp$new_york) %>% 
  hc_title(text = "This is a title with <i>margin</i> and <b>Strong or bold text</b>",
           margin = 20, align = "left",
           style = list(color = "#90ed7d", useHTML = TRUE)) %>% 
  hc_subtitle(text = "And this is a subtitle with more information",
              align = "left",
              style = list(color = "#2b908f", fontWeight = "bold")) %>% 
  hc_credits(enabled = TRUE, # add credits
             text = "www.link.tomy.site",
             href = "http://jkunst.com") %>% 
  hc_legend(align = "left", verticalAlign = "top",
            layout = "vertical", x = 0, y = 100) %>%
  hc_tooltip(crosshairs = TRUE, backgroundColor = "#FCFFC5",
             shared = TRUE, borderWidth = 5) %>% 
  hc_exporting(enabled = TRUE)

frameWidget(hc, height = '600')

Here. A light weighted, interactive chart with exporting ability and customized tooltip with less than 20 lines of code.

Sunburst Chart

Let’s take a look at an example of sunburst chart by official Highcharts.org (created in jsfiddle, added to this RMD document using iframe):

Sunburst chart shows hierarchy through a series of rings, that are sliced for each category node. Each ring corresponds to a level in the hierarchy, with the central circle representing the root node and the hierarchy moving outwards from it.

Rings are sliced up and divided based on their hierarchical relationship to the parent slice. The angle of each slice is either divided equally under its parent node or can be made proportional to a value.

Color can be used to highlight hierarchical groupings or specific categories.

Add Wrapper to Existing highcharter

This is very straightforward since the hc_add_series() function provides an robust interface to Highcharts series API link. Moreover, the data structure required of sunburst chart is actually same as treemap chart.

Assume we have a dataframe with 3 levels(index1, index2 and index3), each observation is also associated with an numeric value:

library(tibble)
data <- tibble(
  index0 = "Letter",
  index1 = sample(LETTERS[1:5], 500, replace = T),
  index2 = sample(LETTERS[6:10], 500, replace = T),
  index3 = sample(LETTERS[11:15], 500, replace = T),
  value = rpois(500, 5)  
)

head(data)
# A tibble: 6 x 5
  index0 index1 index2 index3 value
  <chr>  <chr>  <chr>  <chr>  <int>
1 Letter D      F      N          1
2 Letter B      J      M          5
3 Letter A      J      O          4
4 Letter D      J      N          8
5 Letter B      J      M          3
6 Letter D      G      M          8

and remember we need to parse the data into the format that Highcharts can recognized, which is a nested list:


var data = [{
    id: '0.0',
    parent: '',
    name: 'The World'
}, {
    id: '1.3',
    parent: '0.0',
    name: 'Asia'
}, {
    id: '1.1',
    parent: '0.0',
    name: 'Africa'
}, {
    id: '1.2',
    parent: '0.0'
},
...
}
    

Note that every item in the list must have an “id” and “parent”.

In hctreemap2() function, author parsed the dataframe into list nicely using purrr and rlang package from tidyverse. Do not worry if you don’t understand those “Quasiquotations” link. It is just syntax sugar using non-standard evaluation. more reference

group_vars <- c("index0","index1", "index2", "index3")
size_var <- "value"
group_syms <- rlang::syms(group_vars)
size_sym <- rlang::sym(size_var)
  
  if (data %>%
      select(!!!group_syms) %>%
      map(unique) %>%
      unlist() %>%
      anyDuplicated()) stop("Sunburst data uses same label at multiple levels.")
  
  data <- data %>% mutate_at(group_vars, as.character)
  
  name_cell <- function(..., depth) paste0(list(...), 1:depth, collapse = "")
  
  data_at_depth <- function(depth) {
    data %>%
      group_by(!!!group_syms[1:depth]) %>%
      summarise(
        value = sum(!!size_sym)
      ) %>%
      ungroup() %>%
      mutate(
        name = !!group_syms[[depth]],
        level = depth
      ) %>% 
      mutate_at(group_vars, as.character()) %>% 
      {
        if (depth == 1) mutate(., id = paste0(name, 1))
        else {
          mutate(
            .,
            parent = pmap_chr(
              list(!!!group_syms[1:depth - 1]),
              name_cell,
              depth = depth - 1),
            id = paste0(parent, name, depth)
          )
        }
      }
  }

Lets look at the dataframe before we parse it to a list:

sunburst_df <- 1:length(group_vars) %>%
  map(data_at_depth) %>%
  bind_rows() %>% 
  arrange(level)

head(sunburst_df)
# A tibble: 6 x 9
  index0 value name   level id        index1 parent  index2 index3
  <chr>  <int> <chr>  <int> <chr>     <chr>  <chr>   <chr>  <chr> 
1 Letter  2496 Letter     1 Letter1   <NA>   <NA>    <NA>   <NA>  
2 Letter   440 A          2 Letter1A2 A      Letter1 <NA>   <NA>  
3 Letter   575 B          2 Letter1B2 B      Letter1 <NA>   <NA>  
4 Letter   549 C          2 Letter1C2 C      Letter1 <NA>   <NA>  
5 Letter   477 D          2 Letter1D2 D      Letter1 <NA>   <NA>  
6 Letter   455 E          2 Letter1E2 E      Letter1 <NA>   <NA>  

Let’s take a look at the first couple of items:

data_list <- sunburst_df %>%
  highcharter::list_parse() %>%
  purrr::map(~.[!is.na(.)])


data_list[1:3]
[[1]]
[[1]]$index0
[1] "Letter"

[[1]]$value
[1] 2496

[[1]]$name
[1] "Letter"

[[1]]$level
[1] 1

[[1]]$id
[1] "Letter1"


[[2]]
[[2]]$index0
[1] "Letter"

[[2]]$value
[1] 440

[[2]]$name
[1] "A"

[[2]]$level
[1] 2

[[2]]$id
[1] "Letter1A2"

[[2]]$index1
[1] "A"

[[2]]$parent
[1] "Letter1"


[[3]]
[[3]]$index0
[1] "Letter"

[[3]]$value
[1] 575

[[3]]$name
[1] "B"

[[3]]$level
[1] 2

[[3]]$id
[1] "Letter1B2"

[[3]]$index1
[1] "B"

[[3]]$parent
[1] "Letter1"

Alright, the data has been parsed into the corresponding format. Now we just need to pass this in to highcharter::hc_add_series()

hc <- highchart() %>%
  hc_add_series(data = data_list,
                type = "sunburst",
                allowDrillToNode = TRUE)

frameWidget(hc, height = '600')

Here we go!

We are able to create the sunburst chart using Highcharts API! link to full code Of course it’s a bit ugly now because we didn’t pass any other parameters to the chart.

More Configurations

Putting everything together,let’s make the center spot transparent by adding color = 'transparent' arguments and change the sub level coloring by colorVariation = list(key = 'brightness', to = 0.5)

library(tidyverse)
library(highcharter)
library(RColorBrewer)

hc <- tibble(
 index0 = "Letter",
 index1 = sample(LETTERS[1:5], 500, replace = T),
 index2 = sample(LETTERS[6:10], 500, replace = T),
 index3 = sample(LETTERS[11:15], 500, replace = T),
 value = rpois(500, 5)  
) %>%
 hcsunburst(
   group_vars = c("index0","index1", "index2", "index3"),
   size_var = "value",
   levels = list(
     list(level = 1, color = 'transparent', dataLabels = list(enabled = TRUE)),
     list(level = 2, colorByPoint = TRUE, dataLabels = list(enabled = TRUE)),
     list(level = 3, colorVariation = list(key = 'brightness', to = 0.5),
          dataLabels = list(enabled = TRUE)),
     list(level = 4, colorVariation = list(key = 'brightness', to = -0.5),
          dataLabels = list(enabled = FALSE))
   )
 ) %>% 
 hc_tooltip(pointFormat = "<b>{point.name}</b>:<br>
            Value: {point.value:,.0f}<br>")

frameWidget(hc, height = '600')

TL;DR — You can start using this function by installing the forked version on my GitHub: devtools::install_github(repo = "wwwjk366/highcharter"). I have already initiated a pull request and hopefully the maintainer of `highcharter’ can add this function soon.

Avatar
Michael Yan
Staff Data Scientist @ SpotHero

A student of the last forbidden art of Data Science.

comments powered by Disqus