Hi,
first of all, big thanks to authors and contributors of Makie!
This is the package that dragged me into Julia and it works so well for my use cases, that it feels illegal.
I am working with long time series (EEG signal), typical array will have dimensions 80 x 7_000_000 (channels x time points) and I am using GLMakie to make an interactive plot visualizing the raw signal for inspection.
Main idea is to show a subset of electrodes in a short time window (e.g. 10 seconds of signal for 20 channels) and the user can add/subtract channels for view, browse through time or increase the displayed time span.
Right now, I am initially drawing everything with visible=false
, then making visible only the subset that will be displayed. This still draws the whole time series (outside the limits of the plot), but it felt more responsive than reading and plotting chunks of data on each key press (but this was almost one year ago).
Here is a MWE of such a plot
using GLMakie
using Statistics
function plot(data)
fig = Figure(resolution = (1920,1080));
ax = fig[1,1] = Axis(fig);
step = Observable(1:20000)
chanRange = Observable(1:20)
on(events(fig).keyboardbutton) do event
if event.action in (Keyboard.press, Keyboard.repeat)
event.key == Keyboard.left && step_back(ax, step, chanRange)
event.key == Keyboard.right && step_forw(ax, step, chanRange)
event.key == Keyboard.page_down && chans_less(data, ax, step, chanRange)
event.key == Keyboard.page_up && chans_more(data, ax, step, chanRange)
end
return Consume(false)
end
xlims!(ax, step.val[1], step.val[end])
ylims!(ax, -10*chanRange.val[end]-5, -10*chanRange.val[1]+5)
draw(data, ax, step)
visible(data, ax, chanRange)
display(fig);
end
function draw(data, ax::Axis, step::Observable)
for i=1:(size(data)[2])
lines!(ax, step, @lift(data[$step,i].-mean(data[$step,i]).-10i), color="black", visible=false)
end
end
function visible(data, ax::Axis, chanRange::Observable)
for j=1:(size(data)[2])
if j in chanRange.val
ax.scene.plots[j+1].visible=true
else
ax.scene.plots[j+1].visible=false
end
end
end
function step_back(ax::Axis, step::Observable, chanRange::Observable)
step[] = step.val.-100
xlims!(ax, step.val[1], step.val[end])
end
function step_forw(ax::Axis, step::Observable, chanRange::Observable)
step[] = step.val.+100
xlims!(ax, step.val[1], step.val[end])
end
function chans_less(data, ax::Axis, step::Observable, chanRange::Observable)
chanRange[] = chanRange.val.start:chanRange.val.stop-1
visible(data, ax, chanRange)
ylims!(ax, -10*chanRange.val[end]-5, -10*chanRange.val[1]+5)
end
function chans_more(data, ax::Axis, step::Observable, chanRange::Observable)
chanRange[] = chanRange.val.start:chanRange.val.stop+1
visible(data, ax, chanRange)
ylims!(ax, -10*chanRange.val[end]-5, -10*chanRange.val[1]+5)
end
data = rand(5000000,80);
plot(data)
My question is:
Is there a more optimal Makie-way to update a plot with so many points?
This approach starts to feel slugish around 1,5 milion visible points (which is crazy good compared to things I tried in Python), but maybe there are some optimizations that could push this limit further.