How to implement interactive plots in IJulia

question

#1

I am trying to get interactive plots with Gaston in IJulia notebooks. I am almost there, but there is one final piece of the puzzle that I can’t quite figure out. I have this code in a notebook:

using Gaston, Reactive, Interact
t = 0:0.01:1
@manipulate for phi=0:0.1:6.28
    plot(cos.(2π*t+phi))
end

This gets me a slider and an initial plot. When I move the slider, though, what I get is a bunch of slightly different plots in a very long stack; in other words, each new plot is not being displayed on top of the previous one, but below it.

What do I need to do to get this to work? I looked at Gadfly’s code, but I can’t find what they’re doing differently from me.

In Gaston, I add a method to show:

function Base.show(io::IO, ::MIME"image/svg+xml", x::Figure)
    write(io,x.svgdata)
end

and I display the image at the end of every plot command with:

display(fig)

where Figure is Gaston’s figure type, fig is the current figure, and fig.svgdata contains the actual SVG code that I send to the notebook.

(Let me know if you need to see the whole code: I’m working on a private branch right now, and the code on github produces PNG files instead of SVG code).

Thanks!


#2

You might try GR:

using GR, Interact
t = 0:0.01:1
@manipulate for phi=0:0.1:6.28
    plot(cos.(2π*t+phi))
end

It should work out of the box and you don’t have the Gnuplot overhead - moreover the display should be much faster …


#3

I wasn’t very clear, sorry: I’m the maintainer of Gaston, and I’m trying to get it to work as an experiment. I want to learn how to do it, and I’m also curious about how fast it actually is.


#4

Hi @mbaz,

The problem is that the code you wrote above is actually trying to render the plot itself. That’s not what you are supposed to do in order to use interact properly.

Instead, the @manipulate macro is supposed to return a plot object (in your case, a Gaston.Figure object, I suppose). What happens then is that Interact will trigger a show() of the returned object each time the value of phi changes.

I can kind of get your code to work using the latest tagged version of Gaston by doing the following:

using Gaston, Reactive, Interact
t = 0:0.01:1
@manipulate for phi=0:0.1:6.28
    id=plot(cos.(2π*t+phi))
    p=Gaston.Figure(id)
    return p
end

