16
$\begingroup$

Consider the following plot:

enter image description here

The line to the top right corner of the inset is only approximate (found by trial and error). How can I determine the location of that corner? More generally, how can I determine the inset bounding box in graph coordinates?

The code to produce the plot follows:

{r, b} = {-0.25, 0.5}; (* right, bottom of inset *)
{ll, ur} = {{-0.03, 0}, {0.03, 1.1}}; (* rectangle bounds *)
f = Function[x,-Sin[1/(.08 + Abs[x])]];  (* function to plot *)
ginset = Plot[f[x], {x, First@ll, First@ur}, 
  PlotRange -> {Last@ll, Last@ur},
  Axes -> False, Frame -> True, FrameTicks -> None]
gmain = Plot[f[x], {x, -0.5, 0.5},
  PlotRange -> All, Ticks -> None, PlotStyle -> Black,
  Prolog -> {
    Inset[ginset, {r, b}, {Right, Bottom}, 0.25,Background -> Lighter[Gray, 0.95]],
    {FaceForm[Lighter[Gray, 0.95]], EdgeForm[LightGray],Rectangle[ll, ur]},
    {Gray, Dashed, Line[{{r, b}, ll}]},
    {Gray, Dashed, Line[{{r, 1.05}, {First@ll, Last@ur}}]} (* the problem *)
    }]
$\endgroup$
4
  • $\begingroup$ Just for clarification, the problem on the line tagged (* the problem *) is the 1.05 chilling there? $\endgroup$
    – b3m2a1
    Commented Mar 15, 2017 at 4:11
  • $\begingroup$ @MB1965 That is correct! $\endgroup$
    – Alan
    Commented Mar 15, 2017 at 15:15
  • $\begingroup$ Then I think the best solution is really what either Alexey or Kuba posted. I tried to compute that value but fell into a real rabbit hole where what I thought should work didn't. It's harder to post process than to slightly reformat using Scaleds and Offsets and whatnot. $\endgroup$
    – b3m2a1
    Commented Mar 15, 2017 at 15:17
  • $\begingroup$ @MB1965 Yes, that seems to be the conclusion. The two answers you mention both look very good, subject to that constraint. Not sure how to choose between them. $\endgroup$
    – Alan
    Commented Mar 15, 2017 at 15:51

4 Answers 4

14
$\begingroup$
{ll, ur} = {{-0.03, 0}, {0.03, 1.1}}; 
f = Function[x, -Sin[1/(.08 + Abs[x])]];

inset = Plot[f[x], {x, First@ll, First@ur}, 
   PlotRange -> Last /@ {ll, ur}, Axes -> False, Frame -> True, 
   FrameTicks -> None, PlotStyle -> Red, 
   ImageSize -> 100 {1, 1/GoldenRatio}];

plot = Plot[f[x], {x, -0.5, 0.5}, PlotRange -> All, Ticks -> None, 
   PlotStyle -> Black];

Undocumented way

Szabolcs's got me interested in DynamicLocation and other features used behind the scenes for Graphs visualization. See more in:

Framework behind Graph plots. DynamicNamespace and friends

It is exactly what we need, just keep in mind that this is not documented and may change in a future.

gmain = Show[  plot
, Prolog -> DynamicNamespace @ {
    DynamicName[
      Inset[inset, Scaled[{0, 1}], {Left, Top}]
    , "zoom"
    ]
  , { FaceForm @ None, EdgeForm @ Gray
    , DynamicName[ Rectangle[ll, ur], "source"]
    }
  , { Gray, Dashed
    , Line[{
          DynamicLocation["source", Automatic, {Left, Top}]
        , DynamicLocation["zoom", Automatic, {Right, Top}]
      }]
    , Line[{
          DynamicLocation["source", Automatic, {Left, Bottom}]
        , DynamicLocation["zoom", Automatic, {Right, Bottom}]
      }]
    }
 }]

enter image description here

Documented way

You only need to assume inset content size, the rest can be done with Scaled coordinates and Offset:

insetSize = 100 {1, 1/GoldenRatio};

gmain = Show[plot        
  , Prolog -> {
        Inset[
            Show[inset, ImageSize -> insetSize]
          , Scaled[{0, 1}]
          , {Left, Top}              
        ]
      , { FaceForm @ None, Rectangle[ll, ur] }
      , { Gray, Dashed 
        , Line[{
              ll
            , Offset[{1, -1} insetSize, Scaled[{0, 1}]]
          }]
        }
      , { Gray, Dashed
        , Line[{
              {First @ ll, Last @ ur}
            , Offset[{1, 0} insetSize, Scaled[{0, 1}]]
          }]
        }
      }
]
$\endgroup$
2
  • 1
    $\begingroup$ This is an elegant approach; thank you. As suggested by Alexey's answer, you need to add AspectRatio->Full to your Show for it to work correctly. (This problem is hidden in your answer because you chose the default aspect ratio.) $\endgroup$
    – Alan
    Commented Mar 16, 2017 at 19:42
  • $\begingroup$ @Alan good point $\endgroup$
    – Kuba
    Commented Mar 16, 2017 at 20:20
8
$\begingroup$

Obtaining Inset's bounding box in intrinsic plot coordinates

The key: specify both its size and position in the plot coordinates

When size of Inset is specified as a pair of numbers they are taken in the units of the intrinsic coordinate system of the enclosing graphics. This useful property isn't mentioned under the Details and Options section on the Docs page for Inset, but it is described under the Examples ► Scope ► Sizes sub-subsection as the third and fourth examples:

screenshot

When everything related to Inset's placement and size is specified in the ordinary plotting coordinates, its bounding box in the intrinsic coordinate system of the enclosing graphics can be obtained with easy:

