10
$\begingroup$

I've been attempting to find efficient ways to generate graphics with legends for export, and have been working with both LevelScheme and the new (to v9) PlotLegends. It seems the easiest approach would be to use PlotLegends to automatically generate the legends with the graphic, peel out the plots and legends separately, then use LevelScheme to arrange/combine as desired. Sounds pretty painless, but...

The RawGraphics command of LevelScheme does not accept MMA's LineLegend, etc. objects. The only workaround I've found is to wrap the legend in Graphics[Inset[...]], but this breaks down, as LevelScheme dumps these graphics objects in the middle of the MultiPanel area.

Any ideas on how to get this working (or maybe why it cannot work)? Below is my attempt so far:

Get["LevelScheme`"];

myPlot = Plot[Sin[x], {x, 0, 10}, ImageSize -> 72*3, 
   PlotLegends -> Placed[LineLegend["Expressions"], {After, Center}]];
Graphics[Inset[myPlot[[2, 1, 1]]], ImagePadding -> None, 
 PlotRangeClipping -> True]

myDensity = 
  DensityPlot[Sin[x], {x, 0, 10}, {y, -1, 1}, ImageSize -> 72*3, 
   PlotLegends -> Automatic];
Graphics[Inset[myDensity[[2, 1, 1]]], ImagePadding -> None, 
 PlotRangeClipping -> True]

Figure[
 {
  Multipanel[
   {{0, 1}, {0, 1}},
   {2, 2},
   XPlotRanges -> {{0, 10}, {-1, 1}},
   YPlotRanges -> {{-1.1, 1.1}, {-1.1, 1.1}}
   ],
  FigurePanel[{1, 1}],
  RawGraphics[myPlot[[1]]],
  FigurePanel[{2, 1}],
  RawGraphics[myDensity[[1]]],
  FigurePanel[{1, 2}],
  RawGraphics[
   Graphics[Inset[myPlot[[2, 1, 1]]], ImagePadding -> None, 
    PlotRangeClipping -> True]],
  FigurePanel[{2, 2}],
  RawGraphics[
   Graphics[Inset[myDensity[[2, 1, 1]]], ImagePadding -> None, 
    PlotRangeClipping -> True]],
  },
 PlotRange -> Automatic,
 ExtendRange -> {{0.1, 0.1}, {0.15, 0.1}},
 ImageSize -> 6*72, Frame -> True
 ]

enter image description here

$\endgroup$

3 Answers 3

4
+300
$\begingroup$

The reason the OP's hack works is because Inset allows to place non-graphic objects in a graphics object. The reason it does not work is because Inset places the inset in the center of hosting graph by default.

The package has a command to include non-graphics objects: ScaledLabel.

The following function takes a legend plot pand returns the command to include it in a Figure at the location l with the scaling s. The location is respect to the original graph.

transformLegendPlot[p_, l_, s_: 1] := 
  Sequence[RawGraphics@p[[1]], 
   ScaledLabel[l, 
    p[[2, 1, 1]] /. (LegendMarkerSize -> x_) :> 
      LegendMarkerSize -> s x]];

Now, transformLegendPlot can be used almost as a native command:

Figure[{Multipanel[{{0, 1}, {0, 1}}, {2, 1}, XPlotRanges -> {{0, 10}},
    YPlotRanges -> {{-1.1, 1.1}, {-1.1, 1.1}}], FigurePanel[{1, 1}], 
  transformLegendPlot[myPlot, {0.5, 0.7}], FigurePanel[{2, 1}], 
  transformLegendPlot[myDensity, {0.2, 0.5}, .5]}, 
 PlotRange -> Automatic, ExtendRange -> {{0.1, 0.1}, {0.15, 0.1}}, 
 ImageSize -> 6*72, Frame -> True]

enter image description here


Update

Extracting and formatting the legends PlotLegends (for use in LevelScheme).

The OP mentions in a comment that the plot legends should be posted in a different pane. Since that seems to have been his original intent, I will oblige.

