-
Notifications
You must be signed in to change notification settings - Fork 23
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
(Default)Dict for some attributes #89
Conversation
`nlabels_textsize` and `node_size` interface supports Dict and DefaultDict input
Codecov ReportPatch coverage:
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. Additional details and impacted files@@ Coverage Diff @@
## master #89 +/- ##
==========================================
+ Coverage 79.76% 80.11% +0.34%
==========================================
Files 5 5
Lines 687 704 +17
==========================================
+ Hits 548 564 +16
- Misses 139 140 +1
☔ View full report in Codecov by Sentry. |
Overall I am in favour of doing this. Other things to consider:
Just brainstroming here, but maybe we should do something like prep_arguments(x, size, default) = x
function prep_arguments(x::Observable{AbstractDict}, size, default)
@lift [collect based on $size and $x and $default]
end
function prep_arguments(x::Observable{DefaultDict}, size, _)
@lift [collect based on $size and $x]
end so that in the recipe the code becomes something like |
Sorry i totally forgot about that PR. And also I am an Idiot: i didn't like how it adds another dep, however, we already implicitly depend on I am not a huge fan of the implementation of |
you made some valuable comments above and I didn't have time to process them in detail. Especially looking into the
I would prefer that we first decide on the structure of the code, merge this PR as a prototype, and then we can slowly start working on gradually doing the same for all attributes as a non-brainer. I am already using this feature locally, so sooner or later I will focus on finalizing & merging this PR.. |
for a demo you can try the following: julia> using GraphMakie, GLMakie, Graphs;
julia> gc = circular_ladder_graph(5);
julia> ons = Observable(30);
julia> onf = Observable(30);
julia> graphplot(gc; nlabels=repr.(vertices(gc)), node_size=ons, nlabels_fontsize=onf)
julia> ons[] = 10; # check changes
julia> onf[] = 10; # check changes
julia> ons = Observable(Dict(1=>30, 10=>30));
julia> onf = Observable(Dict(1=>30, 10=>30));
julia> graphplot(gc; nlabels=repr.(vertices(gc)), node_size=ons, nlabels_fontsize=onf)
julia> onf[] = Dict(7=>30); # check changes
julia> ons[] = Dict(7=>30); # check changes
julia> ons = Observable(DefaultDict(70, 1=>30, 10=>30));
julia> onf = Observable(DefaultDict(70, 1=>30, 10=>30));
julia> graphplot(gc; nlabels=repr.(vertices(gc)), node_size=ons, nlabels_fontsize=onf)
julia> ons[] = DefaultDict(20, 10=>70); # check changes
julia> onf[] = DefaultDict(20, 10=>70); # check changes |
|
@hexaeder please let me know what do you think. As already mentioned this PR is a prototype and the other arguments should follow rather easily. The documentation and tests you mention should follow after we reach an agreement :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, example works great!
Actually I also thought about a small change that in my opinion will lead to cleaner code (non-disruptive): Change function getattr(o::Observable, idx, default=nothing) = getattr(o[], idx, default Add function getattr(x, idx, default=nothing)
if x isa AbstractVector && !isa(x, Point)
return x[idx]
elseif x isa DefaultDict || x isa DefaultOrderedDict
return getindex(x, idx)
elseif x isa AbstractDict
return get(x, idx, default)
else
return x === nothing ? default : x
end
end So then the loop can be @lifft begin
...
[getattr($o, i, $default) for i in 1:$size]
...
end intead of @lift begin
...
[getattr(o, i, $default) for i in 1:$size]
...
end which I always find a bit awkward to handle observables as observables and not values inside a What do you think ? |
Co-authored-by: Hans Würfel <[email protected]>
Sure, you may redefine Have you looked into applying those things to the edge attributes yet? There it is a bit more complicated since |
Also, since |
I see. No I still haven't looked into edges.
yes, sound better. |
Since we started this work, I think it's better we might as well finish off all attributes in this PR. Following a list of the attributes and which ones can support the new feature:
node attributes (Scatter)
edge attributes (LineSegements)
arrow attributes (Scatter)
node label attributes (Text)
edge label attributes (Text)
self edge attributes (already use
|
I start to be happy with where this goes and I think we are close to merging. SummaryMainly, this PR extends functionality by supporting arguments of Bug FixesTesting my previous workflow #89 (comment)
Renaming variablesIn lines like Line 206 in 7c2e706
Line 235 in 7c2e706
Line 286 in 7c2e706
the same name as the recipe Observable is given to represent same reference. This was in contrast to how node_color , node_marker , node_strokewidth are meant which are different dependent Observable . I think it's more clear to give them different name. I opted for name_m where "m" stood for modified.
Extension of Dict to all supporting argumentsThe main work of this PR is modifying Use of Dict{Edge} for edge propertiesWe 've talked in the past about supporting SimpleTraits.jl dependency
DocsI updated the docs to talk about the new feature. ReftestsI introduced quite some reftests. VersioningThis is non-breaking but still introduces some obvious new features. Should that be a minor or patch update ? docs/Manifest.tomlThe RemarksI just wanted to take the opportunity to highlight some points element-wise
|
src/recipes.jl
Outdated
color=gp.edge_color, | ||
linewidth=gp.edge_width, | ||
gp.edge_attr...) | ||
color=prep_attributes(gp.edge_color, @lift(getedgeinds($graph, $(gp.edge_color))), dfth.edge_color), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Funcionally I am on board but I am not super happy with this construct. Because a change of gp.edge_color
from the outside would trigger two things:
- recalculation of observable definition within
prep_attributes
- recalculation of
getedgeinsd
output which then triggers recalculation ofprep_attributes
Havn't though this through, but maybe it is worth splitting prep_attributes
in
prep_node_attributes(attr::Observable, graph::Observable{g}, default)
and
prep_edge_attributes(attr::Observable, graph::Observable{g}, default)
@lift begin
if issingleattribute($attr)
isnothing($attr) ? $default : $attr
elseif $attr isa AbstractVector
$attr
else
[getattr($attr, i, $default) for i in getedgeinds($graph, $attr)]
end
end
end
So @lift getedgeinds(..)
for edges and @lift vertice(g..)
could become part of the prep_attributes
lift.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If gp.edge_color
changes then tempobs = @lift(getedgeinds($graph, $(gp.edge_color))
is updated and then the @lift
inside inside prep_arguments
. To me this sounds sequential and all right.
recalculation of
getedgeinsd
output which then triggers recalculation of prep_attributes
Isn't this the same as the first point of recalculating the tempobs
I mentioned above ?
Could you explain your concerns once more ?
I indeed thought about splitting it but I didn't want to be intrusive in our previous design, so I ended up putting another layer of the getedgeinds
thing on top. The problem was that prep_attributes
would need information of the graph like IsDirected
, so we will need to pass in further arguments. In the end I preferred to separate the two functionalities:
- get keys (trivial for vertices)
- call
prep_arguments
with the indices/keys generated
It also looked more modular.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two subproblems:
- even if
gp.edge_color
is an vector, thetempobs
will be updated every single time - the observable created in
prep_attributes
lifts both ongp.edge_color
and on thetempobs
. Therefore, change ofgp.edge_color
will triggerprep_attributes
, thentempobs
and thentempobs
will triggerprep_attributes
again.
I think this function
prep_edge_attributes(attr::Observable, graph::Observable{g}, default)
@lift begin
if issingleattribute($attr)
isnothing($attr) ? $default : $attr
elseif $attr isa AbstractVector
$attr
else
[getattr($attr, i, $default) for i in getedgeinds($graph, $attr)]
end
end
end
elides both of those problems. I think withint prep_edge_attributes
it still makes perfect sense to use the trait dispatches function getedgeinds
. But we get rid of one layer of observables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right! Nice catch! I though there was a dependency tree that decided what/how to execute with Observables
.
However the following code which is similar indeed is executed twice:
using Observables
edge_color = Observable(1)
graph = Observable(2)
tempobs = map(edge_color, graph) do e,g
@info("tempobs update")
e + g
end
prep_attrs = map(edge_color, tempobs) do e, t
@info("prep_attrs update")
e + t
end
myplot = map(prep_attrs) do p
@info("myplot update")
p * 10
end
Changing edge_color[] will execute prepr_attrs twice:
julia> edge_color[] = 3;
[ Info: tempobs update
[ Info: prep_attrs update
[ Info: myplot update
[ Info: prep_attrs update
[ Info: myplot update
I will rewrite it as you suggested together with the other comments.
Also in order to keep it symmetric I would also make a prep_vertex_attributes
that also gets a graph Observable
.
totally agree
see comments on files, i think we can reduce some unecssary updates by splitting prep arguments in two functions for nodes and edges
No, not yet. There is a bit of a tradeoff, currently we only need to annotate with
I see that it is a big feature upgrade but I'd still opt for non-breaking release. I think there is no reason to make everybody bump there versions.
What i normally do is executing
I agree, this changes the structure quit a bit but is relatively simple and should be done in a separate PR. Basicially we would first need to expand/prep the arguments before resolving the automatics, which is currently done first creating those
Agree. I think there is room for a lot of improvement in the package, currently everything is super dynamic and type unstable. |
Regarding Manifest.toml I will delete it then. I just can't wait for JuliaLang/Pkg.jl#492 |
Sorry maybe I missunderstood you: so you were suggesting to add the Manifest in the repo? Normally I just keep my |
e0d58c7
to
e9135ba
Compare
Regarding the element-wise automatic thing, theoretically that would be breaking if we introduce it in a version later than the one of this PR. |
I'll merge this into a feature branch of the repo to get the doc preview build, you should have all the rights to push to that new branch too |
There was some talk about a newer interface taking advantage of the
utils.jl/getattr()
method.#67 (comment)
It happened that I needed it for some arguments and I developed this PR for a possible implementation.
I implemented it just for
nlabels_textsize
andnode_size
.If you agree with the implementation it's rather straight forward to expand it for all other arguments.
DefaultDict
I didn't really like the idea of bringing in a dependency for
DataStructures.jl
, but as much I thought about it, I couldn't find a way to leverage multiple dispatch to handle it transparently.As a result I added one more check to the the
getattr()
method.I think overall it is a nice feature though, to have an
DefaultDict
as an input.Example
Generating the following plots:
Conclusions
This PR is not meant to be merged without any discussion.
Instead, I wanted to suggest a way of implementing the new interface and talk it over.
Personally I believe that if we want to do exactly this for all attributes, the code could get messy.
So I think in the future it would be good to do all these new
lift
s inside a function or a macro.