Not able to update the canvas of a GR plot within Gtk windows

(using Gtk.jl https://github.com/JuliaGraphics/Gtk.jl)

I am able to produce a window which has a widget (button) and a canvas which is ‘drawing’ a GR plot. The problem is that on display, calling the ‘draw’ function again, does not result in an updated plot. The button can listen for signals of “clicked” and that function handler can respond by calling the draw function, but that does not update the plot in the canvas. Now, if the window is resized the plot then updates and redraws/refreshes.

This code here displays the effect:

using Gtk.ShortNames, Gtk.GConstants
using Printf
using GR

#counter to track the draw calls
global drawInt = 0

function draw(widget)
    ctx = Gtk.getgc(widget)
    ENV["GKS_WSTYPE"] = "142"
    ENV["GKSconid"] = @sprintf("%lu", UInt64(ctx.ptr))
    #random data to plot that gets shifted with every draw call
    scatter( randn(100) .+ drawInt , rand(100) .+ drawInt )
    global drawInt
    drawInt += 1
    println("in draw fn drawInt=$(drawInt)")    
end
#put a canvas for the plot and a button
win = Window("Gtk") |> (bx = Box(:v))
canvas = Canvas(600, 450)
push!(bx, canvas)
bt = Button("ok")
push!(bx, bt)
function on_button_clicked(w)
    ctx = Gtk.getgc(canvas)
    draw(ctx)
    println("in button fn drawInt=$(drawInt)")    
end
signal_connect(on_button_clicked, bt, "clicked")
canvas.draw = draw

Gtk.showall(win)


There are some println statement so that the calls to the draw and button functions can be tracked as the canvas plot fails to update. Press the button multiple times to see the calls and updates to the global drawInt counter. Now, resize the window with the mouse, or click on another application window pane (eg browser) and you will see plot space update (the counter is a shift for the dummy data).

How can the update to the plot be performed without requiring a window pane event?

(from the Gtk3 developer guide something similar is stated->
https://developer.gnome.org/gtk3/stable/ch01s05.html, The contents of a widget often need to be partially or fully redrawn, e.g. when another window is moved and uncovers part of the widget, or when the window containing it is resized. It is also possible to explicitly cause part or all of the widget to be redrawn, by calling gtk_widget_queue_draw() or its variants. GTK+ takes care of most of the details by providing a ready-to-use cairo context to the ::draw signal handler)

i thought i replied to you on slack. I have a working example in https://gist.github.com/lobingera/40851a7fbc35850c00a9319444ca4e27

  • you need to register a drawing function for the canvas (widget) (and i recommend it to have a different name than ‘draw’)
  • call draw(canvas) for redrawing
  • the example updates counter on every button click and draws a scaled rectangle
1 Like

Thanks for the reply.

  1. I have run the code, and see the same effect as I have described. I don’t know if it runs differently on your system (I’m on Debian9), but the button registers the clicks, the function scopes appear to be running their inner statements, but the update to that red rectangle you put, does not appear until a window pane event/signal is received

  2. that is with Cairo, and the goal is to have the ability to include figures from GR or Plots in general so that numerical investigations in a gui do not require ground up work from Cairo

I don’t know if it is the same on your system, but running your code multiple times shows that the drawing of the rescalled rectangle only occurs after a window pane event signal and not due to a widget event signal

As you imply, the canvas draw function is invoked by events like resizing the parent window,
so probably that is not where you should update the plot contents. Consider maintaining global buffers for data to be plotted, and have the draw function use that data, but otherwise as you have it above. The on_button_clicked method would then update the global data buffers, then force a redraw.

1 Like

I am unsure how to do that within the Gtk.jl package. The Gtk3 developer guides at something along the lines of what you say with gtk_widget_queue_draw() but I don’t see how I can do that within Gtk.jl. Or can I access that global buffer with a direct call?

I just meant some global Julia variables (I suspect there are ways to attach data to Gtk widgets, but have no experience there). This is what I had in mind:

canvas = nothing

#counter to track the draw calls
drawInt = 0


const xvals = Ref(zeros(0))
const yvals = Ref(zeros(0))

function newplot(widget)
    global drawInt
    drawInt += 1
    #random data to plot that gets shifted with every call
    xvals[] = randn(100) .+ drawInt
    yvals[] = rand(100) .+ drawInt
end

function mydraw(widget)
    ctx = Gtk.getgc(widget)
    ENV["GKS_WSTYPE"] = "142"
    ENV["GKSconid"] = @sprintf("%lu", UInt64(ctx.ptr))
    scatter( xvals[], yvals[] )
    println("in draw fn drawInt=$(drawInt)")
end
function on_button_clicked(w)
    newplot(w)
    ctx = Gtk.getgc(canvas)
    draw(canvas)
    println("in button fn drawInt=$(drawInt)")
end
function runme()
    #put a canvas for the plot and a button
    win = Window("Gtk") |> (bx = Box(:v))
    global canvas
    canvas = Canvas(600, 450)
    push!(bx, canvas)
    bt = Button("ok")
    push!(bx, bt)
    canvas.draw = mydraw
    newplot(canvas)
    signal_connect(on_button_clicked, bt, "clicked")
    Gtk.showall(win)
end

Edit: this builds on the suggestions from @lobingera

1 Like

That worked! now I don’t know exactly what changed to make it different.

1)Why did the scope of the variables make a difference?

2)If there is the const declaration, then how is there allowed to be an update (because it is a vector?)?

  1. Did we need 2 draw functions? could newplot and mydraw be a single function?

