This repository has been archived by the owner on Jul 15, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
interaction.html
56 lines (49 loc) · 22.5 KB
/
interaction.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>Observables & Interaction · Makie Plotting Ecosystem</title><link href="https://fonts.googleapis.com/css?family=Lato|Roboto+Mono" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/fontawesome.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/solid.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/brands.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/katex.min.css" rel="stylesheet" type="text/css"/><script>documenterBaseURL="."</script><script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js" data-main="assets/documenter.js"></script><script src="siteinfo.js"></script><script src="../versions.js"></script><link class="docs-theme-link" rel="stylesheet" type="text/css" href="assets/themes/documenter-dark.css" data-theme-name="documenter-dark" data-theme-primary-dark/><link class="docs-theme-link" rel="stylesheet" type="text/css" href="assets/themes/documenter-light.css" data-theme-name="documenter-light" data-theme-primary/><script src="assets/themeswap.js"></script><link href="assets/favicon.ico" rel="icon" type="image/x-icon"/><link href="assets/syntaxtheme.css" rel="stylesheet" type="text/css"/></head><body><div id="documenter"><nav class="docs-sidebar"><a class="docs-logo" href="index.html"><img src="assets/logo.png" alt="Makie Plotting Ecosystem logo"/></a><div class="docs-package-name"><span class="docs-autofit"><a href="index.html">Makie Plotting Ecosystem</a></span></div><form class="docs-search" action="search.html"><input class="docs-search-query" id="documenter-search-query" name="q" type="text" placeholder="Search docs"/></form><ul class="docs-menu"><li><a class="tocitem" href="index.html">Home</a></li><li><span class="tocitem">Basics</span><ul><li><a class="tocitem" href="basic-tutorial.html">Basic Tutorial</a></li><li><a class="tocitem" href="makielayout/tutorial.html">Layout Tutorial</a></li><li><a class="tocitem" href="animation.html">Animations</a></li><li class="is-active"><a class="tocitem" href="interaction.html">Observables & Interaction</a><ul class="internal"><li><a class="tocitem" href="#The-Node-structure"><span>The <code>Node</code> structure</span></a></li><li><a class="tocitem" href="#Triggering-A-Change"><span>Triggering A Change</span></a></li><li><a class="tocitem" href="#Chaining-Nodes-With-lift"><span>Chaining <code>Node</code>s With <code>lift</code></span></a></li><li><a class="tocitem" href="#Shorthand-Macro-For-lift"><span>Shorthand Macro For <code>lift</code></span></a></li><li><a class="tocitem" href="#Problems-With-Synchronous-Updates"><span>Problems With Synchronous Updates</span></a></li><li><a class="tocitem" href="#Mouse-Interaction"><span>Mouse Interaction</span></a></li><li><a class="tocitem" href="#Keyboard-Interaction"><span>Keyboard Interaction</span></a></li><li><a class="tocitem" href="#Interactive-Widgets"><span>Interactive Widgets</span></a></li><li><a class="tocitem" href="#Recording-Animations-with-Interactions"><span>Recording Animations with Interactions</span></a></li></ul></li><li><input class="collapse-toggle" id="menuitem-2-5" type="checkbox"/><label class="tocitem" for="menuitem-2-5"><span class="docs-label">Plotting Functions</span><i class="docs-chevron"></i></label><ul class="collapsed"><li><a class="tocitem" href="plotting_functions/arrows.html">arrows</a></li><li><a class="tocitem" href="plotting_functions/band.html">band</a></li><li><a class="tocitem" href="plotting_functions/barplot.html">barplot</a></li><li><a class="tocitem" href="plotting_functions/contour.html">contour</a></li><li><a class="tocitem" href="plotting_functions/contourf.html">contourf</a></li><li><a class="tocitem" href="plotting_functions/density.html">density</a></li><li><a class="tocitem" href="plotting_functions/errorbars.html">errorbars</a></li><li><a class="tocitem" href="plotting_functions/heatmap.html">heatmap</a></li><li><a class="tocitem" href="plotting_functions/hist.html">hist</a></li><li><a class="tocitem" href="plotting_functions/image.html">image</a></li><li><a class="tocitem" href="plotting_functions/lines.html">lines</a></li><li><a class="tocitem" href="plotting_functions/linesegments.html">linesegments</a></li><li><a class="tocitem" href="plotting_functions/mesh.html">mesh</a></li><li><a class="tocitem" href="plotting_functions/meshscatter.html">meshscatter</a></li><li><a class="tocitem" href="plotting_functions/poly.html">poly</a></li><li><a class="tocitem" href="plotting_functions/rangebars.html">rangebars</a></li><li><a class="tocitem" href="plotting_functions/scatter.html">scatter</a></li><li><a class="tocitem" href="plotting_functions/scatterlines.html">scatterlines</a></li><li><a class="tocitem" href="plotting_functions/stem.html">stem</a></li><li><a class="tocitem" href="plotting_functions/streamplot.html">streamplot</a></li><li><a class="tocitem" href="plotting_functions/surface.html">surface</a></li><li><a class="tocitem" href="plotting_functions/text.html">text</a></li><li><a class="tocitem" href="plotting_functions/volume.html">volume</a></li></ul></li><li><a class="tocitem" href="theming.html">Theming</a></li></ul></li><li><span class="tocitem">Documentation</span><ul><li><a class="tocitem" href="plot_method_signatures.html">Plot Method Signatures</a></li><li><a class="tocitem" href="figure.html">Figure</a></li><li><input class="collapse-toggle" id="menuitem-3-3" type="checkbox"/><label class="tocitem" for="menuitem-3-3"><span class="docs-label">Layoutables & Widgets</span><i class="docs-chevron"></i></label><ul class="collapsed"><li><a class="tocitem" href="makielayout/layoutables.html">Layoutables</a></li><li><a class="tocitem" href="makielayout/axis.html">Axis</a></li><li><a class="tocitem" href="makielayout/axis3.html">Axis3</a></li><li><a class="tocitem" href="makielayout/box.html">Box</a></li><li><a class="tocitem" href="makielayout/button.html">Button</a></li><li><a class="tocitem" href="makielayout/colorbar.html">Colorbar</a></li><li><a class="tocitem" href="makielayout/gridlayout.html">GridLayout</a></li><li><a class="tocitem" href="makielayout/intervalslider.html">IntervalSlider</a></li><li><a class="tocitem" href="makielayout/label.html">Label</a></li><li><a class="tocitem" href="makielayout/legend.html">Legend</a></li><li><a class="tocitem" href="makielayout/lscene.html">LScene</a></li><li><a class="tocitem" href="makielayout/menu.html">Menu</a></li><li><a class="tocitem" href="makielayout/slider.html">Slider</a></li><li><a class="tocitem" href="makielayout/toggle.html">Toggle</a></li></ul></li><li><a class="tocitem" href="makielayout/layouting.html">How layouts work</a></li><li><a class="tocitem" href="generated/colors.html">Colors</a></li><li><a class="tocitem" href="generated/plot-attributes.html">Plot attributes</a></li><li><a class="tocitem" href="recipes.html">Plot Recipes</a></li><li><a class="tocitem" href="backends_and_output.html">Backends & Output</a></li><li><a class="tocitem" href="scenes.html">Scenes</a></li><li><a class="tocitem" href="lighting.html">Lighting</a></li><li><a class="tocitem" href="cameras.html">Cameras</a></li><li><a class="tocitem" href="faq.html">Frequently Asked Questions</a></li><li><a class="tocitem" href="abstractplotting_api.html">API Reference AbstractPlotting</a></li><li><a class="tocitem" href="makielayout/reference.html">API Reference MakieLayout</a></li><li><a class="tocitem" href="generated/axis.html">Integrated Axes (Axis2D / Axis3D)</a></li></ul></li></ul><div class="docs-version-selector field has-addons"><div class="control"><span class="docs-label button is-static is-size-7">Version</span></div><div class="docs-selector control is-expanded"><div class="select is-fullwidth is-size-7"><select id="documenter-version-selector"></select></div></div></div></nav><div class="docs-main"><header class="docs-navbar"><nav class="breadcrumb"><ul class="is-hidden-mobile"><li><a class="is-disabled">Basics</a></li><li class="is-active"><a href="interaction.html">Observables & Interaction</a></li></ul><ul class="is-hidden-tablet"><li class="is-active"><a href="interaction.html">Observables & Interaction</a></li></ul></nav><div class="docs-right"><a class="docs-edit-link" href="https://github.com/JuliaPlots/AbstractPlotting.jl/blob/master/docs/src/interaction.md#" title="Edit on GitHub"><span class="docs-icon fab"></span><span class="docs-label is-hidden-touch">Edit on GitHub</span></a><a class="docs-settings-button fas fa-cog" id="documenter-settings-button" href="#" title="Settings"></a><a class="docs-sidebar-button fa fa-bars is-hidden-desktop" id="documenter-sidebar-button" href="#"></a></div></header><article class="content" id="documenter-page"><h1 id="Observables-and-Interaction"><a class="docs-heading-anchor" href="#Observables-and-Interaction">Observables & Interaction</a><a id="Observables-and-Interaction-1"></a><a class="docs-heading-anchor-permalink" href="#Observables-and-Interaction" title="Permalink"></a></h1><p>Interaction and animations in Makie are handled using <a href="https://juliagizmos.github.io/Observables.jl/stable/"><code>Observables.jl</code></a>. <code>Observable</code>s are called <code>Node</code>s in Makie for historical reasons and the two terms are used interchangeably. An <code>Observable</code> is a container object whose stored value you can update interactively. You can create functions that are executed whenever an observable changes. You can also create observables whose values are updated whenever other observables change. This way you can easily build dynamic and interactive visualizations.</p><p>On this page you will learn how the <code>Node</code>s pipeline and the event-based interaction system work.</p><h2 id="The-Node-structure"><a class="docs-heading-anchor" href="#The-Node-structure">The <code>Node</code> structure</a><a id="The-Node-structure-1"></a><a class="docs-heading-anchor-permalink" href="#The-Node-structure" title="Permalink"></a></h2><p>A <code>Node</code> is an object that allows its value to be updated interactively. Let's start by creating one:</p><pre><code class="language-julia">using GLMakie, AbstractPlotting
x = Node(0.0)</code></pre><pre class="documenter-example-output">Observable{Float64} with 0 listeners. Value:
0.0</pre><p>Each <code>Node</code> 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 <code>Node</code> later with objects of different types. You could for example write:</p><pre><code class="language-julia">x2 = Node{Real}(0.0)
x3 = Node{Any}(0.0)</code></pre><p>This is often the case when dealing with attributes that can come in different forms. For example, a color could be <code>:red</code> or <code>RGB(1,0,0)</code>.</p><h2 id="Triggering-A-Change"><a class="docs-heading-anchor" href="#Triggering-A-Change">Triggering A Change</a><a id="Triggering-A-Change-1"></a><a class="docs-heading-anchor-permalink" href="#Triggering-A-Change" title="Permalink"></a></h2><p>You change the value of a Node with empy index notation:</p><pre><code class="language-julia">x[] = 3.34</code></pre><p>This was not particularly interesting. But Nodes allow you to register functions that are executed whenever the Node's content is changed.</p><p>One such function is <code>on</code>. Let's register something on our Node <code>x</code> and change <code>x</code>'s value:</p><pre><code class="language-julia">on(x) do x
println("New value of x is $x")
end
x[] = 5.0</code></pre><pre class="documenter-example-output">New value of x is 5.0</pre><div class="admonition is-info"><header class="admonition-header">Note</header><div class="admonition-body"><p>All registered functions in a <code>Node</code> are executed synchronously in the order of registration. This means that if you change two Nodes after one another, all effects of the first change will happen before the second change.</p></div></div><p>There are two ways to access the value of a <code>Node</code>. You can use the indexing syntax or the <code>to_value</code> function:</p><pre><code class="language-julia">value = x[]
value = to_value(x)</code></pre><p>The advantage of using <code>to_value</code> is that you can use it in situations where you could either be dealing with Nodes or normal values. In the latter case, <code>to_value</code> just returns the original value, like <code>identity</code>.</p><h2 id="Chaining-Nodes-With-lift"><a class="docs-heading-anchor" href="#Chaining-Nodes-With-lift">Chaining <code>Node</code>s With <code>lift</code></a><a id="Chaining-Nodes-With-lift-1"></a><a class="docs-heading-anchor-permalink" href="#Chaining-Nodes-With-lift" title="Permalink"></a></h2><p>You can create a Node depending on another Node using <a href="abstractplotting_api.html#AbstractPlotting.lift-Tuple{Any, Observables.AbstractObservable, Vararg{Any, N} where N}"><code>lift</code></a>. The first argument of <code>lift</code> must be a function that computes the value of the output Node given the values of the input Nodes.</p><pre><code class="language-julia">f(x) = x^2
y = lift(f, x)</code></pre><pre class="documenter-example-output">Observable{Float64} with 0 listeners. Value:
25.0</pre><p>Now, whenever <code>x</code> changes, the derived <code>Node</code> <code>y</code> will immediately hold the value <code>f(x)</code>. In turn, <code>y</code>'s change could trigger the update of other observables, if any have been connected. Let's connect one more observable and update x:</p><pre><code class="language-julia">z = lift(y) do y
-y
end
x[] = 10.0
@show x[]
@show y[]
@show z[]</code></pre><pre class="documenter-example-output">New value of x is 10.0
x[] = 10.0
y[] = 100.0
z[] = -100.0</pre><p>If <code>x</code> changes, so does <code>y</code> and then <code>z</code>.</p><p>Note, though, that changing <code>y</code> does not change <code>x</code>. There is no guarantee that chained Nodes are always synchronized, because they can be mutated in different places, even sidestepping the change trigger mechanism.</p><pre><code class="language-julia">y[] = 20.0
@show x[]
@show y[]
@show z[]</code></pre><pre class="documenter-example-output">x[] = 10.0
y[] = 20.0
z[] = -20.0</pre><h2 id="Shorthand-Macro-For-lift"><a class="docs-heading-anchor" href="#Shorthand-Macro-For-lift">Shorthand Macro For <code>lift</code></a><a id="Shorthand-Macro-For-lift-1"></a><a class="docs-heading-anchor-permalink" href="#Shorthand-Macro-For-lift" title="Permalink"></a></h2><p>When using <a href="abstractplotting_api.html#AbstractPlotting.lift-Tuple{Any, Observables.AbstractObservable, Vararg{Any, N} where N}"><code>lift</code></a>, it can be tedious to reference each participating <code>Node</code> at least three times, once as an argument to <code>lift</code>, once as an argument to the closure that is the first argument, and at least once inside the closure:</p><pre><code class="language-julia">x = Node(rand(100))
y = Node(rand(100))
z = lift((x, y) -> x .+ y, x, y)</code></pre><p>To circumvent this, you can use the <code>@lift</code> macro. You simply write the operation you want to do with the lifted <code>Node</code>s and prepend each <code>Node</code> variable with a dollar sign $. The macro will lift every Node variable it finds and wrap the whole expression in a closure. The equivalent to the above statement using <code>@lift</code> is:</p><pre><code class="language-julia">z = @lift($x .+ $y)</code></pre><p>This also works with multiline statements and tuple or array indexing:</p><pre><code class="language-julia">multiline_node = @lift begin
a = $x[1:50] .* $y[51:100]
b = sum($z)
a .- b
end</code></pre><p>If the Node you want to reference is the result of some expression, just use <code>$</code> with parentheses around that expression.</p><pre><code class="language-example">container = (x = Node(1), y = Node(2))
@lift($(container.x) + $(container.y))</code></pre><h2 id="Problems-With-Synchronous-Updates"><a class="docs-heading-anchor" href="#Problems-With-Synchronous-Updates">Problems With Synchronous Updates</a><a id="Problems-With-Synchronous-Updates-1"></a><a class="docs-heading-anchor-permalink" href="#Problems-With-Synchronous-Updates" title="Permalink"></a></h2><p>One very common problem with a pipeline based on multiple observables is that you can only change observables one by one. Theoretically, each observable change triggers its listeners immediately. If a function depends on two or more observables, changing one right after the other would trigger it multiple times, which is often not what you want.</p><p>Here's an example where we define two nodes and lift a third one from them:</p><pre><code class="language-julia">xs = Node(1:10)
ys = Node(rand(10))
zs = @lift($xs .+ $ys)</code></pre><p>Now let's update both <code>xs</code> and <code>ys</code>:</p><pre><code class="language-julia">xs[] = 2:11
ys[] = rand(10)</code></pre><p>We just triggered <code>zs</code> twice, even though we really only intended one data update. But this double triggering is only part of the problem.</p><p>Both <code>xs</code> and <code>ys</code> in this example had length 10, so they could still be added without a problem. If we want to append values to xs and ys, the moment we change the length of one of them, the function underlying <code>zs</code> will error because of a shape mismatch. Sometimes the only way to fix this situation, is to mutate the content of one observable without triggering its listeners, then triggering the second one.</p><pre><code class="language-julia">xs.val = 1:11 # mutate without triggering listeners
ys[] = rand(11) # trigger listeners of ys (in this case the same as xs)</code></pre><p>Use this technique sparingly, as it increases the complexity of your code and can make reasoning about it more difficult. It also only works if you can still trigger all listeners correctly. For example, if another observable listened only to <code>xs</code>, we wouldn't have updated it correctly in the above workaround. Often, you can avoid length change problems by using arrays of containers like <code>Point2f0</code> or <code>Vec3f0</code> instead of synchronizing two or three observables of single element vectors manually.</p><h2 id="Mouse-Interaction"><a class="docs-heading-anchor" href="#Mouse-Interaction">Mouse Interaction</a><a id="Mouse-Interaction-1"></a><a class="docs-heading-anchor-permalink" href="#Mouse-Interaction" title="Permalink"></a></h2><p>Each <code>Scene</code> has an Events struct that holds a few predefined Nodes (see them in <code>scene.events</code>) To use them in your interaction pipeline, you can use them with <code>lift</code> or <code>on</code>.</p><p>For example, for interaction with the mouse cursor, use the <code>mouseposition</code> Node.</p><pre><code class="language-julia">on(scene.events.mouseposition) do mpos
# do something with the mouse position
end</code></pre><h2 id="Keyboard-Interaction"><a class="docs-heading-anchor" href="#Keyboard-Interaction">Keyboard Interaction</a><a id="Keyboard-Interaction-1"></a><a class="docs-heading-anchor-permalink" href="#Keyboard-Interaction" title="Permalink"></a></h2><p>You can use <code>scene.events.keyboardbuttons</code> to react to raw keyboard events and <code>scene.events.unicode_input</code> to react to specific characters being typed.</p><p>The <code>keyboardbuttons</code> Node, for example, contains an enum that can be used to implement a keyboard event handler.</p><pre><code class="language-julia">on(scene.events.keyboardbuttons) do button
ispressed(button, Keyboard.left) && move_left()
ispressed(button, Keyboard.up) && move_up()
ispressed(button, Keyboard.right) && move_right()
ispressed(button, Keyboard.down) && move_down()
end</code></pre><h2 id="Interactive-Widgets"><a class="docs-heading-anchor" href="#Interactive-Widgets">Interactive Widgets</a><a id="Interactive-Widgets-1"></a><a class="docs-heading-anchor-permalink" href="#Interactive-Widgets" title="Permalink"></a></h2><p>Makie has a couple of useful interactive widgets like sliders, buttons and menus, which you can read about in the <a href="makielayout/layoutables.html#Layoutables">Layoutables</a> section.</p><h2 id="Recording-Animations-with-Interactions"><a class="docs-heading-anchor" href="#Recording-Animations-with-Interactions">Recording Animations with Interactions</a><a id="Recording-Animations-with-Interactions-1"></a><a class="docs-heading-anchor-permalink" href="#Recording-Animations-with-Interactions" title="Permalink"></a></h2><p>You can record a <code>Scene</code> while you're interacting with it. Just use the <a href="abstractplotting_api.html#AbstractPlotting.record-Tuple{Any, Any, Any}"><code>record</code></a> function (also see the <a href="animation.html#Animations">Animations</a> page) and allow interaction by <code>sleep</code>ing in the loop.</p><p>In this example, we sample from the Scene <code>scene</code> for 10 seconds, at a rate of 10 frames per second.</p><pre><code class="language-julia">fps = 10
record(scene, "test.mp4"; framerate = fps) do io
for i = 1:100
sleep(1/fps)
recordframe!(io)
end
end</code></pre></article><nav class="docs-footer"><a class="docs-footer-prevpage" href="animation.html">« Animations</a><a class="docs-footer-nextpage" href="plotting_functions/arrows.html">arrows »</a><div class="flexbox-break"></div><p class="footer-message">Powered by <a href="https://github.com/JuliaDocs/Documenter.jl">Documenter.jl</a> and the <a href="https://julialang.org/">Julia Programming Language</a>.</p></nav></div><div class="modal" id="documenter-settings"><div class="modal-background"></div><div class="modal-card"><header class="modal-card-head"><p class="modal-card-title">Settings</p><button class="delete"></button></header><section class="modal-card-body"><p><label class="label">Theme</label><div class="select"><select id="documenter-themepicker"><option value="documenter-light">documenter-light</option><option value="documenter-dark">documenter-dark</option></select></div></p><hr/><p>This document was generated with <a href="https://github.com/JuliaDocs/Documenter.jl">Documenter.jl</a> on <span class="colophon-date" title="Saturday 10 April 2021 14:43">Saturday 10 April 2021</span>. Using Julia version 1.6.0.</p></section><footer class="modal-card-foot"></footer></div></div></div></body></html>