applying fill colors to a grid based on threshold value?

Hi Community
I've got a question about how to set the fill colors on a grid generated with surface() (or something similar).
For background, I've generated a temperature profile image using contourf() and overlaid a grid over top of it like so:
[X,Y]=meshgrid(xrange,yrange);
hold on;
plot(X,Y,'k');
plot(Y,X,'k');
(See attached figure.) Now I was hoping to generate another figure with the same grid, but with the squares filled with a color based on some threshold--i.e., if the temperature is above threshold in a grid region, fill color is red, and if temperature is below, the fill color is green. I'm not really sure how to do this, although some internet sleuthing pointed me to surface():
surface(X,Y,foo);
To test this I generated a figure with 'foo' as a binary matrix ( randi([0 1],length(X)) ) and I can get some fill colors. The problem is I don't know how to specify RGB colors in that matrix.
I suspect there's an easy solution to this and I'm just too new to MATLAB...
Thanks for the help!

 Respuesta aceptada

Adam Danz
Adam Danz el 1 de Feb. de 2021
Editada: Adam Danz el 2 de Feb. de 2021
I understand that you want to create a contour map with two levels of color to show values above and below a threshold within a grid.
The trickiest part is setting up the colorbar to show where the binary levels change at the threshold point.
This demo includes the original contour plot (subplot 1), a binary contour plot prior to applying the grid (subplot 2) and the gridded "contour" produced with imagesc() and the underlying contour lines for comparison.
See inline comments for details.
Generate basic contour plot
% Generate data
[X,Y,Z] = peaks(40);
X = (X-min(X(:)))/range(X(:))*24;
Y = (Y-min(Y(:)))/range(Y(:))*24;
% Create basic contour plot
figure()
contourf(X,Y,Z);
axis equal
set(gca,'xtick',0:2:24, 'ytick', 0:2:24)
grid on
colormap('jet')
colorbar()
cm = get(gca,'Colormap');
title('Original contour')
Replicate the contour plot with binary color scale
% Plot altered ZData
figure()
contourf(X,Y,Z);
axis equal
set(gca,'xtick',0:2:24, 'ytick', 0:2:24)
grid on
title('Bi-level contour')
% Define where colorbar changes based on the ZData (temperature)
zThreshold = 2.0;
% Create binary colorbar that changes at threshold
cbIncr = round(range(caxis)/100,1,'significant');
cbRng = [min(Z(:)), max(Z(:))];
cbSegments = unique([(zThreshold : -cbIncr : cbRng(1)),(zThreshold : cbIncr : cbRng(2))]);
cmap = [1 0 0] .* ones(numel(cbSegments)-1,1); % peak color
cmap(cbSegments<zThreshold,:) = [0 1 0] .*ones(sum(cbSegments<zThreshold),1); % trough color
colormap(gca,cmap)
cl = [min(cbSegments), max(cbSegments)];
caxis(cl)
cb = colorbar();
cb.Ticks = [min(cbSegments), zThreshold, max(cbSegments)];
Create grid and compute the mean z-value for each 2D bin
This mean can easily be replaced with the median, max, min, or whatever other statistic you want to use.
% Create bin edges
gridSpace = 2;
xg = min(X(:)) : gridSpace : max(X(:))+mod(max(X(:)), gridSpace);
yg = min(Y(:)) : gridSpace : max(Y(:))+mod(max(Y(:)), gridSpace);
% Identify the bin of each z-value
xbinID = discretize(X, xg);
ybinID = discretize(Y, yg);
% Average the values within bins
[~,~,groupID] = unique([xbinID(:),ybinID(:)],'rows','stable');
groupStats = groupsummary(Z(:), groupID, 'mean','IncludeEmptyGroups',true); % <--------- change statistic here
zdata = reshape(groupStats, numel(yg)-1, numel(xg)-1);
% Define where colorbar changes based on the ZData (temperature)
zThreshold = 2.0;
% Set zdata to high and low values based on threshold
zdata(zdata < zThreshold) = min(zdata(:));
zdata(zdata >= zThreshold) = max(zdata(:));
% Plot grided "contour"
figure()
imagesc(xg([1,end])+gridSpace./[2,-2], yg([1,end])+gridSpace./[2,-2], zdata)
axis equal
axis tight
set(gca,'xtick',0:2:24, 'ytick', 0:2:24,'ydir','normal')
colormap(gca,cmap) % See previous section for definition of cmap
caxis([min(cbSegments), max(cbSegments)])
cb = colorbar();
cb.Ticks = [min(cbSegments), zThreshold, max(cbSegments)];
caxis(cl) % see previous section for cl definition
title(sprintf('Grid means (%d unit grid)', gridSpace))
grid on
hold on
% Add grid lines; Note that this is unnecessary since the grid is on
xgridLines = [xg, repelem(xg(1),1,numel(yg)); xg, repelem(xg(end),1,numel(xg))];
ygridLines = [repelem(yg(1),1,numel(xg)), yg; repelem(yg(end),1,numel(xg)), yg];
plot(xgridLines, ygridLines, 'Color', [.2 .2 .2]);
% plot underlying contour for comparison
contour(X,Y,Z,'LineColor', 'k','LineStyle','--');

