CliMA / Oceananigans.jl

🌊 Julia software for fast, friendly, flexible, ocean-flavored fluid dynamics on CPUs and GPUs
https://clima.github.io/OceananigansDocumentation/stable
MIT License
958 stars 191 forks source link

Adding Makie extension #3178

Closed navidcy closed 1 month ago

navidcy commented 1 year ago

I was reading about the extension feature in Julia v1.9

https://pkgdocs.julialang.org/v1/creating-packages/#Conditional-loading-of-code-in-packages-(Extensions)

and it reads to me that it's ideal to add for plotting methods that step onto Makie functionality. This way the code won't load unless one loads GLMakie/CairoMakie in the environment. What do others think?

I have put together a few methods for plotting Oceananigans fields at https://github.com/navidcy/Imaginocean.jl. I could move those into an extension in Oceananigans.

Ideally I would like these things to live outside Oceananigans repo to minimise the burden of maintenance. But from what I understand for how extensions work they have to live in this repo? Or in the Makie repo which makes even less sense? Am I right?

x-ref: https://github.com/navidcy/Imaginocean.jl/issues/2

navidcy commented 1 year ago

On the other hand if this extension lives in Oceananigans it also opens up the pathway for other extensions? E.g. somebody might wanna write something for Plots.jl at some point...?

glwagner commented 1 year ago

Hmm yes we might need extensions to provide that functionality.

Another possibility is to use plot recipes: https://docs.makie.org/stable/documentation/recipes/

That page says

If you're a package developer, it's possible to add recipes without adding all of Makie.jl as a dependency. Instead, you can use the MakieCore package, which is a lightweight package which provides all the necessary elements to create a recipe, such as the @recipe macro, convert_arguments and convert_attribute functions, and even some basic plot type definitions.

I'm not sure the stuff on Imaginocean.jl can be implemented with plot recipes, but if it can then perhaps that is a better approach?

Otherwise I agree that we should investigate extensions.

glwagner commented 1 month ago

I would like to start working on this.

Note that in terms of maintenance burden, the docs already depend extensively on plotting. So I'm not sure a plotting extension will increase CI significantly. On the contrary we can start with a minimalist approach that simply uses the examples to test the functionality in the extension.

I'd like to discuss design before starting. I think the basic functionality we need is to support automatically plotting of 2D fields. Basically we want to be able to write heatmap!(ax, c) and have it work automatically, eg if c is two-dimensional, then the non-trivial dimensions are automatically detected and appropriate node values inserted. I think this should work even if the dimensions are not Flat, so basically we just want to take a look at size(c). We can also support lines! and scatter! and scatterlines! for 1D fields. Is there a streamlined way to do this for all situations, or do we need to add support for each method like heatmap!, contour!, contourf!, etc, individually?

A second question is how to support inspection of 3D fields. I think this is pretty hard.

@Sbozzolo has some experience with this so he might have some valuable input.

jagoosw commented 1 month ago

My understanding is that you can write convert_arguments methods and then all the different plot types just work, for example (obviously not what you'd want in a finished extension but just as a simple example):

using CairoMakie 

import MakieCore: convert_arguments

convert_arguments(P::Type{<:AbstractPlot}, f::Field) = convert_arguments(P, xnodes(f), ynodes(f), interior(f, :, :, 1))

fig = Figure()
ax = Axis(fig[1, 1])
heatmap!(ax, c)

correctly plots the bottom level of a field c and puts the x and y axes in.

I think you can pass other arguments too like:

convert_arguments(P::Type{<:AbstractPlot}, f::Field, indices = (:, :, 1)) = convert_arguments(P, xnodes(f)[indices[1]], ynodes(f)[indices[2]], interior(f, indices...))
heatmap!(ax, c, (1:10, :, 1))

so you could deal with slicing up 3D fields that way?

jagoosw commented 1 month ago

I cleaned this up a bit but I don't know how to make an extension, I've put what I wrote in https://github.com/CliMA/Oceananigans.jl/tree/jsw/makie-methods but its not the cleanest and I can imagine this would be a lot of work to change all the docs over.

It now works like:

c = CenterField(grid)

line!(ax, c, 1, 1) # plots interior(c, 1, 1, :) with the z-coordinate along the x axis
heatmap!(ax, c, :, :, 1) # plots interior(c, :, :, 1) with x and y coordinates

fts = FieldTimeSeries("somepath.jld2", "u")

line!(ax, fts, 1, 1, 1, :) # plots a timeseries of points at (1, 1, 1) with times along x axis
heatmap!(ax, fts, :, 1, 1, :) # plots an x-time heatmap of fts

This syntax was inspired by interior and the fact it doesn't seem like you can put keywords into convert_arguments.

glwagner commented 1 month ago

Ah nice. We could put keyword arguments into heatmap! though right? For example

heatmap!(ax, c, k=1)

to plot an x, y slice at k=1.

What would be the advantage of

heatmap!(ax, c, :, :, 1)

versus

heatmap!(ax, view(c, :, :, 1))

?

glwagner commented 1 month ago

When I said "support inspection of 3D fields", what I meant was a widget or tool that would allow us to interactively inspect the data. I think plotting a view(c, :, :, 1) might work for users that want to manually pass an index. But I'm open to other syntax for sure.

navidcy commented 1 month ago

A first attempt like that (eg with kwargs as you suggest) is done in https://github.com/navidcy/Imaginocean.jl

But it needs revisit and it is definitely much better having it as an extension!

E.g., see https://github.com/navidcy/Imaginocean.jl/blob/1b2694ac040fff1c7a9da001bfd5ade0eefb05b4/src/Imaginocean.jl#L291-L311