2

I have boxplots on highcharter and I would like to customize both the

  • Fill color
  • Border color

Here is my code

df = data.frame(cbind(categ = rep(c('a','b','c','d')),value = rnorm(1000)))

hcboxplot(var = df$categ, x = as.numeric(df$value)) %>% 
  hc_chart(type = "column")  %>% 
  hc_colors(c("#203d7d","#a0a0ed","#203d7e","#a0a0ad")) 

The hc_colors works only if I put var2 instead of var but then the box plot are shrunken...

K.Hua
  • 769
  • 4
  • 20
  • Would you also be open for other solutions than highcharter? – jay.sf Mar 13 '19 at 07:07
  • Depends on the solution, I would like a plot that draw itself when you open it like in highcharter. I already know how to do it in ggplot or plotly. – K.Hua Mar 13 '19 at 11:26

3 Answers3

1

API for styling fillColor: https://api.highcharts.com/highcharts/series.boxplot.fillColor

And for "Border color": https://api.highcharts.com/highcharts/series.boxplot.color

Pure JavaScript example of how to style and define points: https://jsfiddle.net/BlackLabel/6tud3fgx

And R code:

library(highcharter)
df = data.frame(cbind(categ = rep(c('a','b','c','d', 'e')),value = rnorm(1000)))
hcboxplot(var = df$categ, x = as.numeric(df$value)) %>% 
  hc_chart(type = "column", events = list(
    load = JS("function() {
        var chart = this;
        chart.series[0].points[2].update({
          color: 'red'
        })
        chart.series[0].points[4].update({
          x: 4,
          low: 600,
          q1: 700,
          median: 800,
          q3: 900,
          high: 1000,
          color: 'orange'
        })
      }")
  ))  %>% 
  hc_plotOptions(boxplot = list(
    fillColor = '#F0F0E0',
    lineWidth = 2,
    medianColor = '#0C5DA5',
    medianWidth = 3,
    stemColor = '#A63400',
    stemDashStyle = 'dot',
    stemWidth = 1,
    whiskerColor = '#3D9200',
    whiskerLength = '20%',
    whiskerWidth = 3,
    color = 'black'
  ))  %>% 
  hc_colors(c("#203d7d","#a0a0ed","#203d7e","#a0a0ad"))
raf18seb
  • 2,096
  • 1
  • 7
  • 19
  • From my side I indeed have the custom colors but the same for each box. – yeahman269 Aug 24 '20 at 14:20
  • My answer is just an example. I changed colors for specific points (points[2] and points[4]). Nothing stops you to change the colors for the whole series (each box) using chart.series[0].update({...}) – raf18seb Aug 25 '20 at 08:00
0

Since there's no highcharter answer yet, I give you at least a base solution.

First, your definition of the data frame is somewhat flawed, rather do:

dat <- data.frame(categ=c('a','b','c','d'), value=rnorm(1000))

Now, using boxplot is quite straightforward. border option colors your borders. With option col you also could color the fills.

boxplot(value ~ categ, dat, border=c("#203d7d","#a0a0ed","#203d7e","#a0a0ad"), pars=list(outpch=16))

Gives

enter image description here

Note: See this nice solution for further customizations.

jay.sf
  • 60,139
  • 8
  • 53
  • 110
0

I made a couple functions to do some stuff with highcharts and boxplots. It will let you color each boxplot and fill it accordingly, and then inject new graphical parameters according to the Highcharts API, should you desire.

Check it out:

## Boxplots Data and names, note the data index (0,1,2) is the first number in the datum
series<- list(
  list(
    name="a",
    data=list(c(0,1,2,3,4,5))
  ),
  list(
    name="b",
    data=list(c(1,2,3,4,5,6))

  ),
  list(
    name="c",
    data=list(c(2,3,4,5,6,7))

  )
)

# Graphical attribute to be set: fillColor.
# Make the colors for the box fill and then also the box lines (make them match so it looks pretty)
cols<- viridisLite::viridis(n= length(series2), alpha = 0.5) # Keeping alpha in here! (for box fill)
cols2<- substr(cols, 0,7) # no alpha, pure hex truth, for box lines 

gen_key_vector<-function(variable, num_times){
  return(rep(variable, num_times))
} 

kv<- gen_key_vector(variable = "fillColor", length(series)) 

# Make a function to put stuff in the 'series' list, requires seq_along to be used since x is the list/vector index tracker