The following code produces a ScaledLabel object to be used in a different panel with the addition that the style of the legends can be changed.

extractLegendsAndFormat[plot_, scale_, styles___] := 
  ScaledLabel[{0.5, 0.5}, 
   plot[[2, 1, 1]] /. 
     x : TraditionalForm[HoldForm[_]] :> 
      Style[x, styles] /. (LegendMarkerSize -> x_) :> 
     LegendMarkerSize -> scale x];

Now, you can give format options as shown below:

l1 := extractLegendsAndFormat[myPlot, 1, FontName -> "Helvetica", 
  FontSize -> 33, FontColor -> Red];
l2 := extractLegendsAndFormat[myDensity, 0.5];

Those objects can be placed in their own panels:

Figure[{Multipanel[{{0, 1}, {0, 1}}, {2, 2}, XPlotRanges -> {{0, 10}},
    YPlotRanges -> {{-1.1, 1.1}, {-1.1, 1.1}}],
  FigurePanel[{1, 1}], RawGraphics@myPlot[[1]],
  FigurePanel[{1, 2}], l1, 
  FigurePanel[{2, 1}], RawGraphics@myDensity[[1]], 
  FigurePanel[{2, 2}], l2}, PlotRange -> Automatic, 
 ExtendRange -> {{0.1, 0.1}, {0.15, 0.1}}, ImageSize -> 6*72, 
 Frame -> True]

enter image description here


Previous answer

300 reputation is too good to let it go, so here is my second try. It still uses Overlay.

ff = Figure[{Multipanel[{{0, 1}, {0, 1}}, {2, 2}, 
     XPlotRanges -> {{0, 10}, {-1, 1}}, 
     YPlotRanges -> {{-1.1, 1.1}, {-1.1, 1.1}}], FigurePanel[{1, 1}], 
    RawGraphics[myPlot[[1]]], FigurePanel[{2, 1}], 
    RawGraphics[myDensity[[1]]], FigurePanel[{1, 2}], 
    FigurePanel[{2, 2}]}, PlotRange -> Automatic, 
   ExtendRange -> {{0.1, 0.1}, {0.15, 0.1}}, ImageSize -> 6*72, 
   Frame -> True];

Manipulate[
 Overlay[{ff, 
   Graphics[{Opacity[0], Rectangle[{0, 0}, {1, 1}], Opacity[1], 
     Inset[myPlot[[2, 1, 1]], {x, y}]}], 
   Graphics[{Opacity[0], Rectangle[{0, 0}, {1, 1}], Opacity[1], 
     Inset[myDensity[[2, 1, 1]], {.94, .618}]}]}], {{x,.45}, 0, 1}, {{y,.8}, 0, 
  1}]

enter image description here

$\endgroup$
9
  • 1
    $\begingroup$ I will update this answer in a couple of days. LevelScheme updates the coordinates of each pane but it does not update the coordinates of the Inset. Fortunately, Inset can use either coordinates respect to the original plot or respect to the container. $\endgroup$
    – Hector
    Oct 19, 2013 at 12:14
  • $\begingroup$ Thanks for your time! I'm just curious: you say in your post that this is your second try. I'm just curious: what was your first try? $\endgroup$
    – Andrew
    Oct 19, 2013 at 14:52
  • $\begingroup$ @Andrew: I posted a solution using Column and Overlay to produce a layout similar to the OP's but with a lot more control (but less special effects). A fellow poster with 50K reputation made me realize that your question specifically asks about the package LevelScheme. $\endgroup$
    – Hector
    Oct 19, 2013 at 17:49
  • $\begingroup$ Oh, ok, so your original answer is no longer posted? Or is it the first section of your answer above? $\endgroup$
    – Andrew
    Oct 19, 2013 at 19:31
  • 1
    $\begingroup$ @Andrew: I deleted my first post because it did not address fully the OP's question. The first section above is the update that I mentioned in a previous comment. With transformLegendPlot, you specify the coordinates of the legend respect to the scaled coordinates of the original graphic. It also allows you to scale the legend of a density plot. The resulting commands must be used with LevelScheme. I think that completes the requests from OP, doesn't it? $\endgroup$
    – Hector
    Oct 19, 2013 at 19:52
