├── 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 | ![Screenshot](http://i.imgur.com/xLWjmNb.png) 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", 507 | " \n", 509 | "\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", 595 | " \n", 597 | "\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", 637 | " \n", 639 | "\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", 361 | "\n", 362 | " \n", 363 | "\n", 364 | "\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", 470 | "\n", 471 | " \n", 472 | "\n", 473 | "\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", 550 | "\n", 551 | " \n", 552 | "\n", 553 | "\n", 554 | " \n", 555 | "\n", 556 | "\n", 557 | " \n", 558 | "\n", 559 | "\n", 560 | " \n", 561 | "\n", 562 | "\n", 563 | " \n", 564 | "\n", 565 | "\n", 566 | " \n", 567 | "\n", 568 | "\n", 569 | " \n", 570 | "\n", 571 | "\n", 572 | " \n", 573 | "\n", 574 | "\n", 575 | " \n", 576 | "\n", 577 | "\n", 578 | " \n", 579 | "\n", 580 | "\n", 581 | " \n", 582 | "\n", 583 | "\n", 584 | " \n", 585 | "\n", 586 | "\n", 587 | " \n", 588 | "\n", 589 | "\n", 590 | " \n", 591 | "\n", 592 | "\n", 593 | " \n", 594 | "\n", 595 | "\n", 596 | " \n", 597 | "\n", 598 | "\n", 599 | " \n", 600 | "\n", 601 | "\n", 602 | " \n", 603 | "\n", 604 | "\n", 605 | " \n", 606 | "\n", 607 | "\n", 608 | " \n", 609 | "\n", 610 | "\n", 611 | " \n", 612 | "\n", 613 | "\n", 614 | " \n", 615 | "\n", 616 | "\n", 617 | " \n", 618 | "\n", 619 | "\n", 620 | " \n", 621 | "\n", 622 | "\n", 623 | " \n", 624 | "\n", 625 | "\n", 626 | " \n", 627 | "\n", 628 | "\n", 629 | " \n", 630 | "\n", 631 | "\n", 632 | " \n", 633 | "\n", 634 | "\n", 635 | " \n", 636 | "\n", 637 | "\n", 638 | " \n", 639 | "\n", 640 | "\n", 641 | " \n", 642 | "\n", 643 | "\n", 644 | " \n", 645 | "\n", 646 | "\n", 647 | " \n", 648 | "\n", 649 | "\n", 650 | " \n", 651 | "\n", 652 | "\n", 653 | " \n", 654 | "\n", 655 | "\n", 656 | " \n", 657 | "\n", 658 | "\n", 659 | " \n", 660 | "\n", 661 | "\n", 662 | " \n", 663 | "\n", 664 | "\n", 665 | " \n", 666 | "\n", 667 | "\n", 668 | " \n", 669 | "\n", 670 | "\n", 671 | " \n", 672 | "\n", 673 | "\n", 674 | " \n", 675 | "\n", 676 | "\n", 677 | " \n", 678 | "\n", 679 | "\n", 680 | " \n", 681 | "\n", 682 | "\n", 683 | " \n", 684 | "\n", 685 | "\n", 686 | " \n", 687 | "\n", 688 | "\n", 689 | " \n", 690 | "\n", 691 | "\n", 692 | " \n", 693 | "\n", 694 | "\n", 695 | " \n", 696 | "\n", 697 | "\n", 698 | " \n", 699 | "\n", 700 | "\n", 701 | " \n", 702 | "\n", 703 | "\n", 704 | " \n", 705 | "\n", 706 | "\n", 707 | " \n", 708 | "\n", 709 | "\n", 710 | " \n", 711 | "\n", 712 | "\n", 713 | " \n", 714 | "\n", 715 | "\n", 716 | " \n", 717 | "\n", 718 | "\n", 719 | " \n", 720 | "\n", 721 | "\n", 722 | " \n", 723 | "\n", 724 | "\n", 725 | " \n", 726 | "\n", 727 | "\n", 728 | " \n", 729 | "\n", 730 | "\n", 731 | " \n", 732 | "\n", 733 | "\n", 734 | " \n", 735 | "\n", 736 | "\n", 737 | " \n", 738 | "\n", 739 | "\n", 740 | " \n", 741 | "\n", 742 | "\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", 836 | "\n", 837 | " \n", 838 | "\n", 839 | "\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 | --------------------------------------------------------------------------------