Dynamically reorder subplots in GUI

Hello everyone,
I'm developing a GUI using GUIDE. The functionality I want to achieve implies that when the user clicks a button, a new subplot is added to a parent figure. I have managed to structure the subplots in a square layout (for instance, if the user clicks the button 9 times, 9 plots are displayed in 3 rows and 3 columns.) However, everytime a new subplot is added, I delete all the subplots handles and re-create them to include the new one. This isn't the desired behaviour since I just want to reorder the subplots (change their position) in figure in a fair enough square layout. Any help would be appreciated.

5 Comments

Jan
Jan on 28 Jun 2017
Edited: Jan on 28 Jun 2017
What exactly is the problem? If you do not want to delete the axes, what about not deleting them, but assigning the new position? If you post your current code, suggestion the small modification would be easier.
What do you want for 3 axes? A 2x2 array with an empty field, or a 1x3 or 3x1 setup?
Lask
Lask on 28 Jun 2017
Edited: Lask on 28 Jun 2017
Hi Jan, first of all, thanks for your time. The problem is, initially when you create a subplot, you can assign its position very easyly using the three arguments in subplot(x,y,z), however, when the handle is already created, you only can change its 'Position' property, which is based in coordinates and changing the 'Position' of a large number of subplots for displaying a square layout seems to be a bit tedious.
"What do you want for 3 axes? A 2x2 array with an empty field, or a 1x3 or 3x1 setup?" I have a 2x2 setup with an empty field.
This is part of the callback function of the button I mentioned:
% Delete subplots in canvas for restructuring
delete(findall(gcf,'type','axes'));
% Increment the number of devices (subplots) on click
handles.n_devices = handles.n_devices + 1;
% Plot devices data including the new one
for i=1:handles.n_devices
% Create subplots handles
a_subplot_handle = subplot(handles.n_rows,handles.n_cols,i, 'Parent', handles.uipanel1);
% Update subplots handles array
handles.subplots_handles = [ handles.subplots_handles;a_subplot_handle];
% Plot dummy data (same data is plotted in every subplot)
plot(handles.x, handles.x.^(5));
a_subplot_handle.UIContextMenu = c;
end
% Logic for incrementing the number of rows (square-fitting)
if handles.n_devices == (handles.n_rows)^2
handles.n_rows = handles.n_rows + 1;
end
% Logic for incrementing the number of cols (square-fitting)
if (handles.n_devices/handles.n_rows == handles.n_cols)
handles.n_cols = handles.n_cols + 1;
end
% Save changes
guidata(hObject, handles);
To be honest you would be better off doing something as dynamic as this programatically and using just regular axes that you position mathematically according to an algorithm dependent on how many there are.
I have done something similar with panels of images. It took a fair bit of programming, but it worked at least. I never dynamically moved them after creating them, but I could do easily enough if I wanted because they were dynamically created in the first place.
Thanks Adam. What do you mean "programatically"? I'm doing the re-positioning of the subplots with the code I posted before. I'm open to any alternative or hint you could give me.
Well, by programmatically I was mostly referring to positionining axes explicitly based on an algorithm that calculates the positions for a grid of whatever size you give it.
subplot is limited if you use that and you have little choice but to keep deleting and recreating axes. I don't fully understand what your workflow is as to what is on these axes, but if you are just wanting to add a new axes deleting and recreating the existing 9 is certainly not very efficient.

Sign in to comment.

 Accepted Answer

