How to update Makie.jl plot based on ComputeGraph?

I am using the ComputeGraph feature of Makie.jl for the first time. After adding widgets (observables) as nodes in the graph, I also added nodes for the plot objects:

# initialize scene with elements
# that do not depend on inputs
fig = Figure()
ax = Axis(fig[1,1])
sl = Slider(fig[1, 2], ...)

# create compute graph mapping
# inputs to outputs and plots
G = ComputeGraph()
add_input!(G, :w, sl.value)
map!(w -> [cos(w .* x) for x in 0:2pi], G, :w, :y)
map!(y -> lines!(ax, y), G, :y, :lineplot)

# initialize the elements in the
# scene that depend on inputs
G.lineplot[]

If I request the value of the node with the plot object, I get the display of the plot in the scene, as shown above. How to update these plot objects on every change to the mapped widgets? I am looking for the correct on idiom, something like:

on(any_change_to_input_nodes) do
  # update plots
  G.lineplot[]
  G.scatplot[]
  ...
end

Or a different method is recommended to get automatic plot updates whenever changes are made to input Observable nodes stored in a ComputeGraph?

it’s thought to be created in recipes as part of the plots compute graph…or the nodes are supposed to be handed to the plot, not created inside the computegraph…they ideally use pure functions. for creating plots on a signal, observables are the right tool;)

Is it possible to query all input observables of a compute graph? That would be enough for current purposes. I see that we can get the nodes with graph.inputs, but they are not the observables that can be used in on listeners.

I mean the problem is, that you’re not using compute graph for what it’s designed.
You should just use the input observables directly, if you’re not hooking the graph up to the actual plot (by passing the node to the plot).

Do you have an example in which the ComputeGraph is used correctly? I’m still trying to figure out when it is useful.

That’s what its mainly used for: Makie.jl/Makie/src/basic_recipes/spy.jl at master · MakieOrg/Makie.jl · GitHub

I see. The plot recipe is a compute graph. Thank you for sharing.

@sdanisch on the other hand, the ComputeGraph automates the update logic when lift has multiple inputs?

Wouldn’t it be useful to have a new function that takes a compute graph as input and add listeners to all input changes that force the re-computation of child nodes? Something like

on(graph.inputs) do
  update_graph(graph)
end

That would save end-users from writing on blocks manually, which leads to a lot of headache. Does it make sense? I tried to rewrite my example in terms of simple Makie.@lift blocks, but whenever I change the widgets, I get error messages because some observables have been updated to the new problem size while others are still outdated (race condition?).

The whole idea is that it’s polling.
It usually gets polled inside the plot!
so:

G = ComputeGraph()
add_input!(G, :w, sl.value)
map!(w -> [cos(w .* x) for x in 0:2pi], G, :w, :y)
lines!(ax, G.y)

This gets correctly polled whenever a frame gets rendered.
You should show the concrete example, your example shouldnt have a length problem?

I don’t know why, but I can’t make it work (with automatic updates) without adding on blocks that call the plot functions like lines! again. Placing the plot inside the node seems to work fine:

map!(G, :y, :lineplot) do y
  empty!(ax)
  lines!(ax, y)
end

I then have to loop over all observables and add on blocks that call G.lineplot[].

The final application is illustrated here:

Every time I interact wih Observable I struggle a bit. That is why I am trying to learn the ComputeGraph for basic apps like the one above.

I don’t know why, but I can’t make it work (with automatic updates) without adding on blocks that call the plot functions like lines! again.

When you call map!(f, graph, inputs, outputs) the compute graph saves connectivity information. When you update some input graph.input = val, that information is used to mark any dependent as outdated. When you fetch a value graph.output[], that information is used again with the outdated tag to find all inputs that need updates.

When you call lines!(..., G.y) like in Simons example, Makie effectively calls map!(identity, plot.attributes, G.y, :arg1). This creates a connection between the outside graph G and the compute graph in of lines plot. Outdated flags can propagate from G down to plot.attributes and update resolution can travel back up into Gfrom plot.attributes.

If you do

map!(G, :y, :lineplot) do y
  empty!(ax)
  lines!(ax, y)
end

that’s not the case. You’re creating a node :lineplot in G that (at this point) connects to nothing else. The lines plot in the node is created with the data G.y[], so it has no connectivity information either. The compute graph of lines and G don’t know each other, so G can’t tell lines that something is outdated and lines can’t ask G for updated inputs.

If you do somehow create a connection here I would not be surprised if you get buggy updates, errors, crashes or deadlocks. You would be deleting something that’s trying to update after all. (You wouldn’t with an on(slider.value) because that’s an update pushed from events, not pulled from rendering)

I will try to create a MWE. I remember trying to pass a compute graph node to colorrange in a heatmap! call and it not working. Probably an unrelated issue.