5

My question is how do I get dynamic legends (legends that turn data on/off) to work with Show? This is an extension of an old question here;

How to dynamically toggle curves on/off in a crowded Plot?

The application I have is to show data using a histogram and overlay many different distributions on top of it. Here is the code

randData = RandomVariate[NormalDistribution[], 10^3];
estDists = 
  EstimatedDistribution[randData, #] & /@ {NormalDistribution[mu, 
     sigma], StudentTDistribution[mu, sigma, 5]};
pdfs = PDF[#, x] & /@ estDists;
histPlot = Histogram[randData, Automatic, "ProbabilityDensity"];
distPlots = 
  Plot[pdfs, {x, Min[randData], Max[randData]}, 
   PlotLegends -> LineLegend[estDists]];
Show[{histPlot, distPlots}]

enter image description here

Now that's fine if you have 2 or 3 fitted distributions but if you have 10 it gets a little crowded. I found a very nice solution to this (see link above) that lets you click on the legend to toggle the curves on/off. I'll repeat the code here;

 (*Don't allow the Plot to evaluate yet*)
Attributes[DynamicLegend] = {HoldAll};

DynamicLegend[(plotfnc_)[f_, args___, (opts___)?OptionQ]] := 
 Module[{plot},(*evaluate the plot*)plot = plotfnc @@ {f, args, opts};
  (*if it does not have a legend,there is nothing to do*)
  If[! MatchQ[plot, _Legended], Return[plot]];
  DynamicModule[{(*get the directives and legend_text from the plot*)
    leg = plot[[2, 1]],(*by default,
    all plots are visible (not transparent)*)
    trans = Table[False, {Length[plot[[2, 1, 1, 1]]]}]}, 
   Dynamic[(*dynamically re-
    evaluate the plot with dynamic PlotStyles and PlotLegends*)
    plotfnc @@ {f, 
      args,(*prepend the directives with Transparent on dynamic \
switches*)PlotStyle -> 
       Table[With[{i = i}, 
         Flatten[Directive[
           If[TrueQ[trans[[i]]], Transparent, Directive[]], 
           leg[[1, 1, i]]], 1, Directive]], {i, 
         Length[leg[[1, 
           1]]]}],(*turn the legend_text's into Togglers and shove \
them back into the*)(*LineLegend structure.Also,
      delete the old directives in the*)(*LineLegend structure to \
force PlotLegends to pick up the new*)(*directives,
      so legend lines turn on/off with the curves*)
      PlotLegends -> 
       ReplacePart[leg, 
        Join[{{1, 1} -> Sequence[]}, 
         Table[With[{i = i}, {1, 2, i} -> 
            Toggler[
             Dynamic[trans[[i]]], {True -> leg[[1, 2, i]], 
              False -> leg[[1, 2, i]]}, FrameMargins -> 0]], {i, 
           Length[leg[[1, 2]]]}]]], 
      FilterRules[{opts}, 
       Except[{PlotStyle, 
         PlotLegends}]]},(*only need dynamic updates when trans is \
modified*)TrackedSymbols :> {trans}]]]

And can use it for a single plot

dynamicDistPlots = DynamicLegend[Plot[pdfs, {x, Min[randData], Max[randData]}, 
PlotLegends -> LineLegend[estDists]]]

But can't get it to work with the histogram

Show[{histPlot, dynamicDistPlots}]

I've had a play with applying the DynamicLegend to the Show and even tried to modify the DynamicLegend function (although I think that was a bit over by head). Any help would be appreciated as this would be a great tool to compare distributions.

Cam
  • 1,596
  • 12
  • 20
  • The problem is that dynamicDistPlots has Head DynamicModule and not Graphics. I would recommend allowing another input into dynamicDistPlots function which will allow you to pass in another graphics object directly. – Jonathan Shock May 30 '13 at 01:42
  • Thanks Jonathan. So if I add another input for DynamicLegend which contains the histogram I can do the overlaying in that function. Seems to make sense, I'll try it out. – Cam May 30 '13 at 05:14

1 Answers1

4

Will this work?

 DynamicModule[{x1 = True, x2 = True},
 randData = RandomVariate[NormalDistribution[], 10^3];
 estDists = 
  EstimatedDistribution[randData, #] & /@ {NormalDistribution[mu, 
     sigma], StudentTDistribution[mu, sigma, 5]};
 pdfs = PDF[#, x] & /@ estDists;
 histPlot = Histogram[randData, Automatic, "ProbabilityDensity"];
 Dynamic[distPlots = 
   Plot[pdfs, {x, Min[randData], Max[randData]}, 
    PlotStyle -> {If[x1, ColorData[1, 1], Opacity[0.1]], 
      If[x2, ColorData[1, 2], Opacity[0.1]]}, 
    PlotLegends -> {Button[estDists[[1]], x1 = ! x1], 
      Button[estDists[[2]], x2 = ! x2]}];
  Show[{histPlot, distPlots}]
  ]
 ]

Adding Appearance->None inside Button can make them more like just strings.

MinHsuan Peng
  • 2,046
  • 15
  • 14