I am creating a multi-panel figure in MATLAB (with multiple axes of different size). I would like all tick marks to have the same absolute size across all subplots.
According to the MATLAB user guide, tick length is normalized with respect to the longest axis:
TickLength. Tick mark length, specified as a two-element vector of the form [2Dlength 3Dlength]. [...] Specify the values in units normalized relative to the longest of the visible x-axis, y-axis, or z-axis lines.
In order to make all ticks of the same length, I am running the following code:
fixlen = 0.005; % Desired target length
for i = 1:numel(h) % Loop over axes handles
rect = get(h(i),'Position'); % Get the axis position
width = rect(3); % Axis width
height = rect(4); % Axis height
axislen = max([height,width]); % Get longest axis
ticklen = fixlen/axislen; % Fix length
set(h(i),'TickDir','out','TickLength',ticklen*[1 1]);
end
Unfortunately, the above code does not produce a figure in which all tick lengths are equal. Perhaps I am missing something?
Solution. There were two problems in my code.
First of all, I needed to switch from Normalized units to some fixed units (such as pixels). See the answer below.
In some part of the code, before the above snippet, I was resizing the figure and I had a drawnow to update it. However, MATLAB would reach the code snippet before the graphics commands had been executed, and therefore the reported sizes were not correct. I solved the issue by placing a pause(0.1) command after the drawnow.
By default, Axis objects have their Units property set to normalized. This means that the values in the Position property are normalized by the size of the figure. Hence your code may not be producing the desired behavior if the figure is not square.
One way to fix this is the following:
rect = get(h(i),'Position'); % Axis position (relative to figure)
hfig = get(h(i),'Parent'); % Handle to parent figure
rectfig = get(hfig, 'Position'); % Figure position (in pixels)
width = rect(3) * rectfig(3); % Absolute width of axis (in pixels)
height = rect(4) * rectfig(4); % Absolute height of axis (in pixels)
This will give you width/height in terms of pixels on your screen (assuming that you didn't change the Units property of the figure).
And if you use rectfig = get(hfig,'PaperPosition') then you will get width/height in terms of inches on the printed page (again, assuming default value for the figure's PaperUnits property).
Note, however, that you will need to adjust the value you use for fixlen to match the new units we're using here.
Related
I use the tiledlayout(2,2) command to create a plot in Matlab. The first three title fields filled with normal plots. The fourth and last field is a field with an image. I used the commands IMG = 'mypic.tif'and imshow(IMG).I wonder why I can not change the size of my image. Is this not possible with the tiledlayoutcommand? I have looked up the documentary for tiledlayout and imshow(), but found nothing which helps me.
It indeed seems as if this is not possible using the tiledlayout functionality. However, it is possible using the subplot functionality. Below, I will give an example how this works.
Consider the following script:
% Generate some dummy data for the four plots
x = linspace(0, 2*pi, 25);
y = sin(x);
% Load sample image
image = imread('ngc6543a.jpg');
% Generate the figure
fig = figure(1);
% The three subplots
for i = 1:3
subplot(2,2,i);
plot(x, y);
end
% The image
subplot(2,2,4);
imshow(image);
% Increase size of the image
size_factor = 1.4; % 1.0 is original size
im_ax = fig.Children(1); % axes belonging to image
dims = im_ax.Position(3:4); % current dimensions
dims_new = size_factor * dims; % scale old dimensions
dxdy = (dims_new - dims) / 2; % offset for left bottom corner of image
im_ax.Position = [im_ax.Position(1:2) - dxdy, dims_new]; % update position
In this script, we start by generating some dummy data for the three 'normal' plots and loading a sample image (this image came with my MATLAB installation). Subsequently, we create a figure. After the figure has been created, I add the three dummy plots to the figure using a for loop and the subplot functionality. Then, we plot the image using imshow in the fourth subplot.
Now, the interesting part begins. First of all, I define a scale factor for the image (size_factor). Then, I retrieve the axes in which the image is plotted and store it in im_ax. From this axes, I retrieve the last two elements of the Position field and store them in dims. These two elements define the size of the axes. Based on the value of size_factor, I calculate the new size of the axes and store this in dims_new. To ensure that the axes (and thus the image) remains centered, we need to calculate by how much we need to shift the left bottom corner of the axes (whose coordinates are stored in the first two elements of the Position field). This result is stored in dxdy. The last thing we do is simply updating the Position field of the axes in which the image is plotted.
Now, to show you some results:
size_factor equals 1.0:
size_factor equals 1.4:
size_factor equals 0.55:
I have a scatterplot of data in Matlab, along with a horizontal linegraph which divides two sub groups of this data - all on the same plot. I have plotted these two entities separately using the hold on command.
Ideally, I want the plot window to automatically adjust to just the scatterplot data, and would prefer the horizontal line I plotted to simply extend off the screen in all cases. Is there a simple way to do this?
At the moment when I change the limits of the horizontal line, the graph window shifts to accommodate these points, skewing the view of the scatterplot data I'm actually interested in.
Example:
% central line segment
boundary_line = plot(csv_results.data(:,9),csv_results.data(:,10));
% negative extension of line segment off screen
line_negext = plot([-10,csv_results.data(1,9)],[csv_results.data(1,10),csv_results.data(1,10)]);
% positive extension of line segment off screen
line_posext = plot([10,csv_results.data(6,9)],[csv_results.data(6,10),csv_results.data(6,10)]);
% scatterplot data of interest
scatt_data = plot(csv_results.data(:,3),csv_results.data(:,4));
UPDATE: My problem is that, as seen in my code above, I need to plot two line segments at different y values that continue to positive and negative infinity, which link up to an existing plot in the middle. If I use yline I can simply draw one horizontal line - if I use xlim I risk cropping out data for subsequent runs..
If you want to adjust the axis to more restrictive portion (reduce), then xlim() and ylim() get the job done. Horizontal lines drawn by yline() will persist, as will vertical lines drawn by xline(). Note that xline() and yline() should work for releases including R2018b and later.
% MATLAB R2019a
% Sample Data
n = 10;
X1 = 5*rand(n,1);
Y1 = 5*rand(n,1);
X2 = 5 + 5*rand(n,1);
Y2 = 5 + 5*rand(n,1);
figure, hold on
yline(5)
scatter(X1,Y1,'bo')
scatter(X2,Y2,'rd')
scatter(X1,Y2,'ks')
xlim([0 5])
Notice that by calling xlim() for a larger x-axis range also expands the horizontal line.
xlim([-1,12])
If you plot new data after xlim() outside the range, the plot won't automatically adjust. However, if you do so before calling xlim(), then the horizontal line will expand. Try the example below.
figure, hold on
yline(5)
scatter(X1,Y1,'bo')
scatter(X2,Y2,'rd')
scatter(X1,Y2,'ks')
Then immediately execute
scatter(100*rand(n,1),Y1)
and see that the horizontal line has expanded to cover the new, much longer x-axis.
After posting this answer, I found: How to draw horizontal and vertical lines in MATLAB?
Here is a simple MATLAB code. How to control the default distance between ticks of the Y axis? I want to make it smaller to fit in my paper. Hint: I update the post with 2 picture that shows what I mean (they are the same but the distance between the y axis ticks is smaller in one picture that the other.
x = linspace(-10,10,200);
y = sin(4*x)./exp(x);
plot(x,y)
xlim([0 10])
ylim([-0.4 0.8])
You can control the tick by using the gca object of the plot. Here is an example for xtick. Change 'xtick' to 'ytick':
plot(x,y);
set(gca, 'xtick', [-10:2:10]);
If you want to change the x-axis tick labels with new labels, then you can change the values of the labels as follows:
% specify the ticks first where you want to change
xticks([0 2 4 6 8])
% change the corresponding labels to the required ones
xticklabels({'-1', '-2', '-3', '-4', '-5'})
You can modify the height of the graph, maintaining the number and values of the tick marks, which makes the distance between tick marks smaller.
To do so, set the figure window’s 'Position' property (this is equivalent to dragging the edges of the window to make the figure smaller), and setting the locations of the tick marks manually to prevent MATLAB from reducing their number. For example:
h = gcf; % figure handle
a = gca; % axes handle
ticks = get(a,'YTick');
pos = get(h,'Position');
pos(4) = pos(4) * 0.75; # reduce the size
set(h,'Position',pos);
set(a,'YTick',ticks)
You should also note the PaperPosition, PaperSize and other Paper... properties of the figure, as they are used when printing (also to file). You might want to manually set those properties before creating a PDF or EPS from the graph.
Here is even a simpler way then what #Cris suggested:
ax = axes;
ax.YTickMode = 'manual';
ax.Position(4) = ax.Position(4)*0.75;
by setting the YTickMode to manual you prevent Matlab from updating the ticks upon resizing of the axes. Then you change the hight of the axes by setting the position property directly.
I've been plotting some figures and I want to save them multiple times zoomed in certain areas. All my views have pivot-point at y=0 except one, the last view. I've been using 'XLim' to change the view on the X axis. Now I need to pan the view on the Y axis. When using YLim, the aspect ratio of the image changes.
Is it possible to set some pivot point on the Y-axis?
Here is a minimum working code where my problem can be seen. Pauses 2 times.
f1 = figure(10);
hold on
axis equal
x = linspace(1,2*pi);
y = sin(x);
plot(x,y,'*')
xlims=[0 2*pi; 1 2*pi; 0.5 1.5];
for i = 1:size(xlims,1)
set(gca,'XLim',xlims(i,:))
disp('Here I am saving this view! (Paused)')
pause
end
% Now I want to PAN the Y axis and set the view around Y = 1.
% But still keep last set Xlims AND keep the aspect ratio of figure.
% When using YLim, the aspect ratio changes.
set(gca,'YLim',[0.9 1.1]) % Not what I have in mind.
% Just need to pivot Y=1...
There's no "panning" command as such. (there is a pan command but this just toggles the ability to pan using the mouse on or off).
But, there is the ability to set your axis limits dictating which portion of the plot to show, using the axis function, with manually specified limits.
e.g. axis([ xmin, xmax, ymin, ymax ]);
Furthermore, if you apply this after any other axis calls that affect shape / aspect ratio (e.g. axis square or axis equal) then these will be preserved.
Note that in your particular example, the use of axis equal therefore necessarily means that for a smaller range of Y your aspect ratio will be smaller. Therefore you might to opt against that.
Alternatively, you can also control the aspect ratio manually with the daspect function. e.g., in your example:
>> axis equal % your initial axis / aspect ratio situation
>> A = axis;
>> axis([0.5,1.5,0.9,1.1]); % your new values. aspect ratio will change to preserve equality
>> B = axis;
>> Y_factor = (A(4)-A(3))/(B(4)-B(3));
>> daspect([Y_factor, 1, 1]); % back to the old "aspect ratio"
However, this clearly means that the two axes are now not "equal", and the comparison with previous graphs might be misleading. This may or may not be a factor to take into consideration.
Finally, if what you're really after is simply to make sure your axes object does not change size, you can simply set the size of your axes object manually (e.g. set(gca, 'position', [x,y,x_width, y_width]) ) and ensure axis is on normal mode (i.e. adapting to the axes size).
The answer given in a comment by #jodag is perfect. Since comments have a tendency to disappear I thought I would reproduce it here.
To "pan", you need to change both limits without changing their interval. Since you can read the current interval with
get(gca, 'YLim')
you can simply pan the y axis by a distance dy with the command:
set(gca, 'YLim', get(gca, 'YLim')+dy)
I have written a program to process and print a 81x81 2D surface plot, which needs to be used as an input. Saving the plot made by Matlab includes side axes as well as white margins on the sides.
How do I crop this image to get just the (81pixels)x(81pixels) output as an image?
Try placing this after your figure code, it will remove the margins around your figure.
set(gca,'units','pixels') % set the axes units to pixels
xx = get(gca,'position'); % get the position of the axes;
set(gcf,'units','pixels') % set the figure units to pixels
yy = get(gcf,'position'); % get the figure position;
set(gcf,'position',[yy(1) yy(2) xx(3) xx(4)]) % set the position of the figure to the length and width of the axes
set(gca,'units','normalized','position',[0 0 1 1]) % set the axes units to pixels
You can avoid using surf plot and just save your array as image. So, you have 81x81 array. I'll use this one for the example:
a = magic(81);
Now you need to normalize image in order to have values from 0 to 255:
a = (a - min(min(a)))/(max(max(a))-min(min(a)))*255;
Finally you use imwrite to save your array as image.
imwrite(a,jet(256),'SO_4.png')
The first argument is your 81x81 array. The second argument is colormap. You can specify any colormap you want, but I like the heatmap jet(256). You can skip this argument, in this case the image would be grayscale. From here you can find what colormaps are available and what they look like. The last argument is image name. The result is shown below:
Hope that helps, good luck.