The Woes of contourf patches, findobj, and Matlab 2014b.

Hello all, thanks in advance for help.
With the recent update to OS X Yosemite, and of course the required update to MATLAB 2014b, AND the graphics changes to 2014b, I am experiencing some problems with our legacy code.
Background: We have a script called cpatch.m (see below), which [before updating] used
findobj(gca,'type','patches');
on a contourf plot to recolor the patches so that they were a specific color corresponding to a specific contour interval (necessary for the accuracy of our science).
Problem: Contourf in 2014b creates an handle which no longer has any children. It used to be the case that hidden in the children, were the individual patches that one could recolor. Now, these patches do not exist, so one cannot recolor them. In MATLAB:
patches = findobj(gca,'type','patch')
returns
0x0 empty GraphicsPlaceholder array.
and hence is useless.
Ideal Solution: Can someone please tell me how to access the individual patches so that I can change their face color?
Code:
clev = 0:.2:1; % These are the contour levels
colors = bone(length(clev)-1);
cpt = [clev(1:end-1)' clev(2:end)'];
[C,H] = contourf(rand(10,10),clev);
patches = findobj(gca,'type','patch');
for ipatches=1:length(patches)
c = get(patches(ipatches),'cdata');
if length(c) <= 1
ii = find(cpt(:,1) <= c);
iwant = max(ii);
if length(iwant) >= 1
set(patches(ipatches),'facecolor',colors(iwant,:));
end
end
end
Please help! Thanks!

Answers (1)

