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

Implement Align (fix #79) #81

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -104,6 +104,23 @@ nothing #hide
```
![spring animation](spring_animation.mp4)

## Aligning Layouts

Any two-dimensional layout can have its principal axis aligned along a desired angle (default, zero angle), by nesting an "inner" layout into an [`Align`](@ref) layout.
For example, we may align the above `Spring` layout of the small cubical graph along the horizontal or vertical axes:

```@docs
Align
```
```@example layouts
g = smallgraph(:cubical)
layout = Spring(Ptype=Float32)
f, ax, p = graphplot(g, layout=layout) # horizontal alignment (zero-angle by default)
hidedecorations!(ax); hidespines!(ax); ax.aspect = DataAspect(); f #hide
f, ax, p = graphplot(g, layout=Align(layout, pi/2)) # vertical alignment
hidedecorations!(ax); hidespines!(ax); ax.aspect = DataAspect(); f #hide
```

## Stress Majorization
```@docs
Stress
1 change: 1 addition & 0 deletions src/NetworkLayout.jl
Original file line number Diff line number Diff line change
@@ -238,5 +238,6 @@ include("stress.jl")
include("spectral.jl")
include("shell.jl")
include("squaregrid.jl")
include("align.jl")

end
47 changes: 47 additions & 0 deletions src/align.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export Align

"""
Align(inner_layout :: AbstractLayout{2, Ptype}, angle :: Ptype = zero(Ptype))

Align the vertex positions of `inner_layout` so that the principal axis of the resulting
layout makes an `angle` with the **x**-axis.

Only supports two-dimensional inner layouts.
"""
@addcall struct Align{Ptype, L <: AbstractLayout{2, Ptype}} <: AbstractLayout{2, Ptype}
inner_layout :: L
angle :: Ptype
function Align(inner_layout::L, angle::Real) where {L <: AbstractLayout{2, Ptype}} where Ptype
new{Ptype, L}(inner_layout, convert(Ptype, angle))

Check warning on line 15 in src/align.jl

Codecov / codecov/patch

src/align.jl#L14-L15

Added lines #L14 - L15 were not covered by tests
end
end
Align(inner_layout::AbstractLayout{2, Ptype}) where Ptype = Align(inner_layout, zero(Ptype))

Check warning on line 18 in src/align.jl

Codecov / codecov/patch

src/align.jl#L18

Added line #L18 was not covered by tests

function layout(algo::Align{Ptype, <:AbstractLayout{2, Ptype}}, adj_matrix::AbstractMatrix) where {Ptype}

Check warning on line 20 in src/align.jl

Codecov / codecov/patch

src/align.jl#L20

Added line #L20 was not covered by tests
# compute "inner" layout
rs = layout(algo.inner_layout, adj_matrix)

Check warning on line 22 in src/align.jl

Codecov / codecov/patch

src/align.jl#L22

Added line #L22 was not covered by tests

# align the "inner" layout to have its principal axis make `algo.angle` with x-axis
# step 1: compute covariance matrix for PCA analysis:
# C = ∑ᵢ (rᵢ - ⟨r⟩) (rᵢ - ⟨r⟩)ᵀ
# for vertex positions rᵢ, i = 1, …, N, and center of mass ⟨r⟩ = N⁻¹ ∑ᵢ rᵢ.
centerofmass = sum(rs) / length(rs)
C = zeros(SMatrix{2, 2, Ptype})
for r in rs
C += (r - centerofmass) * (r - centerofmass)'
end
vs = eigen(C).vectors

Check warning on line 33 in src/align.jl

Codecov / codecov/patch

src/align.jl#L28-L33

Added lines #L28 - L33 were not covered by tests

# step 2: pick principal axis (largest eigenvalue → last eigenvalue/vector)
axis = vs[:, end]
axis_angle = atan(axis[2], axis[1])

Check warning on line 37 in src/align.jl

Codecov / codecov/patch

src/align.jl#L36-L37

Added lines #L36 - L37 were not covered by tests

# step 3: rotate positions `rs` so that new axis is aligned with `algo.angle`
s, c = sincos(-axis_angle + algo.angle)
R = @SMatrix [c -s; s c] # [cos(θ) -sin(θ); sin(θ) cos(θ)]
for (i, r) in enumerate(rs)
rs[i] = Point2{Ptype}(R * r) :: Point2{Ptype}
end

Check warning on line 44 in src/align.jl

Codecov / codecov/patch

src/align.jl#L40-L44

Added lines #L40 - L44 were not covered by tests

return rs

Check warning on line 46 in src/align.jl

Codecov / codecov/patch

src/align.jl#L46

Added line #L46 was not covered by tests
end