MATLAB: Dynamically reorder subplots in GUI

guiguidehandlessubplot

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.

Best Answer

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"