Can I recommend contourfcmap? I believe it does exactly what your code used to do (assign precise colors to the shaded intervals in a contourf plot), and it works in R2014b.
If you look under the hood of that function, you can find the workaround to extract and recolor. Pre-2014b, contour objects were composed of patches. In 2014b, their underlying polygon components are TriangleStrips, rather than simple patches, and their properties are mostly hidden from the user. But you can still hack into it.
[X,Y,Z] = peaks;
[l, h] = contourf(X,Y,Z,4);
h.FacePrims(1).ColorData = uint8([255 0 0 255]'); % change first interval to red

12 Comments

Thanks, looks like the second option works [mostly]. I haven't checked out the contourfcmap yet, but the problem with the second option you've given me is that when I try to save the figure with export_fig from the FEX, the colors revert to the default colormap, which is sucks. Ideas?
I'll take a look at contourfcmap in the meantime.
Yes, the reverting-on-print thing seems to be pervasive in 2014b... If you try to make custom changes to certain objects in a way that "unlinks" the low-level object from it's original properties (such as changing the face color of a contour, or resizing a marker in a legend), that change gets undone when you call print (or anything that uses print, including saveas, export_fig, etc). Definitely a bug in my mind, though the Mathworks may not see it that way. Unfortunately, the actual reverting/relinking happens on a line of code in print.m that calls an inaccessible p-function, so I haven't been able to find a good way to stop it.
Two solutions:
  1. Save the figure using a screen capture (e.g. getframe). With the improved screen rendering in 2014b, this isn't as terrible an option as in older versions, but still not ideal, especially if you prefer to save to vector graphics.
  2. Don't rely on a Contour object. Instead, plot actual lines and patches, like the pre-2014b contourf used to. I offer this option in contourfcmap.m.
Thanks again on getting back to me. I tried using contourfcmap, and it looks like a great function. However, and I'm not really sure why, regardless of the "method" option I choose, colors do not persist.
Additionally, I'm working with Hovmoller plots, so my x-axis is matlab date/time, which really screws up the xyz variable in your code. To remedy this, I have to plot x=1:xend, and then use set(gca,'xdata',dates), which has the effect of reverting the color same. I think I'm doomed to making 2012a limp along unless you have any other ideas.
Thanks again for all your help with this.
Interesting. You're right, using datenumbers along the x-axis wreaks havoc with the recolor method in 2014b... I'll have to look into that more. But if you use the calccontour method, neither the dates nor the color-reverting should be an issue.
If you do see color-reverting, the function must be reverting to the default recolor for some reason. Do you have the Mapping Toolbox? If so, perhaps it's a syntax issue? Can you show how you've set up your function call?
I just tested it again with the following example:
z = peaks(100);
t = datenum(2000,1,1) + (1:100);
y = 1:100;
clev = [-5 -3 -2:.5:2 3 5];
cmap = jet(12);
ax(1) = subplot(4,1,1);
[c,h] = contour(t,y,z, clev);
% pcolor(t,y,z); shading flat;
datetick('x');
title('contour');
ax(2) = subplot(4,1,2);
hc = contourfcmap(t,y,z,clev,cmap, 'method', 'calccontour');
datetick('x');
title('countourfcmap, calccontour');
try
ax(3) = subplot(4,1,3);
hc = contourfcmap(t,y,z,clev,cmap, 'method', 'recolor');
datetick('x');
catch
text(mean(get(ax(3),'xlim')), mean(get(ax(3),'ylim')), ...
'FAILED','fontsize', 30, 'horiz', 'center');
end
title('contourfcmap, recolor, x=dates');
ax(4) = subplot(4,1,4);
hc = contourfcmap(1:100,y,z,clev,cmap, 'method', 'recolor');
title('contourfcmap, recolor, x=1:nx');
set(ax(1:3), 'xlim', [t(1) t(end)]);
im = frame2im(getframe(gcf));
imwrite(im, 'test1.png', 'png');
export_fig('test2', gcf, '-png', '-nocrop');
Here's the result of the screenshot:
And here's the exported version:
As you can see, I'm replicating the bug with dates and recolor, and also experiencing the color-revert-on-print with the recolor version. But the version using calccontour properly renders both on screen and when printed.
Can you let me know if you see different results running this code snippet? And I'll look into the date bug.
Thanks again! Running the code snippets produced identical results.
When I try to run my code, I ran into a bunch of errors, so I ended up having to use no NaN values and set them to a minimal value and allow contourfcmap to use the 'lo' parameter to white them out (which isn't working).
I've attached some made up sample data and a very hacked up version of my code so you can see the results I get. It plots and retains colors which is great, but it seems the plot doesn't line up with the correct values on the x- or y-axes, and the blue box that you will see above the plotted data should be filled with the low value (white), but it is not.
Thanks for all of your help!
Code:
% Setup Plot
WD = 7.5; HT=8.5; txtSize=12;
set(gcf,'DefaultAxesFontSize',txtSize,'DefaultTextFontSize',txtSize)
set(gcf, 'Units', 'inches', 'PaperPosition', [5,5,WD,HT]);
set(gcf, 'Position', [5 5 WD HT]);
lmarg = .85; tmarg = .3; rmarg = .5; bmarg = 1; vspace = 0.10;
height = (HT-4*vspace-tmarg-bmarg)/5; width = WD-lmarg-rmarg;
xshift = lmarg; yshift = HT - tmarg - height;
% Load Files
load ~/Desktop/test_data.mat;
% Setup axes
fprintf('Plotting...');
position = [xshift yshift width height];
handle = axes('Units','Inches','Position',position);
% Contour
HC = contourfcmap(t,y,z,clev,cmap,'lo',[0 0 0],'hi',cmap(end,:),'method','calccontour');
% Xticks & Text
datetick('x',28,'keeplimits');
title('Makeshift Title');
ylabel('Distance (km)');
fprintf(' Done!\n');
% Save Figure
shg;
set(gcf,'color','w');
export_fig ~/Desktop/test1.pdf '-pdf' '-painters'
Result:
I assume the portion that used to be NaNs is that upper triangular bit? When I look at your data, it seems those values are near 0 (-3.1415e-05). But you specify contour levels ranging from -45 to 50, so that value doesn't qualify as low (and instead is being shaded by the light blue you prescribed to the -5-0 interval).
I added the line
z(z == z(end,1)) = -999;
immediately after loading the .mat file, and now when plotted that region will be properly categorized a low.
As for the values lining up, I think you're just referring to the changed axis x-limits. That's just due to differing default behavior between contour and patch; contour sets the axis limits tight to the data, while patch allows a little space around the plotted objects. Adding
axis tight;
before you call datetick should fix that.
With those two small changes, I think the result is what you want:
-
I'll add the NaN problem to my bug list for contourfcmap.
Also, sorry the calccontour method is so slow for this particular example... the polygon-intersection calculation gets pretty resource-intensive with this many contour lines. Might be able to speed that up a lot if I call gpcmex directly rather than using polybool... or perhaps I need to rethink the algorithm there.
Ahh, yes. facepalm I think that another cup of coffee would have made me realize that I was being stupid about the low value.
Thanks for your help, I'll give this a try and report back. If not, I did manage to get 2012a to "work" on Yosemite.
Yup. That all worked. Thanks again for all of your help with this! I hope that it wasn't too much trouble.
No problem. That example was very handy to work out a few remaining bugs in the function. NaNs are now handled properly (with all options), I changed the axis properties to mimic contour behavior with the calccontour method (tight axis limits, axis layered on top of patches), I found the source of the datenumber issue (TriangleStrip vertex coordinates are normalized to 1 when larger values are involved), and switching from polybool to directly calling gpcmex did indeed speed things up considerably.
Kelly,
You mentioned the datenumber issue in this comment. I was wondering if you found a way to override the normalization? I'm having issues with converting some code to 2014B, I end up getting contour plots that are blocks of data instead of curves. Even using the set(gca,'ydata',dateNumData) results in the block data.
Very informative post and answers. I will be looking at your contourfcmap code to see if it would be a good replacement for our current code. Thanks.
If by blocks of data, do you mean the contour lines seem to zig-zag in pixelated manner based on the underlying data resolution?
[x,y,z] = peaks(100);
dn = now;
clev = -8:8;
c = contourcs(x(1,:)+dn,y(:,1),z);
ax(1) = subplot(2,2,1);
contour(x+dn,y,z,clev);
ax(2) = subplot(2,2,2);
contourf(x+dn,y,z,clev);
ax(3) = subplot(2,2,3);
hold on;
arrayfun(@(X) plot(X.X, X.Y), c);
linkaxes(ax, 'x');
If so, then no, I haven't found a way around that behavior, which seems to be a bug in R2014b contour and contourf. Luckily, it does seem to be resolved in 2015a.
With contourfcmap, using the calccontour method will avoid the bug, since it plots lines using the output of contourc, and the zig-zaggy bug doesn't seem to manifest itself in countourc.
This zig-zag look is exactly what I am seeing. I will try in 2015a and avoid using 2014b. Thank you for your insight and quick response.

Sign in to comment.

Categories

Find more on Creating, Deleting, and Querying Graphics Objects in Help Center and File Exchange

Products

Asked:

on 12 Jan 2015

Commented:

on 27 Mar 2015

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!