add_variable_to_series_list<- function(x, series_list, key_vector, value_vector){
  base::stopifnot(length(key_vector) == length(value_vector))
  base::stopifnot(length(series_list) == length(key_vector))
  series_list[[x]][length(series_list[[x]])+1]<- value_vector[x]
  names(series_list[[x]])[length(series_list[[x]])]<- key_vector[x]
  return(series_list[[x]])
}
## Put the extra stuff in the 'series' list
series2<- lapply(seq_along(series), function(x){ add_variable_to_series_list(x = x, series_list = series, key_vector = kv, value_vector = cols) })


hc<- highcharter::highchart() %>%
  highcharter::hc_chart(type="boxplot", inverted=FALSE) %>%
  highcharter::hc_title(text="This is a title") %>%
  highcharter::hc_legend(enabled=FALSE) %>%
  highcharter::hc_xAxis(type="category", categories=c("a", "b", "c"), title=list(text="Some x-axis title")) %>%
  highcharter::hc_add_series_list(series2) %>%
  hc_plotOptions(series = list(
    marker = list(
      symbol = "circle"
    ),
    grouping=FALSE
  )) %>%
  highcharter::hc_colors(cols2) %>%
  highcharter::hc_exporting(enabled=TRUE)
hc

This probably could be adjusted to work with a simple dataframe, but I think it will get you what you want for right now without having to do too much extra work. Also, maybe look into list_parse or list_parse2' fromhighcharter...it could probably help with building out theseries` object..I still need to look into that.

Edit:

I have expanded the example to make it work with a regular DF. As per some follow up questions, the colors are set using the viridis palette inside the make_highchart_boxplot_with_colored_factors function. If you want to allow your own palette and colors, you could expose those arguments and just include them as parameters inside the function call. The expanded example borrows how to add outliers from the highcharter library (albeit in a hacky way) and then builds everything else up from scratch. Hopefully this helps clarify my previous answer. Please note, I could probably also clean up the if condition to make it a little more brief, but I kept it verbose for illustrative purposes.

Double Edit: You can now specify a vector of colors for each level of the factor variable

library(highcharter)
library(magrittr)
library(viridisLite)

df = data.frame(cbind(categ = rep(c('a','b','c','d')),value = rnorm(1000)))
df$value<- base::as.numeric(df$value)


add_variable_to_series_list<- function(x, series_list, key_vector, value_vector){
  base::stopifnot(length(key_vector) == length(value_vector))
  base::stopifnot(length(series_list) == length(key_vector))
  series_list[[x]][length(series_list[[x]])+1]<- value_vector[x]
  names(series_list[[x]])[length(series_list[[x]])]<- key_vector[x]
  return(series_list[[x]])
}


# From highcharter github pages:
hc_add_series_bwpout = function(hc, value, by, ...) {
  z = lapply(levels(by), function(x) {
    bpstats = boxplot.stats(value[by == x])$stats
    outliers = c()
    for (y in na.exclude(value[by == x])) {
      if ((y < bpstats[1]) | (y > bpstats[5]))
        outliers = c(outliers, list(which(levels(by)==x)-1, y))
    }
    outliers
  })
  hc %>%
    hc_add_series(data = z, type="scatter", ...)
}


gen_key_vector<-function(variable, num_times){
  return(rep(variable, num_times))
} 
gen_boxplot_series_from_df<- function(value, by,...){
  value<- base::as.numeric(value)
  by<- base::as.factor(by)
  box_names<- levels(by)
  z=lapply(box_names, function(x) {
    boxplot.stats(value[by==x])$stats
  })
  tmp<- lapply(seq_along(z), function(x){
    var_name_list<- list(box_names[x])
    #tmp0<- list(names(df)[x])
    names(var_name_list)<- "name"
    index<- x-1
    tmp<- list(c(index,  z[[x]]))
    tmp<- list(tmp)
    names(tmp)<- "data"
    tmp_out<- c(var_name_list, tmp)
    #tmp<- list(tmp)
    return(tmp_out)

  })
  return(tmp)
}
# Usage: 
#series<- gen_boxplot_series_from_df(value = df$total_value, by=df$asset_class)


