Skip to content

Commit

Permalink
Merge pull request #106 from hdavid16/master
Browse files Browse the repository at this point in the history
Allow selfloop to have waypoints. Update docstring
  • Loading branch information
hexaeder authored Jan 24, 2023
2 parents c74d3f9 + 7520d5f commit 402e15e
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 57 deletions.
Binary file added assets/reftests.jl-13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/examples/reftests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,9 @@ graphplot(fig[2,1],
edge_plottype = :beziersegments,
)
@save_reference fig

# ##self loop with waypoints
g1 = SimpleDiGraph(1)
add_edge!(g1, 1, 1) #add self loop
fig, ax, p = graphplot(g1, layout = _ -> [(0,0)], waypoints = [[(1,-1),(1,1),(-1,1),(-1,-1)]])
@save_reference fig
4 changes: 2 additions & 2 deletions src/beziercurves.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function Path(P::Vararg{PT, N}; tangents=nothing, tfactor=.5) where {PT<:Abstrac
# first command, recalculate WP if tangent is given
first_wp = WP[1]
if tangents !== nothing
p1, p2, t = P[1], P[2], normalize(tangents[1])
p1, p2, t = P[1], P[2], normalize(Pointf(tangents[1]))
dir = p2 - p1
d = tfactor * norm(dir t)
first_wp = PT(p1+d*t)
Expand All @@ -214,7 +214,7 @@ function Path(P::Vararg{PT, N}; tangents=nothing, tfactor=.5) where {PT<:Abstrac
# last command, recalculate last WP if tangent is given
last_wp = (P[N] + WP[N-1])/2
if tangents !== nothing
p1, p2, t = P[N-1], P[N], normalize(tangents[2])
p1, p2, t = P[N-1], P[N], normalize(Pointf(tangents[2]))
dir = p2 - p1
d = tfactor * norm(dir t)
last_wp = PT(p2-d*t)
Expand Down
123 changes: 68 additions & 55 deletions src/recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ the edge.
- `edge_plottype=Makie.automatic()`: Either `automatic`, `:linesegments` or
`:beziersegments`. `:beziersegments` are much slower for big graphs!
Self edges / loops:
Self edges / loops:
- `selfedge_size=Makie.automatic()`: Size of self-edge-loop (dict/vector possible).
- `selfedge_direction=Makie.automatic()`: Direction of self-edge-loop as `Point2` (dict/vector possible).
- `selfedge_size=Makie.automatic()`: Size of selfloop (dict/vector possible).
- `selfedge_direction=Makie.automatic()`: Direction of center of the selfloop as `Point2` (dict/vector possible).
- `selfedge_width=Makie.automatic()`: Opening of selfloop in rad (dict/vector possible).
- Note: If valid waypoints are provided for selfloops, the selfedge attributes above will be ignored.
High level interface for curvy edges:
Expand Down Expand Up @@ -104,14 +105,21 @@ Tangents interface for curvy edges:
Higher factor means bigger radius. Can be tuple per edge to specify different
factor for src and dst.
- Note: Tangents are ignored on selfloops if no waypoints are provided.
Waypoints along edges:
- `waypoints=nothing`
Specify waypoints for edges. This parameter should be given as a vector or
dict. Waypoints will be crossed using natural cubic splines. The waypoints may
or may not include the src/dst positions.
- `waypoint_radius=nothing`: If number (dict/vector possible) bent lines within radius of waypoints.
- `waypoint_radius=nothing`
If the attribute `waypoint_radius` is `nothing` or `:spline` the waypoints will
be crossed using natural cubic spline interpolation. If number (dict/vector
possible), the waypoints won't be reached, instead they will be connected with
straight lines which bend in the given radius around the waypoints.
"""
@recipe(GraphPlot, graph) do scene
# TODO: figure out this whole theme business
Expand Down Expand Up @@ -356,61 +364,48 @@ function find_edge_paths(g, attr, pos::AbstractVector{PT}) where {PT}
paths = Vector{AbstractPath{PT}}(undef, ne(g))

for (i, e) in enumerate(edges(g))
if src(e) == dst(e) # selfedge
size = getattr(attr.selfedge_size, i)
direction = getattr(attr.selfedge_direction, i)
width = getattr(attr.selfedge_width, i)
paths[i] = selfedge_path(g, pos, src(e), size, direction, width)
else # no selfedge
p1, p2 = pos[src(e)], pos[dst(e)]
tangents = getattr(attr.tangents, i)
tfactor = getattr(attr.tfactor, i)
waypoints::Vector{PT} = getattr(attr.waypoints, i, PT[])

cdu = getattr(attr.curve_distance_usage, i)
if cdu === true
p1, p2 = pos[src(e)], pos[dst(e)]
tangents = getattr(attr.tangents, i)
tfactor = getattr(attr.tfactor, i)
waypoints::Vector{PT} = getattr(attr.waypoints, i, PT[])
if !isnothing(waypoints) && !isempty(waypoints) #remove p1 and p2 from waypoints if these are given
waypoints[begin] == p1 && popfirst!(waypoints)
waypoints[end] == p2 && pop!(waypoints)
end

cdu = getattr(attr.curve_distance_usage, i)
if cdu === true
curve_distance = getattr(attr.curve_distance, i, 0.0)
elseif cdu === false
curve_distance = 0.0
elseif cdu === automatic
if is_directed(g) && has_edge(g, dst(e), src(e))
curve_distance = getattr(attr.curve_distance, i, 0.0)
elseif cdu === false
else
curve_distance = 0.0
elseif cdu === automatic
if is_directed(g) && has_edge(g, dst(e), src(e))
curve_distance = getattr(attr.curve_distance, i, 0.0)
else
curve_distance = 0.0
end
end
end

if !isnothing(waypoints) && !isempty(waypoints) #there are waypoints
# the waypoints may already include the endpoints
waypoints[begin] == p1 && popfirst!(waypoints)
waypoints[end] == p2 && pop!(waypoints)

radius = getattr(attr.waypoint_radius, i, nothing)

if isempty(waypoints) || radius === nothing || radius === :spline
paths[i] = Path(p1, waypoints..., p2; tangents, tfactor)
elseif radius isa Real
paths[i] = Path(radius, p1, waypoints..., p2)
else
throw(ArgumentError("Invalid radius $radius for edge $i!"))
end
elseif !isnothing(tangents)
paths[i] = Path(p1, p2; tangents, tfactor)
elseif PT<:Point2 && !iszero(curve_distance)
d = curve_distance
s = norm(p2 - p1)
γ = 2*atan(2 * d/s)
a = (p2 - p1)/s * (4*d^2 + s^2)/(3s)

m = @SMatrix[cos(γ) -sin(γ); sin(γ) cos(γ)]
c1 = PT(p1 + m*a)
c2 = PT(p2 - transpose(m)*a)

commands = [MoveTo(p1), CurveTo(c1, c2, p2)]
paths[i] = BezierPath(commands)
else # straight line
paths[i] = Path(p1, p2)
if !isnothing(waypoints) && !isempty(waypoints) #there are waypoints
radius = getattr(attr.waypoint_radius, i, nothing)
if radius === nothing || radius === :spline
paths[i] = Path(p1, waypoints..., p2; tangents, tfactor)
elseif radius isa Real
paths[i] = Path(radius, p1, waypoints..., p2)
else
throw(ArgumentError("Invalid radius $radius for edge $i!"))
end
elseif src(e) == dst(e) # selfedge
size = getattr(attr.selfedge_size, i)
direction = getattr(attr.selfedge_direction, i)
width = getattr(attr.selfedge_width, i)
paths[i] = selfedge_path(g, pos, src(e), size, direction, width)
elseif !isnothing(tangents)
paths[i] = Path(p1, p2; tangents, tfactor)
elseif PT<:Point2 && !iszero(curve_distance)
paths[i] = curved_path(p1, p2, curve_distance)
else # straight line
paths[i] = Path(p1, p2)
end
end

Expand Down Expand Up @@ -462,7 +457,7 @@ end
"""
selfedge_path(g, pos, v, size, direction, width)
Return a Path for the
Return a BezierPath for a selfedge.
"""
function selfedge_path(g, pos::AbstractVector{<:Point2}, v, size, direction, width)
vp = pos[v]
Expand Down Expand Up @@ -519,6 +514,24 @@ function selfedge_path(g, pos::AbstractVector{<:Point3}, v, size, direction, wid
error("Self edges in 3D not yet supported")
end

"""
curved_path(p1, p2, curve_distance)
Return a BezierPath for a curved edge (not selfedge).
"""
function curved_path(p1::PT, p2::PT, curve_distance) where {PT}
d = curve_distance
s = norm(p2 - p1)
γ = 2*atan(2 * d/s)
a = (p2 - p1)/s * (4*d^2 + s^2)/(3s)

m = @SMatrix[cos(γ) -sin(γ); sin(γ) cos(γ)]
c1 = PT(p1 + m*a)
c2 = PT(p2 - transpose(m)*a)

return BezierPath([MoveTo(p1), CurveTo(c1, c2, p2)])
end

"""
edgeplot(paths::Vector{AbstractPath})
edgeplot!(sc, paths::Vector{AbstractPath})
Expand Down

0 comments on commit 402e15e

Please sign in to comment.