# How to fix vertical axis scaling across multiple plots?

I have a collection of plots, all of which have the same horizontal-axis range, but different vertical-axis ranges. These ranges differ not only in the absolute locations of their maxima (ymax) and minima (ymin), but also in the differences between these limits (ymax - ymin).

To make the plots immediately comparable, it is important that they all their vertical axes be scaled equally. By this I mean that the on-screen distance corresponding to a given vertical-axis interval is the same for all the plots. For example, if the vertical ranges for two plots are 80-110 and 150-200, all I require is that the on-screen distance between the vertical coordinates 100 and 90 (for example) in the first plot be the same as the on-screen distance between the vertical coordinates 155 and 145 (for example) in the second plot.

Is there a simple way to fix the scaling of the vertical axis?

When I have encountered this sort of trouble in the past, I have managed to get around it by using AspectRatio->Automatic, but in this case this idea does not work, because the desired transformation is one that should affect only the vertical dimension, while specifying AspectRatio->Automatic would normally affect both the height and the width.

EDIT (clarifications):

First, I apologize for not showing examples. I realize that it is somewhat irritating to read about graphics in the abstract. Unfortunately, the data I'm working with, and even the details of its representation, are proprietary (and not mine!).

The fundamental issue here is that there is no "natural origin" for the axes of the plots in question. The basic information being communicated through these plots would be the same if one redefined the origin for either axis (or for both)1. (The plots live in an "affine space", if you like.) More specifically, at the coarsest level, the information conveyed by these plots is mostly entirely visual, and is either in the form of areas (under curves) or in the form of slopes (of piecewise-linear curves). Furthermore, what matters are not the numeric values of these areas and slopes, but rather their comparison across several plots. For such comparison it is therefore essential that the scaling (the "choice of units", as it were) be the same for all the plots being compared.

Therefore, the ideal solution to this problem would be a way for me to specify, for each plot, the scaling for the vertical axis, and let Mathematica calculate everything else. I can't stress this last point enough: I want to keep to a minimum the number of parameters I need to set in order to achieve consistency of vertical-axis scaling. (That said, if it turns out that the vertical axis scaling can be specified only in conjunction with the specification of the horizontal axis scaling, as long as the two scalings are allowed to be different, then this would be fine too. In fact, this would be the most "correct" solution to the general problem of making multiple comparable plots of data living in the same 2D affine space.)

As it happens, for any collection of cases to be compared, the ranges of the horizontal axes always have the same extent2, and this somewhat fortuitous situation tends to keep the scaling of the horizontal axis sufficiently constant for my purposes. Hence, my interest in controlling the scaling of only the vertical axis is the result of this fortuitous situation.

Regarding the question of whether the plots will be displayed on a grid, the short, but somewhat inaccurate, answer would be "no". These plots are useful mostly for displaying fairly striking contrasts between various cases. This means that, as long as their scaling is controlled, they can communicate effectively even when they are not displayed side-by-side. (In other words, as long as "long-and-skinny" and "short-and-fat" represent real differences in the data being portrayed, rather than artifacts of scaling, then a comparison between a plot showing a "long-and-skinny" blob with another one two pages earlier showing some "short-and-fat" blob would still easily get the point across.)

Assuming the scaling is kept consistent, then the contrasts between the plots are, for the most part, pretty blatant. This means the benefit of a side-by-side display of these plots (e.g. in a grid) is relatively minor.

