1
$\begingroup$

Mathematica's automatic contour label placement inside ContourPlot[] and ListContourPlot will frequently clip off part of the contour labels, or have them overlap so that they are illegible.

In the case where all the labels can be placed along a line, say x=0.5, the ContourLabels->(Text[#3], {0.5, #2}] &) option with a specified function can work, but the coordinates are still selected by Mathematica's automatic contour label placement.

I found another solution at https://answerbun.com/, but it involved randomly placing the labels, which makes it difficult to reproduce plots and still doesn't allow specification of the location of the contour plots.

How can we manually select, in a relatively easy way, where contour plot labels are located?

$\endgroup$

1 Answer 1

1
$\begingroup$

Description

In essence, this solution extracts the contours from a contour plot, and then parametrizes them over [0,1]. (This parametrization avoids issue when a contour line cannot be converted into a function of the bottom axis). The contour line values are also obtained. It also prints a guide which gives the user number labels for each contour, and which side of the contour corresponds to 0 or 1, giving directional information. This guide can be used to adjust inputs and obtain desired contour label placement.

Function arguments

This function takes in a basic contour plot argument contourPlot with no contour labels and a number of contours specified nContour. The argument relLabelPos is a length nContour list. The list elements of relLabelPos must be values in [0, 1], and they specify how far along the contour the contour label should be placed. The argument onOrOff is also a length nContour list, comprised of booleans True or False, corresponding to whether a contour is displayed or not respectively.

Function defintion

contourLabelFn[contourPlot_, nContour_, relLabelPos_, onOrOff_, 
   fontWeight_ : Bold, fontSize_ : 12] := 
  Module[{cases, contourValues, data, paths, curves, contourFn, 
    domains, tCoords, xyCoords, contourMapLabels, contourLabels, 
    whichContourLabels, xyCoordLo, xyCoordHi, contourMapLabelsNum, 
    contourMapLabelsLo, contourMapLabelsHi},
   (*Extract contour lines from plot*)
   cases = Cases[Normal[contourPlot], Line[_, ___], Infinity];
   (*Extract contour line values*)
   contourValues = 
    Cases[contourPlot, Tooltip[_, label_] :> label, Infinity];
   (*Basic input checks*)
   If[nContour != Length[cases], 
    Print["Error: Number of contours is " <> ToString[Length[cases]] <>
       ". Set nContour correctly."]; Abort[]];
   If[nContour != Length[contourValues], 
    Print["Error: Number of extracted contour values doesn't equal " <>
       ToString[nContour]]];
   If[nContour != Length[relLabelPos], 
    Print["Error: Number of contours is " <> ToString[nContour] <> 
      ". Number of relative label positions must have this length."]; 
    Abort[]];
   If[nContour != Length[onOrOff], 
    Print["Error: Number of on or off values doesn't equal " <> 
      ToString[nContour] <> 
      ". The number of on or off values must have this length."]; 
    Abort[]];
   (*Obtain 2D coordinates comprising contour lines*)
   data = Map[First[#] &, cases];
   (*Create connected curves from the 2D coordinates*)
   paths = Map[First@FindCurvePath@Standardize[#] &, data];
   (*Define parametrized functions from the connected curves*)
   contourFn = 
    Table[Interpolation@
      MapIndexed[{#2[[1]], #1} &, data[[i, paths[[i]]]]], {i, 1, 
      nContour}];
   (*Parametrized function domains*)
   domains = Map[Flatten[#@Domain[]] &, contourFn];
   (*Rescaling from parametrized function domain to [0,1]*)
   tCoords = 
    Table[(domains[[i, 2]] - domains[[i, 1]]) relLabelPos[[i]] + 
      domains[[i, 1]], {i, 1, nContour}];
   (*Placement locations for the contour labels*)
   xyCoords = Table[contourFn[[i]][tCoords[[i]]], {i, 1, nContour}];
   (**Guide data**)
   (*Extreme coordinates of parameterized output*)
   xyCoordLo = 
    Table[contourFn[[i]][domains[[i, 1]]], {i, 1, nContour}];
   xyCoordHi = 
    Table[contourFn[[i]][domains[[i, 2]]], {i, 1, nContour}];
   (*Places numbers on the contours so they can be easily referred to*)
   contourMapLabelsNum = 
    Table[Text[
      Style["#" <> ToString[i], FontWeight -> fontWeight, 
       FontSize -> fontSize], xyCoords[[i]]],
     {i, 1, nContour}];
   contourMapLabelsLo = 
    Table[Text[
      Style[ToString["0"], FontWeight -> fontWeight, 
       FontSize -> fontSize], xyCoordLo[[i]]],
     {i, 1, nContour}];
   contourMapLabelsHi = 
    Table[Text[
      Style[ToString["1"], FontWeight -> fontWeight, 
       FontSize -> fontSize], xyCoordHi[[i]]],
     {i, 1, nContour}];
   contourMapLabels = 
    Join[contourMapLabelsNum, contourMapLabelsLo, contourMapLabelsHi];
   (*Actual contour labels*)
   contourLabels = Table[
     Text[
      Style[ToString[contourValues[[i]]], FontWeight -> fontWeight, 
       FontSize -> fontSize],
      xyCoords[[i]]],
     {i, 1, nContour}];
   (*Display guide*)
   Print[Show[contourPlot, 
     PlotLabel -> "Contour ordering - only for adjusting labels", 
     Epilog -> contourMapLabels]];
   (*Checks if contour label is desired or not*)
   whichContourLabels = Flatten[Position[onOrOff, True]];
   Show[contourPlot, Epilog -> contourLabels[[whichContourLabels]]]
   ];

Test case

Suppose we want to adjust the location of the labels on

contourPlot = ContourPlot[Exp[-(x^2 + y^2)], {x, -3, 3}, {y, -3, 3}, Contours -> 3]

To call the function, use

Module[{nContour, relLabelPos, onOrOff},
 nContour = 3;
 relLabelPos = 0.5*ConstantArray[1.,nContour];
 onOrOff = ConstantArray[True, nContour];
 contourLabelFn[contourPlot, nContour, relLabelPos, onOrOff]
]

The first part of the output is the guide

enter image description here

The #1, #2, #3 labels refer to the order of the contours. These number labels occur in the exact location the contour labels are placed. Note that the 0 and 1 overlap here, which will occur whenever a closed contour is parametrized.

The second part of the output is the actual contour plot itself

enter image description here

If we wanted to change the location of some/all the contour labels, or remove one of the labels entirely, we specify a different value for relLabelPos and/or onOrOff.

For example, to move the labels each to its own relative position, and to remove the contour labeled #1, use

Module[{nContour, relLabelPos, onOrOff}, nContour = 3;
 relLabelPos = {0.5, 0.3, 0.75};
 onOrOff = {False, True, True};
 contourLabelFn[contourPlot, nContour, relLabelPos, onOrOff]]

The outputs here are

enter image description here

and

enter image description here

Known Limitations

If a single contour line is not fully enclosed in the contour plot, as in the case of ContourPlot[x^2 + y^2, {x, -3, 3}, {y, -3, 3}, Contours -> 4, ContourLabels -> True]

output shown below

enter image description here

then the function won't work as is, but can be modified to accommodate this use case. The issue here is the number of curves extracted from this plot does not equal the number of contours.

$\endgroup$

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.