TL;DR: The purpose of the code below is to show only one part of the evaluation sequence (only one subtree) at a time. As a result, the potentially very deep hierarchy in an evaluation does not result in a complex structure of nested parts. Instead, the hierarchy is summarized into a separate row of past evaluations (past nodes in the evaluation tree) which are known as breadcrumbs in tree navigation.
Note: animations shown below in the section: Explanation
As shown by @IanFord, finding the clearest way to visualize the evaluation sequence is equivalent to the clearest way of visualizing a tree with expressions as nodes.
There are many ways to visualize a tree as shown here: https://ux.stackexchange.com/q/18991/165903 and https://ux.stackexchange.com/q/2317.
Below I follow the drill-down and breadcrumb design that has the following benefit:
It focuses on a particular subtree at a time thereby avoiding scrolling and
filling the page with a very deep and complicated hierarchical
structure.
Here is an example of the drill-down and breadcrumb design:
Description of image below: The rectangle at the bottom shows a subtree in an evaluation (will be explained more later). The row of cells at the top shows clickable button breadcrumbs that represent the path of tree nodes from the evaluation tree's root to the current subtree and allows navigating back to a precedent level in the tree. The key point is that only one subtree is shown rather than the entire hierarchy. The hierarchy is summarized into breadcrumbs. Further details are given later.
The equivalent path using @WReach's traceView2:
My main motivation was to avoid forcing the front end to display very complex structures when the expressions are very long, as this seems to cause the front end to get stuck.
Code
A broad explanation is given in the next section
Note: ⎵=\[UnderBracket]
Note: the code below defines global variables which may conflict with user-defined variables:
original⎵tree
bread⎵crumbs
displayed⎵tree
Note: The functions that output a tree have the same options as Tree
.
Creating a tree from the evaluation sequence
This is a modification of WReach's TraceView2
function to create a tree.
ClearAll[Trace⎵Tree];
Options[Trace⎵Tree]=Options[Tree];
Trace⎵Tree[expr_,opts:OptionsPattern[]]:=
Module[{steps={},stack={},pre,post,show,dynamic},
pre[e_]:=(stack={steps,stack};steps={});
post[e_,r_]:=
(steps=First@stack~Join~{show[e,HoldForm[r],steps]}
;
stack=stack[[2]]);
SetAttributes[post,HoldAllComplete];
show[e_,r_,steps_]:=
steps/.
{{}->(Grid[{{"Expr",Row[{e," ",Style["inert",{Italic,Small}]}]}},Frame->All]->{})
,
_->(
Grid[{{"Expr",e},{"Result",r}},Frame->All]->
(
steps/.{{}->Style["no definitions apply"
,Italic]
,
_:>steps
}
)
)
}
;
TraceScan[pre,expr,___,post];
RulesTree[steps[[1]],opts,Sequence@@Options[Trace⎵Tree]]
];
SetOptions[Trace⎵Tree
,
ImageSize->Full
,
MaxDisplayedChildren->Infinity
,
TreeLayout->Right];
SetAttributes[Trace⎵Tree,{HoldAllComplete}];
Breadcrumb view
The code below takes as input the evaluation tree from Trace⎵Tree
and outputs the breadcrumb view from the image above.
Clear[add⎵buttons];
Options[Tree⎵Data⎵Map]=Options[Tree];
Tree⎵Data⎵Map[f_][tree_,opts:OptionsPattern[]]:=
Module[{aux},
aux=TreeMapAt[
f
,
tree
,
{0}
];
Tree[aux,opts,Sequence@@Options[Tree⎵Data⎵Map]]
];
SetOptions[Tree⎵Data⎵Map,
ImageSize->Full
,
MaxDisplayedChildren->Infinity
,
TreeLayout->Right];
Options[add⎵buttons]=Options[Tree];
add⎵buttons[tree_,toggle_:False,root⎵position_:{},opts:OptionsPattern[]]:=
Module[{root⎵tree,root⎵tree⎵with⎵button⎵leaves
,tree⎵data,bread⎵crumb,bread⎵crumb⎵position,global},
(*view root node plus children (toggle=False)
or root node plus children and their children(toggle=True)*)
root⎵tree=RootTree[tree,1+Boole[toggle]]
;
(* add buttons to children
where the button action changes
the display to the subtree of the child *)
root⎵tree⎵with⎵button⎵leaves=
TreeReplacePart[
root⎵tree
,
{i_}
:>
If[toggle
,
(* If the children of the children are shown,
then add buttons to the children at the first level *)
Module[{sub⎵tree},
sub⎵tree=TreeExtract[root⎵tree,i]
;
Tree⎵Data⎵Map[s|->Button[s
,
add⎵buttons[
sub⎵tree
,
toggle
,
Append[root⎵position,i]
,
opts
,
Sequence@@Options[add⎵buttons]
]
,
Appearance->"Frameless"
]
][sub⎵tree
,
opts
,
Sequence@@Options[add⎵buttons]
]
]
,
Button[TreeExtract[root⎵tree,{i}]//TreeData
,
add⎵buttons[
TreeExtract[tree,{i}]
,
toggle
,
Append[root⎵position,i]
,
opts
,
Sequence@@Options[add⎵buttons]
]
,
Appearance->"Frameless"
]
]
]
;
tree⎵data=TreeData[tree]
;
bread⎵crumb=
Button[
Column[
{Short[tree⎵data]
,
root⎵position}
,
Dividers->Center
]
,
If[
root⎵position==={}
,
bread⎵crumbs={bread⎵crumbs[[1]]}
;
displayed⎵tree=
add⎵buttons[
original⎵tree
,
toggle
,
root⎵position
,
opts
,
Sequence@@Options[add⎵buttons]
]
,
bread⎵crumb⎵position=
LengthWhile[bread⎵crumbs,Not[#[[1,1,2]]===root⎵position]&]+1
;
bread⎵crumbs=bread⎵crumbs[[1;;bread⎵crumb⎵position]]
;
displayed⎵tree=
add⎵buttons[
TreeExtract[original⎵tree
,
root⎵position
]
,
toggle
,
root⎵position
,
opts
,
Sequence@@Options[add⎵buttons]
]
]
,
Appearance->"Palette"
]
;
(* check if the breadcrumb was already added
at the prior step. If not add the breadcrumb*)
If[bread⎵crumbs==={}
∨
Not[bread⎵crumb[[1,1,2]]===bread⎵crumbs[[-1,1,1,2]]] (* compare tree positions*)
,
AppendTo[bread⎵crumbs,bread⎵crumb]
]
;
(* add button to root to view the children of the children
if clicked *)
displayed⎵tree=
Tree⎵Data⎵Map[
s|->Button[s
,
add⎵buttons[
tree
,
Not[toggle]
,
root⎵position
,
opts
,
Sequence@@Options[add⎵buttons]]
,
Appearance->"Frameless"
]
][root⎵tree⎵with⎵button⎵leaves
,
opts
,
Sequence@@Options[add⎵buttons]
]
];
SetOptions[add⎵buttons,
ImageSize->Full
,
MaxDisplayedChildren->Infinity
,
TreeLayout->Right];
ClearAll[Trace⎵Local⎵Tree];
Options[Trace⎵Local⎵Tree] = Options[Tree];
Trace⎵Local⎵Tree[expr_,
opts : OptionsPattern[]] :=
Module[{tree},
tree = Trace⎵Tree[expr, opts];
bread⎵crumbs = {};
original⎵tree = tree;
add⎵buttons[tree, False, {}, opts,
Sequence @@
Options[Trace⎵Local⎵Tree]]];
SetOptions[Trace⎵Local⎵Tree
,
ImageSize -> Full
,
MaxDisplayedChildren -> Infinity
,
TreeLayout -> Right];
SetAttributes[Trace⎵Local⎵Tree, HoldAll];
Usage:
Below we consider the evaluation sequence of (2+3)^2
Trace⎵Local⎵Tree[(2+3)^2];
Dynamic[Row@bread⎵crumbs]
Dynamic[Panel@TreeOutline[displayed⎵tree, {___}]]
Explanation
Consider the example of the Fibonnaci sequence:
fib[1] = fib[2] = 1; fib[n_] := fib[n - 1] + fib[n - 2];
Here is an example of the output of the code above for fib[3]
:
Trace⎵Local⎵Tree[fib[3]];
Dynamic[Row@bread⎵crumbs]
Dynamic[Panel@TreeOutline[displayed⎵tree, {___}]]
The gif above shows:
Clicking on a leaf of the the subtree changes the displayed tree to the subtree of that node. This is similar to the openerview mechanism but it's purpose is to avoid displaying all opened subtrees in the front end.
Clicking on a breadcrumb changes the displayed subtree to a subtree where the root is the same as the displayed expression in the button breadcrumb
The list displayed on each bread crumb button at the bottom represents its position in the tree, if the reader is unfamiliar with tree positions, the documentation of TreeExtract
might help.
The rectangle at the bottom shows the current subtree as explained before. The elements are ordered as in TraceView2 except that the "result" element from the evaluation of a subtree evaluation sequence was added to the first element (rather than the last element as in TraceView2). The purpose of adding the "result" element to the first part was to better visualize the underlying tree structure when using the the optional tree view of the next section.
If we click on the root of a subtree we can peek one level down in the tree:
Clicking again on the root returns to the previous view
Optional: TreeView
Alternatively, we may also view directly the underlying tree structure. The buttons work as before.
Trace⎵Local⎵Tree[fib[3],
ImageSize -> Medium];
Dynamic[Row@bread⎵crumbs]
Dynamic[displayed⎵tree]
The full tree can be seen with Trace⎵Tree
, for example Trace⎵Tree[fib[3]]
for the evaluation sequence of fib[3]
. One advantage of the tree view is that it allows more customization and might be easier to read when peaking one level down by clicking on the root of the subtree as explained before.