Did you run the example in an new julia session (include)? As there are already a list of functions with the name draw defined (in your code and in Gtk) this might lead to some ambiguity.
I remember having a similar problem and it was back then subject of ‘wrong’ binding of draw.
Gtk.draw(widget) calls reveal on the widget and that ccalls gtk_widget_queue_draw so it puts a signal into Gtk’s queue for redrawing. That’s actually the same way how the resize/uncover works.
Technically GR/Plots also renders with Cairo (as actually you cannot do anything else in GtkCanvas) - so you use the same interface.

1 Like

Yes, a new julia session was used for your example.

The answer by @Ralph_Smith works and appears to address the underlying issue

After doing some more exploration I see the meaning of your sentence

"Technically GR/Plots also renders with Cairo (as actually you cannot do anything else in GtkCanvas) - so you use the same interface."

Question, would it maybe be a more flexible approach to consider the canvas widgets as regions for Cairo drawings? And somehow convert plots into Cairo drawings? Where maybe we go from a plot to imshow? Would that maybe be too slow? but it would avoid problems correct?

Sorry, i cannot follow. What do you want to do? What do you want to achieve?

Can I bypass GR (Plots) completely, and produce ‘scatter’ plots and line plots using Cairo instead?

Can I take a scatter plot object and produce a translation of the plot into a Cairo set of primitives to draw?

Someone else ran your code and it… did solve the problem as well; thanks alot*! I don’t know why it exhibited different behavior at first. The only thing is that the resizing still affects the size of the rectangle

I’m not fully sure if i understand you question(s)

  1. but there are alternative plotting packages like Gadfly and Winston that allow you to plot scatter and line plots using Cairo in the process.
  2. if you are interested in seeing the sequence of path construction and filling/drawing in Cairo you can use a CairoScriptSurface to write coordinates and operators to a file.
1 Like

I just used globals to make things visible to all methods. It would be much better to use a composite widget which includes all the needed data as properties; that would take more effort to design.

The const variables here are Ref types, and act like pointers so the vector content can be replaced. This pattern can help for efficiency and prevents some programmer error. This usage is not well documented, and should perhaps be discouraged since it’s not naturally thread-safe.

Although one could probably consolidate the functions, the separation simplifies the logic.

1 Like