Reputation: 2899
I'm trying to use plotlyproxy
to change the color
of a trace
, which works,
but the problem is, is that it also alters the size of my markers / legendmarkers
.
A long time ago I found out that there is (as far as my current research shows) still no way to separately set the size of legend markers to be different than the plot marker.
A disaster if you want to plot 5000 points in a scatter plot if you ask me as you end up with either tiny legends or giant plot markers.
So the question is an A or B solution type:
A: find a way to use plotlyproxy
without changing my legend
marker
size
or
B: find a way to size the legend
separately in a way that is not affected when plotlyproxy
fires
I welcome any feedback from people who have knowledge on this legend size issue.
note: possibly this can be done with javascript, but if in that case, I might need to provide more info about the actual app I'm working on to get that to work
here is the dummy app to show it:
library(plotly)
library(shiny)
library(htmlwidgets)
library(colourpicker)
ui <- fluidPage(
fluidRow(
column(8,
plotlyOutput("plot1")
),
column(2,
colourpicker::colourInput(inputId = 'markercolor', label = 'X',
palette = "limited",
showColour = "background", returnName = TRUE),
selectInput(inputId = 'traceNo', label = 'Trace', choices = c(1:3), selected = 1),
br(),
h5('Switch'),
actionButton(inputId = 'Switch', label = icon('refresh'), style = "color: #f7ad6e; background-color: white; border-color: #f7ad6e;
height: 40px; width: 40px; border-radius: 6px; border-width: 2px; text-align: center; line-height: 50%; padding: 0px; display:block; margin: 2px")
)
)
)
server <- function(input, output, session) {
# values <- reactiveValues()
observeEvent(input$Switch, {
plotlyProxy("plot1", session) %>%
plotlyProxyInvoke("restyle", list(marker = list(color = input$markercolor)), list(as.numeric(input$traceNo)-1))
})
output$plot1 <- renderPlotly({
markersize <- 4
markerlegendsize <- 20
colors <- c('red', 'blue', 'black')
p1 <- plot_ly()
p1 <- add_trace(p1, data = mtcars, x = ~disp, y = ~mpg, type = 'scatter', mode = 'markers', color = ~as.factor(cyl), colors = colors)
p1 <- layout(p1, title = 'mtcars group by cyl with switching colors')
p1 <- plotly_build(p1)
## this is a bit of a hack to change the size of the legend markers to not be equal to the plot marker size.
## it makes a list of 1 size value for each marker in de trace in the plot, and another half of with sizes that are a lot bigger.
## the legend marker size is effectively the average size of all markers of a trace
for(i in seq(1, length(sort(unique(mtcars$cyl) )))) {
length.group <- nrow(mtcars[which(mtcars$cyl == sort(unique(mtcars$cyl))[i]), ])
p1$x$data[[i]]$marker$size <- c(rep(markersize,length.group), rep(c(-markersize+2*markerlegendsize), length.group))
}
p1
})
}
shinyApp(ui, server)
Upvotes: 3
Views: 1750
Reputation: 2835
You can inject custon javascript code using shinyJS. Here, i use some d3 to select the legend items and change their size. It is very hacky but unfortunatly, as far as i know, plotly does not provide an internal solution.
library(plotly)
library(shiny)
library(htmlwidgets)
library(colourpicker)
library(shinyjs)
jsCode <- "shinyjs.changelegend = function(){
var paths = d3.select('#plot1').
select('.legend').
select('.scrollbox').
selectAll('.traces').
select('.scatterpts')
.attr('d','M8,0A8,8 0 1,1 0,-8A8,8 0 0,1 8,0Z');}"
ui <- fluidPage(
tags$script(src = "https://d3js.org/d3.v4.min.js"),
useShinyjs(),
extendShinyjs(text = jsCode),
fluidRow(
column(8,
plotlyOutput("plot1")
),
column(2,
colourpicker::colourInput(inputId = 'markercolor', label = 'X',
palette = "limited",
showColour = "background", returnName = TRUE),
selectInput(inputId = 'traceNo', label = 'Trace', choices = c(1:3), selected = 1),
br(),
h5('Switch'),
actionButton(inputId = 'Switch', label = icon('refresh'), style = "color: #f7ad6e; background-color: white; border-color: #f7ad6e;
height: 40px; width: 40px; border-radius: 6px; border-width: 2px; text-align: center; line-height: 50%; padding: 0px; display:block; margin: 2px")
)
),
tags$div(id = "test")
)
server <- function(input, output, session) {
# values <- reactiveValues()
observeEvent(input$Switch, {
plotlyProxy("plot1", session) %>%
plotlyProxyInvoke("restyle", list(marker = list(color = input$markercolor)), list(as.numeric(input$traceNo)-1))
})
observeEvent(input$Switch,{
js$changelegend()
})
output$plot1 <- renderPlotly({
markersize <- 4
markerlegendsize <- 20
colors <- c('red', 'blue', 'black')
p1 <- plot_ly()
p1 <- add_trace(p1, data = mtcars, x = ~disp, y = ~mpg, type = 'scatter', mode = 'markers', color = ~as.factor(cyl), colors = colors)
p1 <- layout(p1, title = 'mtcars group by cyl with switching colors')
p1 <- plotly_build(p1)
# this is a bit of a hack to change the size of the legend markers to not be equal to the plot marker size.
# it makes a list of 1 size value for each marker in de trace in the plot, and another half of with sizes that are a lot bigger.
# the legend marker size is effectively the average size of all markers of a trace
for(i in seq(1, length(sort(unique(mtcars$cyl) )))) {
length.group <- nrow(mtcars[which(mtcars$cyl == sort(unique(mtcars$cyl))[i]), ])
p1$x$data[[i]]$marker$size <- c(rep(markersize,length.group), rep(c(-markersize+2*markerlegendsize), length.group))
}
return(p1)
})
}
shinyApp(ui, server)
The custom javascript code is defined in jsCode
which is initialized in extendShinyjs()
. Finally, it is invoked in js$changelegend()
whenever the button is clicked.
If you have multiple plots and you want the same behavior, you can pass the plot id as a parameter into js$changelegend()
and change the jsCode
accordingly to handle this.
Upvotes: 1