Unfortunately, this code still uses the plot() function (because I don’t know how to update the plotted values in Gaston without using the plot() function… So you end up with an incorrectly generated plot at the top, followed by the properly rendered interactive plot at the bottom of the IJulia output.

Hope this helps!


#5

That is very useful information and probably the piece I was missing. Thank you!


#6

I’d appreciate a bit of advice; I’ve made progress but not found a complete solution.

I’ve modified my code so that plot returns a value of type Gaston.Figure. I also have a function llplot that renders the figure using gnuplot. I want to handle two cases:

  • Case 1 is REPL use: gnuplot renders the figure to the screen.
  • Case 2 is Jupyter use: gnuplot returns the figure in SVG format, which I want to pass to the notebook.

Case 1 is easy to handle:

function Base.show(io::IO, x::Figure)
    println("show 1")  # debugging
    llplot(x)
end

Note that there is no MIME string here. I don’t think MIME makes sense here; at least I can’t imagine how to define a meaningful MIME string for this case.

Attempting to address Case 2, I have this function:

function Base.show(io::IO, ::MIME"image/svg+xml", x::Figure)
    println("show 2")  # debugging
    llplot(x)
    write(io,x.svg)
end

In this case, llplot writes the SVG image to x.svg.

I’m pretty sure this is incorrect, because when I plot in IJulia, I get a correct plot but I also see

show 1
show 2

meaning that the plot is getting rendered twice. How is the correct way to code this? Does display play a role here?


#7

Short Answer

You are not supposed to define Base.show(::IO, ::Figure) to do what you are trying to do. I am not even certain you are ever supposed to define show() without including ::MIME as the second argument.

How to do this properly

Consider this dummy Figure structure as an illustration:

struct Figure
end

We then define the show function as you did previously for SVG:

function Base.show(io::IO, ::MIME"image/svg+xml", x::Figure)
    println(io, "show 2")  # debugging
    println("show 2 -runs")
end

Then, you can specialize display() to depend on IJulia/REPL:

function Base.display(d::IJulia.InlineDisplay, x::Figure)
    println("display fig from IJulia")
end
function Base.display(d::Base.REPL.REPLDisplay, x::Figure)
    println(d.repl.t.out_stream, "display fig from REPL")
end

Simple test

d=Base.Multimedia.displays[2] #usually top-most display in IJulia
f=Figure() #Some plot

println("\n\nrun show(f):")
show(f)

println("\n\nrun show(STDOUT, MIME, f):")
show(STDOUT, MIME"image/svg+xml"(), f)

println("\n\nrun display(f):")
display(f) #Use topmost display on Base.Multimedia.displays[] "stack"

println("\n\nrun display(d, f):")
display(d,f) #Explicitly display on display "d"

; #semicolon: Don't "show" "result" from last line

More advanced usage of display

I personally prefer being more explicit with display(). Instead of specializing on InlineDisplay or REPLDisplay, I would expect my user to push a GnuPlotDisplay (defined in your module).

This gives the user a choice to use the GnuPlot window when working with IJulia as well…

struct GnuPlotDisplay <: Base.Display
end

function Base.display(d::GnuPlotDisplay, x::Figure)
    println("display fig with GnuPlot")
end

Here is a small example on how to use GnuPlotDisplay to control plotting in a more explicit fashion:

pushdisplay(GnuPlotDisplay()) #Could add GnuPlotDisplay @ top of stack (as "most rich display")
@show Base.Multimedia.displays

display(f) #Implicitly use topmost display on Base.Multimedia.displays "stack"

popdisplay() #Remove GnuPlotDisplay from top of stack
@show Base.Multimedia.displays

display(GnuPlotDisplay(), f) #Explicitly call for displaying with GnuPlot
;

#8

Thanks a lot for the detailed explanation; it will be very useful. There’s a lot for me to learn here.

If you don’t mind my asking, how did you figure all this out? I’ve read the docs on multimedia I/O but I’m unable to extrapolate from that to what you just explained.


#9

@mbaz: If you don’t mind my asking, how did you figure all this out? […]

Good question. I don’t remember exactly. I think that in the 2015-16 timeframe, there was a more philosophical section on multimedia I/O than what there is now. What I see right now seems more mechanical than what I remember… more like a simple API description. I am not certain anymore, though.

I do know I had to follow the code a bit using the @edit macro. For example, running:

@edit show("hello")

shows me where the 1-parameter wrapper function for show is defined:

show(x) = show(STDOUT::IO, x)

#10

Errata

It appears I was wrong when I mentionned you were probably “not supposed to define Base.show(::IO, ::Figure) […]”.

Upon reading the “Types|Custom pretty-printing” section of the manual, it appears you are encouraged to define the 2-argument version of show(). In fact, the manual provides the example:

julia> Base.show(io::IO, z::Polar) = print(io, z.r, " * exp(", z.Θ, "im)")

… but I highly suggest you to output a “textual” version of your Figure object if you do so (ex: Figure: GnuPlot "TITLE HERE"; 4 subplots; ...). This would allow users to quickly “inspect” what’s in a plot object without actually having to render anything.

So why did you get those odd results?

I am not really sure why, in IJulia, you get both show() functions to run (both “show 1” & “show 2” get written to your output).

I did a few more tests that reveal something interesting:

struct Figure
end

function Base.show(io::IO, x::Figure)
    println(io, "show 1")
        @show typeof(io)
    println("show 1 -runs")
end

function Base.show(io::IO, ::MIME"image/svg+xml", x::Figure)
    println(io, "show 2")
        @show typeof(io)
    println("show 2 -runs")
end

I then simply create a new Figure, and leave it to be output in IJulia:

f = Figure()
f #Have IJulia "write" f to its output

The output is quite intriguing:

typeof(io) = IOContext{Base.AbstractIOBuffer{Array{UInt8,1}}}
show 1 -runs
typeof(io) = IOContext{Base.AbstractIOBuffer{Array{UInt8,1}}}
show 2 -runs

I am not sure why both versions of show() run… I am also not sure what this IOContext{AbstractIOBuffer} is exactly…

I actually expected that “returning” f at the end of an IJulia block would trigger the appropriate display() method. I certainly do not expect it to trigger 2 consecutive show() methods.

If you “run” f by itself in the Julia REPL, it does exactly what I expect: it triggers the appropriate display() method.

Conclusion

I really don’t understand what’s going on in the IJulia session. I don’t know if this is the intended behaviour, and I am not sure who to ask.


#11

I got it to work, and in the end the code was quite simple. I wanted to have a plot command that returns a Figure object that renders like this:

  • If inside Jupyter, display figure as SVG.
  • elseif terminal=="null", don’t display anything.
  • elseif terminal=="dumb", display the figure as text on the REPL
  • elseif terminal is something else, have gnuplot render the figure using X11.

To plot inside Jupyter I define

function Base.show(io::IO, ::MIME"image/svg+xml", x::Figure)
    llplot()
    write(io,x.svg)
end

The key insight is that IJulia will call this function because it is the “richer” display for a Figure. To plot on the REPL I have

function Base.show(io::IO, ::MIME"text/plain", x::Figure)
    llplot()
    term = gaston_config.terminal
    if term == "null"
        return nothing
    elseif term == "dumb"
        print(x.text)
    else
        return nothing
    end
end

In the REPL, this function will be called instead of the first one, because the REPL does not support SVG.

I’m still not sure how to support the “textual” version of the Figure object. It’d be nice to support that, but I think many users expect plot to render the figure without further output. Maybe I can have an inspect(x::Figure) command for that.

I’m not yet certain but I think the IJulia behavior is a bug. I can certainly reproduce the behavior where it calls show twice. I’ll play with it a bit more and maybe open an issue. EDIT: This is expected behavior.

Thank you for all your help and for setting me on the right track.