JuliaAPlavin / MakieExtra.jl

MIT License
27 stars 1 forks source link

Mix backends for plotting #2

Open Kolaru opened 1 month ago

Kolaru commented 1 month ago

This PR allows to mix backends. It is especially useful to have GLMakie 3D plots in CairoMakie PDF figure, like this:

using CairoMakie
using GLMakie
using GeometryBasics
using MakieExtra

CairoMakie.activate!()

fig = Figure()

Label(fig[1, 1, Top()], "Cairo backend")
ax_cairo = LScene(fig[1, 1])
mesh!(ax_cairo, s)
mesh!(ax_cairo, c)

Label(fig[1, 2, Top()], "GL backend")
with_backend(fig[1, 2], GLMakie) do figpos
    ax_gl = LScene(figpos)
    mesh!(ax_gl, s)
    mesh!(ax_gl, c)
end
fig

save("backend_demo.pdf", fig)

backend_demo.pdf

It is a snippet I have been sharing a bit on Slack (it is useful for publications), and I thought it could be a good fit for this package.

If it isn't, that's no big deal, now that I have cleaned the code I am happy to share it differently too.

aplavin commented 1 month ago

What is this black magic??!!! :) Surely it's suitable for MakieExtra, thanks – didn't know this is possible.

asinghvi17 commented 1 month ago

Interesting! I have a couple PRs on Makie which do a similar thing but baked into CairoMakie, but could never work around the fact that the screen size propagates to the displayed Scene. Nice to see that this is also an option :)

Could this be made to update the image when an observable updates?

Kolaru commented 1 month ago

Could this be made to update the image when an observable updates?

All the data is passed as a function, so, well, by using more forbidden magic and allowing this function to be an Observable, then yes:


function myplot!(figpos, x)
    ax_gl = LScene(figpos)
    meshscatter!(ax_gl, x, 0, 0 ; markersize = 0.4)
    mesh!(ax_gl, Cylinder(Point3(-1.0, 0, 0), Point3(1.0, 0, 0), 0.2))
end

begin
    x = Observable(0.0)
    fig = Figure()
    with_backend(MakieExtra.@lift(figpos -> myplot!(figpos, $x)), fig[1, 1], GLMakie)
    fig
end

In the example, with the latest commit, updating x updates the plot.

Alternatively, you can use the returned Observable to force the update, which also implies some boilerplate.

Also I guess that it is possible to make with_backend a macro, in which you could interpolate Observables.

I don't know if it is worth is, though. If you are using reactivity, you are probably working with GLMakie already, no ?

aplavin commented 1 month ago

Btw, do you think

function with_backend(f, backend)
    old_backend = ???
    backend.activate!()
    res = f()
    old_backend.activate!()
    return res
end

also makes sense? To switch a backend for a single plot.

Kolaru commented 1 month ago

As long as f returns a Figure, yes, that's just

function with_backend(f, backend)
     return rotr90(Makie.colorbuffer(f() ; backend))
end

and it returns an image (i.e. a matrix of color).

I could add it with a bit of user friendly addition to allow FigureAxisPlot objects as well.

aplavin commented 1 month ago

it returns an image (i.e. a matrix of color).

That's not really what I had in mind... Imagine a notebook, with CairoMakie activated. Then, you need one interactive plot inline, so you do with_backend(WGLMakie) do ... end and it's displayed with webgl in the notebook.

asinghvi17 commented 1 month ago

display(figurelike; backend = WGLMakie) already exists and would do the same thing more or less, no?

aplavin commented 1 month ago

Hmm, am I doing something wrong? For me, display(...) does a very different thing: image

asinghvi17 commented 2 weeks ago

Hmm, might be an interaction between the Julia and Pluto display stacks...probably worth an issue on Makie. That syntax works for me in VSCode, FWIW.

aplavin commented 2 weeks ago

I don't think it could be solved by changing display() behavior in any way.

When doing display(fig, backend=GLMakie), I really expect a new OpenGL window to pop up with my plot. But I also want to have a way to say "generate a static figure using GLMakie backend, and display it here". That's what just having fig at the end of a notebook cell does, but it can only use one backend (the one activated now).

asinghvi17 commented 2 weeks ago

Display can be inline as well with display(...; inline = true), although that's buggy on GLMakie for Mac at the moment. There is a lot of work to be done here for sure but we should fix it!

aplavin commented 2 weeks ago

Yeah, I understand. Basically, I'm wondering if there's a simple workaround for now to do "exactly what showing fig does", but allow selecting a different backend.