11
$\begingroup$

Bug introduced in 7.0.1 or earlier and persists through 12.0


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}]

Mathematica graphics

Giving an explicit AspectRatio produces the output I desire:

Show[p, AspectRatio -> 1]

Mathematica graphics

A plot without the custom ticks is not distorted:

Show[p, Ticks -> Automatic]

Mathematica graphics

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.

$\endgroup$
14
  • $\begingroup$ AspectRatio::aspr: Value of option AspectRatio -> Full is not a finite positive number or Automatic. >> :D $\endgroup$ Feb 15, 2013 at 20:13
  • $\begingroup$ @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. $\endgroup$
    – Mr.Wizard
    Feb 15, 2013 at 21:00
  • $\begingroup$ Is the result of AbsoluteOptions[ListPlot[Range@5, AspectRatio -> Full], AspectRatio] on v8.0 $\endgroup$ Feb 15, 2013 at 21:33
  • $\begingroup$ 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 $\endgroup$
    – andre314
    Feb 15, 2013 at 22:55
  • 1
    $\begingroup$ 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. $\endgroup$
    – m_goldberg
    Feb 16, 2013 at 3:04

3 Answers 3

5
$\begingroup$

not a answer

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 :

enter image description here

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 :

enter image description here

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}]

enter image description here

Once again : ~2 = 300 0.007

$\endgroup$
1
  • $\begingroup$ 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. $\endgroup$
    – m_goldberg
    Feb 16, 2013 at 14:35
5
$\begingroup$

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

plot

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

screenshot

$\endgroup$
2
  • $\begingroup$ Thank you for your analysis and work-around! $\endgroup$
    – Mr.Wizard
    Jan 29, 2016 at 23:11
  • 1
    $\begingroup$ @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! :( $\endgroup$ Jan 30, 2016 at 0:32
3
$\begingroup$

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.

enter image description here

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;
x = MapThread[
        {#, #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}
  ]

enter image description here

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,
  mask,
  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}
  ]

enter image description here

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

$\endgroup$
6
  • $\begingroup$ 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. $\endgroup$
    – Mr.Wizard
    Apr 9, 2013 at 22:25
  • $\begingroup$ @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. $\endgroup$
    – jmlopez
    Apr 9, 2013 at 22:30
  • $\begingroup$ v7 still :^) -- anyway, I'm sure this will be helpful to others. $\endgroup$
    – Mr.Wizard
    Apr 9, 2013 at 22:31
  • $\begingroup$ Good to know. I just checked v8, it works fine there. $\endgroup$
    – jmlopez
    Apr 9, 2013 at 22:32
  • $\begingroup$ Belated +1 now that I can test this. $\endgroup$
    – Mr.Wizard
    Aug 19, 2014 at 17:29

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.