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

Integration with Makie #95

Open
KronosTheLate opened this issue Mar 12, 2021 · 9 comments
Open

Integration with Makie #95

KronosTheLate opened this issue Mar 12, 2021 · 9 comments

Comments

@KronosTheLate
Copy link

Currently, there is some LOVELY integration and fluidity between Plots.jl and this package. I guess this is acomplished by exporting a plotting-recipe for measurements.

If I am not mistaken, Makie consumes Plots.jl recipes. Is it possible to also export this plotting recipe for the Makie ecosystem?

@giordano
Copy link
Member

I never got Makie working so it's highly unlikely I'll work on that myself. I thought Makie used the same plotting recipes as Plots?

@KronosTheLate
Copy link
Author

I never got Makie working so it's highly unlikely I'll work on that myself. I thought Makie used the same plotting recipes as Plots?

I also though so, but I does not work out-the box as with Plots.jl. So something is different.

@giordano
Copy link
Member

giordano commented Mar 12, 2021

Well, you need someone who can use Makie and cares about this package. It won't be me though 🙂 (but ideally, a single solution that works for both Plots and Makie would be best)

@KronosTheLate
Copy link
Author

Noted, and completely fair. I hope someone from Makie could help out. If not, ill probably get around to learning how to fix it at some point.

@icweaver
Copy link

icweaver commented Jul 9, 2021

Would a few simple type recipes be a start possibly? I'm still learning about full recipes, but something like this seems to work pretty well:

using CairoMakie
using Measurements
import Measurements: value, uncertainty

Makie.convert_arguments(P::PointBased, v::Vector, m::AbstractVector{<:Measurement}) = 
convert_arguments(P, v, value.(m))
Makie.convert_arguments(P::Type{<:Errorbars}, v::Vector, m::AbstractVector{<:Measurement}) = 
convert_arguments(P, v, value.(m), uncertainty.(m))

fig = Figure()

x = rand(10) |> sort
y = rand(10)  rand(0:0.01:1, 10)

scatter(fig[1, 1], x, y)
lines(fig[2, 1], x, y)
scatterlines(fig[1, 2], x, y)
errorbars(fig[2, 2], x, y)
scatter!(fig[2, 2], x, y)

axs = reshape(copy(fig.content), 2, 2)
linkaxes!(axs...)
hidexdecorations!.(axs[begin, :])
hideydecorations!.(axs[:, end])

fig

6d6494ad-ec28-4afc-aabd-bd8a960e3c78

@KronosTheLate
Copy link
Author

Last time I tried looking into this, it seemed as if recipes did not work properly in Makie. It was a WIP.

But your example definitely looks functional! Nice ^_^ Personally, I was imagining something like scatter producing a scatter+errorbars, and lines producing a shaded area around the line representing 1 standard deviation, when the uncertanty is on the y values. I don't know what would be best for a potential x value uncertanty in a lines plot.

But your approach definitely seems more modular, which I like! One would e.g. choose how many standard deviations should be represented by the shaded area, and simply layer the uncertainty and means values on top of each other, as you have.

@icweaver
Copy link

I think having a way to let Makie.jl know that scatter(m::AbstractVector{<:Measurement}) -> errorbar + scatter and line -> band+line would be really neat! I'm not aware of a way to do this though, so in the meantime this is just my current attempt at trying to accomplish the same thing with a super bare-bones dedicated full recipe for each:

using CairoMakie
using Measurements
import Measurements: value, uncertainty

@recipe(Scatterbars) do scene
    Attributes(;)
@recipe(BandLines) do scene
    Attributes(;)

function Makie.convert_arguments(::Type{<:Scatterbars},  v::Vector, m::AbstractVector{<:Measurement})
    v, value.(m), uncertainty.(m)
end

function Makie.convert_arguments(::Type{<:BandLines},  v::Vector, m::AbstractVector{<:Measurement})
    v, value.(m), uncertainty.(m)
end

function Makie.plot!(plot::Scatterbars{T}) where T <: Tuple{Vector, Vector, Vector}
    x, y, y_err = plot[1][], plot[2][], plot[3][]
    errorbars!(plot, x, y, y_err)
    scatter!(plot, x, y)
    plot
end

function Makie.plot!(plot::BandLines{T}) where T <: Tuple{Vector, Vector, Vector}
    x, y, y_err = plot[1][], plot[2][], plot[3][]
    band!(plot, x, y - y_err, y + y_err, color=(:blue, 0.25))
    lines!(plot, x, y)
    plot
end

fig = Figure()
ax_sb, ax_bl = Axis(fig[1, 1]), Axis(fig[1, 2])

x = rand(10) |> sort
y = rand(10)  rand(0:0.01:0.1, 10)

scatterbars!(ax_sb, x, y)
bandlines!(ax_bl, x, y)

axs = reshape(copy(fig.content), 1, 2)
linkaxes!(axs...)
hideydecorations!.(axs[:, end])

