function varargout = varyParameters(varargin)
%% Initialize
% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @varyParameters_OpeningFcn, ...
                   'gui_OutputFcn',  @varyParameters_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT


% --- Executes just before varyParameters is made visible.
%% opening function
function varyParameters_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to varyParameters (see VARARGIN)

% Choose default command line output for varyParameters
handles.output = hObject;

%% Initialize figure data
% Update handles structure
handles.mName{1}   = [];           % no model name
handles.model   = [];           % no model
handles.f       = ones(1, 12);  % no parameter multipliers
handles.fData   = ones(1, 2);   % no data multipliers
handles.fT      = 1;            % no time multiplier
handles.expt1   = 1;            % use experiment on axes 1
handles.expt2   = 0;            % plot experiment 0 (SS) on axes 2
handles.offset  = 0;            % start with params 1-12
handles.dOffset = 0;            % start with data rows 1-2
handles.path    = pwd;          % current path (base reference for all subdirectories)

% empty data structure
handles.data    = struct('t', [], 'y', [], 'nD', 0, 'expt', []);

% easier to store them all as one big vector

handles.dataSlider = [handles.dataSlider1; 
                      handles.dataSlider2];

handles.dataText   = [handles.dataText1; 
                      handles.dataText2];

handles.dtext  = [handles.dText1; 
                  handles.dText2];

handles.axes   = [handles.axes1;
                  handles.axes2];

handles.slider = [handles.slider1; 
                  handles.slider2; 
                  handles.slider3; 
                  handles.slider4; 
                  handles.slider5; 
                  handles.slider6; 
                  handles.slider7; 
                  handles.slider8; 
                  handles.slider9; 
                  handles.slider10; 
                  handles.slider11; 
                  handles.slider12];

handles.text   = [handles.text1; 
                  handles.text2; 
                  handles.text3; 
                  handles.text4; 
                  handles.text5; 
                  handles.text6; 
                  handles.text7; 
                  handles.text8; 
                  handles.text9; 
                  handles.text10; 
                  handles.text11; 
                  handles.text12];

handles.ptext  = [handles.ptext1; 
                  handles.ptext2; 
                  handles.ptext3; 
                  handles.ptext4; 
                  handles.ptext5; 
                  handles.ptext6; 
                  handles.ptext7; 
                  handles.ptext8; 
                  handles.ptext9; 
                  handles.ptext10; 
                  handles.ptext11; 
                  handles.ptext12];
guidata(hObject, handles);

