I'm trying to get plotly
to put values in scientific notation regardless of their size, i.e. 100 should be 1E02 in the ticks, but it keeps showing numbers below 10.000 as normal annotation.
Setting the format is done through exponentformat = "E"
"but it only affects larger numbers.
Here is an example code of how I write it:
f2 <- list(family = "Old Standard TT, serif", size = 14, color = "black")
ax <- list(showticklabels = TRUE, tickfont = f2, showgrid=F, zeroline=T, showline=T, nticks = 4, exponentformat = "E")
ay <- list(nticks = 4, showticklabels = TRUE, tickfont = f2, showgrid=F, zeroline=T, showline=T, range =c(0,max(mtcars$disp*1.2)), exponentformat = "E")
plot_ly(x = mtcars$mpg , y = mtcars$disp) %>%
add_trace(type = 'scatter', mode = 'markers',
marker = list(color = c('black'))) %>%
add_lines(hoverinfo='none', line = list(color = 'black')) %>%
layout(title = 'A plot in science',yaxis = ay, xaxis = ax,
showlegend = FALSE, hovermode = "y")
manipulating the values to be in the 10k plus range gives the desired output though:
mtcars$disp <- mtcars$disp *100
Let's just do it ourselves in JavaScript, if Plotly doesn't provide the needed functionality.
let's grab all ticks on the y-axis using d3
ticks = Plotly.d3.selectAll('g.ytick');
the raw data is stored in
data.x
then change the representation of each one to scientific notation
Plotly.d3 .selectAll('g.ytick') .each(function(data, i) { Plotly.d3.select(this) .select('text') .html(formatNumber(data.x, 2)); })
finally inject all the code using
htmlwidgets
in our graphp <- onRender(p, javascript)
now it would be one-time only change, every time a user zooms or modifies the plot the changes would be lost. In order to make sure that changes are applied every time the code is wrapped in a function
fix_ticks()
and added to Plotly'splotly_afterplot
event (el
is thehtmlwidget
element)el.on('plotly_afterplot', fix_ticks);
Update
If you want to change the format of the scientific notation, you could write your function, e.g.
function formatNumber(num, desiredLength)
{
num = num.toExponential().toUpperCase();
var r = /(\\d*)([E][-+])(\\d*)/;
var fields = r.exec(num);
if (fields !== null && fields.length > 3)
{
return fields[1] + fields[2] + fields[3].padStart(desiredLength, '0');
}
else
{
return num;
}
}
and then call it for each tick
ticks.forEach(function(tick)
{
var num = parseInt(tick[0].innerHTML);
tick[0].innerHTML = formatNumber(num, 2);
})
Note: this might not work in RStudio but shows up correctly in your browser after saving the output.
Complete code
library(plotly)
library(htmlwidgets)
p <- plot_ly(x = mtcars$mpg , y = mtcars$disp) %>%
add_lines()
javascript <- "
function(el, x)
{
function fixTicks()
{
Plotly.d3
.selectAll('g.ytick')
.each(function(data, i)
{
Plotly.d3.select(this)
.select('text')
.html(formatNumber(data.x, 2));
})
}
function formatNumber(num, desiredLength)
{
num = num.toExponential().toUpperCase();
var r = /(\\d*)([E][-+])(\\d*)/;
var fields = r.exec(num);
if (fields !== null && fields.length > 3)
{
return fields[1] + fields[2] + fields[3].padStart(desiredLength, '0');
}
else
{
return num;
}
}
el.on('plotly_afterplot', fixTicks);
}"
p <- onRender(p, javascript)
p
Particularly aimed at plots where log scale is used (which seems to cause problems with the current javascript solution), I found another solution without using javascript
. It works based on making a list of tickvalues and one of text labels at the whole exponent numbers and leaving the rest empty, and then inserting the two into the plot through the layout
arguments for tickvals
and ticktext
arguments
depending on whether it is a regular scatter
or scatter3d
the layout code changes a bit, but the principle is the same.
In scatter3d
the axes are set within the scene = list()
argument. in scatter
it is done directly in layout()
. camera
, autosize
etc are arguments used to make the plots nice and square, and for 3D at the right zoom level, and of a fixed size.
The answer is based on another SO post found: here
library(shiny)
library(plotly)
shinyApp(
ui = fluidPage( plotlyOutput('plot') ),
server = function(input, output) {
output$plot <- renderPlotly ({
mtcars <- rbind(mtcars, mtcars*1000, mtcars/1000) #create data with big logarithmic range
maxlog <- round(log10(max(mtcars[['mpg']][mtcars[['mpg']]>0], mtcars[['disp']][mtcars[['disp']]>0],mtcars[['cyl']][mtcars[['cyl']]>0])), digits = 0) +1 # determine max log needed
minlog <- round(log10(min(mtcars[['mpg']][mtcars[['mpg']]>0], mtcars[['disp']][mtcars[['disp']]>0],mtcars[['cyl']][mtcars[['cyl']]>0])), digits = 0) -1 # determine min log needed
logrange <- (maxlog - minlog)*9 +1 # get the distance between smallest and largest log power
tval <- sort(as.vector(sapply(seq(1,9), function(x) x*10^seq(minlog, maxlog)))) #generates a sequence of numbers in logarithmic divisions
ttxt <- rep("",length(tval)) # no label at most of the ticks
ttxt[seq(1,logrange,9)] <- formatC(tval, format = "e", digits = 2)[seq(1,logrange,9)] # every 9th tick is labelled
p <- plot_ly(source = 'ThresholdScatter')
p <- add_trace(p, data = mtcars,
x = mtcars[['mpg']],
y = mtcars[['disp']],
z = mtcars[['cyl']],
type = 'scatter3d',
mode = 'markers',
marker = list(size = 2))
p <- layout(p, autosize = F, width = 500, height = 500,
scene = list(yaxis = list(type="log",
zeroline=F, showline=T,
ticks="outside",
tickvals=tval,
ticktext=ttxt),
xaxis = list(type="log",
zeroline=F, showline=T,
ticks="outside",
tickvals=tval,
ticktext=ttxt),
zaxis = list(type="log",
zeroline=F, showline=T,
ticks="outside",
tickvals=tval,
ticktext=ttxt),
camera = list(eye = list(x = -1.5, y = 1.5, z = 1.5))))
})
}
)
for a 2D solution:
library(shiny)
library(plotly)
shinyApp(
ui = fluidPage( plotlyOutput('plot') ),
server = function(input, output) {
output$plot <- renderPlotly ({
mtcars <- rbind(mtcars, mtcars*1000, mtcars/1000) #create data with big logarithmic range
maxlog <- round(log10(max(mtcars[['mpg']][mtcars[['mpg']]>0], mtcars[['disp']][mtcars[['disp']]>0])), digits = 0) +1 # determine max log needed
minlog <- round(log10(min(mtcars[['mpg']][mtcars[['mpg']]>0], mtcars[['disp']][mtcars[['disp']]>0])), digits = 0) -1 # determine min log needed
logrange <- (maxlog - minlog)*9 +1 # get the distance between smallest and largest log power
tval <- sort(as.vector(sapply(seq(1,9), function(x) x*10^seq(minlog, maxlog)))) #generates a sequence of numbers in logarithmic divisions
ttxt <- rep("",length(tval)) # no label at most of the ticks
ttxt[seq(1,logrange,9)] <- formatC(tval, format = "e", digits = 2)[seq(1,logrange,9)] # every 9th tick is labelled
p <- plot_ly(source = 'ThresholdScatter')
p <- add_trace(p, data = mtcars,
x = mtcars[['mpg']],
y = mtcars[['disp']],
type = 'scatter',
mode = 'markers',
marker = list(size = 2))
p <- layout(p,autosize = F, width = 500, height = 500,
yaxis = list(type="log",
zeroline=F, showline=T,
ticks="outside",
tickvals=tval,
ticktext=ttxt),
xaxis = list(type="log",
zeroline=F, showline=T,
ticks="outside",
tickvals=tval,
ticktext=ttxt))
})
}
)
来源:https://stackoverflow.com/questions/49626679/set-major-tick-labels-to-be-displayed-as-scientific-notation-in-a-plotly-plot-in