I am writing a code to produce a house with a given height, width, length, and angle of sloped roof.
I was able to produce the walls with surf by using a zero value for a specific dimension, allowing a wall to be made as an x-y plane, x-z plane, etc.
However, Now I am trying to produce a desk inside that is an x-y plane with a specific height, and cant figure out how..
MATLAB says it needs a matrix, but a matrix formed only from the height doesn't work, also using ndgrid(heightValue:.5:heightValue) doesn't work
If anyone knows how to help, I would greatly appreciate it.
Code:
(one of the desk sides is in the wrong place as well for similar problems, but help with this problem will allow me to figure out how to fix it)
clear; clc; close all;
%% INPUT VARIABLES
lengthdlg = 'Please enter length of base (m): ';
widthdlg = 'Please enter width of base (m): ';
heightdlg = 'Please enter height of wall (m): ';
angledlg = 'Please enter the angle of the roof (degrees): ';
dlgbox = inputdlg({lengthdlg, widthdlg, heightdlg, angledlg},...
'Simple AutoCAD', [1 50; 1 50; 1 50; 1 50], {'30','40','15','30'});
BaseL = str2double(dlgbox(1));
BaseW = str2double(dlgbox(2));
WallH = str2double(dlgbox(3));
RoofA = str2double(dlgbox(4));
m = tand(RoofA);
h = figure;
set(h,'name','Simple AutoCAD House','numbertitle','off')
xlabel('x'),ylabel('y'),zlabel('z')
title('Simple AutoCAD House')
colormap white
%% Base/Floor
[xB,yB] = ndgrid(0:1:BaseL, 0:1:BaseW);
zB = (xB*0);
surf(xB,yB,zB,'FaceColor','k')
hold on
%% Walls
%Front Wall (w/Door opening)
[xFW,zFW] = ndgrid(0:1:BaseL, 0:1:WallH);
yFW = (xFW*0);
yFW(xFW>.4*BaseL & xFW<.6*BaseL & zFW<.8*WallH) = nan;
surf(xFW,yFW,zFW);
hold on
%Back Wall
[xBW,zBW] = ndgrid(0:.5:BaseL, 0:.5:WallH);
yBW = (xBW*0)+BaseW;
surf(xBW,yBW,zBW);
%Right Wall
[yRW,zRW] = ndgrid(0:.5:BaseW, 0:.5:WallH);
xRW = (yRW*0)+BaseL;
surf(xRW,yRW,zRW);
%Left Wall
[yLW,zLW] = ndgrid(0:.5:BaseW, 0:.5:WallH);
xLW = (yLW*0);
xLW(yLW>.25*BaseW & yLW<.75*BaseW & zLW>.5*WallH & zLW<.7*WallH) = nan;
surf(xLW,yLW,zLW);
%% Roof
%Left Panel
[xLP,yLP] = ndgrid(0:.5:(BaseL/2), 0:.5:BaseW);
zLP = (m*xLP)+WallH;
surf(xLP,yLP,zLP)
hold on
%Right Panel
[xRP,yRP] = ndgrid((BaseL/2):.5:BaseL, 0:.5:BaseW);
zRP = (-m*xRP)+(WallH+m*BaseL);
surf(xRP,yRP,zRP)
%% Roof Triangles
%Front Triangle
[xFT,zFT] = ndgrid(0:.25:BaseL, WallH:.25:(m*BaseL/2)+WallH);
yFT = 0*xFT;
yFT(zFT>(m*xFT+WallH)) = nan;
yFT(zFT>(-m*xFT+(m*BaseL+WallH))) = nan;
surf(xFT,yFT,zFT)
%Back Triangle
[xBT,zBT] = ndgrid(0:.25:BaseL, WallH:.25:(m*BaseL/2)+WallH);
yBT = (xBT*0)+BaseW;
yBT(zBT>(m*xBT+WallH)) = nan;
yBT(zBT>(-m*xBT+(m*BaseL+WallH))) = nan;
surf(xBT,yBT,zBT)
%% Door
[xD,zD] = ndgrid(.4*BaseL:.5:.6*BaseL, 0:.5:.8*WallH);
yD = xD*0;
door = surf(xD,yD,zD,'FaceColor','g');
%% Windows
%Left Window
[yLWin,zLWin] = ndgrid(.25*BaseW:.5:.75*BaseW, .5*WallH:.5:.75*WallH);
xLWin = (yLWin*0);
surf(xLWin,yLWin,zLWin,'FaceAlpha',.25,'FaceColor','b')
%% Desk and chair
%desktop
[xDT,yDT] = ndgrid(.8*BaseL:.5:.99*BaseL, .7*BaseW:.5:.99*BaseW);
zDT = (0*xDT);
surf(xDT,yDT,zDT,'FaceColor','r')
%DeskNear
[xDN,zDN] = ndgrid(.8*BaseL:.5:BaseL, 0:.5:.25*WallH);
yDN=(0*zDN)
surf(xDN,yDN,zDN,'FaceColor','r')
%% Door Opening
k = waitforbuttonpress;
if k == 0
delete(door)
end
The ZData property of a surface, is the z coordinate for each point in the surface. In your example, you set it to the right size by making it the same size as your XData and YData; however, the way you construct your ZData is a little off. You essentially do this:
zDT = (0 * xDT);
What this effectively does, is forces the z coordinate of every point in the mesh to be 0 (i.e. on the floor). This is why the desk top simply sits on the floor, and also why the side of the desk sits on the side of the wall (y = 0).
To fix this, you want to instead calculate what the values should actually be rather than setting them to zero.
Desktop
The nice thing is that the z value for the entire desktop is actually a constant for the whole surface since it's parallel to the floor. You define what the height of the desktop is when you create the side of the desk.
[xDN, zDN] = ndgrid(.8*BaseL:.5:BaseL, 0:.5:.25*WallH);
Here you're saying the height of the desk is 0.25 * WallH rounded to the previous 0.5 (because of your step size). So we want to set the ZData of the desktop to be that value (the max of zDN) and we can multiply that value by a matrix of ones the size of the XData to make it the right size.
zDT = max(zDN(:)) * ones(size(xDT));
Side of Desk
For the side of the desk, the issue isn't the ZData anymore, it's now the YData. As it currently is, you have it set to all zeros so it get's stuck to the wall at y = 0.
yDN = (0 * zDN);
Again, we want the y value to be constant for the whole side of the desk. We can compute what this y value is by using the minimum y value from the desk top (again, making sure it's the right size by using a matrix of ones).
yDN = min(yDT(:)) * ones(size(xDN));
Summary
We can make those two changes and this makes the bottom portion of your code look something like this.
%desktop
[xDT,yDT] = ndgrid(.8*BaseL:.5:.99*BaseL, .7*BaseW:.5:.99*BaseW);
[xDN,zDN] = ndgrid(.8*BaseL:.5:BaseL, 0:.5:.25*WallH);
zDT = max(zDN(:)) * ones(size(xDT));
surf(xDT,yDT,zDT,'FaceColor','r')
%DeskNear
yDN = min(yDT(:)) * ones(size(xDN));
surf(xDN,yDN,zDN,'FaceColor','r')
If we apply that, we get the desktop off of the floor and the side of the desk where it belongs.
As Amro said in the comment to your post, it will likely be much easier to draw this sort of thing using patch rather than surf as it gives you a great deal more flexibility as far as shapes.
Here is my version of the house :)
Code should be easy to follow. Note that I'm using the patch function instead.
% input measurements
BaseL = 30;
BaseW = 40;
WallH = 15;
RoofA = 30;
% colors
m = tand(RoofA);
clr = hsv(10);
clf
% floor
patch([0 1 1 0].*BaseL, ...
[0 0 1 1].*BaseW, ...
[0 0 0 0].*WallH, clr(1,:));
% front wall (w/ door opening)
patch([1 1 0 0 0.4 0.4 0.6 0.6].*BaseL, ...
[0 0 0 0 0 0 0 0].*BaseW, ...
[0 1 1 0 0 0.8 0.8 0].*WallH, clr(2,:));
% back wall
patch([0 1 1 0].*BaseL, ...
[1 1 1 1].*BaseW, ...
[0 0 1 1].*WallH, clr(3,:));
% right wall
patch([1 1 1 1].*BaseL, ...
[0 0 1 1].*BaseW, ...
[0 1 1 0].*WallH, clr(4,:));
% left wall (w/ window opening)
patch([0 0 0 0 0 0 0 0 0 0 0 0].*BaseL, ...
[0 0 0.5 0.5 0.25 0.25 0.75 0.75 0.5 0.5 1 1].*BaseW, ...
[0 1 1 0.7 0.7 0.5 0.5 0.7 0.7 1 1 0].*WallH, clr(5,:));
% roof left/right panels
patch([0 0.5 0.5 0].*BaseL, ...
[0 0 1 1].*BaseW, ...
[0 1 1 0].*(m*BaseL/2)+WallH, clr(6,:));
patch([1 0.5 0.5 1].*BaseL, ...
[0 0 1 1].*BaseW, ...
[0 1 1 0].*(m*BaseL/2)+WallH, clr(7,:));
% roof front/back triangles
patch([0 1 0.5].*BaseL, ...
[0 0 0].*BaseW, ...
[0 0 1].*(m*BaseL/2)+WallH, clr(8,:));
patch([0 1 0.5].*BaseL, ...
[1 1 1].*BaseW, ...
[0 0 1].*(m*BaseL/2)+WallH, clr(9,:));
% door
hDoor = patch([0.4 0.6 0.6 0.4].*BaseL, ...
[0 0 0 0].*BaseW, ...
[0 0 0.8 0.8].*WallH, 'k', 'FaceAlpha',0.75);
% window on left wall
hWin = patch([0 0 0 0].*BaseL, ...
[0.25 0.75 0.75 0.25].*BaseW, ...
[0.5 0.5 0.7 0.7].*WallH, 'k', 'FaceAlpha',0.75);
% table inside
patch([0.8 0.99 0.99 0.8].*BaseL, ...
[0.7 0.7 0.99 0.99].*BaseW, ...
[0.2 0.2 0.2 0.2].*WallH, clr(10,:));
patch([0.8 0.99 0.99 0.8].*BaseL, ...
[0.7 0.7 0.7 0.7].*BaseW, ...
[0.01 0.01 0.2 0.2].*WallH, clr(10,:));
patch([0.8 0.99 0.99 0.8].*BaseL, ...
[0.99 0.99 0.99 0.99].*BaseW, ...
[0.01 0.01 0.2 0.2].*WallH, clr(10,:));
% 3D view
axis([0 BaseL 0 BaseW 0 WallH*2]), axis vis3d
view(3), grid on
xlabel('X'), ylabel('Y'), zlabel('Z')
title('Simple AutoCAD House')
% animate door/window transparencies
t = linspace(-pi,pi,20);
anim = #(t) 1-(tanh(t)+1)/2; % smooth function from 1 to 0
for i=1:numel(t)
set([hDoor,hWin], 'FaceAlpha',anim(t(i)))
pause(0.1)
end
There is a small animation at the end, where the door and window go from tinted to clear. Hope you like it!
Related
My mathematics (and matlab) is just not up to this and so I humbly ask for your help:
I have a model of flow through a annulus. I want to get the radius of the peak flow point and the radius of the annulus to the centreline.
So, I have 3 points, ABC, in a 3D model whose co-ordinates we know but not aligned to xyz. I want AB to be two points on a plane and point A to be the origin (centre of the annulus). Point C is on a plane parallel to the previous plane (the peak flow point). I would like to know how to calculate the distance between C on its plane to the normal vector of the AB plane that goes through the point A.
Here are pics of the model:
Flow through an annulus
Points in the model
Now I've had a friend who's cleverer than I come up with a matlab code to translate and rotate the model and then calculate the magnitudes of AB and AnormC. However, it's not working as it puts C at a greater radius than B. I also realise there is more than one way to solve this problem.
The code is below. Any thoughts where we've gone wrong? Or maybe is there a better way to do this? I thought of vectors but my scribblings amount to nought.
Thank you.
Toby
%Origin
O = [0 0 0]'; Ox = [O' 1]';
%Vector co-ordinates of 1 (A - Origin centreline), ...
% 2 (B - Radius of artery), and 3 (C - Radius of Peak Velocity)
A = [13.3856 -60.0377 15.8443]'; Ax = [A' 1]';
B = [26.9486 -51.0653 20.9265]'; Bx = [B' 1]';
C = [16.2240 -92.5594 40.8687]'; Cx = [C' 1]';
%Find the new i unit vector in the old co-ords (the unit vector along the new x axis)
AB = B - A;
magAB = sqrt(sum(AB.^2));
new_i=AB./magAB;
%Calculate the angle to rotate through Z when transforming
thetaZ = atan(new_i(2)/new_i(1));
%Hence, define the translation matrix (to move the origin to A) and ...
% the rotation matrixes (to align the new x axis with AB and new_i)
T = [1 0 0 -A(1) ; 0 1 0 -A(2) ; 0 0 1 -A(3) ; 0 0 0 1];
Rz = [cos(thetaZ) sin(thetaZ) 0 0 ; -sin(thetaZ) cos(thetaZ) 0 0 ; 0 0 1 0 ; 0 0 0 1];
Transform = Rz * T;
%transform Cx to the new co-ordinates by translation and rotation in Z
A_dash = round(Transform * Ax,10);
B_dash = round(Transform * Bx,10);
C_dash = round(Transform * Cx,10);
new_i_t = round(Transform * [new_i' 1]',4); new_i_t = new_i_t(1:3);
new_O = round(Transform * Ox,4); new_O = new_O(1:3);
A_dash = A_dash(1:3); B_dash = B_dash(1:3); C_dash = C_dash(1:3);
%Perform a final rotation in Y
thetaY = atan(B_dash(3)/B_dash(1));
Ry = [cos(thetaY) 0 sin(thetaY) ; 0 1 0 ; -sin(thetaY) 0 cos(thetaY)];
B_dash = Ry * B_dash;
C_dash = Ry * C_dash;
%Taking point C onto the plane of AB
C_dashflat = C_dash.*[1 1 0]';
%Find Radius of Peak Flow
Radius_Peak_Flow = sqrt(sum(C_dashflat.^2));
%Find Radius of Artery
Radius_Artery = magAB;
%Ratio (between 0 -1)
Ratio = Radius_Peak_Flow/Radius_Artery
I'm trying to make the center of a rectangle follow a trajectory and rotate it according to the trajectory, like the ones shown in this video (the video doesn't have a trajectory but shows a simple rotating rectangle)https://www.youtube.com/watch?v=NT45HE7L7yk.
So far this is my code
figure;
subplot(2,1,1)
%visualize trajectory
ax = gca;
h = hgtransform('Parent',ax);
hold on
plot(x(1),y(1),'o','Parent',h);
for k = 2:length(x)
m = makehgtform('translate',x(k)-x(1),y(k)-y(1),0);
h.Matrix = m;
axis([2 10 2 10]);
drawnow
end
subplot(2,1,2)
ax = gca;
polyin = polyshape([0.1 0.1 -0.1 -0.1], [0.2 -0.2 -0.2 0.2]); % %w/2,h/2
% polyin = rectangle('Position',[-0.1 -0.1 0.2, 0.4]);
polyout = rotate(polyin, -atand(Y(1)/X(1)));
polyout = translate(polyout,[X(1) Y(1)]);
plot(polyout);
axis([2 10 2 10]);
for k=2:length(x)
hold on;
t = atan((y(k-1)-y(k))/(x(k)-x(k-1)))
% t = acos((y(k-1)*x(k-1)+y(k)*x(k))/(norm([x(k-1) y(k-1)])*norm([x(k) y(k)])));
% t = atand(y(k))/(x(k));
if (x(k)* y(k-1)- y(k)*x(k-1) )< 0
t = -1*t;
end
polyout=translate(polyout,x(k)-x(k-1),y(k)-y(k-1));
polyout=rotate(polyout, t);
plot(polyout);
axis([2 10 2 10]);
drawnow;
end
I want the rectangle to rotate towards the next point in trajectory, and the center to follow the trajectory however my code is not working right.
Any help is very much appreciated thank you for your time.
x and y data for reference
x=[2 2.20138766511364 2.40315325924044 2.60463533746946 2.80522353545458 3.00435711210347 3.20152349226664 3.39625680942646 3.58813644838617 3.77678558795894 3.96186974365685 4.14309531037993 4.32020810510520 4.49299190957566 4.66126701298937 4.82488875468838 4.98374606684784 5.13776001716501 5.28688235154823 5.43109403680600 5.57040380333597 5.70484668781400 5.83448257588312 5.95939474484264 6.07968840633708 6.19548924904527 6.30694198136934 6.41420887412373 6.51746830322425 6.61691329237706 6.71275005576773 6.80519654075027 6.89448097053608 6.98084038688309 7.06451919278466 7.14576769515872 7.22484064753668 7.30199579275257 7.37749240563194 7.45158983568100 7.52454604977557 7.59661617485011 7.66805104058679 7.73909572210444 7.80998808264764 7.88095731627572 7.95222249055176 8.02399108923166 8.09645755495312 8.16980183192468 8.24418790861475 8.31976236044063 8.39665289245752 8.47496688204757 8.55478992160887 8.63618436124451 8.71918785145157 8.80381188581016 8.89004034367245 8.97782803285167 9.06709923231116 9.15774623485338 9.24962788980894 9.34256814572560 9.43635459305732 9.53073700685329 9.62542588944692 9.72009101314488 9.81435996291615 9.90781667908099 10]
y=[2 2.02032989606539 2.04061708413251 2.06134914940940 2.08296946958339 2.10587858004199 2.13043553909377 2.15695929318928 2.18573004214188 2.21699060434868 2.25094778201139 2.28777372635721 2.32760730285977 2.37055545645991 2.41669457678668 2.46607186337814 2.51870669090231 2.57459197437800 2.63369553439574 2.69596146233865 2.76131148560331 2.82964633282069 2.90084709907698 2.97477661113452 3.05128079265267 3.13019002940870 3.21132053451867 3.29447571365832 3.37944753028395 3.46601787085333 3.55395991004655 3.64303947598694 3.73301641546193 3.82364595914396 3.91468008681135 4.00586889256917 4.09696195007019 4.18770967773568 4.27786470397637 4.36718323241328 4.45542640709865 4.54236167773681 4.62776416490505 4.71141802527453 4.79311781683116 4.87266986409647 4.94989362334853 5.02462304784280 5.09670795303304 5.16601538179218 5.23243096963322 5.29586030993012 5.35623031913866 5.41349060201735 5.46761481684833 5.51860204065821 5.56647813443899 5.61129710836894 5.65314248703349 5.69212867464610 5.72840232026917 5.76214368303492 5.79356799736624 5.82292683819765 5.85050948619610 5.87664429298192 5.90170004634970 5.92608733548913 5.95025991620594 5.97471607614275 6]
Since you want the rectangle to rotate around its center you should specify the reference point of rotation to be the center of the rectangle. Secondly, when rotating you have to keep in mind that in the previous step you already did a rotation and therefore need to substract the previous angle from the new one.
polyin = polyshape([0.1 0.1 -0.1 -0.1], [0.2 -0.2 -0.2 0.2]); % %w/2,h/2
% polyin = rectangle('Position',[-0.1 -0.1 0.2, 0.4]);
polyout = translate(polyout,[x(1) y(1)]);
plot(polyout);
axis([2 10 2 10]);
prev_t = 0;
for k=2:length(x)
hold on;
t = atan((y(k)-y(k-1))/(x(k)-x(k-1))) % corrected formula
% if (x(k)* y(k-1)- y(k)*x(k-1) )< 0 % commented because I don't see why this should be here
% t = -1*t;
% end
polyout=translate(polyout,x(k)-x(k-1),y(k)-y(k-1));
polyout=rotate(polyout, t - prev_t, [x(k) y(k)]);
plot(polyout);
axis([2 10 2 10]);
drawnow;
prev_t = t;
end
I need to plot horizontal bars using the grouped style such that all the bars belonging to each group have the same color but different from other groups (i.e. all bars of the top group are red, the group below it - green, and so on...).
Also, how can I put the values on the top of each bar horizontally? How can I control the position of such values?
This is my code:
y = [91.9 8.1 94.4 5.6; 84.9 15.1 90.12 9.88; 89.4 10.6 91.2 8.8; 72 28 50.9 49.1];
h = barh(y,'grouped');
job = {'group1','group2 ','group 3','group4'};
legend(job,'location','northeast');
This is my current figure:
Here's a hack:
Plot bar graph for 1 row at a time and fill the space with NaNs while specifying a single color for all rows. Plotting NaNs will plot nothing.
[ry, cy] = size(y); %Number of rows and columns of y
hold on;
for k = 1:ry
tmp = [NaN(k-1,cy); y(k,:); NaN(4-k,cy)]; %Filling with NaNs
h{k} = barh(tmp, 'FaceColor', rand(1,3)); %Plotting bar graph in random color
h{k} = h{k}(1); %Store handle of any of the rows (Required for legend)
end
job = {'group1', 'group2', 'group3', 'group4'}; %Required legend entries
legend([h{:}], job); %Default location is already 'northeast' (so can be skipped)
Output:
This is another type of hack; tested on R2017b.
The reason why what you're asking is difficult to do is because each group in a bar plot is actually a Quadrilateral object (i.e. polygon). For example, let's take the 1st Bar object:
>> h(1).Face.VertexData
ans =
3×16 single matrix
Columns 1 through 9
0 91.9000 91.9000 0 0 84.9000 84.9000 0 0
0.6545 0.6545 0.8000 0.8000 1.6545 1.6545 1.8000 1.8000 2.6545
0 0 0 0 0 0 0 0 0
Columns 10 through 16
89.4000 89.4000 0 0 72.0000 72.0000 0
2.6545 2.8000 2.8000 3.6545 3.6545 3.8000 3.8000
0 0 0 0 0 0 0
And if we add this line to the code:
drawnow; hold on; scatter(h(1).Face.VertexData(1,:), h(1).Face.VertexData(2,:));
we get (notice the green circles):
What's interesting is that the points in VertexData are not read-only, which means we can just intelligently "rearrange" the VertexData matrices of the N bar groups to get the desired result. Here's one way to do it (notice I renamed h to hBar):
% Obtain the old VertexData:
vd_old = cell2mat(reshape(get([hBar.Face],'VertexData'),1,1,[]));
% Rearrange VertexData:
nB = numel(hBar);
vd_new = cell2mat(permute(mat2cell(vd_old,3,repelem(4,nB),repelem(1,nB)),[1,3,2]));
% Assign the new VertexData back into the Bar objects' Edge and Face fields:
for indB = 1:nB
hBar(indB).Edge.VertexData = vd_new(:,:,indB);
hBar(indB).Face.VertexData = vd_new(:,:,indB);
end
Finally, we manually add labels using the text command:
xOffset = 1;
for indB = 1:nB
text(double(vd_new(1,2:4:end,indB)) + xOffset, double(tmpY(2:4:end)), ...
string(vd_new(1,2:4:end,indB)),'VerticalAlignment','middle');
end
The end result:
The final code:
function q47978293
close all force;
y = [91.9 8.1 94.4 5.6; 84.9 15.1 90.12 9.88; 89.4 10.6 91.2 8.8; 72 28 50.9 49.1];
figure(47978293); set(gcf,'Position',[786,556,887,420]); hBar = barh(y,'grouped');
legend("group" + (1:4),'location','northeast');
drawnow;
% hold on; scatter(h(1).Face.VertexData(1,:), h(1).Face.VertexData(2,:));
% Obtain the old VertexData:
vd_old = cell2mat(reshape(get([hBar.Face],'VertexData'),1,1,[]));
% Rearrange VertexData:
nB = numel(hBar);
vd_new = cell2mat(permute(mat2cell(vd_old,3,repelem(4,nB),repelem(1,nB)),[1,3,2]));
% Assign the new VertexData back into the Bar objects' Edge and Face fields:
xOffset = 1; % < Adjust as you see fit
for indB = 1:nB
hBar(indB).Edge.VertexData = vd_new(:,:,indB);
hBar(indB).Face.VertexData = vd_new(:,:,indB);
try
text( y(indB,:) + xOffset, mean(reshape(vd_new(2,1:2:end,indB),2,[]),1,'double'), ...
string(vd_new(1,2:4:end,indB)),'VerticalAlignment','middle');
catch % Compatibility fix for R2016b & R2017a. Credit #SardarUsama
text( y(indB,:) + xOffset, mean(reshape(vd_new(2,1:2:end,indB),2,[]),1,'double'), ...
split(num2str(vd_new(1,2:4:end,indB))),'VerticalAlignment','middle');
end
end
end
Note: due to the hacky nature of this solution, if the figure is redrawn (due to e.g. resizing), the bar colors will return to their original state - so make sure that the part that changes colors is the very last thing you do.
P.S.
The R2017b release notes state that this customization should be officially supported now (through the CData property of Bar objects), but I couldn't get the legend to display the correct colors. If you want to try it, just plot using the following command (R2017b+):
barh(y,'grouped','FaceColor','flat','CData',lines(size(y,1)));
The two requests overlap making everything kinda complex, since using uniform colors for each group implies using a workaround that needs another workaround to male bar text work. Here is the code:
y = [
91.90 8.10 94.40 5.60;
84.90 15.10 90.12 9.88;
89.40 10.60 91.20 8.80;
72.00 28.00 50.90 49.10
];
[rows,cols] = size(y);
n = NaN(rows,cols);
c = {'r' 'g' 'b' 'y'};
bh = cell(rows,1);
lh = cell(rows,1);
hold on;
for k = 1:rows
curr = n;
curr(k,:) = y(k,:);
bar = barh(curr,'FaceColor',c{k});
bh{k} = bar;
lh{k} = bar(1);
end
hold off;
legend([lh{:}],{'G1','G2','G3','G4'},'Location','southoutside');
yl = get(gca,'XLim');
yl_up = yl(2);
xoff = yl_up * 0.01;
set(gca,'XLim',[yl(1) (yl_up + (yl_up * 0.1))]);
set(gcf,'Units','normalized','Position',[0.1 0.1 0.8 0.8]);
for i = 1:numel(bh)
bh_i = bh{i};
for j = 1:numel(bh_i)
bh_ij = bh_i(j);
hx = bh_ij.YData + xoff;
hy = bh_ij.XData + bh_ij.XOffset;
text(hx,hy,num2str(hx.','%.2f'), ...
'FontSize',10, ...
'HorizontalAlignment','left', ...
'VerticalAlignment','middle');
end
end
And here is the output:
In my research area (meteorology) graphs within graphs are commonly produced.
more information about it can be found here.
Each of those lines joints up data points that have:
An x-value, between 0 and 1 (values greater than 1 should not be represented in the graph).
A y-value, between 0 and 1.
A PSS value, between 1 and -1.
A Frequency Bias value, ranging from 0 to +∞, but values higher than 4 are not displayed.
A False Alarm Ratio (FAR) value, ranging from 0.0 to 0.9. The False Alarm Ratio value is held constant at a particular value for each data point on any given line.
EDIT: To make things really concrete, I've drawn a pink dot on the graph. That dot represents a data point for which x=0.81, y=0.61, PSS=-0.2, B=3.05, FAR=0.8.
I am trying to reproduce something similar in MATLAB. Googling turned up a lot of answers like this, which feature inset figures rather than what I'm looking for.
I have the data organized in a 3D array, where each page refers to a different level of False Alarm Ratio. The page with a FAR of 0.8 (data here) starts out like this
Then there are other pages on the 3D array devoted to FARs of 0.7, 0.6, and so on.
Questions
1. Is it even possible to create such an graph in MATLAB?
2. If so, what function should I use, and what approach should I take? EDIT: I have working code (below) that creates a somewhat similar figure using the linear plot function, but the documentation for this function does not indicate any way to insert a graph inside another graph. I am not sure how helpful this code is, but have inserted it in response to the downvoter.
H = [0:0.01:1];
figure; hold on
fill([0 1 1],[0 0 1],[0 0.2 0.4]) % Deep blue
fill([0 1 0],[0 1 1],[0.4 0 0]) % Purple
low_colours = {[0 0.501 1],[0 0.8 0.4], [0.4 0.8 0], [0.8 0.8 0]};
high_colours = {[0.6 0 0],[0.8 0 0], [1 0.5019 0], [0.988 0.827 0.196]};
colour_counter = 0;
for ii = -0.8:0.2:0
colour_counter = colour_counter + 1;
if colour_counter < 5
colour_now = low_colours{colour_counter};
end
ORSS = ones(1,size(H,2))*ii;
F = (H .* (1-ORSS)) ./ ((1-2.*H) .* ORSS + 1);
plot(F,H)
fill(F,H,colour_now);
end
colour_counter = 0;
for ii = 0.8:-0.2:0
colour_counter = colour_counter + 1;
if colour_counter < 5
colour_now = high_colours{colour_counter};
end
ORSS = ones(1,size(H,2))*ii;
F = (H .* (1-ORSS)) ./ ((1-2.*H) .* ORSS + 1);
plot(F,H)
fill(F,H,colour_now);
end
I think I got what you want, but before you go to the code below, notice the following:
I didn't need any of your functions in the link (and I have no idea what they do).
I also don't really use the x and y columns in the data, they are redundant coordinates to the PSS and B.
I concat all the 'pages' in your data to one long table (FAR below) with 5 columns (FAR,x,y,PSS,FB).
If you take closer look at the data you see that some areas that supposed to be colored in the graph has no representation in it (i.e. no values). So in order to interpolate the color to there we need to add the corners:
FAR{end+1,:} = [0.8 0 0 0 4];
FAR{end+1,:} = [0.9 0 0 -0.66 3.33];
FAR{end+1,:} = [1 0 0 0 0];
FAR{end+1,:} = [1 0 0 -1 3];
Next, the process has 2 parts. First we make a matrix for each variable, that ordered in columns by the corresponding FAR value, so for instance, in the PSS matrix the first column is all PSS values where FAR is 0, the second column is all PSS values where FAR is 0.1, and so on. We make such matrices for FAR(F), PSS and FreqBias(B), and we initialize them with NaNs so we can have columns with different number of values:
F = nan(max(histcounts(FAR.FAR,10)),10);
PSS = F;
B = F;
c = 1;
f = unique(FAR.FAR).';
for k = f
valid = FAR.FAR==k & FAR.x<=1;
B(1:sum(valid),c) = FAR.FB(valid);
B(sum(valid):end,c) = B(sum(valid),c);
PSS(1:sum(valid),c) = FAR.PSS(valid);
PSS(sum(valid):end,c) = PSS(sum(valid),c);
F(:,c) = k;
c = c+1;
end
Then we set the colors for the colormap (which I partially took from you), and set the labels position:
colors = [0 0.2 0.4
0 0.501 1;
0 0.8 0.4;
0.4 0.8 0;
0.8 0.8 0;
0.988 0.827 0.196;
1 0.5019 0;
0.8 0 0;
0.6 0 0.2;
0.4 0.1 0.5];
label_pos =[0.89 0.77
1.01 0.74
1.14 0.69
1.37 0.64
1.7 0.57
2.03 0.41
2.65 0.18
2.925 -0.195
2.75 -0.55];
And we use contourf to plot everything together, and set all kind of properties to make it look good:
[C,h] = contourf(B,PSS,F);
xlim([0 4])
ylim([-1 1])
colormap(colors)
caxis([0 1])
xlabel('Frequency Bias B')
ylabel('Pierce Skill Score PSS')
title('False Alarm Ratio')
ax = h.Parent;
ax.XTick = 0:4;
ax.YTick = -1:0.5:1;
ax.FontSize = 20;
for k = 1:numel(f)-2
text(label_pos(k,1),label_pos(k,2),num2str(f(k+1)),...
'FontSize',12+k)
end
And here is the result:
Getting the labels position:
If you wonder what is a fast way to obtain the variable label_pos, then here is how I made it...
You run the code above without the last for loop. Then you run the following code:
clabel(C,'manual')
f = gcf;
label_pos = zeros(numel(f.Children.Children)-1,2);
for k = 1:2:size(label_pos,1)
label_pos(k,:) = f.Children.Children(k).Position(1:2);
end
label_pos(2:2:size(label_pos,1),:) = [];
After the first line the script will pause and you will see this message in the command window:
Carefully select contours for labeling.
When done, press RETURN while the Graph window is the active window.
Click on the figure where you want to have a label, and press Enter.
That's it! Now the variable label_pos has the positions of the labels, just as I used it above.
I have the following code:
limits = [-1 1 -1 1];
g1 = [1;2;3;4;5;6;7;8];
col = [1 1 0; 1 0 1; 0 1 1; 1 0 0; 0 1 0; 0 0 1; 0 0 0; 0.5 0.3 0.1];
sym = 'vvvv^^^^';
sym2 = 'xxxxxxxx';
points = 30;
for i = 1:8;
mhuR(i,1) = mean(HSRXdistpR(i,:));
mhuR(i,2) = mean(HSRYdistpR(i,:));
mhuL(i,1) = mean(HSRXdistpL(i,:));
mhuL(i,2) = mean(HSRYdistpL(i,:));
mhuX(i,1) = mean(TotxcomHSRpX(i,:));
mhuX(i,2) = mean(TotxcomHSRpY(i,:));
CR{i} = cov(HSRXdistpR(i,:),HSRYdistpR(i,:));
CL{i} = cov(HSRXdistpL(i,:),HSRYdistpL(i,:));
CX{i} = cov(TocomXdistp(i,:),TocomYdistp(i,:));
ellipR{i} = uncertEllip(CR{i},mhuR(i,:),points);
ellipL{i} = uncertEllip(CL{i},mhuL(i,:),points);
ellipX{i} = uncertEllip(CX{i},mhuX(i,:),points);
end
figure; hold on
scatter(HSRXdistbR2,HSRYdistbR2,'ko'); hold on
scatter(HSRXdistbL2,HSRYdistbL2,'ko'); hold on
scatter(TocomXdistb2,TocomYdistb2,'kx'); hold on
gscatter(HSRXp2(:,1),HSRYp2(:,1),g1,col,sym), hold on
gscatter(HSRXp2(:,2),HSRYp2(:,2),g1,col,sym), hold on
gscatter(copHSRXp2(:,2),copHSRYp2(:,2),g1,col,sym2), hold on
for i = 1:8;
plot(ellipR{i}(:,1),ellipR{i}(:,2),col(i,:)), hold on
plot(ellipL{i}(:,1),ellipL{i}(:,2),col(i,:)), hold on
plot(ellipX{i}(:,1),ellipX{i}(:,2),col(i,:)), hold on
end
vline(0)
hline(0)
legend('base','base','base','-0.04m', '-0.08m', '-0.12m', '-0.16m', '0.04m', '0.08m', '0.12m', '0.16m');
title({'xCoM and left and right foot placement relative',...
'to xCoM per perturbation, for all subjects'})
axis(limits)
xlabel('x displacement (scaled)');
ylabel('y displacement (scaled)');`
I'm trying to get the line plots (second for loop) to have the same colors as the scatter plot data (the uncertainty ellipses each belong to their own scatter point). However, it won't allow me to use the colors from the col matrix. Am I missing something here?
Error:
Data must be a single matrix Y or a list of pairs X,Y.
Not trying to define the colors works fine, but of course the colors don't match.
Try replacing each of those erroring lines with this:
plot(ellipR{i}(:,1),ellipR{i}(:,2),'Color',col(i,:));