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

[BUG] Wrong labels when combining series recipe with subplots #4668

Open
judober opened this issue Feb 14, 2023 · 4 comments
Open

[BUG] Wrong labels when combining series recipe with subplots #4668

judober opened this issue Feb 14, 2023 · 4 comments

Comments

@judober
Copy link

judober commented Feb 14, 2023

I observed a problem with the label order when creating subplots in a recipe.
It happens when the number of lines is different between the subplots. The lines seem right but the label order in the legend is messed up.

Details

It works with normal code:

pla = plot(1:3, [0 * ones(3), ones(3)], label=["a0" "a1"])
plb = plot(1:3, [2 * ones(3), 3 * ones(3), 4 * ones(3)], label=["b2" "b3" "b4"])
plab1 = plot(pla, plb, layout=(2, 1))

display(plab1)

Correct

And I would expect to get the same result with this recipe but the labels are wrong:

mutable struct MyType end

@recipe function f(::MyType)
    layout --> (2, 1)

    @series begin
        subplot := 1
        label --> (["a0" "a1"])
        (1:3, [0 * ones(3), ones(3)])
    end

    @series begin
        subplot := 2
        label --> (["b2" "b3" "b4"])
        (1:3, [2 * ones(3), 3 * ones(3), 4 * ones(3)])
    end
end

plab2 = plot(MyType())
display(plab2)

Wrong

Note that the color order is identical but the labels of the second plot are interchanged.

Backends

This bug occurs on ( insert x below )

Backend yes no untested
gr (default) x
pythonplot
plotlyjs x
pgfplotsx
unicodeplots
inspectdr
gaston

Versions

Plots.jl version:
v1.38.5
Backend version (]st -m <backend(s)>):

  • GR v0.71.7
  • st -m Plotly does not show any result; maybe related to the fact that I set Plotly as my standard Backend?

Output of versioninfo():
Julia Version 1.8.5
Commit 17cfb8e65e (2023-01-08 06:45 UTC)
Platform Info:
OS: Windows (x86_64-w64-mingw32)
CPU: 8 × 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-13.0.1 (ORCJIT, tigerlake)
Threads: 8 on 8 virtual cores
Environment:
JULIA_EDITOR = code

Edit: updated to fit the template, don't know why it was not shown before.

@judober judober changed the title Wrong labels when combining series recipie with subplots [BUG] Wrong labels when combining series recipie with subplots Feb 19, 2023
@judober judober changed the title [BUG] Wrong labels when combining series recipie with subplots [BUG] Wrong labels when combining series recipe with subplots Feb 19, 2023
@ivan-boikov
Copy link
Contributor

ivan-boikov commented Mar 5, 2023

I looked into this and I think the issue too complex to try to solve for a newbie (like me, haha). Though I'll still share what I found.
If we run the "normal" code

function RecipesPipeline.slice_series_attributes!(plt::Plot, kw_list, kw)
will process each subplot separately, starting with (for second plot with unrelated fields removed)

Dict(:series_plotindex => 1, :subplot => Subplot{1}, :label => ["b2" "b3" "b4"])
Dict(:series_plotindex => 2, :subplot => Subplot{1}, :label => ["b2" "b3" "b4"])
Dict(:series_plotindex => 3, :subplot => Subplot{1}, :label => ["b2" "b3" "b4"])

and slicing argument arrays (:label in our case) with

function _slice_series_args!(plotattributes::AKW, plt::Plot, sp::Subplot, commandIndex::Int)
eventually producing

Dict(:series_plotindex => 1, :subplot => Subplot{1}, :label => "b2")
Dict(:series_plotindex => 2, :subplot => Subplot{1}, :label => "b3")
Dict(:series_plotindex => 3, :subplot => Subplot{1}, :label => "b4")

It does so by iterating over each Dict in this array and computing commandIndex of needed item in :label

Plots.jl/src/pipeline.jl

Lines 302 to 303 in 511784f

series_idx(kw_list::AVec{KW}, kw::AKW) =
Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1

Since :series_plotindex is 1...3 and there are 3 label items at the start, slicing works.

Now let's run the "recipe" code, slicing will process both subplots at the same time

Dict(:subplot => Subplot{1}, :y => [0.0, 0.0, 0.0], :series_plotindex => 1, :label => ["a0" "a1"])
Dict(:subplot => Subplot{1}, :y => [1.0, 1.0, 1.0], :series_plotindex => 2, :label => ["a0" "a1"])
Dict(:subplot => Subplot{2}, :y => [2.0, 2.0, 2.0], :series_plotindex => 3, :label => ["b2" "b3" "b4"])
Dict(:subplot => Subplot{2}, :y => [3.0, 3.0, 3.0], :series_plotindex => 4, :label => ["b2" "b3" "b4"])
Dict(:subplot => Subplot{2}, :y => [4.0, 4.0, 4.0], :series_plotindex => 5, :label => ["b2" "b3" "b4"])

and when it tries to compute commandIndex, it will find 1 for a0 and 2 for a1 (and the legend is fine for those), but 3 for b2, 4 for b3 and 5 for b4. "Accessing" 4th and 5th item of :labels with 3 items does not crash thanks to mod1 in

Plots.jl/src/args.jl

Lines 1655 to 1660 in 511784f

function slice_arg(v::AMat, idx::Int)
isempty(v) && return v
c = mod1(idx, size(v, 2))
m, n = axes(v)
size(v, 1) == 1 ? v[first(m), n[c]] : v[:, n[c]]
end
which does mod1.([3,4,5],3) = [3,1,2], slicing :labels into ["b4", "b2", "b3"] seen in the bug report legend.

I tried to "fix" this by filtering kw_list by subplots before passing to series_idx, which solved this exact issue, but it breaks test

spl = scatter(
4.53 .* [1 / 1 1 / 2 1 / 3 1 / 4 1 / 5],
[0 0 0 0 0],
layout = (5, 1),
ylims = (-1.1, 1.1),
xlims = (0, 5),
series_annotations = permutedims([["1/1"], ["1/2"], ["1/3"], ["1/4"], ["1/5"]]),
)
where there are 5 subplots and it does not work.

Hope that would help, maybe @t-bltg can tag somebody who can help.

@judober
Copy link
Author

judober commented Mar 6, 2023

Thank you for sharing these insights!
This fits with my observations that all works fine as long as the number of lines in the subplots are identical. In this case, the mod1 removes the right amount.
Maybe slice_arg can incorporate the number of labels from the other plots? Something like

c = mod1(idx - num_labels_before, size(v, 2)) 

@BeastyBlacksmith
Copy link
Member

BeastyBlacksmith commented Mar 10, 2023

most likely related to #4108

@judober
Copy link
Author

judober commented Mar 12, 2023

I take it from the other issue that the workaround is to plot every line by its own.

@recipe function f(::MyType)
    layout --> (2, 1)

    for (ys, name) in zip([0 * ones(3), ones(3)], ["a0" "a1"])
        @series begin
            subplot := 1
            label --> name
            (1:3, ys)
        end
    end

    for (ys, name) in zip([2 * ones(3), 3 * ones(3), 4 * ones(3)], ["b2" "b3" "b4"])
        @series begin
            subplot := 2
            label --> name
            (1:3, ys)
        end
    end
end

While not as convenient, this works. Thanks!

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

3 participants