Skip to content

Commit a544318

Browse files
authored
Improved edgelabels for multigraphs (#78)
1 parent b8493cf commit a544318

File tree

3 files changed

+116
-35
lines changed

3 files changed

+116
-35
lines changed

REQUIRE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ LightGraphs
66
PlotUtils
77
NaNMath
88
GeometryTypes
9+
Interpolations

src/GraphRecipes.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ using SparseArrays
1212
using Statistics
1313
using NaNMath
1414
using GeometryTypes
15-
15+
using Interpolations
1616

1717
include("utils.jl")
1818
include("graph_layouts.jl")

src/graphs.jl

Lines changed: 114 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ end
377377
# generate a list of colors, one per segment
378378
segment_colors = get(plotattributes, :linecolor, nothing)
379379
edge_label_array = Vector{Tuple}()
380+
edge_label_box_vertices_array = Vector{Array}()
380381
if !isa(edgelabel, Dict) && !isnothing(edgelabel)
381382
tmp = Dict()
382383
if length(size(edgelabel)) < 2
@@ -392,6 +393,16 @@ end
392393
end
393394
edgelabel = tmp
394395
end
396+
# If the edgelabel dictionary is full of length two tuples, then make all of the
397+
# tuples length three with last element 1. (i.e. a multigraph that has no extra
398+
# edges).
399+
if edgelabel isa Dict
400+
for key in keys(edgelabel)
401+
if length(key) == 2
402+
edgelabel[(key..., 1)] = edgelabel[key]
403+
end
404+
end
405+
end
395406
edge_has_been_seen = Dict()
396407
for edge in zip(source, destiny)
397408
edge_has_been_seen[edge] = 0
@@ -456,16 +467,48 @@ end
456467
push!(yseg, NaN)
457468
else
458469
xpt, ypt = if method != :chorddiagram
459-
control_point(xsi, xdi,
460-
ysi, ydi,
470+
control_point(xsi, x[di],
471+
ysi, y[di],
461472
edge_has_been_seen[(si, di)]*curvature_scalar*sign(si - di))
462473
else
463474
(0.0, 0.0)
464475
end
465-
push!(xseg, xsi, xpt, xdi, NaN)
466-
push!(yseg, ysi, ypt, ydi, NaN)
467-
_3d && push!(zseg, z[si], z[si], z[di], NaN)
468-
push!(l_wg, wi)
476+
xpts = [xsi, xpt, xdi]
477+
ypts = [ysi, ypt, ydi]
478+
t = range(0, stop=1, length=3)
479+
A = hcat(xpts, ypts)
480+
481+
itp = scale(interpolate(A, BSpline(Cubic(Natural(OnGrid())))), t, 1:2)
482+
483+
tfine = range(0, 1, length=30)
484+
xpts, ypts = [itp(t,1) for t in tfine], [itp(t,2) for t in tfine]
485+
if !isnothing(edgelabel) && haskey(edgelabel, (si, di, edge_has_been_seen[(si, di)]))
486+
q = control_point(xsi, x[di],
487+
ysi, y[di],
488+
(edgelabel_offset
489+
+ edge_has_been_seen[(si, di)]*curvature_scalar)*sign(si - di))
490+
push!(edge_label_array,
491+
(q...,
492+
string(edgelabel[(si, di, edge_has_been_seen[(si, di)])]), fontsize))
493+
edge_label_box_vertices = (
494+
annotation_extent(plotattributes,
495+
(q[1], q[2],
496+
edgelabel[(si, di, edge_has_been_seen[(si, di)])],
497+
0.05fontsize)))
498+
if !any(isnan.(q))
499+
push!(edge_label_box_vertices_array, edge_label_box_vertices)
500+
end
501+
end
502+
if method != :chorddiagram && !_3d
503+
append!(xseg, push!(xpts, NaN))
504+
append!(yseg, push!(ypts, NaN))
505+
push!(l_wg, wi)
506+
else
507+
push!(xseg, xsi, xpt, xdi, NaN)
508+
push!(yseg, ysi, ypt, ydi, NaN)
509+
_3d && push!(zseg, z[si], z[si], z[di], NaN)
510+
push!(l_wg, wi)
511+
end
469512
end
470513
else
471514
push!(xseg, xsi, xdi, NaN)
@@ -480,14 +523,25 @@ end
480523
nodewidth = nodewidth_array[si]
481524
if nodeshape == :circle
482525
xpts = [xsi + nodewidth*cos(θ1)/2,
483-
xsi + edge_has_been_seen[(si, di)]*(nodewidth + self_edge_size)*cos(θ1),
484-
xsi + edge_has_been_seen[(si, di)]*(nodewidth + self_edge_size)*cos(θ2),
526+
NaN, NaN, NaN,
485527
xsi + nodewidth*cos(θ2)/2]
528+
xpts[2] = mean([xpts[1], xpts[end]]) + 0.5*(0.5 + edge_has_been_seen[(si, di)])*self_edge_size*cos(θ1)
529+
xpts[3] = mean([xpts[1], xpts[end]]) + edge_has_been_seen[(si, di)]*self_edge_size*cos((θ1 + θ2)/2)
530+
xpts[4] = mean([xpts[1], xpts[end]]) + 0.5*(0.5 + edge_has_been_seen[(si, di)])*self_edge_size*cos(θ2)
486531
ypts = [ysi + nodewidth*sin(θ1)/2,
487-
ysi + edge_has_been_seen[(si, di)]*(nodewidth + self_edge_size)*sin(θ1),
488-
ysi + edge_has_been_seen[(si, di)]*(nodewidth + self_edge_size)*sin(θ2),
532+
NaN, NaN, NaN,
489533
ysi + nodewidth*sin(θ2)/2]
534+
ypts[2] = mean([ypts[1], ypts[end]]) + 0.5*(0.5 + edge_has_been_seen[(si, di)])*self_edge_size*sin(θ1)
535+
ypts[3] = mean([ypts[1], ypts[end]]) + edge_has_been_seen[(si, di)]*self_edge_size*sin((θ1 + θ2)/2)
536+
ypts[4] = mean([ypts[1], ypts[end]]) + 0.5*(0.5 + edge_has_been_seen[(si, di)])*self_edge_size*sin(θ2)
537+
538+
t = range(0, stop=1, length=5)
539+
A = hcat(xpts, ypts)
540+
541+
itp = scale(interpolate(A, BSpline(Cubic(Natural(OnGrid())))), t, 1:2)
490542

543+
tfine = range(0, 1, length=50)
544+
xpts, ypts = [itp(t,1) for t in tfine], [itp(t,2) for t in tfine]
491545
else
492546
_, _,
493547
start_point1,
@@ -503,33 +557,59 @@ end
503557
node_vec_vec_xy[si])
504558

505559
xpts = [start_point1,
506-
xsi + edge_has_been_seen[(si, di)]*(nodewidth + self_edge_size)*cos(θ1),
507-
xsi + edge_has_been_seen[(si, di)]*(nodewidth + self_edge_size)*cos(θ2),
560+
NaN, NaN, NaN,
508561
end_point1]
562+
xpts[2] = mean([xpts[1], xpts[end]]) + 0.5*(0.5 + edge_has_been_seen[(si, di)])*self_edge_size*cos(θ1)
563+
xpts[3] = mean([xpts[1], xpts[end]]) + edge_has_been_seen[(si, di)]*self_edge_size*cos((θ1 + θ2)/2)
564+
xpts[4] = mean([xpts[1], xpts[end]]) + 0.5*(0.5 + edge_has_been_seen[(si, di)])*self_edge_size*cos(θ2)
509565
ypts = [start_point2,
510-
ysi + edge_has_been_seen[(si, di)]*(nodewidth + self_edge_size)*sin(θ1),
511-
ysi + edge_has_been_seen[(si, di)]*(nodewidth + self_edge_size)*sin(θ2),
566+
NaN, NaN, NaN,
512567
end_point2]
568+
ypts[2] = mean([ypts[1], ypts[end]]) + 0.5*(0.5 + edge_has_been_seen[(si, di)])*self_edge_size*sin(θ1)
569+
ypts[3] = mean([ypts[1], ypts[end]]) + edge_has_been_seen[(si, di)]*self_edge_size*sin((θ1 + θ2)/2)
570+
ypts[4] = mean([ypts[1], ypts[end]]) + 0.5*(0.5 + edge_has_been_seen[(si, di)])*self_edge_size*sin(θ2)
571+
572+
t = range(0, stop=1, length=5)
573+
A = hcat(xpts, ypts)
574+
575+
itp = scale(interpolate(A, BSpline(Cubic(Natural(OnGrid())))), t, 1:2)
576+
577+
tfine = range(0, 1, length=50)
578+
xpts, ypts = [itp(t,1) for t in tfine], [itp(t,2) for t in tfine]
513579
end
514580
append!(xseg, push!(xpts, NaN))
515581
append!(yseg, push!(ypts, NaN))
582+
mid_ind = div(length(xpts), 2)
583+
q = [xpts[mid_ind] + edgelabel_offset*cos((θ1 + θ2)/2),
584+
ypts[mid_ind] + edgelabel_offset*sin((θ1 + θ2)/2)]
585+
if !isnothing(edgelabel) && haskey(edgelabel, (si, di, edge_has_been_seen[(si, di)]))
586+
push!(edge_label_array,
587+
(q...,
588+
string(edgelabel[(si, di, edge_has_been_seen[(si, di)])]), fontsize))
589+
edge_label_box_vertices = annotation_extent(plotattributes, (q...,
590+
edgelabel[(si, di, edge_has_been_seen[(si, di)])],
591+
0.05fontsize))
592+
if !any(isnan.(q))
593+
push!(edge_label_box_vertices_array, edge_label_box_vertices)
594+
end
595+
end
516596
end
517597

518598
if isa(segment_colors, ColorGradient)
519599
line_z := segment_colors[i]
520600
end
521601
linewidthattr = get(plotattributes, :linewidth, 1)
522-
seriestype := if si == di
602+
seriestype := if method in (:tree, :buchheim, :chorddiagram)
523603
:curves
524604
else
525-
if curves
526-
:curves
527-
else
528-
if _3d
529-
:path3d
605+
if _3d
606+
if curves
607+
:curves
530608
else
531-
:path
609+
:path3d
532610
end
611+
else
612+
:path
533613
end
534614
end
535615
linewidth --> linewidthattr * edgewidth(si, di, wi)
@@ -542,24 +622,24 @@ end
542622

543623
end
544624
end
545-
546-
for (i, (si, di, wi)) in enumerate(zip(source, destiny, weights))
547-
edge_has_been_seen[(si, di)] += 1
548-
xsi, ysi, xdi, ydi = shorten_segment(x[si], y[si], x[di], y[di], shorten)
549-
if !isnothing(edgelabel) && haskey(edgelabel, (si, di))
550-
@assert !_3d # TODO: make this work in 3D
551-
q = control_point(xsi, xdi, ysi, ydi,
552-
(curvature_scalar + edgelabel_offset)*sign(si - di))
553-
push!(edge_label_array,
554-
(q..., string(edgelabel[(si, di)]), fontsize))
555-
edge_label_box_vertices = annotation_extent(plotattributes, (q[1], q[2],
556-
edgelabel[(si, di)], 0.05fontsize))
557-
if edge_label_box
625+
# The boxes around edge labels are defined as another list of series that sits on top
626+
# of the series for the edges.
627+
edge_has_been_seen = Dict()
628+
for edge in zip(source, destiny)
629+
edge_has_been_seen[edge] = 0
630+
end
631+
if edge_label_box
632+
index = 0
633+
for (i, (si, di, wi)) in enumerate(zip(source, destiny, weights))
634+
edge_has_been_seen[(si, di)] += 1
635+
if !isnothing(edgelabel) && haskey(edgelabel, (si, di, edge_has_been_seen[(si, di)]))
636+
index += 1
558637
@series begin
559638
seriestype := :shape
560639
fillcolor --> get(plotattributes, :background_color, :white)
561640
linewidth := 0
562641
linealpha := 0
642+
edge_label_box_vertices = edge_label_box_vertices_array[index]
563643
([edge_label_box_vertices[1][1], edge_label_box_vertices[1][2],
564644
edge_label_box_vertices[1][2], edge_label_box_vertices[1][1],
565645
edge_label_box_vertices[1][1]],

0 commit comments

Comments
 (0)