7
$\begingroup$

TL;DR warning! My question is not about getting the length of the ticks correct (although if someone has good suggestions, that would be welcomed). My question is about constructing functions that have "awareness" of where they are called from. To prevent confusion you can skip down to the last one or two paragraphs. The wall of text in the middle provides some context (no pun intended).

Begin TL;DR stuff

The need for such a thing came to me, when I was making a bunch of figures for a paper and realized, that while many plots may be of very different sizes, their tick labels, axes labels and so on all need to be identical. What also needed to be identical across all plots, was the length of the ticks. Which seems like not a great deal, especially when using the CustomTicks package, except

Tick mark lengths are given as a fraction of the distance across the whole plot.

To be absolutely precise, they are given as a fraction of the width of the plot (meaning just the plot range, not the padding), the height of the plot doesn't matter. As much as I looked, I couldn't find any way to specify the length of the ticks in absolute units (e.g. printer points). Of course, an option would be to specify tick length as 2/plotwidth, where plotwidth means what it sounds like (in units of printer points). That would get me a tick with a length of two printer points. That would also cause some annoying overhead, where I would have to calculate the width of the PlotRange of every plot I make in printer points, so I decided to try and automate this a bit. First I wrote a function that gets me the dimensions of the plot in printer points, e.g.:

g=Plot[Sin[x],{x,0,10},Frame->True,ImagePadding->50,ImageSize->{600,360},AspectRatio->Full];
GetGeometry[g]

This would return something like

{
(*some other values*)
"PlotRangeSize"->{500,260},
(*some other values*)
}

So now for any plot I could just type plotwidth=("PlotRangeSize"/.GetGeometry[plot]) and get the width needed for the tick size specification. After that I wrote a wrapper for the LinTicks function from the CustomTicks package that would take another argument with the width of the plot range, and call LinTicks with an appropriately adjusted TickLengthScale option to ensure that the ticks would be of a specific size in absolute units. It worked something like this:

AbsoluteLinTicks[1,2,TickPointSize->2][500]=LinTicks[1,2,TickLengthScale->2*100/500]

The value of the TickLengthScale option would be what it is because I want a major tick 2 printer points long, I assume the PlotRange to be 500 points wide and the 100 comes from the default length of a major tick in LinTicks being 0.01. Unfortunately, this still doesn't solve my problem, as I still have to run the GetGeometry command every time, insert the plot width into every call of my wrapper function by hand, etc. etc.

End TL;DR stuff

So now I actually come to my question. Is it possible to construct such a function in Mathematica, that would be aware of the context in which it is called? By context I mean not the contexts in Mathematica, such as Global, System, etc. Here I mean context in the more normal sense, as in a function that would be "aware" that it is called from within Plot, for example. Here a short cocktail of Mathematica + plain english to illustrate what I mean:

Plot[Sin[x],{x,0,10},Ticks->AbsoluteLinTicks[TickPointSize->5]]
(* Plot starts to run and as per usual, passes the arguments 0 and 10 to AbsoluteLinTicks *)
(* AbsoluteLinTicks is called by the Plot function *)
(* AbsoluteLinTicks realizes, that it is being called from within Plot and starts to execute something like the following code *)
Module[{g=Plot[Sin[x],{x,0,10}],plotwidth},
plotwidth=("PlotRangeSize"/.GetGeometry[g])[[1]];
LinTicks[0,10,TickLengthScale->5*100/plotwidth]]
(* Finally we get the result which looks like *)
Plot[Sin[x],{x,0,10},Ticks->(*output of the module above*)]

Would it be possible to convert the stuff I gave commented out in plain english to MMA code? Because the workaround I have at the moment amounts to using a wrapper function for all the 2D plotting functions, which parses their options and modifies them appropriately, before calling the plotting functions themselves. I'm sure there must be a more elegant way of doing this.

$\endgroup$
2
  • 1
    $\begingroup$ Probably you can use the Stack[] function. $\endgroup$
    – enzotib
    Commented Mar 16, 2015 at 10:39
  • $\begingroup$ Well, today I learned, thank you! The first page of the documentation for it looks very promising. $\endgroup$
    – LLlAMnYP
    Commented Mar 16, 2015 at 10:43

1 Answer 1

5
$\begingroup$

After studying the documentation of Stack I was able to come up with a minimal working example. Thanks, enzotib!

Needs["CustomTicks`"]

GetGeometry[g_Graphics] :=
    Module[{q = 
        Rasterize[
            Show[g,
            FrameTicks -> None,
            Ticks -> None, 
            Epilog -> {Annotation[Rectangle[Scaled[{0, 0}], Scaled[{1, 1}]],"One", "Region"]}],
        "Regions"][[-1, 2]],
        },

        {"PlotRangeSize" -> q[[2]] - q[[1]]}

    ]

ALT[length_] := 
    Module[{stack = Stack[_Plot], width},
        stack = Delete[stack, Position[stack, Ticks -> _]];
        width = ("PlotRangeSize" /. GetGeometry[stack[[1, 1]]])[[1]];

        LinTicks[#1, #2, TickLengthScale -> (100*length/width)] &
    ]

Row[Table[Plot[x, {x, 0, 10}, Ticks -> ALT[10], ImageSize -> i], {i, 200, 500,150}]]

This code returns Correctly sized ticks!

Note, how regardless of ImageSize the lengths of the ticks are the same in each case, as well as being the desired 10 points long (major ticks). There is a drawback I am concerned about, that ALT is being called twice for every plot, which leads to constructing the plot sans ticks every time to calculate its width, but the automatization is for my purposes well worth it.

$\endgroup$
1
  • 1
    $\begingroup$ In hindisght, I really wonder, why nobody mentioned UpValues and closed the question for being so simple and ignorant of the documentation... $\endgroup$
    – LLlAMnYP
    Commented Mar 26, 2015 at 17:15

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.