I have a number of 2d probability mass functions from 2 categories. I am trying to plot the contours to visualise them (for example at their half height, but doesn't really matter).
I don't want to use contourf to plot directly because I want to control the fill colour and opacity. So I am using contourc to generate xy coordinates, and am then using fill with these xy coordinates.
The problem is that the xy coordinates from the contourc function have strange numbers in them which cause the following strange vertices to be plotted.
At first I thought it was the odd contourmatrix format, but I don't think it is this as I am only asking for one value from contourc. For example...
contourmatrix = contourc(x, y, Z, [val, val]);
h = fill(contourmatrix(1,:), contourmatrix(2,:), 'r');
Does anyone know why the contourmatrix has these odd values in them when I am only asking for one contour?
UPDATE:
My problem seems might be a failure mode of contourc when the input 2D matrix is not 'smooth'. My source data is a large set of (x,y) points. Then I create a 2D matrix with some hist2d function. But when this is noisy the problem is exaggerated...
But when I use a 2d kernel density function to result in a much smoother 2D function, the problem is lessened...
The full process is
a) I have a set of (x,y) points which form samples from a distribution
b) I convert this into a 2D pmf
c) create a contourmatrix using contourc
d) plot using fill
Your graphic glitches are because of the way you use the data from the ContourMatrix. Even if you specify only one isolevel, this can result in several distinct filled area. So the ContourMatrix may contain data for several shapes.
simple example:
isolevel = 2 ;
[X,Y,Z] = peaks ;
[C,h] = contourf(X,Y,Z,[isolevel,isolevel]);
Produces:
Note that even if you specified only one isolevel to be drawn, this will result in 2 patches (2 shapes). Each has its own definition but they are both embedded in the ContourMatrix, so you have to parse it if you want to extract each shape coordinates individually.
To prove the point, if I simply throw the full contour matrix to the patch function (the fill function will create patch objects anyway so I prefer to use the low level function when practical). I get the same glitch lines as you do:
xc = X(1,:) ;
yc = Y(:,1) ;
c = contourc(xc,yc,Z,[isolevel,isolevel]);
hold on
hp = patch(c(1,1:end),c(2,1:end),'r','LineWidth',2) ;
produces the same kind of glitches that you have:
Now if you properly extract each shape coordinates without including the definition column, you get the proper shapes. The example below is one way to extract and draw each shape for inspiration but they are many ways to do it differently. You can certainly compact the code a lot but here I detailed the operations for clarity.
The key is to read and understand how the ContourMatrix is build.
parsed = false ;
iShape = 1 ;
while ~parsed
%// get coordinates for each isolevel profile
level = c(1,1) ; %// current isolevel
nPoints = c(2,1) ; %// number of coordinate points for this shape
idx = 2:nPoints+1 ; %// prepare the column indices of this shape coordinates
xp = c(1,idx) ; %// retrieve shape x-values
yp = c(2,idx) ; %// retrieve shape y-values
hp(iShape) = patch(xp,yp,'y','FaceAlpha',0.5) ; %// generate path object and save handle for future shape control.
if size(c,2) > (nPoints+1)
%// There is another shape to draw
c(:,1:nPoints+1) = [] ; %// remove processed points from the contour matrix
iShape = iShape+1 ; %// increment shape counter
else
%// we are done => exit while loop
parsed = true ;
end
end
grid on
This will produce:
Related
I'd like to create a heat map to analyze the porosity of some specimens that I have 3D-printed. the X-Y coordinates are fixed since they are the positions in which the specimens are printed on the platform.
Heatmap:
Tbl = readtable('Data/heatmap/above.csv');
X = Tbl(:,1);
Y = Tbl(:,2);
porosity = Tbl(:,3);
hmap_above = heatmap(Tbl, 'X', 'Y', 'ColorVariable', 'porosity');
The first question is: how can I sort the Y-axis of the plot? since it goes from the lower value (top) to the higher value (bottom) and I need it the other way around.
The second question is: I only have around 22 data points and most of the chart is without color, so I'd like to get a smoother heatmap without the black parts.
The data set is quite simple and is shown below:
X
Y
porosity
74.4615
118.3773
0.039172163
84.8570
69.4699
0.046314637
95.2526
20.5625
0.041855213
105.6482
-28.3449
0.049796110
116.0438
-77.2522
0.045010692
25.5541
107.9817
0.038562053
35.9497
59.0743
0.041553065
46.3453
10.1669
0.036152061
56.7408
-38.7404
0.060719664
67.1364
-87.6478
0.037756115
-23.3533
97.5861
0.052840845
-12.9577
48.6787
0.045216851
-2.5621
-0.2286
0.033645353
7.8335
-49.1360
0.030670865
18.2290
-98.0434
0.024952472
-72.2607
87.1905
0.036199237
-61.8651
38.2831
0.026725885
-51.4695
-10.6242
0.029212058
-41.0739
-59.5316
0.028572611
-30.6783
-108.4390
0.036796151
-121.1681
76.7949
0.031688096
-110.7725
27.8876
0.034619855
-100.3769
-21.0198
0.039070101
-89.9813
-69.9272
NaN
-79.5857
-118.8346
NaN
If you want to assign color to the "black parts" you will have to interpolate the porosity over a finer grid than you currently have.
The best tool for 2D interpolation over a uniformly sampled grid is griddata
First you have to define the X-Y grid you want to interpolate over, and choose a suitable mesh density.
% this will be the number of points over each side of the grid
gridres = 100 ;
% create a uniform vector on X, from min to max value, made of "gridres" points
xs = linspace(min(X),max(X),gridres) ;
% create a uniform vector on Y, from min to max value, made of "gridres" points
ys = linspace(min(Y),max(Y),gridres) ;
% generate 2D grid coordinates from xs and ys
[xq,yq]=meshgrid(xs,ys) ;
% now interpolate the pososity over the new grid
InterpolatedPorosity = griddata(X,Y,porosity,xq,yq) ;
% Reverse the Y axis (flip the `yq` matrix upside down)
yq = flipud(yq) ;
Now my version of matlab does not have the heatmap function, so I'll just use pcolor for display.
% now display
hmap_above = pcolor(xq,yq,InterpolatedPorosity);
hmap_above.EdgeColor = [.5 .5 .5] ; % cosmetic adjustment
colorbar
colormap jet
title(['Gridres = ' num2str(gridres)])
And here are the results with different grid resolutions (the value of the gridres variable at the beginning):
Now you could also ask MATLAB to further graphically smooth the domain by calling:
shading interp
Which in the 2 cases above would yield:
Notes: As you can see on the gridres=100, you original data are so scattered that at some point interpolating on a denser grid is not going to produce any meaningful improvment. No need to go overkill on your mesh density if you do not have enough data to start with.
Also, the pcolor function uses the matrix input in the opposite way than heatmap. If you use heatmap, you have to flip the Y matrix upside down as shown in the code. But if you end up using pcolor, then you don't need to flip the Y matrix.
The fact that I did it in the code (to show you how to do) made the result display in the wrong orientation for a display with pcolor. Simply comment the yq = flipud(yq) ; statement if you stick with pcolor.
Additionally, if you want to be able to follow the isolevels generated by the interpolation, you can use contour to add a layer of information:
Right after the code above, the lines:
hold on
contour(xq,yq,InterpolatedPorosity,20,'LineColor','k')
will yield:
I am stuck with this problem for the last two days and haven't found a solution so far. I have a data in the following format:
x1, y1, val1
.. .. ..
.. .. ..
xn, yn, valn
The values val1, ..., valn are the field quantities I obtain after simulation on a geometry as below.
Only the grey region is the domain of interest whereas the one in blue/dark blue is not (including the inverted L shaped blue region in the interior). Thus the x and y coordinates of the data are scattered/irregular and with large gaps due to the hole in my original geometry. Is there a way to get a filled contour plot for this data? Trying the following in Matlab gives me triangulation with triangles outside the original polygon. Also, it fills the holes which is not what I want.
x = data(:,1);
y = data(:,2);
z = data(:,3);
%
dt = delaunayTriangulation(x,y) ;
tri = dt.ConnectivityList ;
xi = dt.Points(:,1) ;
yi = dt.Points(:,2) ;
F = scatteredInterpolant(x,y,z);
zI = F(xi,yi) ;
trisurf(tri,xi,yi,zI)
Another possibility was to import the data in ParaView and do filtering as Table-to-Points--> Delaunay Triangulation 2D. But this has the same problems as Matlab. The holes are not analytical to mask the unwanted interpolated regions with NaNs by using some mathematical expression.
Paraview seems to have the solution for this. Although I did not use finite elements to solve the pde, I could generate a finite element mesh inside GMsh for my geometry with holes. I then import both my CSV data file and the GMsh mesh file (in .vtk format) in ParaView. Resampling my field data with Dataset filter with the results of the Delaunay2D as the input gives me the contour only on the original geometry.
I am having difficulty with calculating 2D area of contours produced from a Kernel Density Estimation (KDE) in Matlab. I have three variables:
X and Y = meshgrid which variable 'density' is computed over (256x256)
density = density computed from the KDE (256x256)
I run the code
contour(X,Y,density,10)
This produces the plot that is attached. For each of the 10 contour levels I would like to calculate the area. I have done this in some other platforms such as R but am having trouble figuring out the correct method / syntax in Matlab.
C = contourc(density)
I believe the above line would store all of the values of the contours allowing me to calculate the areas but I do not fully understand how these values are stored nor how to get them properly.
This little script will help you. Its general for contour. Probably working for contour3 and contourf as well, with adjustments of course.
[X,Y,Z] = peaks; %example data
% specify certain levels
clevels = [1 2 3];
C = contour(X,Y,Z,clevels);
xdata = C(1,:); %not really useful, in most cases delimters are not clear
ydata = C(2,:); %therefore further steps to determine the actual curves:
%find curves
n(1) = 1; %n: indices where the certain curves start
d(1) = ydata(1); %d: distance to the next index
ii = 1;
while true
n(ii+1) = n(ii)+d(ii)+1; %calculate index of next startpoint
if n(ii+1) > numel(xdata) %breaking condition
n(end) = []; %delete breaking point
break
end
d(ii+1) = ydata(n(ii+1)); %get next distance
ii = ii+1;
end
%which contourlevel to calculate?
value = 2; %must be member of clevels
sel = find(ismember(xdata(n),value));
idx = n(sel); %indices belonging to choice
L = ydata( n(sel) ); %length of curve array
% calculate area and plot all contours of the same level
for ii = 1:numel(idx)
x{ii} = xdata(idx(ii)+1:idx(ii)+L(ii));
y{ii} = ydata(idx(ii)+1:idx(ii)+L(ii));
figure(ii)
patch(x{ii},y{ii},'red'); %just for displaying purposes
%partial areas of all contours of the same plot
areas(ii) = polyarea(x{ii},y{ii});
end
% calculate total area of all contours of same level
totalarea = sum(areas)
Example: peaks (by Matlab)
Level value=2 are the green contours, the first loop gets all contour lines and the second loop calculates the area of all green polygons. Finally sum it up.
If you want to get all total areas of all levels I'd rather write some little functions, than using another loop. You could also consider, to plot just the level you want for each calculation. This way the contourmatrix would be much easier and you could simplify the process. If you don't have multiple shapes, I'd just specify the level with a scalar and use contour to get C for only this level, delete the first value of xdata and ydata and directly calculate the area with polyarea
Here is a similar question I posted regarding the usage of Matlab contour(...) function.
The main ideas is to properly manipulate the return variable. In your example
c = contour(X,Y,density,10)
the variable c can be returned and used for any calculation over the isolines, including area.
I have a question about using the area function; or perhaps another function is in order...
I created this plot from a large text file:
The green and the blue represent two different files. What I want to do is fill in the area between the red line and each run, respectively. I can create an area plot with a similar idea, but when I plot them on the same figure, they do not overlap correctly. Essentially, 4 plots would be on one figure.
I hope this makes sense.
Building off of #gnovice's answer, you can actually create filled plots with shading only in the area between the two curves. Just use fill in conjunction with fliplr.
Example:
x=0:0.01:2*pi; %#initialize x array
y1=sin(x); %#create first curve
y2=sin(x)+.5; %#create second curve
X=[x,fliplr(x)]; %#create continuous x value array for plotting
Y=[y1,fliplr(y2)]; %#create y values for out and then back
fill(X,Y,'b'); %#plot filled area
By flipping the x array and concatenating it with the original, you're going out, down, back, and then up to close both arrays in a complete, many-many-many-sided polygon.
Personally, I find it both elegant and convenient to wrap the fill function.
To fill between two equally sized row vectors Y1 and Y2 that share the support X (and color C):
fill_between_lines = #(X,Y1,Y2,C) fill( [X fliplr(X)], [Y1 fliplr(Y2)], C );
You can accomplish this using the function FILL to create filled polygons under the sections of your plots. You will want to plot the lines and polygons in the order you want them to be stacked on the screen, starting with the bottom-most one. Here's an example with some sample data:
x = 1:100; %# X range
y1 = rand(1,100)+1.5; %# One set of data ranging from 1.5 to 2.5
y2 = rand(1,100)+0.5; %# Another set of data ranging from 0.5 to 1.5
baseLine = 0.2; %# Baseline value for filling under the curves
index = 30:70; %# Indices of points to fill under
plot(x,y1,'b'); %# Plot the first line
hold on; %# Add to the plot
h1 = fill(x(index([1 1:end end])),... %# Plot the first filled polygon
[baseLine y1(index) baseLine],...
'b','EdgeColor','none');
plot(x,y2,'g'); %# Plot the second line
h2 = fill(x(index([1 1:end end])),... %# Plot the second filled polygon
[baseLine y2(index) baseLine],...
'g','EdgeColor','none');
plot(x(index),baseLine.*ones(size(index)),'r'); %# Plot the red line
And here's the resulting figure:
You can also change the stacking order of the objects in the figure after you've plotted them by modifying the order of handles in the 'Children' property of the axes object. For example, this code reverses the stacking order, hiding the green polygon behind the blue polygon:
kids = get(gca,'Children'); %# Get the child object handles
set(gca,'Children',flipud(kids)); %# Set them to the reverse order
Finally, if you don't know exactly what order you want to stack your polygons ahead of time (i.e. either one could be the smaller polygon, which you probably want on top), then you could adjust the 'FaceAlpha' property so that one or both polygons will appear partially transparent and show the other beneath it. For example, the following will make the green polygon partially transparent:
set(h2,'FaceAlpha',0.5);
You want to look at the patch() function, and sneak in points for the start and end of the horizontal line:
x = 0:.1:2*pi;
y = sin(x)+rand(size(x))/2;
x2 = [0 x 2*pi];
y2 = [.1 y .1];
patch(x2, y2, [.8 .8 .1]);
If you only want the filled in area for a part of the data, you'll need to truncate the x and y vectors to only include the points you need.
I have a custom function which returns either 0 or 1 depending on two given inputs:
function val = myFunction(val1, val2)
% logic to determine if val=1 or val=0
end
How can I create a contour plot of the function over the x,y coordinates generated by the following meshgrid?
meshgrid(0:.5:3, 0:.5:3);
This plot will just simply display where the function is 0 or 1 on the contour map.
If your function myFunction is not designed to handle matrix inputs, then you can use the function ARRAYFUN to apply it to all the corresponding entries of x and y:
[x,y] = meshgrid(0:0.5:3); %# Create a mesh of x and y points
z = arrayfun(#myFunction,x,y); %# Compute z (same size as x and y)
Then you could use the function CONTOUR to generate a contour plot for the above data. Since your z data only has 2 different values, it would probably make sense for you to only plot one contour level (which would be at a value of 0.5, halfway between your two values). You might also want to instead use the function CONTOURF, which produces color-filled contours that will clearly show where the ones and zeroes are:
contourf(x,y,z,1); %# Plots 1 contour level, filling the area on either
%# side with different color
NOTE: Since you are plotting data that only has ones and zeroes, plotting contours may not be the best way to visualize it. I would instead use something like the function IMAGESC, like so:
imagesc(x(1,:),y(:,1),z);
Keep in mind the y-axis in this plot will be reversed relative to the plot generated by CONTOURF.
The following will do it:
function bincontour
clear; clc;
xrange = 0:.5:3;
yrange = 1:.5:5;
[xmesh, ymesh] = meshgrid(xrange, yrange);
z = arrayfun(#myFunction, xmesh, ymesh);
contourf(xrange, yrange, z, 5)
end
function val = myFunction(val1, val2)
val = rand() > 0.5;
end