4
$\begingroup$

I think the "easiest" way is to abandon the use of LevelScheme to position your legend. Instead, I would do something like this,

legend = myplot /. Legended[_, Placed[l_, ___]|{Placed[l_, ___]}] :> l;

Legended[
  Figure[ (* fill in details *),
  Placed[ legend, placement ]
]
$\endgroup$
4
  • 1
    $\begingroup$ This is ok if you're using only one figure, but gets cumbersome for multipanel figures. Also, PlotLegends doesn't support (AFAIK) legends inside the plot area, which is more economical (space wise) than outside. $\endgroup$
    – rm -rf
    Oct 11, 2013 at 21:40
  • 1
    $\begingroup$ @rm-rf If there is only a single legend, then this method works just fine. Also, Placed accepts numerical positions, too, so placing it inside the plot is just fine. $\endgroup$
    – rcollyer
    Oct 12, 2013 at 0:22
  • 2
    $\begingroup$ Yes...all around. The built-in PlotLegends with Placed (using numerical positioning) is definitely the most efficient, automated way I've found to get a legend on a plot. However, all the forums I've read, as well as my own experience thus far, indicate that LevelScheme is the most efficient way to arrange multiple graphics for publication/export. I'm a long way from mastering all of MMA's graphics functions...I'm hoping there's some function in there that'll cause LevelScheme to just accept the legend as it would any other graph. $\endgroup$
    – brad
    Oct 14, 2013 at 17:43
  • $\begingroup$ @brad you could try Rasterize. $\endgroup$
    – rcollyer
    Oct 14, 2013 at 19:39
2
$\begingroup$

I would approach your problem, firstly by dropping all legends from your plots. As an example, your density plot would be redefined as

myDensity = DensityPlot[Sin[x], {x, 0, 10}, {y, -1, 1}];

and I would define a bar legend using another density plot:

lgDensity = 
 DensityPlot[y, {x, 0, 1}, {y, -1, 1}, 
  ColorFunction -> ColorData["LakeColors"]
  ]

Everything after this is stock, standard level scheme. One presentation using your input and level scheme is

Figure[{
  SetOptions[SchemeObject, FontFamily -> "Helvetica", 
   FontSize -> 10],
  Multipanel[{{0, 1}, {0, 1}}, {2, 2},
   XPlotRanges -> {{0, 10}, {-1, 1}}, 
   YPlotRanges -> {{-1.1, 1.1}, {-1.1, 1.1}},
   XFrameLabels -> {"X Axis", ""},
   YFrameLabels -> {"Label 1", "Label 2"},
   YGapSizes -> 0.05, XGapSizes -> .05,
   BufferB -> 5, BufferL -> 7,
   Margin -> 45,
   YPanelSizes -> {1, 1},
   XPanelSizes -> {0.98, .05}
   ],
  FigurePanel[{1, 1}],
  RawGraphics[myPlot[[1]]],
  FigurePanel[{2, 1}],
  RawGraphics[myDensity],
  FigurePanel[{2, 2}, ShowPanelLetter -> False, 
   ShowTickLabels -> {False, False, False, True}, 
   XPlotRange -> {0, 1}, YPlotRange -> {-1, 1}],
  RawGraphics@lgDensity
  },
 ImageSize -> 500,
 Frame -> True]

which, to my eyes anyway, is considerably cleaner than your example. YMMV.

As other responders have pointed out, you can easily add more ScaledLabel[]'s in the appropriate frame.

$\endgroup$
1
  • 1
    $\begingroup$ You make a good point in favor of a more straightforward approach. I keep debating that point with myself, but in the end I like having MMA automatically pull all the color/line data, etc. from the graphic into the legend. Definitely a matter of taste though. $\endgroup$
    – brad
    Oct 21, 2013 at 15:53

Your Answer

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

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