Skip to content

Instantly share code, notes, and snippets.

@kubaPod
Last active June 8, 2018 13:11
Show Gist options
  • Save kubaPod/e88660fedeea19023a0df0ed06d7b7aa to your computer and use it in GitHub Desktop.
Save kubaPod/e88660fedeea19023a0df0ed06d7b7aa to your computer and use it in GitHub Desktop.
Controlling dynamic view in TabView/PaneSelector/MenuView and similar cell expressions

There are several problems/bugs with controlling updating/refreshing/initialization of specific panes/tabs in mentioned constructs:

  1. Too sensitive refreshing:

    'Visibility' of Dynamics in TabView with respect to SynchronousUpdating

  2. Too insensitive refreshing...:

    What is the point of PaneSelector?

  3. Flawed caching design:

    Why is PaneSelector caching nested Dynamics and how to switch it off?

Let's try to create an enhanced version of TabView which can meet following goals:

  • lazy: never refresh unless pane/tab is active

  • caching: don't re-evaluate if tracked parameters didn't change

  • caching: overcome FE caching which is based on input form of evaluated content.

  • refreshing: sensitive to Queued/Asynchronous changes of the symbol which controls visible pane.

  • refreshing: should handle long calculations gracefully

(* under construction... *)

(* we can't do real FE caching because even if we cache evalaution but FE needs to toggle view
  the initialization will be run.
  The implication is that it is better to design your view generating functions without initialization
  unless it is really needed
*)
(*TODO: here's is an idea for front-end-caching, assuming fixed cache length, pregenerate PaneSelector 
  and *)
ClearAll[HDViewWrapper];
HDViewWrapper // Attributes={HoldAll};
HDViewWrapper // Options = {
  "KernelCacheLength" -> 1 
  (*It is called kernel to stress that this won't cache FE view state, it only prevents unnecessary evaluation of view, 
    if otoh that view contains Initalization, it will be run if $view had to change in a meanwhile. 
   *)
, "EventMonitor" -> None
};
HDViewWrapper[
  state_Dynamic, viewPos_Integer, spec_Rule, HDOptions : OptionsPattern[]
]:=HDViewWrapper[
  state, viewPos, {viewPos, spec}, HDOptions
];

HDViewWrapper[
  Dynamic[state_]
, viewPos_
, {viewId_, viewLabel_ -> Dynamic[view_, TrackedSymbols :> {tracked__}]}
, HDOptions : OptionsPattern[]
]:= With[
  { log = OptionValue["EventMonitor"]
  , cacheLength = OptionValue["KernelCacheLength"]
  }
, Module[{theView}
  , theView = DynamicModule[
  				{$view = "", $cachedView,updateView, $updating
				  
				  }
				, DynamicWrapper[ (* listener *)
					  		PaneSelector[ 
						      { True -> ProgressIndicator[ Appearance->"Necklace" ]
						      , False -> Dynamic[$view]
						      }
						    , Dynamic[$updating+0] (* XD *)
						    , Alignment->{Center,Center}
						    ]
					  , If[
					      state === viewId
					    , log[viewId, " listener: I will update"]; updateView[]
					    , log[viewId, " listener: I will not update: ", {state, viewId}]
					    ]
					  , TrackedSymbols :> {tracked, state}
					  , SynchronousUpdating -> False
					]
				, UnsavedVariables:>{$view,$cachedView, updateView, $updating}
				, Initialization:>(
				    $updating = True
				   
					  ; $cachedView[n_]:=(
				      log[viewId, " recalculation, new cache"]
				    ; DownValues[$cachedView] = Reverse @ Take[Reverse @ DownValues[$cachedView], UpTo[cacheLength]]
				      (*we will cache only the last one but we need to keep a downvalue responsible for caching*)
				      (*notice that we get here only when conditions changed and cached value wasn't returned. *)
				    ; $cachedView[n] = view
				    
				    )
				  ; updateView[]:=(
				      
				      $updating=True 
				    
				    ; log[viewId, " updating, checking cache"]  
				    ; $view = $cachedView[tracked]
				    ; $updating=False
				    )
				  )
    ]
  ; {viewId, viewLabel -> theView}
  ]
]
HDViewWrapper[ Dynamic[state_], viewPos_, viewSpec_, OptionsPattern[] ]:=viewSpec;
HDViewWrapper[args___]:=Failure["InvalidArgument", "Message" ->InputForm@Hold[args]];
ClearAll[HDView];

HDView // Options = {
  ImageSize -> Automatic
, "LayoutFunction" -> TabView
, "EventMonitor" -> None
};

HDView[  views:{_Rule..}|{{_, _Rule}..}, Dynamic[state_Symbol], patt: OptionsPattern[{HDView, TabView}] ]:= With[
  { wrapperOptions = FilterRules[{patt}, Options @ HDViewWrapper]
  , tabViewOptions = Sequence @@ FilterRules[Join[{patt}, Options[HDView]], Options[OptionValue["LayoutFunction"]]]
  , layoutFunction = OptionValue["LayoutFunction"]
  }
, layoutFunction[
    Table[ With[{pos = pos, viewSpec = views[[pos]]}
      , HDViewWrapper[ Dynamic[state], pos, viewSpec, wrapperOptions ] 
      ]
    , {pos, Length[views]}
    ]  
  , Dynamic[state + 0, Function[new, state = new]]
  , tabViewOptions
  ]
]

HDView[args___]:=Failure["InvalidArgument", "Message" ->InputForm@Hold[args]];
(*this functions comes from a third party package, the solution should not assume anything about it and should not expect anything *)
data=EntityValue[CountryData[],{"Name","Population"}];
view[n_]:=DynamicModule[
  {}
, WordCloud[data[[;;-n]]]
, Initialization :> ("just to check something"; Print["initialization for input: ", n])
]
DynamicModule[{tab=1,n=1,nest=1},
  Column[{
    Grid[{{
      "n: ", PopupMenu[Dynamic[n],Range[5]]
    , "tab: ", SetterBar[Dynamic[tab],Range[10]]
    , "nest: ", PopupMenu[Dynamic[nest],Range[5]]
    }}, Alignment->{Center,Center}
    ]
    ,
    HDView[
       Join[
        Table[With[{i=i}, 
          {i, i -> Dynamic[view[n+i], TrackedSymbols :> {n}] }
          (* view[n+i] and {n} can be whatever, `Dynamic` does not matter and do not pass there more options as they will be ignored anyway *)
          (* consider this Dynamic[stuff, TrackedSymbols :> {__}] as a symbolic marker for HDView *)
        ], {i, 10} ]
      , {{"static1", "static1" -> "whatever"}  }
      , {{"anotherDynamic", "anotherDynamic" -> Dynamic[Pause[1]; Nest[Framed,1,nest], TrackedSymbols:>{nest}]}} (*just an independent tab *)
      ]      
    , Dynamic[tab]    
    , "LayoutFunction" -> TabView (*can be PaneSelector or whatever to handle layoutFunction[{lbl->pane,...}, Dynamic[tag], automatically-filtered-options]*)
    , "EventMonitor" -> Print
    , ControlPlacement -> {Left, Top}
    , ImageSize -> { Full, Full }
    , Alignment -> {Center, Center}
    
    ]//Panel[#, "Test", ImageSize-> All{1,1}]&
  }]
]
TabView[{1->1,"a"->"a"},Dynamic[tag]]
tag="a"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment