Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scene magic #10

Closed
SimonDanisch opened this issue Nov 6, 2017 · 20 comments
Closed

Scene magic #10

SimonDanisch opened this issue Nov 6, 2017 · 20 comments

Comments

@SimonDanisch
Copy link
Member

As @Evizero pointed out in JuliaPlots/Plots.jl#392:

for what its worth, the way scene = Scene() seems to affect global state already seems very magical to me. then again i suppose Plots does the same thing

I agree with that and I meant to discuss the magic we actually want here ;)
I have a few ideas and problems which I wanted to write down.

  1. Should Scene() open a new window? I'm thinking about having it create a new tab as well
  2. I was planning to make scatter(name = :myname); scatter(name = :myname) overwrite the first plot, while without having the same name they would get added with a unique default name (:scatter1, :scatter2, ...). I'm not sure if that isn't too quirky, and if that shouldn't mean that Scene() without a new unique name shouldn't create a completely new scene! But I also want an easy way to replace a scene that is meant to take the same place. I could even reuse the GPU objects in that case, making the example even more efficient.
@Evizero
Copy link
Contributor

Evizero commented Nov 6, 2017

Well the reason why I call it magic is that the way this seems to work with global objects doesn't seem like a convenience layer on first glance. e.g. in Plots i can be explicit about what I am referring to p = plot(...); plot!(p, ...) and when to show things.

If feasible it would be nice if the global object stuff would be optional and a user can be very explicit when to do what with which scene. Or is this already possible?

@ChrisRackauckas
Copy link
Contributor

If feasible it would be nice if the global object stuff would be optional and a user can be very explicit when to do what with which scene. Or is this already possible?

I agree here. I would like a way to avoid globals when possible, simply because it is required to make things like multithreading work.

@ssfrr
Copy link
Contributor

ssfrr commented Dec 28, 2017

👍
If there's an API that has some implicit global state, it would be great if it were built on top of an explicit API with no global state.

Should Scene() open a new window? I'm thinking about having it create a new tab as well

I think it's fine if Scene() opens a new window, but then I'd expect it to open a new window every time. E.g. if I ran

scene1 = Scene()
scene2 = Scene()

Then I'd end up with 2 scene windows.

Also given that scatter and friends seem to add themselves to the scene's object list, I'd prefer an API that let me do scatter(sceen1, rand(10), rand(10)) or similar so it's explicit what scene the objects are being added to, or even push!(scene1, scatter(rand(10), rand(10))

@ssfrr
Copy link
Contributor

ssfrr commented Dec 28, 2017

oh, hah, just realized that scatter and friends do take a scene argument. Nice!

@pfitzseb
Copy link
Contributor

Should Scene() open a new window?

No, but display(Scene()) or something similar should imho.

@SimonDanisch
Copy link
Member Author

How about this:

root = plot() # returns rootscene
# root contains a theme, from which all defaults get feeded
# root also contains all window signals

root = plot(root, rand(10), thickness = 1mm) # creates a new node, getting attached to root
root = plot(rand(10), thickness = 1mm) # equal to the above, since root will just be inserted
# -root
#     -p1

# adds a new node to p1
p1 = plot!(p1, rand(10)) # takes defaults from kw_args and if not in there from root.theme
# root
    # p1
        # p2

root = plot!(root, rand(10), p1, color = :red) # takes defaults from kw_args, then p1, then root.theme
# root
    # p1
        # p2
    # p3

# convenient way to access subplots
root[1] == p1
p1[1] == p2 && p2 == root[1][1]
root[2] == p3

hcombined = plot(root...) # equals to plot(root[1], root[2]) # equals to plot(p1, p3)
# hcombined
    # subplot1
        # p1
            # P2
    # subplot2
        # p3
#=
┏━━━━━━━┳━━━━━┓
┃ p1 p2 ┃ p3  ┃
┗━━━━━━━┻━━━━━┛
=#
hcombined == hbox(root...) # horizontal concat layout
vcombined = vbox(root...) # vertical concat layout

