Matlab: Why is the fill function not filling the area between two circles? - matlab

I'm trying to fill the intersecting area between two circles in Matlab. I've quite literally copied and pasted this piece of code from this article on Matlab Central.
t = linspace(0, 2*pi, 100);
cir = #(r,ctr) [r*cos(t)+ctr(1); r*sin(t)+ctr(2)]; % Circle Function
c1 = cir(1.0, [0; 0]);
c2 = cir(1.5, [1; 1]);
in1 = find(inpolygon(c1(1,:), c1(2,:), c2(1,:), c2(2,:))); % Circle #1 Points Inside Circle #2
in2 = find(inpolygon(c2(1,:), c2(2,:), c1(1,:), c1(2,:))); % Circle #2 Points Inside Circle #1
[fillx,ix] = sort([c1(1,in1) c2(1,in2)]); % Sort Points
filly = [c1(2,in1) (c2(2,in2))];
filly = filly(ix);
figure(1)
plot(c1(1,:), c1(2,:))
hold on
plot(c2(1,:), c2(2,:))
fill([fillx fliplr(fillx)], [filly fliplr(filly)], 'g', 'EdgeColor','none')
hold off
axis square
What I end up with is the following image:
However, it should appear as this image:
Why is the area not being filled as it is in the example article?

If you have Mapping Toolbox you can use polybool to find the intersection between to polygones, and than patch (which dosen't require Mapping Toolbox, and is better than fill) to draw it. The folowing code works even without the first 2 lines that use poly2cw, but it issues some warnings. This can be solved with the poly2cw trasfomation:
[c1(1,:), c1(2,:)] = poly2cw(c1(1,:), c1(2,:)); % clock-wise transform
[c2(1,:), c2(2,:)] = poly2cw(c2(1,:), c2(2,:)); % clock-wise transform
[xb, yb] = polybool('intersection',c1(1,:),c1(2,:),c2(1,:), c2(2,:));
plot(c1(1,:), c1(2,:))
hold on
plot(c2(1,:), c2(2,:))
patch(xb, yb, 1, 'FaceColor', 'g','EdgeColor','none')
axis equal

The code in the question does not work because it has some mistake in the order of the points in fillx and filly. We can see that if we set the 'EdgeColor' to be visible, and follow the peripheral line of the patch (see below, here reduced to 20 points, for the illustration). We can clearly see that the point of the polygon to be filled are ordered in a 'zig-zag' between the circles, so it has no area at all. I have numbered the vertices of the polygon taken from each circle to demonstrate in what order the fill function read them.
In order to fill with color all the intersection between the circles, we need to define the 'polygon' by the points on the circles that intersect (in1 and in2) in the right order. This means we want them to make a closed shape if an imaginary pencil is drawing a line between them in the same order they are given. Like this:
We start with 1 in one of the circles and continue until the numbers on that circle are ended, and then move to 1 on the other circle, and when we reach the end in the second circle we close the polygon by connecting the last point to the first one. As you can see in both figure above, the starting point and the end point of the circles are really close, so we get 2 numbers above each other.
How can we order the points correctly?
We start by getting in1 and in2 as described in the question. Let's take a look at in1:
in1 =
1 2 3 4 5 6 7 19 20
Those are the indices of the points from c1 to be taken, they appear to be in order, but contains a gap. This gap is because inpolygon checks the points by the order in c1, and the starting point of c1 is within the intersection region. So we get the first 7 points, then we are out of the intersection, and we go back in as we reach points 19 and 20. However, for our polygon, we need this points to start from the closest point to one of that places where the circle intersect and to go around the circle until we reach the second point of intersection.
To do that, we look for the 'gap' in the points order:
gap = find(diff(in1)>1);
and reorder them correctly:
X1 = [c1(1,in1(gap+1:end)) c1(1,in1(1:gap))];
Y1 = [c1(2,in1(gap+1:end)) c1(2,in1(1:gap))];
But, there may be no 'gap', as we see in in2:
in2 =
11 12 13 14
So we need to wrap this within an if to check if the points need to be reordered:
if ~isempty(gap)
X1 = [c1(1,in1(gap+1:end)) c1(1,in1(1:gap))];
Y1 = [c1(2,in1(gap+1:end)) c1(2,in1(1:gap))];
else
X1 = c1(1,in1);
Y1 = c1(2,in1);
end
Now all we need to do it to concatenate X1 and X2 (for circle 2), and the same for the Ys, and use patch (which is like fill, but better) to draw it:
patch([X1 X2],[Y1 Y2],'g','EdgeColor','none')
With 20 points the circle is not really a circle and the intersection is partially colored, so here is the full code and the result with 200 points:
t = linspace(0, 2*pi, 200);
cir = #(r,ctr) [r*cos(t)+ctr(1); r*sin(t)+ctr(2)]; % Circle Function
c1 = cir(1.0, [0; 0]);
c2 = cir(1.5, [1; 1]);
plot(c1(1,:), c1(2,:))
hold on
plot(c2(1,:), c2(2,:))
axis equal
in1 = find(inpolygon(c1(1,:), c1(2,:), c2(1,:), c2(2,:)));
in2 = find(inpolygon(c2(1,:), c2(2,:), c1(1,:), c1(2,:)));
gap = find(diff(in1)>1);
if ~isempty(gap)
X1 = [c1(1,in1(gap+1:end)) c1(1,in1(1:gap))];
Y1 = [c1(2,in1(gap+1:end)) c1(2,in1(1:gap))];
else
X1 = c1(1,in1);
Y1 = c1(2,in1);
end
gap = find(diff(in2)>1);
if ~isempty(gap)
X2 = [c2(1,in2(gap+1:end)) c2(1,in2(1:gap))];
Y2 = [c2(2,in2(gap+1:end)) c2(2,in2(1:gap))];
else
X2 = c2(1,in2);
Y2 = c2(2,in2);
end
patch([X1 X2],[Y1 Y2],'g','EdgeColor','none')
hold off
All mentioned above could be replace with the use of convhull on the vertices that intersect and give the same result:
x = [c1(1,in1) c2(1,in2)]; % all x's for intersecting vertices
y = [c1(2,in1) c2(2,in2)]; % all y's for intersecting vertices
k = convhull(x,y); % calculate the convex polygon
patch(x(k),y(k),'g','EdgeColor','none')

Related

How to divide the area outside a polygon between left and right with respect to a certain given value in MATLAB?

I have another blocking problem, I extracted the x and y coordinates outside the polygon, when plotting them the result is correct, however, I want to extract x and y coordinates on the right side outside the polygon(considered for x < 1078), and the x and y coordinates on the right side outside the polygon otherwise, however, when running this code (where I think it is a more logical one), it's dividing the whole set of coordinates (in and out of the polygon) between left and right.
This's my code, and the resulted images, the first one is the resulted plot of my code, and the second one is the initial polygon with the inside and outside areas, and if any one has a better idea I will appreciate their help, thanks a lot.
%% Check inpolygon function
nb_lignes = size(image_in_ref,1);
nb_colonnes = size(image_in_ref,2);
[Xq,Yq] = meshgrid(1:nb_colonnes,1:nb_lignes);%grille du cube
[in,on] = inpolygon(Xq,Yq,X_ref,Y_ref);
out = ~inpolygon(Xq,Yq,X_ref,Y_ref);
f = numel(Xq(in));
p = numel(Xq(on));
X_out = Xq(~in);
Y_out = Yq(~in);
figure
plot(X_ref,Y_ref) % polygon
axis equal
hold on
plot(Xq(in),Yq(in),'r-') % points inside
plot(X_out,Y_out,'b:') % points outside
plot(out,'y')
hold off
%% Cocatenate X_out and Y_out into one table
My_Matrix_out=horzcat(X_out(:),Y_out(:));
colNames = {'x_out','y_out'};
sTable_out = array2table(My_Matrix_out,'VariableNames',colNames);
%% Divide between left and right side limits
LeftTable_out = sTable_out(sTable_out.x_out< 1078, :);
RightTable_out = sTable_out(sTable_out.x_out >= 1078 , :);
%% Extract X and Y coordinates from each table separately
x_Left_out = LeftTable_out{:,'x_out'}; % or LeftTable{:,1}
y_Left_out = LeftTable_out{:,'y_out'}; % or LeftTable{:,2}
x_Right_out = RightTable_out{:,'x_out'};
y_Right_out = RightTable_out{:,'y_out'};
%% Plot Each Part Separately
figure
Left_Side_out =plot(x_Left_out,y_Left_out, 'r-');
hold on;
Right_Side_out=plot(x_Right_out, y_Right_out , 'g:');
The resulted plot of the posted code
The polygon with the inside and outside areas

Plotting circles with complex numbers in MATLAB

I want to make a figure in MATLAB as described in the following image
What I did is the following:
x = [1 2 3];
y = [2 2 4];
radius = [1 1.2 2.2];
theta = [-pi 0 pi];
figure;
scatter(x,y,radius)
How do I add an angle theta to the plot to represent a complex number z = radius.*exp(1j*theta) at every spacial coordinates?
Technically speaking, those are only circles if x and y axes are scaled equally. That is because scatter always plots circles, independently of the scales (and they remain circles if you zoom in nonuniformly. + you have the problem with the line, which should indicate the angle...
You can solve both issues by drawing the circles:
function plotCirc(x,y,r,theta)
% calculate "points" where you want to draw approximate a circle
ang = 0:0.01:2*pi+.01;
xp = r*cos(ang);
yp = r*sin(ang);
% calculate start and end point to indicate the angle (starting at math=0, i.e. right, horizontal)
xt = x + [0 r*sin(theta)];
yt = y + [0 r*cos(theta)];
% plot with color: b-blue
plot(x+xp,y+yp,'b', xt,yt,'b');
end
having this little function, you can call it to draw as many circles as you want
x = [1 2 3];
y = [2 2 4];
radius = [1 1.2 2.2];
theta = [-pi 0 pi];
figure
hold on
for i = 1:length(x)
plotCirc(x(i),y(i),radius(i),theta(i))
end
I went back over scatter again, and it looks like you can't get that directly from the function. Hopefully there's a clean built-in way to do this, and someone else will chime in with it, but as a backup plan, you can just add the lines yourself.
You'd want a number of lines that's the same as the length of your coordinate set, from the center point to the edge at the target angle, and fortunately 'line' does multiple lines if you feed it a matrix.
You could just tack this on to the end of your code to get the angled line:
x_lines = [x; x + radius.*cos(theta)];
y_lines = [y; y + radius.*sin(theta)];
line(x_lines, y_lines, 'Color', 'b')
I had to assign the color specifically, since otherwise 'line' makes each new line cycle through the default colors, but that also means you could easily change the line color to stand out more. There's also no center dot, but that'd just be a second scatter plot with tiny radius. Should plot most of what you're looking for, at least.
(My version of Matlab is old enough that scatter behaves differently, so I can only check the line part, but they have the right length and location.)
Edit: Other answer makes a good point on whether scatter is appropriate here. Probably better to draw the circle too.

How to generate random positions with distance between them?

Assume that I have a vector with 6 distance elements as
D = [10.5 44.8 30.01 37.2 23.4 49.1].
I'm trying to create random pair positions of a given distances, inside a 200 meters circle. Note that the distance D created by using (b - a).*rand(6,1) + a, with a = 10 and b = 50 in Matlab. I do not know how to generate the random pairs with given the distances.
Could anybody help me in generating this kind of scenario?
This is an improvement to Alessiox's answer. It follows same logic, first generate a set of points ([X1 Y1]) that have at least distance D from the main circle border, then generate the second set of points ([X2 Y2]) that have exact distance D from the first set.
cx = 50; cy = -50; cr = 200;
D = [10.5 44.8 30.01 37.2 23.4 49.1]';
n = numel(D);
R1 = rand(n, 1) .* (cr - D);
T1 = rand(n, 1) * 2 * pi;
X1 = cx+R1.*cos(T1);
Y1 = cy+R1.*sin(T1);
T2 = rand(n, 1) * 2 * pi;
X2 = X1+D.*cos(T2);
Y2 = Y1+D.*sin(T2);
You can tackle the problem using a two-steps approach. You can
randomly generate the first point and then you can consider the circle whose center is that point and whose radius is the distance in D
once you did draw the circle, every point lying on that circle will have distance D from the first point previously created and by random selection of one of these candidates you'll have the second point
Let's see with an example: let's say you main circle has radius 200 and its center is (0,0) so we start by declaring some main variables.
MAINCENTER_x=0;
MAINCENTER_y=0;
MAINRADIUS=200;
Let's now consider the first distance, D(1)=10.5 and we now generate the first random point which (along with its paired point - I reckon you don't want one point inside and the other outside of the main circle) must lie inside the main circle
r=D(1); % let's concentrate on the first distance
while true
x=((MAINRADIUS-2*r) - (-MAINRADIUS+2*r))*rand(1,1) + (-MAINRADIUS+2*r);
y=((MAINRADIUS-2*r) - (-MAINRADIUS+2*r))*rand(1,1) + (-MAINRADIUS+2*r);
if x^2+y^2<=(MAINRADIUS-2*r)^2
break;
end
end
and at the end of this loop x and y will be our first point coordinates.
Now we shall generate all its neighbours, thus several candidates to be the second point in the pair. As said before, these candidates will be the points that lie on the circle whose center is (x,y) and whose radius is D(1)=10.5. We can create these candidates as follows:
% declare angular spacing
ang=0:0.01:2*pi;
% build neighbour points
xp=r*cos(ang)+x;
yp=r*sin(ang)+y;
Now xp and yp are two vectors that contain, respectively, the x-coordinates and y-coordinates of our candidates, thus we shall now randomly select one of these
% second point
idx=randsample(1:length(xp),1);
secondPoint_x=xp(idx);
secondPoint_y=yp(idx);
Finally, the pair (x,y) is the first point and the pair (secondPoint_x, secondPoint_y) is our second point. The following plot helps summarizing these steps:
The red circle is the main area (center in (0,0) and radius 200), the red asterisk is the first point (x,y) the blue little circle has center (x,y) and radius 10.5 and finally the black asterisk is the second point of the pair (secondPoint_x, secondPoint_y), randomly extracted amongst the candidates lying on the blue little circle.
You must certainly can repeat the same process for all elements in D or rely on the following code, which does the very same thing without iterating through all the elements in D.
MAINCENTER_x=0;
MAINCENTER_y=0;
MAINRADIUS=200;
D = [10.5 44.8 30.01 37.2 23.4 49.1];
% generate random point coordinates
while true
x=((MAINRADIUS-2*D) - (-MAINRADIUS+2*D)).*rand(1,6) + (-MAINRADIUS+2*D);
y=((MAINRADIUS-2*D) - (-MAINRADIUS+2*D)).*rand(1,6) + (-MAINRADIUS+2*D);
if all(x.^2+y.^2<=(MAINRADIUS-2*D).^2)
break;
end
end
% declare angular spacing
ang=0:0.01:2*pi;
% build neighbour points
xp=bsxfun(#plus, (D'*cos(ang)),x');
yp=bsxfun(#plus, (D'*sin(ang)),y');
% second points
idx=randsample(1:size(xp,2),length(D));
secondPoint_x=diag(xp(1:length(D),idx));
secondPoint_y=diag(yp(1:length(D),idx));
%plot
figure(1);
plot(MAINRADIUS*cos(ang)+MAINCENTER_x,MAINRADIUS*sin(ang)+MAINCENTER_y,'r'); %main circle
hold on; plot(xp',yp'); % neighbours circles
hold on; plot(x,y,'r*'); % first points (red asterisks)
hold on; plot(secondPoint_x,secondPoint_y,'k*'); %second points (black asterisks)
axis equal;
Now x and y (and secondPoint_x and secondPoint_y by extension) will be vector of length 6 (because 6 are the distances) in which the i-th element is the i-th x (or y) component for the first (or second) point.

Translating 2D image with RGB colours along axes

I am trying to create my own voronoi diagram. I have an arbitrary shape defined by their x and y coordinates stored in separate vectors. Inside this shape are some points of interest (with known coordinates) that belong to two different groups and act as seeds to the voronoi diagram. As an example, the whole diagram ranges from x=-10 to x=90 and y=-20 to y=60. The boundary shape is not rectangular but falls within the axes range above.
What I have done so far is to create a 3D matrix (100 X 80 X 3), C, with all ones so that the default colour will be white. I then looped through from i=1:100, j=1:80, testing individual pixels to see if they fall within the shape using inpolygon. If they do, I then find out which point is the pixel closest to and assign it a colour based on whether the closest point belongs to group 1 or 2.
All is good so far. I then used imagesc to display the image with a custom axis range. The problem is that the voronoi diagram has the general shape but it is off in terms of position as the pixel coordinates are different from the actual world coordinates.
I tried to map it using imref2d but I do not know how it really works or how to display the image after using imref2d. Please help me on this.
I am open to other methods too!
Thank you!
Edit:
As requested, let me give a more detailed example and explanation of my problem.
Let us assume a simple diamond shape boundary with the following vectors and 4 points with the following coordinate vectors:
%Boundary vectors
Boundary_X = [-5 40 85 40 -5];
Boundary_Y = [20 50 20 -10 20];
%Point vectors
Group_One_X = [20 30];
Group_One_Y = [10 40];
Group_Two_X = [50 70];
Group_Two_Y = [5 20];
Next I plot all of them, with different groups having different colours.
%Plot boundary and points
hold on
plot(Boundary_X,Boundary_Y)
scatter(Group_One_X,Group_One_Y,10,'MarkerFaceColor','Black',...
'MarkerEdgeColor','Black')
scatter(Group_Two_X,Group_Two_Y,10,'MarkerFaceColor','Red',...
'MarkerEdgeColor','Red')
hold off
axis([-10, 90, -20, 60])
This is the result:
Boundary with points
Next I test the whole graph area pixel by pixel, and colour them either cyan or yellow depending on whether they are closer to group 1 or 2 points.
%Create pixel vector with default white colour
C=ones(100,80,3);
Colour_One = [0 1 1];
Colour_Two = [1 1 0];
%Loop through whole diagram
for i=1:100
for j=1:80
x=i;
y=j
if inpolygon(x,y,Boundary_X,Boundary_Y)
%Code for testing which point is pixel closest to
%If closest to group 1, assign group 1 colour, else group 2
%colour
end
end
end
%Display image
hold on
imagesc(C)
hold off
This is the result
Failed Voronoi Diagram
The shape is somewhat correct for the right side but not for the others. I understand that this because my world coordinates start from negative values but the pixel coordinates start from 1.
Hence I am at a lost as to how can I solve this problem.
Thank you!
One thing to notice is that you have to convert between image and plot coordinates. The plot coordinates are (x,y) where x goes to the right, and y goes up. The matrix coordinates are (i,j) where i goes down, and j to the right. An easy way to do this is with the use of vec_X,vec_Y as shown below.
Another solution for the newer Matlab versions would be - as you said - using imref2d but unfortunately I have no experience with that command.
%Boundary vectors
Boundary_X = [-5 40 85 40 -5];
Boundary_Y = [20 50 20 -10 20];
%Point vectors
Group_One_X = [20 30];
Group_One_Y = [10 40];
Group_Two_X = [50 70];
Group_Two_Y = [5 20];
%Coordinate system
min_X = -10;
max_X = 90;
min_Y = -20;
max_Y = 60;
axis([min_X, max_X, min_Y, max_Y])
%Create pixel vector with default white colour
rows_N = 100;
columns_N = 80;
C=ones(rows_N,columns_N,3);
%These vectors say where each of the pixels is in the plot coordinate
%system
vec_X = linspace(min_X,max_X,columns_N);
vec_Y = linspace(min_Y,max_Y,rows_N);
Colour_One = [0 1 1];
Colour_Two = [1 1 0];
%Loop through whole diagram
for i=1:100
for j=1:80
if inpolygon(vec_X(j),vec_Y(i),Boundary_X,Boundary_Y)
%calculate distance to each point
Distances_One = zeros(size(Group_One_X));
Distances_Two = zeros(size(Group_Two_X));
for k=1:numel(Group_One_X);
Distances_One(k) = norm([Group_One_X(k),Group_One_Y(k)]-[vec_X(j),vec_Y(i)]);%assuming euclidean norm, but can be adjusted to whatever norm you need
end
for k=1:numel(Group_Two_X);
Distances_Two(k) = norm([Group_Two_X(k),Group_Two_Y(k)]-[vec_X(j),vec_Y(i)]);%assuming euclidean norm, but can be adjusted to whatever norm you need
end
if min(Distances_One) < min(Distances_Two);
C(i,j,:) = Colour_One;
else
C(i,j,:) = Colour_Two;
end
end
end
end
%Display image
imagesc(vec_X,vec_Y,C) %lets you draw the image according to vec_X and vec_Y
%Plot boundary and points
hold on
plot(Boundary_X,Boundary_Y)
scatter(Group_One_X,Group_One_Y,10,'MarkerFaceColor','Black',...
'MarkerEdgeColor','Black')
scatter(Group_Two_X,Group_Two_Y,10,'MarkerFaceColor','Red',...
'MarkerEdgeColor','Red')
hold off

Draw a line through two points

Using MatLab, I know how to create a line segment connecting two points using this code:
line([0 1],[0 1])
This draws a straight line segment from the point (0,0) to the point (1,1).
What I am trying to do is continue that line to the edge of the plot. Rather than just drawing a line between these two points I want to draw a line through those two points that spans the entire figure for any set of two points.
For this particular line and a x=-10:10, y=-10:10 plot I could write:
line([-10 10], [-10 10]);
But I would need to generalize this for any set of points.
Solve the line equation going through those two points:
y = a*x + b;
for a and b:
a = (yp(2)-yp(1)) / (xp(2)-xp(1));
b = yp(1)-a*xp(1);
Find the edges of the plotting window
xlims = xlim(gca);
ylims = ylim(gca);
or take a edges far away, so you can still zoomout, later reset the x/y limits.
or if there is no plot at the moment, define your desired edges:
xlims = [-10 10];
ylims = [-10 10];
Fill in those edges into the line equation and plot the corresponding points:
y = xlims*a+b;
line( xlims, y );
And reset the edges
xlim(xlims);
ylim(ylims);
There is one special case, the vertical line, which you'll have to take care of separately.
What about
function = long_line(X,Y,sym_len)
dir = (Y-X)/norm(Y-X);
Yp = X + dir*sym_len;
Xp = X - dir*sym_len;
line(Xp,Yp);
end
being sym_len one half of the expected length of the plotted line around X?