9 comentarios

Adam Danz
Adam Danz el 1 de Feb. de 2021
Editada: Adam Danz el 2 de Feb. de 2021
Here's the code in one easy-to-copy block
%% Generate basic contour plot
% Generate data
[X,Y,Z] = peaks(40);
X = (X-min(X(:)))/range(X(:))*24;
Y = (Y-min(Y(:)))/range(Y(:))*24;
% Create basic contour plot
subplot(1,3,1)
contourf(X,Y,Z);
axis equal
set(gca,'xtick',0:2:24, 'ytick', 0:2:24)
grid on
colormap('jet')
colorbar()
cm = get(gca,'Colormap');
title('Original contour')
%% Replicate the contour plot with binary color scale
% Plot altered ZData
subplot(1,3,2)
contourf(X,Y,Z);
axis equal
set(gca,'xtick',0:2:24, 'ytick', 0:2:24)
grid on
title('Bi-level contour')
% Define where colorbar changes based on the ZData (temperature)
zThreshold = 2.0;
% Create binary colorbar that changes at threshold
cbIncr = round(range(caxis)/100,1,'significant');
cbRng = [min(Z(:)), max(Z(:))];
cbSegments = unique([(zThreshold : -cbIncr : cbRng(1)),(zThreshold : cbIncr : cbRng(2))]);
cmap = [1 0 0] .* ones(numel(cbSegments)-1,1); % peak color
cmap(cbSegments<zThreshold,:) = [0 1 0] .*ones(sum(cbSegments<zThreshold),1); % trough color
colormap(gca,cmap)
cl = [min(cbSegments), max(cbSegments)];
caxis(cl)
cb = colorbar();
cb.Ticks = [min(cbSegments), zThreshold, max(cbSegments)];
%% Create grid and compute the mean z-value for each 2D bin
% This mean can easily be replaced with the median, max, min, or whatever other statistic you want to use.
% Create bin edges
gridSpace = 2;
xg = min(X(:)) : gridSpace : max(X(:))+mod(max(X(:)), gridSpace);
yg = min(Y(:)) : gridSpace : max(Y(:))+mod(max(Y(:)), gridSpace);
% Identify the bin of each z-value
xbinID = discretize(X, xg);
ybinID = discretize(Y, yg);
% Average the values within bins
[~,~,groupID] = unique([xbinID(:),ybinID(:)],'rows','stable');
groupStats = groupsummary(Z(:), groupID, 'mean','IncludeEmptyGroups',true); % <--------- change statistic here
zdata = reshape(groupStats, numel(yg)-1, numel(xg)-1);
% Define where colorbar changes based on the ZData (temperature)
zThreshold = 2.0;
% Set zdata to high and low values based on threshold
zdata(zdata < zThreshold) = min(zdata(:));
zdata(zdata >= zThreshold) = max(zdata(:));
% Plot grided "contour"
subplot(1,3,3)
imagesc(xg([1,end])+gridSpace./[2,-2], yg([1,end])+gridSpace./[2,-2], zdata)
axis equal
axis tight
set(gca,'xtick',0:2:24, 'ytick', 0:2:24,'ydir','normal')
colormap(gca,cmap) % See previous section for definition of cmap
caxis([min(cbSegments), max(cbSegments)])
cb = colorbar();
cb.Ticks = [min(cbSegments), zThreshold, max(cbSegments)];
caxis(cl) % see previous section for cl definition
title(sprintf('Grid means (%d unit grid)', gridSpace))
grid on
hold on
% Add grid lines; Note that this is unnecessary since the grid is on
xgridLines = [xg, repelem(xg(1),1,numel(yg)); xg, repelem(xg(end),1,numel(xg))];
ygridLines = [repelem(yg(1),1,numel(xg)), yg; repelem(yg(end),1,numel(xg)), yg];
plot(xgridLines, ygridLines, 'Color', [.2 .2 .2]);
% plot underlying contour for comparison
contour(X,Y,Z,'LineColor', 'k','LineStyle','--');
wow, thanks Adam!! That's a very detailed response. But I'm a bit surprised it's so complicated.
In my own playing around I was able to generate a two-color, randomly filled grid with just a few lines:
[X,Y]=meshgrid(xrange,yrange);
hold on;
plot(X,Y,'k');
plot(Y,X,'k');
foo=randi([0 1],length(X)) ;
surface(X,Y,foo);
I just assumed there was some easy way to replace the random 'foo' in the above with a matrix that is the result of the above/below threshold calculation and produces a green or red fill.
Perhaps there's a reason that it's not as simple as I imagine?
Thanks!
Well, there might be a simpler way to do it. Never under estimate the power of over-thinking 😎
Producing a 2D grid of colors isn't the hard part, as you've shown. The hard parts are 1) converting a 2D smooth contour to a 2D grid and 2) setting a binary color scheme that changes color at a specific value.
Here's another example of converting the color range of a contour plot to binary colors which solves the second task listed above, but not the first task.
FYI, I've updated the following section of the answer to improve flexibility of setting the gridSpace variable.
% Create bin edges
gridSpace = 2;
xg = min(X(:)) : gridSpace : max(X(:))+mod(max(X(:)), gridSpace);
yg = min(Y(:)) : gridSpace : max(Y(:))+mod(max(Y(:)), gridSpace);
% Identify the bin of each z-value
xbinID = discretize(X, xg);
ybinID = discretize(Y, yg);
% Average the values within bins
[~,~,groupID] = unique([xbinID(:),ybinID(:)],'rows','stable');
groupStats = groupsummary(Z(:), groupID, 'mean','IncludeEmptyGroups',true); % <--------- change statistic here
zdata = reshape(groupStats, numel(yg)-1, numel(xg)-1);
Dan Siegal
Dan Siegal el 2 de Feb. de 2021
Editada: Dan Siegal el 2 de Feb. de 2021
ha! that discretize() function is cool. did not know about that one.
I believe I've actually already achieved the averaging of values in bins in a separate and less elegant function I wrote:
xv2 = min(Xdata):0.25:max(Xdata);
yv2 = min(Ydata):0.25:max(Ydata);
[X2,Y2] = meshgrid(xv2,yv2);
Z2 = griddata(Xdata, Ydata, Zdata, X2, Y2);
interpolatedData=[X2(:), Y2(:), Z2(:)]';
% calculating temperature in a block between upper and lower x and y bounds
cond = xlower<=interpolatedData(1,:) & interpolatedData(1,:)<xupper & ylower<=interpolatedData(2,:) & interpolatedData(2,:)<yupper;
averageTemp= round(mean(interpolatedData(3,cond)),1);
this gets to the same thing as your method, right?
(this of course only does one bin at a time--the bin between xlower and xupper and ylower and yupper--which is why i had this in a function that i would call repeatedly. Your solution is definitely preferable. :)
Yeah, that looks like it would work too, although that approach could also be vectorized. I think you mean to switch those indicies around, don't you?
interpolatedData(1,:) --> interpolatedData(:,1)
interpolatedData(2,:) --> interpolatedData(:,2)
I transposed my interpolatedData :)
Have got a fondness for rows over columns for some reason :)
Ahhh good, I see the transpose now.
Generally, the best practice to organize data known as Tidy Data is a matrix where columns are different variables and rows are samples of those variables:
thanks!

Iniciar sesión para comentar.

Más respuestas (0)

Preguntada:

el 1 de Feb. de 2021

Comentada:

el 2 de Feb. de 2021

Community Treasure Hunt

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

Start Hunting!

Translated by