10
$\begingroup$

There are numerous questions here on combining two plots (usually ListPlots), but (as far as I can see) none with quite what I seek. I would like to make a single plot consisting of two functions, each with the same abscissa (x range) but different ordinates (y range), in both offset and scale. I want the left Frame axis to be in one style (blue) and the right Frame axis to be in another (red). Inserting is extremely awkward because the spatial extents of the tick labels, offsets, scales, padding, and so on all differ.

Here is how far I've gotten:

pt1 = Plot[1 + Sin[x], {x, 1, 5},
  PlotRange -> {0, 3},
  PlotStyle -> Blue,
  Frame -> {{True, False}, {True, False}},
  FrameStyle -> {{Blue, None}, {Black, None}}]

enter image description here

pt2 = Plot[80 + 50 Cos[x^2],
  {x, 1, 5},
  PlotRange -> {1, All},
  AxesOrigin -> {1, 20},
  PlotStyle -> Red,
  Frame -> {{False, True}, {True, False}},
  FrameStyle -> {{None, Red}, {Black, None}},
  FrameTicks -> {{None, Range[20, 140, 10]}, {Automatic, None}}]

enter image description here

I would like to combine these into a single plot, where the left frame axis goes from 0 to 3 while the right axis goes from 20 to 120. That is, the same height in the graph should correspond to 0 on the left and 20 on the right, and the same height in the graph should correspond to 3 on the left and 120 on the right.

Show[pt1,pt2] simply does not work:

enter image description here

I can change aspect ratios and offsets by hand but surely there is a more direct way.

