2

My goal is to make a bar plot with images as axis labels, like this (screen 1). I also want plotly's chart annotations, but adding plotly to the program causes the axis labels to be interpreted as raw text instead of images (screenshot 2).

Code:

library("shiny")
library('bslib')
library("ggplot2")


library('ggtext')
library('plotly')

# Example data. The project's www/ directory contains apple.png, mangosteen.png, etc.
df <- data.frame('fruit' = c('Apple', 'Mangosteen', 'Pear', 'Banana'),
             'quantity' = c(5, 4, 1, 3))
ui <- page_fluid(plotlyOutput('groceries_plot'))

server <- function(input, output) {  
  # Add image paths that can be rendered with ggtext's markdown feature.
  df <- df %>% mutate(html_img = paste0("<img src='www/",tolower(fruit),".png'",
                                        " height = '95'><br>", fruit))

  output$groceries_plot <- renderPlotly({
    ggplot(df, aes(x = html_img, y = quantity, fill = fruit)) +
      geom_col() +
      theme_light() +
      # Should read html_img as markdown - instead gives raw text.
      theme(axis.text.x = ggtext::element_markdown())
    })
}

runApp(list(ui = ui, server = server))

It looks like plotly is interacting with ggtext's markdown(). If plotlyOutput is changed to plotOutput and renderPlotly is changed to renderPlot, I get screenshot 1, but the version above gives screenshot 2.

1
  • The way that I would approach this is to create two plots one for your bar plot and one that just shows the images and then use something like cowplot's plot_grid() while specifying the relative heights and aligning them. Commented Sep 3 at 7:11

1 Answer 1

2

ggtext::element_markdown() works with ggplot2. But if the plot is transformed to plotly if it's placed in renderPlotly(), then the markup can not be parsed by plotly so it's rendered as text. One way could be to create a native plotly barchart and add images using my answer here

Edit 1

Thanks to ismirsehregal for pointing out, that we can just directly link to the images within "www" without the need to transform them to base64.

library(shiny)
library(bslib)
library(plotly)

# create directory www with images for reproduciblity
if (!dir.exists("www")) dir.create("www")
download.file("https://purepng.com/public/uploads/large/purepng.com-red-appleapplegreenhealthycut-641522015209apibb.png", destfile = "www/apple.png", mode = 'wb')
download.file("https://purepng.com/public/uploads/large/purepng.com-single-bananabananafruityellowyummytastylight-green-331522344078czeap.png", destfile = "www/banana.png", mode = 'wb')
download.file("https://static.vecteezy.com/system/resources/previews/046/825/056/non_2x/mangosteen-isolated-on-transparent-background-png.png", destfile = "www/mangosteen.png", mode = 'wb')
download.file("https://static.vecteezy.com/system/resources/previews/029/335/116/non_2x/pear-transparent-background-png.png", destfile = "www/pear.png", mode = 'wb')

df <- data.frame(
  name = c('Apple', 'Mangosteen', 'Pear', 'Banana'),
  quantity = c(5, 4, 1, 3)
)

img_list <- lapply(seq_along(df$name), function(i, size = 0.6) {
  list(
    source = paste0("/", tolower(df$name[i]), ".png"),  
    xref = "x",       
    yref = "paper",   
    x = df$name[i],   
    y = -0.15,     # adjust y position of images   
    sizex = size,  # adjust "size" function parameter for scaling image
    sizey = size,
    xanchor = "center"
  )
})

ui <- page_fluid(plotlyOutput('groceries_plot'))

server <- function(input, output) {  
  
  output$groceries_plot <- renderPlotly({
    plot_ly(df, x = ~name, y = ~quantity, type = 'bar', color = ~name, height = 1000) |>
      layout(
        images = img_list,
        margin = list(b = 450) # adjust bottom margin to add space for images
      )
  })
}

shinyApp(ui, server) # We can set source = paste0("/", tolower(df$name[i]), ".png") as long as the app ends with shinyApp(ui, server) instead of runApp, as in this case the app is passed as a shiny app object and the www to "/" prefix mapping doesn't take place.

res Note: Plotly also allows you to use image-links as source parameter, so you don't need to store the images within "www".

Sign up to request clarification or add additional context in comments.

3 Comments

There is no need to use the base64 format. We can set source = paste0("/", tolower(df$name[i]), ".png") as long as the app ends with shinyApp(ui, server) instead of runApp. Please see this.
I see, so a ggplot passed into ggplotly is converted into a plotly object.
@M-- please see ?addResourcePath: Static files under the ⁠www/⁠ directory are automatically made available under a request path that begins with /.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.