# Makie: Calculation on Observables?

**URL:** https://discourse.julialang.org/t/makie-calculation-on-observables/135099
**Category:** Visualization
**Tags:** makie, observables
**Created:** [January 16, 2026, 3:57pm UTC](https://discourse.julialang.org/t/makie-calculation-on-observables/135099 "2026-01-16T15:57:55Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![ryofurue](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ryofurue/32/24531_2.png) [@ryofurue](https://discourse.julialang.org/u/ryofurue)
#### Post date: [January 16, 2026, 3:57pm UTC](https://discourse.julialang.org/t/makie-calculation-on-observables/135099/1 "2026-01-16T15:57:55Z")

</div>

I have a relatively complicated function to produce values and a relatively complicated function to plot the values. The combination already works to produce a still image.

Now, I want to produce an animation changing a parameter, say time `t`. Both functions depend on `t`. Both functions include some arithmetics on `t`.

When I turn this program into one using `Observable`s as below, the complier complains that some functions I use aren’t defined on `Observable{Float64}`, which the new `t` is.

The following is a much simplified toy program that (I hope correctly) duplicates my problem. Below, the function `single()` succeeds in producing the still image but `anime()` fails for the reason stated above.

How should I proceed? I’m stuck because I don’t understand Observables after all.

```julia-auto
"Returns a vector"
function func(t, xs)
  sin.(t/100.0 .+ xs)
end

function plot_func(xs, fs; t)
  lab = "$(t/(24*60*60)) days"
  fig = Figure()
  ax = Axis(fig[1,1]; title = lab)
  lines!(ax, xs, fs)
  return fig
end

function single() # -> Works
  xs = -5.0:(5/32):5.0
  t = 0.0
  fs = func.(t, xs)
  fig = plot_func(xs, fs; t = t)
  display(fig)
end

# Fails because Observable{Float64} cannot be used in calculations
function anime()
  xs = -5.0:(5/32):5.0
  t = Observable(0.0) # -> t/100.0 fails?
  fs = Observable(func.(t, xs))
  fig = plot_func(xs, fs; t = t)
  nrange = 0:20
  record(fig, "tmp.mp4", nrange; framerate=2) do n
    t.val = 40.0*n
    fs[] = func.(t, xs)
  end
end

single()
anime() # -> fails

```

---

<div class="post-metadata">

### Author: ![contradict](https://avatars.discourse-cdn.com/v4/letter/c/ac91a4/32.png) [@contradict](https://discourse.julialang.org/u/contradict)
#### Post date: [January 16, 2026, 5:55pm UTC](https://discourse.julialang.org/t/makie-calculation-on-observables/135099/2 "2026-01-16T17:55:08Z")

</div>

To operate on the value of an Observable, you need to use `map` or `@lift` to unwrap it, use the value, and create a new observable. There is no need to explicitly evaluate `fs` inside the animation, setting `t` pushes the update through.

```julia-auto
using GLMakie

"Returns a vector"
function func(t, xs)
  sin.(t/100.0 .+ xs)
end

function plot_func(xs, fs; t)
   lab = map(t) do t
       "$(t/(24*60*60)) days"
   end
  fig = Figure()
  ax = Axis(fig[1,1]; title = lab)
  lines!(ax, xs, fs)
  return fig
end

function single() # -> Works
  xs = -5.0:(5/32):5.0
  t = 0.0
  fs = func.(t, xs)
  fig = plot_func(xs, fs; t )
  display(fig)
end

function anime()
  xs = -5.0:(5/32):5.0
  t = Observable(0.0)
  fs = @lift( func.($t, xs) )
  fig = plot_func(xs, fs; t)
  nrange = 0:20
  record(fig, "tmp.mp4", nrange; framerate=2) do n
      t[] = 40.0*n
  end
end

single()
anime()

```

---

<div class="post-metadata">

### Author: ![ryofurue](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ryofurue/32/24531_2.png) [@ryofurue](https://discourse.julialang.org/u/ryofurue)
#### Post date: [January 17, 2026, 6:41am UTC](https://discourse.julialang.org/t/makie-calculation-on-observables/135099/3 "2026-01-17T06:41:37Z")

</div>

> [@contradict](#):
>
> To operate on the value of an Observable, you need to use `map` or `@lift` to unwrap it, use the value, and create a new observable.

Aha! Thank you for your kind explanation! That’s exactly what I wanted to know. (I thought: This `Observable` thing looks like a monad or functor or something along the lines . . . there must be some functionality to unwrap and wrap . . . )

Does that mean that within the `Makie` code, `map` is always used before any calculations because any of the arguments can by an `Observable` ?

Or is there a way to write your code that works whether the arguments are `Observable`s or not without using `map` all the time? I can imagine that you could build a mechanism to automatically generate the unwrap-calculate-wrap version of a function:

```julia-auto
f(x::Float64) = 2*x
#--> generate the following
f(x::Observable{Float64}) = @lift(f($x))

```

Another question: Is the macro form `@lift( . . . $t . . . )` provided only as a convenience? Or is it more general than `map`? Perhaps you can use multiple Observables within `@lift` ?

For reference, the following is my final solution. (I’ve also fixed my little awkward `func` function.)

```julia-auto
using GLMakie

function func(t, x)
  sin(t/100.0 + x)
end

"`t` can be an Observable."
function plot_func(xs, fs; t)
  lab = map(t) do tval # unrap t, do the calculation, and wrap the result.
    "$(tval/(24*60*60)) days"
  end
  fig = Figure()
  ax = Axis(fig[1,1]; title = lab)
  lines!(ax, xs, fs)
  return fig
end

function single()
  xs = -5.0:(5/32):5.0
  t = 0.0
  fs = func.(t, xs)
  fig = plot_func(xs, fs; t = t)
  display(fig)
end

function anime()
  xs = -5.0:(5/32):5.0
  t = Observable(0.0)
# fs = @lift( func.($t, xs) ) # same as below?
  fs = map(tval -> func.(tval,xs), t)
  fig = plot_func(xs, fs; t = t)
  nrange = 0:20
  record(fig, "tmp.mp4", nrange; framerate=2) do n
    t[] = 40.0*n
  end
end

single()
anime()

```
