# Distorted graphics when using custom ticks and AspectRatio -> Full

Bug introduced in 7.0.1 or earlier and partially persists through 10.4 or later

I am having a problem using custom tick specifications along with AspectRatio -> Full.
The problem affects both Ticks and FrameTicks.

An example of the problem:

x = MapThread[{#, #2, {0, 0.007}} &, {100 Range@5, {"One","Two","Three","Four","Five"}}];

p = ListPlot[Array[Log, 600], Ticks -> {x}, AspectRatio -> Full, ImageSize -> {250, 250}]


Giving an explicit AspectRatio produces the output I desire:

Show[p, AspectRatio -> 1]


A plot without the custom ticks is not distorted:

Show[p, Ticks -> Automatic]


I wish to understand what is causing the problem and find a way to work around it besides specifying a numeric AspectRatio. I could add a routine to calculate the aspect ratio from the image size but I would rather find a way to make AspectRatio -> Full work as intended.

-
AspectRatio::aspr: Value of option AspectRatio -> Full is not a finite positive number or Automatic. >> :D – Dr. belisarius Feb 15 '13 at 20:13
@belisarius Are you saying this has been removed in later versions? v7 help says: AspectRatio->Full specifies that a graphic should be stretched so as to fill out its enclosing region in a Grid or related construct. – Mr.Wizard Feb 15 '13 at 21:00
Is the result of AbsoluteOptions[ListPlot[Range@5, AspectRatio -> Full], AspectRatio] on v8.0 – Dr. belisarius Feb 15 '13 at 21:33
On my computer, the tick height is proportional to the horizontal PlotRange. The formula seems to be "absolute tick height" = "horizontal PlotRange" X "specified tick height". This does not correspond to what I see on your first graphics – andre Feb 15 '13 at 22:55
I'm running v9.0.1 on OS X 10.6.8. I get the same output as you (@Mr.Wizard) do when I evaluate your code. – m_goldberg Feb 16 '13 at 3:04

With my Mathematica 8.0.4 on Win 7 your code :

x = MapThread[{#, #2, {0, 0.007}} &, {100 Range@5, {"One", "Two",
"Three", "Four", "Five"}}];

p = ListPlot[Array[Log, 600], Ticks -> {x}, AspectRatio -> Full,
ImageSize -> {250, 250}]


gives :

This graphics is not exactly the same as yours

One can see that the ticks height is ~ -4, in accordance with the formula : "absolute tick height" = "horizontal PlotRange" X "specified tick height"

here : ~4 = 600 0.007

If I try another PlotRange :

x = MapThread[{#, #2, {0, 0.007}} &, {100 Range@5, {"One", "Two",
"Three", "Four", "Five"}}];

p = ListPlot[Array[Log, 600], Ticks -> {x}, AspectRatio -> Full,
ImageSize -> {250, 250}, PlotRange -> {{1, 300}, Automatic}]


I get :

corresponding to 2 = 300 0.007

etc ...

Note

It is easier to play with "positive" ticks and it doesn't change the problem. Example :

x = MapThread[{#, #2, (* here is the difference --> *) {0.007, 0}} &, {100 Range@5, {"One", "Two",
"Three", "Four", "Five"}}];

p = ListPlot[Array[Log, 600], Ticks -> {x}, AspectRatio -> Full,
ImageSize -> {250, 250}, PlotRange -> {{1, 300}, Automatic}]


Once again : ~2 = 300 0.007

-
What you show here is what I see with v.9.0.1 on OS X 10.6.8 with one difference: I see the y-axis always positioned at x = 0. The ticks marks, however, are exactly the same as you show. – m_goldberg Feb 16 '13 at 14:35

I believe andre already answered the question. I'm only providing this as a reference to myself and others who might need to specify the ticks while having AspectRatio -> Full.

The reason I set AspectRatio to Full is so that I can have more control of the final figure. To try to fix Mr. Wizards problem we can remove the tick length information.

