Trial event figure maker

This function makes a picture like this:

I wrote this because I was too lazy to do it by hand :) You set it up using a task_specs structure illustrated as the default in the m-file. The stimulus displays would probably require most extra work of course.

As always, remember to save the figure in a suitable format (e.g. tiff) if you plan on ever having it printed!

function task_figure(varargin)

% function task_figure(task_specs, angle)
%
% task_specs.times [ms]
% task_specs.names
% task_specs.interval_names
% task_specs.stim
% task_specs.plotting.eventArrowLength;
% task_specs.plotting.screenSize;
% task_specs.plotting.plotside;
%
% Thomas Edward Gladwin, June 2006. thomasgladwin@hotmail.com
%

if length(varargin) >= 1,
    task_specs = varargin{1};
else,
    task_specs = [];
end;
if isempty(task_specs),
    task_specs.times = [0 1500 3500 4250 5500];
    
    task_specs.names{1} = 'Cue';
    task_specs.names{2} = 'Stimulus';
    task_specs.names{3} = 'End of trial';
    task_specs.names{4} = 'Next trial';
    task_specs.names{5} = [];

    task_specs.interval_names{1} = 'Preparation interval';
    task_specs.interval_names{2} = 'Response interval';
    task_specs.interval_names{3} = 'Inter-trial interval';
    task_specs.interval_names{4} = [];

    task_specs.stim{1} = {'*' 'R'};
    task_specs.stim{2} = {'L' 'R'};
    task_specs.stim{3} = {' '};
    task_specs.stim{4} = {'*' '*'};
    task_specs.stim{5} = [];
    
    task_specs.plotting.eventArrowLength = 0.1;
    task_specs.plotting.screenSize = 0.1;
    task_specs.plotting.plotside = 600;
end;

if length(varargin) >= 2,
    angle = varargin{2};
else
    angle = pi / 8;
end;

conditionNames = fieldnames(task_specs);
t0 = task_specs.times(1);
t_end = task_specs.times(end);
t_buffer = 0; %0.1 * (t_end - t0);
t0 = t0 - t_buffer;
t_end = t_end + t_buffer;
timeLineX = [t0 t_end * cos(angle)];
timeLineY = [t0 t_end * sin(angle)];
timeLineL = sqrt(diff(timeLineX) .^ 2 + diff(timeLineY) .^ 2);

figure(1); clf; hold on;
minx = timeLineX(1) - 0.1 * abs(diff(timeLineX));
maxx = timeLineX(2) + 0.1 * abs(diff(timeLineX));
miny = timeLineY(1) - 1.2 * task_specs.plotting.screenSize * timeLineL;
maxy = timeLineY(2) + 1.6 * task_specs.plotting.eventArrowLength * timeLineL;
XLim(sort([minx maxx]));
YLim(sort([miny maxy]));
set(gca, 'XTick', []);
set(gca, 'YTick', []);
fprintf('Now stretch to beautifulness\n');
dx = diff(xlim);
dy = diff(ylim); 
pos = get(gcf, 'Position');
if dx > dy,
    pos = [25 25 task_specs.plotting.plotside task_specs.plotting.plotside * dy / dx];
else,
    pos = [25 25 task_specs.plotting.plotside * dx / dy task_specs.plotting.plotside];
end;
set(gcf, 'Position', pos);

time_line = plot(timeLineX, timeLineY, 'k-'); set(time_line, 'LineWidth', 2);
arrow(timeLineX, timeLineY, 0.1, pi/8, [0 0 0], '-', 2);

