Code covered by the BSD License  

Highlights from
JISUBPLOT + NEXTPLOT: enhanced replacement for subplot

image thumbnail
from JISUBPLOT + NEXTPLOT: enhanced replacement for subplot by John Iversen
Automatically create complex subplot layouts

ret=jisubplot(rows,columns,pane,orientation,gap,varargin)
function ret=jisubplot(rows,columns,pane,orientation,gap,varargin)
% JISUBPLOT  Set up a figure for multi-pane plotting. Used with NEXTPLOT.
%
%   axis=jisubplot(rows,columns,[pane],[orientation],[gap],['param1','value1',...])
%
%   OVERVIEW: Enhanced replacement for SUBPLOT
%
%       Problem:
%           Multi-axis plotting (using SUBPLOT) is great and essential, but has
%           shortcomings. The main one is that keeping track of the axis indexes
%           can be tedious if you want to do anything more elaborate than plotting
%           in row-order. In addition, the size of all axes are the same, and
%           spacing is not adjustable.
%
%       Solution: JISUBPLOT + NEXTPLOT
%           What you might really want is to set up a grid of subplots once and
%           then just tell the figure when you want to move to the next plot,
%           letting it take care of the details for you.
%
%           That's what the pair of functions JISUBPLOT and NEXTPLOT can do.
%
%           You can move by row, or by column, or arbitrarily. You can create
%           subplots of different sizes. Finally, wouldn't it be nice if
%           when you changed a figure's orientation (e.g. orient tall),
%           it actually changed shape to reflect that?
%
%       Examples:
%
%           figure
%           jisubplot(4,2,1)        % can be used just like SUBPLOT
%           plot(X)
%           nextplot                % advance to next axis
%           plot(something_else)
%           nextplot
%           plot(some_other_thing)
%
%           nextplot('newRow')      % start a new row of axes
%           plot(something_new)
%           nextplot('byCol')       % move down columns
%
%           %% jisubplot / nextplot is especially useful in loops
%           figure
%           jisubplot(4,4,0)
%           for idx = 1:32,
%               nextplot
%               plot(data(idx,:))
%           end
%
%           %% a more complete usage: specify orientation, plot spacing and fontsize
%           figure
%           jisubplot(5,3,0,'tall',[.3 .3],'fontsize',9)
%           nextplot
%
%       See JISUBPLOTDEMO, NEXTPLOT and CURRENTPLOTIS for more advanced usage.
%
%       Description:
%
%       Sets up current figure for multi-pane plotting in a rows x columns grid.
%       To advance among sub-plots, use NEXTPLOT, or specify a value for pane.
%       If pane is unspecified (or=0), doesn't immediately create an axis for
%       plotting, but initializes current figure so that a subsequent call to
%       NEXTPLOT will create the first axis (pane 1). Note, this will also clear
%       the figure. If pane > rows*columns, or NEXTPLOT advances past the current
%       figure, will automatically overflow to a new figure with the same properites.
%
%       If orientation is specified, the figure will resize to have the correct
%       aspect ratio for that type of orientation (e.g. 'tall' or 'landscape').
%       The spacing between plots can be specified (gap), as well as param/value
%       pairs to pass on to each axis.
%
%
%   USAGE:
%
%   axis=jisubplot(rows,columns,[pane],[orientation],[gap],['param1','value1',...])
%
%   INPUTS
%       rows, columns   number of axes in horizontal and vertical directions
%       pane            current sub-plot (if 0 or unspecified, initialze figure)
%                           pane can be specified in two ways:
%                               1) scalar, pick that pane (as in subplot)
%                               2) vector, [toprow leftcol width height] 
%                               row,col of upper left and width & height of axis
%       orientation     'portrait', 'tall', 'landscape' or '' (default is portrait)
%       gap             [x y] space between axes as proportion of axis size
%                           (if unspecified, or [], default to [0.2 0.2].
%                           Note: as is, this is optimized for a high density of
%                           plots, without axis labels. Increase the gap to allow
%                           room for axis labels and titles, etc, use a smaller
%                           axis font size, or use CURRENTPLOTIS to selectively
%                           add labels based on axis position on page.)
%       prop,value      any additional prop/value pairs will be applied to each 
%                           new axis
%
%   OUTPUT
%       axis            handle to current axis (if non-zero value for pane was given)
%
%
%   NOTES
%       
%       if pane is > rows*columns: will automatically overflow to a new figure
%           created with the same properties.
%
%
%   See also NEXTPLOT, CURRENTPLOTIS, JISUBPLOTDEMO.
%
%   John Iversen iversen@nsi.edu
%

