3

I did some workaround before to achieve wonderful subplots in Julia Plotly, but currently struggling with a more complex problem. There are three ways below that should do the job. draw1 does it perfectly, but not applicable in my situation, draw2 does not, draw3 does in the REPL but otherwise not.

Here is the expected matrix of graphs aka subplots. expected matrix of graphs

draw1 does the job -> expected matrix of graphs appears

function draw1()
    [plot([1,1,1]) plot([2,2,2]); plot([3,3,3]) plot([4,4,4])]
end

draw2a and draw2b do not, regardless of being called as a function of a module or copied into the REPL

function draw2a()
    local mx = [1 2; 3 4]
    local p(i) = plot([i,i,i])
    p.(mx)
end
function draw2b()
    local mx = [1 2; 3 4]
    local p = map(i-> plot([i,i,i]), collect(1:4))
    p[mx]
end

REPL does the same for draw2a and draw2b:

julia> subplots.draw2()
2×2 Array{PlotlyJS.SyncPlot,2}:
 SyncPlot(data: [
  "scatter with fields type, x, and y"
]
...
followed by the content of the graphs

draw3 perfectly does the job if copied into the REPL but does not if called

function draw3()
    local p(i) = plot([i,i,i])
    eval(Meta.parse("[p(1) p(2); p(3) p(4)]"))
end

if called:

julia> subplots.draw3()
ERROR: UndefVarError: p not defined

it must be a scope issue

1 Answers1

1

The fix

The following is a modification of draw2a which works (i.e. it shows the expected graph):

function draw4()
    local mx = [1 2; 3 4]
    local p(i) = plot([i,i,i])
    matrix_with_plots = p.(mx)
    hvcat((2, 2), matrix_with_plots...)
end

To understand why hvcat displays the expected graph, we have to step back and understand how SyncPlot works.

How does SyncPlot work?

Let's take the draw1 code:

[plot([1,1,1]) plot([2,2,2]); plot([3,3,3]) plot([4,4,4])]

The shell processes this code block with the following steps:

  1. plot([1,1,1]) calls PlotlyJS.plot, which creates a SyncPlot object, which contains the plot information and some metadata. The other the calls of the PlotlyJS.plot function produce similar objects.

  2. [a b; c d] is just syntactic sugar for Base.hvcat((2, 2), a, b, c, d). The (2, 2) tuple means that a 2x2 matrix shall be created. So the next step is that the Julia shell calls the Base.hvcat function with the (2, 2) tuple and our plots as parameters: Base.hvcat((2, 2), sync_plot1, sync_plot2, sync_plot3, sync_plot4).

  3. But (twist!) the Base.hvcat function has a method specifically for SyncPlot objects, which is defined in PlotlyJS/utils.jl:

    # subplot methods on syncplot
    Base.hcat(sps::SyncPlot...) = SyncPlot(hcat([sp.plot for sp in sps]...))
    Base.vcat(sps::SyncPlot...) = SyncPlot(vcat([sp.plot for sp in sps]...))
    Base.vect(sps::SyncPlot...) = vcat(sps...)
    Base.hvcat(rows::Tuple{Vararg{Int}}, sps::SyncPlot...) =
        SyncPlot(hvcat(rows, [sp.plot for sp in sps]...))
    

    This means that instead of executing the general, built-in version of the Base.hvcat function and creating a usual Array, Julia calls PlotlyJS's own Base.hvcat function, which produces a new SyncPlot object. This new SyncPlot object encapsulates the four previous plots as subplots.

  4. When the Julia shell finishes the evaluation of a code block, it will try to display the result. It does that by calling the Base.display function on the return value of the code block.

    In our case, the return value of the code block is the SyncPlot, so Base.display(<our SyncPlot>) is called.

  5. But (another twist!) the Base.display function has a method specifically for SyncPlot objects, which is defined in PlotlyJS/display.jl:

    Base.display(::PlotlyJSDisplay, p::SyncPlot) = display_blink(p::SyncPlot)
    

    This means that instead of executing the general, built-in version of the Base.display function and printing the data structure to the console, Julia calls PlotlyJS's own Base.display function, which opens the AtomShell window and shows the graphs there.

The last point also means that you can call display explicitly. The following call opens four windows with four plots:

for i in 1:4 display(plot([i, i, i])) end

Why does the hvcat call fix the code?

The matrix_with_plots variable in draw4 is a plain Julia matrix containing plots. This is returned by draw2a. Therefore when the display function is called on it, display simply prints the data structure on the console.

By calling hvcat, we call PlotlyJS's custom hvcat, which creates a SyncPlot with four subplots (as opposed to a plain Julia matrix containing four SyncPlot objects).

hcs42
  • 13,376
  • 6
  • 41
  • 36