$\endgroup$
  • $\begingroup$ related: Plot with multiple Y-axes $\endgroup$ – kglr Nov 22 '19 at 21:36
  • $\begingroup$ @kglr: Yes, related... but without addressing my problem of offsets. And all this awkward and arbitrary image padding to be done by hand. $\endgroup$ – David G. Stork Nov 22 '19 at 21:38
  • $\begingroup$ maybe something like: r1 = {0, 3}; r2 = {20, 140}; Plot[{1 + Sin[x], Rescale[80 + 50 Cos[x^2], r2, r1]}, {x, 1, 5}, PlotRange -> {0, 3}, PlotStyle -> {Blue, Red}, Frame -> {{True, True}, {True, False}}, FrameTicks -> {{Automatic, Charting`FindTicks[r1, r2][##] &}, {Automatic, Automatic}}, FrameStyle -> {{Blue, Red}, {Black, None}}]? $\endgroup$ – kglr Nov 22 '19 at 22:10
  • $\begingroup$ My understanding is that two x-axes should exist in the image, since 0 on the left is not the same as 20 on the right, is this correct? $\endgroup$ – C. E. Nov 22 '19 at 22:16
  • $\begingroup$ Related MultipleAxesListPlot. $\endgroup$ – Rohit Namjoshi Nov 23 '19 at 2:41
9
$\begingroup$

You can use the CombinePlots ResourceFunction to do all the work for you:

pt1 = Plot[1 + Sin[x], {x, 1, 5},
  PlotRange -> {0, 3},
  PlotStyle -> Blue,
  Frame -> {{True, False}, {True, False}},
  FrameStyle -> {{Blue, None}, {Black, None}}]

pt2 = Plot[80 + 50 Cos[x^2],
  {x, 1, 5},
  PlotRange -> {20, All},
  AxesOrigin -> {1, 20},
  PlotStyle -> Red,
  Frame -> {{True, False}, {True, False}},
  FrameStyle -> {{Red, None}, {Black, None}},
  FrameTicks -> {{{#,#}&/@Range[20, 140, 10], Automatic}, {Automatic, None}}]

ResourceFunction["CombinePlots"][pt1, pt2, "AxesSides" -> "TwoY"]

enter image description here

Note that the frame of pt2 is on the left side - CombinePlots automatically moves it to the right. Also note that I am converting the list of tick positions {x1, x2, ...} into a list of the form {{x1, lbl1}, {x2, lbl2}, ...} - this is necessary due to a bug in the current version of CombinePlots1. Note also that I have set the PlotRange of pt2 to {20, All} to align the 0 on the left axis with the 20 on the right one.

You can look at the documentation for details on the options and other examples. Some of the advantages of the approach used by CombinePlots are:

  • It returns a single Graphics expression without any insets, and no Overlay. This makes it easier to process the expression futher.
  • Accepts arbitrary graphics expressions, and is thus not limited to e.g. a two-axis ListPlot
  • It is fairly easy to use (that is at least the intention): Essentially, CombinePlots is Show with a few additional options for more advanced combining features

1 I have submitted a fixed version and will update this answer once it has been published. In the meantime, you can get the fixed version as ResourceFunction[CloudObject["https://www.wolframcloud.com/obj/langl/DeployedResources/Function/CombinePlots"]]

$\endgroup$
  • 1
    $\begingroup$ LukasLang: This is SOO useful! (How did I not know about this?) Thanks so very very much. ($\color{green} \checkmark$) . Wait: the height of $0$ on the left should be $20$ on the right. How to fix that? $\endgroup$ – David G. Stork Nov 22 '19 at 23:01
  • $\begingroup$ @DavidG.Stork I'm wondering the same, how can this be accepted when 20 on the right is not zero on the left, and 120 on the right is not 3 on the left? $\endgroup$ – C. E. Nov 22 '19 at 23:48
  • $\begingroup$ I accepted too quickly... but then figured it out on my own... by simply setting the PlotRanges appropriately. $\endgroup$ – David G. Stork Nov 22 '19 at 23:54
  • $\begingroup$ @DavidG.Stork ah, so you don't want the axis on the right to extend outside of the plot to the left then. That's how I interpreted the question... I was trying to get at that in my question in my other comment. Oh well. Then I will undelete my deleted answer since I suppose that solves the question as well then. $\endgroup$ – C. E. Nov 23 '19 at 0:07
  • 1
    $\begingroup$ @DavidG.Stork Setting the PlotRange is indeed the way to go - I have also updated the answer to reflect this. The reason for the behavior is that CombinePlots will only move axes, it will never change the plot range. I will think about a good way to incorporate such alignment of left/right axes positions into the functionality for a future version though. $\endgroup$ – Lukas Lang Nov 23 '19 at 9:46
5
$\begingroup$

You can do this with Inset and ImageScaled.

First, we define your left frame with a specific ImagePadding (it is arbitrary but it should be specified so we know what it is):

pt1 = Plot[
  1 + Sin[x], {x, 1, 5},
  PlotRange -> {{1, 5}, {0, 3}},
  PlotStyle -> Blue,
  Frame -> {{True, False}, {True, False}},
  FrameStyle -> {{Blue, None}, {Black, None}},
  ImagePadding -> {{20, 20}, {20, 20}}
  ]

Mathematica graphics

Now define the right frame with the same ImagePadding:

pt2 = Plot[
  80 + 50 Cos[x^2], {x, 1, 5},
  PlotRange -> {{1, 5}, {20, 130}},
  Axes -> None,
  PlotStyle -> Red,
  Frame -> {{False, True}, {False, False}},
  FrameStyle -> {{None, Red}, {Black, None}},
  FrameTicks -> {{None, Range[20, 140, 10]}, {Automatic, None}},
  ImagePadding -> {{20, 20}, {20, 20}}
  ]

Mathematica graphics

Now, since they both have the same image padding and no image margins, it should be possible to simply put one on top of the other if we make sure that they have the same size. This is where ImageScaled comes in, by using ImageScaled we're saying exactly this; that the plot that we use as overlay should have the same size as the first plot.

Show[
 pt1,
 Graphics[{
   Inset[
    pt2,
    {Center, Center},
    {Center, Center},
    ImageScaled[{1, 1}]
    ]}
  ],
  PlotRangeClipping -> False
 ]

Mathematica graphics

$\endgroup$

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.