17
$\begingroup$

Suppose I wanted to draw the zero contour for the function $$d^2-g.$$ I could use the following code:

ContourPlot[d^2-g,{g,0,1},{d,0,1},Contours->{0},  
              ContourStyle->Black,ContourShading->None,FrameLabel->{"g","d"}]

Which produces this:

contour plot

Taking this one step further, I might want to plot the zero contour for $$td^2-g.$$ at various values of $t$, which I could achieve functionally with

Show[Table[ContourPlot[t*d^2-g,{g,0,1},{d,0,1},Contours->{0},  
         ContourStyle->Black,ContourShading->None,FrameLabel->{"g","d"}],{t,0.1,1,0.1}]]

producing this:

multi-contour plot

Now, to make this easier to understand, I want to label each contour with its corresponding value of $t$, but this is where I run into trouble. I had thought that this could be achieved with the option

ContourLabels->t

but that yields this:

enter image description here

(it seems to have moved the labels rather than changing them). Does anyone know how to get Mathematica to draw a label equal to the relevant value of t on each of these contours?

Secondly, assuming I can get that working, how can I reposition the labels towards the top of the contours so that they appear where the contours are furthest apart.

$\endgroup$

3 Answers 3

17
$\begingroup$

Another quick way is to write your own ContourLabels function which places the labels where you want them. Note that I added some PlotRangePadding to make the labels fully visible in the frame

Show[Table[
  ContourPlot[t*d^2 - g, {g, 0, 1}, {d, 0.1, 1}, Contours -> {0}, 
   ContourStyle -> Black, ContourShading -> None, 
   FrameLabel -> {"g", "d"}, 
   ContourLabels -> 
    Function[{x, y, z}, 
     Text[Framed[t], {t, 1}, Background -> White]]], {t, 0.1, 1, 
   0.1}], PlotRangePadding -> .05]

enter image description here

Additionally a short side-note: When your function is not that simple, the placement of the labels may require that you solve for the root (contour-value) of your expression. This should hopefully work with a simple FindRoot in most cases.

$\endgroup$
1
  • $\begingroup$ Thanks to all the responders. I think @halirutan's solution is the closest in spirit to what I had in mind. $\endgroup$
    – Ubiquitous
    Aug 9, 2012 at 20:41
29
$\begingroup$

I'm revisiting this topic because I have a new solution at the bottom of the post. The outline of this answer is:

Interactively labeling contours

  1. Using the graphics editor
  2. Using Tooltip
  3. Converting Tooltip displays to static labels at custom positions

Section 1 was my original answer, the other two are new.

1. Using the graphics editor

What I usually do is label the contours by hand with the help of the drawing tools. For that you first have to know what labels belong to which contour. In your example, the label you want is the parameter t, so to get myself oriented I first defined a ContourLabels function that can display that number:

labelFunction[t_][x_, y_, z_] := Text[t, {x, y}]

Show[Table[
  ContourPlot[t*d^2 - g, {g, 0, 1}, {d, 0, 1}, Contours -> {0}, 
   ContourStyle -> Black, ContourShading -> None, 
   FrameLabel -> {"g", "d"}, ContourLabels -> labelFunction[t]], {t, 
   0.1, 1, 0.1}]]

contours

I left all the other things unchanged in your example.

Then I re-did the plot without ContourLabels (just delete that option), and highlighted the resulting plot by clicking on it. Next, press t and click where you want to add the first manually placed contour label. Type the number for the contour, and click outside the text to accept the label. press t again to start adding the next contour label at your desired position, etc.

labeledmanually

After entering the labels like this, you can also double-click each label and edit it further, e.g. by moving it around if you don't like your original choice of placement.

How permanent are these annotations?

One problem that always makes me feel uneasy about using the editing tools is that manual annotations appear less permanent because they don't get reproduced when you re-run the notebook. However, it's worth pointing out that manual annotations aren't all that bad in practice:

Assume you want to re-do the last plot above, keeping the labels but coloring the contours differently. The colors could be added like this:

coloredPlot = 
 Show[Table[
   ContourPlot[t*d^2 - g, {g, 0, 1}, {d, 0, 1.5}, Contours -> {0}, 
    ContourStyle -> Hue[t], ContourShading -> None, 
    FrameLabel -> {"g", "d"}], {t, 0.1, 1, 0.1}]]

Note that I added ContourStyle -> Hue[t] and also changed the plot range in the vertical direction. But now of course the labels are gone again. Fortunately, you can copy the contour labels from the previous plot as follows:

highlights

Here I double clicked one label, then shift-clicked the others to select them all. Now I pressed the copy keyboard shortcut and go back to the colored plot coloredPlot I just produced.

Next, type the following:

showwithlabels

Here, I pasted the previously copied labels into the Show command. Executing this line, I get the labels at the correct absolute plot coordinates in the new plot.

The conclusion from this is that the manual labor involved in making the labels as I showed above is not completely wasted when you re-draw the contour plot, because copying your work preserves the coordinates of the insets.

2. Using Tooltip

Normally, Tooltip is the easiest way to label contours because that's the default behavior of ContourPlot when no ContourLabels option is specified.

However, we want to label contours by a Tooltip showing a custom quantity, not the default which is contour height. In the documentation for ContourLabels (under Details), you find that custom tool tips should be available by specifying

ContourLabels -> {f, g}

where the second function, g, is used "as a tooltip for each complete contour line." This is very ambiguously worded, and it seems to have had people stumped (see the MathGroup archive). It turns out that g isn't the function that specifies the argument for the Tooltip, it actually has to be of Head Tooltip.

