Repository: JuliaPlots/Makie.jl (julia)

Issue:
linkaxes! is asymmetric
Zooming on the first axis propagates to the others but zooming on the third does not. Is it intended that `linkaxes!` behave in this asmmetric way?

```jl
using WGLMakie

fig = Figure()
for i in 1:5
  ax = WGLMakie.Axis(fig[i,1])
  density!(ax, rand(1_000))
end
for ax in fig.content
  linkaxes!(fig.content[1], ax)
end
fig
```

Pull request description:
# Description

Fixes #1718.

It wasn't checked correctly that if Axis A was linked to B, and then Axis B to C, that C should also be linked to A.

## Type of change

Delete options that do not apply:

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist

- [ ] Added an entry in NEWS.md (for new features and breaking changes)
- [ ] Added or changed relevant sections in the documentation
- [x] Added unit tests for new algorithms, conversion methods, etc.
- [ ] Added reference image tests for new plotting functions, recipes, visual options, etc.

Patch:
diff --git a/NEWS.md b/NEWS.md
index 3880a8eaf34..a611f37cd32 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -5,7 +5,7 @@
 - Patterns (`Makie.AbstractPattern`) are now supported by `CairoMakie` in `poly` plots that don't involve `mesh`, such as `bar` and `poly` [#2106](https://github.com/JuliaPlots/Makie.jl/pull/2106/).
 - Fixed regression where `Block` alignments could not be specified as numbers anymore [#2108](https://github.com/JuliaPlots/Makie.jl/pull/2108).
 - Added the option to show mirrored ticks on the other side of an Axis using the attributes `xticksmirrored` and `yticksmirrored` [#2105](https://github.com/JuliaPlots/Makie.jl/pull/2105).
-
+- Fixed a bug where a set of `Axis` wouldn't be correctly linked together if they were only linked in pairs instead of all at the same time [#2116](https://github.com/JuliaPlots/Makie.jl/pull/2116).
 
 ## v0.17.7
 
diff --git a/src/makielayout/blocks/axis.jl b/src/makielayout/blocks/axis.jl
index fa8a639f526..0e8043a1f7f 100644
--- a/src/makielayout/blocks/axis.jl
+++ b/src/makielayout/blocks/axis.jl
@@ -1008,56 +1008,41 @@ function adjustlimits!(la)
     return
 end
 
-"""
-    linkxaxes!(a::Axis, others...)
-
-Link the x axes of all given `Axis` so that they stay synchronized.
-"""
-function linkxaxes!(a::Axis, others...)
+function linkaxes!(dir::Union{Val{:x}, Val{:y}}, a::Axis, others...)
     axes = Axis[a; others...]
 
-    for i in 1:length(axes)-1
-        for j in i+1:length(axes)
-            axa = axes[i]
-            axb = axes[j]
+    all_links = Set{Axis}(axes)
+    for ax in axes
+        links = dir isa Val{:x} ? ax.xaxislinks : ax.yaxislinks
+        for ax in links
+            push!(all_links, ax)
+        end
+    end
 
-            if axa ∉ axb.xaxislinks
-                push!(axb.xaxislinks, axa)
-            end
-            if axb ∉ axa.xaxislinks
-                push!(axa.xaxislinks, axb)
+    for ax in all_links
+        links = (dir isa Val{:x} ? ax.xaxislinks : ax.yaxislinks)
+        for linked_ax in all_links
+            if linked_ax !== ax && linked_ax ∉ links
+                push!(links, linked_ax)
             end
         end
     end
-    # update limits because users will expect to see the effect
     reset_limits!(a)
 end
 
 """
-    linkyaxes!(a::Axis, others...)
+    linkxaxes!(a::Axis, others...)
 
-Link the y axes of all given `Axis` so that they stay synchronized.
+Link the x axes of all given `Axis` so that they stay synchronized.
 """
-function linkyaxes!(a::Axis, others...)
-    axes = Axis[a; others...]
-
-    for i in 1:length(axes)-1
-        for j in i+1:length(axes)
-            axa = axes[i]
-            axb = axes[j]
+linkxaxes!(a::Axis, others...) = linkaxes!(Val(:x), a, others...)
 
-            if axa ∉ axb.yaxislinks
-                push!(axb.yaxislinks, axa)
-            end
-            if axb ∉ axa.yaxislinks
-                push!(axa.yaxislinks, axb)
-            end
-        end
-    end
-    # update limits because users will expect to see the effect
-    reset_limits!(a)
-end
+"""
+    linkyaxes!(a::Axis, others...)
 
+Link the y axes of all given `Axis` so that they stay synchronized.
+"""
+linkyaxes!(a::Axis, others...) = linkaxes!(Val(:y), a, others...)
 
 """
 Keeps the ticklabelspace static for a short duration and then resets it to its previous


Test patch:
diff --git a/test/makielayout.jl b/test/makielayout.jl
index 2ae0451090f..a6e7d710290 100644
--- a/test/makielayout.jl
+++ b/test/makielayout.jl
@@ -210,3 +210,28 @@ end
     @test first.(extrema(ax2.finallimits[])) == (1, 5)
     @test last.(extrema(ax2.finallimits[])) == (2, 6)
 end
+
+# issue 1718
+@testset "Linked axes of linked axes" begin
+    # check that if linking axis A and B, where B has already been linked to C, A and C are also linked
+    f = Figure()
+    ax1 = Axis(f[1, 1])
+    ax2 = Axis(f[1, 2])
+    ax3 = Axis(f[1, 3])
+    
+    linkaxes!(ax2, ax3)
+    @test Set(ax1.xaxislinks) == Set([])
+    @test Set(ax2.xaxislinks) == Set([ax3])
+    @test Set(ax3.xaxislinks) == Set([ax2])
+    @test Set(ax1.yaxislinks) == Set([])
+    @test Set(ax2.yaxislinks) == Set([ax3])
+    @test Set(ax3.yaxislinks) == Set([ax2])
+
+    linkaxes!(ax1, ax2)
+    @test Set(ax1.xaxislinks) == Set([ax2, ax3])
+    @test Set(ax2.xaxislinks) == Set([ax1, ax3])
+    @test Set(ax3.xaxislinks) == Set([ax1, ax2])
+    @test Set(ax1.yaxislinks) == Set([ax2, ax3])
+    @test Set(ax2.yaxislinks) == Set([ax1, ax3])
+    @test Set(ax3.yaxislinks) == Set([ax1, ax2])
+end