# Scale Insetted Characters to Plot

I am trying to place a curly brace within a plot such that the top/bottom of the curly brace line up with two horizontal lines in the plot:

I have not been able to find a way to make the curly brace have the right size without setting the size explicitly by hand. Insetting a Text object doesn't work as the font size is used to scale text, and guessing the right font size is a complete guessing game. I settled upon using "Pane" with the "ResizeToFit" ImageSizeAction set, however I am not sure how to then specify the Inset size or the ImageSize for pane to get the desired results.

Here is the code I am using to generate this plot. ImageSize being set to "5" is just a guess to get approximately the right size, but ideally I want the top/bottom of the curly brace to exactly line up with the two horizontal dashed lines:

f = 1/x;
ϵpl =
Plot[{f}, {x, 0, 1}, PlotStyle -> Black,
PlotStyle -> AbsoluteThickness[1]];

ϵ1pl =
Plot[{f}, {x, 0.2, 0.4},
PlotStyle -> Directive[Red, AbsoluteThickness[1.5]]];
ϵ1dashed =
ListPlot[{{{0.1, f /. x -> 0.27}, {0.27, f /. x -> 0.27}}, {{0.1,
f /. x -> 0.3}, {0.3, f /. x -> 0.3}}},
PlotStyle -> Directive[Red, Dashed, AbsoluteThickness[1]],
Joined -> True];

Show[ϵpl, ϵ1pl, ϵ1dashed,
PlotRange -> {{0, 1}, {0, 6}}, Axes -> False, Frame -> True,
ImageSize -> 600,
FrameStyle -> Directive[AbsoluteThickness[1], FontSize -> 24],
FrameTicks -> False, FrameLabel -> {"\!$$\* StyleBox[\"r\",\nFontSlant->\"Italic\"]$$", "\!$$\* StyleBox[\"ϵ\",\nFontSlant->\"Italic\"]$$"},
Epilog ->
Inset[Pane["{", ImageSize -> 5,
ImageSizeAction ->
"ResizeToFit"], {0.095, ((f /. x -> 0.27) + (f /. x -> 0.3))/
2}, {0, 0}, {Automatic, (f /. x -> 0.27) - (f /. x -> 0.3)}]]


Additionally, I would like to be able to scale the curly brace such that the original aspect ratio isn't maintained (i.e. the width of the curly brace will be a fixed value, but the height can vary). What is the best way to go about doing this?

-

"Vectorizing" the font:

curve = First[First[ ImportString[ExportString[
Style["{",FontFamily ->"Times",FontSize -> 72],  "PDF"], "TextMode" -> "Outlines"]]];
cg = Graphics[curve]


and then your code, replacing the Inset[] clause by

