# Makie - Optimal way of drawing milions of data points

**URL:** <https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748>\
**Category:** Visualization\
**Tags:** makie, glmakie, inspectdr\
**Created:** [March 30, 2022, 2:36pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748 "2022-03-30T14:36:39Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![mkoculak](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkoculak/32/28310_2.png) [@mkoculak](https://discourse.julialang.org/u/mkoculak)\
**Post date:** [March 30, 2022, 2:36pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/1 "2022-03-30T14:36:39Z")

</div>

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

```julia
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.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [March 30, 2022, 2:40pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/2 "2022-03-30T14:40:49Z")

</div>

is scattered plot the right visualization for this?.. maybe a 2D histogram?

---

<div class="post-metadata">

**Author:** ![mkoculak](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkoculak/32/28310_2.png) [@mkoculak](https://discourse.julialang.org/u/mkoculak)\
**Post date:** [March 30, 2022, 2:48pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/3 "2022-03-30T14:48:55Z")

</div>

These are actually lines as the data points are samples from a continuous electrical brain activity.  
And for this purpose we want to see them that way - mostly to check the quality of the signal etc. That is also why it is useful to scroll through the data.

---

<div class="post-metadata">

**Author:** ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)\
**Post date:** [March 30, 2022, 3:26pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/4 "2022-03-30T15:26:30Z")

</div>

I’ve written some code like that for Beacon Biosignals.  
You can actually hook into the zoom rect from Makie, and use that in a timeline to navigate - that way you can create a signal, that only shows the data you’re looking at and switch to a resampled version above some threshold.  
I thought this was better documented, but that’s the struct one can hook into:  
[https://github.com/JuliaPlots/Makie.jl/blob/master/src/makielayout/types.jl#L205=](https://github.com/JuliaPlots/Makie.jl/blob/master/src/makielayout/types.jl#L205=)  
[https://makie.juliaplots.org/stable/examples/layoutables/axis/index.html#axis\_interaction](https://makie.juliaplots.org/stable/examples/layoutables/axis/index.html#axis_interaction)  
I can see if I find some time to extract / open source some of the viewer code.

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [March 30, 2022, 3:33pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/5 "2022-03-30T15:33:53Z")

</div>

Or you hook into `ax.finallimits` directly to choose a subset of data that fits in there. It might be faster to make one `series` plot and then just extract a view of a matrix of your data (`series` will reform it to a vector with NaN in between).

---

<div class="post-metadata">

**Author:** ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)\
**Post date:** [March 30, 2022, 4:04pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/6 "2022-03-30T16:04:24Z")

</div>

There’s also  
[https://github.com/org-arl/InteractiveViz.jl](https://github.com/org-arl/InteractiveViz.jl)  
which is meant to help visualizing large signals

---

<div class="post-metadata">

**Author:** ![mkoculak](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkoculak/32/28310_2.png) [@mkoculak](https://discourse.julialang.org/u/mkoculak)\
**Post date:** [March 30, 2022, 8:52pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/7 "2022-03-30T20:52:05Z")

</div>

> [@sdanisch](#):
>
> for Beacon Biosignals

Yeah, I have seen many EEG related stuff on their github, but wanted to give it a try from scratch, to better understand Makie and Julia. Also haven’t seen ploting stuff there.

@sdanisch & @jules - thanks for the suggestions! Will look into those options, although I do not immediately see how to do the “hooking into”.  
@jules - do have an example of the one `series` option?

@baggepinnen - I think I’ve seen it somewhere in the past, but forgot about. Will try out, thanks!

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [March 30, 2022, 9:17pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/8 "2022-03-30T21:17:52Z")

</div>

> [@mkoculak](#):
>
> thanks for the suggestions! Will look into those options, although I do not immediately see how to do the “hooking into”.

I mean something like

```julia
on(ax.finallimits) do lims
   compute_which_data_to_show_and_update_observables(lims)
end

```

And for the series, it takes a matrix argument so you could determine which rows to display and what column steps and then do `data[rows, cols]` and pass that to `series`.

---

<div class="post-metadata">

**Author:** ![mkoculak](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkoculak/32/28310_2.png) [@mkoculak](https://discourse.julialang.org/u/mkoculak)\
**Post date:** [March 30, 2022, 9:18pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/9 "2022-03-30T21:18:54Z")

</div>

And on a different note:  
is there an easy way to measure the performance/speed of rendering?  
some function measuring frame rate etc.?  
I tried to google it, but didn’t find anything useful.

---

<div class="post-metadata">

**Author:** ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)\
**Post date:** [March 31, 2022, 8:55am UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/10 "2022-03-31T08:55:47Z")

</div>

With nvidia, you can turn on an FPS counter with geforce experience overlays. That’d be the easiest I suppose.  
AMD might have the same.  
Otherwise, you can jump in the code and log the timings here:  
[https://github.com/JuliaPlots/Makie.jl/blob/master/GLMakie/src/rendering.jl#L29=](https://github.com/JuliaPlots/Makie.jl/blob/master/GLMakie/src/rendering.jl#L29=)  
If you make it optional and configurable via `GLMakie.set_window_config!` a PR would be appreciated a lot 🙂

---

<div class="post-metadata">

**Author:** ![MA\_Laforge](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ma_laforge/32/385_2.png) [@MA\_Laforge](https://discourse.julialang.org/u/MA_Laforge)\
**Post date:** [April 1, 2022, 2:42am UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/11 "2022-04-01T02:42:33Z")

</div>

@mkoculak: Looks like Makie is a good, flexible platform that suits most of your needs.

Glad to see you’ve managed to put in event hooks! I’m really kind of sad I haven’t yet had time to investigate Makie further myself.

### Alternate solution for this SPECIFIC problem

For this _specific_ problem, you might want to give InspectDR a try, though. It was built expressly for this kind of problem: _ **Plotting multiple time-domain signals containing a large amount of datapoints** _.

### Interactivity & bindkeys

InspectDR isn’t as flexible as Makie, but:

- Automatically handles mouse events & bindkeys for pan, zoom, etc.
- Has an x-axis slider on the bottom to move pan along your time data.
- See: [https://github.com/ma-laforge/InspectDR.jl/blob/master/doc/input\_bindings.md](https://github.com/ma-laforge/InspectDR.jl/blob/master/doc/input_bindings.md)
- Zooming is a bit tricky with mutliple y-strips.   
TIP: CTRL+V on the selected y-strip snaps-back y-extents to data range.

### Example

Here is an example you can try to quickly InspectDR is adequate for your immediate problem:

 ![image](https://global.discourse-cdn.com/julialang/original/3X/7/e/7e3b14f1a1e611edb244b96ccdf6ab112396ff09.png)

```julia
using InspectDR
using Colors

#Input parameters
NSIG = 15
TMAX = 60 #seconds
NSAMPLES = 1_000_000
#NSAMPLES = 10_000_000
t = range(0, TMAX, length=NSAMPLES)
fList = range(1, step=1, length=NSIG) #Hz

red = RGB24(1, 0, 0)
line_default = line(color=red, width=2)

@info("NSAMPLES = $NSAMPLES")
Δt = Float64(t.step)
@info("Δt = $Δt")

#Generate data
@info("Calculating data array...")
sigA = Array{Float64}(undef, NSIG, NSAMPLES)
for (i, f) in enumerate(fList)
	sigA[i,:] = sin.(2pi*f * t)
end

#Generate plots
nstrips = NSIG
@info("Computing plots...")
mplot = InspectDR.Multiplot(title="Multi-signal time-domain plot")
plot = InspectDR.transientplot(:lin, title="EEG")
plot = add(mplot, InspectDR.transientplot([:lin for i in 1:nstrips],
	title="", #No title - use strip labels instead
	ylabels=["Potential (V)" for i in 1:nstrips]
))
#Zero-out gap between y-strips:
plot.layout[:valloc_mid] = 0
plot.xext = InspectDR.PExtents1D(min=18, max=28) #Zoom in on time span

t = collect(t) #InspectDR only supports Vector{Float64}
for (i, f) in enumerate(fList)
	sig_i = collect(sigA[i,:])
	wfrm = add(plot, t, sig_i, id="Signal $i", strip=i)
	wfrm.line = line_default #Set color, thickness, etc
end

gplot = display(InspectDR.GtkDisplay(), mplot)

```

### Alternative APIs

There are also 2 alternative high-level APIs that can be used with InspectDR if you are curious:

- Plots.jl (Increase time-to-first-plot & slower to run overall due to overhead)
- CMDimData.jl: [https://github.com/ma-laforge/CMDimData.jl](https://github.com/ma-laforge/CMDimData.jl)   
(Increased time-to first plot & more to learn, sadly.)

Note that `CMDimData.jl` is probably not worth learning unless you need to post-process signal data without thinking too much about low-level data structures.

---

<div class="post-metadata">

**Author:** ![mkoculak](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkoculak/32/28310_2.png) [@mkoculak](https://discourse.julialang.org/u/mkoculak)\
**Post date:** [April 1, 2022, 11:39pm UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/12 "2022-04-01T23:39:01Z")

</div>

This actually sounds like a fun idea. 🙂

I played around with the fps\_renderloop and have some questions (probably related to GLFW functions calls). Where should I start the conversation about it? As a draft of a PR on github or here/slack, etc?

@MA_Laforge thanks for the suggestion! I have seen the InspectDR package couple of days ago and will certainly give it a try.

---

<div class="post-metadata">

**Author:** ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)\
**Post date:** [April 22, 2022, 9:29am UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/13 "2022-04-22T09:29:38Z")

</div>

> As a draft of a PR on github or here/slack, etc?

Draft PR sounds great 🙂

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [October 17, 2022, 1:42am UTC](https://discourse.julialang.org/t/makie-optimal-way-of-drawing-milions-of-data-points/78748/14 "2022-10-17T01:42:13Z")

</div>

This is _almost_ exactly what I’ve been looking for lately!

Unfortunately if I modify `finallimits`, my settings don’t get stored (such that future interactions, such as zooming, undo my modifications). But `targetlimits` does the trick.

I made it so scrolling sideways (common on trackpads) would move the graph left and right:

```julia
on(events(fig).scroll, priority=50) do scrl
    if scrl[1] ≠ 0.0 # if sideways scroll
        for ax ∈ axes
            lims = ax.targetlimits
            lims[] = typeof(lims[])((lims[].origin...,) .+ (-lims[].widths[1]*scrl[1]*0.01, 0.0), (lims[].widths...,))
        end
    end
end

```
