In [1]:
using Reactive, Interact
Interact.jl provides interactive widgets for IJulia. Interaction relies on Reactive.jl reactive programming package. Reactive provides the type Signal
which represent time-varying values. For example, a Slider widget can be turned into a "signal of numbers". Execute the following two cells, and then move the slider. You will see that the value of signal(slider)
changes accordingly.
In [2]:
s = slider(0:.1:1,label="Slider X:")
Out[2]:
In [3]:
signal(s)
Out[3]:
Let us now inspect the types of these entities.
In [4]:
display(typeof(s));
isa(s, Widget)
Out[4]:
In [5]:
display(typeof(signal(s)));
isa(signal(s), Signal{Float64})
Out[5]:
You can have many instances of the same widget in a notebook, and they stay in sync:
In [6]:
s
Out[6]:
A slider is useless if you cannot do more with it than just watch its value. Thankfully we can transform one signal into another, which means we can transform the signal of values that the slider takes into, say a signal of it's squares:
In [7]:
xsquared = map(x -> x*x, signal(s))
Out[7]:
Go ahead and vary the slider to see this in action.
You can transform a signal into pretty much anything else. Let's use the Color package to produce different saturations of red:
In [8]:
using Colors
map(x -> RGB(x, 0.5, 0.5), signal(s))
Out[8]:
You can of course use several inputs as arguments to map
enabling you to combine many signals. Let's create a full color-picker.
In [9]:
r = slider(0:0.01:1, label="R")
g = slider(0:0.01:1, label="G")
b = slider(0:0.01:1, label="B")
map(display, [r,g,b]);
In [10]:
color = map((x, y, z) -> RGB(x, y, z), signal(r), signal(g), signal(b))
Out[10]:
We can use the HTML widget to write some text you can change the color of.
In [11]:
map(color -> HTML(string("<div style='color:#", hex(color), "'>Hello, World!</div>")), signal(color))
Out[11]:
The @manipulate
macro lets you play with any expression using widgets. We could have, for example, used @manipulate
to make a color picker along with our HTML output in one line of code:
In [12]:
@manipulate for r = 0:.05:1, g = 0:.05:1, b = 0:.05:1
HTML(string("<div style='color:#", hex(RGB(r,g,b)), "'>Color me</div>"))
end
Out[12]:
You can in fact create signal of other widgets to update them reactively. We have seen one case with HTML
above. Let us now create a signal of Sliders:
In [13]:
x = slider(0:.1:2pi, label="x")
s = map(a -> slider(-1:.05:1, value=sin(2a), label="sin(2x)"), signal(x))
c = map(a -> slider(-1:.05:1, value=cos(2a), label="cos(2x)"), signal(x))
map(display, [x,s,c]);
Now vary the x slider to see sin(2x) and cos(2x) get set to their appropriate values.
But in the above case, you cannot also use sin(2x) and cos(2x) sliders as input values (without doing tricky stuff beyond the scope of this introduction). To do this more simply, we will create a separate Input signal and pass it as an argument to map, to be the signal that the widget updates. Example:
In [14]:
fx = Signal(0.0) # A float input
Out[14]:
In [15]:
x = slider(0:.1:2pi, label="x")
y = map(v -> slider(-1:.05:1, value=sin(v), signal=fx, label="f(x)"), signal(x))
map(display, (x,y,fx));
f(x) will update as x changes. But if the user slides f(x) then the fx
signal takes the value chosen by the user.