A few weeks ago a user posted a question on Matlab’s Answers forum, asking whether it is possible to display contour labels in the same color as their corresponding contour lines. In today’s post I’ll provide some insight that may assist users with similar customizations in other plot types.
Matlab does not provide, for reasons that escape my limited understanding, documented access to the contour plot’s component primitives, namely its contour lines, labels and patch faces. Luckily however, these handles are accessible (in HG2, i.e. R2014b onward) via undocumented hidden properties aptly named EdgePrims, TextPrims and FacePrims, as I explained in a previous post about contour plots customization, two years ago.
Let’s start with a simple contour plot of the peaks function:
[X,Y,Z] = peaks; [C,hContour] = contour(X,Y,Z, 'ShowText','on', 'LevelStep',1); |
The result is the screenshot on the left:
In order to update the label colors (to get the screenshot on the right), we create a short
updateContours
function that updates the TextPrims color to their corresponding EdgePrims color:The updateContours() function
function updateContours(hContour) % Update the text label colors drawnow % very important! levels = hContour.LevelList; labels = hContour.TextPrims; % undocumented/unsupported lines = hContour.EdgePrims; % undocumented/unsupported for idx = 1 : numel(labels) labelValue = str2double(labels(idx).String); lineIdx = find(abs(levels-labelValue)<10*eps, 1); % avoid FP errors using eps labels(idx).ColorData = lines(lineIdx).ColorData; % update the label color %labels(idx).Font.Size = 8; % update the label font size end drawnow % optional end |
Note that in this function we don’t directly equate the numeric label values to the contour levels’ values: this would work well for integer values but would fail with floating-point ones. Instead I used a very small 10*eps
tolerance in the numeric comparison.
Also note that I was careful to call drawnow at the top of the update function, in order to ensure that EdgePrims and TextPrims are updated when the function is called (this might not be the case before the call to drawnow). The final drawnow at the end of the function is optional: it is meant to reduce the flicker caused by the changing label colors, but it can be removed to improve the rendering performance in case of rapidly-changing contour plots.
Finally, note that I added a commented line that shows we can modify other label properties (in this case, the font size from 10 to 8). Feel free to experiment with other label properties.
Putting it all together
The final stage is to call our new updateContours
function directly, immediately after creating the contour plot. We also want to call updateContours
asynchronously whenever the contour is redrawn, for example, upon a zoom/pan event, or when one of the relevant contour properties (e.g., LevelStep or *Data) changes. To do this, we add a callback listener to the contour object’s [undocumented] MarkedClean event that reruns our updateContours
function:
[X,Y,Z] = peaks; [C,hContour] = contour(X,Y,Z, 'ShowText','on', 'LevelStep',1); % Update the contours immediately, and also whenever the contour is redrawn updateContours(hContour); addlistener(hContour, 'MarkedClean', @(h,e)updateContours(hContour)); |
Contour level values
As noted in my comment reply below, the contour lines (hContour.EdgePrims) correspond to the contour levels (hContour.LevelList).
For example, to make all negative contour lines dotted, you can do the following:
[C,hContour] = contour(peaks, 'ShowText','on', 'LevelStep',1); drawnow set(hContour.EdgePrims(hContour.LevelList<0), 'LineStyle', 'dotted'); |
Prediction about forward compatibility
As I noted on my previous post on contour plot customization, I am marking this article as “High risk of breaking in future Matlab versions“, not because of the basic functionality (being important enough I don’t presume it will go away anytime soon) but because of the property names: TextPrims, EdgePrims and FacePrims don’t seem to be very user-friendly property names. So far MathWorks has been very diligent in making its object properties have meaningful names, and so I assume that when the time comes to expose these properties, they will be renamed (perhaps to TextHandles, EdgeHandles and FaceHandles, or perhaps LabelHandles, LineHandles and FillHandles). For this reason, even if you find out in some future Matlab release that TextPrims, EdgePrims and FacePrims don’t exist, perhaps they still exist and simply have different names. Note that these properties have not changed their names or functionality in the past 3 years, so while it could well happen next year, it could also remain unchanged for many years to come. The exact same thing can be said for the MarkedClean event.
Professional assistance anyone?
As shown by this and many other posts on this site, a polished interface and functionality is often composed of small professional touches, many of which are not exposed in the official Matlab documentation for various reasons. So if you need top-quality professional appearance/functionality in your Matlab program, or maybe just a Matlab program that is dependable, robust and highly-performant, consider employing my consulting services.
Hi Yair:
Using the old matlab utilities it was possible to get the Cdata value associated with each line in the contour plot:
This utility was useful to dash the negative contours of some plot. Apparently there is no “cdata” value in the h.EdgePrims properties.
How do you think that one can proceed in order to get these values?
Regards,
Elbio
@Elbio – in HG2 (R2014b or newer) the contour lines (hContour.EdgePrims) correspond to the contour levels (hContour.LevelList). For example, to make all negative contour lines dotted, you can do the following:
I added an explanation and screenshot to the main article text above.
Hi Yair:
Many thanks for your helpful answer. I found a problem though when using contourf.
Instead of highlighting the zero line it is the “1” line the one that appears wider.
It might be related to the different size of the
levels
andlines
arrays. For example if I tried to change the linestyle using:there are dotted lines with positive values.
Regards,
Elbio.
Unlike contour, the contourf function returns a list of levels that includes the minimum value, which does not correspond to any contour line (
[-6.5466,-6,-5,-4,...]
rather than[-6,-5,-4,...]
). So the solution for contourf is simply to equatelevels(2:end)
withlines
, as follows:Hello Yair,
Note that if there are unused contour lines, as may be the case when
hContour.LevelListMode
is set tomanual
, the above code will not work, ashContour.EdgePrims
enumerates only the visible contour lines (while not hinting at the contour level they are related to), whilehContour.LevelList
enumerates them all.Instead, you must enumerate the visible contour lines:
Then use the
levels
generated in the code you supplied.Best regards,
Alon
@Alon
This method runs very slow for a complex contour (e.g. not necessarily in size but perhaps one generated from real, noisy, data), which led me to experiment with some other methods. In the case where contour levels are specified to the function, I found a heuristic that seems to work:
I’m sure this might fail on some other contour plots, but it has worked on several I generated. Thoughts?
You can also use
clabel(c,hContour,'FontSize',10,'Color',[1 1 1])
Hi,
Thank you so much for this improvement.
But when I use the function
ax=gca
, it will overrun theset(hContour.EdgePrims(hContour.LevelList<0),'LineStyle','dotted');
here is my code:
So do you have any solution for this?
Thanks
@Shan – The
ax.YDir='reverse'
command, and any other similar command that repaints the axes, reverts the EdgePrims to their default (documented) behavior. So after any such command, you need to redo the setting of the undocumented functionality (set(hContour.EdgePrims...)
Hello all,
First of all let me thank you for your posts they were very helpful.
I implemented a code to show contour labels with scientific notation in latex format “mantissa\cdot 10^{exponent}” based on your guidelines. It was very tedious as matlab resets these labels with many things (even not doing it when debugging the function for the plot but yes when invoking it).
Currently I reach the figures with contours seen with the abovementioned format. I can save them manually clicking in saveas but… when I try to save the figure automatically from a script using command , the figure in this case updates to the previous contour labels format.
Unbelievable! Did you find any solution for this?
Thanks!
It is not clear from your comment what command you’re trying to use. You basically have 4 alternatives: