Interface Programming

Principles of Dynamic

Faisal Whelpley
faisal@wolfram.com

User Interface Group

Wolfram Research, Inc.

Principles of Symbolic Interfaces

These are principles which aren’t inviolable for simply using Mathematica, but following them will generally make your life simpler. Every one of these principles is vital for an interface which you intend to deploy in the wild.  They will improve your interface, fix bugs, improve performance, make your interfaces act as good citizens in the world of interfaces and quite possibly bring about world peace.

The Principle of Programmatic Generation

Plan to throw one away, you will anyhow. — Fred Brooks

If you plan to throw one away, you will throw away two. — Craig Zerouni

The Principle of Programmatic Generation

Generating interfaces with programs is easy

Almost half of the BasicMathInput palette (in one line!):

greek=Grid[Partition[Map[PasteButton, ToExpression[CharacterRange["α","ω"]]],{5}],Spacings->{0,0}]

Slider:

Slider[]

Buttons:

Button["Expand", NotebookWrite[ButtonNotebook[],ToBoxes[Expand[ToExpression[NotebookRead[ButtonNotebook[]]]]]]]

(((x+y))^5)

etc...

{Slider2D[], InputField[], RadioButton[],Checkbox[], PopupMenu[1, Range[3]]}

The Principle of Programmatic Generation

Programs can easily create palettes

Palettes can be created instantly and installed by using File ▶ Install:

pal=CreatePalette[greek];

Or you can even install them programmatically.  Note the undocumented method of resetting the menus:

NotebookSave[CreatePalette[greek],
ToFileName[{$UserBaseDirectory, "SystemFiles", "FrontEnd", "Palettes"}, "Greek2.nb"]];
FrontEndExecute[ResetMenusPacket[{Automatic}]]

The Principle of Programmatic Generation

Programs can easily create toolbars

A simple find interface:

findInterface=DynamicModule[{findText=""},
Row[{InputField[Dynamic[findText], String],
Button["Find Next", NotebookFind[ButtonNotebook[],findText]],
Button["Find Previous", NotebookFind[ButtonNotebook[],findText,Previous]]
}]]

Docked cells aren't yet working with ExpressionCell[] directly, but using ToBoxes is easy

nb=CreateDocument[{},DockedCells->{Cell[BoxData[ToBoxes[findInterface]], "DockedCell"]}];

The Principle of Programmatic Generation

Tweaking the program which generates an interface is easier than tweaking the interface

Options[nb, DockedCells]

Copied interface code

A simple find interface:

findInterface=DynamicModule[{findText=""},
Row[{EventHandler[InputField[Dynamic[findText], String],
{"ReturnKeyDown":>NotebookFind[ButtonNotebook[],findText]},
EvaluationOrder->After,PassEventsDown->True],
Button["Find Next", NotebookFind[ButtonNotebook[],findText]],
Button["Find Previous", NotebookFind[ButtonNotebook[],findText,Previous]]
}]]

SetOptions[nb, DockedCells->{Cell[BoxData[ToBoxes[findInterface]], "DockedCell"]}]

The Principle of Programmatic Generation

We use generators for nearly all of our user interfaces, including...

The help viewer toolbar

The package editor toolbar

The preferences dialog

The history dialog

The about boxes

Various palettes and dialogs

The find dialog

The citation dialogs

The Principle of Minimal Dynamics

Make everything as simple as possible, but not simpler. — Albert Einstein

Simplicity is the ultimate sophistication. — Leonardo da Vinci

The Principle of Minimal Dynamics

A refresher on how Dynamics work

When a Dynamic is evaluated for display, a full dependency graph is computed in the kernel, and any subsequent change to any variable in that dependency graph triggers another update.

refresh`Count = 0;
pal=CreatePalette[Style[Dynamic[refresh`Count],FontSize->50],Saveable->False];

Here's a technique for tracking Dynamic refreshes by using Refresh[_,None]

Clear[f,g,a,b,c,d];
Dynamic[Refresh[refresh`Count++, None];If[True,f[a],g[b]];c+d]

Multiple variable changes in quick succession often coalesce into one update

c=5;d=2;

Things which don't directly appear in the output can trigger updates

a=1;

DownValues can trigger updates

f[x_]:=x^2

Variables which were not evaluated will not be tracked

b=3;

The Principle of Minimal Dynamics

Dynamics will be invalidated by variable updates, but no further computation happens until the front end needs to draw them.  
If the Dynamic is offscreen, then that means no further computation will happen until the Dynamic is scrolled back onscreen.

({{"Hide", "Show", "Close"}})

c=10;

d=10;

refresh`Count

Once the Dynamic is required to be regenerated, that means the entire Dynamic will be re-executed. No matter which dependency triggered the Dynamic, the entire contents re-evaluate.

refresh`Count=0;
Clear[a,b];
Dynamic[Pause[2];Refresh[refresh`Count++,None];a]

a=5;

Even a temporary change is enough to trigger this.

Block[{a=3},0];

The Principle of Minimal Dynamics

Manipulate wraps Dynamic around its first argument, so these tricks apply equally well to Manipulate

Some of the following examples use Manipulate, while some use Dynamic.  
All of these demonstrations, however, apply equally well on both fronts.

The Principle of Minimal Dynamics — Applications

A Dynamic interactive interface element should not commit suicide

A small plot explorer environment, but with a bug.
The Dynamic encompasses the LocatorPane in a way that causes it to be recreated as it is being used.

DynamicModule[{plotrange={{0,-1},{5,1}},point={0,0},startPoint,rubberband={}},
Dynamic[
LocatorPane[
Dynamic[point,
{(startPoint=#)&,
(rubberband=Rectangle[startPoint,#])&,
(rubberband={};plotrange=Sort[{startPoint,#}])&}],Plot[Sin[1/x],{x,plotrange[[1,1]],plotrange[[2,1]]},PlotRange->Transpose[plotrange],Epilog->{FaceForm[],EdgeForm[Thin],rubberband}],Appearance->None]]
]

The Principle of Minimal Dynamics — Applications

Here, the Dynamic is moved inside the expression as far as we can move it, which is the Plot command.  The LocatorPane is no longer suicidal, and well into a recovery program.

DynamicModule[{plotrange={{0,-1},{5,1}},point={0,0},startPoint,rubberband={}},
LocatorPane[
Dynamic[point,
{(startPoint=#)&,
(rubberband=Rectangle[startPoint,#])&,
(rubberband={};plotrange=Sort[{startPoint,#}])&}],Dynamic[Plot[Sin[1/x],{x,plotrange[[1,1]],plotrange[[2,1]]},PlotRange->Transpose[plotrange],Epilog->{FaceForm[],EdgeForm[Thin],rubberband}]],Appearance->None]]

The Principle of Minimal Dynamics

Dynamic graphical elements can be mixed in with a list of static graphical elements to minimize computation

Dynamics in primitive coordinates can minimize computation

Here's a Locator example I plucked from the documentation.  It's small and in no particular need of optimization, but it illustrates some points.

DynamicModule[{v1={2,0},v2={-1,1}},Dynamic@Graphics[{Circle[],Red,GeometricTransformation[Circle[],Transpose[{v1,v2}]],Green,Line[{{0,0},v1}],Line[{{0,0},v2}],Locator[Dynamic[v1]],Locator[Dynamic[v2]]},PlotRange->3]]

The version below uses Deploy to avoid unwanted modification of static elements, reduces the number of Dynamic elements.

Deploy[
DynamicModule[{v1={2,0},v2={-1,1}},Graphics[{Circle[],Red,Dynamic[GeometricTransformation[Circle[],Transpose[{v1,v2}]]],Green,Line[Dynamic[{v1,{0,0},v2}]],Locator[Dynamic[v1]],Locator[Dynamic[v2]]},PlotRange->3]]]

The Principle of Minimal Dynamics

Dynamics in option values can minimize computation

Any front end option can have a Dynamic right-hand side.  Here are some very useful ones.

(* This example needs a Dynamic around the Lighting option *)
Manipulate[Graphics3D[{Specularity[White,50],
Table[Sphere[{x,y,z}],{x,15},{y,15},{z,15}]},
Lighting->{{"Point",Orange,{xy[[1]],xy[[2]],2.5}}} 
], {xy, {-2, -2}, {18, 18}}]

DynamicModule[{setterValue = 1}, 
Panel[Labeled[Grid[
{{Button["aaa", Enabled->Dynamic[setterValue===1]],Button["ddd",Enabled->Dynamic[setterValue===2]]},
{Button["bbb", Enabled->Dynamic[setterValue===1]],Button["eee",Enabled->Dynamic[setterValue===2]]},
{Button["ccc", Enabled->Dynamic[setterValue===1]],Button["fff",Enabled->Dynamic[setterValue===2]]}}],
SetterBar[Dynamic[setterValue],{1->"some options", 2->"other options"}],Top]]]

The Principle of Minimal Dynamics

Dynamics don't belong in every option value, however

Manipulate[Plot[Sin[E^x], {x,0,6}, PerformanceGoal->"Quality", PlotPoints->Dynamic[plotpoints]],
{plotpoints, 10, 50, 1}]

Manipulate[Plot[Sin[E^x], {x,0,6}, PerformanceGoal->"Quality",PlotPoints->plotpoints],
{plotpoints, 10, 50, 1}]

The Principle of Minimal Dynamics

How Nested Dynamics work

When nested, an inner Dynamic can update independently of whether outer Dynamics have been triggered.  However, an outer Dynamic always force inner Dynamics to re-evaluate.

refresh`CountInner = refresh`CountOuter = 0;
Clear[a,b]
CreatePalette[Grid[{{"Inner refresh count:", Dynamic[refresh`CountInner]},
{"Outer refresh count:", Dynamic[refresh`CountOuter]}},BaseStyle->(FontSize->50)],Saveable->False];

Dynamic[Refresh[refresh`CountOuter++,None]; {a, Dynamic[Refresh[refresh`CountInner++,None];b]}]

b=10;

a=20;

The Principle of Minimal Dynamics

Nested Dynamics can save effort by reducing the impact of a dependency change

If, in an example like the following, you expect b to change often when a does not, wrapping Dynamic around b will definitely bring a performance benefit.

Clear[a,b];
Dynamic[Pause[4];{a,Dynamic[b]}]

b=10;

b++;

a=2;

The Principle of Minimal Dynamics

Minimizing Dynamic computation using With

Dynamic holds its first argument.  As with any function which holds its argument, the way to insert a precomputed value is using With.  In this example, arguments computed every time the Checkbox is toggled can be moved outside the scope of Dynamic using With.

DynamicModule[{enabled = True},
Panel[
Grid[{{"Enabled", Checkbox[Dynamic[enabled]]},
{Button[
Dynamic[If[enabled,
Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, 2Pi},Axes->None,Boxed->False,ImageSize->Tiny,PlotPoints->50],
Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, 2Pi},Axes->None,Boxed->False,ImageSize->Tiny,PlotPoints->50, Lighting->"Neutral"]
]], Enabled->Dynamic[enabled]], SpanFromLeft}
}]]]

The Principle of Minimal Dynamics

Using With speeds things up significantly, although it's still slightly slow because of the dense resulting Graphics3D image and the time it takes to render. Rasterize takes care of that problem.  Note that everything has now been precomputed and slipped straight into the Dynamic now.

With[{enabledImage=Rasterize@Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, 2Pi},Axes->None,Boxed->False,ImageSize->Tiny,PlotPoints->50],
disabledImage=Rasterize@Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, 2Pi},Axes->None,Boxed->False,ImageSize->Tiny,PlotPoints->50, Lighting->"Neutral"]},
DynamicModule[{enabled = True},
Panel[
Grid[{{"Enabled", Checkbox[Dynamic[enabled]]},
{Button[
Dynamic[If[enabled,enabledImage,disabledImage]],
Enabled->Dynamic[enabled]], SpanFromLeft}
}]]]]

The Principle of Maximum Encapsulation

What are you hiding?  No one ever asks that. — Sarah Vowell

The open secrets of good design practice include the importance of knowing what to keep whole, what to combine, what to separate, and what to throw away. — Kevlin Henny

The Principle of Maximum Encapsulation

Global variables are promiscuous

By not localizing your variables with DynamicModule, you risk cross-contamination with other evaluations

{Slider[Dynamic[x]], Dynamic[x]}

Other functions with unscoped variables may break

Limit[x, x->5]

Even scoping constructs may not help, if those constructs still affect the global environment

Block[{x=2}, Pause[x]];

An interesting case ... Plot actually uses Block - style scoping

The Principle of Maximum Encapsulation

{Slider[Dynamic[x]], Dynamic[x]}

Here, you can actually see the result in slow-motion

Plot[Sin[x], {x, 0, 1}, EvaluationMonitor:>Pause[.1]]

It may seem counter-intuitive at first, but this is actually quite useful behavior

Monitor[Plot[Sin[x], {x, 0, 1}, EvaluationMonitor:>Pause[.1]],x]

But back to the original point, none of this would have been an issue if you used DynamicModule.

The Principle of Maximum Encapsulation

Initialization code should not require Shift+Enter to set up

Needs["Splines`"]

DynamicModule[{pts={{-1,-1},{1,1}}},
LocatorPane[Dynamic[pts],
Graphics[{
Dynamic[Spline[Sort[pts],Bezier]]},
PlotRange->{{-1,1},{-1,1}},
Axes->True],
LocatorAutoCreate->All]]

Quit

The Principle of Maximum Encapsulation

The Initialization option of DynamicModule is guaranteed to evaluate before its contents

But we run into a bit of a pickle here...the first evaluation produces a bad result because Needs["Splines`"] must evaluate in a separate evaluation before parsing the body of the expression.  DynamicModule will take care of this for us, but Shift+Enter does not!

Quit

DynamicModule[{pts={{-1,-1},{1,1}}},
LocatorPane[Dynamic[pts],
Graphics[{
Dynamic[Spline[Sort[pts],Bezier]]},
PlotRange->{{-1,1},{-1,1}},
Axes->True],
LocatorAutoCreate->All],
Initialization:>Needs["Splines`"]]

The Principle of Maximum Encapsulation

Can be solved by two Needs[] statements...one to fulfill Shift+Enter evaluation and one to fulfill Dynamic evaluation

Note that this is only an issue because of $ContextPath.  Any other initialization code could have safely appeared one time inside of Initialization.

Quit

Needs["Splines`"];
DynamicModule[{pts={{-1,-1},{1,1}}},
LocatorPane[Dynamic[pts],
Graphics[{
Dynamic[Spline[Sort[pts],Bezier]]},
PlotRange->{{-1,1},{-1,1}},
Axes->True],
LocatorAutoCreate->All],
Initialization:>Needs["Splines`"]]

The Principle of Maximum Encapsulation

Encapsulation in Manipulate

The same Initialization principle holds for Manipulate as for DynamicModule.  Here is basically the same example using Manipulate.

Quit

Needs["Splines`"];
Manipulate[
Graphics[{
Spline[Sort[pts],Bezier]},
PlotRange->{{-1,1},{-1,1}},
Axes->True],
{{pts, {{-1, -1}, {1, 1}}}, Locator,LocatorAutoCreate->All},
Initialization:>Needs["Splines`"]]

Quit

The Principle of Maximum Encapsulation

Encapsulation in Manipulate

SaveDefinitions can also be used for function definitions, although it doesn't always reach into packages correctly.  Nonetheless, it often works well, and is used in Demonstrations by default.

Quit

Needs["Splines`"];
Manipulate[
Graphics[{
Spline[Sort[pts],Bezier]},
PlotRange->{{-1,1},{-1,1}},
Axes->True],
{{pts, {{-1, -1}, {1, 1}}}, Locator,LocatorAutoCreate->All},
SaveDefinitions->True]

Quit

The Principle of Maximum Encapsulation

If the code or data can't be stored in the notebook, try storing them with the notebook

Use NotebookDirectory to track the notebook's location, and bundle your other files in the same directory.

FileNames["*",NotebookDirectory[]]

Button["Load Package",
Block[{$Path=Prepend[$Path, NotebookDirectory[ButtonNotebook[]]]}, Get["print.m"]]]

The Principle of Evaluation Blocking

All computers wait at the same speed. — Anonymous

The Principle of Evaluation Blocking — Defining Terms

Preemptive evaluations

Preemptive evaluations demand that the kernel stop whatever it's doing and immediately respond.

Dynamics

Buttons with default options

Event handlers

Queued evaluations

Queued evaluations sit in a queue in the front end and are served to the kernel in a first-in-first-out manner

Shift+Enter calculations

Buttons with Method->"Queued"

Manipulates with SynchronousUpdating->False

The Principle of Evaluation Blocking

Blocked evaluations

Any evaluation which must pause while getting input from the user is a blocking evaluation. Evaluations might also be considered to block if they simply take a very long time.

Input[]

SystemDialogInput["FileOpen"]

Since preemptive calculations have a timeout, it's a bad idea to block a preemptive evaluation.  The following type of bug can be a difficult one to find:

Button["Print Filename", Print[SystemDialogInput["FileOpen"]]]

The Principle of Evaluation Blocking

Buttons with Method->"Queued"

Buttons can use queued evaluation if they're expected to block.  However, quite significantly, that means the operation of a button may be delayed.

Button["Print Filename", Print[SystemDialogInput["FileOpen"]],Method->"Queued"]

Pause[10]

The Principles of Symbolic Interfaces Summarized