Engee 文档

可观察的

该页面正在翻译中。

Makie中的交互和动画使用https://juliagizmos.github.io/Observables.jl/stable/[可观察的。jl]. 一个 可观察的 是一个容器对象,其存储值可以交互更新。 您可以创建在observable发生变化时执行的函数。 您还可以创建每当其他可观察对象更改时其值都会更新的可观察对象。 通过这种方式,您可以轻松构建动态和交互式可视化。

在这一页,你将学习如何 可观察的s管道和基于事件的交互系统工作。 除此之外,还有一个关于如何使用Makie制作交互式可视化(或动画)的视频教程。jl和 可观察的 系统:

<无翻译></无翻译>

可观察的 结构

A 可观察的 是允许以交互方式更新其值的对象。 让我们从创建一个开始:

using GLMakie, Makie

x = Observable(0.0)
Each `Observable` has a type parameter, which determines what kind of objects it can store. If you create one like we did above, the type parameter will be the type of the argument. Keep in mind that sometimes you want a wider parametric type because you intend to update the `Observable` later with objects of different types. You could for example write:

```julia
x2 = Observable{Real}(0.0)
x3 = Observable{Any}(0.0)

在处理可以以不同形式出现的属性时,通常会出现这种情况。 例如,颜色可以是 :红色RGB(1,0,0).

触发更改

使用空索引表示法更改Observable的值:

x[] = 3.34

这不是特别有趣。 但是,Observable允许您注册每当Observable的内容更改时执行的函数。

一个这样的功能是 . 让我们在我们的Observable上注册一些东西 x 和改变 x价值:

on(x) do x
    println("New value of x is $x")
end

x[] = 5.0

"X的新值为5.0"

请注意,如果您更新了 可观察的 使用就地语法(例如 img[]。=着色剂"红色"),您需要手动 通知(img) 来触发功能。

注意a中的所有注册函数 可观察的 注册的顺序同步执行。 这意味着如果您一个接一个地更改两个Observable,则第一个更改的所有效果都将在第二个更改之前发生。

有两种方法可以访问a的值 可观察的. 您可以使用索引语法或 to_值 功能:

value = x[]
value = to_value(x)

使用的优点 to_值 你可以在处理可观察值或正常值的情况下使用它。 在后一种情况下, to_值 只是返回原始值,就像 身份认同.

链接 可观察的s与 升降机

您可以根据另一个Observable创建一个Observable 升降机. 第一个论点 升降机 必须是一个函数,在给定输入可观察值的值的情况下计算输出可观察值。

f(x) = x^2
y = lift(f, x)
Now, whenever `x` changes, the derived `Observable` `y` will immediately hold the value `f(x)`. In turn, `y`'s change could trigger the update of other observables, if any have been connected. Let's connect one more observable and update x:

```julia
z = lift(y) do y
    -y
end

x[]=10.0

@显示x[]
@显示y[]
@显示z[]
New value of x is 10.0
x[] = 10.0
y[] = 100.0
z[] = -100.0

如果 x 变化,也是如此 y 然后 z.

但请注意,这一变化 y 不会改变 x. 不能保证链式Observables总是同步的,因为它们可以在不同的地方发生突变,甚至避开了变化触发机制。

y[] = 20.0

@show x[]
@show y[]
@show z[]
x[] = 10.0
y[] = 20.0
z[] = -20.0

速记宏 升降机

使用时 升降机,它可以是繁琐的引用每个参与 可观察的 至少三次,一次作为论据 升降机,一次作为闭包的参数,即第一个参数,至少一次在闭包内部:

x = Observable(rand(100))
y = Observable(rand(100))
z = lift((x, y) -> x .+ y, x, y)

为了避免这种情况,你可以使用 @升降机 宏。 你只需写出你想要做的操作 可观察的s和prepend每个 可观察的 带有美元符号$的变量。 宏将提升它找到的每个可观察变量,并将整个表达式包装在闭包中。 相当于上述语句使用 @升降机 是:

z = @lift($x .+ $y)

这也适用于多行语句和元组或数组索引:

multiline_node = @lift begin
    a = $x[1:50] .* $y[51:100]
    b = sum($z)
    a .- b
end

如果要引用的Observable是某个表达式的结果,只需使用 $ 在那个表达式周围加上括号。

container = (x = Observable(1), y = Observable(2))

@lift($(container.x) + $(container.y))

同步更新的问题

基于多个可观察对象的管道的一个非常常见的问题是,您只能逐个更改可观察对象。 从理论上讲,每个可观察到的变化都会立即触发其侦听器。 如果一个函数依赖于两个或多个observable,那么在另一个之后更改一个会多次触发它,这通常不是你想要的。

这里有一个例子,我们定义了两个Observable,并从中提升了第三个Observable:

xs = Observable(1:10)
ys = Observable(rand(10))

zs = @lift($xs .+ $ys)

现在让我们更新两者 xys:

xs[] = 2:11
ys[] = rand(10)

我们刚刚触发了 zs 两次,即使我们真的只打算一次数据更新。 但这种双重触发只是问题的一部分。

两者兼而有之 xys 在这个例子中有长度10,所以他们仍然可以添加没有问题。 如果我们想将值附加到xs和ys,那么当我们更改其中一个的长度时,函数底层 zs 会因为形状不匹配而出错。 有时解决这种情况的唯一方法是在不触发其侦听器的情况下改变一个observable的内容,然后触发第二个。

xs.val = 1:11 # mutate without triggering listeners
ys[] = rand(11) # trigger listeners of ys (in this case the same as xs)

谨慎使用这种技术,因为它会增加代码的复杂性,并使推理变得更加困难。 它也只有在您仍然可以正确触发所有侦听器时才有效。 例如,如果另一个observable只听 x,我们不会在上述解决方法中正确更新它。 通常,您可以通过使用容器数组来避免长度变化问题,例如 第2f点Vec3f 而不是手动同步单个元素向量的两个或三个可观察值。