7
$\begingroup$

Take a simple ContourPlot or DensityPlot with a corresponding BarLegend:

DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, 
  PlotLegends -> Placed[BarLegend[Automatic], After]]

Simple density plot.

Note the perfect alignment of the BarLegend with the contents of the plot. Now, let's label the axes (which one should always do!):

Simple density plot with axes labels.

The alignment is now ruined! Further investigation reveals the culprit to be the label on the y-axis. Specifying x-axis labels

DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, 
  PlotLegends -> Placed[BarLegend[Automatic], After], 
  FrameLabel -> {"x", None, "x", None}]

works, whereas

DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, 
  PlotLegends -> Placed[BarLegend[Automatic], After], 
  FrameLabel -> {None, "y", None, None}]

and similar combinations involving a y-axis label don't.

This seems to me to more of a bug than a feature. Is there any way to fix this automatically without having to manually control the BarLegend size and placement? It seems Mathematica is able to do it but some frame setting is throwing it off.

$\endgroup$
6
  • $\begingroup$ I can not reproduce your problem when I run your code on my system: V11.3 running on MacOS 10.13.4. Please indicate Mathematica version and OS you are using. $\endgroup$
    – m_goldberg
    Aug 29, 2019 at 16:20
  • $\begingroup$ Mathematica version 12.0.0 and Mac OSX 10.12.6 $\endgroup$ Aug 29, 2019 at 16:22
  • $\begingroup$ What happens if you try this: DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, PlotLegends -> Automatic, FrameLabel -> {{"lf", "rt"}, {"btm", "top"}}]? $\endgroup$
    – m_goldberg
    Aug 29, 2019 at 16:26
  • $\begingroup$ I am unable to reproduce this on 12.0 on macOS 10.14 on a Retina MacBook Pro. $\endgroup$
    – ktm
    Aug 29, 2019 at 17:36
  • $\begingroup$ @m_goldberg Same problem. $\endgroup$ Aug 30, 2019 at 7:32

4 Answers 4

5
+100
$\begingroup$

The output of the DensityPlot call is a Legended object, that basically creates a Grid object that lines up the graphic and the legend. So, to get the size of the legend and the alignment of the legend to match the graphic, we first need to figure out how to do this when using Grid. Consider the following two graphics objects:

g = Graphics[{Circle[{1,1}]}, Frame->True, FrameLabel->{{None, None}, {"x", None}}];
b = Graphics[{Rectangle[{0,-100}, {10,100}]}, ImageSize->{Automatic, 360}];

which basically have the same shapes as the density plot and the bar legend. Let's see what happens when we put them side by side using Grid:

Grid[{{g, b}}]

enter image description here

Not very good. To get the sizes to match, I will use the syntax: ImageSize -> Automatic -> size instead of ImageSize -> size:

Grid[{{Show[g, ImageSize->Automatic->360], Show[b, ImageSize->Automatic->{Automatic, 360}]}}]

enter image description here

Much better. However, the alignment is not quite correct. The only way I know to align two graphics objects in a Grid when ImagePadding is involved, is to use BaselinePosition->Axis:

Grid[{{
    Show[g, ImageSize->Automatic->360, BaselinePosition->Axis],
    Show[b, ImageSize->Automatic->{Automatic, 360}, BaselinePosition->Axis]
}}]

enter image description here

One final issue is that the AxesOrigin of the two objects are not in the same scaled position. I think the BarLegend always uses a raster with the center at {0,0}. So, we need to make sure that the graphic axis is also at the center:

Grid[{{
    Show[g, ImageSize->Automatic->360, BaselinePosition->Axis, AxesOrigin->{0, 1}],
    Show[b, ImageSize->Automatic->{Automatic, 360}, BaselinePosition->Axis]
}}]

enter image description here

This is basically the way to size and align two graphics objects in a Grid.

Now, unfortunately, Legended does two things that ruin the above alignment procedure:

TracePrint[
    ToBoxes @ DensityPlot[
        Sin[2 x y],
        {x,-2,2},
        {y,-2,2},
        PlotLegends->Placed[BarLegend[Automatic],After],
        FrameLabel->{{None,None},{"x",None}}
    ],
    ToBoxes[_Grid,_] | Pane[_Graphics, __Rule],
    TraceInternal->True,
    TraceAction->Print@*OutputForm@*ReplaceAll[Grid->Inactive[Grid]]
];
Pane[-Graphics-, Alignment -> Left, AppearanceElements -> None, ImageMargins -> {{5, 5}, {5, 5}}, ImageSizeAction -> ResizeToFit]

Pane[-Graphics-, Alignment -> Left, AppearanceElements -> None, ImageMargins -> {{5, 5}, {5, 5}}, ImageSizeAction -> ResizeToFit]

ToBoxes[Inactive[Grid][Map[RawBoxes, BoxForm`grid$716644, {2}], Alignment -> {Center, Center}, WrappersDump`removeIfDefault[BaselinePosition -> WrappersDump`baselineposition$716644, Automatic], WrappersDump`removeIfDefault[Spacings -> OptionValue[Labeled, {}, Spacings], Automatic], StripOnInput -> True], StandardForm]

ToBoxes[Inactive[Grid][{{RawBoxes[TagBox[ItemBox[PaneBox[TagBox[#1, SkipImageSizeLevel], Alignment -> {Center, Baseline}, BaselinePosition -> Baseline], DefaultBaseStyle -> Labeled], SkipImageSizeLevel]], RawBoxes[ItemBox[#2, DefaultBaseStyle -> LabeledLabel]]}}, Alignment -> {Center, Center}, BaselinePosition -> {1, 1}, StripOnInput -> True], StandardForm]

Both this Pane wrapper and the Alignment -> {Center, Center} option in the Grid prevent the above alignment procedure from working. For example, using Alignment -> {Center, Center}:

Grid[
    {{
        Show[g, ImageSize->Automatic->360, BaselinePosition->Axis, AxesOrigin->{0, 1}],
        Show[b, ImageSize->Automatic->{Automatic, 360}, BaselinePosition->Axis]
    }},
    Alignment->{Center, Center}
]

enter image description here

and similarly for the Pane option.

So, to fix your issue, we need to do the following:

  1. Use the ImageSize -> Automatic -> size option setting.
  2. Use BaselinePosition -> Axis.
  3. Avoid using Alignment -> {Center, Center} when constructing the Grid.
  4. Avoid wrapping the graphic in Pane.
  5. Make sure that the AxesOrigin of the graphic is in the center of the graphic.

Here is one approach that does this. First define a LegendFunction that fixes the BarLegend Graphics options and removes the Pane wrapper:

fixLegend[size_][legend_] := ReplaceAll[
    legend,
    {
        g_Graphics :> Show[g, ImageSize->Automatic->{Automatic, size}, BaselinePosition->Axis],
        Pane -> Function@#
    }
]

Then, define a wrapper function that removes the unwanted Grid alignment during box generation:

MakeBoxes[stripGridAlignemnt[expr_], form_] ^:= ReplaceAll[
    MakeBoxes[expr, form],
    Rule[GridBoxAlignment, _] -> Sequence[]
]

Now, we are ready to create the desired output:

stripGridAlignemnt @ DensityPlot[
    Sin[2 x y],
    {x,-2,2},
    {y, 0, 4},
    BaselinePosition->Axis,
    AxesOrigin -> {0, 2},
    ImageSize->Automatic->360,
    PlotLegends->Placed[BarLegend[Automatic, LegendFunction -> fixLegend[360]], After],
    FrameLabel->{{None,None},{"x",None}}
]

enter image description here

$\endgroup$
6
  • 2
    $\begingroup$ Great detailed answer! $\endgroup$
    – Carl Lange
    Oct 30, 2019 at 16:32
  • $\begingroup$ What does a mean in ImageSize -> a -> b when a is not Automatic? $\endgroup$
    – Szabolcs
    Oct 31, 2019 at 9:06
  • $\begingroup$ It would be really useful to be able to align the baseline on the frame (not axis) ... Luckily, in this case I had frames, not axes (and when I also have axes, they don't have ticks, so it's easy to replace them with plain lines). $\endgroup$
    – Szabolcs
    Oct 31, 2019 at 9:07
  • 1
    $\begingroup$ @Szabolcs ImageSize -> a -> b when a is a number means that a in plot coordinates has size b. When a is Automatic it means the base plot range (before plot range padding) has size b. Was there a question in your second comment? $\endgroup$
    – Carl Woll
    Oct 31, 2019 at 15:12
  • $\begingroup$ @Szabolcs I understand your comment now. If you actually displayed axes in the plot, then using AxesOrigin to align the graphic would be a problem, because it would also change the position of the axes. I think I have an idea of how to handle this situation using Inset to create a graphic where the inset graphic has the correct AxesOrigin, while the enclosing graphic has a different AxesOrigin. $\endgroup$
    – Carl Woll
    Oct 31, 2019 at 16:55
5
$\begingroup$
$Version (* Wolfram Cloud FrontEnd *)

12.0.0 for Linux x86 (64-bit) (April 7, 2019)

We can add the frame labels using Labeled instead of using FrameLabel

ClearAll[addLabels]
addLabels[plot_, labels_, pos_, legpos_ : Right]:= Labeled[First @ plot, 
 Append[labels, plot[[2, 1]]], Append[pos, legpos]]

Examples:

dp1 = DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, 
   ImageSize -> 320,  PlotLegends -> Placed[BarLegend[Automatic], After]];

Row[{dp1, addLabels[dp1, {"x", "y"}, {Bottom , Left}]},  Spacer[20]]

enter image description here

Row[{dp1, addLabels[dp1, Style[#, 32] & /@ {"x", "y", "z"}, {Bottom , Left, Top}]}, 
 Spacer[20]]

enter image description here

cp = ContourPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, ImageSize -> 320,  
  PlotLegends -> Placed[BarLegend[Automatic, LegendLayout -> "Row"], Top]];

Row[{cp, addLabels[cp, Style[#, 32] & /@ {"x", "y", "z"}, 
 {Bottom , Left, Right}, Top]}, Spacer[20]] 

enter image description here

$\endgroup$
4
  • $\begingroup$ Thanks for the suggestion, although I do note that the labels are now not centred around the plot but around the plot + legend. Anyhow, I have good reason to believe this issue is a bug as several users cannot replicate it. $\endgroup$ Aug 30, 2019 at 11:03
  • $\begingroup$ @OldTomMorris, the labels are centered around the plot (not around the plot + legend.) $\endgroup$
    – kglr
    Aug 30, 2019 at 16:12
  • 1
    $\begingroup$ Perhaps @OldTomMorris meant to say that the label is centered around the plot including ImagePadding, and not just around the plot range. As you can see, the x label is located to the left of 0. $\endgroup$
    – Carl Woll
    Oct 30, 2019 at 18:38
  • 1
    $\begingroup$ @CarlWoll, oh, he meant plot+labels. Great point; thank you. I don't see an easy and clean fix for that issue. $\endgroup$
    – kglr
    Oct 30, 2019 at 20:34
2
$\begingroup$

Seems that I am the few people who can reproduce this "bug". (MMA 12.0 on Windows 10)

I suggest you manually control the height of the image and the size of barlegend. Here is what I used:

DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, AspectRatio -> 1, 
 PlotLegends -> 
  Placed[BarLegend[Automatic, LegendMarkerSize -> 270], After], 
 FrameLabel -> {"x", None, "x", None}, ImageSize -> {Automatic, 300}]

Output:enter image description here

Adding y axis label:

DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, AspectRatio -> 1, 
 PlotLegends -> 
  Placed[BarLegend[Automatic, LegendMarkerSize -> 270], After], 
 FrameLabel -> {"x", "y", "x", None}, ImageSize -> {Automatic, 300}]

enter image description here

It is not perfect in alignment, but at least, it will not shift uncontrollably, so that you can manually adjust the alignment.

Again, for publication-quality production of figures, I suggest trying SciDraw. This might be helpful to your question, as you can treat the bar legend as a side by side nested frame.

$\endgroup$
1
  • $\begingroup$ The size of the main image cannot be changed. It is constrained by the physical medium in which it will be printed. I cannot use this solution. $\endgroup$
    – Szabolcs
    Oct 30, 2019 at 8:17
1
$\begingroup$

Mathematica is bad at alignment. However, the job can be done in different ways:

f = DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, 
  ColorFunction -> "BlueGreenYellow", 
  LabelStyle -> {FontSize -> 17, Black, Bold, 
    FontFamily -> "Times New Roman"}, PlotRange -> All, 
  PlotLegends -> None, FrameLabel -> {"X", "y", "Z", None}]  
Legended[f, 
 Placed[BarLegend[{ColorData[{"BlueGreenYellow", {-1, 1}}], {-1, 1}}, 
   LabelStyle -> {FontSize -> 13, Blue, Bold}, 
   LegendMarkerSize -> 250.], {1, .51}]]

enter image description here

You can also use Grid when you have more figures as follows:

f = DensityPlot[Sin[2 x y], {x, -2, 2}, {y, -2, 2}, 
  ColorFunction -> "BlueGreenYellow", 
  LabelStyle -> {FontSize -> 17, Black, Bold, 
    FontFamily -> "Times New Roman"}, PlotRange -> All, 
  PlotLegends -> None, FrameLabel -> {"X", "y", "Z", None}, 
  ImagePadding -> 80, ImageSize -> 300]
Legended[Grid[{{f, f}, {f, f}}, Spacings -> {-12, -12}], 
 Placed[BarLegend[{ColorData[{"BlueGreenYellow", {-1, 1}}], {-1, 1}}, 
   LabelStyle -> {FontSize -> 13, Blue, Bold}, 
   LegendMarkerSize -> 313.], {0.94, .45}]]

enter image description here

$\endgroup$

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.