9
$\begingroup$

Edit: I am still using version 10.1; has anything changed by 11.0.1?


Plot properties defined in a Theme override (supersede) properties defined on Options.

This can be undesirable in a case such as:

customLegend = 
  LineLegend[97, {"base 2", "natural", "base 10"}, 
   LegendLabel -> "Logarithms"];

SetOptions[ListPlot, PlotLegends -> customLegend];

ListPlot[{Log2, Log, Log10}[Range@40] // Through,
  PlotTheme -> "Detailed"
]

enter image description here
( Where is my legend? )

To make it explicitly clear if you use the option PlotTheme -> None or set $PlotTheme = None the legend works as intended.

Considering work-arounds:

  • One can make the Options-defined legend appear by using PlotTheme -> None but that is a heavy-handed solution that loses benefit of Themes.

  • One can of course set PlotLegends -> customLegend within every ListPlot expression but that loses the benefit of a global setting.

  • One can define a custom theme with a prioritizing weight and use PlotTheme -> {"legend", "Detailed"} but the two must be used together: one cannot use SetOptions[ListPlot, PlotTheme -> "legend"] and then PlotTheme -> "Detailed" within ListPlot and have them combine.

  • One could use a custom myListPlot that gives priority to Options settings, e.g. myListPlot[args___] := ListPlot[args, Options @ myListPlot]. This seems to have the least downside of the listed methods but it will not apply to existing ListPlot uses without substitution.

What other avenues of approach might one take to this problem?

$\endgroup$
  • 3
    $\begingroup$ One could lobby WRI to do PlotThemes right. One could work to get many others to do the same. $\endgroup$ – m_goldberg Jul 30 '15 at 16:33
  • $\begingroup$ @m_goldberg I do not feel that Plot Themes are done "wrong." Actually I'm quite happy with them and egotistically I like to think of them as an outgrowth of my own method. However there are certain points such as this that could benefit from improvement. I'd like to be able to do SetOptions[ListPlot, PlotLegends -> Priority[customLegend]] and have it supersede Themes. I'll code up something like that in a self-answer at some point I think. $\endgroup$ – Mr.Wizard Jul 30 '15 at 17:25
  • 2
    $\begingroup$ Same in v11.0.0.0. $\endgroup$ – István Zachar Oct 16 '16 at 10:38
  • $\begingroup$ The example doesn't work even without PlotTheme. It seems like the legends have to be used with every ListPlot call. Checked on both Windows and Linux. $\endgroup$ – Stitch Feb 28 '17 at 20:07
  • 1
    $\begingroup$ How about this? PlotTheme /: Rule[PlotTheme, x_String] := RuleDelayed[PlotTheme, If[MemberQ[Stack[], ListPlot], {"customLegendTheme", x}, x]]? For complete solution, one would also need to define the TagSetDelayed for the RuleDelayed $\endgroup$ – Stitch Feb 28 '17 at 22:47
2
$\begingroup$

This is a working example using the original post bullet #3 idea and the discussion in the comment section above (including custom theme and theme weighting):

First, we define a function that sets options using UpValues and TagSetDelayed and the other one that clears them:

ClearAll[robustSetOptions, robustClearOptions];

robustSetOptions[function_, 
  options_?(MatchQ[#, Rule[_, _]] || MatchQ[#, List[Rule[_, _] ...]] &)] := 
 Module[{newTheme = "customTheme"},
  System`PlotThemeDump`resolvePlotTheme[newTheme, ToString@function] :=
    Themes`SetWeight[options, System`PlotThemeDump`$ComponentWeight];
  Unprotect[PlotTheme];
  If[UpValues[PlotTheme] =!= {},
   PlotTheme /: Rule[PlotTheme, x_String] =.;
   PlotTheme /: RuleDelayed[PlotTheme, x_String] =.
   ];
  SetOptions[function, PlotTheme -> newTheme];
  Unprotect[PlotTheme];
  PlotTheme /: Rule[PlotTheme, x_String] := 
   RuleDelayed[PlotTheme, 
    If[MemberQ[Stack[], function], {newTheme, x}, {x}]];
  PlotTheme /: RuleDelayed[PlotTheme, x_String] := 
   RuleDelayed[PlotTheme, 
    If[MemberQ[Stack[], function], {newTheme, x}, x]];
  Protect[PlotTheme];
  ]

robustClearOptions[function_] := Module[{},
  If[UpValues[PlotTheme] =!= {},
     Unprotect[PlotTheme];
     PlotTheme /: Rule[PlotTheme, x_String] =.;
     PlotTheme /: RuleDelayed[PlotTheme, x_String] =.;
     Protect[PlotTheme];
     ]
    SetOptions[function, PlotTheme -> $PlotTheme];
  ]

Then we can call it to define custom options (applied as a custom theme). Multiple options are possible (in this example there is a custom legend from the OP and the AspectRatio):

ClearAll[g1, g1t, g2, g2t, g1d, g2d, plot1, plot1t, plot2, plot2t];

(* define custom options *)
customLegend = LineLegend[97, {"base 2", "natural", "base 10"}, LegendLabel -> "Logarithms"];
plotTheme = "Detailed";

plot1 := ListPlot[{Log2, Log, Log10}[Range@40] // Through];
plot1t := ListPlot[{Log2, Log, Log10}[Range@40] // Through, PlotTheme -> plotTheme];
plot2 := ListLinePlot[{Log2, Log, Log10}[Range@40] // Through];
plot2t := ListLinePlot[{Log2, Log, Log10}[Range@40] // Through, PlotTheme -> plotTheme];

(* set custom options *)
robustSetOptions[ListPlot, {PlotLegends -> customLegend, AspectRatio -> 1}];
g1 = plot1; (* should see a legend *)
g1t = plot1t; (* should see a legend *)
g2 = plot2; (* should not see a legend *)
g2t = plot2t; (* should not see a legend *)

(* remove custom options *)
robustClearOptions[ListPlot];
g1d = plot1;  (* should not see a legend *)
g2d = plot2;  (* should not see a legend *)

Grid[{
  {"", "robustSetOptions", SpanFromLeft, "default"},
  {SpanFromAbove, "No PlotTheme", "With PlotTheme \"" <> plotTheme <> "\"", SpanFromAbove},
  {"ListPlot", g1, g1t, g1d}, {"ListLinePlot", g2, g2t, g2d}}, 
 Alignment -> {Left, 
   Automatic, {{1, 2} -> Center, {1, 4} -> {Center, Center}, {2, 2} ->
      Center, {2, 3} -> Center, {2, 1} -> Center, {3, 1} -> Center}}, 
 Dividers -> Center]

As expected, there is a legend and the graphs are squared when ListPlotis called with or without the PlotTheme option. Any other function (ilustrated with ListLinePlot) is not affected by the options, as they are set only for a specific function (ListPlotin this example). After the options are cleared, all functions are back to their default style.

enter image description here

As this is a working example, please comment, critique, improve or suggest better ways of implementing it.

$\endgroup$
  • $\begingroup$ I surely like where you're going with this. I'll need to play with it a bit when I have time before I know if it will behave as I hope, but +1 for now. $\endgroup$ – Mr.Wizard Mar 1 '17 at 23:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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