x = MapThread[{#, #2} &, {100 Range@5, {"One", "Two", "Three", "Four", "Five"}}];
p = Framed@ListPlot[Array[Log, 600], Ticks -> {x}, AspectRatio -> Full, ImageSize -> {250, 250}]


This gives us a plot with inverted tick marks.

The key part with the ticks is the information that andre provided:

"absolute tick height" = "horizontal PlotRange" X "specified tick height"

To create the figure we can do:

basicPlot = ListPlot[Array[Log, 600]];
xlen = First@Differences@AbsoluteOptions[basicPlot, PlotRange][[1, 2, 1]];
tickLength = .1;
{#, #2, {0, tickLength/xlen}} &,
{100 Range@5, {"One", "Two", "Three", "Four", "Five"}}
];


tickLength is set to .1, in units of the actual plot. This should be changed to the desired length of the ticks.

As a bonus I like to specify where I wish to place my x and y labels:

xLabel[x_, pos_] := Graphics[{
Text[Style[x, 10, FontFamily -> "Arial"], pos, {0, 1}]
}];
yLabel[x_, pos_] := Graphics[{
Text[Style[x, 10, FontFamily -> "Arial"], pos, {0, 1}, {0, 1}]
}];


Now we draw the final plot:

Framed@Show[
basicPlot,
xLabel["X Label", {300, -1}],
yLabel["Y Label", {-100, 3}],
AspectRatio -> Full,
Axes -> None,
FrameTicks -> {x, Automatic, None, None},
Frame -> {True, True, True, True},
ImagePadding -> {{45, 5}, {45, 5}},
PlotRangeClipping -> False,
ImageSize -> {250, 250}
]


Notice how I use ImagePadding to control how much space I will leave for my labels and where the figure will be placed in the space specified by ImageSize. PlotRangeClipping needs to be set to False so that the labels can be displayed. If you need to clip then mask it.

mask = Graphics[{
Gray,
Polygon[{
ImageScaled[{0, 0}],
ImageScaled[{0, 1}],
ImageScaled[{1, 1}],
ImageScaled[{1, 0}],
ImageScaled[{0, 0}],
Scaled[{0, 0}],
Scaled[{1, 0}],
Scaled[{1, 1}],
Scaled[{0, 1}],
Scaled[{0, 0}],
ImageScaled[{0, 0}]
}]
}];
Framed@Show[
basicPlot,
xLabel["X Label", {300, -1}],
yLabel["Y Label", {-100, 3}],
AspectRatio -> Full,
Axes -> None,
FrameTicks -> {x, Automatic, None, None},
Frame -> {True, True, True, True},
ImagePadding -> {{45, 5}, {45, 5}},
PlotRangeClipping -> False,
ImageSize -> {250, 250}
]


In mask change the color to White. Once you are done with the final edits to the figure remove the outside frame.

-
Thanks for the answer! Sadly, for your "final plot" I get this: i.stack.imgur.com/T4vkp.png Apparently some part of this bug has been fixed in later versions, but a form of it still remains. – Mr.Wizard Apr 9 '13 at 22:25
@Mr.Wizard, what mma version are you using? So, just to be clear the above output was for mma 9.0.1. I still have mm8, I'll try it there. – jmlopez Apr 9 '13 at 22:30
v7 still :^) -- anyway, I'm sure this will be helpful to others. – Mr.Wizard Apr 9 '13 at 22:31
Good to know. I just checked v8, it works fine there. – jmlopez Apr 9 '13 at 22:32
Belated +1 now that I can test this. – Mr.Wizard Aug 19 '14 at 17:29

## A workaround

This long-term bug is a constant source of pain for Mathematica users for many years. After years of customization of plots "by hands" I have figured out what happens and developed a general approach which allows to get the expected output with as little pain as possible. It was even necessary to develop special technical vocabulary in order to avoid ambiguity.

### Definitions:

• plot range term here means - the complete plotting range always bounded by Frame (even when the Frame is not shown) which includes PlotRange and PlotRangePadding but doesn't include ImagePadding and ImageMargins

• internalWidth and internalHeight - correspondingly width and height of the plot range in the units of the intrinsic coordinate system of the plot

• printerPointsWidth and printerPointsHeight - correspondingly the actual width and height of the plot range in priter's points (or pixels - depending on the final export format)

• xResolution - how many priter's points correspond to the unit horizontal distance in the intrinsic coordinate system of the plot

• yResolution - how many priter's points correspond to the unit vertical distance in the intrinsic coordinate system of the plot

• xTickLength and yTickLength - correspondingly the tick mark length specified for horizontal and vertical axis in the custom ticks specification

• xTickPointsLength and yTickPointsLength - correspondingly the actual tick mark length in printer's points obtained for horizontal and vertical axis

• c - empirical constant approximately equal to 1.16

Basic relationships:

xResolution = printerPointsWidth/internalWidth

yResolution = printerPointsHeight/internalHeight


With andre's hint I found the mathematical formulae which currently (checked with versions 8.0.4 and 10.3.1) determine the final tick mark lengths in the case of AspectRatio -> Full both for vertical and horizontal axes:

xTickPointsLength = xTickLength*yResolution*internalWidth*c

yTickPointsLength = yTickLength*xResolution*internalWidth*c


These formulae are valid only when AspectRatio -> Full and only for custom tick marks specifications.

### Code

Assuming that plot is our plot, internalWidth and internalHeight can be obtained using my completePlotRange function in the following way:

{internalWidth, internalHeight} = -Subtract @@@ completePlotRange[plot]


printerPointsWidth and printerPointsHeight can be obtained using the printerPointsPlotRange function developed by user LLlAMnYP:

{printerPointsWidth, printerPointsHeight} = printerPointsPlotRange[plot]


Now if we want our tick marks to be of length 6 printer's points, we can proceed in the following way:

targetPointsLength = 6;
c = 1.16;

xTickLength = targetPointsLength/(yResolution*internalWidth*c)
yTickLength = targetPointsLength/(xResolution*internalWidth*c)


### Self-contained example

completePlotRange[plot:(_Graphics|_Graphics3D|_Graph)] :=
Last@
Last@Reap[
Rasterize[
Show[plot, Axes -> True, Frame -> False, Ticks -> ((Sow[{##}]; Automatic) &),
DisplayFunction -> Identity, ImageSize -> 0], ImageResolution -> 1]];

printerPointsPlotRange =
(#[[2]] - #[[1]] &)@
(Rasterize[Show[#, Epilog ->
{Annotation[Rectangle[Scaled[{0, 0}], Scaled[{1, 1}]],
"Two", "Region"]}], "Regions"][[-1, 2]]) &;

plot = ListPlot[Array[Log, 600], AspectRatio -> Full, ImageSize -> {250, 250}];

{internalWidth, internalHeight} = -Subtract @@@ completePlotRange[plot]
{printerPointsWidth, printerPointsHeight} = printerPointsPlotRange[plot]
{xResolution,
yResolution} = {printerPointsWidth, printerPointsHeight}/{internalWidth, internalHeight}
targetPointsLength = 6;
c = 1.16;
xTickLength = targetPointsLength/(yResolution*internalWidth*c)
yTickLength = targetPointsLength/(xResolution*internalWidth*c)

xTicks = MapThread[{#, #2, {0, xTickLength}} &, {100 Range@5, {"One", "Two", "Three",
"Four", "Five"}}];
yTicks = MapThread[{#, #2, {0, yTickLength}} &, {
Range@6, {"One", "Two", "Three", "Four", "Five", "Six"}}];
Show[plot, Ticks -> {xTicks, yTicks}]

{625., 6.87842}

{240.625, 237.245}

{0.385, 34.4912}

0.000239941

0.0214957


Now open this figure in MS Paint and ensure that tick marks indeed have length 6 pixels:

-
Thank you for your analysis and work-around! – Mr.Wizard Jan 29 at 23:11
@Mr.Wizard I face the Ticks problem for many years regularly. But without the linked contribution by LLlAMnYP it was not solvable. It is a great shame that such a basic task requires such a huge collective effort to be solved! Even worse, the solution is so ridiculously complicated and hackish! :( – Alexey Popkov Jan 30 at 0:32