将主要刻度标签设置为在 R 中的 Plotly 图中显示为科学记数法

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

我试图让 plotly 以科学记数法表示值,而不管它们的大小如何,即 100 在刻度中应该是 1E02,但它一直显示低于 10.000 的数字作为正常注释。

设置格式是通过 exponentformat = "E"" 完成的,但它只影响较大的数字。

这是我如何编写的示例代码:

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")

将值控制在 10k 以上的范围内可以得到所需的输出:

 mtcars$disp <- mtcars$disp *100 

如果 Plotly 没有提供所需的功能,我们就在 JavaScript 中自己动手吧。

  • 让我们使用 d3

    获取 y 轴上的所有刻度
    ticks = Plotly.d3.selectAll('g.ytick');
    
  • 原始数据存储在data.x

  • 然后将每一个的表示方式改为科学计数法

    Plotly.d3
          .selectAll('g.ytick')
          .each(function(data, i) 
            {
               Plotly.d3.select(this)
                        .select('text')
                        .html(formatNumber(data.x, 2));
            }) 
    
    • 最终在我们的图表中使用 htmlwidgets 注入所有代码

      p <- onRender(p, javascript)

  • 现在这将是一次性更改,每次用户缩放或修改绘图时,更改都会丢失。为了确保每次将代码包装在函数 fix_ticks() 中并添加到 Plotly 的 plotly_afterplot 事件时应用更改(elhtmlwidget 元素)

    el.on('plotly_afterplot', fix_ticks);
    

更新

如果你想改变科学计数法的格式,你可以写你的函数,例如

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;
  }   
}

然后为每个报价单调用它

ticks.forEach(function(tick) 
{
  var num = parseInt(tick[0].innerHTML); 
  tick[0].innerHTML = formatNumber(num, 2);
})

注意:这在 RStudio 中可能不起作用,但在保存输出后会在浏览器中正确显示。


完整代码

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

特别针对使用对数刻度的地块(这似乎会导致当前 javascript 解决方案出现问题),我找到了另一个不使用 javascript 的解决方案。它的工作原理是在整个指数数字处制作一个刻度值列表和一个文本标签,并将其余部分留空,然后通过 tickvals 和 [=] 的 layout 参数将两者插入绘图中15=] 个参数

根据是正则scatter还是scatter3d布局代码略有变化,但原理是一样的。

scatter3d 中,轴在 scene = list() 参数中设置。在 scatter 中直接在 layout() 中完成。 cameraautosize 等是用于使绘图美观且方正的参数,并且对于 3D 在正确的缩放级别和固定大小。

答案基于另一个 SO post 发现: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))))
    })
  }
    )

对于 2D 解决方案:

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))
    })
  }
)