%% Load paths
% researchPath = 'C:\Documents and Settings\jaredt\My Documents\research\';
% addpath([handles.path],                     ...
%         [handles.path, '\inputs'],          ...
%         [handles.path, '\simulations'],     ...
%         [handles.path, '\data'],     ...
%         [handles.path, '\fits'],     ...
%         [handles.path, '\models'],      '-end');
% 
% addpath([researchPath, 'utils\data'],       ...
%         [researchPath, 'utils\abstract'],   ...
%         [researchPath, 'utils\'], '-end');
    
%% Return outputs to the command line
function varargout = varyParameters_OutputFcn(hObject, eventdata, handles) 
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;

%% Callback Functions

%% loadModel_Callback
function loadModel_Callback(hObject, eventdata, handles)

mName = get(hObject, 'String');

    varNames = evalin('base', 'who');
    if any(strcmp(mName, varNames));
        m = evalin('base', mName);
        fprintf(1, 'Read model %s from command-line workspace\n', mName);
    elseif exist([mName '_parameters.txt']) && exist([mName '_species.txt']) && exist([mName '_reactions.txt'])
        m = loadModel(mName);
        fprintf(1, 'Loaded model %s from text files\n', mName);
    else
        model = load(mName);
        fprintf(1, 'Loaded model %s from .mat file\n', mName);
        mName = fieldnames(model);
        m = model.(mName{1});
    end
    
    handles.model = m.update(m.p, m.ic);
    handles.mName{1} = mName;
    handles.f     = ones(1, m.nP);
    handles.offset = 0;
    guidata(hObject, handles);
    if isfield(handles.model, 'pNames')   % if you know the parameter names
        for i = 1:min(12, m.nP)
            set(handles.ptext(i), 'String', handles.model.pNames{handles.offset + i});
        end
    else
        for i = 1:min(12, m.nP)        % otherwise
            set(handles.ptext(i), 'String', num2str(handles.offset + i));
        end
    end
    if ~max(0, m.nP - 12)
        set(handles.paramSlider, 'Enable', 'off');
    else
        set(handles.paramSlider, 'Enable', 'on');
        set(handles.paramSlider, 'Max',   max(0, m.nP - 12));
        set(handles.paramSlider, 'Value', max(0, m.nP - 12));
        step = min([1, 1/max([0, m.nP - 12])]);
        set(handles.paramSlider, 'SliderStep', [step step]);
    end
% catch
%     set(hObject, 'String', 'Invalid model file!');
% end

%% loadSim_Callback
function loadSim_Callback(hObject, eventdata, handles)

simName = get(hObject, 'String');
try
    varNames = evalin('base', 'who');
    if any(strcmp(simName, varNames));
        expt = evalin('base', simName);
        fprintf(1, 'Read experiment %s from command-line workspace\n', simName);
    else
        load([simName, '.mat']);
        fprintf(1, 'Read experiment %s from file\n', simName);
    end
    
    for i = 1:length(expt)
        exptNames{i} = expt(i).name;
    end
    
    set(handles.simsList1, 'String', {'none' exptNames{:}});
    set(handles.simsList2, 'String', {'none' exptNames{:}});
    set(handles.simsList1, 'Value', 2);
    set(handles.simsList2, 'Value', 1);
    handles.experiments = expt;
    handles.expt1   = 1;
    handles.expt2   = 0;
    handles.expts = [1 0];

    guidata(hObject, handles);
catch
    set(hObject, 'String', 'Invalid experiment file!');
end

%% loadData callback
function loadData_Callback(hObject, eventdata, handles)

dataName = get(hObject, 'String');

try
    load([dataName, '.mat']);
    handles.data = data;
    handles.fData = ones(1, data(1).nD);
    % put the data on the appropriate fitting window, assuming the fit
    % parameters are also specified
    guidata(hObject, handles);
    if isfield(handles.data(1), 'dNames')   % if you know the parameter names
        for i = 1:min(2, data(1).nD)
            set(handles.dtext(i), 'String', handles.data.dNames{handles.dOffset + i});
        end
    else
        for i = 1:min(2, data(1).nD)        % otherwise
            set(handles.dtext(i), 'String', num2str(handles.dOffset + i));
        end
    end
    if ~max(0, data(1).nD - 2)
        set(handles.dataSlider0, 'Enable', 'off');
    else
        set(handles.dataSlider0, 'Enable', 'on');
        set(handles.dataSlider0, 'Max',   max(0, data(1).nD - 2));
        set(handles.dataSlider0, 'Value', max(0, data(1).nD - 2));
        step = min([1, 1/max([0, data(1).nD - 2])]);
        set(handles.dataSlider0, 'SliderStep', [step step]);
    end
catch
    set(hObject, 'String', 'Invalid data file!');
end

%% loadFit callback
function loadFit_Callback(hObject, eventdata, handles)

fitName = get(hObject, 'String');

try
    load([fitName, '.mat']);
    handles.fitOpts = fitOpts;
    % put the data on the appropriate fitting window, assuming the fit
    % parameters are also specified
    guidata(hObject, handles);
catch
    set(hObject, 'String', 'Invalid fit file!');
end

%% paramSlider_Callback
function paramSlider_Callback(hObject, eventdata, handles)

offset = round(get(hObject, 'Value'));
set(hObject, 'Value', offset);
handles.offset = get(hObject, 'Max') - offset;

m = handles.model;

if isfield(handles.model, 'pNames')   % if you know the parameter names
    for i = 1:min(12, m.nP)
        set(handles.ptext(i), 'String', handles.model.pNames{i + handles.offset});
    end
else
    for i = 1:min(12, m.nP)        % otherwise
        set(handles.ptext(i), 'String', num2str(i + handles.offset));
    end
end
for i = 1:min(12, m.nP)
    val = log10(handles.f(i + handles.offset));
    set(handles.text(i), 'String',  num2str(val));
    set(handles.slider(i), 'Value', val);
end


guidata(hObject, handles);

%% slider_Callback
function slider_Callback(hObject, eventdata, handles, sliderNum)

val = get(hObject, 'Value');
handles.f(handles.offset + sliderNum) = 10^val;
set(handles.text(sliderNum), 'String', num2str(val));

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);


%% text_Callback
function text_Callback(hObject, eventdata, handles, textNum)

val = str2num(get(handles.text(textNum), 'String'));
if isempty(val)
    set(handles.text(textNum), 'String', num2str(get(handles.slider(textNum), 'Value')));
    return
end
set(handles.slider(textNum), 'Value', val);
handles.f(handles.offset + textNum) = 10^val;

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);


%% parameterRange_Callback
function parameterRange_Callback(hObject, eventdata, handles)

range = str2num(get(hObject, 'String'));
if isempty(range), return, end
m = handles.model;
for i = 1:min(12, m.nP)
    val = get(handles.slider(i), 'Value');
    if val < -log10(range), val = -log10(range), end
    if val >  log10(range), val =  log10(range), end
    set(handles.slider(i), 'Value', val);
    set(handles.text(i), 'String', num2str(val));
    handles.f(handles.offset + i) = 10^val;
    
    set(handles.slider(i), 'Max',  log10(range));
    set(handles.slider(i), 'Min', -log10(range));
