Logarithmic scaling with plotly

I want to create my own recipe for bodeplots, where both the x and y axis normally has logarithmic scaling. So I can easily create a plot with with an exponential relation:

using Plots
plotly()

xs = 0:0.001:10
ys = exp.(xs)
plot(xs, ys)

And I can get a logarithmic scaling on the y-axis by

plot(xs, ys, yaxis=:log)

However, when trying to set the scale if the x-axis or both (by setting the xaxis or axis kwargs. Replacing axis with scale does seems like it does exactly the same in all cases), I get the following error:

julia> plot(xs, ys, xaxis=:log)
┌ Warning: No strict ticks found
â”” @ PlotUtils C:\Users\densb\.julia\packages\PlotUtils\es5pb\src\ticks.jl:283
Error showing value of type Plots.Plot{Plots.PlotlyJSBackend}:
ERROR: ArgumentError: At least one finite value must be provided to formatter.
Stacktrace:
  [1] showoff(xs::Vector{Float64}, style::Symbol)
    @ Showoff ~\.julia\packages\Showoff\C1Bj4\src\Showoff.jl:111
  [2] optimal_ticks_and_labels(ticks::Nothing, alims::Tuple{Float64, Float64}, scale::Symbol, formatter::Symbol)
    @ Plots ~\.julia\packages\Plots\SVksJ\src\axes.jl:197
  [3] get_ticks(::Symbol, ::Vector{Float64}, ::Vector{Any}, ::Tuple{Float64, Float64}, ::Vararg{Any, N} where N)
    @ Plots ~\.julia\packages\Plots\SVksJ\src\axes.jl:251
  [4] get_ticks(sp::Plots.Subplot{Plots.PlotlyJSBackend}, axis::Plots.Axis; update::Bool)
    @ Plots ~\.julia\packages\Plots\SVksJ\src\axes.jl:232
  [5] get_ticks
    @ ~\.julia\packages\Plots\SVksJ\src\axes.jl:221 [inlined]
  [6] tick_padding(sp::Plots.Subplot{Plots.PlotlyJSBackend}, axis::Plots.Axis)
    @ Plots ~\.julia\packages\Plots\SVksJ\src\backends.jl:76
  [7] _update_min_padding!(sp::Plots.Subplot{Plots.PlotlyJSBackend})
    @ Plots ~\.julia\packages\Plots\SVksJ\src\backends.jl:109
  [8] iterate
    @ .\generator.jl:47 [inlined]
  [9] _collect(c::Matrix{AbstractLayout}, itr::Base.Generator{Matrix{AbstractLayout}, typeof(Plots._update_min_padding!)}, #unused#::Base.EltypeUnknown, isz::Base.HasShape{2})
    @ Base .\array.jl:691
 [10] collect_similar
    @ .\array.jl:606 [inlined]
 [11] map
    @ .\abstractarray.jl:2294 [inlined]
 [12] _update_min_padding!(layout::Plots.GridLayout)
    @ Plots ~\.julia\packages\Plots\SVksJ\src\layouts.jl:282
 [13] prepare_output(plt::Plots.Plot{Plots.PlotlyJSBackend})
    @ Plots ~\.julia\packages\Plots\SVksJ\src\plot.jl:189
 [14] display(d::VSCodeServer.InlineDisplay, m::MIME{Symbol("application/vnd.plotly.v1+json")}, x::Plots.Plot{Plots.PlotlyJSBackend})      
    @ VSCodeServer ~\.vscode\extensions\julialang.language-julia-1.1.38\scripts\packages\VSCodeServer\src\display.jl:0
 [15] display(d::VSCodeServer.InlineDisplay, mime::String, x::Any)
    @ Base.Multimedia .\multimedia.jl:216
 [16] display(d::VSCodeServer.InlineDisplay, x::Plots.Plot{Plots.PlotlyJSBackend})
    @ VSCodeServer ~\.vscode\extensions\julialang.language-julia-1.1.38\scripts\packages\VSCodeServer\src\display.jl:108
 [17] display(x::Any)
    @ Base.Multimedia .\multimedia.jl:328
 [18] #invokelatest#2
    @ .\essentials.jl:708 [inlined]
 [19] invokelatest
    @ .\essentials.jl:706 [inlined]
 [20] print_response(errio::IO, response::Any, show_value::Bool, have_color::Bool, specialdisplay::Union{Nothing, AbstractDisplay})        
    @ REPL C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\REPL\src\REPL.jl:247
 [21] (::REPL.var"#40#41"{REPL.LineEditREPL, Pair{Any, Bool}, Bool, Bool})(io::Any)
    @ REPL C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\REPL\src\REPL.jl:231
 [22] with_repl_linfo(f::Any, repl::REPL.LineEditREPL)
    @ REPL C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\REPL\src\REPL.jl:462
 [23] print_response(repl::REPL.AbstractREPL, response::Any, show_value::Bool, have_color::Bool)
    @ REPL C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\REPL\src\REPL.jl:229
 [24] (::REPL.var"#do_respond#61"{Bool, Bool, REPL.var"#72#82"{REPL.LineEditREPL, REPL.REPLHistoryProvider}, REPL.LineEditREPL, REPL.LineEdit.Prompt})(s::REPL.LineEdit.MIState, buf::Any, ok::Bool)
    @ REPL C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\REPL\src\REPL.jl:798
 [25] #invokelatest#2
    @ .\essentials.jl:708 [inlined]
 [26] invokelatest
    @ .\essentials.jl:706 [inlined]
 [27] run_interface(terminal::REPL.Terminals.TextTerminal, m::REPL.LineEdit.ModalInterface, s::REPL.LineEdit.MIState)
    @ REPL.LineEdit C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\REPL\src\LineEdit.jl:2441
 [28] run_frontend(repl::REPL.LineEditREPL, backend::REPL.REPLBackendRef)
    @ REPL C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\REPL\src\REPL.jl:1126
 [29] (::REPL.var"#44#49"{REPL.LineEditREPL, REPL.REPLBackendRef})()
    @ REPL .\task.jl:406

I don’t know what “strict ticks” means, and I don’t see why I should supply ticks.

During the time of writing this post, I solved my problem. I decided to post and answer it anyways, in case someone else googles the same problem.

The problem is that determening x-ticks is not trivial for a logarithmic scale. Normal plots often go from 0 to something, but on a logarithmic scale, there is an infinite number of logarithmically spaced ticks between 0 and 1.

My hacky solution is this:
I have an idea that most things I want to plot will be between 10^-10 and 10^20. So I define the following function:

function get_x_ticks(x_lower, x_upper)
    initial_range = -10:1.0:20
    ticks = Float64[]
    for i in initial_range
        if x_lower ≤ 10^i ≤ x_upper
            push!(ticks, i)
        end
    end
    return 10 .^ ticks
end

When plotting, I generally supply a vector (or range) of x-values, xs. So create my plot with something like

plot(xs, ys, xticks=get_x_ticks(xs[1], xs[end]), scale=:log10

In my test, the lowest x-tick was -3 where I expected -10. But as I zoom and and use the mouse-over display of data from Plotly, this solution is good enough for me.

The xlims argument seems to do the job here:

using Plots; plotly()

xs = 0:0.001:10
ys = exp.(xs)
plot(xs, ys, xlims=(1e-3, 1e1), scale=:log10)

Interesting… Any idea why the smallest and largest x-vals are not automatically taken as xlims? Or, I mean - they are in terms of the actual limits, but not when it comes to this behaviour.

Maybie a PR could explicitly supply xlims from (minimum(xs), maximum(xs) for xscale/scale/axis/xaxis = log/log10

In your example, xmin = 0, so we need to avoid log(0). Otherwise, for xmin>0, it should behave as you expect.

1 Like