I am asking this question as an extension of a previous question. Curiosity lead me to wonder if you could modify the hovermode such that in a stacked percent chart, it will highlight the percent values of all the segments of the same color, one from each bar. Essentially the "x" hover mode in a normal chart. but since we are stacking different values on top of each other the default x mode does not seem to work intuitively this way.
Here is my code. Any help would be appreciated.
dat <- data.frame(group1=(rep(letters[1:4], each=5)),
group2=rep(LETTERS[1:5], times=4),
pct = c(rep(.2, times=5),
c(.1, .1, .1, .3, .4),
c(.2, .2, .2, .3, .1),
c(.3, .1, .2, .2, .2))
)
dat %>%
plot_ly() %>%
add_bars(x=~pct, y=~group1, color=~group2,
customdata = ~group2,
hovertemplate = paste0(
"%{y:.2f%}<br>",
"<extra>%{customdata}</extra>"
)
) %>%
layout(yaxis = list(title = 'Group 1',
autorange='reversed',
categoryorder='trace'),
xaxis = list(title = 'Percent', tickformat = ".0%"),
barmode = 'stack',
hovermode = 'y',
legend = list(traceorder = "normal")) %>%
config(displaylogo = FALSE
#,modeBarButtonsToRemove = list('hoverCompareCartesian'
)
I initially used an HTML button that can be aesthetically controlled. I've updated this answer to include that original working method and a method that uses Plotly's bottons, so that the button can be in the plot space.
I used a button to trigger this additional hovermode
. So that you can turn it off and use other hovermodes as well.
This uses the library htmlwidgets
. This was only tested using a stacked bar chart.
This includes code to fix the hovermode
when Plotly tries to make it x
(an issue with stacked bar charts compare
mode -- the premise of your previous question).
I added a lot of CSS for the button that isn't strictly necessary - if you have any questions regarding that part or anything, let me know.
dat %>%
plot_ly(height = 450) %>% # so Plotly makes room for the btn!
add_bars(x = ~pct, y = ~group1, color = ~group2,
split = ~group2, customdata = ~group2,
hovertemplate = paste0("%{x:.2f%}<br>", "<extra>%{customdata}</extra>")) %>%
layout(yaxis = list(title = 'Group 1', autorange = 'reversed', categoryorder = 'trace'),
xaxis = list(title = 'Percent', tickformat = ".0%"),
barmode = 'stack', hovermode = 'y', legend = list(traceorder = "normal")) %>%
config(displaylogo = FALSE) %>%
htmlwidgets::onRender( # <---- I'm new!
"function(el, x) {
dv = document.createElement('div'); /* create button parent */
dv.id = 'dBtn'
dv.innerHTML = '<btn id=\"hb\" >Click here to compare by color</btn>'
sty = document.createElement('style'); /* add css to btn */
sty.innerHTML = `
#hb {
padding: 15px 25px;
color: white;
font-size: 1.5em;
background-color: #2A0134;
box-shadow: 0 9px rgba(0, 0, 0, .35);
border-radius: 15px;
cursor: pointer;
}
#hb:hover {
background-color: #0b000e;
}
#hb:active {
background-color: #0b000e;
box-shadow: 0 5px #666;
transform: translateY(4px);
}
#dBtn {
position: relative;
margin: 20px;
height: 30px;
}`
document.querySelector('head').appendChild(sty);
el.insertBefore(dv, el.firstChild); /* add button to webpage */
el.on('plotly_hover', function(d) { /* when hovering, check hovermode */
if (el.layout.hovermode !== 'closest' && el.layout.hovermode !== 'y') {
Plotly.relayout(el.id, {hovermode: 'y'}) /* fix hovermode, if needed */
}
if(thatButton === true) {
tellMe = d.points[0].pointNumber; /* changes by color */
gimme = d.points[0].curveNumber; /* stays the same by color */
let xlen = el.data[0].x.length; /* number of stacked bars */
let thatsIt = [];
for(i = 0; i < xlen; i++) { /* make a hover for each bar */
thatsIt.push({curveNumber: gimme, pointNumber: i})
}
Plotly.Fx.hover(el.id, thatsIt);
}
});
el.on('plotly_unhover', function(d) { /* reset hovermode if needed */
if(thatButton === false) { /* if color compare off */
whatIs = el.layout.hovermode; /* what's current hovermode */
Plotly.relayout(el.id, {hovermode: whatIs}); /* reset hovermode to previous */
}
})
hb = document.getElementById('hb'); /* btn location */
let thatButton = false; /* btn toggle */
hb.addEventListener('click', function() { /* when btn is clicked */
if(thatButton === false) { /* if off, turn on */
hb.innerText = 'Click here to stop comparison by color'; /* indicate triggering */
thatButton = true;
Plotly.relayout(el.id, {hovermode: 'closest'}) /* switch hovermode, if needed */
} else { /* if on, turn off */
hb.innerText = 'Click here to compare by color'; /* change btn text back */
thatButton = false;
}
})
}")
Because of Plotly's inherently dynamic and interactive usage, adding pointer or click events to their event heavy space requires the use of their button.
Using the argument execute = F
in buttons, and plotly_buttonclicked
in the onRender
call, the Plotly button is used to change the hovermode
. In the onRender
you'll notice that the calls to make and style the button is gone. The content that added a click event to the button is replaced with the plotly_buttoniclicked
method.
In the plot's layout(legend = ...
I've added a call to positioning the legend. That's so that the legend and buttons don't overlap if your plot is small. Additionally, adding width =
to Plotly()
is no longer necessary.
Last thing I'll mention is regarding the button labels: " Compare by Color "
and "End Color Compare"
, the latter of which is 1 more character in length than the initial label. The whitespace is used so that both labels are centered.
dat %>%
plot_ly() %>%
add_bars(x = ~pct, y = ~group1, color = ~group2,
split = ~group2, customdata = ~group2,
hovertemplate = paste0("%{x:.2f%}<br>", "<extra>%{customdata}</extra>")) %>%
layout(yaxis = list(title = 'Group 1', autorange = 'reversed', categoryorder = 'trace'),
xaxis = list(title = 'Percent', tickformat = ".0%"),
barmode = 'stack', hovermode = 'y',
# set a legend position so buttons and legend don't overlap when you resize the graph
legend = list(traceorder = "normal", y = .75, yanchor = "bottom"), # <---- I'm new!
updatemenus = list( # using bottom (legend) and top (button) --
# so buttons won't overlap legend
list(type = "buttons", y = .7, x = 1.1, xanchor = "center", yanchor = "top", # <---- I'm new!
buttons = list(
list(label = " Compare by Color ", method = "relayout",
args = list(list(hovermode = "closest")), execute = F) # <--- I'm important
))
)) %>%
config(displaylogo = FALSE) %>%
htmlwidgets::onRender( # <---- I'm new!
"function(el, x) {
let thatButton = false; /* btn toggle */
hb = document.querySelector('[data-unformatted*=\"Color\"]'); /* get btn locale */
el.on('plotly_buttonclicked', function(d) { /* defines plotly btn*/
if(thatButton === false) { /* if off, turn on */
thatButton = true;
hb.innerHTML = 'End Color Compare'; /* indicate triggering */
Plotly.relayout(el.id, {hovermode: 'closest'}); /* switch hovermode, if needed */
} else { /* if on, turn off */
thatButton = false;
hb.innerHTML = ' Compare by Color '; /* change btn text back */
}
});
el.on('plotly_hover', function(d) { /* when hovering, check hovermode */
if (el.layout.hovermode !== 'closest' && el.layout.hovermode !== 'y') {
Plotly.relayout(el.id, {hovermode: 'y'}) /* fix hovermode, if needed */
}
if(thatButton === true) {
tellMe = d.points[0].pointNumber; /* changes by color */
gimme = d.points[0].curveNumber; /* stays the same by color */
let xlen = el.data[0].x.length; /* number of stacked bars */
let thatsIt = [];
for(i = 0; i < xlen; i++) { /* make a hover for each bar */
thatsIt.push({curveNumber: gimme, pointNumber: i})
}
Plotly.Fx.hover(el.id, thatsIt);
}
});
el.on('plotly_unhover', function(d) { /* reset hovermode if needed */
if(thatButton === false) { /* if color compare off */
whatIs = el.layout.hovermode; /* what's current hovermode */
Plotly.relayout(el.id, {hovermode: whatIs}); /* reset hovermode to previous */
}
});
}")