So in the example of the question, for a Table of several contour plots where each contour is custom labeled by the Table index t, we can get a tooltip-labeled plot like this:

toolPlot = Show[
 Table[
  ContourPlot[t*d^2 - g, {g, 0, 1}, {d, 0, 1}, Contours -> {0}, 
   ContourStyle -> Black, ContourShading -> None, 
   FrameLabel -> {"g", "d"},
   ContourLabels -> {
     None,
     Tooltip[#, t] &}
   ],
  {t, 0.1, 1, 0.1}
  ]
 ]

tool tips

The first function in ContourLabels is simply set to None so we don't get any static labels at all. The second argument shows how you get the Tooltip labeling "each complete contour line," as the documentation put it.

Now we're done debugging the mysterious documentation, and it's time for the fun part:

3. Converting Tooltip displays to static labels at custom positions

Here we ask the question:

Given a graphics object such as toolPlot from above, can we convert the information contained in the Tooltips into static labels and position these labels interactively?

This is different from adding labels completely manually, because we're going to rely on the auto-generated information in the Tooltip and only leave the choice of position as a manual task.

For this, I'll use the same idea as in my answer to this question, but modified to get a dynamically updated graphic that includes labels wherever I click on a Tooltip-decorated line:

Options[burnTooltips] = {ImageSize -> 360, 
   "LabelFunction" -> (Framed[#, FrameStyle -> None, 
       RoundingRadius -> 8, Background -> RGBColor[1, .8, .4]] &)};

burnTooltips[plot_, opt : OptionsPattern[]] := DynamicModule[
  {
   ins = {},
   wrapper = OptionValue["LabelFunction"],
   toolRule = Function[{arg},
     Tooltip[t__] :> Button[
       Tooltip[t],
       AppendTo[
        arg,
        Inset[wrapper[Last[{t}]], MousePosition["Graphics"]]
        ]
       ],
     HoldAll]
   },
  EventHandler[
   Dynamic@Show[
     plot /. toolRule[ins],
     Graphics@ins,
     ImageSize -> OptionValue[ImageSize]
     ],
   {"MouseUp", 2} :> (toolRule = {} &)
   ]
  ]

The function burnTooltips can be applied to the contour plots of our example as follows:

burnTooltips[toolPlot]

This will display the graphic in the argument again, but now whenever you click on an object displaying a Tooltip, the displayed value appears as a static label in the graphic. When you're done adding the labels at locations where they look good to you, you may want to prevent this dynamic display from receiving any further clicks. To do that, right-click the mouse over the completed picture.

Here is what the result might look like:

tooltips to labels

To continue working with this graphic, copy it and paste it into the next Input cell. For example, to get an image to upload above, I typed

Export["picture.png",   ]

and pasted the image in the space.

There are two options you may want to play with for burnTooltips: ImageSize and "LabelFunction". The first one doesn't need explanation, and the second one should be something of the type Framed[#]& or Pane[#]& (my default setting is shown in the image). By adding the label formatting as a function (note the &), it's possible to customize the labels almost arbitrarily. For example, one could also try this:

"LabelFunction" -> (Pane[Style[#, Red, Background -> White], 
    ImageSize -> {20, 20}, ImageSizeAction -> "ResizeToFit"] &)

This method is pretty amenable to how I normally work with plots: use default labeling (i.e., Tooltip in this case) while exploring the problem, and start worrying about the exact placement of labels only at the end.

Here I showed how one can get to the finalized labeled plot from the Tooltip stage. In the function burnTooltips, the Tooltip is used as a kind of vehicle with which a new "payload" can be injected into an existing plot, turning it into a dynamic object that accumulates information about where you click to place labels containing the exact same information that the Tooltip displayed.

Generalizations

You should be able to apply burnTooltips to virtually any Graphics object, not just ContourPlots. But I think ContourPlot is one of the most important applications for this technique because even in single plots (not tables as we considered here), the automatically generated labels are often placed in awkward locations so that manual adjustment is required.

$\endgroup$
5
  • 4
    $\begingroup$ +1 for showing me something I've never seen before. $\endgroup$
    – Mr.Wizard
    Aug 8, 2012 at 22:47
  • $\begingroup$ Jens, thanks for this excellent answer. There are some further questions on how the second argument of ContourLabels works in this question - could I interest you in taking a look? $\endgroup$ May 25, 2015 at 18:01
  • $\begingroup$ Could this be tweaked to work with a Grid of ContourPlots? $\endgroup$
    – Delphine
    Feb 15, 2016 at 16:19
  • 1
    $\begingroup$ @Delphine It should work fine with GraphicsGrid instead of Grid. $\endgroup$
    – Jens
    Feb 15, 2016 at 17:39
  • $\begingroup$ Part 3 of your answer is a great way to customize the placement of contour labels. Thank you. $\endgroup$
    – nanjun
    Jan 31, 2017 at 0:34
8
$\begingroup$

One quick way, not very general, is to use custom ticks :

ContourPlot[#*d^2 - g & /@ Range[0.1, 1, 0.1], {g, 0, 1}, {d, 0, 1}, Contours -> {0},    
 ContourStyle -> Black, ContourShading -> None, FrameLabel -> {"g", "d"}, 
 FrameTicks -> {{Automatic, None}, {Automatic, Range[0.1, 1, 0.1]}}, 
 FrameTicksStyle -> {{Automatic, Automatic}, {Automatic, Red}}]

contours

$\endgroup$
1
  • 1
    $\begingroup$ That would be a good solution, if it worked for a general function. $\endgroup$
    – Dr_Zaszuś
    Jun 13, 2014 at 11:19

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.