Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to position an axes in a figure relative to another axes?

When laying out a figure in MATLAB, typing axis equal ensures that no matter what the figure dimensions, the axes will always be square:

enter image description here

My current problem is that I want to add a second axes to this plot. Usually, that's no problem; I would just type axes([x1 y1 x2 y2]), and a new square figure would be added with corners at (x1, y1), (x2, y2), which is a fixed location relative to the figure. The problem is, I want this new axes to be located at a fixed location relative to the first axes.

So, my questions are:

  1. Does anyone know how I can position an axes in a figure by specifying the location relative to another axes?
  2. Assuming I can do 1, how can I have this new axes remain in the same place even if I resize the figure?
like image 791
eykanal Avatar asked Aug 18 '11 20:08

eykanal


People also ask

How do you add an axis to a figure in MATLAB?

Make Axes the Current Axes First create two Axes objects and specify the positions. Display the box outline around each axes. Return the Axes objects as ax1 and ax2 . figure ax1 = axes('Position',[0.1 0.1 .

What does axis tight do in MATLAB?

axis tight sets the axis limits to the range of the data. axis fill sets the axis limits and PlotBoxAspectRatio so that the axes fill the position rectangle.

How do I change the size of an AXE in MATLAB?

To change which aspect of the axes is preserved, specify this property as "innerposition" or "outerposition" . Use this property to query or change the units of the axes position values. The value must be set to "normalized" (the default) to enable automatic axes resizing.


2 Answers

An axis position property is relative to its parent container. Therefore, one possibility is to create a transparent panel with the same size as the first axis, then inside it create the second axis, and set its location and size as needed. The position specified would be as if it were relative to the first axis.

Now we need to always maintain the panel to be the same size/location as the first axis. Usually this can be done using LINKPROP which links a property of multiple graphic objects (panel and axis) to be the same, namely the 'Position' property.

However, this would fail in your case: when calling axis image, it fixes the data units to be the same in every direction by setting aspect ratio properties like 'PlotBoxAspectRatio' and 'DataAspectRatio'. The sad news is that the 'Position' property will not reflect the change in size, thus breaking the above solution. Here is an example to illustrate the problem: if you query the position property before/after issuing the axis image call, it will be the same:

figure, plot(1:10,1:10)
get(gca,'Position')
pause(1)
axis image
get(gca,'Position')

Fortunately for us, there is a submission on FEX (plotboxpos) that solves this exact issue, and returns the actual position of the plotting region of the axis. Once we have that, it's a matter of syncing the panel position to the axis position. One trick is to create a event listener for when the axis changes size (it appears that the 'TightInset' property changes unlike the 'Position' property, so that could be the trigger in our case).

I wrapped the above in a function AXESRELATIVE for convenience: you call it as you would the builtin AXES function. The only difference is you give it as first argument the handle to the axis you want to relatively-position the newly created axis against. It returns handles to both the new axis and its containing panel.

Here is an example usage:

%# automatic resize only works for normalized units
figure
hParentAx = axes('Units','normalized');
axis(hParentAx, 'image')

%# create a new axis positioned at normalized units with w.r.t the previous axis
%# the axis should maintain its relative position on resizing the figure
[hAx hPan] = axesRelative(hParentAx, ...
    'Units','normalized', 'Position',[0.7 0.1 0.1 0.1]);
set(hAx, 'Color','r')

And the function implementation:

function [hAx hPan] = axesRelative(hParentAx, varargin)
    %# create panel exactly on top of parent axis
    s = warning('off', 'MATLAB:hg:ColorSpec_None');
    hPan = uipanel('Parent',get(hParentAx, 'Parent'), ...
        'BorderType','none', 'BackgroundColor','none', ...
        'Units',get(hParentAx,'Units'), 'Position',plotboxpos(hParentAx));
    warning(s)

    %# sync panel to always match parent axis position
    addlistener(handle(hParentAx), ...
        {'TightInset' 'Position' 'PlotBoxAspectRatio' 'DataAspectRatio'}, ...
        'PostSet',@(src,ev) set(hPan, 'Position',plotboxpos(hParentAx)) );

    %# create new axis under the newly created panel
    hAx = axes('Parent',hPan, varargin{:});
end

axesRelative


On a completely different note: before you recent edit, I got the impression that you were trying to produce a scatter plot of images (i.e like a usual scatter plot, but with full images instead of points).

What you suggested (from what I understand) is creating one axis for each image, and setting its position corresponding to the x/y coordinates of the point.

My solution is to use the IMAGE/IMAGESC functions and draw the small images by explicitly setting the 'XData' and 'YData' properties to shift and scale the images appropriately. The beauty of this is it require a single axis, and doesn't suffer from having to deal with resizing issues..

Here is a sample implementation for that:

%# create fan-shaped coordinates
[R,PHI] = meshgrid(linspace(1,2,5), linspace(0,pi/2,10));
X = R.*cos(PHI); Y = R.*sin(PHI);
X = X(:); Y = Y(:);
num = numel(X);

%# images at each point (they don't have to be the same)
img = imread('coins.png');
img = repmat({img}, [num 1]);

%# plot scatter of images
SCALE = 0.2;             %# image size along the biggest dimension
figure
for i=1:num
    %# compute XData/YData vectors of each image
    [h w] = size(img{i});
    if h>w
        scaleY = SCALE;
        scaleX = SCALE * w/h;
    else
        scaleX = SCALE; 
        scaleY = SCALE * h/w;
    end
    xx = linspace(-scaleX/2, scaleX/2, h) + X(i);
    yy = linspace(-scaleY/2, scaleY/2, w) + Y(i);

    %# note: we are using the low-level syntax of the function
    image('XData',xx, 'YData',yy, 'CData',img{i}, 'CDataMapping','scaled')
end
axis image, axis ij
colormap gray, colorbar
set(gca, 'CLimMode','auto')

image_scatter

like image 73
Amro Avatar answered Nov 16 '22 00:11

Amro


This is usually the sort of thing you can take care of with a custom 'ResizeFcn' for your figure which will adjust the position and size of the smaller axes with respect the the larger. Here's an example of a resize function that maintains the size of a subaxes so that it is always 15% the size of the larger square axes and located in the bottom right corner:

function resizeFcn(src,event,hAxes,hSubAxes)

  figurePosition = get(get(hAxes,'Parent'),'Position');
  axesPosition = get(hAxes,'Position').*figurePosition([3 4 3 4]);
  width = axesPosition(3);
  height = axesPosition(4);
  minExtent = min(width,height);
  newPosition = [axesPosition(1)+(width-minExtent)/2+0.8*minExtent ...
                 axesPosition(2)+(height-minExtent)/2+0.05*minExtent ...
                 0.15*minExtent ...
                 0.15*minExtent];
  set(hSubAxes,'Units','pixels','Position',newPosition);

end

And here's an example of its use:

hFigure = figure('Units','pixels');  %# Use pixel units for figure
hAxes = axes('Units','normalized');  %# Normalized axes units so it auto-resizes
axis(hAxes,'image');                 %# Make the axes square
hSubAxes = axes('Units','pixels');   %# Use pixel units for subaxes
set(hFigure,'ResizeFcn',{@resizeFcn,hAxes,hSubAxes});  %# Set resize function
like image 40
gnovice Avatar answered Nov 15 '22 23:11

gnovice