I have a horizontal legend in a Plotly plot, however I'd like to reduce the amount of spacing between each symbol/label to preserve space. I've tried a number of options both in ggplot (including legend.spacing.x, and legend.key.width, but these aren't preserved when I convert the plot to a Plotly) and in Plotly (including the itemmargin, spacing, tracegroupgap layout settings - these only seem to work for vertical legends).
And my code to reproduce that plot:
p <- ggplot(mtcars, aes(x = mpg, y = hp, color = as.factor(cyl))) +
scale_color_discrete(name = '') +
geom_point()
ggplotly(p) %>%
layout(legend = list(orientation = "h", y = 1.15, x = 0))
Any other ideas? Thanks!
To add a dependency in Shiny, you need to both declare the dependency in the ui
and modify the dependency in the plot.
BTW: In this update, I combined methods 1 and 2 from the original answer. (All of the original content is still in this answer, just below the update.)
There is a call for htmlDependency()
. That is added to the ui
.
The function fixDep
is unchanged from my original answer.
library(htmltools) # only for htmlDependency()
library(tidyverse)
library(plotly)
library(shiny)
# update the Plotly dependency (UI and plot)
# this is for the UI
newDep <- htmlDependency(name = "plotly-latest",
version = "2.21.1",
src = list(href = "https://cdn.plot.ly"),
script = "plotly-2.21.0.min.js")
# this is for the plot (same as in original answer)
fixDep <- function(plt) {
# changes to dependency so that entrywidth/entrywidthmode work
plt$dependencies[[5]]$src$file = NULL
plt$dependencies[[5]]$src$href = "https://cdn.plot.ly"
plt$dependencies[[5]]$script = "plotly-2.21.0.min.js"
plt$dependencies[[5]]$local = FALSE
plt$dependencies[[5]]$package = NULL
plt
}
I added your plot two times here. That is to show you the differences whether you include the legend mods or not.
Please note that you have to change the dependency on both plots for this to work. (Even if you don't use the benefits of the updated dependency on both plots.)
I used fluidPage
, but that's not required, you can probably use any ui
formatter that works with shiny
.
# your original plot (unchanged)
p <- ggplot(mtcars, aes(x = mpg, y = hp, color = as.factor(cyl))) +
scale_color_discrete(name = '') +
geom_point()
ui <- fluidPage(
createWebDependency(newDep), # adding the dep to the UI
### the rest of your ui
plotlyOutput("fixLegendSpacing"), # added for reproducibility
plotlyOutput("originalLegend") # added for comparison
)
server <- function(input, output, session) {
output$fixLegendSpacing <- renderPlotly({
# your call for ggplotly, with JS for legend entry sizing
ggplotly(p) %>% layout(legend = list(orientation = "h", y = 1.15, x = 0,
entrywidth = 5)) %>% # <<---- I added this!
fixDep() %>%
htmlwidgets::onRender(
"function(el, x) {
what = el.querySelectorAll('text.legendtext'); /* Find all legend entries */
gimme = what[1].x.baseVal[0].value; /* Collect current px spacing */
what.forEach(function(entry) {
entry.setAttribute('x', gimme * .7) /* Modify whitespace w/in entry */
});
}")
})
output$originalLegend <- renderPlotly({
# your original call for ggplotly
ggplotly(p) %>% layout(legend = list(orientation = "h", y = 1.15, x = 0)) %>%
fixDep() # <<------ you still have to change the dep here!
})
}
shinyApp(ui, server)
Here are the plots as in this app.
The Plotly JS dependency that is used in the R Plotly package does not include the specific arguments designed for this type of modification, so it won't be an 'out-of-the-box' Plotly solution.
Here are two approaches you can use to control this spacing.
NOTE The Viewer pane will not reflect the change, you'll have to open it in your browser to see it.
To open a plot in your browser from RStudio's viewer pane, click on the rightmost icon in the menubar. (It's next to the broom.)
Here are the three legends aligned horizontally, original plot, method 1 (JS), and method 2 (updated dependency). You can see that the JS only changed within each entry, while the dependency update changed the overall size (without changing the font size).
This uses the package htmlwidgets
, but as it's one call, I've just appended the library name to the function.
This modifies the SVG using Javascript. I've used .7 or 70%. Depending on what you're looking for this is the value that will increase or decrease the white space. I've added comments in the JS to explain a bit about what's happening.
library(tidyverse)
library(plotly)
# your plot as you coded it
p <- ggplot(mtcars, aes(x = mpg, y = hp, color = as.factor(cyl))) +
scale_color_discrete(name = '') +
geom_point()
# your call for ggplotly, with JS for legend entry sizing
ggplotly(p) %>% layout(legend = list(orientation = "h", y = 1.15, x = 0)) %>%
htmlwidgets::onRender(
"function(el, x) {
what = el.querySelectorAll('text.legendtext'); /* Find all legend entries */
gimme = what[1].x.baseVal[0].value; /* Collect current px spacing */
what.forEach(function(entry) {
entry.setAttribute('x', gimme * .7) /* Modify whitespace w/in entry */
});
}")
This modifies the space for each entry, so the icon (dot, line, etc.) and the string that goes with it. This may not be particularly useful here.
The relative arguments are (along with Plotly's descriptions):
entrywidth
Code: fig.update_layout(legend_entrywidth=)
Type: number greater than or equal to 0
Sets the width (in px or fraction) of the legend. Use 0 to size the entry based on the text width, whenentrywidthmode
is set to "pixels".
entrywidthmode
Code: fig.update_layout(legend_entrywidthmode=)
Type: enumerated , one of ( "fraction" | "pixels" )
Default: "pixels"
Determines what entrywidth means.
The coding I use here won't work if you're using Shiny. (If you are, let me know I can walk you through how to do this with Shiny, as well.)
Here is a function that will modify the Plotly dependency in your plot.
fixDep <- function(plt) {
# changes to dependency so that entrywidth/entrywidthmode work
plt$dependencies[[5]]$src$file = NULL
plt$dependencies[[5]]$src$href = "https://cdn.plot.ly"
plt$dependencies[[5]]$script = "plotly-2.21.0.min.js"
plt$dependencies[[5]]$local = FALSE
plt$dependencies[[5]]$package = NULL
plt
}
library(tidyverse)
library(plotly)
fixDep <- function(plt) {
# changes to dependency so that entrywidth/entrywidthmode work
plt$dependencies[[5]]$src$file = NULL
plt$dependencies[[5]]$src$href = "https://cdn.plot.ly"
plt$dependencies[[5]]$script = "plotly-2.16.1.min.js"
plt$dependencies[[5]]$local = FALSE
plt$dependencies[[5]]$package = NULL
plt
}
# your plot as you coded it
p <- ggplot(mtcars, aes(x = mpg, y = hp, color = as.factor(cyl))) +
scale_color_discrete(name = '') +
geom_point()
# your call for ggplotly, with additional calls for legend entry sizing
ggplotly(p) %>%
layout(legend = list(orientation = "h", y = 1.15, x = 0,
entrywidth = 15)) %>% # <--- I added, using default entrywidthmode
fixDep()
If you have any questions, let me know.