30
$\begingroup$

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:

enter image description here

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?

$\endgroup$

4 Answers 4

20
$\begingroup$

"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):

Mathematica graphics

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)}]]
$\endgroup$
5
  • $\begingroup$ Excellent, exactly what I was looking for. $\endgroup$
    – Guillochon
    Apr 17, 2012 at 21:47
  • $\begingroup$ Very nice. I figured there might be an easy way to do this. +1 $\endgroup$
    – Andy Ross
    Apr 17, 2012 at 22:03
  • $\begingroup$ @Mr.Wizard FilledCurve[] and JoinedCurve[] are new in 8 ... and the ImportString thing is returning a FilledCurve[ blah blah] $\endgroup$ Apr 17, 2012 at 22:37
  • 1
    $\begingroup$ as they say: "duh" -- I've really gotten lazy; sorry. $\endgroup$
    – Mr.Wizard
    Apr 17, 2012 at 22:52
  • 2
    $\begingroup$ +1 for "Vectorizing the font", just the thing I was looking for. $\endgroup$ Apr 18, 2012 at 0:17
26
$\begingroup$

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"], If[$VersionNumber >= 13, {"PDF", "PageGraphics"}, "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}]
   ];

Stretching character

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.

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.

$\endgroup$
7
  • $\begingroup$ Very nice improvement to belisarius' solution. $\endgroup$
    – Guillochon
    Apr 18, 2012 at 2:29
  • $\begingroup$ 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... $\endgroup$
    – Jens
    Apr 18, 2012 at 2:40
  • $\begingroup$ This is fantastic, thank you. When I run Graphics[stretchText["︷", {200, -360}, {350, 30}]] it gives me an image which is heavily padded on the left. Do you know how to remove this padding? $\endgroup$ Jul 29, 2020 at 12:28
  • 1
    $\begingroup$ @JonathanShock Thanks - the problem with your short Graphics expression is that is needs to specify PlotRange -> {{200, 550}, {-360, -330}} if you want the graphic to enclose the Inset tightly. That range is just the minimal and maximal coordinates obtained from the arguments you provided in stretchText. $\endgroup$
    – Jens
    Jul 31, 2020 at 2:32
  • 2
    $\begingroup$ @yode Thanks for letting me know. Mathematica version 13 broke backward compatibility with earlier PDF import syntax, so to fix the code in version 13 and above you need to insert {"PDF", "PageGraphics"}, before the option "TextOutlines" -> True $\endgroup$
    – Jens
    Jun 22, 2022 at 15:46
15
$\begingroup$

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}}
 ]
]

Showing distance with a curly brace

$\endgroup$
1
  • $\begingroup$ 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. $\endgroup$
    – Jens
    Aug 7, 2013 at 21:55
6
$\begingroup$

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.

enter image description here

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]]
$\endgroup$
1
  • $\begingroup$ 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. $\endgroup$
    – Guillochon
    Apr 17, 2012 at 21:09

Your Answer

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

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