I wrapped a really nice plotting extension called implot, a C++ plugin for Dear ImGui. With some clutch help from @Gnimuc and the magic of BinaryBuilder.jl this package can be imported into a Julia session that’s using CImGui.jl and everything should “JustWork”.
The plotting is straight forward and, notably, quite fast even with 100s of thousands of points. There’s also optional arguments to do strided/offset array access for interleaved data. Generally a nice solution for anyone that wants to do real-time visualization of analog or digital sensor data (in my case, electrophysiology signals).
If there’s interest from the community, I might go to the effort of documenting it a little more thoroughly and throwing it up on the registry.Update: ImPlot.jl is now on the registry–you can just ]add ImPlot now. The underlying C++ lib has also been updated to include a couple extra features (namely shaded line plots).
This is great. Also, not so great as I am now thoroughly confused about what plotting library to switch over to from ggplot. There is Plots.jl which is the most native and FTOP is reduced significantly in 1.5, there is VegaLite.jl which somewhat follows the Grammar of Graphics of ggplot, there is gnuplot.jl which is my top contender to switch to, Makie.jl which seems more tedious than necessary, but packs some serious power, and a few others I can’t recall.
But this is great. Will definitely try it out in the next few days
This is great. I needed a real-time vizualisation lib, so today I decided “Let me ask on the Julia discourse about the situation of real-time vizualisation libs. No, first let me search to see what has currently been announced.”
So I found this thread. Started 11 hours ago! How lucky
Now excuse my breathtaking ignorance, but… From what I understand this is a wrapper to some C or C++ libs (great!). But the demo (which @gnimuc posted) is on the browser. How does that work?
Those demos are done independently by some other people, who got it running in the browser with emscripten + WebGL afaik. The Julia libraries here are calling natively compiled binaries and use an OpenGL backend. The performance is slower in the browser, but it’s also easy to give someone a link to see what’s going on.
Sure. I was waiting for another tag from implot since there’s some added plot styles and options. That has now been done and the maintainer of cimplot has regenerated bindings to match. The implot C++ API was changing a lot, so that’s why I hesitated registering it–there were breaking changes happening. However, it looks like the dust has settled now. I have to rebuild the JLL, add in the extra API functions and it should be more or less ready for registration. I’ll post an update when I get around to it.
What exactly is the difference between ImPlot.jl and CImGui.jl? Looking at the demo.jl files of both, it seems they both CImGui’s interface.
I have an application that produces real-time metrics and I would like to use ImPlot.jl/CImGui.jl to plot them. Question: what is the lowest effort way of putting my real-time metrics into a real-time plot? I was looking at ImPlot’s demo.jl trying to figure out how the interface works, but it looks like all the interesting plots in that example are actually implemented in some C++ code. How do I plot real-time from Julia?
Copy pasting from a sample somewhere I could get a basic real-time plot working in Julia:
rt_buffer = CircularBuffer{Float64}(1000)
( ... )
if show_main_window
@c CImGui.Begin("Plot Window", &show_main_window)
push!(rt_buffer, rand())
y = collect(rt_buffer)
ImPlot.SetNextPlotLimits(0.0,1000,0.0,1.0, ImGuiCond_Once)
if (ImPlot.BeginPlot("Foo", "x1", "y1", CImGui.ImVec2(-1,300)))
ImPlot.PlotLine(y)
ImPlot.EndPlot()
end
CImGui.End()
end
However, I’d rather not have to copy my CircularBuffer to a Vector every time I want to plot it. Is it possible to avoid copying? Doing ImPlot.PlotLine(rt_buffer) throws:
┌ Error: Error in renderloop!
│ exception = conversion to pointer not defined for CircularBuffer{Float64}
└ @ Main ~/demo.jl:147
Alright, I just discovered that all the functions that demo.jl uses are implemented in files in examples/.
Excuse the drivel.
Ok, another question. What is the recommended way of integrating an app with the display?
My app has a loop, and so does the display (e.g. this).
So should I put the display update inside the app’s loop and implicitly let the app’s loop control the display rate? Or should I have the app running in one thread, in display on another thread, and pass data from the former to the latter by using some shared data structure? Or how?
Thanks for the response. I still haven’t figured it out though, I guess because I don’t understand yield().
My application loop is schematically:
initialize!(state)
while true
update!(state)
end
Now, by copy pasting code around I’ve made it work by not using my app loop and using your renderer loop instead. I.e. this works:
initialize!(state)
try
while !GLFW.WindowShouldClose(window)
GLFW.PollEvents()
ImGui_ImplOpenGL3_NewFrame()
ImGui_ImplGlfw_NewFrame()
CImGui.NewFrame()
# below is my code (schematically)
# inspired by copy pasting from your examples
update!(state)
y = collect(state)
ImPlot.SetNextPlotLimits(0.0,1000,0.0,1.0, ImGuiCond_Once)
if (ImPlot.BeginPlot("Foo", "x1", "y1", CImGui.ImVec2(-1,300)))
ImPlot.PlotLine(y)
ImPlot.EndPlot()
end
CImGui.End()
# end of my code
CImGui.Render()
GLFW.MakeContextCurrent(window)
display_w, display_h = GLFW.GetFramebufferSize(window)
glViewport(0, 0, display_w, display_h)
glClearColor(0.2, 0.2, 0.2, 1)
glClear(GL_COLOR_BUFFER_BIT)
ImGui_ImplOpenGL3_RenderDrawData(CImGui.GetDrawData())
GLFW.MakeContextCurrent(window)
GLFW.SwapBuffers(window)
end
catch e
@error "Error in renderloop!" exception=e
Base.show_backtrace(stderr, catch_backtrace())
finally
ImGui_ImplOpenGL3_Shutdown()
ImGui_ImplGlfw_Shutdown()
CImGui.DestroyContext(ctx)
GLFW.DestroyWindow(window)
end
Obviously I would like to decouple my update! for your renderer. Exactly how should this be re-organized? From the example that you linked to (the one with the renderloop with a yield()) I’m guessing that somehow I’m supposed to wrap my code into a ui() function and pass it to the renderloop, but I can’t see exactly how. Additionally, where would I put the initialize!(state) line? If I just put it inside ui() then it would be run on every loop.
@freeman I just wrote a short step-by-step post to demonstrate how to do state-management with CImGui.jl. Hopefully this will answer your questions. Feel free to ask questions at Github Discussions.