p2 = pop!(root[1]) # delete the last plot from (root[1] == p1)
layouted = vbox(hbox(root[1], p2), root[2]) # vertical + horizontal layout combined
# pcombined
    # subplot1 vertical
        # subplot 2 horizontal
            # p1
        # subplot 3 horizontal
            # p2
    # subplot2 vertical
        # p3
#=
┏━━━━┳━━━━┓
┃ p1 ┃ p2 ┃
┣━━━━┻━━━━┫
┃   p3    ┃
┗━━━━━━━━━┛
=#
p1 = root[1]
p3 = root[2]
layouted_axis = vbox(hbox(p1, plot(p2, axis = p2[:axis]), p3)) # vertical + horizontal layout combined
#=
┏━━━━┳━━━━┓
┃ p1 ┃ p2 ┃ # p1, p2 share the same axis object
┣━━━━┻━━━━┫
┃   p3    ┃
┗━━━━━━━━━┛
=#

# the above will all be backend independent!
display(layouted) # create the actual plot + window for the preferred + backend / display combo

disp = gl_display(resolution = ...) # explicitely chose a gl backend for display
display(disp, layouted)
display(layouted) # will use disp, the last created display

@ChrisRackauckas
Copy link
Contributor

This is interesting, but I am not sure you want to commit to an hcat vs vcat style for layouts because it seems like it would get confusing when you have that last kind of "two on top, on bottom", or going even further what about one in the middle, a long one on the right and left, and open space on top and bottom? And then changing sizes? Some long some thing? For example, what would these look like?

http://docs.juliaplots.org/latest/layouts/#advanced-layouts

It would be nice to have something easier for specifying such layouts, and the internal array that is used to hold the subplots seems like it could get quite messy trying to hold all of this information. Plots.jl's macro does this quite well and I hope we don't lose that.

@SimonDanisch
Copy link
Member Author

I see...
For the sizes I would just do:

vbox(hbox(p1, box(p2, height = 0.5rel)), box(p3, Rect(6mm, 5mm, 10mm, 15mm)) 

Well, that is just an idea. I used it in WebIO, which is inspired a bit by html I think, and I immediately liked it.
At the same time, I always tried to stay away from the layout macro. I find the mapping from plot to layout and the subplot argument pretty confusing :D Also, I was never able to create a layout without looking up the documentation for it...

Anyways, this is all surface syntax, so we might as well have both, or decide which one to drop ;)

@mschauer
Copy link
Contributor

mschauer commented Jan 5, 2018

Hm, tiling a given square with (sub-)grids with absolute and relative guides and cell-joins is such an universal task that it should be it's own package. That also immediately enforces a modular design.

@SimonDanisch
Copy link
Member Author

Well, one motivation behind this was definitely, to be also compatible with more css/html like styling @shashi and possibly @yurivish are working on.
Not sure what part besides the API we'll be able to share. But the whole :padding etc thing starts looking more and more like a css style ;)

@mschauer
Copy link
Contributor

mschauer commented Jan 5, 2018

I was thinking of css, I did not mention it because some people have mixed feelings ;-)

@SimonDanisch
Copy link
Member Author

Anyways, the layouting wasn't supposed to be the controversial part ;) I was rather trying to make sure that everyone is on board with the way to separate plots/display and creating the hierarchy. Although I can see how that would be less controversial, since it's pretty much what Plots.jl currently does.

@ssfrr
Copy link
Contributor

ssfrr commented Jan 9, 2018

FWIW I like the vcat/hcat methods for stacking plots as used in PlotlyJS.jl, they're really convenient and easy to remember how they work. I can see how it breaks down for sophisticated layouts where you want to specify the sizes manually, but it pretty much handles all my use-cases. Note that for this to work I think it would require defining display(Array{MakiePlot}) or similar, and using Julia's display system rather than having plot display the plot as a side-effect.

Maybe I'm being dense, but I'm having a hard time following your first few examples of the plot and plot! functions.

You have:

root = plot() # returns rootscene

does root now point to some global? If I do:

r1 = plot()
r2 = plot()

do r1 and r2 point to the same object?

then:

root = plot(root, rand(10), thickness = 1mm) # creates a new node, getting attached to root
root = plot(rand(10), thickness = 1mm) # equal to the above, since root will just be inserted
# -root
#     -p1

