30

Bug fixed in 13.0.0


The problem in general involves the unreliable behaviour of AbsoluteOptions when option values are implicitly specified (e.g. Automatic, All, Full, etc.), for example the graphics below clearly has a different plot range than the one reported by AbsoluteOptions:

{g = Graphics[{}, Frame -> True], AbsoluteOptions[g, PlotRange]}

enter image description here


Original example

For demonstrating the problem, have a look at the following example, and try adjusting the rotation angle a and/or the Locator position, comparing the real PlotRange of pic indicated by the frame-ticks with the one under the graph obtained by AbsoluteOptions[pic, PlotRange]:

Manipulate[
 DynamicModule[{pic},
  Column[{
    pic = Graphics[{FaceForm[], EdgeForm[Black],
       GeometricTransformation[Rectangle[], RotationTransform[a]],
       Red, Point[p]},
      Frame -> True],
    p,
    AbsoluteOptions[pic, PlotRange]
    }]
  ],
 {{a, 0}, 0, 2 Pi},
 {{p, {.1, .2}}, {-2, -2}, {2, 2}, Locator}]

Mathematica graphics

As shown in the screen capture above, in my Mathematica 9.0 on Windows 7 64-bit system, the PlotRange from AbsoluteOptions is not consistent with the real range. And the angle a seems to do nothing with it.

Additional tests in my system suggest this problem is not restricted to the presence of RotationTransform, but comes with the GeometricTransformation. And it happens not only on Graphics but also on Graphics3D.

So my questions are:

  • What is going on here?

  • How can I obtain the real PlotRange of the Graphics/Graphics3D when there are GeometricTransformations in it?

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
Silvia
  • 27,556
  • 3
  • 84
  • 164
  • 3
    I wish there was a collection with all the unexpected behaiviours of AbsoluteOptions – ssch Jan 18 '13 at 21:01
  • 2
    @ssch Hmm.. I'm not sure this is a behavior of AbsoluteOptions or GeometricTransformation. After all the latter one has records too. – Silvia Jan 18 '13 at 21:04
  • In previous versions AbsoluteOptions had several problems with PlotRange and Ticks (at least). – Dr. belisarius Jan 18 '13 at 21:34
  • @belisarius I see.. So I have an answer to my first question. What about the second one? Is there any idea on how to get the real PlotRange? – Silvia Jan 18 '13 at 21:37
  • @Silvia What does AbsoluteOptions[ListPlot[Table[Sin@x, {x, 0, 5, .05}]], PlotRange] returns in v9? – Dr. belisarius Jan 18 '13 at 21:48
  • @belisarius It's {PlotRange -> {{0., 101.}, {-0.999923, 0.999784}}} here. – Silvia Jan 18 '13 at 21:50
  • @Silvia Well, in v8 it returns {PlotRange -> {{0., 1.}, {0., 1.}}}, so I guess I any efforts done in another version will not be useful for you – Dr. belisarius Jan 18 '13 at 21:52
  • I get same as Silvia in v8.0.4 linux64 – ssch Jan 18 '13 at 21:54
  • @belisarius Thanks a lot:) It looks like I'd test the range manually by now, and wait and see if any ideas come up with from others. – Silvia Jan 18 '13 at 21:58
  • 2
    @Silvia Do you think that GeometricTransformation is relevant here? In my understanding, the problem boils down to this simple example which returns wrong values: g = Graphics[{}, Frame -> True]; Print[g]; AbsoluteOptions[g, PlotRange]. I hope you don't mind that I added this to your Q to make it more general, if so, please feel free to revert. – István Zachar Sep 10 '13 at 09:09
  • @IstvánZachar I completely agree with you. Thanks very much for the improvement! – Silvia Sep 10 '13 at 17:10
  • @belisarius V8 OS X AbsoluteOptions[ListPlot[Table[Sin@x, {x, 0, 5, .05}]], PlotRange] gives the right result. I think it is Windows that gives problems. From memory adding PlotRange->All or PlotRange->Full might fix. – Mike Honeychurch Oct 01 '13 at 22:05
  • 1
    Strongly related: http://mathematica.stackexchange.com/a/138907/280 – Alexey Popkov Mar 01 '17 at 16:50
  • 1
    Strongly related: https://mathematica.stackexchange.com/q/132755/280 – Alexey Popkov Sep 30 '17 at 02:38
  • 1
    It is sad users still need to hack their way in to get such a vital information... – Silvia Jan 04 '21 at 15:17
  • 2
    @AlexeyPopkov Thank you for the prompt edit. I feel very thankful this is fixed! :D – Silvia Dec 15 '21 at 19:56

5 Answers5

24

UPDATE

In version 13.0.0 the described long-standing bug in determining PlotRange via AbsoluteOptions is fixed.


Original answer

AbsoluteOptions is known as very buggy function and the bug in determining the true PlotRange has very long history...

You could try my Ticks-based workaround for getting the complete PlotRange (with PlotRangePadding added):

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

Manipulate[ DynamicModule[{pic}, Column[{pic = Graphics[{FaceForm[], EdgeForm[Black], GeometricTransformation[Rectangle[], RotationTransform[a]], Red, Point[p]}, Frame -> True, PlotRangePadding -> 0], p, AbsoluteOptions[pic, PlotRange], completePlotRange[pic]}]], {{a, 4}, 0, 2 Pi}, {{p, {.1, -.6}}, {-2, -2}, {2, 2}, Locator}, ContinuousAction -> False, SynchronousUpdating -> False]

screenshot

EDIT

One can get the exact PlotRange (without the PlotRangePadding added) with the following function:

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

Manipulate[ DynamicModule[{pic}, Column[{pic = Graphics[{FaceForm[], EdgeForm[Black], GeometricTransformation[Rectangle[], RotationTransform[a]], Red, Point[p]}, Frame -> True], p, AbsoluteOptions[pic, PlotRange], plotRange[pic]}]], {{a, 4}, 0, 2 Pi}, {{p, {.1, -.6}}, {-2, -2}, {2, 2}, Locator}, SynchronousUpdating -> False]

screenshot

EDIT 2

Here is timing comparison of various ways to get real PlotRange:

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

pic = Graphics[{FaceForm[], EdgeForm[Black], GeometricTransformation[Rectangle[], RotationTransform[.3]]}, Frame -> True]; Print[{#, AbsoluteTiming[ First@Table[ completePlotRange[pic, #], {100}]]}] & /@ {"RawBitmap", "BMP", "WMF", "EMF", "SVG", "PDF", "EPS"};

{RawBitmap,{2.8931655,{{-0.32158,0.981396},{-0.0250171,1.27587}}}}

{BMP,{3.0201728,{{-0.32158,0.981396},{-0.0250171,1.27587}}}}

{WMF,{4.3242473,{{-0.32158,0.981396},{-0.0250171,1.27587}}}}

{EMF,{4.0182298,{{-0.32158,0.981396},{-0.0250171,1.27587}}}}

{SVG,{3.1461800,{{-0.32158,0.981396},{-0.0250171,1.27587}}}}

{PDF,{16.9799712,{{-0.32158,0.981396},{-0.0250171,1.27587}}}}

{EPS,{7.3074179,{{-0.32158,0.981396},{-0.0250171,1.27587}}}}

AbsoluteTiming[First@Table[completePlotRange[pic], {100}]]

{2.3991372, {{-0.32158, 0.981396}, {-0.0250171, 1.27587}}}

One can see that Rasterize with ImageSize -> 0 is the fastest.

UPDATE 3

Here is purely Dynamic implementation of the same idea:

plotRange[plot : (_Graphics | _Graphics3D | _Graph)] := 
 Reap[NotebookDelete[
    First@{PrintTemporary[
       Show[plot, Axes -> True, Frame -> False, 
        Ticks -> ((Sow[{##}]; Automatic) &), 
        DisplayFunction -> Identity, PlotRangePadding -> None, 
        ImageSize -> 0]], FinishDynamic[]}]][[2, 1]]

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

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
  • Awesome, thanks! This bug has been annoying me many times, good to finally have a proper workaround. – ssch Jan 19 '13 at 00:16
  • Very nice idea +1 – Rojo Jan 19 '13 at 00:34
  • Would you expect it to work on ContourPlot[Sin[x y], {x, 0, 7}, {y, 0, 2}] // completePlotRange? – chris Jan 19 '13 at 01:03
  • @chris That gives correct PlotRange from the start, either way: Change Ticks to FrameTicks and you might want PlotRangePadding->0 as well, result will be for all sides, not just x and y – ssch Jan 19 '13 at 01:26
  • @chris Yes, it should work correctly to any Graphics although for such things as LogPlot, LogLogPlot etc. it will not respect logarithmic Ticks: for example, for LogPlot[x^x, {x, 1, 5}] it returns plot range consistent with Show[LogPlot[x^x, {x, 1, 5}], Ticks -> Automatic]. – Alexey Popkov Jan 19 '13 at 06:29
  • This is brilliant! Thanks a lot! Only a small problem for 2D cases that when a is small (say 0.1), both functions give Last[{}]. But everything seems working fine for 3D cases. – Silvia Jan 19 '13 at 16:52
  • @Silvia I think it is due to a FrontEnd <−> kernel communication conflict between Manipulate and Rasterize (the latter also uses FrontEnd). Outside of Dynamic constructs it is OK, try: Graphics[{FaceForm[],EdgeForm[Black],GeometricTransformation[Rectangle[],RotationTransform[.01]]}]//plotRange. – Alexey Popkov Jan 20 '13 at 05:50
  • @AlexeyPopkov I see. Thanks again! – Silvia Jan 20 '13 at 11:24
  • 1
    @Alexey I've extended your marvellous code with Graph functionality. I also realized that if you have Frame -> True (at least for graphs) it is necessary that the frame is turned off inside the function, so added that too. – István Zachar Sep 10 '13 at 09:41
  • @István Thanks. See also the UPDATE 3 section. Probably is could be improved further... – Alexey Popkov Sep 10 '13 at 09:58
  • 1
    @Alexey Nice, clever idea to use PrintTemporary! This is fast enough for my purpose. – István Zachar Sep 10 '13 at 12:01
  • Finally! Thanks fo reporting the v13 advancement. – István Zachar Dec 17 '21 at 11:53
13

Here's another way using hidden functions that returns the plot range + padding...

Charting`get3DPlotRange @ Graphics3D[{}]
(*
   {{-1.04167, 1.04167}, {-1.04167, 1.04167}, {-1.04167, 1.04167}}
*)


Charting`get2DPlotRange @ Plot[Sin[x], {x, 0, 6}]
(*
   {{-0.12, 6.12}, {-1.04, 1.04}}
*)

The second argument of Charting`get2DPlotRange specifies whether padding should be calculated or not. Here, padding is ignored:

Charting`get2DPlotRange[Plot[Sin[x], {x, 0, 6}], False]
(*
   {{0, 6}, {-1., 1.}}
*)

...except that Charting`get2DPlotRange doesn't work on simple Graphics[{}] -- either of the OP's examples.

Charting`get2DPlotRange@Graphics[{}]
(*
   {{-0.02, 1.02}, {-0.02, 1.02}}
*)

Charting`get2DPlotRange@
 Graphics[{FaceForm[], EdgeForm[Black], 
   GeometricTransformation[Rectangle[], RotationTransform[Pi/4]], Red,
    Point[{2, 2}]}, Frame -> True]
(*
   {{-0.02, 1.02}, {-0.02, 1.02}}
*)

But Charting`get3DPlotRange seems more reliable (so far):

SeedRandom[1];
g = Graphics3D[{Translate[Cuboid[], RandomReal[{-5, 5}, {10, 3}]]}, Axes -> True]
Charting`get3DPlotRange[g]

Mathematica graphics

(*
   {{-3.8777, 4.41753}, {-4.07619, 5.44314}, {-4.55333, 5.98243}}
*)
István Zachar
  • 47,032
  • 20
  • 143
  • 291
Michael E2
  • 235,386
  • 17
  • 334
  • 747
  • This is Great! With Alexey's method now we have full solution for any graphics! – Silvia Jan 11 '14 at 20:26
  • 4
    @MichaelE2 (+1) Note that Charting`get2DPlotRange is based only on Options and AbsoluteOptions, so we cannot expect much from it. Charting`get3DPlotRange is more interesting: it is based on MathLink`CallFrontEnd[FrontEnd`AbsoluteOptions[FrontEnd`NotebookSelection[nbobj],Graphics3DBoxOptions]]. I have made an analog for Graphics but it does not work as expected. It is clear that FrontEnd`AbsoluteOptions just does not currently support Graphics. – Alexey Popkov Jan 12 '14 at 10:26
  • @AlexeyPopkov Nice digging! – Silvia Jan 13 '14 at 16:55
9

I've enhanced my GraphicsInformation function to return both the actual and the base PlotRange. Install with:

PacletInstall[
    "GraphicsInformation",
    "Site"->"http://raw.githubusercontent.com/carlwoll/GraphicsInformation/master"
];

and load with:

<<GraphicsInformation`

Then:

GraphicsInformation @ Graphics[{}, Frame -> True]

{"ImagePadding" -> {{23., 1.5}, {17., 0.5}}, "ImageSize" -> {360., 352.463}, "PlotRangeSize" -> {335.5, 334.963}, "ImagePaddingSize" -> {24.5, 17.5}, "PlotRange" -> {{-1.04167, 1.04167}, {-1.04, 1.04}}, "BasePlotRange" -> {{-1., 1.}, {-1., 1.}}}

Another example:

GraphicsInformation @ ContourPlot[Sin[x y], {x, 0, 7}, {y, 0, 2}]

{"ImagePadding" -> {{17., 1.5}, {17., 0.5}}, "ImageSize" -> {360., 359.}, "PlotRangeSize" -> {341.5, 341.5}, "ImagePaddingSize" -> {18.5, 17.5}, "PlotRange" -> {{-0.145833, 7.14583}, {-0.0416667, 2.04167}}, "BasePlotRange" -> {{0., 7.}, {0., 2.}}}

Carl Woll
  • 130,679
  • 6
  • 243
  • 355
7

A completely stupid workaround that perhaps someone knows how to automate:

  • Create a Graphics object
    Graphics[{GeometricTransformation[Rectangle[], RotationTransform[1.]]}]
  • Right click the Graphics and select Get Coordinates
  • Drag around a bit in the graphics
  • AbsoluteOptions[< Put the object here >, PlotRange] gives the correct PlotRange

It also works by opening Drawing Tools and making a point (or anything else)

ssch
  • 16,590
  • 2
  • 53
  • 88
  • LOL Might be a nice clue :) – Silvia Jan 18 '13 at 22:34
  • Perhaps you can make an outstanding answer out of this one. Check the packets JFultz mentioned on chat: StringCases[ Flatten@MathLinkCallFrontEnd[ FrontEndNeedCurrentFrontEndSymbolsPacket[]][[1, 1, 4]], ___ ~~ "Mouse" ~~ ___] – Dr. belisarius Nov 24 '13 at 00:08
  • @belisarius Managed to crash my kernel a few times, but otherwise didn't get a reaction – ssch Nov 24 '13 at 15:08
6

I filed a bug report with Wolfram about this issue, and they said

AbsoluteOptions seems to be returning the range for PlotRange -> Automatic. I have forwarded a report of the issue to our developers, using the information you provided.

tparker
  • 1,856
  • 12
  • 22