Repository: JuliaPlots/Makie.jl (julia)

Issue:
the label of a zero-height stacked bar is placed to the bottom by mistake
In `barplot`, the label of a zero-height stacked bar is placed to the bottom by mistake. See the below example. This right axis is correct, while the left is wrong:

```julia
import Makie, CairoMakie

fig = Makie.Figure()

ax = Makie.Axis(fig[1, 1], title="Wrong")
ys = [100, 0]
Makie.barplot!(ax, [1, 1], ys; stack=[1, 2], color=[:red, :green], bar_labels=string.(ys))

ax = Makie.Axis(fig[1, 2], title="Correct")
ys = [100, 1]
Makie.barplot!(ax, [1, 1], ys; stack=[1, 2], color=[:red, :green], bar_labels=string.(ys))

display(fig)
```

![barplot-bug](https://github.com/MakieOrg/Makie.jl/assets/46297350/9379eb4e-b15c-4e2d-9220-15285e9a947d)

Pull request description:
fix #3055

Patch:
diff --git a/NEWS.md b/NEWS.md
index ee74c8bd739..9190c0d8d1f 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,7 @@
 # News
 
 ## master
+- Fix grouping of a zero-height bar in `barplot`. Now a zero-height bar shares the same properties of the previous bar, and if the bar is the first one, its height is treated as positive if and only if there exists a bar of positive height or all bars are zero-height. [#3058](https://github.com/MakieOrg/Makie.jl/pull/3058)
 
 - Fixed a bug where Axis still consumes scroll events when interactions are disabled [#3272](https://github.com/MakieOrg/Makie.jl/pull/3272)
 
diff --git a/src/basic_recipes/barplot.jl b/src/basic_recipes/barplot.jl
index 99430e1a4ce..9aba359429b 100644
--- a/src/basic_recipes/barplot.jl
+++ b/src/basic_recipes/barplot.jl
@@ -141,21 +141,24 @@ function stack_from_to(i_stack, y)
 end
 
 function stack_grouped_from_to(i_stack, y, grp)
-
     from = Array{Float64}(undef, length(y))
     to   = Array{Float64}(undef, length(y))
 
-    groupby = StructArray((; grp..., is_pos = y .> 0))
-
+    groupby = StructArray((; grp...))
     grps = StructArrays.finduniquesorted(groupby)
+    last_pos = map(grps) do (g, inds)
+        g => any(y[inds] .> 0) || all(y[inds] .== 0)
+    end |> Dict
+    is_pos = map(y, groupby) do v, g
+        last_pos[g] = iszero(v) ? last_pos[g] : v > 0
+    end
 
+    groupby = StructArray((; grp..., is_pos))
+    grps = StructArrays.finduniquesorted(groupby)
     for (grp, inds) in grps
-
         fromto = stack_from_to(i_stack[inds], y[inds])
-
         from[inds] .= fromto.from
         to[inds] .= fromto.to
-
     end
 
     (from = from, to = to)


Test patch:
diff --git a/test/barplot.jl b/test/barplot.jl
new file mode 100644
index 00000000000..2a1e6bf9a79
--- /dev/null
+++ b/test/barplot.jl
@@ -0,0 +1,130 @@
+@testset "Barplot" begin
+    @testset "label align" begin
+        @testset "automatic" begin
+            # for more info see https://github.com/MakieOrg/Makie.jl/issues/3160
+            # below is the best square angles behavior for bar labels
+
+            al = Makie.automatic
+
+            y_dir, flip = false, false
+            @test Makie.calculate_bar_label_align(al, 0.0, y_dir, flip) ≈ Vec2f(0.0, 0.5)
+            @test Makie.calculate_bar_label_align(al, π, y_dir, flip) ≈ Vec2f(1.0, 0.5)
+            @test Makie.calculate_bar_label_align(al, π/2, y_dir, flip) ≈ Vec2f(0.5, 1.0)
+            @test Makie.calculate_bar_label_align(al, -π/2, y_dir, flip) ≈ Vec2f(0.5, 0.0)
+
+            y_dir, flip = true, false
+            @test Makie.calculate_bar_label_align(al, 0.0, y_dir, flip) ≈ Vec2f(0.5, 0.0)
+            @test Makie.calculate_bar_label_align(al, π, y_dir, flip) ≈ Vec2f(0.5, 1.0)
+            @test Makie.calculate_bar_label_align(al, π/2, y_dir, flip) ≈ Vec2f(0.0, 0.5)
+            @test Makie.calculate_bar_label_align(al, -π/2, y_dir, flip) ≈ Vec2f(1.0, 0.5)
+
+            y_dir, flip = false, true
+            @test Makie.calculate_bar_label_align(al, 0.0, y_dir, flip) ≈ Vec2f(1.0, 0.5)
+            @test Makie.calculate_bar_label_align(al, π, y_dir, flip) ≈ Vec2f(0.0, 0.5)
+            @test Makie.calculate_bar_label_align(al, π/2, y_dir, flip) ≈ Vec2f(0.5, 0.0)
+            @test Makie.calculate_bar_label_align(al, -π/2, y_dir, flip) ≈ Vec2f(0.5, 1.0)
+
+            y_dir, flip = true, true
+            @test Makie.calculate_bar_label_align(al, 0.0, y_dir, flip) ≈ Vec2f(0.5, 1.0)
+            @test Makie.calculate_bar_label_align(al, π, y_dir, flip) ≈ Vec2f(0.5, 0.0)
+            @test Makie.calculate_bar_label_align(al, π/2, y_dir, flip) ≈ Vec2f(1.0, 0.5)
+            @test Makie.calculate_bar_label_align(al, -π/2, y_dir, flip) ≈ Vec2f(0.0, 0.5)
+        end
+
+        @testset "manual" begin
+            input = 0.0, false, false
+            for align in (Vec2f(1.0, 0.5), Point2f(1.0, 0.5), (1.0, 0.5), (1, 0), (1.0, 0))
+                @test Makie.calculate_bar_label_align(align, input...) ≈ Vec2f(align)
+            end
+        end
+
+        @testset "symbols" begin
+            input = 0.0, false, false
+            @test Makie.calculate_bar_label_align((:center, :center), input...) ≈ Makie.calculate_bar_label_align((0.5, 0.5), input...)
+        end
+
+        @testset "error" begin
+            input = 0.0, false, false
+            for align in ("center", 0.5, ("center", "center"))
+                @test_throws ErrorException Makie.calculate_bar_label_align(align, input...)
+            end
+        end
+    end
+
+    @testset "stack" begin
+        x1         = [1, 1,  1,  1]
+        grp_dodge1 = [2, 2,  1,  1]
+        grp_stack1 = [1, 2,  1,  2]
+        y1         = [2, 3, -3, -2]
+
+        x2         = [2, 2,  2,  2]
+        grp_dodge2 = [3, 4,  3,  4]
+        grp_stack2 = [3, 4,  3,  4]
+        y2         = [2, 3, -3, -2]
+
+        from, to = Makie.stack_grouped_from_to(grp_stack1, y1, (; x1 = x1, grp_dodge1 = grp_dodge1))
+        from1 = [0.0, 2.0,  0.0, -3.0]
+        to1   = [2.0, 5.0, -3.0, -5.0]
+        @test from == from1
+        @test to   == to1
+
+        from, to = Makie.stack_grouped_from_to(grp_stack2, y2, (; x2 = x2, grp_dodge2 = grp_dodge2))
+        from2 = [0.0,  0.0,  0.0,  0.0]
+        to2   = [2.0,  3.0, -3.0, -2.0]
+        @test from == from2
+        @test to   == to2
+
+        perm = [1, 4, 2, 7, 5, 3, 8, 6]
+        x = [x1; x2][perm]
+        y = [y1; y2][perm]
+        grp_dodge = [grp_dodge1; grp_dodge2][perm]
+        grp_stack = [grp_stack1; grp_stack2][perm]
+
+        from_test = [from1; from2][perm]
+        to_test = [to1; to2][perm]
+
+        from, to = Makie.stack_grouped_from_to(grp_stack, y, (; x = x, grp_dodge = grp_dodge))
+        @test from == from_test
+        @test to == to_test
+    end
+
+    @testset "zero-height" begin
+        grp_stack = [1, 2, 1, 2]
+        x = [1, 1, 2, 2]
+
+        y = [1.0, 0.0, -1.0, -1.0]
+        from = [0.0, 1.0, 0.0, -1.0]
+        to = [1.0, 1.0, -1.0, -2.0]
+        from_, to_ = Makie.stack_grouped_from_to(grp_stack, y, (; x))
+        @test from == from_
+        @test to == to_
+
+        y = [-1.0, 0.0, -1.0, -1.0]
+        from = [0.0, -1.0, 0.0, -1.0]
+        to = [-1.0, -1.0, -1.0, -2.0]
+        from_, to_ = Makie.stack_grouped_from_to(grp_stack, y, (; x))
+        @test from == from_
+        @test to == to_
+
+        y = [0.0, 1.0, -1.0, -1.0]
+        from = [0.0, 0.0, 0.0, -1.0]
+        to = [0.0, 1.0, -1.0, -2.0]
+        from_, to_ = Makie.stack_grouped_from_to(grp_stack, y, (; x))
+        @test from == from_
+        @test to == to_
+
+        y = [0.0, -1.0, -1.0, -1.0]
+        from = [0.0, 0.0, 0.0, -1.0]
+        to = [0.0, -1.0, -1.0, -2.0]
+        from_, to_ = Makie.stack_grouped_from_to(grp_stack, y, (; x))
+        @test from == from_
+        @test to == to_
+
+        y = [0.0, 1.0, -1.0, -1.0]
+        from = [0.0, 0.0, 0.0, -1.0]
+        to = [0.0, 1.0, -1.0, -2.0]
+        from_, to_ = Makie.stack_grouped_from_to(1:4, y, (; x=ones(4)))
+        @test from == from_
+        @test to == to_
+    end
+end
diff --git a/test/barplot_labels.jl b/test/barplot_labels.jl
deleted file mode 100644
index f99bcf1347e..00000000000
--- a/test/barplot_labels.jl
+++ /dev/null
@@ -1,52 +0,0 @@
-@testset "Barplot label align" begin
-    @testset "automatic" begin
-        # for more info see https://github.com/MakieOrg/Makie.jl/issues/3160
-        # below is the best square angles behavior for bar labels
-
-        al = Makie.automatic
-
-        y_dir, flip = false, false
-        @test Makie.calculate_bar_label_align(al, 0.0, y_dir, flip) ≈ Vec2f(0.0, 0.5)
-        @test Makie.calculate_bar_label_align(al, π, y_dir, flip) ≈ Vec2f(1.0, 0.5)
-        @test Makie.calculate_bar_label_align(al, π/2, y_dir, flip) ≈ Vec2f(0.5, 1.0)
-        @test Makie.calculate_bar_label_align(al, -π/2, y_dir, flip) ≈ Vec2f(0.5, 0.0)
-
-        y_dir, flip = true, false
-        @test Makie.calculate_bar_label_align(al, 0.0, y_dir, flip) ≈ Vec2f(0.5, 0.0)
-        @test Makie.calculate_bar_label_align(al, π, y_dir, flip) ≈ Vec2f(0.5, 1.0)
-        @test Makie.calculate_bar_label_align(al, π/2, y_dir, flip) ≈ Vec2f(0.0, 0.5)
-        @test Makie.calculate_bar_label_align(al, -π/2, y_dir, flip) ≈ Vec2f(1.0, 0.5)
-
-        y_dir, flip = false, true
-        @test Makie.calculate_bar_label_align(al, 0.0, y_dir, flip) ≈ Vec2f(1.0, 0.5)
-        @test Makie.calculate_bar_label_align(al, π, y_dir, flip) ≈ Vec2f(0.0, 0.5)
-        @test Makie.calculate_bar_label_align(al, π/2, y_dir, flip) ≈ Vec2f(0.5, 0.0)
-        @test Makie.calculate_bar_label_align(al, -π/2, y_dir, flip) ≈ Vec2f(0.5, 1.0)
-
-        y_dir, flip = true, true
-        @test Makie.calculate_bar_label_align(al, 0.0, y_dir, flip) ≈ Vec2f(0.5, 1.0)
-        @test Makie.calculate_bar_label_align(al, π, y_dir, flip) ≈ Vec2f(0.5, 0.0)
-        @test Makie.calculate_bar_label_align(al, π/2, y_dir, flip) ≈ Vec2f(1.0, 0.5)
-        @test Makie.calculate_bar_label_align(al, -π/2, y_dir, flip) ≈ Vec2f(0.0, 0.5)
-    end
-
-    @testset "manual" begin
-        input = 0.0, false, false
-        for align in (Vec2f(1.0, 0.5), Point2f(1.0, 0.5), (1.0, 0.5), (1, 0), (1.0, 0))
-            @test Makie.calculate_bar_label_align(align, input...) ≈ Vec2f(align)
-        end
-    end
-
-    @testset "symbols" begin
-        input = 0.0, false, false
-        @test Makie.calculate_bar_label_align((:center, :center), input...) ≈ Makie.calculate_bar_label_align((0.5, 0.5), input...)
-    end
-
-    @testset "error" begin
-        input = 0.0, false, false
-        for align in ("center", 0.5, ("center", "center"))
-            @test_throws ErrorException Makie.calculate_bar_label_align(align, input...)
-        end
-    end
-
-end
\ No newline at end of file
diff --git a/test/runtests.jl b/test/runtests.jl
index 6b0ce30ad40..c812f861316 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -29,11 +29,10 @@ using Makie: volume
     include("makielayout.jl")
     include("figures.jl")
     include("transformations.jl")
-    include("stack.jl")
     include("events.jl")
     include("text.jl")
     include("boundingboxes.jl")
     include("ray_casting.jl")
     include("PolarAxis.jl")
-    include("barplot_labels.jl")
+    include("barplot.jl")
 end
diff --git a/test/stack.jl b/test/stack.jl
deleted file mode 100644
index e03b5779a8b..00000000000
--- a/test/stack.jl
+++ /dev/null
@@ -1,38 +0,0 @@
-using Makie: stack_grouped_from_to
-
-@testset "grouped bar: stack" begin
-    x1         = [1, 1,  1,  1]
-    grp_dodge1 = [2, 2,  1,  1]
-    grp_stack1 = [1, 2,  1,  2]
-    y1         = [2, 3, -3, -2]
-
-    x2         = [2, 2,  2,  2]
-    grp_dodge2 = [3, 4,  3,  4]
-    grp_stack2 = [3, 4,  3,  4]
-    y2         = [2, 3, -3, -2]
-
-    from, to = stack_grouped_from_to(grp_stack1, y1, (; x1 = x1, grp_dodge1 = grp_dodge1))
-    from1 = [0.0, 2.0,  0.0, -3.0]
-    to1   = [2.0, 5.0, -3.0, -5.0]
-    @test from == from1
-    @test to   == to1
-
-    from, to = stack_grouped_from_to(grp_stack2, y2, (; x2 = x2, grp_dodge2 = grp_dodge2))
-    from2 = [0.0,  0.0,  0.0,  0.0]
-    to2   = [2.0,  3.0, -3.0, -2.0]
-    @test from == from2
-    @test to   == to2
-
-    perm = [1, 4, 2, 7, 5, 3, 8, 6]
-    x = [x1; x2][perm]
-    y = [y1; y2][perm]
-    grp_dodge = [grp_dodge1; grp_dodge2][perm]
-    grp_stack = [grp_stack1; grp_stack2][perm]
-
-    from_test = [from1; from2][perm]
-    to_test = [to1; to2][perm]
-
-    from, to = stack_grouped_from_to(grp_stack, y, (; x = x, grp_dodge = grp_dodge))
-    @test from == from_test
-    @test to == to_test
-end