Jan
Jan on 28 Jun 2017
Edited: Jan on 30 Jun 2017
Hm. I've written a small example which shows, that moving the existing axes is not tedious:
function Callback(hObject, EventData) % *** ERROR - WILL CRASH! ***
handles = guidata(hObject);
% Increment the number of devices (subplots) on click
handles.n_devices = handles.n_devices + 1;
n_rows = ceil(sqrt(handles.n_devices));
% Old plots must be moved:
if handles.n_rows ~= n_rows
for iDev = 1:handles.n_devices - 1
% *ERROR*: subplot deletes existing axes!
axesH = subplot(n_rows, n_rows, iDev, 'Parent', handles.uipanel1);
set(handles.subplots_handles(iDev), 'Position', get(axesH, 'Position'));
delete(axesH);
end
end
handles.n_rows = n_rows;
handles.n_cols = n_rows;
% Plot new device:
a_subplot_handle = subplot(handles.n_rows, handles.n_cols, handles.n_devices, ...
'Parent', handles.uipanel1);
handles.subplots_handles(handles.n_devices) = a_subplot_handle;
plot(1:10, rand(1, 10));
guidata(hObject, handles);
end
Unfortunately it does not run, because subplot deletes the former axes overlapping with the new one. What a pitty. All I want to get is the new position of the axes, but subplot decides smartly that I want to delete objects.
Then I have to rewrite subplot to reply the position only and to avoid any fancy stuff. Wait a little bit...
[EDITED] A working version:
function main % Just to create a dummy figure
FigH = figure;
handles.n_devices = 0;
handles.n_rows = 0;
handles.n_cols = 0;
handles.subplots_handles = matlab.graphics.axis.Axes([]);
handles.uipanel1 = uipanel('Units', 'pixels');
handles.FullPos = get(handles.uipanel1, 'Position') + [40, 40, -60, -60];
uicontrol('Style', 'PushButton', 'String', 'Add plot', ...
'Position', [5, 5, 80, 20], ...
'Callback', @Callback);
guidata(FigH, handles);
end
function Callback(hObject, EventData)
handles = guidata(hObject);
% Increment the number of devices (subplots) on click
handles.n_devices = handles.n_devices + 1;
n_rows = ceil(sqrt(handles.n_devices));
% Old plots must be moved:
if handles.n_rows ~= n_rows
AxesPos = SubPlotPos(handles.FullPos, n_rows, n_rows);
for iDev = 1:handles.n_devices - 1
set(handles.subplots_handles(iDev), 'Position', AxesPos(iDev, :));
end
end
handles.n_rows = n_rows;
handles.n_cols = n_rows;
% Plot new device:
AxesPos = SubPlotPos(handles.FullPos, n_rows, n_rows, handles.n_devices);
a_subplot_handle = axes('Units', 'Pixels', 'Position', AxesPos, ...
'Parent', handles.uipanel1);
handles.subplots_handles(handles.n_devices) = a_subplot_handle;
plot(1:10, rand(1, 10));
guidata(hObject, handles);
end
This replaces subplot:
function AxesPos = SubPlotPos(FullPos, nCol, nRow, k)
% Position of diagrams - a very light SUBPLOT
% AxesPos = SubPlotPos(Area, nRow, nCol, kAxes)
% A very light version of SUBPLOT without shifting, force requests, an so on.
% Only the positions are replied and the caller has to create its axes itself.
% Input:
% Area: Available area [X, Y, W, H] in pixels. This area is filled by
% the diagrams.
% nCol, nRow: Number of columns and rows.
% kAxes: Reply position of the k'th axes only.
% Optional. Without this, all nCol*nRow positions are replied.
%
% Author: Jan Simon, Heidelberg, (C) 2017, License: CC BY-SA 3.0
% Formula: Space = a + b * n
% Increase [b] to increase the space between the diagrams.
if nRow < 3
BplusT = 0.18;
else
BplusT = 0.09 + 0.045 * nRow;
end
if nCol < 3
LplusR = 0.18;
else
LplusR = 0.09 + 0.05 * nCol;
end
spread = 0.7;
nPlot = nRow * nCol;
plots = 0:(nPlot - 1);
row = (nRow - 1) - fix(plots(:) / nCol);
col = rem(plots(:), nCol);
col_offset = FullPos(3) * LplusR / (nCol - LplusR);
row_offset = FullPos(4) * BplusT / (nRow - BplusT);
totalwidth = FullPos(3) + col_offset;
totalheight = FullPos(4) + row_offset;
width = totalwidth / nCol - col_offset;
height = totalheight / nRow - row_offset;
if width * 2 > totalwidth / nCol
if height * 2 > totalheight / nRow
AxesPos = [(FullPos(1) + col * totalwidth / nCol), ...
(FullPos(2) + row * totalheight / nRow), ...
width(ones(nPlot, 1), 1), ...
height(ones(nPlot, 1), 1)];
else
AxesPos = [(FullPos(1) + col * totalwidth / nCol), ...
(FullPos(2) + row * FullPos(4) / nRow), ...
width(ones(nPlot, 1), 1), ...
(spread * FullPos(ones(nPlot, 1), 4) / nRow)];
end
else
if height * 2 <= totalheight / nRow
AxesPos = [(FullPos(1) + col * FullPos(3) / nCol), ...
(FullPos(2) + row * FullPos(4) / nRow), ...
(spread * FullPos(ones(nPlot, 1), 3) / nCol), ...
(spread * FullPos(ones(nPlot, 1), 4) / nRow)];
else
AxesPos = [(FullPos(1) + col * FullPos(3) / nCol), ...
(FullPos(2) + row * totalheight / nRow), ...
(spread * FullPos(ones(nPlot, 1), 3) / nCol), ...
height(ones(nPlot, 1), 1)];
end
end
if nargin > 3 && ~isempty(k)
AxesPos = AxesPos(k, :);
end
end
These are not exactly the same positions as with subplot. But you can adjust the parameters to your needs.
See also the mayn submissions in the FEX to improve the subplot command: FEX: Search for "subplot"

1 Comment

Lask
Lask on 28 Jun 2017
Edited: Lask on 28 Jun 2017
Thanks Jan. Yes, I can see what you're trying to do. I'm also trying to do something similar. I'm using an aux_figure to display the first subplot and get its position so I can move the original subplot without overlap. Hopefully we'll get to some interesting point. I'll wait, thanks.

Sign in to comment.

More Answers (0)

Categories

Find more on Graphics Performance in Help Center and File Exchange

Asked:

on 28 Jun 2017

Edited:

Jan
on 30 Jun 2017

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!