├── src
├── IJulia
│ ├── README.md
│ ├── statedict.jl
│ ├── handle_msg.jl
│ ├── ijulia.js
│ └── setup.jl
├── compose.jl
├── html_setup.jl
├── manipulate.jl
├── Interact.jl
├── widgets.js
└── widgets.jl
├── REQUIRE
├── .travis.yml
├── README.md
├── LICENSE.md
├── DESIGN.md
└── doc
└── notebooks
├── 01-Introduction.ipynb
├── 02-Widgets Overview.ipynb
└── 04-Animations.ipynb
/src/IJulia/README.md:
--------------------------------------------------------------------------------
1 | The is where IJulia-specific setup for Interact go.
2 |
--------------------------------------------------------------------------------
/REQUIRE:
--------------------------------------------------------------------------------
1 | julia 0.3
2 | JSON
3 | Compat
4 | Reactive 0.1.9
5 | DataStructures 0.2.10
6 |
--------------------------------------------------------------------------------
/src/IJulia/statedict.jl:
--------------------------------------------------------------------------------
1 | statedict(s::Union(Slider, Progress)) =
2 | @compat Dict(:value=>s.value,
3 | :min=>first(s.range),
4 | :step=>step(s.range),
5 | :max=>last(s.range))
6 |
7 | # when we say value to javascript, it really means value label
8 | statedict(d::Options) =
9 | @compat Dict(:selected_label=>d.value_label,
10 | :icons=>d.icons,
11 | :tooltips=>d.tooltips,
12 | :_options_labels=>collect(keys(d.options)))
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: cpp
2 | compiler:
3 | - clang
4 | notifications:
5 | email: false
6 | env:
7 | matrix:
8 | - JULIAVERSION="juliareleases"
9 | - JULIAVERSION="julianightlies"
10 | before_install:
11 | - sudo add-apt-repository ppa:staticfloat/julia-deps -y
12 | - sudo add-apt-repository ppa:staticfloat/${JULIAVERSION} -y
13 | - sudo apt-get update -qq -y
14 | - sudo apt-get install libpcre3-dev julia -y
15 | script:
16 | - julia -e 'Pkg.init(); run(`ln -s $(pwd()) $(Pkg.dir("Interact"))`); Pkg.pin("Interact"); Pkg.resolve()'
17 | - julia -e 'using Interact; @assert isdefined(:Interact); @assert typeof(Interact) === Module'
18 |
--------------------------------------------------------------------------------
/src/compose.jl:
--------------------------------------------------------------------------------
1 | # Composable Widget API
2 |
3 | abstract WidgetMod <: Widget
4 |
5 | signal(w::WidgetMod) = signal(w.widget)
6 |
7 | immutable Styled{W <: Widget} <: WidgetMod
8 | widget::W
9 | style::Dict
10 | end
11 |
12 | style(widget, style) = StyledWidget(widget, style)
13 |
14 | immutable Labeled{W <: Widget} <: WidgetMod
15 | widget::W
16 | label::String
17 | end
18 |
19 | label(widget, label) = LabeledWidget(widget, label)
20 |
21 | immutable WithClass{class, W <: Widget} <: WidgetMod
22 | widget::W
23 | end
24 |
25 | addclass(widget, class) = WithClass{symbol(class)}(widget)
26 |
27 | abstract Container <: Widget
28 |
29 | immutable WidgetStack{direction} <: Container
30 | widgets::Vector{Widget}
31 | end
32 |
33 | hstack(widgets::Widget...) = WidgetStack{:x}([widget...])
34 | vstack(widgets::Widget...) = WidgetStack{:y}([widget...])
35 | zstack(widgets::Widget...) = WidgetStack{:z}([widget...])
36 |
--------------------------------------------------------------------------------
/src/html_setup.jl:
--------------------------------------------------------------------------------
1 | import Base: writemime
2 |
3 | using JSON
4 |
5 | const widgets_js = readall(joinpath(dirname(Base.source_path()), "widgets.js"))
6 |
7 | try
8 | display("text/html", """""")
9 | catch
10 | end
11 |
12 | function writemime(io, ::MIME{symbol("text/html")}, w::InputWidget)
13 | wtype = typeof(w)
14 | while super(wtype) <: InputWidget
15 | wtype = super(wtype)
16 | end
17 |
18 | widgettype = string(typeof(w).name.name)
19 | inputtype = string(wtype.parameters[1].name.name)
20 |
21 | id = register_widget(w)
22 | el_id = "widget-$(id)"
23 |
24 | write(io, "
")
29 | end
30 |
31 |
--------------------------------------------------------------------------------
/src/IJulia/handle_msg.jl:
--------------------------------------------------------------------------------
1 |
2 | function handle_msg(w::InputWidget, msg)
3 | if msg.content["data"]["method"] == "backbone"
4 | recv_msg(w, msg.content["data"]["sync_data"]["value"])
5 | end
6 | end
7 |
8 | function handle_msg{T}(w::Button{T}, msg)
9 | try
10 | if msg.content["data"]["method"] == "custom" &&
11 | msg.content["data"]["content"]["event"] == "click"
12 | # click event occured
13 | recv_msg(w, convert(T, w.value))
14 | end
15 | catch e
16 | warn(string("Couldn't handle Button message ", e))
17 | end
18 | end
19 |
20 | function handle_msg{view}(w::Options{view}, msg)
21 | try
22 | if msg.content["data"]["method"] == "backbone"
23 | key = string(msg.content["data"]["sync_data"]["selected_label"])
24 | if haskey(w.options, key)
25 | recv_msg(w, w.options[key])
26 | end
27 | end
28 | catch e
29 | warn(string("Couldn't handle ", view, " message ", e))
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Interact
2 |
3 | Interact.jl allows you to use interactive widgets such as sliders, dropdowns and checkboxes to play with your Julia code.
4 |
5 | 
6 |
7 | ## Getting Started
8 |
9 | To install Interact, run the following command in the Julia REPL:
10 | ```{.julia execute="false"}
11 | Pkg.add("Interact")
12 | ```
13 |
14 | **Note:** Interact only works with IPython version 3.x, support for 4.x is a work in progress.
15 |
16 | To start using it in an IJulia notebook, include it:
17 | ```{.julia execute="false"}
18 | using Interact
19 | ```
20 | [`GtkInteract`](https://github.com/jverzani/GtkInteract.jl) provides Gtk support for Interact, letting you use tools like `Winston` for plotting.
21 |
22 | ## Example notebooks
23 |
24 | The best way to learn to use the interactive widgets is to try out the example notebooks in the doc/notebooks/ directory. Start up IJulia from doc/notebooks/:
25 |
26 | ```{.shell execute="false"}
27 | ipython notebook --profile julia
28 | ```
29 | Interact needs IJulia to be running on Jupyter/IPython 3.0
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The Interact.jl package is licensed under the MIT "Expat" License:
2 |
3 | > Copyright (c) 2014: Shashi Gowda.
4 | >
5 | > Permission is hereby granted, free of charge, to any person obtaining
6 | > a copy of this software and associated documentation files (the
7 | > "Software"), to deal in the Software without restriction, including
8 | > without limitation the rights to use, copy, modify, merge, publish,
9 | > distribute, sublicense, and/or sell copies of the Software, and to
10 | > permit persons to whom the Software is furnished to do so, subject to
11 | > the following conditions:
12 | >
13 | > The above copyright notice and this permission notice shall be
14 | > included in all copies or substantial portions of the Software.
15 | >
16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/src/manipulate.jl:
--------------------------------------------------------------------------------
1 | export @manipulate
2 |
3 | function make_widget(binding)
4 | if binding.head != :(=)
5 | error("@manipulate syntax error.")
6 | end
7 | sym, expr = binding.args
8 | Expr(:(=), esc(sym),
9 | Expr(:call, widget, esc(expr), string(sym)))
10 | end
11 |
12 | function display_widgets(widgetvars)
13 | map(v -> Expr(:call, esc(:display), esc(v)), widgetvars)
14 | end
15 |
16 | function lift_block(block, symbols)
17 | lambda = Expr(:(->), Expr(:tuple, symbols...),
18 | block)
19 | Expr(:call, Reactive.lift, lambda, symbols...)
20 | end
21 |
22 | function symbols(bindings)
23 | map(x->x.args[1], bindings)
24 | end
25 |
26 | macro manipulate(expr)
27 | if expr.head != :for
28 | error("@manipulate syntax is @manipulate for ",
29 | " [=,]... end")
30 | end
31 | block = expr.args[2]
32 | if expr.args[1].head == :block
33 | bindings = expr.args[1].args
34 | else
35 | bindings = [expr.args[1]]
36 | end
37 | syms = symbols(bindings)
38 | Expr(:let, Expr(:block,
39 | display_widgets(syms)...,
40 | esc(lift_block(block, syms))),
41 | map(make_widget, bindings)...)
42 | end
43 |
--------------------------------------------------------------------------------
/src/Interact.jl:
--------------------------------------------------------------------------------
1 | module Interact
2 |
3 | using Reactive, Compat
4 |
5 | import Base: mimewritable, writemime
6 | import Reactive.signal
7 | export signal, Widget, InputWidget
8 |
9 | # A widget
10 | abstract Widget <: SignalSource
11 |
12 | # A widget that gives out a signal of type T
13 | abstract InputWidget{T} <: Widget
14 |
15 | signal(w::InputWidget) = w.signal
16 |
17 | function statedict(w::Widget)
18 | msg = Dict()
19 | attrs = @compat fieldnames(w)
20 | for n in attrs
21 | if n in [:signal, :label]
22 | continue
23 | end
24 | msg[n] = getfield(w, n)
25 | end
26 | msg
27 | end
28 |
29 | # Convert e.g. JSON values into Julia values
30 | parse_msg{T <: Number}(::InputWidget{T}, v::AbstractString) = parse(T, v)
31 | parse_msg(::InputWidget{Bool}, v::Number) = v != 0
32 | parse_msg{T}(::InputWidget{T}, v) = convert(T, v)
33 |
34 | function update_view(w)
35 | # update the view of a widget.
36 | # child packages need to override.
37 | end
38 |
39 | function recv_msg{T}(widget ::InputWidget{T}, value)
40 | # Hand-off received value to the signal graph
41 | parsed = parse_msg(widget, value)
42 | println(STDERR, signal(widget))
43 | push!(signal(widget), parsed)
44 | widget.value = parsed
45 | if value != parsed
46 | update_view(widget)
47 | end
48 | end
49 |
50 | uuid4() = string(Base.Random.uuid4())
51 |
52 | const id_to_widget = Dict{String, InputWidget}()
53 | const widget_to_id = Dict{InputWidget, String}()
54 |
55 | function register_widget(w::InputWidget)
56 | if haskey(widget_to_id, w)
57 | return widget_to_id[w]
58 | else
59 | id = uuid4()
60 | widget_to_id[w] = id
61 | id_to_widget[id] = w
62 | return id
63 | end
64 | end
65 |
66 | function get_widget(id::String)
67 | if haskey(id_to_widget, id)
68 | return id_to_widget[id]
69 | else
70 | warn("Widget with id $(id) does not exist.")
71 | end
72 | end
73 |
74 | include("widgets.jl")
75 | include("compose.jl")
76 | include("manipulate.jl")
77 | include("html_setup.jl")
78 |
79 | if isdefined(Main, :IJulia) && Main.IJulia.inited
80 | include("IJulia/setup.jl")
81 | end
82 |
83 | end # module
84 |
--------------------------------------------------------------------------------
/src/IJulia/ijulia.js:
--------------------------------------------------------------------------------
1 |
2 | (function (IPython, $, _, MathJax, Widgets) {
3 | $.event.special.destroyed = {
4 | remove: function(o) {
5 | if (o.handler) {
6 | o.handler.apply(this, arguments)
7 | }
8 | }
9 | }
10 |
11 | var OutputArea = IPython.version >= "4.0.0" ? require("notebook/js/outputarea").OutputArea : IPython.OutputArea;
12 |
13 | var redrawValue = function (container, type, val) {
14 | var selector = $("");
15 | var oa = new OutputArea(_.extend(selector, {
16 | selector: selector,
17 | prompt_area: true,
18 | events: IPython.events,
19 | keyboard_manager: IPython.keyboard_manager
20 | })); // Hack to work with IPython 2.1.0
21 |
22 | switch (type) {
23 | case "image/png":
24 | var _src = 'data:' + type + ';base64,' + val;
25 | $(container).find("img").attr('src', _src);
26 | break;
27 | default:
28 | var toinsert = OutputArea.append_map[type].apply(
29 | oa, [val, {}, selector]
30 | );
31 | $(container).empty().append(toinsert.contents());
32 | selector.remove();
33 | }
34 | if (type === "text/latex" && MathJax) {
35 | MathJax.Hub.Queue(["Typeset", MathJax.Hub, toinsert.get(0)]);
36 | }
37 | }
38 |
39 |
40 | $(document).ready(function() {
41 | Widgets.debug = false; // log messages etc in console.
42 | function initComm(evt, data) {
43 | var comm_manager = data.kernel.comm_manager;
44 | //_.extend(comm_manager.targets, require("widgets/js/widget"))
45 | comm_manager.register_target("Signal", function (comm) {
46 | comm.on_msg(function (msg) {
47 | //Widgets.log("message received", msg);
48 | var val = msg.content.data.value;
49 | $(".signal-" + comm.comm_id).each(function() {
50 | var type = $(this).data("type");
51 | if (val[type]) {
52 | redrawValue(this, type, val[type], type);
53 | }
54 | });
55 | delete val;
56 | delete msg.content.data.value;
57 | });
58 | });
59 |
60 | // coordingate with Comm and redraw Signals
61 | // XXX: Test using Reactive here to improve performance
62 | $([IPython.events]).on(
63 | 'output_appended.OutputArea', function (event, type, value, md, toinsert) {
64 | if (md && md.reactive) {
65 | // console.log(md.comm_id);
66 | toinsert.addClass("signal-" + md.comm_id);
67 | toinsert.data("type", type);
68 | // Signal back indicating the mimetype required
69 | var comm_manager = IPython.notebook.kernel.comm_manager;
70 | var comm = comm_manager.comms[md.comm_id];
71 | comm.then(function (c) {
72 | c.send({action: "subscribe_mime",
73 | mime: type});
74 | toinsert.bind("destroyed", function() {
75 | c.send({action: "unsubscribe_mime",
76 | mime: type});
77 | });
78 | })
79 | }
80 | });
81 | }
82 |
83 | try {
84 | // try to initialize right away. otherwise, wait on the status_started event.
85 | initComm(undefined, IPython.notebook);
86 | } catch (e) {
87 | $([IPython.events]).on('kernel_created.Kernel kernel_created.Session', initComm);
88 | }
89 | });
90 | })(IPython, jQuery, _, MathJax, InputWidgets);
91 |
--------------------------------------------------------------------------------
/src/widgets.js:
--------------------------------------------------------------------------------
1 | (function ($, undefined) {
2 |
3 | function createElem(tag, attr, content) {
4 | // TODO: remove jQuery dependency
5 | var el = $("<" + tag + "/>").attr(attr);
6 | if (content) {
7 | el.append(content);
8 | }
9 | return el[0];
10 | }
11 |
12 | // A widget must expose an id field which identifies it to the backend,
13 | // an elem attribute which is will be added to the DOM, and
14 | // a getState() method which returns the value to be sent to the backend
15 | // a sendUpdate() method which sends its current value to the backend
16 | var Widget = {
17 | id: undefined,
18 | elem: undefined,
19 | label: undefined,
20 | getState: function () {
21 | return this.elem.value;
22 | },
23 | sendUpdate: undefined
24 | };
25 |
26 | var Slider = function (typ, id, init) {
27 | var attr = { type: "range",
28 | value: init.value,
29 | min: init.min,
30 | max: init.max,
31 | step: init.step },
32 | elem = createElem("input", attr),
33 | self = this;
34 |
35 | elem.onchange = function () {
36 | self.sendUpdate();
37 | }
38 |
39 | this.id = id;
40 | this.elem = elem;
41 | this.label = init.label;
42 |
43 | InputWidgets.commInitializer(this); // Initialize communication
44 | }
45 | Slider.prototype = Widget;
46 |
47 | var Checkbox = function (typ, id, init) {
48 | var attr = { type: "checkbox",
49 | checked: init.value },
50 | elem = createElem("input", attr),
51 | self = this;
52 |
53 | this.getState = function () {
54 | return elem.checked;
55 | }
56 | elem.onchange = function () {
57 | self.sendUpdate();
58 | }
59 |
60 | this.id = id;
61 | this.elem = elem;
62 | this.label = init.label;
63 |
64 | InputWidgets.commInitializer(this);
65 | }
66 | Checkbox.prototype = Widget;
67 |
68 | var Button = function (typ, id, init) {
69 | var attr = { type: "button",
70 | value: init.label },
71 | elem = createElem("input", attr),
72 | self = this;
73 | this.getState = function () {
74 | return null;
75 | }
76 | elem.onclick = function () {
77 | self.sendUpdate();
78 | }
79 |
80 | this.id = id;
81 | this.elem = elem;
82 | this.label = init.label;
83 |
84 | InputWidgets.commInitializer(this);
85 | }
86 | Button.prototype = Widget;
87 |
88 | var Text = function (typ, id, init) {
89 | var attr = { type: "text",
90 | placeholder: init.label,
91 | value: init.value },
92 | elem = createElem("input", attr),
93 | self = this;
94 | this.getState = function () {
95 | return elem.value;
96 | }
97 | elem.onkeyup = function () {
98 | self.sendUpdate();
99 | }
100 |
101 | this.id = id;
102 | this.elem = elem;
103 | this.label = init.label;
104 |
105 | InputWidgets.commInitializer(this);
106 | }
107 | Text.prototype = Widget;
108 |
109 | var Textarea = function (typ, id, init) {
110 | var attr = { placeholder: init.label },
111 | elem = createElem("textarea", attr, init.value),
112 | self = this;
113 | this.getState = function () {
114 | return elem.value;
115 | }
116 | elem.onchange = function () {
117 | self.sendUpdate();
118 | }
119 |
120 | this.id = id;
121 | this.elem = elem;
122 | this.label = init.label;
123 |
124 | InputWidgets.commInitializer(this);
125 | }
126 | Textarea.prototype = Widget;
127 |
128 | // RadioButtons
129 | // Dropdown
130 | // HTML
131 | // Latex
132 |
133 | var InputWidgets = {
134 | Slider: Slider,
135 | Checkbox: Checkbox,
136 | Button: Button,
137 | Text: Text,
138 | Textarea: Textarea,
139 | debug: false,
140 | log: function () {
141 | if (InputWidgets.debug) {
142 | console.log.apply(console, arguments);
143 | }
144 | },
145 | // a central way to initalize communication
146 | // for widgets.
147 | commInitializer: function (widget) {
148 | widget.sendUpdate = function () {};
149 | }
150 | };
151 |
152 | window.InputWidgets = InputWidgets;
153 |
154 | })(jQuery, undefined);
155 |
--------------------------------------------------------------------------------
/DESIGN.md:
--------------------------------------------------------------------------------
1 | IDE Specifics
2 | -------------
3 | IJulia-specific code is in `ijulia_setup.jl` for now (probably should factor out into a separate module later). This file is included by `Interact.jl` if used from inside IJulia notebooks. This is the only file where IJulia specific stuff must go (e.g. setting up comm)
4 |
5 | It will be possible for any environment to tap into the bulk of code here which will help in building interactive features.
6 |
7 | **To integrate Interact.jl with your IDE, you will have to implement the following:**
8 |
9 | 1. When displaying a value of type `Signal{T}` render it as you'd render a value of type `T`.
10 | 2. Set things up so that when the diplayed signal updates, you also redraw its display. You can use the `lift` operator to do this:
11 | ```julia
12 | # a hypothetical updater sending a redraw message to the frontend
13 | lift(v -> redraw(signal_identifier, v), signal)
14 | # the frontend should receive this message and redraw all displayed instances of the signal
15 | ```
16 | 3. A value of type `InputWidget{T}` must be rendered as a GUI element corresponding to its concrete type. e.g. a `Slider{Int}` should be shown as a slider with integer values. Every InputWidget type has its own set of attributes that the display must use, see `widgets.jl`. If your IDE supports HTML/JS, `widgets.js` already does this for you! You only need to set up the communication.
17 | 4. When a GUI element updates due to a user event, the GUI should, using some kind of messaging scheme, encode the update and send it to the backend.
18 | 5. You can attach an InputWidget{T} to an Input{T} using the `attach!` function, (and detach using detach!). `parse{T}(msg, :: InputWidget{T})` function takes a serialized representation (e.g. a JSON parsed Dict) of the message from a specific type of widget, and returns a value of type `T`. And finally `recv(widget, value)` hands-off the value to all the Input values attached to the widget. Ideally there should be a one-to-one mapping between Widgets and Inputs.
19 |
20 | For a better understanding see `ijulia.js` and `ijulia_setup.jl`
21 |
22 | Optimizations
23 | -------------
24 | ### DiffPatch.jl
25 | A `DiffPatch.jl` module can be made which can take the diff of two values of the same type and create a delta, and also patch a delta over a value to get the next value. Frontends and backends can send the deltas when it becomes economical. A diff-patch mecahnism will be easy to plug in at both ends at a later stage without any breaking change to the external API. Right now the focus is on making things work.
26 |
27 | ### Throttle
28 | Will be adding features to throttle signals to send kernel-client messages at an optimal rate so that interactive IDEs feel responsive. I am planning to put this in a Timing module in Reactive.jl.
29 |
30 | Macro based API
31 | ---------------
32 | It should be intuitive to create interactive objects.
33 |
34 | One idea:
35 | ```julia
36 | @manipulate expr x=widget_description1 y=widget_description2 ...
37 | # this will evaluate expr where variables x, y etc are populated from the widget description
38 | # the macro will handle low level signal handling.
39 | ```
40 | Will be writing more about this in the later part of the project.
41 |
42 | Layout system
43 | -------------
44 | Sometimes we will need to group and lay widgets out in specific ways. I think this should be decoupled from interactive stuff. Having things like a container widget which builds a composite typed value will be unnecessarily complex in the context of Reactive.jl. A Layout module would have the following:
45 |
46 | A type `Element` which represents anything displayable (can use type promotion here to make specific types displayable). Element will be a [group](http://en.wikipedia.org/wiki/Group_(mathematics)) that has some useful operators:
47 |
48 | ```julia
49 | # stack elems in the specified direction (down, right, left, up)
50 | flow(direction :: Symbol, elems :: Element....) # returns an Element
51 | # Create a tab group with a tab for each element
52 | tabs(elems :: (String, Element)...) # returns an Element
53 | # accordion, etc in the same vein
54 | ```
55 |
56 | The idea is to create a declarative representation of the layout and have the IDEs handle the actual rendering. `widgets.js` will help you do this for IDEs that support HTML/JS/CSS.
57 |
--------------------------------------------------------------------------------
/src/widgets.jl:
--------------------------------------------------------------------------------
1 | import Base: convert, haskey, setindex!, getindex
2 | export slider, togglebutton, button,
3 | checkbox, textbox, textarea,
4 | radiobuttons, dropdown, select,
5 | togglebuttons, html, latex,
6 | progress, widget
7 |
8 | ### Input widgets
9 |
10 | ########################## Slider ############################
11 |
12 | type Slider{T<:Number} <: InputWidget{T}
13 | signal::Input{T}
14 | label::String
15 | value::T
16 | range::Range{T}
17 | end
18 |
19 | # differs from median(r) in that it always returns an element of the range
20 | medianelement(r::Range) = r[(1+length(r))>>1]
21 |
22 | slider(args...) = Slider(args...)
23 | slider{T}(range::Range{T};
24 | value=medianelement(range),
25 | signal::Signal{T}=Input(value),
26 | label="") =
27 | Slider(signal, label, value, range)
28 |
29 | ######################### Checkbox ###########################
30 |
31 | type Checkbox <: InputWidget{Bool}
32 | signal::Input{Bool}
33 | label::String
34 | value::Bool
35 | end
36 |
37 | checkbox(args...) = Checkbox(args...)
38 | checkbox(value::Bool; signal=Input(value), label="") =
39 | Checkbox(signal, label, value)
40 | checkbox(; label="", value=false, signal=Input(value)) =
41 | Checkbox(signal, label, value)
42 |
43 | ###################### ToggleButton ########################
44 |
45 | type ToggleButton <: InputWidget{Bool}
46 | signal::Input{Bool}
47 | label::String
48 | value::Bool
49 | end
50 |
51 | togglebutton(args...) = ToggleButton(args...)
52 |
53 | togglebutton(; label="", value=false, signal=Input(value)) =
54 | ToggleButton(signal, label, value)
55 |
56 | togglebutton(label; kwargs...) =
57 | togglebutton(label=label; kwargs...)
58 |
59 | ######################### Button ###########################
60 |
61 | type Button{T} <: InputWidget{T}
62 | signal::Input{T}
63 | label::String
64 | value::T
65 | end
66 |
67 | button(; value=nothing, label="", signal=Input(value)) =
68 | Button(signal, label, value)
69 |
70 | button(label; kwargs...) =
71 | button(label=label; kwargs...)
72 |
73 | ######################## Textbox ###########################
74 |
75 | type Textbox{T} <: InputWidget{T}
76 | signal::Input{T}
77 | label::String
78 | range::Union(Nothing, Range)
79 | value::T
80 | end
81 |
82 | function empty(t::Type)
83 | if is(t, Number) zero(t)
84 | elseif is(t, String) ""
85 | end
86 | end
87 |
88 | function Textbox(; label="",
89 | value=utf8(""),
90 | # Allow unicode characters even if initiated with ASCII
91 | typ=typeof(value),
92 | range=nothing,
93 | signal=Input{typ}(value))
94 | if isa(value, String) && !isa(range, Nothing)
95 | throw(ArgumentError(
96 | "You cannot set a range on a string textbox"
97 | ))
98 | end
99 | Textbox{typ}(signal, label, range, value)
100 | end
101 |
102 | textbox(;kwargs...) = Textbox(;kwargs...)
103 | textbox(val; kwargs...) =
104 | Textbox(value=val; kwargs...)
105 | textbox(val::String; kwargs...) =
106 | Textbox(value=utf8(val); kwargs...)
107 |
108 | parse_msg{T<:Number}(w::Textbox{T}, val::AbstractString) = parse_msg(w, parse(T, val))
109 | function parse_msg{T<:Number}(w::Textbox{T}, val::Number)
110 | v = convert(T, val)
111 | if isa(w.range, Range)
112 | # force value to stay in range
113 | v = max(first(w.range),
114 | min(last(w.range), v))
115 | end
116 | v
117 | end
118 |
119 | ######################### Textarea ###########################
120 |
121 | type Textarea{String} <: InputWidget{String}
122 | signal::Input{String}
123 | label::String
124 | value::String
125 | end
126 |
127 | textarea(args...) = Textarea(args...)
128 |
129 | textarea(; label="",
130 | value="",
131 | signal=Input(value)) =
132 | Textarea(signal, label, value)
133 |
134 | textarea(val; kwargs...) =
135 | textarea(value=val; kwargs...)
136 |
137 | ##################### SelectionWidgets ######################
138 |
139 | immutable OptionDict
140 | keys::Vector
141 | dict::Dict
142 | end
143 |
144 | Base.getindex(x::OptionDict, y) = getindex(x.dict, y)
145 | Base.haskey(x::OptionDict, y) = haskey(x.dict, y)
146 | Base.keys(x::OptionDict) = x.keys
147 | Base.values(x::OptionDict) = [x.dict[k] for k in keys(x)]
148 | function Base.setindex!(x::OptionDict, v, k)
149 | if !haskey(x.dict, k)
150 | push!(x.keys, k)
151 | end
152 | x.dict[k] = v
153 | v
154 | end
155 | type Options{view, T} <: InputWidget{T}
156 | signal::Signal
157 | label::String
158 | value::T
159 | value_label::String
160 | options::OptionDict
161 | icons::AbstractArray
162 | tooltips::AbstractArray
163 | end
164 |
165 | Options(view::Symbol, options::OptionDict;
166 | label = "",
167 | value_label=first(options.keys),
168 | value=options[value_label],
169 | icons=[],
170 | tooltips=[],
171 | typ=typeof(value),
172 | signal=Input(value)) =
173 | Options{view, typ}(signal, label, value, value_label, options, icons, tooltips)
174 |
175 | addoption(opts, v::NTuple{2}) = opts[string(v[1])] = v[2]
176 | addoption(opts, v) = opts[string(v)] = v
177 | function Options(view::Symbol,
178 | options::AbstractArray;
179 | kwargs...)
180 | opts = OptionDict(Any[], Dict())
181 | for v in options
182 | addoption(opts, v)
183 | end
184 | Options(view, opts; kwargs...)
185 | end
186 |
187 | function Options(view::Symbol,
188 | options::Associative;
189 | kwargs...)
190 | opts = OptionDict(Any[], Dict())
191 | for (k, v) in options
192 | opts[string(k)] = v
193 | end
194 | Options(view, opts; kwargs...)
195 | end
196 |
197 | dropdown(opts; kwargs...) =
198 | Options(:Dropdown, opts; kwargs...)
199 |
200 | radiobuttons(opts; kwargs...) =
201 | Options(:RadioButtons, opts; kwargs...)
202 |
203 | select(opts; kwargs...) =
204 | Options(:Select, opts; kwargs...)
205 |
206 | togglebuttons(opts; kwargs...) =
207 | Options(:ToggleButtons, opts; kwargs...)
208 |
209 | ### Output Widgets
210 |
211 | export HTML, Latex, Progress
212 |
213 |
214 | type HTML <: Widget
215 | label::String
216 | value::String
217 | end
218 | html(label, value) = HTML(label, value)
219 | html(value; label="") = HTML(label, value)
220 |
221 | # assume we already have HTML
222 | ## writemime(io::IO, m::MIME{symbol("text/html")}, h::HTML) =
223 | ## write(io, h.value)
224 |
225 | type Latex <: Widget
226 | label::String
227 | value::String
228 | end
229 | latex(label, value::String) = Latex(label, value)
230 | latex(value::String; label="") = Latex(label, value)
231 | latex(value; label="") = Latex(label, mimewritable("application/x-latex", value) ? stringmime("application/x-latex", value) : stringmime("text/latex", value))
232 |
233 | ## # assume we already have Latex
234 | ## writemime(io::IO, m::MIME{symbol("application/x-latex")}, l::Latex) =
235 | ## write(io, l.value)
236 |
237 | type Progress <: Widget
238 | label::String
239 | value::Int
240 | range::Range
241 | end
242 |
243 | progress(args...) = Progress(args...)
244 | progress(;label="", value=0, range=0:100) =
245 | Progress(label, value, range)
246 |
247 | # Make a widget out of a domain
248 | widget(x::Signal, label="") = x
249 | widget(x::Widget, label="") = x
250 | widget(x::Range, label="") = slider(x, label=label)
251 | widget(x::AbstractVector, label="") = togglebuttons(x, label=label)
252 | widget(x::Associative, label="") = togglebuttons(x, label=label)
253 | widget(x::Bool, label="") = checkbox(x, label=label)
254 | widget(x::String, label="") = textbox(x, label=label, typ=String)
255 | widget{T <: Number}(x::T, label="") = textbox(typ=T, value=x, label=label)
256 |
--------------------------------------------------------------------------------
/src/IJulia/setup.jl:
--------------------------------------------------------------------------------
1 |
2 | using JSON
3 | using Reactive
4 | using Interact
5 | using Compat
6 |
7 | import Interact.update_view
8 | export mimewritable, writemime
9 |
10 | const ijulia_js = readall(joinpath(dirname(Base.source_path()), "ijulia.js"))
11 |
12 | try
13 | display("text/html", """
14 |
15 |
16 |
25 |
""")
26 | catch
27 | end
28 |
29 | import IJulia
30 | import IJulia: metadata, display_dict
31 | using IJulia.CommManager
32 | import IJulia.CommManager: register_comm
33 | import Base: writemime, mimewritable
34 |
35 | const comms = Dict{Signal, Comm}()
36 |
37 | function get_data_dict(value, mimetypes)
38 | dict = Dict{ASCIIString, ByteString}()
39 | for m in mimetypes
40 | if mimewritable(m, value)
41 | dict[m] = stringmime(m, value)
42 | elseif m == "text/latex" && mimewritable("application/x-latex", value)
43 | dict[string("text/latex")] =
44 | stringmime("application/x-latex", value)
45 | else
46 | warn("IPython seems to be requesting an unavailable mime type")
47 | end
48 | end
49 | return dict
50 | end
51 |
52 | function init_comm(x::Signal)
53 | if !haskey(comms, x)
54 | subscriptions = Dict{ASCIIString, Int}()
55 | function handle_subscriptions(msg)
56 | if haskey(msg.content, "data")
57 | action = get(msg.content["data"], "action", "")
58 | if action == "subscribe_mime"
59 | mime = msg.content["data"]["mime"]
60 | subscriptions[mime] = get(subscriptions, mime, 0) + 1
61 | elseif action == "unsubscribe_mime"
62 | mime = msg.content["data"]["mime"]
63 | subscriptions[mime] = get(subscriptions, mime, 1) - 1
64 | end
65 | end
66 | end
67 | # One Comm channel per signal object
68 | comm = Comm(:Signal)
69 | comms[x] = comm # Backend -> Comm
70 | # Listen for mime type registrations
71 | comm.on_msg = handle_subscriptions
72 | # prevent resending the first time?
73 | function notify(value)
74 | mimes = keys(filter((k,v) -> v > 0, subscriptions))
75 | if length(mimes) > 0
76 | send_comm(comm, @compat Dict(:value =>
77 | get_data_dict(value, mimes)))
78 | end
79 | end
80 | lift(notify, x)
81 | else
82 | comm = comms[x]
83 | end
84 |
85 | return comm
86 | end
87 |
88 | function metadata(x :: Signal)
89 | comm = init_comm(x)
90 | return @compat Dict("reactive"=>true,
91 | "comm_id"=>comm.id)
92 | end
93 |
94 | # Render the value of a signal.
95 | mimewritable(m :: MIME, s :: Signal) =
96 | mimewritable(m, s.value)
97 |
98 | function writemime(io:: IO, m :: MIME, s :: Signal)
99 | writemime(io, m, s.value)
100 | end
101 |
102 | function writemime(io::IO, ::MIME{symbol("text/html")},
103 | w::InputWidget)
104 | create_view(w)
105 | end
106 |
107 | function writemime(io::IO, ::MIME{symbol("text/html")},
108 | w::Widget)
109 | create_view(w)
110 | end
111 |
112 | function writemime{T<:Widget}(io::IO, ::MIME{symbol("text/html")},
113 | x::Signal{T})
114 | create_widget_signal(x)
115 | end
116 |
117 | ## This is for our own widgets.
118 | function register_comm(comm::Comm{:InputWidget}, msg)
119 | w_id = msg.content["data"]["widget_id"]
120 | comm.on_msg = (msg) -> recv_msg(w, msg.content["data"]["value"])
121 | end
122 |
123 | JSON.print(io::IO, s::Signal) = JSON.print(io, s.value)
124 |
125 | ##################### IPython IPEP 23: Backbone.js Widgets #################
126 |
127 | ## ButtonView ✓
128 | ## CheckboxView ✓
129 | ## DropdownView ✓
130 | ## FloatSliderView ✓
131 | ## FloatTextView ✓
132 | ## IntSliderView ✓
133 | ## IntTextView ✓
134 | ## ProgressView
135 | ## RadioButtonsView ✓
136 | ## SelectView ✓
137 | ## TextareaView ✓
138 | ## TextView ✓
139 | ## ToggleButtonsView ✓
140 | ## ToggleButtonView ✓
141 | ## AccordionView W
142 | ## ContainerView W
143 | ## PopupView W
144 | ## TabView W
145 |
146 | # Interact -> IJulia view names
147 | view_name(::HTML) = "HTMLView"
148 | view_name(::Latex) = "LatexView"
149 | view_name(::Progress) = "ProgressView"
150 | view_name{T<:Integer}(::Slider{T}) = "IntSliderView"
151 | view_name(::Button) = "ButtonView"
152 | view_name(::Textarea) = "TextareaView"
153 | view_name{T<:FloatingPoint}(::Slider{T}) = "FloatSliderView"
154 | view_name{T<:Integer}(::Textbox{T}) = "IntTextView"
155 | view_name(::Checkbox) = "CheckboxView"
156 | view_name(::ToggleButton) = "ToggleButtonView"
157 | view_name{T<:FloatingPoint}(::Textbox{T}) = "FloatTextView"
158 | view_name(::Textbox) = "TextView"
159 | view_name{view}(::Options{view}) = string(view, "View")
160 |
161 | function metadata{T <: Widget}(x :: Signal{T})
162 | Dict()
163 | end
164 |
165 | function add_ipy3_state!(state)
166 | for attr in ["color" "background" "width" "height" "border_color" "border_width" "border_style" "font_style" "font_weight" "font_size" "font_family" "padding" "margin" "border_radius"]
167 | state[attr] = ""
168 | end
169 | end
170 |
171 | const widget_comms = Dict{Widget, Comm}()
172 | function update_view(w; src=w)
173 | send_comm(widget_comms[w], view_state(w, src=src))
174 | end
175 |
176 | function view_state(w::InputWidget; src::InputWidget=w)
177 | msg = Dict()
178 | msg["method"] = "update"
179 | state = Dict()
180 | state["msg_throttle"] = 3
181 | state["_view_name"] = view_name(src)
182 | state["description"] = w.label
183 | state["visible"] = true
184 | state["disabled"] = false
185 | state["readout"] = true
186 | add_ipy3_state!(state)
187 | msg["state"] = merge(state, statedict(src))
188 | msg
189 | end
190 |
191 | function view_state(w::Widget; src::Widget=w)
192 | msg = Dict()
193 | msg["method"] = "update"
194 | state = Dict()
195 | state["msg_throttle"] = 3
196 | state["_view_name"] = view_name(src)
197 | state["description"] = w.label
198 | state["visible"] = true
199 | state["disabled"] = false
200 | add_ipy3_state!(state)
201 |
202 | msg["state"] = merge(state, statedict(src))
203 | msg
204 | end
205 |
206 | function create_view(w::Widget)
207 | if haskey(widget_comms, w)
208 | comm = widget_comms[w]
209 | else
210 | comm = Comm("ipython.widget", data=merge(Dict{String, Any}([
211 | ("model_name", "WidgetModel"),
212 | ("_model_name", "WidgetModel"), # Jupyter 4.0 missing (https://github.com/ipython/ipywidgets/pull/84)
213 | ]), view_state(w)))
214 | widget_comms[w] = comm
215 | # Send a full state update message.
216 | update_view(w) # This is redundant on 4.0 but keeps it working on Jupyter 3.0
217 |
218 | # dispatch messages to widget's handler
219 | comm.on_msg = msg -> handle_msg(w, msg)
220 | nothing # display() nothing
221 | end
222 |
223 | send_comm(comm, @compat Dict("method"=>"display"))
224 | end
225 |
226 | function create_widget_signal(s)
227 | create_view(s.value)
228 | local target = s.value
229 | lift(x->update_view(target, src=x), s, init=nothing)
230 | end
231 |
232 | include("statedict.jl")
233 | include("handle_msg.jl")
234 |
--------------------------------------------------------------------------------
/doc/notebooks/01-Introduction.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Introduction to Interact.jl"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 1,
13 | "metadata": {
14 | "collapsed": false
15 | },
16 | "outputs": [
17 | {
18 | "data": {
19 | "text/html": [
20 | ""
175 | ]
176 | },
177 | "metadata": {},
178 | "output_type": "display_data"
179 | },
180 | {
181 | "data": {
182 | "text/html": [
183 | ""
273 | ]
274 | },
275 | "metadata": {},
276 | "output_type": "display_data"
277 | }
278 | ],
279 | "source": [
280 | "using Reactive, Interact"
281 | ]
282 | },
283 | {
284 | "cell_type": "markdown",
285 | "metadata": {},
286 | "source": [
287 | "Interact.jl provides interactive widgets for IJulia. Interaction relies on [Reactive.jl](http://julialang.org/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."
288 | ]
289 | },
290 | {
291 | "cell_type": "code",
292 | "execution_count": 2,
293 | "metadata": {
294 | "collapsed": false
295 | },
296 | "outputs": [
297 | {
298 | "data": {
299 | "text/html": [],
300 | "text/plain": [
301 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.5,\"Slider X:\",0.5,0.0:0.1:1.0)"
302 | ]
303 | },
304 | "execution_count": 2,
305 | "metadata": {},
306 | "output_type": "execute_result"
307 | }
308 | ],
309 | "source": [
310 | "s = slider(0:.1:1,label=\"Slider X:\")"
311 | ]
312 | },
313 | {
314 | "cell_type": "code",
315 | "execution_count": 3,
316 | "metadata": {
317 | "collapsed": false
318 | },
319 | "outputs": [
320 | {
321 | "data": {
322 | "text/plain": [
323 | "0.5"
324 | ]
325 | },
326 | "execution_count": 3,
327 | "metadata": {
328 | "comm_id": "6c3d9404-54c5-4771-a614-3fe53821f0ca",
329 | "reactive": true
330 | },
331 | "output_type": "execute_result"
332 | }
333 | ],
334 | "source": [
335 | "signal(s)"
336 | ]
337 | },
338 | {
339 | "cell_type": "markdown",
340 | "metadata": {},
341 | "source": [
342 | "Let us now inspect the types of these entities."
343 | ]
344 | },
345 | {
346 | "cell_type": "code",
347 | "execution_count": 4,
348 | "metadata": {
349 | "collapsed": false
350 | },
351 | "outputs": [
352 | {
353 | "data": {
354 | "text/plain": [
355 | "Interact.Slider{Float64}"
356 | ]
357 | },
358 | "metadata": {},
359 | "output_type": "display_data"
360 | },
361 | {
362 | "data": {
363 | "text/plain": [
364 | "true"
365 | ]
366 | },
367 | "execution_count": 4,
368 | "metadata": {},
369 | "output_type": "execute_result"
370 | }
371 | ],
372 | "source": [
373 | "display(typeof(s));\n",
374 | "isa(s, Widget)"
375 | ]
376 | },
377 | {
378 | "cell_type": "code",
379 | "execution_count": 5,
380 | "metadata": {
381 | "collapsed": false
382 | },
383 | "outputs": [
384 | {
385 | "data": {
386 | "text/plain": [
387 | "Reactive.Input{Float64}"
388 | ]
389 | },
390 | "metadata": {},
391 | "output_type": "display_data"
392 | },
393 | {
394 | "data": {
395 | "text/plain": [
396 | "true"
397 | ]
398 | },
399 | "execution_count": 5,
400 | "metadata": {},
401 | "output_type": "execute_result"
402 | }
403 | ],
404 | "source": [
405 | "display(typeof(signal(s)));\n",
406 | "isa(signal(s), Signal{Float64})"
407 | ]
408 | },
409 | {
410 | "cell_type": "markdown",
411 | "metadata": {},
412 | "source": [
413 | "You can have many instances of the same widget in a notebook, and they stay in sync:"
414 | ]
415 | },
416 | {
417 | "cell_type": "code",
418 | "execution_count": 6,
419 | "metadata": {
420 | "collapsed": false
421 | },
422 | "outputs": [
423 | {
424 | "data": {
425 | "text/html": [],
426 | "text/plain": [
427 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.5,\"Slider X:\",0.5,0.0:0.1:1.0)"
428 | ]
429 | },
430 | "execution_count": 6,
431 | "metadata": {},
432 | "output_type": "execute_result"
433 | }
434 | ],
435 | "source": [
436 | "s"
437 | ]
438 | },
439 | {
440 | "cell_type": "markdown",
441 | "metadata": {},
442 | "source": [
443 | "## Using Widget Signals"
444 | ]
445 | },
446 | {
447 | "cell_type": "markdown",
448 | "metadata": {},
449 | "source": [
450 | "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:"
451 | ]
452 | },
453 | {
454 | "cell_type": "code",
455 | "execution_count": 7,
456 | "metadata": {
457 | "collapsed": false
458 | },
459 | "outputs": [
460 | {
461 | "data": {
462 | "text/plain": [
463 | "0.25"
464 | ]
465 | },
466 | "execution_count": 7,
467 | "metadata": {
468 | "comm_id": "d69fa2a2-c01f-49ba-9cec-c1be202dfb6e",
469 | "reactive": true
470 | },
471 | "output_type": "execute_result"
472 | }
473 | ],
474 | "source": [
475 | "xsquared = lift(x -> x*x, signal(s))"
476 | ]
477 | },
478 | {
479 | "cell_type": "markdown",
480 | "metadata": {},
481 | "source": [
482 | "Go ahead and vary the slider to see this in action."
483 | ]
484 | },
485 | {
486 | "cell_type": "markdown",
487 | "metadata": {},
488 | "source": [
489 | "You can transform a signal into pretty much anything else. Let's use the Color package to produce different saturations of red:"
490 | ]
491 | },
492 | {
493 | "cell_type": "code",
494 | "execution_count": 8,
495 | "metadata": {
496 | "collapsed": false
497 | },
498 | "outputs": [
499 | {
500 | "data": {
501 | "image/svg+xml": [
502 | "\n",
503 | "\n",
505 | "\n"
510 | ],
511 | "text/plain": [
512 | "RGB{Float64}(0.5,0.5,0.5)"
513 | ]
514 | },
515 | "execution_count": 8,
516 | "metadata": {
517 | "comm_id": "1f132e49-5507-4dc9-bb58-973669e21d93",
518 | "reactive": true
519 | },
520 | "output_type": "execute_result"
521 | }
522 | ],
523 | "source": [
524 | "using Colors\n",
525 | "lift(x -> RGB(x, 0.5, 0.5), signal(s))"
526 | ]
527 | },
528 | {
529 | "cell_type": "markdown",
530 | "metadata": {},
531 | "source": [
532 | "You can of course use several inputs as arguments to `lift` enabling you to combine many signals. Let's create a full color-picker."
533 | ]
534 | },
535 | {
536 | "cell_type": "code",
537 | "execution_count": 9,
538 | "metadata": {
539 | "collapsed": false
540 | },
541 | "outputs": [
542 | {
543 | "data": {
544 | "text/html": [],
545 | "text/plain": [
546 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.5,\"R\",0.5,0.0:0.01:1.0)"
547 | ]
548 | },
549 | "metadata": {},
550 | "output_type": "display_data"
551 | },
552 | {
553 | "data": {
554 | "text/html": [],
555 | "text/plain": [
556 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.5,\"G\",0.5,0.0:0.01:1.0)"
557 | ]
558 | },
559 | "metadata": {},
560 | "output_type": "display_data"
561 | },
562 | {
563 | "data": {
564 | "text/html": [],
565 | "text/plain": [
566 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.5,\"B\",0.5,0.0:0.01:1.0)"
567 | ]
568 | },
569 | "metadata": {},
570 | "output_type": "display_data"
571 | }
572 | ],
573 | "source": [
574 | "r = slider(0:0.01:1, label=\"R\")\n",
575 | "g = slider(0:0.01:1, label=\"G\")\n",
576 | "b = slider(0:0.01:1, label=\"B\")\n",
577 | "map(display, [r,g,b]);"
578 | ]
579 | },
580 | {
581 | "cell_type": "code",
582 | "execution_count": 10,
583 | "metadata": {
584 | "collapsed": false
585 | },
586 | "outputs": [
587 | {
588 | "data": {
589 | "image/svg+xml": [
590 | "\n",
591 | "\n",
593 | "\n"
598 | ],
599 | "text/plain": [
600 | "RGB{Float64}(0.5,0.5,0.5)"
601 | ]
602 | },
603 | "execution_count": 10,
604 | "metadata": {
605 | "comm_id": "dba030d5-7a4e-49f8-9b16-e4e334ff2d0e",
606 | "reactive": true
607 | },
608 | "output_type": "execute_result"
609 | }
610 | ],
611 | "source": [
612 | "color = lift((x, y, z) -> RGB(x, y, z), r, g, b)"
613 | ]
614 | },
615 | {
616 | "cell_type": "markdown",
617 | "metadata": {},
618 | "source": [
619 | "the `@lift` macro provides useful syntactic sugar to do this:"
620 | ]
621 | },
622 | {
623 | "cell_type": "code",
624 | "execution_count": 11,
625 | "metadata": {
626 | "collapsed": false
627 | },
628 | "outputs": [
629 | {
630 | "data": {
631 | "image/svg+xml": [
632 | "\n",
633 | "\n",
635 | "\n"
640 | ],
641 | "text/plain": [
642 | "RGB{Float64}(0.5,0.5,0.5)"
643 | ]
644 | },
645 | "execution_count": 11,
646 | "metadata": {
647 | "comm_id": "16a04d93-78c6-420d-944d-1b93ce9b982b",
648 | "reactive": true
649 | },
650 | "output_type": "execute_result"
651 | }
652 | ],
653 | "source": [
654 | "color = @lift RGB(r, g, b)"
655 | ]
656 | },
657 | {
658 | "cell_type": "markdown",
659 | "metadata": {},
660 | "source": [
661 | "We can use the HTML widget to write some text you can change the color of."
662 | ]
663 | },
664 | {
665 | "cell_type": "code",
666 | "execution_count": 12,
667 | "metadata": {
668 | "collapsed": false
669 | },
670 | "outputs": [
671 | {
672 | "data": {
673 | "text/html": [],
674 | "text/plain": [
675 | "Interact.HTML(\"\",\"Hello, World!
\")"
676 | ]
677 | },
678 | "execution_count": 12,
679 | "metadata": {},
680 | "output_type": "execute_result"
681 | },
682 | {
683 | "name": "stderr",
684 | "output_type": "stream",
685 | "text": [
686 | "WARNING: both Interact and Base export \"HTML\"; uses of it in module Main must be qualified\n"
687 | ]
688 | }
689 | ],
690 | "source": [
691 | "lift(color -> html(string(\"Hello, World!
\")), color)"
692 | ]
693 | },
694 | {
695 | "cell_type": "markdown",
696 | "metadata": {},
697 | "source": [
698 | "## The @manipulate Macro"
699 | ]
700 | },
701 | {
702 | "cell_type": "markdown",
703 | "metadata": {},
704 | "source": [
705 | "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:"
706 | ]
707 | },
708 | {
709 | "cell_type": "code",
710 | "execution_count": 13,
711 | "metadata": {
712 | "collapsed": false
713 | },
714 | "outputs": [
715 | {
716 | "data": {
717 | "text/html": [],
718 | "text/plain": [
719 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.5,\"r\",0.5,0.0:0.05:1.0)"
720 | ]
721 | },
722 | "metadata": {},
723 | "output_type": "display_data"
724 | },
725 | {
726 | "data": {
727 | "text/html": [],
728 | "text/plain": [
729 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.5,\"g\",0.5,0.0:0.05:1.0)"
730 | ]
731 | },
732 | "metadata": {},
733 | "output_type": "display_data"
734 | },
735 | {
736 | "data": {
737 | "text/html": [],
738 | "text/plain": [
739 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.5,\"b\",0.5,0.0:0.05:1.0)"
740 | ]
741 | },
742 | "metadata": {},
743 | "output_type": "display_data"
744 | },
745 | {
746 | "data": {
747 | "text/html": [],
748 | "text/plain": [
749 | "Interact.HTML(\"\",\"Color me
\")"
750 | ]
751 | },
752 | "execution_count": 13,
753 | "metadata": {},
754 | "output_type": "execute_result"
755 | }
756 | ],
757 | "source": [
758 | "@manipulate for r = 0:.05:1, g = 0:.05:1, b = 0:.05:1\n",
759 | " html(string(\"Color me
\"))\n",
760 | "end"
761 | ]
762 | },
763 | {
764 | "cell_type": "markdown",
765 | "metadata": {},
766 | "source": [
767 | "## Signal of Widgets"
768 | ]
769 | },
770 | {
771 | "cell_type": "markdown",
772 | "metadata": {},
773 | "source": [
774 | "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 Slider:"
775 | ]
776 | },
777 | {
778 | "cell_type": "code",
779 | "execution_count": 14,
780 | "metadata": {
781 | "collapsed": false
782 | },
783 | "outputs": [
784 | {
785 | "data": {
786 | "text/html": [],
787 | "text/plain": [
788 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 3.1,\"x\",3.1,0.0:0.1:6.2)"
789 | ]
790 | },
791 | "metadata": {},
792 | "output_type": "display_data"
793 | },
794 | {
795 | "data": {
796 | "text/html": [],
797 | "text/plain": [
798 | "Interact.Slider{Float64}([Reactive.Input{Float64}] -0.0830894028174964,\"sin(2x)\",-0.0830894028174964,-1.0:0.05:1.0)"
799 | ]
800 | },
801 | "metadata": {},
802 | "output_type": "display_data"
803 | },
804 | {
805 | "data": {
806 | "text/html": [],
807 | "text/plain": [
808 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.9965420970232175,\"cos(2x)\",0.9965420970232175,-1.0:0.05:1.0)"
809 | ]
810 | },
811 | "metadata": {},
812 | "output_type": "display_data"
813 | }
814 | ],
815 | "source": [
816 | "x = slider(0:.1:2pi, label=\"x\")\n",
817 | "s = @lift slider(-1:.05:1, value=sin(2x), label=\"sin(2x)\")\n",
818 | "c = @lift slider(-1:.05:1, value=cos(2x), label=\"cos(2x)\")\n",
819 | "map(display, [x,s,c]);"
820 | ]
821 | },
822 | {
823 | "cell_type": "markdown",
824 | "metadata": {},
825 | "source": [
826 | "Now vary the x slider to see sin(2x) and cos(2x) get set to their appropriate values."
827 | ]
828 | },
829 | {
830 | "cell_type": "markdown",
831 | "metadata": {},
832 | "source": [
833 | "But in the above case, you cannot also use sin(2x) and cos(2x) sliders as input values. To do this, we will have to create a separate Input signal and pass it as argument to lift. Unfortunaltely, we cannot use the `@lift` macro here because of ambiguity in parsing. Example:"
834 | ]
835 | },
836 | {
837 | "cell_type": "code",
838 | "execution_count": 15,
839 | "metadata": {
840 | "collapsed": false
841 | },
842 | "outputs": [
843 | {
844 | "data": {
845 | "text/plain": [
846 | "0.0"
847 | ]
848 | },
849 | "execution_count": 15,
850 | "metadata": {
851 | "comm_id": "777bf177-217d-4b82-9269-3c95868abce8",
852 | "reactive": true
853 | },
854 | "output_type": "execute_result"
855 | }
856 | ],
857 | "source": [
858 | "fx = Input(0.0) # A float input"
859 | ]
860 | },
861 | {
862 | "cell_type": "code",
863 | "execution_count": 16,
864 | "metadata": {
865 | "collapsed": false
866 | },
867 | "outputs": [
868 | {
869 | "data": {
870 | "text/html": [],
871 | "text/plain": [
872 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 3.1,\"x\",3.1,0.0:0.1:6.2)"
873 | ]
874 | },
875 | "metadata": {},
876 | "output_type": "display_data"
877 | },
878 | {
879 | "data": {
880 | "text/html": [],
881 | "text/plain": [
882 | "Interact.Slider{Float64}([Reactive.Input{Float64}] 0.0,\"f(x)\",0.04158066243329049,-1.0:0.05:1.0)"
883 | ]
884 | },
885 | "metadata": {},
886 | "output_type": "display_data"
887 | }
888 | ],
889 | "source": [
890 | "x = slider(0:.1:2pi, label=\"x\")\n",
891 | "y = lift(v -> slider(-1:.05:1, value=sin(v), signal=fx, label=\"f(x)\"), x)\n",
892 | "map(display, (x,y));"
893 | ]
894 | },
895 | {
896 | "cell_type": "markdown",
897 | "metadata": {},
898 | "source": [
899 | "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."
900 | ]
901 | }
902 | ],
903 | "metadata": {
904 | "kernelspec": {
905 | "display_name": "Julia 0.4.0-rc1",
906 | "language": "julia",
907 | "name": "julia-0.4"
908 | },
909 | "language_info": {
910 | "file_extension": ".jl",
911 | "mimetype": "application/julia",
912 | "name": "julia",
913 | "version": "0.4.0"
914 | }
915 | },
916 | "nbformat": 4,
917 | "nbformat_minor": 0
918 | }
919 |
--------------------------------------------------------------------------------
/doc/notebooks/02-Widgets Overview.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Widgets Overview"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "The following is a run through of the widgets you can create with Interact. All widgets allow for the following keyword arguments:\n",
15 | "\n",
16 | "* `label`: Label to be shown next to the widget\n",
17 | "* `value`: The value the widget should be set to when created\n",
18 | "* `signal`: A signal object of type `Reactive.Input` which gets the value of the widget as user enters input.\n",
19 | "\n",
20 | "Many of the widgets have keyword arguments specific to them. See below for more."
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": 1,
26 | "metadata": {
27 | "collapsed": false
28 | },
29 | "outputs": [
30 | {
31 | "data": {
32 | "text/html": [
33 | ""
188 | ]
189 | },
190 | "metadata": {},
191 | "output_type": "display_data"
192 | },
193 | {
194 | "data": {
195 | "text/html": [
196 | "\n",
197 | " \n",
288 | " \n",
297 | "
"
298 | ]
299 | },
300 | "metadata": {},
301 | "output_type": "display_data"
302 | }
303 | ],
304 | "source": [
305 | "using Interact\n",
306 | "using Reactive"
307 | ]
308 | },
309 | {
310 | "cell_type": "markdown",
311 | "metadata": {},
312 | "source": [
313 | "## Slider"
314 | ]
315 | },
316 | {
317 | "cell_type": "markdown",
318 | "metadata": {},
319 | "source": [
320 | "Sliders are arguably the most useful of the widgets. A slider can be created with the `slider{T <: Number}(range::Range{T})` function. The value of the slider defaults to the median of the range, and can be set using the `value::T` keyword argument. The type of signal a slider depends on the type of the range. E.g. A floating point range like `0:π/4:2π` gives a signal of floating point values, while a range like `1:10` gives a signal of integers."
321 | ]
322 | },
323 | {
324 | "cell_type": "code",
325 | "execution_count": 2,
326 | "metadata": {
327 | "collapsed": false
328 | },
329 | "outputs": [
330 | {
331 | "data": {
332 | "text/html": [],
333 | "text/plain": [
334 | "Slider{Float64}([Input{Float64}] 3.141592653589793,\"\",3.141592653589793,0.0:0.7853981633974483:6.283185307179586)"
335 | ]
336 | },
337 | "execution_count": 2,
338 | "metadata": {},
339 | "output_type": "execute_result"
340 | }
341 | ],
342 | "source": [
343 | "float_slider = slider(0:π/4:2π)"
344 | ]
345 | },
346 | {
347 | "cell_type": "code",
348 | "execution_count": 3,
349 | "metadata": {
350 | "collapsed": false
351 | },
352 | "outputs": [
353 | {
354 | "data": {
355 | "text/html": [],
356 | "text/plain": [
357 | "Slider{Int64}([Input{Int64}] 5,\"\",5,1:10)"
358 | ]
359 | },
360 | "execution_count": 3,
361 | "metadata": {},
362 | "output_type": "execute_result"
363 | }
364 | ],
365 | "source": [
366 | "int_slider = slider(1:10)"
367 | ]
368 | },
369 | {
370 | "cell_type": "code",
371 | "execution_count": 4,
372 | "metadata": {
373 | "collapsed": false
374 | },
375 | "outputs": [
376 | {
377 | "data": {
378 | "text/plain": [
379 | "5"
380 | ]
381 | },
382 | "execution_count": 4,
383 | "metadata": {
384 | "comm_id": "e71a3dec-ee8c-46ca-8b2d-bab0b6611c3e",
385 | "reactive": true
386 | },
387 | "output_type": "execute_result"
388 | }
389 | ],
390 | "source": [
391 | "signal(int_slider)"
392 | ]
393 | },
394 | {
395 | "cell_type": "markdown",
396 | "metadata": {},
397 | "source": [
398 | "## Checkbox"
399 | ]
400 | },
401 | {
402 | "cell_type": "markdown",
403 | "metadata": {},
404 | "source": [
405 | "`checkbox` takes an optional first argument which defaults to `false` and creates a checkbox."
406 | ]
407 | },
408 | {
409 | "cell_type": "code",
410 | "execution_count": 5,
411 | "metadata": {
412 | "collapsed": false
413 | },
414 | "outputs": [
415 | {
416 | "data": {
417 | "text/html": [],
418 | "text/plain": [
419 | "Checkbox([Input{Bool}] false,\"\",false)"
420 | ]
421 | },
422 | "metadata": {},
423 | "output_type": "display_data"
424 | },
425 | {
426 | "data": {
427 | "text/html": [],
428 | "text/plain": [
429 | "Checkbox([Input{Bool}] true,\"\",true)"
430 | ]
431 | },
432 | "execution_count": 5,
433 | "metadata": {},
434 | "output_type": "execute_result"
435 | }
436 | ],
437 | "source": [
438 | "display(checkbox())\n",
439 | "checkbox(true)"
440 | ]
441 | },
442 | {
443 | "cell_type": "markdown",
444 | "metadata": {},
445 | "source": [
446 | "## Toggle"
447 | ]
448 | },
449 | {
450 | "cell_type": "markdown",
451 | "metadata": {},
452 | "source": [
453 | "You can create a toggle button with `togglebutton` which takes as an optional argument its label."
454 | ]
455 | },
456 | {
457 | "cell_type": "code",
458 | "execution_count": 6,
459 | "metadata": {
460 | "collapsed": false
461 | },
462 | "outputs": [
463 | {
464 | "data": {
465 | "text/html": [],
466 | "text/plain": [
467 | "ToggleButton([Input{Bool}] true,\"Mary called\",true)"
468 | ]
469 | },
470 | "execution_count": 6,
471 | "metadata": {},
472 | "output_type": "execute_result"
473 | }
474 | ],
475 | "source": [
476 | "status = togglebutton(\"Mary called\", value=true)"
477 | ]
478 | },
479 | {
480 | "cell_type": "code",
481 | "execution_count": 7,
482 | "metadata": {
483 | "collapsed": false
484 | },
485 | "outputs": [
486 | {
487 | "data": {
488 | "text/plain": [
489 | "\"Mary called\""
490 | ]
491 | },
492 | "execution_count": 7,
493 | "metadata": {
494 | "comm_id": "95bce9a4-54ca-4452-85e3-18c8205ed480",
495 | "reactive": true
496 | },
497 | "output_type": "execute_result"
498 | }
499 | ],
500 | "source": [
501 | "lift(s -> s ? \"Mary called\" : \"Mary didn't call\", status)"
502 | ]
503 | },
504 | {
505 | "cell_type": "markdown",
506 | "metadata": {},
507 | "source": [
508 | "## Button"
509 | ]
510 | },
511 | {
512 | "cell_type": "markdown",
513 | "metadata": {},
514 | "source": [
515 | "A button gives out a signal of a constant signal which is `nothing` by default. You can set this using the `value` keyword argument. The signal updates when the button is clicked. "
516 | ]
517 | },
518 | {
519 | "cell_type": "code",
520 | "execution_count": 8,
521 | "metadata": {
522 | "collapsed": false
523 | },
524 | "outputs": [
525 | {
526 | "data": {
527 | "text/html": [],
528 | "text/plain": [
529 | "Button{Nothing}([Input{Nothing}] nothing,\"Click Me\",nothing)"
530 | ]
531 | },
532 | "execution_count": 8,
533 | "metadata": {},
534 | "output_type": "execute_result"
535 | }
536 | ],
537 | "source": [
538 | "b = button(\"Click Me\")"
539 | ]
540 | },
541 | {
542 | "cell_type": "markdown",
543 | "metadata": {},
544 | "source": [
545 | "Here is how you can count the number of clicks made on a button using `foldl` on the signal:"
546 | ]
547 | },
548 | {
549 | "cell_type": "code",
550 | "execution_count": 9,
551 | "metadata": {
552 | "collapsed": false
553 | },
554 | "outputs": [
555 | {
556 | "data": {
557 | "text/plain": [
558 | "0"
559 | ]
560 | },
561 | "execution_count": 9,
562 | "metadata": {
563 | "comm_id": "eab449fc-854d-4e66-9c61-e4b600e05568",
564 | "reactive": true
565 | },
566 | "output_type": "execute_result"
567 | }
568 | ],
569 | "source": [
570 | "foldl((acc, value) -> acc + 1, 0, signal(b))"
571 | ]
572 | },
573 | {
574 | "cell_type": "markdown",
575 | "metadata": {},
576 | "source": [
577 | "## Option widgets"
578 | ]
579 | },
580 | {
581 | "cell_type": "markdown",
582 | "metadata": {},
583 | "source": [
584 | "There are 3 options widgets: `dropdown`, `togglebuttons`, `radiobuttons`. There are two types allowed as an argument while invoking these:\n",
585 | "1. `AbstractArray` (e.g. `Vector`, `Tuple`)\n",
586 | "2. `Associative` (e.g. `Dict`, `OrderedDict`)\n",
587 | "The default value is the first element (or undefined in case of an undordered `Associative` like `Dict`), but this can be set using the `value` keyword argument."
588 | ]
589 | },
590 | {
591 | "cell_type": "code",
592 | "execution_count": 10,
593 | "metadata": {
594 | "collapsed": false
595 | },
596 | "outputs": [
597 | {
598 | "data": {
599 | "text/html": [],
600 | "text/plain": [
601 | "Options{:Dropdown,ASCIIString}([Input{ASCIIString}] one,\"\",\"one\",\"one\",OptionDict({\"one\",\"two\",\"three\"},{\"two\"=>\"two\",\"one\"=>\"one\",\"three\"=>\"three\"}),None[],None[])"
602 | ]
603 | },
604 | "execution_count": 10,
605 | "metadata": {},
606 | "output_type": "execute_result"
607 | }
608 | ],
609 | "source": [
610 | "a = dropdown([\"one\", \"two\", \"three\"])"
611 | ]
612 | },
613 | {
614 | "cell_type": "code",
615 | "execution_count": 11,
616 | "metadata": {
617 | "collapsed": false
618 | },
619 | "outputs": [
620 | {
621 | "data": {
622 | "text/plain": [
623 | "\"one\""
624 | ]
625 | },
626 | "execution_count": 11,
627 | "metadata": {
628 | "comm_id": "631df3d8-0530-4970-9cef-2e1852842f12",
629 | "reactive": true
630 | },
631 | "output_type": "execute_result"
632 | }
633 | ],
634 | "source": [
635 | "signal(a)"
636 | ]
637 | },
638 | {
639 | "cell_type": "code",
640 | "execution_count": 12,
641 | "metadata": {
642 | "collapsed": false
643 | },
644 | "outputs": [
645 | {
646 | "data": {
647 | "text/html": [],
648 | "text/plain": [
649 | "Options{:RadioButtons,Function}([Input{Function}] -,\"\",-,\"Sub\",OptionDict({\"Sub\",\"Add\",\"Exp\"},{\"Sub\"=>-,\"Add\"=>+,\"Exp\"=>^}),None[],None[])"
650 | ]
651 | },
652 | "execution_count": 12,
653 | "metadata": {},
654 | "output_type": "execute_result"
655 | }
656 | ],
657 | "source": [
658 | "f = radiobuttons([\"Add\" => +, \"Sub\" => -, \"Exp\" => ^])"
659 | ]
660 | },
661 | {
662 | "cell_type": "code",
663 | "execution_count": 13,
664 | "metadata": {
665 | "collapsed": false
666 | },
667 | "outputs": [
668 | {
669 | "data": {
670 | "text/plain": [
671 | "2.718281828459045 - 3.141592653589793im"
672 | ]
673 | },
674 | "execution_count": 13,
675 | "metadata": {
676 | "comm_id": "e5eb4d34-7cfb-4900-a0c7-11c80aa374d8",
677 | "reactive": true
678 | },
679 | "output_type": "execute_result"
680 | }
681 | ],
682 | "source": [
683 | "@lift f(e, π*im)"
684 | ]
685 | },
686 | {
687 | "cell_type": "markdown",
688 | "metadata": {},
689 | "source": [
690 | "Notice that the order \"Add\", \"Sub\", \"Exp\" was not retained in the above example, because a `Dict` does not save the ordering. To overcome this, we can use `OrderedDict` from DataStructures.jl package."
691 | ]
692 | },
693 | {
694 | "cell_type": "code",
695 | "execution_count": 14,
696 | "metadata": {
697 | "collapsed": false
698 | },
699 | "outputs": [
700 | {
701 | "name": "stderr",
702 | "output_type": "stream",
703 | "text": [
704 | "Warning: using DataStructures.status in module Main conflicts with an existing identifier.\n"
705 | ]
706 | },
707 | {
708 | "data": {
709 | "text/html": [],
710 | "text/plain": [
711 | "Options{:ToggleButtons,Function}([Input{Function}] +,\"\",+,\"Add\",OptionDict({\"Add\",\"Sub\",\"Exp\"},{\"Sub\"=>-,\"Add\"=>+,\"Exp\"=>^}),None[],None[])"
712 | ]
713 | },
714 | "execution_count": 14,
715 | "metadata": {},
716 | "output_type": "execute_result"
717 | }
718 | ],
719 | "source": [
720 | "using DataStructures\n",
721 | "f_ = togglebuttons([(\"Add\", +), (\"Sub\", -), (\"Exp\", ^)])"
722 | ]
723 | },
724 | {
725 | "cell_type": "code",
726 | "execution_count": 15,
727 | "metadata": {
728 | "collapsed": false
729 | },
730 | "outputs": [
731 | {
732 | "data": {
733 | "text/plain": [
734 | "2.718281828459045 + 3.141592653589793im"
735 | ]
736 | },
737 | "execution_count": 15,
738 | "metadata": {
739 | "comm_id": "2e173b60-3947-42d6-bee0-a6485417c800",
740 | "reactive": true
741 | },
742 | "output_type": "execute_result"
743 | }
744 | ],
745 | "source": [
746 | "@lift f_(e, π*im)"
747 | ]
748 | },
749 | {
750 | "cell_type": "markdown",
751 | "metadata": {},
752 | "source": [
753 | "## Textbox"
754 | ]
755 | },
756 | {
757 | "cell_type": "markdown",
758 | "metadata": {},
759 | "source": [
760 | "A textbox can be of a `Number` or `String` type. `textbox` takes one argument: its default value."
761 | ]
762 | },
763 | {
764 | "cell_type": "code",
765 | "execution_count": 16,
766 | "metadata": {
767 | "collapsed": false
768 | },
769 | "outputs": [
770 | {
771 | "data": {
772 | "text/html": [],
773 | "text/plain": [
774 | "Textbox{UTF8String}([Input{UTF8String}] Change me,\"\",nothing,\"Change me\")"
775 | ]
776 | },
777 | "execution_count": 16,
778 | "metadata": {},
779 | "output_type": "execute_result"
780 | }
781 | ],
782 | "source": [
783 | "string_box = textbox(\"Change me\")"
784 | ]
785 | },
786 | {
787 | "cell_type": "code",
788 | "execution_count": 17,
789 | "metadata": {
790 | "collapsed": false
791 | },
792 | "outputs": [
793 | {
794 | "data": {
795 | "text/plain": [
796 | "\"Change me\""
797 | ]
798 | },
799 | "execution_count": 17,
800 | "metadata": {
801 | "comm_id": "9446918e-67e5-4748-a666-1340d309275e",
802 | "reactive": true
803 | },
804 | "output_type": "execute_result"
805 | }
806 | ],
807 | "source": [
808 | "signal(string_box)"
809 | ]
810 | },
811 | {
812 | "cell_type": "markdown",
813 | "metadata": {},
814 | "source": [
815 | "A textbox can be of a `Number` type as well. Just set a default number value, or use `textbox(typ=T)` where T is a `Number` type."
816 | ]
817 | },
818 | {
819 | "cell_type": "code",
820 | "execution_count": 18,
821 | "metadata": {
822 | "collapsed": false
823 | },
824 | "outputs": [
825 | {
826 | "data": {
827 | "text/html": [],
828 | "text/plain": [
829 | "Textbox{Int64}([Input{Int64}] 0,\"\",nothing,0)"
830 | ]
831 | },
832 | "execution_count": 18,
833 | "metadata": {},
834 | "output_type": "execute_result"
835 | }
836 | ],
837 | "source": [
838 | "int_box = textbox(0)"
839 | ]
840 | },
841 | {
842 | "cell_type": "code",
843 | "execution_count": 19,
844 | "metadata": {
845 | "collapsed": false
846 | },
847 | "outputs": [
848 | {
849 | "data": {
850 | "text/plain": [
851 | "0"
852 | ]
853 | },
854 | "execution_count": 19,
855 | "metadata": {
856 | "comm_id": "98d82d7a-2940-4048-9534-aa58db46d9b8",
857 | "reactive": true
858 | },
859 | "output_type": "execute_result"
860 | }
861 | ],
862 | "source": [
863 | "signal(int_box)"
864 | ]
865 | },
866 | {
867 | "cell_type": "markdown",
868 | "metadata": {},
869 | "source": [
870 | "If creating a number typed textbox, you can also pass along an optional `range` field to set a bound on the possible values one can input. If an entered value exceeds the range, it is replaced by its nearest bounding number."
871 | ]
872 | },
873 | {
874 | "cell_type": "code",
875 | "execution_count": 20,
876 | "metadata": {
877 | "collapsed": false
878 | },
879 | "outputs": [
880 | {
881 | "data": {
882 | "text/html": [],
883 | "text/plain": [
884 | "Textbox{Float64}([Input{Float64}] 6.283185307179586,\"\",-10.0:1.0:10.0,6.283185307179586)"
885 | ]
886 | },
887 | "execution_count": 20,
888 | "metadata": {},
889 | "output_type": "execute_result"
890 | }
891 | ],
892 | "source": [
893 | "bounded_float_box = textbox(2pi, range=-10.0:10)"
894 | ]
895 | },
896 | {
897 | "cell_type": "code",
898 | "execution_count": 21,
899 | "metadata": {
900 | "collapsed": false
901 | },
902 | "outputs": [
903 | {
904 | "data": {
905 | "text/plain": [
906 | "6.283185307179586"
907 | ]
908 | },
909 | "execution_count": 21,
910 | "metadata": {
911 | "comm_id": "90a427f2-6484-4828-801d-a5587c455cb0",
912 | "reactive": true
913 | },
914 | "output_type": "execute_result"
915 | }
916 | ],
917 | "source": [
918 | "signal(bounded_float_box)"
919 | ]
920 | },
921 | {
922 | "cell_type": "markdown",
923 | "metadata": {},
924 | "source": [
925 | "## Textarea"
926 | ]
927 | },
928 | {
929 | "cell_type": "markdown",
930 | "metadata": {},
931 | "source": [
932 | "`textarea` takes an optional default value and creates a textarea. Its signal changes when you type."
933 | ]
934 | },
935 | {
936 | "cell_type": "code",
937 | "execution_count": 22,
938 | "metadata": {
939 | "collapsed": false
940 | },
941 | "outputs": [
942 | {
943 | "data": {
944 | "text/html": [],
945 | "text/plain": [
946 | "Textarea{ASCIIString}([Input{ASCIIString}] Your very own $\\LaTeX$ editor,\"\",\"Your very own \\$\\\\LaTeX\\$ editor\")"
947 | ]
948 | },
949 | "execution_count": 22,
950 | "metadata": {},
951 | "output_type": "execute_result"
952 | }
953 | ],
954 | "source": [
955 | "tex = textarea(\"Your very own \\$\\\\LaTeX\\$ editor\")"
956 | ]
957 | },
958 | {
959 | "cell_type": "code",
960 | "execution_count": 23,
961 | "metadata": {
962 | "collapsed": false
963 | },
964 | "outputs": [
965 | {
966 | "data": {
967 | "text/html": [],
968 | "text/plain": [
969 | "Latex(\"\",\"Your very own \\$\\\\LaTeX\\$ editor\")"
970 | ]
971 | },
972 | "execution_count": 23,
973 | "metadata": {},
974 | "output_type": "execute_result"
975 | }
976 | ],
977 | "source": [
978 | "@lift latex(tex)"
979 | ]
980 | },
981 | {
982 | "cell_type": "markdown",
983 | "metadata": {},
984 | "source": [
985 | "## The `widget` Function"
986 | ]
987 | },
988 | {
989 | "cell_type": "markdown",
990 | "metadata": {},
991 | "source": [
992 | "`widget` tries to coerce a value into a widget."
993 | ]
994 | },
995 | {
996 | "cell_type": "code",
997 | "execution_count": 24,
998 | "metadata": {
999 | "collapsed": false
1000 | },
1001 | "outputs": [
1002 | {
1003 | "data": {
1004 | "text/html": [],
1005 | "text/plain": [
1006 | "Slider{Int64}([Input{Int64}] 5,\"\",5,1:10)"
1007 | ]
1008 | },
1009 | "metadata": {},
1010 | "output_type": "display_data"
1011 | },
1012 | {
1013 | "data": {
1014 | "text/html": [],
1015 | "text/plain": [
1016 | "Checkbox([Input{Bool}] false,\"\",false)"
1017 | ]
1018 | },
1019 | "metadata": {},
1020 | "output_type": "display_data"
1021 | },
1022 | {
1023 | "data": {
1024 | "text/html": [],
1025 | "text/plain": [
1026 | "Textbox{String}([Input{String}] text,\"\",nothing,\"text\")"
1027 | ]
1028 | },
1029 | "metadata": {},
1030 | "output_type": "display_data"
1031 | },
1032 | {
1033 | "data": {
1034 | "text/html": [],
1035 | "text/plain": [
1036 | "Textbox{Float64}([Input{Float64}] 1.1,\"\",nothing,1.1)"
1037 | ]
1038 | },
1039 | "metadata": {},
1040 | "output_type": "display_data"
1041 | },
1042 | {
1043 | "data": {
1044 | "text/html": [],
1045 | "text/plain": [
1046 | "Options{:ToggleButtons,Symbol}([Input{Symbol}] on,\"\",:on,\"on\",OptionDict({\"on\",\"off\"},{\"off\"=>:off,\"on\"=>:on}),None[],None[])"
1047 | ]
1048 | },
1049 | "metadata": {},
1050 | "output_type": "display_data"
1051 | },
1052 | {
1053 | "data": {
1054 | "text/html": [],
1055 | "text/plain": [
1056 | "Options{:ToggleButtons,Float64}([Input{Float64}] 6.283185307179586,\"\",6.283185307179586,\"τ\",OptionDict({\"τ\",\"π\"},{\"τ\"=>6.283185307179586,\"π\"=>3.141592653589793}),None[],None[])"
1057 | ]
1058 | },
1059 | "metadata": {},
1060 | "output_type": "display_data"
1061 | }
1062 | ],
1063 | "source": [
1064 | "map(display, [\n",
1065 | " widget(1:10), # Slider\n",
1066 | " widget(false), # Checkbox\n",
1067 | " widget(\"text\"), # Textbox\n",
1068 | " widget(1.1), # Number Textbox\n",
1069 | " widget([:on, :off]), # Toggle Buttons\n",
1070 | " widget([:π => float(π), :τ => 2π])\n",
1071 | " ]);"
1072 | ]
1073 | }
1074 | ],
1075 | "metadata": {
1076 | "kernelspec": {
1077 | "display_name": "Julia 0.3.11",
1078 | "language": "julia",
1079 | "name": "julia-0.3"
1080 | },
1081 | "language_info": {
1082 | "file_extension": ".jl",
1083 | "mimetype": "application/julia",
1084 | "name": "julia",
1085 | "version": "0.3.11"
1086 | }
1087 | },
1088 | "nbformat": 4,
1089 | "nbformat_minor": 0
1090 | }
1091 |
--------------------------------------------------------------------------------
/doc/notebooks/04-Animations.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Animating"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 2,
13 | "metadata": {
14 | "collapsed": false
15 | },
16 | "outputs": [
17 | {
18 | "data": {
19 | "text/html": [
20 | ""
175 | ]
176 | },
177 | "metadata": {},
178 | "output_type": "display_data"
179 | },
180 | {
181 | "data": {
182 | "text/html": [
183 | "\n",
184 | " \n",
275 | " \n",
284 | "
"
285 | ]
286 | },
287 | "metadata": {},
288 | "output_type": "display_data"
289 | }
290 | ],
291 | "source": [
292 | "using Interact, Reactive"
293 | ]
294 | },
295 | {
296 | "cell_type": "markdown",
297 | "metadata": {},
298 | "source": [
299 | "It is possible to create interactive animations using Reactive's [timing functions](julialang.org/Reactive.jl/api.html#timing)."
300 | ]
301 | },
302 | {
303 | "cell_type": "markdown",
304 | "metadata": {},
305 | "source": [
306 | "Functions like `fps`, `fpswhen`, `every` etc, let us create periodically updating signals. This, combined with the other functions in Reactive provide for declarative ways to define animations. Let us now take the n-gon compose example from interactive diagrams notebook and animate it."
307 | ]
308 | },
309 | {
310 | "cell_type": "code",
311 | "execution_count": 3,
312 | "metadata": {
313 | "collapsed": false
314 | },
315 | "outputs": [
316 | {
317 | "data": {
318 | "text/html": [],
319 | "text/plain": [
320 | "Options{:ToggleButtons,ASCIIString}([Input{ASCIIString}] yellow,\"color\",\"yellow\",\"yellow\",OptionDict({\"yellow\",\"cyan\",\"tomato\"},{\"cyan\"=>\"cyan\",\"yellow\"=>\"yellow\",\"tomato\"=>\"tomato\"}),None[],None[])"
321 | ]
322 | },
323 | "metadata": {},
324 | "output_type": "display_data"
325 | },
326 | {
327 | "data": {
328 | "text/html": [],
329 | "text/plain": [
330 | "Slider{Int64}([Input{Int64}] 11,\"n\",11,3:20)"
331 | ]
332 | },
333 | "metadata": {},
334 | "output_type": "display_data"
335 | },
336 | {
337 | "data": {
338 | "text/plain": [
339 | "(1.442375647347034e9,0.0)"
340 | ]
341 | },
342 | "metadata": {
343 | "comm_id": "706cae3c-e59a-4c10-b273-8efab186d440",
344 | "reactive": true
345 | },
346 | "output_type": "display_data"
347 | },
348 | {
349 | "data": {
350 | "image/svg+xml": [
351 | "\n",
352 | "\n"
365 | ],
366 | "text/html": [
367 | ""
368 | ],
369 | "text/plain": [
370 | "Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Form{SimplePolygonPrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}}}}(SimplePolygonPrimitive[SimplePolygonPrimitive{Point{XM<:Measure{S,T},YM<:Measure{S,T}}}(Point[Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.40839558819933697,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.008462990469024267,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.15719248063264762,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.13601785117233794,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.014828352228835828,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.3791344871395757,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.026502813642530987,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.6606250743550341,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.18850921213856642,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.8911182545950309,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.4494117858416047,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.9974342495127013,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.7263757621557467,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.9458183647051844,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.931467024284039,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.7526582809952536,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.9995705372749022,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.47928096800365433,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.9090639141835846,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.21248180211888323,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.6886824824245166,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.03696768922015603,0.0,0.0)),Point{Measure{Float64,MeasureNil},Measure{MeasureNil,Float64}}(Measure{Float64,MeasureNil}(0.0,0.4083956194478288,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.00846298464545625,0.0,0.0))])]),ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(1.0,1.0,0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)"
371 | ]
372 | },
373 | "execution_count": 3,
374 | "metadata": {},
375 | "output_type": "execute_result"
376 | }
377 | ],
378 | "source": [
379 | "using Colors\n",
380 | "using Compose\n",
381 | "\n",
382 | "@manipulate for color=[\"yellow\", \"cyan\", \"tomato\"], n=3:20, t_dt=timestamp(fps(30.))\n",
383 | " t, dt = t_dt # current time, time since last frame\n",
384 | " compose(context(), fill(parse(Colorant, color)),\n",
385 | " polygon([((1+sin(θ+t))/2, (1+cos(θ+t))/2) for θ in 0:2π/n:2π]))\n",
386 | "end"
387 | ]
388 | },
389 | {
390 | "cell_type": "markdown",
391 | "metadata": {},
392 | "source": [
393 | "It's often advisable to give your animations a pause checkbox. Here is a bouncing ball that you can pause and resume"
394 | ]
395 | },
396 | {
397 | "cell_type": "code",
398 | "execution_count": 5,
399 | "metadata": {
400 | "collapsed": false
401 | },
402 | "outputs": [
403 | {
404 | "data": {
405 | "text/html": [],
406 | "text/plain": [
407 | "Checkbox([Input{Bool}] false,\"paused\",false)"
408 | ]
409 | },
410 | "metadata": {},
411 | "output_type": "display_data"
412 | },
413 | {
414 | "data": {
415 | "text/plain": [
416 | "0.0"
417 | ]
418 | },
419 | "metadata": {
420 | "comm_id": "b9ebee68-3782-46e4-99b0-3f2418a81db8",
421 | "reactive": true
422 | },
423 | "output_type": "display_data"
424 | },
425 | {
426 | "data": {
427 | "text/plain": [
428 | "0.0"
429 | ]
430 | },
431 | "metadata": {
432 | "comm_id": "bf22d3c6-ba10-4f88-9f8b-9d0c89019124",
433 | "reactive": true
434 | },
435 | "output_type": "display_data"
436 | },
437 | {
438 | "data": {
439 | "text/html": [],
440 | "text/plain": [
441 | "Slider{Float64}([Input{Float64}] 2.5,\"gravity\",2.5,0.0:0.01:5.0)"
442 | ]
443 | },
444 | "metadata": {},
445 | "output_type": "display_data"
446 | },
447 | {
448 | "data": {
449 | "text/html": [],
450 | "text/plain": [
451 | "Options{:ToggleButtons,ASCIIString}([Input{ASCIIString}] tomato,\"color\",\"tomato\",\"tomato\",OptionDict({\"tomato\",\"cyan\"},{\"cyan\"=>\"cyan\",\"tomato\"=>\"tomato\"}),None[],None[])"
452 | ]
453 | },
454 | "metadata": {},
455 | "output_type": "display_data"
456 | },
457 | {
458 | "data": {
459 | "image/svg+xml": [
460 | "\n",
461 | "\n"
474 | ],
475 | "text/html": [
476 | ""
477 | ],
478 | "text/plain": [
479 | "Context(BoundingBox(Measure{Float64,MeasureNil}(0.0,0.5,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),1.0,0.0,0.0),Measure{Float64,MeasureNil}(0.0,0.1,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.1,0.0,0.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Form{CirclePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M<:Measure{S,T}}}(CirclePrimitive[CirclePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0))]),ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(1.0,0.38823529411764707,0.2784313725490196,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)"
480 | ]
481 | },
482 | "execution_count": 5,
483 | "metadata": {},
484 | "output_type": "execute_result"
485 | }
486 | ],
487 | "source": [
488 | "using Compose\n",
489 | "\n",
490 | "@manipulate for \n",
491 | " paused=false,\n",
492 | " dt = fpswhen(lift(!, paused), 30), # stop updating time when paused.\n",
493 | " t = foldl(+, 0., dt), # add up the time deltas to get time\n",
494 | " gravity = 0:0.01:5, # some sort of gravity\n",
495 | " color = [\"tomato\", \"cyan\"] # color the ball\n",
496 | "\n",
497 | " compose(context(0.5, 1-abs(sin(t*gravity)), 0.1, 0.1), fill(parse(Colorant, color)), circle())\n",
498 | "end"
499 | ]
500 | },
501 | {
502 | "cell_type": "markdown",
503 | "metadata": {},
504 | "source": [
505 | "Here is a captivating animation made with tiles of varying colors."
506 | ]
507 | },
508 | {
509 | "cell_type": "code",
510 | "execution_count": 8,
511 | "metadata": {
512 | "collapsed": false
513 | },
514 | "outputs": [
515 | {
516 | "data": {
517 | "text/html": [],
518 | "text/plain": [
519 | "Checkbox([Input{Bool}] true,\"unpaused\",true)"
520 | ]
521 | },
522 | "metadata": {},
523 | "output_type": "display_data"
524 | },
525 | {
526 | "data": {
527 | "text/plain": [
528 | "(1.442375779021515e9,0.0)"
529 | ]
530 | },
531 | "metadata": {
532 | "comm_id": "46a33ba2-d0cc-47c0-9eba-b120967a9400",
533 | "reactive": true
534 | },
535 | "output_type": "display_data"
536 | },
537 | {
538 | "data": {
539 | "image/svg+xml": [
540 | "\n",
541 | "\n"
743 | ],
744 | "text/html": [
745 | ""
746 | ],
747 | "text/plain": [
748 | "Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Table(8x8 Array{Array{Context,1},2}:\n",
749 | " [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7441006401928452,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] … [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7338497560569025,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] \n",
750 | " [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.742733848056367,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7201508894015723,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] \n",
751 | " [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7413347636931693,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7043171926715898,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] \n",
752 | " [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7399032298803999,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.25803020448804437,0.6863936573455055,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)]\n",
753 | " [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7384390584625828,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.4257275915917019,0.6665100998513593,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] \n",
754 | " [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7369421917456164,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] … [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.5469348231040652,0.6448995468330617,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] \n",
755 | " [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7354124629959939,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.6472146429704387,0.6219166916076609,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] \n",
756 | " [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,0.7338497560569025,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] [Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.7334266726955249,0.5980562443362892,1.0,1.0))]),ListNode{ComposeNode}(Form{RectanglePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M1<:Measure{S,T},M2<:Measure{S,T}}}(RectanglePrimitive[RectanglePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0)] ,1:8,1:8,[0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125],[0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125],nothing,{},UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),0,false,false),ListNull{ComposeNode}()),0,false,false,false,false,nothing,nothing,0.0)"
757 | ]
758 | },
759 | "execution_count": 8,
760 | "metadata": {},
761 | "output_type": "execute_result"
762 | }
763 | ],
764 | "source": [
765 | "using Colors\n",
766 | "using Compose\n",
767 | "\n",
768 | "@manipulate for unpaused = true, x=timestamp(fpswhen(unpaused, 30.))\n",
769 | " gridstack([compose(context(), rectangle(), fill(ColorTypes.LCHab(70.0, 60.0, 100*x[1]+i*j)))\n",
770 | " for i in 1:8, j in 1:8])\n",
771 | "end"
772 | ]
773 | },
774 | {
775 | "cell_type": "markdown",
776 | "metadata": {},
777 | "source": [
778 | "And finally, particles in a box."
779 | ]
780 | },
781 | {
782 | "cell_type": "code",
783 | "execution_count": 9,
784 | "metadata": {
785 | "collapsed": false
786 | },
787 | "outputs": [
788 | {
789 | "data": {
790 | "text/plain": [
791 | "(1.442375813705306e9,0.0)"
792 | ]
793 | },
794 | "metadata": {
795 | "comm_id": "1380a4ee-b400-406c-8b57-48a854163e88",
796 | "reactive": true
797 | },
798 | "output_type": "display_data"
799 | },
800 | {
801 | "data": {
802 | "text/html": [],
803 | "text/plain": [
804 | "Button{Nothing}([Input{Nothing}] nothing,\"Add particle\",nothing)"
805 | ]
806 | },
807 | "metadata": {},
808 | "output_type": "display_data"
809 | },
810 | {
811 | "data": {
812 | "text/plain": [
813 | "1-element Array{Any,1}:\n",
814 | " [0.942597,0.413408]"
815 | ]
816 | },
817 | "metadata": {
818 | "comm_id": "75708cde-117a-4b48-bda6-5ad6beee1abe",
819 | "reactive": true
820 | },
821 | "output_type": "display_data"
822 | },
823 | {
824 | "data": {
825 | "image/svg+xml": [
826 | "\n",
827 | "\n"
840 | ],
841 | "text/html": [
842 | ""
843 | ],
844 | "text/plain": [
845 | "Context(BoundingBox(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),1.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,1.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Context(BoundingBox(Measure{Float64,MeasureNil}(0.0,0.569901704788208,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.47907304763793945,0.0,0.0),Measure{Float64,MeasureNil}(0.0,0.05,MeasureNil(),0.0,0.0),Measure{MeasureNil,Float64}(0.0,MeasureNil(),0.05,0.0,0.0)),UnitBox{Nothing,Nothing,Nothing,Nothing}(nothing,nothing,nothing,nothing,Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.0)),Rotation{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}}(0.0,Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5))),nothing,ListNode{ComposeNode}(Form{CirclePrimitive{P<:Point{XM<:Measure{S,T},YM<:Measure{S,T}},M<:Measure{S,T}}}(CirclePrimitive[CirclePrimitive{Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}},Measure{MeasureNil,MeasureNil}}(Point{Measure{MeasureNil,MeasureNil},Measure{MeasureNil,MeasureNil}}(Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.0,0.5)),Measure{MeasureNil,MeasureNil}(0.0,MeasureNil(),MeasureNil(),0.5,0.0))]),ListNode{ComposeNode}(Property{FillPrimitive}([FillPrimitive(RGBA{Float64}(0.0,1.0,1.0,1.0))]),ListNull{ComposeNode}())),0,false,false,false,false,nothing,nothing,0.0),ListNull{ComposeNode}()),0,false,false,false,false,nothing,nothing,0.0)"
846 | ]
847 | },
848 | "execution_count": 9,
849 | "metadata": {},
850 | "output_type": "execute_result"
851 | }
852 | ],
853 | "source": [
854 | "using Compose\n",
855 | "\n",
856 | "box(x) = let i = floor(x)\n",
857 | " i%2==0 ? x-i : 1+i-x\n",
858 | "end\n",
859 | "\n",
860 | "colors = [\"orange\", \"cyan\", \"gray\", \"tomato\"]\n",
861 | "\n",
862 | "dots(points) = [(context(p[1], p[2], .05, .05), fill(parse(Colorant, colors[i%4+1])), circle())\n",
863 | " for (i, p) in enumerate(points)]\n",
864 | "\n",
865 | "@manipulate for t=timestamp(fps(30.)), add=button(\"Add particle\"),\n",
866 | " velocities = foldl((x,y) -> push!(x, rand(2)), Any[rand(2)], add)\n",
867 | "\n",
868 | " compose(context(),\n",
869 | " dots([map(v -> box(v*t[1]), (vx, vy)) for (vx, vy) in velocities])...)\n",
870 | "end"
871 | ]
872 | },
873 | {
874 | "cell_type": "markdown",
875 | "metadata": {},
876 | "source": [
877 | "If you used Interact to come up with something you think people will be wow-ed by, do let us know by commenting on [this issue](https://github.com/JuliaLang/Interact.jl/issues/36). :)"
878 | ]
879 | }
880 | ],
881 | "metadata": {
882 | "kernelspec": {
883 | "display_name": "Julia 0.3.11",
884 | "language": "julia",
885 | "name": "julia-0.3"
886 | },
887 | "language_info": {
888 | "file_extension": ".jl",
889 | "mimetype": "application/julia",
890 | "name": "julia",
891 | "version": "0.3.11"
892 | }
893 | },
894 | "nbformat": 4,
895 | "nbformat_minor": 0
896 | }
897 |
--------------------------------------------------------------------------------