Set major tick labels to be displayed as scientific notation in a Plotly plot in R

别来无恙 提交于 2019-12-02 04:05:19

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 graph

      p <- 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's plotly_afterplot event (el is the htmlwidget 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))
    })
  }
)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!