end

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);


%% dataRange callback
function dataRange_Callback(hObject, eventdata, handles)

range = str2num(get(hObject, 'String'));
if isempty(range), return, end
for i = 1:min(2, handles.data(1).nD)
    val = get(handles.dataSlider(i), 'Value');
    if val < -log10(range), val = -log10(range), end
    if val >  log10(range), val =  log10(range), end
    set(handles.dataSlider(i), 'Value', val);
    set(handles.dataText(i), 'String', num2str(val));
    handles.fD(handles.dOffset + i) = 10^val;
    
    set(handles.dataSlider(i), 'Max',  log10(range));
    set(handles.dataSlider(i), 'Min', -log10(range));
end

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);

%% resetParameters_Callback
function resetParameters_Callback(hObject, eventdata, handles)

m = handles.model;
for i = 1:1:min(12, m.nP)
    set(handles.slider(i), 'Value',  0);
    set(handles.text(i),  'String', '0');
end
handles.f = ones(1, handles.model.nP);

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);


%% saveModel_Callback
function saveModel_Callback(hObject, eventdata, handles)

savePath = [handles.path, '/'];
if exist('models', 'dir')
    savePath = [savePath, 'models\'];
end

name = handles.mName{1};
if iscell(name)
    name = name{1};
end

n = 1;
while 1
    if ~exist(sprintf('%s%s_%0.2d.mat', savePath, name, n), 'file')
        break
    end
    n = n + 1;
end
m   = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
% m.p = handles.model.p(:).*handles.f(:);
save(sprintf('%s%s_%0.2d.mat', savePath, name, n), 'm');


%% export_figures_Callback
function export_figures_Callback(hObject, eventdata, handles)

savePath = [handles.path, '\'];
if exist('output', 'dir')
    savePath = [savePath, 'output\'];
end

tmp    =     get(handles.simsList1, 'String');
sim{1} = tmp{get(handles.simsList1, 'Value')};
tmp    =     get(handles.simsList2, 'String');
sim{2} = tmp{get(handles.simsList2, 'Value')};

n = 1;
while 1
    if ~exist(sprintf('%sdata_%s_%0.2d.mat', savePath, handles.mName{1}, n), 'file')
        break
    end
    n = n + 1;
end
t = handles.t;
y = handles.y;

% save data to a mat file
save(sprintf('%sdata_%s_%0.2d', savePath, handles.mName{1}, n), 't', 'y');

% save figures: plotting loop
colors = 'bgrcmyk';

dataInd = [];
for k = 1:length(handles.data)
    if handles.data(k).nD
        if isfield(handles.data, 'expt')
            dataInd = union(dataInd, find(handles.expts == handles.data(k).expt));
        end
    end
end

for aI = 1:2
    figure
    hold on
    plot(t{aI}, y{aI}(t{aI}));
    if any(dataInd == aI)
        for i = 1:handles.data.nD
            plot(handles.data.t0, handles.fData(i)*handles.data.y0(i,:), colors(handles.data.species(i)));
        end
    end
    legend(handles.model.yNames);
    saveas(gcf, sprintf('%sdata_%s_%0.2d_%s', savePath, handles.mName{1}, n, sim{aI}), 'fig');
    close(gcf)
end

%% simsList1_Callback
function simsList1_Callback(hObject, eventdata, handles)
% Hints: contents = get(hObject,'String') returns simsList1 contents as cell array
%        contents{get(hObject,'Value')} returns selected item from simsList1
handles.expt1 = get(hObject, 'Value') - 1;
handles.expts = [handles.expt1 handles.expt2];

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);

%% simsList2_Callback
function simsList2_Callback(hObject, eventdata, handles)
handles.expt2 = get(hObject, 'Value') - 1;
handles.expts = [handles.expt1 handles.expt2];

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);

%% fit_model callback
function fit_model_Callback(hObject, eventdata, handles)
% **************************
% initialize fit variables
% **************************
data       = handles.data;
fitOpts    = handles.fitOpts;

m          = handles.model;
m          = m.update(m.p.*handles.f, m.ic);

% **************************
% scale data
% **************************
for i = 1:size(data.y0, 1)
    data.y0(i,:) = data.y0(i,:)*handles.fData(i);
end
data.t0 = data.t0*handles.fT;

% **************************
% fit!
% **************************
mFit     =  fitParametersNoJac(m, data, simOpts, inpOpts, fitOpts);

% now, do something with this fit model!
handles.model = mFit;
handles.f     = ones(1, length(m.nP));
saveModel_Callback(hObject, eventdata, handles);


%% dataSlider callback
function dataSlider_Callback(hObject, eventdata, handles, nD)

val = get(hObject, 'Value');
handles.fData(handles.dOffset + nD) = 10^val;
set(handles.dataText(nD), 'String', num2str(val));

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);