does root still point to the root scene here, or does plot return the subplot object? Is this supposed to be p1 = ...?

when nesting plots:

# adds a new node to p1
p1 = plot!(p1, rand(10)) # takes defaults from kw_args and if not in there from root.theme
# root
    # p1
        # p2

what does it mean for a plot to be nested inside another plot? I'm also a little unclear on what the difference between plot and plot! is, because it seems like the call to plot(root, ...) is modifying root (equivalent to push!), but it's likely that I'm not understanding something correctly.

@SimonDanisch
Copy link
Member Author

does root now point to some global? If I do:
r1 = plot()
r2 = plot()
do r1 and r2 point to the same object?

Yes! I want one root object, that contains infos like the theme etc.

does root still point to the root scene here, or does plot return the subplot object? Is this supposed to be p1 = ...?

This still points to the one root and creates a new plot object p1 attached to root. For displaying what the user expects, I thought it will make sense to always return the parent,.

what does it mean for a plot to be nested inside another plot?

This is supposed to be like in a scene graph. So e.g. the child will inherit translations/scales from the parent. I wanted to inherit all attributes here, but I realized that it will make it hard to opt out. So for inheriting attributes like color I want another signature.

@SimonDanisch
Copy link
Member Author

I see how that's confusing. I'm violating the conventions with returning the same root.
So plot is meant to return a new object and plot() is violating that. Maybe I should just stick with the implicit root = Scene() one instead.
The other confusing one is plot(root, ...) which does create a new plot object, but also mutates root.

@ssfrr
Copy link
Contributor

ssfrr commented Jan 9, 2018

Is there any way around the globals? I would propose something more like:

s1 = Scene() # creates a scene with default theme
s2 = Scene(thickness=1mm) # creates a different scene with a new theme overriding the thickness
ax = axis(...) # assume this has some linspaces in it
push!(s1, ax)
push!(ax, lines(rand(10)) # adds a plot to the scene, as a child of the axis
push!(s2, lines(rand(10)) # add some lines with no axis

display(s1) # actually show the scene
display(s2) # show the other scene
display(s1) # show the first scene again

This is obviously pretty verbose, so the plot syntax would simplify it, something akin to:

p1 = plot(rand(10))

# equivalent to:
p1 = begin
    s = Scene()
    ax = axis(...) # compute axis from data
    push!(s, ax)
    push!(ax, line(rand(10))

    s
end

# then you could add another line to the scene (probably with the same axis?) with:
plot!(p1, rand(10))

@SimonDanisch
Copy link
Member Author

display(s1) # actually show the scene
display(s2) # show the other scene
display(s1) # show the first scene again

would those all open a new window?

@ssfrr
Copy link
Contributor

ssfrr commented Jan 9, 2018

good question, I'm not sure. I think it would be nice to support multiple simultaneous windows, but it also seems reasonable for it to only support 1 scene at a time, so the first display call would open a window, but subsequent ones would replace the currently-displayed scene.

That way the only global state would be some reference to the currently-displayed scene (which would be loaded in the GPU, and handle input signals)

@jpsamaroo
Copy link
Contributor

If we want to support plots on multiple monitors from the same julia process, we would need a way to have multiple windows running simultaneously. I think pairing one (or more) scenes with a display object (which corresponds to a window) would make sense. However, the above three calls could still display both scenes on the same window, but could accept a separate argument if you want to plot a scene to the non-current window.

@ssfrr
Copy link
Contributor

ssfrr commented Jan 11, 2018

I was also thinking recently that it might be weird or difficult to implement displaying the same scene in multiple windows simultaneously, so it seems very reasonable for the 1st and 3rd display calls to happen in the same window, even if Makie supports multiple windows (or if default display behavior is to reuse the window for each one).

I think even if initially Makie only supports 1 window, it would be nice if the API would in principle support multiple windows and scenes.

SimonDanisch pushed a commit that referenced this issue Jun 3, 2021
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Anshul Singhvi <[email protected]>
SimonDanisch added a commit that referenced this issue Jun 3, 2021
make sure tests at least run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants