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

Labels inside node markers #128

Merged
merged 8 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
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
Binary file added assets/reftests.jl-20.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/reftests.jl-21.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/reftests.jl-22.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/reftests.jl-23.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/reftests.jl-24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions docs/examples/plots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ offsets[1] = Point2f(0, 0.3)
p.nlabels_offset[] = offsets
autolimits!(ax)
@save_reference f #hide
#=
## Inner Node Labels
Sometimes it is prefered to show the labels inside the node. For that you can use the `ilabels` keyword arguments.
The Node sizes will be changed according to the size of the labels.
=#
g = cycle_digraph(3)
f, ax, p = graphplot(g;
ilabels=[1, L"\sum_{i=1}^n \alpha^i", "a label"],
arrow_shift=:end)
xlims!(ax, (-1.5, 1.3))
ylims!(ax, (-2.3, 0.7))
hidedecorations!(ax); hidespines!(ax); ax.aspect = DataAspect()
f #hide

# ## Adding Edge Labels
g = barabasi_albert(6, 2; seed=42)
Expand Down Expand Up @@ -141,6 +154,14 @@ f, ax, p = graphplot(g; arrow_size, arrow_shift)
hidedecorations!(ax); hidespines!(ax); ax.aspect = DataAspect()
@save_reference f #hide

#=
There is a special case for `arrow_shift=:end` which moves the arrows close to the next node:
=#
g = cycle_digraph(3)
f, ax, p = graphplot(g; arrow_shift=:end, node_size=20, arrow_size=20)
hidedecorations!(ax); hidespines!(ax); ax.aspect = DataAspect()
f #hide

#=
## Self edges

Expand Down
27 changes: 24 additions & 3 deletions docs/examples/reftests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,17 @@ graphplot(fig[2,1],
)
@save_reference fig

# ##self loop with waypoints
# ## 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

# ##shift arrows to nodes
# ## Shift arrows to nodes
fig, ax, p=graphplot(SimpleDiGraph(ones(2,2)),node_size=50,arrow_size=20,curve_distance=0.5,arrow_shift=:end)
@save_reference fig

# ##update shifts
# ### update shifts
g = SimpleDiGraph(3)
add_edge!(g, 1, 1); add_edge!(g, 1, 2); add_edge!(g, 2, 1); add_edge!(g, 2, 3); add_edge!(g, 3, 1);

Expand Down Expand Up @@ -174,3 +174,24 @@ fig, ax, p = graphplot(g; arrow_shift=:end, layout=SquareGrid(cols=2),
edge_color=:red)
xlims!(-.5,1.5); ylims!(-3.5,.5)
@save_reference fig

# ## Inner node labels

fig, ax, p = graphplot(cycle_digraph(3), ilabels=[1, L"\sum_{i=1}^n \alpha^i", "a label"], node_marker=Circle)
@save_reference fig

# Interact with `arrow_shift=:end`

fig, ax, p = graphplot(cycle_digraph(3), ilabels=[1, L"\sum_{i=1}^n \alpha^i", "a label"], node_marker=Circle, arrow_shift=:end)
@save_reference fig

# Update observables
p[:ilabels][][1] = "1111"
notify(p[:ilabels])
@save_reference fig

p[:ilabels_fontsize][] = 10
@save_reference fig

p[:node_color][] = :red
@save_reference fig
94 changes: 79 additions & 15 deletions src/recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ underlying graph and therefore changing the number of Edges/Nodes.
## Attributes
### Main attributes
- `layout=Spring()`: function `AbstractGraph->Vector{Point}` or `Vector{Point}` determines the base layout
- `node_color=scatter_theme.color`
- `node_size=scatter_theme.markersize`
- `node_marker=scatter_theme.marker`
- `node_color=automatic`:
Defaults to `scatter_theme.color` in absence of `ilabels`.
- `node_size=automatic`:
Defaults to `scatter_theme.markersize` in absence of `ilabels`. Otherwise choses node size based on `ilabels` size.
- `node_marker=automatic`:
Defaults to `scatter_theme.marker` in absence of `ilabels`.
- `node_strokewidth=automatic`
Defaults to `scatter_theme.strokewidth` in absence of `ilabels`.
- `node_attr=(;)`: List of kw arguments which gets passed to the `scatter` command
- `edge_color=lineseg_theme.color`: Color for edges.
- `edge_width=lineseg_theme.linewidth`: Pass a vector with 2 width per edge to
Expand All @@ -56,6 +61,16 @@ data space.
- `nlabels_fontsize=labels_theme.fontsize`
- `nlabels_attr=(;)`: List of kw arguments which gets passed to the `text` command

### Inner node labels
Put labels inside the marker. If labels are provided, change default attributes to
`node_marker=Circle`, `node_strokewidth=1` and `node_color=:gray80`.
The `node_size` will match size of the `ilabels`.

- `ilabels=nothing`: `Vector` with label for each node
- `ilabels_color=labels_theme.color`
- `ilabels_fontsize=labels_theme.fontsize`
- `ilabels_attr=(;)`: List of kw arguments which gets passed to the `text` command
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should note that ilabels_attr cannot be set interactively, and users should manipulate the underlying text plot directly!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by that? The idea behind this was basically that users can forward own observables into the underlying plot function without explicitly exposing each and every attribute of the scatter/line/text plots in the graphplots recipe

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

g = complete_graph(3)
visible = Observable(false)
graphplot(g; node_attr=(;visible))
visible[] = true #interactive update

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be the same as nlabels_attr, elabels_attr, node_attr, edge_attr, arrow_attr, right?

Probably this issue should be discussed separately?

Copy link
Collaborator Author

@greimel greimel Apr 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asinghvi17 do you mean that _, _, p = graphplot(...), p[:ilabel_attr] = ... doesn't work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_, _, p = graphplot(...), p[:ilabel_attr] = ... doesn't work

I did mean that - the usage which @hexaeder suggested should work fine. This wouldn't work because a reference to the attributes at the time of plot creation is splatted, and changing the attributes afterwards wouldn't have an effect.

I guess we should probably clarify that somewhere? It's not a blocker for the PR by any means, just something I noticed.


### Edge labels
The base position of each label is determined by `src + shift*(dst-src)`. The
additional `distance` parameter is given in pixels and shifts the text away from
Expand Down Expand Up @@ -134,9 +149,10 @@ Waypoints along edges:
Attributes(
layout = Spring(),
# node attributes (Scatter)
node_color = scatter_theme.color,
node_size = scatter_theme.markersize,
node_marker = scatter_theme.marker,
node_color = automatic,
node_size = automatic,
node_marker = automatic,
node_strokewidth = automatic,
node_attr = (;),
# edge attributes (LineSegements)
edge_color = lineseg_theme.color,
Expand All @@ -156,6 +172,11 @@ Waypoints along edges:
nlabels_offset = nothing,
nlabels_fontsize = labels_theme.fontsize,
nlabels_attr = (;),
# inner node labels
ilabels = nothing,
ilabels_color = labels_theme.color,
ilabels_fontsize = labels_theme.fontsize,
ilabels_attr = (;),
# edge label attributes (Text)
elabels = nothing,
elabels_align = (:center, :center),
Expand Down Expand Up @@ -213,6 +234,49 @@ function Makie.plot!(gp::GraphPlot)

node_pos = gp[:node_pos]

# plot inside labels
scatter_theme = default_theme(sc, Scatter)

if gp[:ilabels][] !== nothing
positions = node_pos

ilabels_plot = text!(gp, positions;
text=@lift(string.($(gp.ilabels))),
align=(:center, :center),
color=gp.ilabels_color,
fontsize=gp.ilabels_fontsize,
gp.ilabels_attr...)

translate!(ilabels_plot, 0, 0, 1)

node_size = lift(ilabels_plot.plots[1][1], gp.ilabels_fontsize) do glyphcollections, ilabels_fontsize
map(glyphcollections) do gc
rect = Rect2f(boundingbox(gc, Quaternion((1,0,0,0))))
norm(rect.widths) + 0.1 * ilabels_fontsize
end
end
else
node_size = @lift $(gp.node_size) === automatic ? scatter_theme.markersize[] : gp.node_size[]
end

node_color = @lift if $(gp.node_color) === automatic
gp.ilabels[] !== nothing ? :gray80 : scatter_theme.color[]
else
$(gp.node_color)
end

node_marker = @lift if $(gp.node_marker) === automatic
gp.ilabels[] !== nothing ? Circle : scatter_theme.marker[]
else
$(gp.node_marker)
end

node_strokewidth = @lift if $(gp.node_strokewidth) === automatic
gp.ilabels[] !== nothing ? 1.0 : scatter_theme.strokewidth[]
else
$(gp.node_strokewidth)
end

# create array of pathes triggered by node_pos changes
# in case of a graph change the node_position will change anyway
gp[:edge_paths] = lift(node_pos, gp.selfedge_size,
Expand All @@ -228,8 +292,8 @@ function Makie.plot!(gp::GraphPlot)
gp.edge_attr...)

# plot arrow heads
arrow_shift = lift(edge_paths, to_px, gp.arrow_shift, gp.node_size, gp.arrow_size) do paths, tpx, shift, nsize, asize
update_arrow_shift(graph[], gp, paths, tpx)
arrow_shift = lift(edge_paths, to_px, gp.arrow_shift, node_marker, node_size, gp.arrow_size) do paths, tpx, shift, nmarker, nsize, asize
update_arrow_shift(graph[], gp, paths, tpx, node_marker, node_size)
end
arrow_pos = @lift if !isempty(edge_paths[])
broadcast(interpolate, edge_paths[], $arrow_shift)
Expand All @@ -252,12 +316,12 @@ function Makie.plot!(gp::GraphPlot)
markerspace = :pixel,
visible = arrow_show,
gp.arrow_attr...)

# plot vertices
vertex_plot = scatter!(gp, node_pos;
color=gp.node_color,
marker=gp.node_marker,
markersize=gp.node_size,
color=node_color,
marker=node_marker,
markersize=node_size,
strokewidth=node_strokewidth,
gp.node_attr...)

# plot node labels
Expand Down Expand Up @@ -678,16 +742,16 @@ end
Checks `arrow_shift` attr so that `arrow_shift = :end` gets transformed so that the arrowhead for that edge
lands on the surface of the destination node.
"""
function update_arrow_shift(g, gp, edge_paths::Vector{<:AbstractPath{PT}}, to_px) where {PT}
function update_arrow_shift(g, gp, edge_paths::Vector{<:AbstractPath{PT}}, to_px, node_markers, node_sizes) where {PT}
arrow_shift = Vector{Float32}(undef, ne(g))

for (i,e) in enumerate(edges(g))
t = getattr(gp.arrow_shift, i, 0.5)
if t === :end
j = dst(e)
p0 = getattr(gp.node_pos, j)
node_marker = getattr(gp.node_marker, j)
node_size = getattr(gp.node_size, j)
node_marker = getattr(node_markers, j)
node_size = getattr(node_sizes, j)
arrow_marker = getattr(gp.arrow_marker, i)
arrow_size = getattr(gp.arrow_size, i)
d = distance_between_markers(node_marker, node_size, arrow_marker, arrow_size)
Expand Down