In my original post I mentioned my use of AspectRatio->Automatic to deal with analogous problems in the past, and noted this solution is not suitable in this case. Let me be more precise. Using AspectRatio to control scaling is reasonable enough when the aspect ratio of the data being presented really does matter (e.g. so that circles don't get displayed as ellipses, etc.), but in the past I've used it, in a pinch, to control scaling even in cases in which the aspect ratio information in the data is not important. A hack, in other words. As it happens, the plots I'm working with is one where the aspect ratio information is not very important3. In fact, the vertical and horizontal axes in these plots are entirely incommensurable.

1This is a slight overstatement. It would be more accurate to say that the information conveyed by the locations of these origins relative to the data being displayed is very secondary, although it is not entirely (ahem) zero, and this is the only reason for not redefining the horizontal-axis origin for all the plots to coincide with, say, the leftmost edge of the PlotRange. That said, most of the story we want to tell with these plots would still come across does not depend on the information contained in the axes.

2The relative position of the horizontal-axis origins may differ from one plot to the next, but, as stated earlier, the locations of these origins is not very significant.

3To be more precise all possible aspect ratios within a rather broad range are equally acceptable. Clearly, once aspect ratios become sufficiently extreme, all the information conveyed through slopes and areas gets essentially squashed away.

-
Regarding "... because the desired transformation is one that should affect only the vertical dimension, while specifying AspectRatio->Automatic would normally affect both the height and the width." Have you tried additionally specifying a width using ImageSize? –  Mr.Wizard Jul 21 '13 at 2:35
@Mr.Wizard: I've added some clarifications to my post, including one on the aspect ratio issue that hopefully answer your question. (The short of it is that the aspect ratio is really a bit of a red herring, and maybe I shouldn't have mentioned it at all in the first place!) –  kjo Jul 21 '13 at 16:51
I can bet this question can be squeezed into ~4 sentences and 2 graphics. Tthere is useful function RandomReal if you do not want to use someone's data. I have read it once and I do not know why Mr. Wizard's and Jens's remarks are not enough for you. Maybe I have missed something but I will not read it second time. –  Kuba Jul 21 '13 at 17:20

In addition to fixing AspectRatio -> Automatic and ImageSize, you also have to fix the ImagePadding that determines the spacing around the plot in which the tick labels are drawn. If you don't fix this space, then it will vary depending on the number of digits in your tick labels, leading to a non-constant absolute vertical axis size interval even when ImageSize is fixed.

So I would suggest something like this:

plots = Table[
Plot[x^(1 + i) + i, {x, 0, 2.5}, PlotRange -> {0, 2^(1 + i) + i},
Ticks -> {Automatic, 2 Range[10]}], {i, 1, 3}];

plotScaled[p_] := Show[p, ImagePadding -> 40, ImageMargins -> 0,
ImageSize -> 170, AspectRatio -> Automatic]

plotScaled /@ plots


Edit

In response to the updated question, I am guessing that the AspectRatio should deal with the possibility of very incommensurate horizontal and vertical axes. This can be done as follows:

plots = Table[
Plot[Log[Log[x]]^(1 + i) + i, {x, 3, Exp@Exp[2.5]},
Ticks -> {{1500, 150000}, 2 Range[20]}], {i, 1, 3}];

Clear[plotScaled];
plotScaled[p_, scale_: 1000] :=
Show[p, ImagePadding -> 40, ImageMargins -> 0, ImageSize -> 170,
AspectRatio ->
scale/(Divide @@ Subtract @@@ (PlotRange /. Options[p]))]

