9
$\begingroup$

I have the following command:

ContourPlot[(-1 + x)^2 (-1 + y) + x^2 y,
{x, 0, 1}, {y, 0, 1},
ContourLabels -> True]

which gives me

enter image description here

I want to highlight the 0-contour line, e.g. make it thicker, dashed, red, etc., but Mathematica's documentation on ContourPlot does not seem to provide a way to specify the style for particular contour lines.

How should I specify the style for particular contour lines?

$\endgroup$
2
  • 2
    $\begingroup$ I do not know how to make it automatic and apply style to only one but it is the way you can go: Contours -> (Table[i, {i, -.8, .8, .2}]~Join~{{0, Directive[Thick, Red]}}) $\endgroup$
    – Kuba
    Jul 18, 2013 at 8:35
  • 2
    $\begingroup$ Or even Contours -> (If[# == 0, {0, Directive[Thick, Dashed, Red]}, #] & /@ Range[-.8, .8, .2]) so it doesn't draw the 0-contour line twice. $\endgroup$
    – user484
    Jul 18, 2013 at 8:38

3 Answers 3

10
$\begingroup$

Here is another very simple way to modify a particular contour. You just have to turn on Tooltip labeling, and then pick out the line with the tooltip 0 to modify its graphics Directive using a replacement rule. First I define a new style for the contour, then I apply it:

newStyle[x_] := x /. l_Line :> Sequence[
    Opacity[.4], Thick, Dashed, Red,
    l
    ]

ContourPlot[(-1 + x)^2 (-1 + y) + x^2 y, {x, 0, 1}, {y, 0, 1}, 
  ContourLabels -> All] /. Tooltip[x_, 0] :> Tooltip[newStyle[x], 0]

red contour

To make Tooltip appear in addition to contour labels, I used ContourLabels -> All.

Edit

Thanks to MichaelE2's comment, I addressed the case where you also want to style the other contour lines, but just add a different style for the particular contour 0.

All the style modifications are done in the function newStyle, which inserts the additional directives in front of the contour line. This allows pre-existing definitions from the ContourStyle option to coexist with the new directives.

Edit 2 in response to comment

One general problem with ContourLabels is that their automatic placement is incredibly clumsy most of the time. You can see this in the above example, where the labels are scrunched up against the frame for no good reason. Giving your labels additional pretty styling such as frames and backgrounds is kind of futile if their placement looks so bad.

Therefore, in practice I would argue it's inevitable that you'll want to place the labels manually. And if you do it that way, then my favorite approach is to use tha answer I gave here.

  • Copy the definition for burnTooltip (including its Options)
  • Define the same ContourPlot as before, but with ContourLabels -> Automatic so that only Tooltip is used.
  • Execute burnToltip with the desired option for "LabelFunction" (e.g. to get a frame)
  • Click to place the contour labels anywhere you want
  • Finish by right-clicking on the plot. Then you can copy or export it as a Graphics object.

Here is the code:

c = ContourPlot[(-1 + x)^2 (-1 + y) + x^2 y, {x, 0, 1}, {y, 0, 1}, 
    ContourLabels -> Automatic] /. 
   Tooltip[x_, 0] :> Tooltip[newStyle[x], 0];

burnTooltips[c, "LabelFunction" -> (Framed[#, Background -> White] &)]

burned

This is the result I got. Now the contour is styled as desired, and you got labels in reasonable-looking positions.

$\endgroup$
7
  • $\begingroup$ +1 Nice and neat. But note it only seems to work for the default ContourStyle -> Automatic and does not override an explicit setting. To do that, one might use something like Tooltip[x_, 0] :> Tooltip[x /. l_Line :> {Opacity[.4], Thick, Dashed, Red, l}, 0]. $\endgroup$
    – Michael E2
    Jul 18, 2013 at 23:02
  • $\begingroup$ @MichaelE2 Thanks - I'll edit when I have time to try it out... $\endgroup$
    – Jens
    Jul 18, 2013 at 23:28
  • $\begingroup$ How should I turn on tooltip if my ContourLabels option is set to ContourLabels -> (Text[Framed[#3], {#1, #2}, Background -> White] &)? $\endgroup$
    – wdg
    Jul 19, 2013 at 17:03
  • $\begingroup$ @wdg In that case see my new edit. $\endgroup$
    – Jens
    Jul 19, 2013 at 18:02
  • $\begingroup$ Hi, I tried your example but strangely the contour labels are not shown. I am using MM 10.4 $\endgroup$
    – yangyang
    Mar 14, 2017 at 13:50
5
$\begingroup$

There is way to style individual Contours:

{{f1, gr1},...} contours drawn with graphics directives gri

and a way to have a function compute the contours for you:

func a function to be applied to zmin, zmax to get the list of contours

Unfortunately they do not seem to cooperate -- that is, if func returns a list of contour levels and graphics directives {{f1, gr1},...}, all I get is a gray square for the plot. No error warnings, no contours. If this is by design, it seems that it leaves room for improvement.

However, one can take a two-step approach. Below is a function that does one contour plot to compute the list of styled contours and another to compute the image. To improve speed, by default, MaxRecursion is set to 0; this can be overridden. All we really need to know to compute the contours is the maximum and minimum of the plotted function over the domain. There are various ways to compute this. ContourPlot is one. Sometimes human inspection is quick enough. If I can assume quality is more important than speed, then a double ContourPlot is reasonable.

I made a some attempt to provide an interface with ContourPlot, but the variety of forms of the options, especially Contours and ContourStyle, and my limited time led to a partial implementation.

The function cFn yields a Function that computes the styled contours. Each contour has to be styled with this form of the option Contours. I apply the ContourStyle cStyle, which needs to be a single style, to the union of the user-styled and the automatically generated contours. The styles of the user-styled contours are then replaced by the styles the user specifies. The integer nContours represents the "target" number of contours to be returned by FindDivisions; it won't be the exact number of contours due to the workings of FindDivisions.

If the max/min of the function are known, then contours = cFn[styledContours][min, max] may be used to compute the value for the option Contours -> contours directly.

To use cplot, just pass a list of contours in the form {{f1, gr1},...}, the same as for the option Contours, followed by the arguments for ContourPlot.

Clear[cFn];
cFn[styledContours_, nContours_: 10, cStyle_: {GrayLevel[0], Opacity[0.4]}] /; 
  ArrayDepth[styledContours] == 1 := cFn[{styledContours}, nContours];
cFn[styledContours_, nContours_: 10, cStyle_: {GrayLevel[0], Opacity[0.4]}] := 
  Function[{min, max}, {#, cStyle} & /@ 
      Union[First /@ N@styledContours, N @ FindDivisions[{min, max}, nContours]] /. 
     Thread[{#, cStyle} & /@ First /@ N @ styledContours -> styledContours]];

ClearAll[cplot];
SetAttributes[cplot, HoldAll];
cplot[styledContours_, fn_, dom1_, dom2_, opts : OptionsPattern[ContourPlot]] :=
 Module[{contours},
  ContourPlot[fn, dom1, dom2, PlotPoints -> OptionValue[PlotPoints], 
   MaxRecursion -> 
    If[OptionValue[MaxRecursion] === Automatic, 0, OptionValue[MaxRecursion]], 
   Contours -> ((contours = cFn[styledContours, 
                  If[IntegerQ[OptionValue[Contours]], IntegerQ[OptionValue[Contours]], 10], 
                  OptionValue[ContourStyle]][##]) &)];
  ContourPlot[fn, dom1, dom2, Contours -> contours, opts]
  ]

Example

cplot[{0, {Thick, Red}}, (-1 + x)^2 (-1 + y) + x^2 y,
 {x, 0, 1}, {y, 0, 1}, ContourLabels -> True]

Mathematica graphics

I hope that helps. If Contours -> func ever works the way I think it should, this would be a bit easier.

$\endgroup$
3
$\begingroup$

You can specify a style for individual contour lines by using Contours->{ { contour value, graphics directive }, … }.

ContourPlot[
  (-1 + x)^2 * (-1 + y) + x^2 * y,
  { x, 0, 1 },
  { y, 0, 1 },
  ContourLabels->True,
  Contours->(
    (* Create a list of { contour value, graphics directive } pairs.
     * If a graphics directive is Null, it will have the default
     * contour line style. *)
    {
      #,
      If[# == 0,
        Directive[ Thick, Dashed, Red ],
        Null (* Replace this if you want to change the default
              * contour graphics directives. *)
      ]
    }& /@ Range[ -10, 10, 0.2 ]
  )
]

Result

This does not work with Contours->Automatic, so you'll need to specify your contour values manually. The example above uses Range to specify the contour values.

$\endgroup$
3
  • 1
    $\begingroup$ Well, this is Kuba's and Rahul's comments merged into one ... @glyph $\endgroup$
    – Sektor
    Jul 11, 2015 at 13:40
  • $\begingroup$ @Sektor: Thanks for pointing that out, I can't believe I didn't notice Rahul's comment when studying this page. It would have saved me some time. I think it is the simplest and most general solution to the question asked and I tried to present it in the clearest way possible with comments; it deserves its own answer. $\endgroup$ Jul 12, 2015 at 0:38
  • $\begingroup$ To improve this answer Range could be automated. Not sure if that is possible in a clean and general way. $\endgroup$ Jul 12, 2015 at 0:41

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.