%% dataText callback
function dataText_Callback(hObject, eventdata, handles, nD)

val = str2num(get(hObject, 'String'));
if isempty(val)
    set(handles.dataText(nD), 'String', num2str(get(handles.dataSlider(nD), 'Value')));
    return
end
set(handles.dataSlider(nD), 'Value', val);
handles.fData(handles.dOffset + nD) = 10^val;

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
guidata(hObject, handles);

%% dataSlider0 callback
% this function is used to scroll between data entries
function dataSlider0_Callback(hObject, eventdata, handles)

offset = round(get(hObject, 'Value'));
set(hObject, 'Value', offset);
handles.dOffset = get(hObject, 'Max') - offset;

m = handles.model;

if isfield(handles.data, 'dNames')   % if you know the parameter names
    for i = 1:min(2, handles.data.nD)
        set(handles.dtext(i), 'String', handles.data.dNames{i + handles.dOffset});
    end
else
    for i = 1:min(2, handles.data.nD)        % otherwise
        set(handles.dtext(i), 'String', num2str(i + handles.dOffset));
    end
end
for i = 1:min(2, handles.data.nD)
    val = log10(handles.fData(i + handles.dOffset));
    set(handles.dataText(i), 'String',  num2str(val));
    set(handles.dataSlider(i), 'Value', val);
end

guidata(hObject, handles);

%% tSlider callback
function tSlider_Callback(hObject, eventdata, handles)

val = get(hObject, 'Value');
handles.fT = 10^val;
set(handles.tText, 'String', num2str(val));

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
handles.t = t;
handles.y = y;
handles.model = previousModel;
guidata(hObject, handles);


%% tText callback
function tText_Callback(hObject, eventdata, handles)

val = str2num(get(hObject, 'String'));
if isempty(val)
    set(handles.tText, 'String', num2str(get(handles.tSlider, 'Value')));
    return
end
set(handles.tSlider, 'Value', val);
handles.fT = 10^val;

previousModel = handles.model;
handles.model = handles.model.update(handles.model.p(:).*handles.f(:), handles.model.ic);
[t,y] = simulateModel(hObject, eventdata, handles);
guidata(hObject, handles);


%% Create functions
function slider_CreateFcn(hObject, eventdata, handles, sliderNum)
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end
function loadModel_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function loadSim_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function paramSlider_CreateFcn(hObject, eventdata, handles)
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end
function parameterRange_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function simsList1_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function simsList2_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function loadFit_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function loadData_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function dataSlider_CreateFcn(hObject, eventdata, handles,nD)
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end
function dataText_CreateFcn(hObject, eventdata, handles,nD)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function dataSlider0_CreateFcn(hObject, eventdata, handles)
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end
function dataRange_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
function tSlider_CreateFcn(hObject, eventdata, handles)
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end
function tText_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

%% Additional Functions
%% simulateModel: --- Simulates the model with the current parameters.
function [t,y] = simulateModel(hObject, eventdata, handles)
% m = handles.model;
% keyboard
t = cell(1,2);
y = cell(1,2);
for i = 1:2
    if ~handles.expts(i), continue, end
    [t{i}, y{i}] = simulate(handles.model, handles.experiments(handles.expts(i)));
end

dataInd = [];
kInd    = [];
for k = 1:length(handles.data)
    if ~isempty(handles.data(k).expt)
        keyboard
        dataInd = [dataInd, find(handles.expts == handles.data(k).expt)];
        kInd    = [kInd k];
    end
end

% color plotting order
colors = 'bgrcmyk';

toPlot = zeros(size(handles.data));
for k = 1:length(handles.data)
    if handles.data.nD
        tmp = find(handles.expts == handles.data(k).expt);
        if ~isempty(tmp)
            toPlot(k) = tmp;
        end
    end
end

% plotting loop
for aI = 1:2
    if ~handles.expts(aI), continue, end
    axes(handles.axes(aI));
    cla(handles.axes(aI));
    hold on
    plot(handles.axes(aI), t{aI}, y{aI}(t{aI}), 'LineWidth', 2);
    if any(dataInd == aI)
        for i = 1:handles.data(1).nD
            plot(handles.axes(aI), ...
                handles.fT*handles.data(kInd(aI)).t0, ...
                handles.fData(i)*handles.data(kInd(aI)).y0(i,:), ...
                colors(handles.data(kInd(aI)).species(i)), 'LineWidth', 2);
        end
    end
    legend(handles.axes(aI), handles.model.yNames, 'Orientation', 'vertical');
end