Inset[Pane[cg, ImageSizeAction ->  "ResizeToFit"], ....


Result (scales OK when resizing the Plot):

Edit

Full code follows, just in case you decide to edit your code:

curve = First[First[
ImportString[
ExportString[Style["{", FontFamily -> "Times", FontSize -> 72],
"PDF"], "TextMode" -> "Outlines"]]];
cg = Graphics[curve];
f = 1/x;
ϵpl =
Plot[{f}, {x, 0, 1}, PlotStyle -> Black,
PlotStyle -> AbsoluteThickness[1]];

ϵ1pl =
Plot[{f}, {x, 0.2, 0.4},
PlotStyle -> Directive[Red, AbsoluteThickness[1.5]]];
ϵ1dashed =
ListPlot[{{{0.1, f /. x -> 0.27}, {0.27, f /. x -> 0.27}}, {{0.1,
f /. x -> 0.3}, {0.3, f /. x -> 0.3}}},
PlotStyle -> Directive[Red, Dashed, AbsoluteThickness[1]],
Joined -> True];

Show[ϵpl, ϵ1pl, ϵ1dashed,
PlotRange -> {{0, 1}, {0, 6}}, Axes -> False, Frame -> True,
ImageSize -> 600,
FrameStyle -> Directive[AbsoluteThickness[1], FontSize -> 24],
FrameTicks -> False, FrameLabel -> {"\!$$\* StyleBox[\"r\",\nFontSlant->\"Italic\"]$$", "\!$$\* StyleBox[\"ϵ\",\nFontSlant->\"Italic\"]$$"},
Epilog ->
Inset[Pane[cg,
ImageSizeAction ->
"ResizeToFit"], {0.095, ((f /. x -> 0.27) + (f /. x -> 0.3))/
2}, {0, 0}, {Automatic, (f /. x -> 0.27) - (f /. x -> 0.3)}]]

-
Excellent, exactly what I was looking for. –  Guillochon Apr 17 '12 at 21:47
Very nice. I figured there might be an easy way to do this. +1 –  Andy Ross Apr 17 '12 at 22:03
@Mr.Wizard FilledCurve[] and JoinedCurve[] are new in 8 ... and the ImportString thing is returning a FilledCurve[ blah blah] –  belisarius Apr 17 '12 at 22:37
as they say: "duh" -- I've really gotten lazy; sorry. –  Mr.Wizard Apr 17 '12 at 22:52
+1 for "Vectorizing the font", just the thing I was looking for. –  István Zachar Apr 18 '12 at 0:17

To make such labeling more user-friendly, I wrote this function (edited to add rotation):

stretchText[char_, pos_, scale_, angle_: 0] :=
Module[{g, coords, xMin, xMax, yMin, yMax},
g = First@First@ImportString[ExportString[char, "PDF"],
"TextOutlines" -> True];
coords =
Apply[Join,
Cases[g, FilledCurve[___, p_] :> Flatten[p, 1], Infinity]];
{{xMin, xMax}, {yMin, yMax}} =
Map[{Min[#], Max[#]} &[#] &, Transpose[coords]];
Rotate[
Inset[
Graphics[g,
PlotRange -> {{xMin, xMax}, {yMin, yMax}},
If[ListQ[scale],
AspectRatio -> Full,
AspectRatio -> Automatic
]
],
pos,
{xMin, yMin},
scale],
angle]
]


Here are some features:

First, you don't have to use a Pane or even an Epilog to use it (the output of stretchText is already an Inset and can be put into a Graphics). Secondly, you can position whatever string you like very precisely by specifying its lower left corner as the second argument pos, and then its scale in the horizontal and vertical direction as the third argument scale. The scale is in the coordinates of the surrounding graphic by default. So if you want the text to be exactly 0.2 wide and 0.7 high in the coordinates of your actual plot, that's exactly what you enter as scale. No need for trial-and-error adjustments.

The scale can be entered as a single number (in which case it sets the text width and scales the height proportionally), or as a pair of numbers for independent scaling of width and height. The latter is really what makes stretchText different from normal text. The last (optional) argument in stretchText is the rotation angle away from the horizontal.

Here is an example that just combines a hyperbola plot with the dashed lines and the stretched { by using a simple Show statement. To make the stretching clearer, I made a movie of it. What really counts is inside the Table:

t = With[{xMax = 3, wX = .2},
Table[
Show[Plot[1/x, {x, 0, 5}],
Graphics[
stretchText["{", {xMin - wX, 1/xMax}, {wX, 1/xMin - 1/xMax}]],
Graphics[{Red, Dashed,
Map[Line[{{0, 1/#}, {#, 1/#}}] &, {xMin, xMax}]}],
PlotRange -> {0, 1.5}, Frame -> True, Axes -> False],
{xMin, .7, 2.5, .2}]
];


As this example shows, the positioning is pretty straightforward because stretchText takes care of rescaling the outlined characters (it could be a whole string, too) to the given parameters.

The essential ingredient in stretchText is the PDF export/import trick to produce FilledCurves, so it requires Mathematica 8. The ability to combine this annotation with Show means that it's easy to manipulate the output with Mathematica's graphics editor too, if needed.

Edit: note on outlining text

The reason why I convert text to outlines here is that text glyphs in a string can't be scaled by independent factors in the horizontal and vertical directions. You can only scale text proportionally, and that would make it impossible to get a stretched brace.

I decided to use "TextOutlines" -> True instead of "TextMode"->"Outlines" to get a FilledCurve representation of the glyphs. That's simply because the documentation gives it a more official status ("TextMode"->"Outlines" is used only in the examples for FilledCurve and JoinedCurve). But both of these options are in principle unnecessary because the PDF options for Import already list "TextOutlines" -> True as the default. Leaving the explicit option in is just a way to be safer in this application.

The point lists in the FilledCurve for the outlined glyphs are also used here to determine a reasonably close-cropped bounding box for the text. This is only approximate because I make no distinction between control and other points, but it is very simple and seems to work well in practice.

-
Very nice improvement to belisarius' solution. –  Guillochon Apr 18 '12 at 2:29
I've actually been doing it this way for a long time, see my web page. Too bad that "TextMode"->"Outline" only got introduced (fully) in version 8 - before that, one had to resort to more complicated work-arounds, e.g., using ghostscript; see here. That would still be a way to crearte similar functionality in older Mathematica versions... –  Jens Apr 18 '12 at 2:40

You can use my brace function.

p2c[p_]:=p[[1]]+I p[[2]];
c2p[p_]:={Re[p],Im[p]};
brace[pa_,pb_,a_:1/10,t_:1/3]:=Module[{θ,ρ},
θ=Arg[p2c[pb-pa]];
ρ=Norm[p2c[pb-pa]];
{Circle[pa+a ρ{Cos[θ],Sin[θ]},a ρ,{π+θ+t π/2,(3π)/2+θ}],
Line[{pa+a ρ{Cos[θ],Sin[θ]}+a ρ{Sin[θ],-Cos[θ]},(pa+pb)/2-a ρ{Cos[θ],Sin[θ]}+a ρ{Sin[θ],-Cos[θ]}}],
Circle[(pa+pb)/2-a ρ{Cos[θ],Sin[θ]}+2a ρ{Sin[θ],-Cos[θ]},a ρ,{θ,π/2+θ}],
Circle[(pa+pb)/2+a ρ{Cos[θ],Sin[θ]}+2a ρ{Sin[θ],-Cos[θ]},a ρ,{π/2+θ,π+θ}],
Line[{(pa+pb)/2+a ρ{Cos[θ],Sin[θ]}+a ρ{Sin[θ],-Cos[θ]},pb-a ρ{Cos[θ],Sin[θ]}+a ρ{Sin[θ],-Cos[θ]}}],
Circle[pb-a ρ{Cos[θ],Sin[θ]},a ρ,{(3π)/2+θ,2π+θ-t π/2}]}
]


Here, a is in [0,1/4] and t is in [0,1], which you can easily figure out how they work. Below, you can find and example.

With[{pa={3,1},pb={1,5},a=1/20,t=1/3},
Graphics[{Point[{pa,pb}],brace[pa,pb,a,t],
Text[EuclideanDistance[pa,pb],(pa+pb)/2+1/2Reverse[(pa-pb)/2]*{-1,1}]},
ImageSize->200,Frame->True,PlotRange->{{0,6},{0,6}}
]
]


-
For the special case of a brace, I also posted an implementation here. It differs from this in that the label is passed as an argument to the function I call braceLabel. –  Jens Aug 7 '13 at 21:55

One possibility is to create your own "{" character using BSplineCurve.

Graphics[{Thick, BSplineCurve[{{0.3, 2.9}, {-0.6, 2.45}, {-0.95, 1.8},
{-0.2, 1.05}, {0.25,0.45}, {0., 0.}, {-0.8, -0.15}, {0., -0.35},
{0.25, -0.75}, {-0.15, -1.25}, {-0.85,-1.9}, {-0.35, -2.55},
{0.35, -3.}}]}
]


You can place the graphic directly in place of the "{" character and it will scale nicely. The only issue is getting the character to look as high quality as the built in ones. I made my poor attempt using the "Bezier" setting from Heike's answer to this question.

Note: You will want to remove the ImageSize option from your Inset.

Here is an example using a larger brace than the one in your example as proof of concept that it scales up nicely.

Edit:

One can also use Rasterize on the character and with a little cropping achieve what you want as well.

ImageCrop@Image[Rasterize["{", ImageSize -> 500]]

-
I'd like to avoid rasterization, if possible. Perhaps if there's a way to vectorize font characters, that would be the ideal solution, as then the graphics can be scaled in a straight-forward way. There might be font issues when the graphic is exported if a font character is used. –  Guillochon Apr 17 '12 at 21:09