Map[plotScaled[#, 30000] &, plots]


Here the horizontal axis is so wide that AspectRatio->Automatic would look really bad. So instead I calculate the correct value from the PlotRange of the given plots, by multiplying the ratio of coordinate interval lengths by a scale factor that has to be common to all plots. I added scale as an optional parameter to plotScaled. Here, I have an example where I had to choose scale = 30000.

Depending on the actual plots you have, it may be necessary to replace Options[p] by AbsoluteOptions[p] in the above function.

-
+1 but how about including something about aligning the vertical axes as well, since you're good at such things. –  Mr.Wizard Jul 21 '13 at 4:46
@Mr.Wizard Thanks - I think I'd first need to ask for clarification whether the plots are supposed to be in separate cells (that's what I assumed) or displayed together in a Grid or a Row etc. If the O.P. could let me know, that would help avoid misunderstandings. –  Jens Jul 21 '13 at 4:53
@Jens: my apologies for my original post's lack of clarity. I've added an EDIT to the post with several clarifications. (As you'll see, succinctness is not my forte.) –  kjo Jul 21 '13 at 16:47
@kjo It would be nice to comment why this answer does not suit your needs insted of forcing us to read your dissertation and guess. –  Kuba Jul 21 '13 at 17:22
@kjo I edited my answer. You should really try to shorten the question so it will be more useful to others. –  Jens Jul 21 '13 at 17:57

Here is a specific example done using Presentations. (I did find I had written a convenient SEExport routine but had forgotten about it.)

<< Presentations


Here I define four sample functions.

f[1][x_] := Sin[30 x]
f[2][x_] := 3 Cos[15 x]
f[3][x_] := 98 + 2 Sin[20 x]
f[4][x_] := 100 + 3 Cos[30 x]


Here I draw them as graphics primitives. Draw is like Plot but just gives you only the primitives.

p[1] = Draw[f[1][x], {x, 0, 1}];
p[2] = Draw[f[2][x], {x, 0, 1}];
p[3] = Draw[f[3][x], {x, 0, 1}];
p[4] = Draw[f[4][x], {x, 0, 1}];


Next I extract plot size data from the primitives using a Presentations routine DrawingWidths2D. It generates the x and y widths, the center point, and the x and y, max and min values of the primitives. But we are only using the y height, min and max.

Do[{height[i], ymin[i], ymax[i]} =
Extract[DrawingWidths2D[p[i]], {{1, 2}, {3, 2, 1}, {3, 2, 2}}], {i,
4}]


The extracted values were:

Table[{height[i], ymin[i], ymax[i]}, {i, 4}]
{{2., -1., 0.999999}, {6., -3., 3.}, {4., 96., 100.}, {6., 97., 103.}}


We are going to stack the curves up, all on the same piece of paper. We will put a spacing of delP (on the paper) between each of the curves. The parameter currentP will be the current vertical location on the paper. We need to calculate the upward shift, and the paper pmin and pmax y values for each plot.

Module[{delP = 1, currentP = 0},
Do[shift[i] = -ymin[i] + currentP;
pmin[i] = currentP;
pmax[i] = pmin[i] + height[i];
currentP = pmax[i] + delP, {i, 4}]
]


These are the values:

Table[{shift[i], pmin[i], pmax[i]}, {i, 4}]
{{1., 0, 2.}, {6., 3., 9.}, {-86., 10., 14.}, {-82., 15., 21.}}


Now we can draw the graphic. Draw2D is more or less like the Mathematica Graphics statement but with some default options. TranslateOp is just a rewriting of the Mathematica Translate routine so it can be easily used as a postfix operator. XTickLine and YTickLine are Presentation routines to draw free standing scales. When composing a graphic like this I would usually put Frame -> True during development so we can see where things are on the paper.

Draw2D[
{Table[p[i] // TranslateOp[{0, shift[i]}], {i, 4}],
XTickLine[{0, 1, -0.5}, {0, 1}, {0, 1, 0.25}, 5],
Table[YTickLine[{pmin[i], pmax[i], -0.015}, {ymin[i],
ymax[i]}, {ymin[i], ymax[i], height[i]}, 2,
YNumberFunction -> (Round[#, 0.1] &)], {i, 4}],
Text[Style["Vertically Aligned Plots", 14, Bold], {0.5, 23}],
Table[Text[
Style["Case " <> ToString[i], 12,
Bold], {1.02, (pmin[i] + pmax[i])/2}, {-1, 0}], {i, 4}]},
AspectRatio -> 1,
Frame -> False,
PlotRange -> {{-0.1, 1.2}, {-2, 24}},
BaseStyle -> {FontSize -> 12},
ImageSize -> 400] // Framed
`

Notice that we create the graphic just by drawing one thing after another. Everything is a graphics primitive so there is no necessity for Epilog or Graphics level jumping. Also notice that all the graphics options that affect the overall look are placed at the end and only options that affect the creation of specific primitives are placed in their statements. With the Show statement users often put overall graphics options into the individual plots and then get caught up on how they are picked up. Finally because the WRI method of composing custom graphics is rather arcane and lacks some useful routines users get diverted into considerations that are removed from the actual task at hand.

I don't know if this is precisely what kjo was looking for but I'll wager it is easily adaptable to what he wants.

-

As I understand it you would like to have a number of plots stacked vertically, all with the same horizontal axis but with different vertical axes. However you would like to control the scale of the vertical axes and in particular have them all the same scale but different ranges.

This is something that is fairly easy to do with the Presentations package, which I sell for \$50. There are some similar examples in PDF form, which you can see at:

http://www.st-andrews.ac.uk/~pl10/c/djmpark/

You could look at Broken-axis Graph2 and Stock Data Chart. They are not exactly like your case but they use the same technique.

The technique is this: Instead of trying to use the Mathematica Frame or Axis facilities draw the various graphics on a "plain piece of paper". Then scale (if necessary) and shift each item upward to it's proper place on the paper. Then use the Presentations YTickScale to provide a custom y scale for each of the items. You could use XTickScale to provide a single x scale, probably at the bottom. (You could provide an x scale for each item if you wanted, but less is better if it will all fit on a page.)

This way you have absolute control over the placement, scale and range of each plot item. You can also place Text at any convenient place on the "piece of paper".

Also Presentations has built-in postfix commands for scaling and shifting that are easy to use.

-