% Events
for iScreen = 1:length(task_specs.names),
    screenX = timeLineX(1) + diff(timeLineX) * (task_specs.times(iScreen) + t_buffer) / (t_end + t_buffer);
    screenY = timeLineY(1) + diff(timeLineY) * (task_specs.times(iScreen) + t_buffer)/ (t_end + t_buffer);
    if ~isempty(task_specs.names{iScreen}),
        x = [screenX screenX]
        y = [screenY screenY + task_specs.plotting.eventArrowLength * timeLineL]
        arrow(x, y, 0.2, pi/8, [0 0 0], '-', 1);
        % once for extent...
        t = text(x(2), y(2), task_specs.names{iScreen});
        ext = get(t, 'Extent');
        xshift = ext(3) / 2;
        yshift = ext(4) / 2;
        set(t, 'Visible', 'off');
        t = text(x(2) - xshift, y(2) + yshift, task_specs.names{iScreen});
    end;
end;

% interval names
for iScreen = 1:length(task_specs.interval_names)
    screenX1 = timeLineX(1) + diff(timeLineX) * (task_specs.times(iScreen) + t_buffer) / (t_end + t_buffer);
    screenY1 = timeLineY(1) + diff(timeLineY) * (task_specs.times(iScreen) + t_buffer)/ (t_end + t_buffer);
    screenX2 = timeLineX(1) + diff(timeLineX) * (task_specs.times(iScreen + 1) + t_buffer) / (t_end + t_buffer);
    screenY2 = timeLineY(1) + diff(timeLineY) * (task_specs.times(iScreen + 1) + t_buffer)/ (t_end + t_buffer);
    screenX = mean([screenX1 screenX2]);
    screenY = mean([screenY1 screenY2]);
    if ~isempty(task_specs.interval_names{iScreen}),
        screenSize0 = task_specs.plotting.screenSize * timeLineL;
        intArrowLength = 1.5 * screenSize0;
        x = [screenX screenX + cos(pi / 4) * intArrowLength];
        y = [screenY screenY - sin(pi / 4) * intArrowLength];
        arrow(x, y, 0.2, pi/8, [0 0 0], '-', 1);
        % once for extent...
        t = text(x(2), y(2), task_specs.interval_names{iScreen});
        ext = get(t, 'Extent');
        xshift = ext(3) / length(task_specs.interval_names{iScreen});
        yshift = ext(4) / length(task_specs.interval_names{iScreen});
        set(t, 'Visible', 'off');
        t = text(x(2) + xshift, y(2) - yshift, task_specs.interval_names{iScreen});
    end;
end;

% screens
for iScreen = length(task_specs.names):(-1):1,
    screenX = timeLineX(1) + diff(timeLineX) * (task_specs.times(iScreen) + t_buffer) / (t_end + t_buffer);
    screenY = timeLineY(1) + diff(timeLineY) * (task_specs.times(iScreen) + t_buffer)/ (t_end + t_buffer);
    if ~isempty(task_specs.stim{iScreen}),
        screenSize0 = task_specs.plotting.screenSize * timeLineL;
        pos = [screenX - screenSize0 / 2 screenY - screenSize0 screenSize0 screenSize0];
        verticesX = [pos(1); pos(1) + pos(3); pos(1) + pos(3); pos(1)];
        verticesY = [pos(2); pos(2); pos(2) + pos(4); pos(2) + pos(4)];
        fill(verticesX, verticesY, [1 1 1]);
        rect0 = rectangle('Position', pos);
        nStim = length(task_specs.stim{iScreen});
        for iStim = 1:nStim,
            x0 = pos(1) + pos(3) / 2;
            dStim = 0.8 * pos(4) / nStim;
            y0 = (pos(2) + 0.9 * pos(4)) - dStim * iStim;
            % once for extent...
            t = text(x0, y0, task_specs.stim{iScreen}{iStim});
            ext = get(t, 'Extent');
            xshift = ext(3) / 2;
            yshift = ext(4) / 2;
            set(t, 'Visible', 'off');
            t = text(x0 - xshift, y0 + yshift, task_specs.stim{iScreen}{iStim});
        end;
    end;
end;

function arrow(varargin)

