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

Lightweight MakieRecipes.jl #996

Open
rafaqz opened this issue May 27, 2021 · 6 comments
Open

Lightweight MakieRecipes.jl #996

rafaqz opened this issue May 27, 2021 · 6 comments

Comments

@rafaqz
Copy link
Contributor

rafaqz commented May 27, 2021

Recipes currently require depending on Makie.jl. It has a lot of dependencies compared to RecipesBase.jl:
https://github.com/JuliaPlots/Makie.jl/blob/master/Project.toml
https://github.com/JuliaPlots/RecipesBase.jl/blob/master/Project.toml

I'm keen to write recipes for packages like DimensionalData.jl and GeoData.jl. Adding Plots.jl recipes with a RecipesBase.jl dependency really had no downsides:

julia> @time using DimensionalData
  0.185980 seconds (528.76 k allocations: 37.096 MiB, 7.28% gc time, 49.88% compilation time)

But:

julia> @time using Makie
  2.849688 seconds (5.90 M allocations: 386.160 MiB, 10.64% gc time)

How feasible would it be to pull out method headers and abstract type definitions from Makie.jl to a base package we can depend on, similar to RecipesBase.jl?

@rafaqz rafaqz changed the title Lightweigth MakieRecipes.jl Lightweight MakieRecipes.jl May 27, 2021
@SimonDanisch
Copy link
Member

I wanted to transfer some core Makie functionality to MakieCore for some time and took the chance to see how minimal I can make it while still supporting creating recipes. It's pretty minimal now (only Observables as a dependency), and fairly quick to compile:

julia> @time using MakieCore
  0.024640 seconds (71.96 k allocations: 4.669 MiB, 6.46% compilation time)

https://github.com/JuliaPlots/MakieCore.jl
#998

I'm not sure how usable this will be for defining lightweight recipes, but I intended to make this refactor anyways so 🤷 I guess we can experiment a bit and see in what direction MakieCore should go.
Initially I wanted to have MakieCore + Backend be a fully blown drawing solution, a.k.a the Makie core that does the graphic drawing without any plotting related APIs.

I'm happy to do the transformation step by step and start with an absolute minimal solution which we can use as a lightweight MakieRecipes.jl prototype.

I do want to emphasize though, that truly lightweight recipes, that can fully describe a Makie plot, will need some serialization / dict base format, to access all advanced recipes + layouting without having any dependencies...

@rafaqz
Copy link
Contributor Author

rafaqz commented May 28, 2021

MakieCore looks good! Thanks for getting that moving. When I have time I'll try some GeoData.jl recipes using only MakieCore.

In terms of keeping it lightweight, should we define what lightweigth means to have a target? Maybe staying under 0.05 seconds on that machine.

By the serialisation/dict format do you mean Attributes, or something additional to that?

@piever
Copy link
Contributor

piever commented May 28, 2021

If I understand correctly, there are two separate things at play here. One mostly consists of moving some function and type definitions from Makie to MakieCore, so that packages can define plots only depending on MakieCore. I imagine the dependency on Observables here is necessary because recipes need to know how to update when the data changes.

A completely separate approach is to define a "format" to save these plots (mostly as nested dicts / tuples / named tuples I imagine), which could then be read by Makie. The format would be Makie-independent, other than for the choice of names for plot types and plot attributes, where it would follow Makie's convention.

Something like:

# for one trace
serialize_plot(obj::MyType1) = (plottype = :Scatter, arguments = (obj.x, obj.y), attributes = (color = obj.z,))

# for multiple traces
function serialize_plot(obj::MyType2)
    return [
        (plottype = :Scatter, arguments = (obj.x, obj.y), attributes = (color = obj.z,)),
        (plottype = :Lines, arguments = (obj.x, obj.y), attributes = (linewidth = obj.w,)),
    ]
end

# Layout support is admittedly a bit tricky...

Ideally, this format could then be loaded using the (planned, but not implemented yet) reactive API (see fonsp/Pluto.jl#155 (comment)).

This way, the only thing needed is an extremely lightweight package with only one function stub called serialize_plot (or whichever name we come up with).

@rafaqz
Copy link
Contributor Author

rafaqz commented May 28, 2021

How would serialization recipes interact with recipes where callbacks/observables are involved?
for something like this: #973

@jkrumbiegel
Copy link
Member

jkrumbiegel commented May 28, 2021

I think it would be beneficial to formalize the interface of a plot object with the axis / scene it's in more, so that the available observables can be specified by symbol without having access (or needing to import) the actual objects. Right now there is no link from plot object to axis / figure, as the figure / axis knows about the scene, but not the other way around.

So for example, for the heatmap you need access to the ax.finallimits struct, but it would easily be possible to just write a function like lazy_on(:axis_finallimits) do ... which is connected to the real finallimits when plotted.

I also really like the idea of a simple keyword / dict based approach like @piever suggested. I think with this it should be possible to do a lot of the current RecipesBase functionality.

In GridLayoutBase there is already the functionality to create a grid from a dict-like description object. So even layouts should be relatively simple with such a static approach. You define some objects, like two axis placeholders and a colorbar, and then describe their position in the layout, all with the relevant keywords. And this is translated at plotting time.

@mchitre
Copy link
Contributor

mchitre commented Jul 23, 2022

So for example, for the heatmap you need access to the ax.finallimits struct, but it would easily be possible to just write a function like lazy_on(:axis_finallimits) do ... which is connected to the real finallimits when plotted.

Is this implemented yet? Or is there any way currently in a recipe to get hold of ax.finallimits for the plot the recipe will output?

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

5 participants