%  JRI 4/99    John Iversen, iversen@nsi.edu
%  JRI 4/21/00 added array to store handles to axes. if hold is 'on'
%      activate existing axis, if not, delete and create new one
%  12/16/00 allow separate specification of x & y gap
%  10/11/01 Port to NSI
%  12/11/01 More flexible specification of pane
%       Figure retains its state in appdata, but is historically compatible with
%           older matlab versions lacking appdata (uses userdata in this case).

% Free for all uses, but please retain the following:
%   Original Author: John Iversen
%   john_iversen@post.harvard.edu

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%set defaults
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

defaultgap = [.25 .25];
usingdefaultgap = 0;
if nargin == 2, pane = 0; end
if nargin <= 3 || isempty(orientation),
    orientation='portrait';
end
if nargin <= 4 || isempty(gap),
    gap=defaultgap;
    usingdefaultgap=1;
end
if nargin >  5, %optional param/value pairs specified
    params = varargin;
else
    params = [];
end

%note, the following refers to system-wide global, G, specific to my personal
%   environment. If this global contains fields G.plot.layout.xplotregion,
%   G.plot.layout.yplotregion and G.plot.layout.superimpose, will use these values;
%   if it's not defined, will set defaults. This can be removed if it conflicts
%   with your personal style, or another use of global G

global G

if isempty(G) | ~isfield(G.plot,'layout') %take care of case when global not defined
    xplotregion = [.05 .95];    %the region on the page in which subplots will fit
    yplotregion = [.05 .95];    %   normalized [0,1] units
else %grab vales from global
    yplotregion = G.plot.layout.yplotregion;
    xplotregion = G.plot.layout.xplotregion;
end
xwidth=xplotregion(2)-xplotregion(1);
ywidth=yplotregion(2)-yplotregion(1);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%get current state, or initialize if it's a new figure
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

currentfig = gcf; %(creates a figure if there is none)
try
    UD = getappdata(currentfig,'JRI_jisubplotData');
    haveAppdata = 1;
catch
    UD = get(currentfig,'userdata');
    haveAppdata = 0;
end
if isempty(UD), %no userdata means it's a fresh figure, so initialize it
    isVisible = get(currentfig,'visible'); %preserve its (in)visibility
    axisHandles=initializeFigure(currentfig, rows, columns, orientation);
    set(currentfig,'visible',isVisible)
else %if have userdata, unpack it, check for consistency
    axisHandles = UD.axisHandles;
    if rows~=UD.rows | columns~=UD.columns, %if we've changed the number of panes
        axisHandles=zeros(rows,columns); %the old handles aren't useful
    end
    orientation = UD.orientation;
    if nargin>5, %if we've specified extra params, override earlier ones
        params=varargin;
    else
        params = UD.params;
    end
    if usingdefaultgap, %gap wasn't passed in explicitly,
        gap=UD.gap; %so use saved value
    end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%calculate extent of axis, given the pane number
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%   pane is 4-element vector specifying row,col of upper left corner
%       and width and height in panes

%expand scalar value of pane
pl = length(pane);
switch pl,
    case 1,
        if pane ~= 0,
            col=mod(pane-1,columns)+1;
            row=floor((pane-1)/columns)+1;
            pane = [row col 1 1];
        else
            pane = [0 0 1 1];
        end
    case 4,
        %do nothing
    otherwise
        error('Incorrect specification of pane: must be scalar or 4-element')
end

%Calculate axis size
ppp=rows*columns;
xsize=1/columns;
ysize=1/rows;
dx=xsize*gap(1)/2;
dy=ysize*gap(2)/2;