% function arrow(x0, y0, r, angle, arrowheadsize, arrowheadangle, colorvec, linestyle, linewidth)
% function arrow(x_comp, y_comp, arrowheadsize, arrowheadangle, colorvec, linestyle, linewidth)
%
% x_comp and y_comp must contain two values.
% Trailing plotting parameters may be ommitted. Any plotting parameter can be specified as [] for defaults.

axes0 = gca;
if length(varargin{1}) == 1,
    x0 = varargin{1};
    y0 = varargin{2};
    r = varargin{3};
    angle = varargin{4};
    x_comp = [x0 x0 + r * cos(angle)];
    y_comp = [x0 x0 + r * sin(angle)];
    plotParIndex = 5;
else,
    x_comp = varargin{1};
    y_comp = varargin{2};
    r = sqrt(diff(x_comp) .^ 2 + diff(y_comp) .^ 2);
    angle = atan2(diff(y_comp), diff(x_comp));
    plotParIndex = 3;
end;

if length(varargin) >= plotParIndex,
    headSize = varargin{plotParIndex} * r;
    if isempty(headSize ),
        headSize = 0.1 * r;
    end;
else,
    headSize = 0.1 * r;
end;
if length(varargin) >= plotParIndex + 1,
    headAngle = varargin{plotParIndex + 1};
    if isempty(headAngle),
        headAngle = pi / 8;
    end;
else,
    headAngle = pi / 8;
end;
if length(varargin) >= plotParIndex + 2,
    color = varargin{plotParIndex + 2};
    if isempty(color),
        color = [0 0 0];
    end;
else,
    color = [0 0 0];
end;
if length(varargin) >= plotParIndex + 3,
    linestyle = varargin{plotParIndex + 3};
    if isempty(linestyle),
        linestyle = '-';
    end;
else,
    linestyle = '-';
end;
if length(varargin) >= plotParIndex + 4,
    linewidth = varargin{plotParIndex + 4};
    if isempty(linewidth),
        linewidth = 0.1;
    end;
else,
    linewidth = 0.1;
end;

holding = get(axes0, 'NextPlot');
if strcmp(holding, 'replace') == 1,
    cla;
end;
%set(axes0, 'NextPlot', 'add');

% The main line
drawLine(x_comp, y_comp, color, linestyle, linewidth);

% The head: draw on origin on a lying arrow, then rotate and translate.
headPoint = getHeadPoint(angle, headSize, headAngle, x_comp, y_comp);
drawLine([x_comp(2) headPoint(1)], [y_comp(2) headPoint(2)], color, linestyle, linewidth);
headPoint = getHeadPoint(angle, headSize, -headAngle, x_comp, y_comp);
drawLine([x_comp(2) headPoint(1)], [y_comp(2) headPoint(2)], color, linestyle, linewidth);

% whitespace if necessary
xlim1 = [min(x_comp - headSize) max(x_comp + headSize)];
ylim1 = [min(y_comp - headSize) max(y_comp + headSize)];
xlim0 = get(gca, 'XLim');
ylim0 = get(gca, 'YLim');
xlim2 = [min([xlim0(1); xlim1(1)]) max([xlim0(2); xlim1(2)])];
ylim2 = [min([ylim0(1); ylim1(1)]) max([ylim0(2); ylim1(2)])];
set(gca, 'XLim', xlim2);
set(gca, 'YLim', ylim2);

%set(axes0, 'NextPlot', holding);

function line0 = drawLine(x_comp, y_comp, color, linestyle, linewidth);
line0 = line(x_comp, y_comp);
set(line0, 'Color', color);
set(line0, 'LineStyle', linestyle);
set(line0, 'LineWidth', linewidth);

function headPoint = getHeadPoint(angle, headSize, headAngle, x_comp, y_comp)
headPoint = headSize * [-cos(headAngle); sin(headAngle)];
rotation_matrix = [cos(angle) -sin(angle); sin(angle) cos(angle)];
headPoint = rotation_matrix * headPoint + [x_comp(2); y_comp(2)];