## Boxplot function:
make_highchart_boxplot_with_colored_factors<- function(value, by, chart_title="Boxplots",
                                                       chart_x_axis_label="Values", show_outliers=FALSE,
                                                       boxcolors=NULL, box_line_colors=NULL){
  by<- as.factor(by)
  box_names_to_use<- levels(by)
  series<- gen_boxplot_series_from_df(value = value, by=by)
  if(is.null(boxcolors)){
    cols<- viridisLite::viridis(n= length(series), alpha = 0.5) # Keeping alpha in here! (COLORS FOR BOXES ARE SET HERE)
  } else {
    cols<- boxcolors
  }
  if(is.null(box_line_colors)){
    if(base::nchar(cols[[1]])==9){
      cols2<- substr(cols, 0,7) # no alpha, pure hex truth, for box lines 
    } else {
      cols2<- cols
    }

  } else {
    cols2<- box_line_colors
  }

  # Injecting value 'fillColor' into series list
  kv<- gen_key_vector(variable = "fillColor", length(series)) 
  series2<- lapply(seq_along(series), function(x){ add_variable_to_series_list(x = x, series_list = series, key_vector = kv, value_vector = cols) })

  if(show_outliers == TRUE){
    hc<- highcharter::highchart() %>%
      highcharter::hc_chart(type="boxplot", inverted=FALSE) %>%
      highcharter::hc_title(text=chart_title) %>%
      highcharter::hc_legend(enabled=FALSE) %>%
      highcharter::hc_xAxis(type="category", categories=box_names_to_use, title=list(text=chart_x_axis_label)) %>%
      highcharter::hc_add_series_list(series2) %>%
      hc_add_series_bwpout(value = value, by=by, name="Outliers") %>%
      hc_plotOptions(series = list(
        marker = list(
          symbol = "circle"
        ),
        grouping=FALSE
      )) %>%
      highcharter::hc_colors(cols2) %>%
      highcharter::hc_exporting(enabled=TRUE)

  } else{
    hc<- highcharter::highchart() %>%
      highcharter::hc_chart(type="boxplot", inverted=FALSE) %>%
      highcharter::hc_title(text=chart_title) %>%
      highcharter::hc_legend(enabled=FALSE) %>%
      highcharter::hc_xAxis(type="category", categories=box_names_to_use, title=list(text=chart_x_axis_label)) %>%
      highcharter::hc_add_series_list(series2) %>%
      hc_plotOptions(series = list(
        marker = list(
          symbol = "circle"
        ),
        grouping=FALSE
      )) %>%
      highcharter::hc_colors(cols2) %>%
      highcharter::hc_exporting(enabled=TRUE)
  }
  hc
}
# Usage:
tst_box<- make_highchart_boxplot_with_colored_factors(value = df$value, by=df$categ, chart_title = "Some Title", chart_x_axis_label = "Some X Axis", show_outliers = TRUE)
tst_box

# Custom Colors:
custom_colors_with_alpha_in_hex<- paste0(gplots::col2hex(sample(x=colors(), size = length(unique(df$categ)), replace = FALSE)), "80")
tst_box2<- make_highchart_boxplot_with_colored_factors(value = df$value, by=df$categ, chart_title = "Some Title",
                                                      chart_x_axis_label = "Some X Axis",
                                                      show_outliers = TRUE, boxcolors = custom_colors_with_alpha_in_hex)
tst_box2

tst_box3<- make_highchart_boxplot_with_colored_factors(value = df$value, by=df$categ, chart_title = "Some Title",
                                                                 chart_x_axis_label = "Some X Axis",
                                                                 show_outliers = TRUE, boxcolors = custom_colors_with_alpha_in_hex, box_line_colors = "black")
tst_box3

I hope this helps, please let me know if you have any more questions. I'm happy to try to help as best I can. -nate

nate
  • 1,172
  • 1
  • 11
  • 26
  • I got what you did for the border colors, but i don't really understand how to change the fill color, I have everything filled in black when I test your code – K.Hua Oct 16 '19 at 13:59
  • Do you have viridis installed? – nate Oct 16 '19 at 16:19
  • Yes I checked, it is installed, still filled in black :( – K.Hua Oct 16 '19 at 18:17
  • Even with the *edited* code? Also, I mis-spoke when I said to install `virids`. I should have said `viridisLite` – nate Oct 16 '19 at 21:38
  • Seems to work perfectly now, thanks you very much ! – K.Hua Oct 17 '19 at 03:19
  • Only one thing is not working though : the colour of the outliers points – K.Hua Oct 17 '19 at 03:57
  • Yeah, that is still something I am trying to work on. I spent a couple hours figuring out/experimenting with how to build out the data. I got very close, but my data ends up being quoted as characters when I try to add in different sets of outliers. I think it is a problem with the `toJSON` method I think gets used in the `htmlwidgets` package...`toJSON` will generate quoted items from a list unless they come from a `dataframe`...For this reason, I get something like `{data: [["2,10"], ["3,5"]]}` when I try to convert to proper JSON. See this JSFiddle: http://jsfiddle.net/BlackLabel/f2z9tLrs/ – nate Oct 17 '19 at 16:56