fig

96f297bb-aaac-421c-adc3-575f138ce61d

There's definitely a lot more that could be done to make this more customizable, but I'm wondering if we'd even want to have separately named specialty functions like these in the first place

@leonvonrabenmond
Copy link

I think having a way to let Makie.jl know that scatter(m::AbstractVector{<:Measurement}) -> errorbar + scatter and line -> band+line would be really neat!

I totally agree with you and that was my first thought in fixing this as well, but this leads to a new problem: How do we deal with arguments? For example, I dislike errorbars with whiskerwidth = 0, which is the default. While this approach would allow us to put arguments for scatter, it would as far as I can tell not allow us to place any arguments for errorbars.

I think a combination of both of your solutions might be best. To allow the standard plotting functions to take measurement inputs and to define a new functions which allow us to customise the combined output. This is the best approach I can see at this point in time, but I am far from an expert in Makie.

@leonvonrabenmond
Copy link

leonvonrabenmond commented Aug 14, 2022

So I played around with the pure conversion and ended up with:

using MakieCore
using Measurements
import Measurements: value, uncertainty

# PointBased plots
MakieCore.convert_arguments(P::PointBased, x::AbstractVector{<:Measurement}, y::AbstractVector{<:Measurement}) =
    convert_arguments(P, value.(x), value.(y))
MakieCore.convert_arguments(P::PointBased, x::AbstractVector{<:Real}, y::AbstractVector{<:Measurement}) = 
    convert_arguments(P, x, value.(y))
MakieCore.convert_arguments(P::PointBased, x::AbstractVector{<:Measurement}, y::AbstractVector{<:Real}) = 
    convert_arguments(P, value.(x), y)

# errorbars
MakieCore.convert_arguments(P::Type{<:Errorbars}, x::AbstractVector{<:Measurement}, y::AbstractVector{<:Measurement}, e::AbstractVector{<:Measurement}) = 
    convert_arguments(P, value.(x), value.(y), uncertainty.(e))
MakieCore.convert_arguments(P::Type{<:Errorbars}, x::AbstractVector{<:Measurement}, y::AbstractVector{<:Real}) = 
    convert_arguments(P, value.(x), y, uncertainty.(x))
MakieCore.convert_arguments(P::Type{<:Errorbars}, x::AbstractVector{<:Real}, y::AbstractVector{<:Measurement}) = 
    convert_arguments(P, x, value.(y), uncertainty.(y))

# band
MakieCore.convert_arguments(P::Type{<:Band}, x::AbstractVector{<:Measurement}, y::AbstractVector{<:Measurement}) = 
    convert_arguments(P, value.(x), value.(y) - uncertainty.(y), value.(y) + uncertainty.(y))
MakieCore.convert_arguments(P::Type{<:Band}, x::AbstractVector{<:Real}, y::AbstractVector{<:Measurement}) = 
    convert_arguments(P, x, value.(y) - uncertainty.(y), value.(y) + uncertainty.(y))

This converts the cases in which x, y or both have errors. I also made a few assumptions regarding these cases. For errorbars, if only one axis has errors, these are plotted automatically (though direction = :x may have to be specified). If both axis have errors, then the axis has to be specified (as this allows for x-errors).

In case of bands the y-error gets plotted automatically, while the x-error (if present) gets ignored.

I tested this on some old data of mine:

using DataFrames, CairoMakie, Measurements

df = DataFrame("α" => ((0:30:180)  5), "U" => ([1.70 ± 0.02, 1.50 ± 0.02, 0.864 ± 0.01, 0.103 ± 0.05, 0.888 ± 0.02, 1.46 ± 0.01, 1.70 ± 0.02]/2))

fig = Figure()

ax1 = Axis(fig[1,1])
errorbars!(ax1, df.α, df.U, df.U)
errorbars!(ax1, df.α, df.U, df.α, direction =:x)
scatter!(ax1, df.α, df.U)

ax2 = Axis(fig[1,2])
band!(ax2, df.α, df.U)
lines!(ax2, df.α, df.U)

fig

fig

So, this works as a basic system and allow us to deal with all types of errors, that I at least have encountered. My question now is, do we actually want plots like scatterbars or linesband? While yes, they would make plotting quicker, it would mess with any form of customisation. We would have to pass arguments for both plot-types, which I don't see how to do elegantly.

In the case that someone does not want to customise the plots a lot, this would be useful, but then we would have to agree on a basic look first. I doubt that the "normal" linesband as in ax2 is something anyone would want to use.

I think we should maybe just do it like this here? Making separate plot types would be more work then just doing two separate plots on the same axis. What do you think?

I think a combination of both of your solutions might be best. To allow the standard plotting functions to take measurement inputs and to define a new functions which allow us to customise the combined output. This is the best approach I can see at this point in time, but I am far from an expert in Makie.

On second thought, I take this back. I think just doing it with simple conversion might really be the best option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants