R Plotly - Assign the color of a marker by its size

Benedikt Franz

I am using plotly to create an interactive pie chart for a shiny app. The underlying data is comparable to sales data. Each observation corresponds to a sale of a product. The pie chart then sums up the sales by product category.

I am looking for a way to assign color (gradient) by size of the respective pie piece, i.e. I would like the biggest share to have the darkest and the smallest share to have the lightest color. I know how to do this manually as shown in the example. However, since these shares will change over time, I am looking for a dynamic solution.

This is my solution to a fixed color assignment by product category:

library(plotly)
library(dplyr)

Product <- c("Product1","Product1","Product1","Product2","Product2","Product2","Product3")
Value <- c(100,100,50,400,50,50,350)

df <- data.frame(Product,Value)

colors_list <- list(
  'Product2' = '#003f5c',
  'Product3' = '#444e86',
  'Product1' = '#955196')

df$color <- dplyr::recode(df$Product, !!!colors_list)


plot_ly(df, labels = ~Product, values = ~Value, type = 'pie', textinfo = 'label+percent',
        marker = list(colors = ~color,
                      line = list(color = '#FFFFFF', width = 0.5))) %>%
  layout(xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))

Here is the link to the corresponding plot

Thank you very much in advance.

Edit: The solution presented by Kat works great with the working example that I provided. However, when I apply it to my "real data" the colors get mixed up as shown below. Unfortunately, I cannot share the data as it is sensitive. The data is imported from an excel sheet and perform some manipulations to extract numeric values from a string using gsub and as.numeric(str_trim(...)). This not a lot of information, but do you have an idea what could be the issue here?

enter image description here

Kat

I tried to make this as repeatable as possible. This does assume that you have only one trace and at least 1 trace (the pie plot). If so, you can you use this to identify all the colors in the gradient and assign the color map to the plot. I used the 'high' and 'low' colors you originally chose for your chart for the gradient in this function.

library(shades)
library(tidyverse)
library(plotly)

updater <- function(plt) {
  plt <- plotly_build(plt)
  if(length(plt$x$data) < 2) { # if there is only 1 trace
    df1 <- data.frame(labs = plt$x$data[[1]]$labels,
                      vals = plt$x$data[[1]]$values)
    vals <- df1 %>% group_by(labs) %>%  # find value
      summarise(perc = sum(df1$vals/sum(vals))) %>% 
      arrange(perc)            # assign colors to values/labels
    vals$colr <- gradient(c('#003f5c', '#955196'), steps = nrow(vals))
    # establish color order by data order in length of data
    df1 <- inner_join(df1, vals, by = "labs", keep = F) 
    # change the plot
    plt$x$data[[1]]$marker$colors <- df1$colr %>% unlist(use.names = F)
    plt # return the updated plot
  }
}

Here are some examples of how you might incorporate this.

plot_ly(iris, labels = ~Species, values = ~Sepal.Length, type = 'pie',
        textinfo = 'label+percent') %>% 
  updater()

enter image description here

plot_ly(diamonds, labels = ~cut, values = ~price, type = "pie",
        textinfo = 'label+percent') %>% 
  updater()

enter image description here

Now when it comes to your original plot, whether you designate color or not, this will work. (It will override whatever you designate for colors.)

plot_ly(df, labels = ~Product, values = ~Value, type = 'pie', 
        textinfo = 'label+percent',
        marker = list(colors = ~color,
                      line = list(color = '#FFFFFF', width = 0.5))) %>%
  updater() %>% 
  layout(xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))

enter image description here

You may not be aware, but all of the arguments you designated in layout are the defaults for a pie plot. Whether you include them or not, you're getting the same plot.

plot_ly(df, labels = ~Product, values = ~Value, type = 'pie', 
        textinfo = 'label+percent') %>% updater()

enter image description here

Last but not least, if you assign your plot to an object name like plt1 <- plot_ly(..., you can call this function, as well.

updater(plt1)         # an alternative
plt2 <- updater(plt1) # an alternative

------- Update --------

From your comments, @Brani brought up a great point. Perhaps you could swap your real labels for letters and share some of the values you're seeing? I'm not able to reproduce your issue, unfortunately. I'm going to guess it's either an issue with negative values, a grouping issue, or both.

Is your data clean? Is your label a factor?

In the function updater, I group by labels (labs). You could add print(df1) immediately following the creation of the vals data frame to find out what is getting sent to the creation of vals$colr.

Check out what happens with negatives.

set.seed(354)
dfn <- data.frame(letters = sample(
  rep(LETTERS[1:7], 10) %>% as.factor(), 50),
  vals = rnorm(50, 0, 10) %>% round(1))

plot_ly(dfn, labels = ~letters, values = ~vals, type = 'pie',
        textinfo = 'label+percent') %>% updater()

enter image description here

See how a slice is unlabeled? Where did E & C go? Let's take a closer look at this data.

dfn2 <- dfn %>% group_by(letters) %>% 
  summarise(vals = sum(vals))
# # A tibble: 7 × 2
#   letters  vals
#   <fct>   <dbl>
# 1 A        25  
# 2 B         6.6
# 3 C        -5  
# 4 D        47.2
# 5 E       -29.7
# 6 F         7.7
# 7 G        19.8 

The negatives are missing. That still doesn't account for why D didn't get a label.

At the beginning of this update, I said you could add print(vals) to updater. I added that call before running this last plot. This is what was printed in the console:

# A tibble: 7 × 2
  labs    perc
  <fct>  <dbl>
1 C     -14.3 
2 E      -2.41
3 D       1.52
4 A       2.86
5 G       3.62
6 F       9.30
7 B      10.8 

If I add the lowest possible value (so that the lowest value is 0), see what happens.

dfn2 <- dfn2 %>% 
  mutate(vals = vals + 29.7)

plot_ly(dfn2, labels = ~letters, values = ~vals, type = 'pie',
        textinfo = 'label+percent') %>% updater()

enter image description here

For this plot, this is what was printed in the console.

# A tibble: 7 × 2
  labs    perc
  <fct>  <dbl>
1 D       3.63
2 A       5.11
3 G       5.65
4 F       7.47
5 B       7.70
6 C      11.3 
7 E     NaN   

Since you're getting more than one slice with the same color, I'm curious about what you see if you add print.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Plotly marker size

Adding color and bubble size legend in R plotly

Issue changing marker color in R plotly scatter plot

Pick color or symbol of marker in Plotly r on a group by condition

R plotly marker/line color by value of other variable

Change color of markers of a trace in plotly with plotly proxy without changing the marker size

R plotly: Adjust absolute marker size on 3D scatterplot

Set text size within marker in r plotly bubble chart

spurious warning when mapping marker size in plotly R

Python plotly_change marker's color

Marker Color based on String Value Plotly Python

Specifying marker size in data unit for plotly

plotly-r: specifying size of marker in add_trace() reduces opacity of markers

Changing the "size" or "color" label in Plotly

color and legend in plotly in R

Remove marker border/line in scatterplot plotly R

Observation number as marker in PCA using Plotly in R

How to fix the size and the color of my custom marker

Assign color to histogram in R

Python plotly scatter chart, take marker color from dataframe

How to change color of text in plotly scatter and different marker?

How to format plotly legend when using marker color?

When update Plotly marker size, I want to preserve graph padding

plotly px.scatter_3d marker size

Change legend marker size for Plotly scatter plot (bubble chart) in Python

Color mapping not working with plotly in R

Color surface by variable with plotly in R

Assign mean marker color by hue groups for seaborn boxplot

Variable marker size feature in leaflet R