%handle cases where we move to a new figure (if pane moves past current figure)
%   for multi-pane axes, this is computed based on upper left pane number
if pane(1) > 0,
    ulpane = (pane(1)-1) * columns + pane(2);
    fig=max(floor((ulpane-1)/ppp),0)+currentfig;
    %quick fix for advancing off the page in column mode 
    if pane(2)>columns & pane(1)==1,
        fig = currentfig+1;
        ulpane=ppp+1;
    end

    if (fig ~= currentfig), %if we're on a new figure, create and initialize
        ulpane=ulpane-(ppp*(fig-currentfig));
        pane(1)=mod(ulpane-1,columns)+1;
        pane(2)=floor((ulpane-1)/columns)+1;
        axisHandles=initializeFigure(fig,rows,columns,orientation);
        %if previous figure was hidden, hide this one, too
        set(fig,'visible',get(currentfig,'visible'));
        currentfig = fig;
    end
end

%bounds checking
ulrow = pane(1);
ulcol = pane(2);
w = pane(3);
h = pane(4);
if any([w h]>1),
    if (w > ( 1 + columns - ulcol)) || ...
            (h > (1 + rows - ulrow)),
        error('width or height exceed available space on figure')
    end
end

if (pane(1)>0),	%create an axis
    currentaxis=axisHandles(pane(1),pane(2));

    %for existing axes, use hold state to determine whether to add to axis
    %  or create a new one
    if currentaxis ~= 0,
        try
            superimpose = strcmp(get(currentaxis,'nextplot'),'add') & ...
                strcmp(get(get(currentaxis,'parent'),'nextplot'),'add');
        catch %current axis is invalid (user may have deleted it), so set to zero
            currentaxis = 0;
            superimpose = 0;
        end
    end

    if currentaxis==0 | superimpose==0,	%no current axis or no superimpose:
        %we will make a new axis

        if currentaxis ~=0, %we're not suprimposing, so delete old axis
            try
                delete(currentaxis);
            catch
                currentaxis = 0;
            end
        end

        %create new axis
        row = rows - (pane(1) + (pane(4) - 1));
        col = pane(2) - 1;
        rect=[(col*xsize+dx)*xwidth+xplotregion(1) ... %x
            (row*ysize+dy)*ywidth+yplotregion(1) ... %y
            (xsize*pane(3)-dx*2)*xwidth... %width
            (ysize*pane(4)-dy*2)*ywidth]; %height

        aa=axes('position',rect);
        %axis equal
        axisHandles(pane(1), pane(2))=aa;
        if ~isempty(params)
            set(aa,params{:});
        end

    else %we have a current axis and want to superimpose
        set(currentfig,'CurrentAxes',currentaxis);
        hold on
        aa=currentaxis;
    end
else %pane == 0, don't create an axis
    aa=[];
end

%save figure userdata
UD.rows = rows;
UD.columns = columns;
UD.pane = pane;
UD.gap = gap;
UD.orientation = orientation;
UD.axisHandles = axisHandles;
UD.params = params;

if haveAppdata,
    setappdata(currentfig,'JRI_jisubplotData',UD);
else
    set(currentfig,'userdata',UD);
end

if nargout,
    ret=aa;
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function axisHandles=initializeFigure(currentfig, rows, columns,orientation)
%setup new figure's userdata and size
%return matrix of axis handles (initialized to zero)

axisHandles=zeros(rows,columns);
%set proportions of figure
figure(currentfig); clf reset
try
    rmappdata(currentfig,'JRI_jisubplotData');
end
pos=get(currentfig,'position');
pos(1)=16 + 16;     %+ 16*currentfig; % to stagger

%adjust figure size according to screen size
saveUnits = get(0,'units');
set(0,'units','pixels');
ss = get(0,'screensize');
%want tall oriented figure to be 85% of screen height
max_size = ss(4) * 0.85;

switch orientation
    case 'portrait'

    case 'tall'
        pos(3)= max_size * 0.75;
        pos(4) = max_size;

    case 'landscape'
        pos(3) = max_size;
        pos(4) = max_size * 0.75;

    case ''
        orientation='portrait';	%default

    otherwise
        error('Invalid orientation')
end

%keep figure on screen: Y > 0, Y+height < screen height
figtop = pos(2)+pos(4)+80; %80 is for toolbars
if figtop > ss(4) + 10,
    pos(2) = 0.95*ss(4) - figtop;
end
if pos(2) < 0,
    pos(2) = ss(4) * 0.05; %keep bottom slightly above screen bottom
end
set(currentfig,'position',pos)
orient(currentfig, orientation);

set(0,'units',saveUnits)

%set figure background to white
set(currentfig,'color',[1 1 1])

Contact us at files@mathworks.com