5
$\begingroup$

Suppose Show contains multiple graphics objects that differ markedly in the amount of computation needed for their display. Hence, when dynamically updating only those individual graphics changed by any underlying variable change should be redrawn. For example (and with apologies to Dutch patriots), in the following dynamic Show, "pulling the red down" (by dragging the l.h.s locator) should take longer than "pulling the blue up" (by dragging the r.h.s. locator) since it is a more computationally intensive graphic (as simulated here by [email protected]). Dragging the r.h.s. locator however, is equally sluggish due to the overarching Dynamic wrapper causing both graphics to update whenever either locator is dragged. The logical solution would seem to be to wrap each inner Plot graphic in a Dynamic but Show doesn't like this apparently expecting a pure graphic for each of its shown components.

DynamicModule[{top = {.02, 0.15}, bottom = {.08, 0.08}},
 Dynamic@
  Show[
   Plot[([email protected]; top[[2]]), {x, 0, .1}, Filling -> Top,FillingStyle -> Red],
   Plot[bottom[[2]], {x, 0, .1}, Filling -> Axis,FillingStyle -> Blue],
   Graphics@Locator[Dynamic[top, (top[[2]] = #[[2]]) &]],
   Graphics@Locator[Dynamic[bottom, (bottom[[2]] = #[[2]]) &]],
  PlotRange -> {0, .2}, Axes -> False]]

enter image description here

Overlay, on the other hand, has no such qualms as indicated by the following graphic being much more responsive to the r.h.s. VerticalSlider "pulling the blue up".

DynamicModule[{top = {.02, 0.15},bottom = {.08, 0.08}},
 {VerticalSlider[Dynamic[top[[2]]], {0, 0.15}],
  Overlay[{
   Dynamic@Plot[([email protected]; top[[2]]), {x, 0, .1}, Filling -> Top,FillingStyle -> Red, Axes -> False,PlotRange -> {0, .2}],
   Dynamic@Plot[bottom[[2]], {x, 0, .1}, Filling -> Axis,FillingStyle -> Blue, Axes -> False, PlotRange -> {0, .2}]}], 
  VerticalSlider[Dynamic[bottom[[2]]], {0, 0.15}]}]

enter image description here

This is not a permanent solution as Overlay doesn't enjoy Show's more natural way of combining plots (more intuitive formatting, locator usage, ability to set common graphics options etc) so the question is; how can Overlay's selective updating be replicated in Show?

Answer Update:

The following answer is a hybrid taking from both Jens and Mr Wizard's responses which I think help to nicely illustrate some more general points about dynamic interface design. First, the combined answer:

SetAttributes[{DynamicShow, DynamicPlotRed, DynamicPlotBlue}, HoldAll];

DynamicShow[p_Plot] := Graphics[Dynamic@p[[1]], p[[2]]];

DynamicPlotRed[top_] := DynamicShow@Plot[([email protected]; top[[2]]), {x, 0, .1}, Filling -> Top, FillingStyle -> Red]

DynamicPlotBlue[bottom_] := DynamicShow@Plot[bottom[[2]], {x, 0, .1}, Filling -> Axis, FillingStyle -> Blue]

DynamicModule[{top = {.02, 0.15},bottom = {.08, 0.08}},
 Show[
  DynamicPlotRed@top,
  DynamicPlotBlue@bottom,
  Graphics@Locator[Dynamic[top, (top[[2]] = #[[2]]) &]],
  Graphics@Locator[Dynamic[bottom, (bottom[[2]] = #[[2]]) &]],
 PlotRange -> {0, .2}, Axes -> False]]

enter image description here

This includes the desired, lower level (indirect) use of Dynamic to maintain the usefulness of its automatic, targeting updating as captured in Mr Wizard's answer. This is important as it allows a more general, more numerous adding of interface components (note the relevant Dynamic sits in DynamicShow to which I'll return). The approach in Jens' answer - having each Locator directly update the corresponding plot, while working perfectly for the quoted two-element example, doesn't generalize in the sense that all plot updating now needs to be done "by hand" or via Dynamic's second argument without harnessing Dynamic's automatic updating (in full generality "Plot" can be an arbitrarily complex interface component with multiple, not-immediately-obvious dependencies).

There is a potential issue in that this solution relies on a consistent FullForm being generated by Plot but this can be addressed by adding a helper function. For example, using the PlotLegend option changes the FullForm structure but this can be handled as follows:

SetAttributes[{DynamicShow, DynamicShowLegend, DynamicPlotRed, DynamicPlotBlue}, HoldAll];

DynamicShow[p_Plot] := Graphics[Dynamic@p[[1]], p[[2]]];

DynamicShowLegend[p_] /; (! FreeQ[Hold@p, PlotLegends]) := Legended[Graphics[Dynamic@p[[1, 1]]], p[[2]]];

DynamicPlotRed[top_] := DynamicShow@Plot[([email protected]; top[[2]]), {x, 0, .1}, Filling -> Top, FillingStyle -> Red]

DynamicPlotBlue[bottom_] := DynamicShowLegend@Plot[{x, bottom[[2]]}, {x, 0, .1}, Filling -> {1 ->{Axis, Green}, 2 -> {Axis, Blue}}, PlotLegends -> True]


DynamicModule[{top = {.02, 0.15}, bottom = {.08, 0.08}},
 Show[
  DynamicPlotRed@top,
  DynamicPlotBlue@bottom,
  Graphics@Locator[Dynamic[top, (top[[2]] = #[[2]]) &]],
  Graphics@Locator[Dynamic[bottom, (bottom[[2]] = #[[2]]) &]],
  PlotRange -> {0, .2}, Axes -> False]]

enter image description here

The functions introduced in Jens' answer also illustrate a fundamental principle for building more complex dynamic interfaces. While introduced here to make Dynamic's second argument more readable, this improved readability assumes much greater proportions as interfaces' complexify (essentially implementing modularization with all its attended advantages, as, for example, enumerated in Tom Wickham-Jones white paper). There is a wrinkle in performing such modularization when it comes to dynamic interfaces however, which can be illustrated by seeing how Mr Wizard's solution modularizes (in fact generalizes).

Essentially his insight was that Show apparently (mostly) expects Graphics heads in its arguments (with exceptions noted as above) so that wrapping one of these in a Dynamic upsets this obstinance (which I regard as a design oversight). This can now however, at least be accommodated by giving Show its cherished Graphics head and instead wrapping Dynamic around Graphic's first argument via Graphics[Plot[...][[1]], AspectRatio->1/GoldenRatio]. The AspectRatio option corresponds to that generated automatically in this plot but in fact any generated option value can be included by defining

DynamicShow[p_Plot]:=Graphics[Dynamic@p[[1]],p[[2]]]

(n.b. The FullForm of a Plot output is Graphics[comps, opts] )

The wrinkle is that in order to apply DynamicShow in this way, its argument, p, needs to avoid evaluation prior to being wrapped in Dynamic to maintain its own "lexical scoping" (a form of code generation/injection). Consequently, this dynamic variable needs to be held right through to its final placement nestled inside a Dynamic thereby requiring a chain of HoldAll attributions.

In a fuller generality, the passed-through variable need not just be a local DynamicModule variable holding a pair of numbers like top and bottom but instead can potentially hold an arbitrary large dataset. This suggests an updated, less-emulative, idiom for answering an earlier question about realizing such modularization in dynamic interfaces; that is, there appears to now be a sufficient level of mutability in the new associations/datasets for expressions like top[[2]] to be fruitfully extended to expressions like interfaceData[[ ...]].

$\endgroup$
9
  • $\begingroup$ Your code generates errors because of how you use Dynamic inside Locator, just so you know. ("Cannot assign to raw object 0.08`" etc.) $\endgroup$
    – C. E.
    Commented Aug 25, 2014 at 2:44
  • $\begingroup$ @Pickett - no errors are generated on my set-up "10.0 for Mac OS X x86 (64-bit) (June 29, 2014)" - Dynamic can be used like this inside a Locator can it not? $\endgroup$ Commented Aug 25, 2014 at 3:23
  • $\begingroup$ There were indeed errors as @Pickett observed, due to the argument of the Locator containing a constant and not a variable. I posted a version that fixes this and also addresses the Show issue. $\endgroup$
    – Jens
    Commented Aug 25, 2014 at 6:05
  • $\begingroup$ As my preferred answer is a composite from both Mr Wizard's and Jens' responses, I'm unable to decide which one to accept. Jens' answer modularized the plotting, reminded about always having Dynamic's second argument up one's sleeve and answered the question as posed. Mr Wizard's answer, on the other hand, captured the preservation of Dynamic's automatic tracking though lower-level placement of dynamic the actual sought-after idiom whose hinting lay in the accompanying Overlay example. On the other, other hand, Jen's modularization provided the opportunity to put this in a broader ... $\endgroup$ Commented Aug 25, 2014 at 22:16
  • $\begingroup$ context of interface design. As usual when faced with such indecision, I inject randomness - here tinged with some "real-time, democracy"; if the number of question votes (currently 2) is odd/even at GMT midday 28th August Jens/MrWizard gets the nod. $\endgroup$ Commented Aug 25, 2014 at 22:16

2 Answers 2

4
$\begingroup$

One of the difficulties in your first approach is that you're trying to use Plot to dynamically generate rectangles. It would be easier to use graphics primitives for that. However, I'll assume that you have a reason to use Plot, so let's simulate the two plotting functions more clearly (one of them with a Pause to indicate its delay). I define two plotting functions (plotRed and plotBlue) here, depending on the parameter param which is controlled by the locators.

In the Locator, I use the variables p1 and p2 as the 2D positions, and then use the second argument to Locator to invoke a function that processes these positions. First, we want to constrain p1 and p2 to move only vertically. This is done by setting the horizontal coordinate to the fixed value determined by top and bottom.

The whole Show expression is now wrapped in Dynamic, but the updating of the two plots, called plot1 and plot2, is done directly in the second Locator argument, as part of the function that is executed whenever the locator changes.

plotRed[param_] := 
 Plot[Pause[.01]; param, {x, 0, .1}, Filling -> Top, 
  FillingStyle -> Red, PlotRange -> {{0, .2}, {0, .2}}]

plotBlue[param_] := 
 Plot[param, {x, 0, .1}, Filling -> Axis, FillingStyle -> Blue, 
  PlotRange -> {{0, .2}, {0, .2}}]

DynamicModule[
 {p1, p2,
  plot1, plot2, top = {.02, 0.15}, bottom = {.08, 0.08}},
 p1 = top;
 p2 = bottom;
 plot1 = plotRed[p1[[2]]];
 plot2 = plotBlue[p2[[2]]];
 Deploy@Dynamic@Show[
    plot1, plot2,
    Graphics@Locator[
      Dynamic[
       p1, (p1 = {top[[1]], #[[2]]}; 
         plot1 = (plotRed[#[[2]]])) &]],
    Graphics@Locator[
      Dynamic[
       p2, (p2 = {bottom[[1]], #[[2]]}; 
         plot2 = plotBlue[#[[2]]]) &]],
    Axes -> False,
    PlotRange -> {{0, .2}, {0, .2}}]]

As an additional modification, I added a fixed PlotRange to all plots (including the vertical extent).

The resulting display now shows the longer delay only when moving the locator for the slow plotting function, as you were asking.

$\endgroup$
2
$\begingroup$

I apologize for the quality of this answer: unlike Jens I did not bother to correct errors.

I would like to illustrate that even when the first argument of Graphics is invalid the layout engine can still render the size and aspect ratio the expression of these are given:

Graphics["foo", ImageSize -> {50, 200}]
Graphics["bar", ImageSize -> {200, 50}]

enter image description here

Because of this you can place Dynamic inside of Graphics and Show will still combine your expressions:

DynamicModule[{top = {.02, 0.15}, bottom = {.08, 0.08}},
 Show[
  Graphics[Dynamic@
    Plot[([email protected]; top[[2]]), {x, 0, .1}, Filling -> Top, FillingStyle -> Red][[1]], 
   PlotRange -> {{0, .1}, {0, .2}}, AspectRatio -> 1/GoldenRatio],

  Graphics[Dynamic@
    Plot[bottom[[2]], {x, 0, .1}, Filling -> Axis, FillingStyle -> Blue][[1]], 
   PlotRange -> {{0, .1}, {0, .2}}, AspectRatio -> 1/GoldenRatio],

  Graphics@Locator[Dynamic[{.02, top[[2]]}]],
  Graphics@Locator[Dynamic[{.08, bottom[[2]]}]],

  PlotRange -> {0, .2},
  Axes -> False
 ]
]

enter image description here

Notes:

  • I use Part to extract the graphics primitives from Plot output
  • I needed to set the PlotRange and AspectRatio in Graphics for them to be displayed correctly
$\endgroup$
4
  • $\begingroup$ This won't work if you add PlotLegends to one or both of the plots (e.g.). $\endgroup$
    – Jens
    Commented Aug 25, 2014 at 16:49
  • $\begingroup$ @Jens see the combined answer above to address this $\endgroup$ Commented Aug 25, 2014 at 22:06
  • $\begingroup$ @RonaldMonson In your updated post, the statement that Show expects Graphics is not correct. Besides Graphics3D, it also accepts Legended@Graphics and Legended@Graphics3D. My point is the Plot can produce the Legended output, too, and I automatically account for that. $\endgroup$
    – Jens
    Commented Aug 25, 2014 at 23:27
  • $\begingroup$ @Jens I'd argue that Show "expects" Graphics but not exclusively so :) but yes, you are right, I'd overlooked Legended etc and the answer is now adjusted. Thanks. $\endgroup$ Commented Aug 27, 2014 at 7:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.