(* Setup *)
(* {right,bottom} of inset in the coordinate system of the enclosing graphics *)
{iRBx, iRBy} = {-0.25, 0.5};
(* inset's own plotting range *)
{{xMin, xMax}, {yMin, yMax}} = {{-0.03, 0.03}, {0, 1.1}};
(* inset's width and height in the coordinate system of the enclosing graphics *)
{iWΔx, iHΔy} = {.2, .5};
(* function to plot *)
f = Function[x, -Sin[1/(.08 + Abs[x])]];

(* Inset's bounding box in the coordinate system of the enclosing graphics *)
iBoundingBox = {{iRBx - iWΔx, iRBy}, {iRBx, iRBy + iHΔy}};

(* Plotting *)
gInset = Plot[f[x], {x, xMin, xMax}, PlotRange -> {{xMin, xMax}, {yMin, yMax}}, 
   Axes -> False, AspectRatio -> Full, ImagePadding -> None];
gMain = Plot[f[x], {x, -0.5, 0.5}, PlotRange -> All, ImageSize -> 360, PlotStyle -> Black,
   Ticks -> False, AxesStyle -> Gray, Prolog -> {
    {Gray, Dashed, CapForm["Round"], 
     Line[{{{xMin, yMin}, {iRBx, iRBy}}, {{xMin, yMax}, {iRBx, iRBy + iHΔy}}}]},
    {FaceForm[Lighter[Gray, 0.95]], EdgeForm[LightGray], 
     Rectangle @@@ {iBoundingBox, {{xMin, yMin}, {xMax, yMax}}}},
    Inset[gInset, {iRBx, iRBy}, {xMax, yMin}, {iWΔx, iHΔy}]}]

plot

$\endgroup$
1
  • $\begingroup$ This was very useful. I ended up relying more on Kuba's answer, but I used elements of yours as well. Thanks! $\endgroup$
    – Alan
    Commented Mar 16, 2017 at 22:34
5
$\begingroup$

Here is how I would approach this:

(*  Setup  *)
(* right, bottom of inset in the coordinate system of the enclosing graphics *)
iRightBottom = {-0.25, 0.5};
(* inset's own plotting range *)
{{xMin, xMax}, {yMin, yMax}} = {{-0.03, 0.03}, {0, 1.1}};
(* inset width and height *)
{iW, iH} = {90, 56};
(* function to plot *)
f = Function[x, -Sin[1/(.08 + Abs[x])]];

(* Plotting *)
gInset = Plot[f[x], {x, xMin, xMax}, PlotRange -> {{xMin, xMax}, {yMin, yMax}}, 
   PlotRangePadding -> None, PlotRangeClipping -> True, Axes -> False, 
   ImageSize -> {iW, iH}, AspectRatio -> Full, 
   Prolog -> {FaceForm[Lighter[Gray, 0.95]], EdgeForm[LightGray], 
     Rectangle[{xMin, yMin}, {xMax, yMax}]}];
gMain = Plot[f[x], {x, -0.5, 0.5}, PlotRange -> All, Ticks -> None, PlotStyle -> Black, 
  AxesStyle -> Gray, 
  Prolog -> {Inset[gInset, iRightBottom, {xMax, yMin}], 
    {FaceForm[Lighter[Gray, 0.95]], EdgeForm[LightGray], 
     Rectangle[{xMin, yMin}, {xMax, yMax}]}, 
    {Gray, Dashed, CapForm["Round"], 
     Line[{{{xMin, yMin}, iRightBottom}, {{xMin, yMax}, Offset[{0, iH}, iRightBottom]}}]}}]

plot

As one can see, everything is almost perfect on the raster image generated by the FrontEnd excepting the frame of the inset which is clipped on the right side. It is possible to cure it by adding ImagePadding (or PlotRangePadding) to the inset and then changing the coordinates of the lines accordingly. But I would prefer to Export the plot as PDF and then use Adobe Acrobat or other PDF viewer for producing a raster image. This method has many advantages:

  • Much more precise rendering of graphical primitives than what Mathematica's FrontEnd offers (FrontEnd rounds everything to screen pixels).

  • You can specify any resolution you want, and the plot won't change with resolution!

  • Mathematica's FrontEnd takes a HUGE amount of memory when it renders graphics with high resolution and it's maximum resolution is limited [1,2]. To the contrary, Adobe Acrobat takes a reasonable amount of memory when saving PDF as PNG image and virtually has no limit on resolution.

And do not forget that you can Export your plot directly into a resolution-independent vector format: PDF, EPS, SVG and EMF are available! Many scientific journals prefer vector formats for figures and any modern desktop publishing software supports a subset of these formats.

$\endgroup$
7
  • $\begingroup$ But how can we go from an inset spec to its graphics coordinates. I thought it was simply a matter of taking the percentage of the x-range specified multiplying it by the total image width, multiplying by the inset aspect ratio, dividing by the image height and then multiplying by the y-range specified. Then those image sizes are actually 1/aspect ratio of image. But for some reason this doesn't give the right answer. And ideas? $\endgroup$
    – b3m2a1
    Commented Mar 15, 2017 at 7:04
  • $\begingroup$ @MB1965 As you can see from my answer, I avoided this problem by using Offset coordinate for the top, right of the inset. $\endgroup$ Commented Mar 15, 2017 at 7:07
  • $\begingroup$ I noticed that and that's surely the proper way to do this. I was just interested in terms of actually being able to determine these Inset coordinates exactly. $\endgroup$
    – b3m2a1
    Commented Mar 15, 2017 at 7:09
  • $\begingroup$ @MB1965 They depend on the ImageSize of the enclosing graphics by default. We have several possibilities: 1) make inset size Scaled in order to avoid this problem; 2) fix ImageSize of the enclosing graphics and calculate the correspondence between pixel coordinates and plot range coordinates. The latter way is more difficult, but everything what is needed is already developed in this thread: "How to align coordinate systems of Inset and enclosing Graphics?" $\endgroup$ Commented Mar 15, 2017 at 7:15
  • $\begingroup$ @MB1965 And finally there is third possibility: 3) avoid using Inset and use GeometricTransformation directly on the primitives extracted from gInset. $\endgroup$ Commented Mar 15, 2017 at 7:15
4
$\begingroup$

Edit

This doesn't work in general. Clearly I have the wrong idea. I'll deal with this when I have the time.

Notably, Alexey Popkov gets divergent results on Windows and if I change the AspectRatio of the enclosing plot we get results that aren't right. Suggests the ratio of aspect ratios is wrong.

Maybe someone else can take this and do it right.

Heres the code all in one block, too:

{r, b} = {-0.25, 0.5};(*right,bottom of inset*)
{ll, 
  ur} = {{-0.03, 0}, {0.03, 
   1.1}};(*rectangle bounds*)
w = .25;(*inset width*)
f = 
 Function[x, -Sin[1/(.08 + Abs[x])]];(*function to plot*)
ginset =
 Plot[f[x], {x, First@ll, First@ur},
  PlotRange -> {Last@ll, Last@ur},
  Axes -> False, Frame -> True, FrameTicks -> None];
gmain =
  Plot[f[x],
   {x, -0.5, 0.5}, PlotRange -> All,
   Ticks -> None, PlotStyle -> Black];
{ar, iar} =
  AspectRatio /. Options[#, AspectRatio] & /@ {ginset, gmain};
{prx, pry} = PlotRange@gmain;
{ppx, ppy} =
  MapThread[
   Replace[{
      {{Scaled[p1_], Scaled[p2_]}, v_} :> (p1 + p2)*
        EuclideanDistance @@ v,
      {Scaled[p_], v_} :> p*EuclideanDistance @@ v,
      {p_, _} :> p
      }]@*List, {
    PlotRangePadding /. Options[gmain, PlotRangePadding],
    {prx, pry}
    }];
h =
  w*ar/iar*
   Divide @@
    (EuclideanDistance @@@ {pry, prx} + {ppy, ppx});
Show[
 gmain,
 Prolog -> {
   Inset[ginset, {r, b}, {Right, Bottom}, w,
    Background -> Lighter[Gray, 0.95]],
   {FaceForm[Lighter[Gray, 0.95]], EdgeForm[LightGray], 
    Rectangle[ll, ur]}, {Gray, Dashed, Line[{{r, b}, ll}]},
   {Gray, Dashed, Line[{{r, b + h}, {First@ll, Last@ur}}
     ]} 
   },(*
 AspectRatio\[Rule]2,*)
 ImageSize -> 150
 ]

Original posting

So I think this should do it for you.

We'll start with our basic plot set up, more or less as you had it except without that Prolog and with a w for the Inset width:

{r, b} = {-0.25, 0.5};(*right,bottom of inset*)
{ll, ur} = {{-0.03, 0}, {0.03, 1.1}};(*rectangle bounds*)
w = .25;(*inset width*)
f = Function[x, -Sin[1/(.08 + Abs[x])]];(*function to plot*)
ginset =
 Plot[f[x], {x, First@ll, First@ur},
  PlotRange -> {Last@ll, Last@ur},
  Axes -> False, Frame -> True, FrameTicks -> None];
gmain =
  Plot[f[x],
   {x, -0.5, 0.5}, PlotRange -> All,
   Ticks -> None, PlotStyle -> Black];

Then find various plot properties we'll need. Here I get the aspect ratios of both the inset plot and the primary plot as well as what will become the total plot range of the main plot (the padding adjustment is necessary):

{ar, iar} =
  AspectRatio /. Options[#, AspectRatio] & /@ {ginset, gmain};
{prx, pry} = PlotRange@gmain;
{ppx, ppy} =
  MapThread[
   Replace[{
      {{Scaled[p1_], Scaled[p2_]}, v_} :> (p1 + p2)*
        EuclideanDistance @@ v,
      {Scaled[p_], v_} :> p*EuclideanDistance @@ v,
      {p_, _} :> p
      }]@*List, {
    PlotRangePadding /. Options[gmain, PlotRangePadding],
    {prx, pry}
    }];

Then we can compute the Inset height from the ratio of the AspectRatios and the ratio of plot x range and plot y range in the main plot:

h =
  w*ar/iar*
   Divide @@
    (EuclideanDistance @@@ {pry, prx} + {ppy, ppx});

Then apply show on this:

Show[
 gmain,
 Prolog -> {
   Inset[ginset, {r, b}, {Right, Bottom}, w,
    Background -> Lighter[Gray, 0.95]],
   {FaceForm[Lighter[Gray, 0.95]], EdgeForm[LightGray], 
    Rectangle[ll, ur]}, {Gray, Dashed, Line[{{r, b}, ll}]},
   {Gray, Dashed, Line[{{r, b + h}, {First@ll, Last@ur}}
     ]} 
   }
 ]

plot

And if we try a bunch of numeric (this is crucial as I do no type checking to adjust for symbolics) AspectRatios, in this case for {1./GoldenRatio, 1/3, 2/3}:

examples

Seems to work.

Dunno if there's an easier way, but I think this'll do for you at least.

$\endgroup$
2
  • $\begingroup$ I get this with the code from your answer and Mathematica 11.0.1 on Win7 x64. $\endgroup$ Commented Mar 15, 2017 at 5:28
  • $\begingroup$ @AlexeyPopkov never mind then. I wonder what I left out. $\endgroup$
    – b3m2a1
    Commented Mar 15, 2017 at 5:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.