The interesting question is: how do we get labels into a plot without having the frame and/or axis range of that plot automatically adjust to enclose the labels. After all, you want labels on top of the plot, which means they will have to be "hovering" above the maximum vertical height of the axes.
I think Overlay
is really good for such tasks in principle. Let's say the y-axis is supposed to extend from $-5$ to $5$, then the first thing we have to do is create some empty space on top of that range. This can be done by setting PlotRegion
. In the plot below, I want the extra height (in the coordinate system of the plot) to be up to yExtra = 8.5
.
The problem with Overlay
is that it aligns two objects using scaled coordinates that are "agnostic" of the plot coordinates we're interested in (e.g., the center of the plot is always {0, 0}
for Overlay
). But we are trying to put labels at specific horizontal positions in the original plot coordinates.
To make the overlays align properly, especially in the horizontal direction where the placement has to be most accurate, I put the annotation into an invisible copy of the original plot. That guarantees that the overlays will fit together nicely:
g = With[{yMax = 5, yExtra = 8.5},
p = Plot[
{-.1 x, Tan[x]}, {x, 0, 3 Pi},
PlotRange -> {-yMax, yMax},
AxesLabel -> {x, y},
ImageSize -> 400
];
Overlay[{
Show[p,
PlotRegion -> {{0, 1}, {0, 2 yMax/(yMax + yExtra)}}],
Show[p,
AspectRatio -> (AspectRatio /.
Options[p, AspectRatio]) (1 + yExtra/yMax)/2,
BaseStyle -> Opacity[0],
PlotRange -> {-yMax, yExtra},
Epilog -> {Opacity[1],
Table[{Arrow[{{(2 i + 1)/2 Pi, 6.5}, {(2 i + 1)/2 Pi,
5.5}}],
Text[Row[{x, " = ", (2 i + 1)/(2 L) Pi}], {(2 i + 1)/
2 Pi, 7.4}]}, {i, 0, 3}]}
]
}
]
]

The invisible layer is created in Show[p, BaseStyle -> Opacity[0]...
and the annotation is added to that layer as Epilog -> {Opacity[1],...
(the specific annotation with text and arrows is created in the Table
).
For the original plot p
, I use Show[p, PlotRegion ->...]
to make sure it is shown with the empty space on top. That PlotRegion
specification can't be added to the initial definition of p
because then it would disappear when the plot is displayed inside Overlay
.
The horizontal coordinates of the original plot and the overlay are identical, but the vertical coordinates are not. This is due to the axis label y
that isn't contained in the spacing calculations.
However, you can now use the Epilog
in the overlay to add annotations that overlap the plot any way you like - partly inside and outside of the coordinate frame. E.g., you could try changing the Arrow
specification above to Arrow[{{(2 i + 1)/2 π, 6}, {(2 i + 1)/2 π, 4}}]
to make the arrows reach deeper into the plot:

Edit: make it prettier
A separate issue is to make the arrows nicer:
With[{yMax = 5, yExtra = 7.5},
p = Plot[{-.1 x, Tan[x]}, {x, 0, 3 Pi}, PlotRange -> {-yMax, yMax},
AxesLabel -> {x, y}, ImageSize -> 400];
Overlay[{Show[p,
PlotRegion -> {{0, 1}, {0, 2 yMax/(yMax + yExtra)}}],
Show[p, AspectRatio -> (AspectRatio /.
Options[p, AspectRatio]) (1 + yExtra/yMax)/2,
BaseStyle -> Opacity[0], PlotRange -> {-yMax, yExtra},
Epilog -> {Opacity[1],
Table[{Arrow@
BezierCurve[{{(2 i + 1)/2 Pi, 6.5}, {(2 i + 1)/2 Pi + 1,
5}, {(2 i + 1)/2 Pi, 3.5}}, SplineDegree -> 2],
Text[Row[{x,
" = ", (2 i + 1)/(2 L) Pi}], {(2 i + 1)/2 Pi - .5,
6.5}]}, {i, 0, 3}]}]}]]

The arrows have been modified to have a curved appearance by using BezierCurve
. All you have to do is replace Arrow
by Arrow@BezierCurve
and add one extra point in-between the start and end point of the original (straight) arrow. That point is the handle that pulls the curve to one side.