Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![MATLAB](https://github.com/simkaryote/MATLAB-SimBiology-DevOps-Workflow-Example/actions/workflows/ci.yml/badge.svg)](https://github.com/simkaryote/MATLAB-SimBiology-DevOps-Workflow-Example/actions/workflows/ci.yml)
[![Tests](https://img.shields.io/badge/Tests-Open_Test_Report-blue)](https://simkaryote.github.io/MATLAB-SimBiology-DevOps-Workflow-Example/tests/)
[![Coverage](https://img.shields.io/badge/Coverage-Open_Code_Coverage_Report-orange)](https://simkaryote.github.io/MATLAB-SimBiology-DevOps-Workflow-Example/coverage/)
[![MATLAB](https://github.com/ChezJe/MATLAB-SimBiology-DevOps-Workflow-Example/actions/workflows/ci.yml/badge.svg)](https://github.com/ChezJe/MATLAB-SimBiology-DevOps-Workflow-Example/actions/workflows/ci.yml)
[![Tests](https://img.shields.io/badge/Tests-Open_Test_Report-blue)](https://ChezJe.github.io/MATLAB-SimBiology-DevOps-Workflow-Example/tests/)
[![Coverage](https://img.shields.io/badge/Coverage-Open_Code_Coverage_Report-orange)](https://ChezJe.github.io/MATLAB-SimBiology-DevOps-Workflow-Example/coverage/)

# Generating Tests for Your MATLAB<sup>&reg;</sup> Code Workshop

Expand Down
62 changes: 54 additions & 8 deletions buildfile.m
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
function plan = buildfile

import matlab.buildtool.tasks.*

% Extract tasks from local functions
plan = buildplan(localfunctions);

% Set default task
plan.DefaultTasks = "test";


% CodeIssues task
plan("check") = CodeIssuesTask();
plan("check") = CodeIssuesTask(Results=["results/codeissues.sarif"; ...
"results/codeissues.mat"]);

% Test task
tTask = TestTask("tests", ...
SourceFiles = "code", ...
IncludeSubfolders = true,...
TestResults = fullfile("results","tests","index.html"), ...
Dependencies = "check");
TestResults = fullfile("results","tests","index.html"),...
RunOnlyImpactedTests=true);

tTaskWithMatlabTest = tTask.addCodeCoverage( ...
fullfile("results","coverage","index.html"), ...
MetricLevel = "condition"); % Note: Change MetricLevel to "statement"
Expand All @@ -26,4 +24,52 @@
% Clean task
plan("clean") = CleanTask();

% Define dependencies
plan("compile").Dependencies = "test";
plan("test").Dependencies = "generateSimFun";
plan("generateSimFun").Dependencies = "check";

% Define inputs and outputs
proj = currentProject;
plan("generateSimFun").Inputs = fullfile(proj.RootFolder,"code","*.sbproj");
plan("generateSimFun").Outputs = fullfile(proj.RootFolder,"code","*.mat");
plan("test").Inputs = fullfile(proj.RootFolder,"code","*");
plan("compile").Inputs = fullfile(proj.RootFolder,"code",["*.mat","*.mlapp","graystyle.m"]);
plan("compile").Outputs = fullfile(proj.RootFolder,"WebAppArchive");

% Set default task
plan.DefaultTasks = "compile";


end

function generateSimFunTask(~)
% Generate SimFunction and associated MAT file for app to run
generateSimFun();
end

function compileTask(~)
% Compile App into Web App

proj = currentProject;
rootFolder = proj.RootFolder; % Get the root folder of the project

imgFiles = dir(fullfile(rootFolder,"code","images","*.*"));
imgFiles = string({imgFiles.name}');
imgFiles = fullfile(rootFolder,"code","images",imgFiles(~matches(imgFiles,[".",".."])));
codeFiles = dir(fullfile(rootFolder,"code","*.m"));
codeFiles = string({codeFiles.name}');
codeFiles = fullfile(rootFolder,"code",setdiff(codeFiles,"generateSimFun.m"));

MATfilename = dir(fullfile(rootFolder,"code","*.mat"));
MATfilename = fullfile(rootFolder,"code",MATfilename.name);
load(MATfilename,"dependenciesSimFun");

appDependencies = [MATfilename; dependenciesSimFun; ...
codeFiles; imgFiles];
appfilename = fullfile(rootFolder,"code","TMDDApp.mlapp");

compiler.build.webAppArchive(appfilename,...
AdditionalFiles=appDependencies,OutputDir="WebAppArchive");

end
75 changes: 75 additions & 0 deletions code/ConcTimecourseView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
classdef ConcTimecourseView < handle

properties ( Access = private )
Model
Axes

ConcColors = [0.30,0.75,0.93;...
0.86,0.55,0.41;...
0.91,0.73,0.42]; % colors to plot concentrations
FontName = "Helvetica";
end

properties ( SetAccess=private, GetAccess={?tTMDDApp} )
% line handles
lhDrug
lhReceptor
lhComplex

end

properties( Access = private )
DataListener % listener
end

methods
function obj = ConcTimecourseView(parent, model)

arguments
parent
model (1,1) SimulationModel
end

ax = uiaxes(parent);
graystyle(ax);
xlabel(ax, "Time (hours)", 'FontName',obj.FontName);
ylabel(ax, "Concentrations (nanomole/liter)",'FontName',obj.FontName);

obj.lhDrug = plot(ax, NaN, NaN, '-','Linewidth',2,'Color',obj.ConcColors(1,:));
hold(ax,'on');
obj.lhReceptor = plot(ax, NaN, NaN, '-','Linewidth',2,'Color',obj.ConcColors(2,:));
obj.lhComplex= plot(ax, NaN, NaN, '-','Linewidth',2,'Color',obj.ConcColors(3,:));
hold(ax,'off');
lh = legend(ax,{'Drug','Receptor','Complex'},'FontName',obj.FontName);
lh.Box = 'off';

% instantiate listener
dataListener = event.listener( model, 'DataChanged', ...
@obj.update );

% store listeners
obj.DataListener = dataListener;

% save objects
obj.Model = model;
obj.Axes = ax;

end % constructor


end % public methods

methods ( Access = private )

function update(obj,~,~)
t = obj.Model.SimDataTable;

set(obj.lhDrug,'XData',t.Time, 'YData',t.Drug);
set(obj.lhReceptor,'XData',t.Time, 'YData',t.Receptor);
set(obj.lhComplex,'XData',t.Time, 'YData',t.Complex);

end % update

end % private method
end % class

70 changes: 70 additions & 0 deletions code/LampView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
classdef LampView < handle

properties ( Dependent )
IsOn (1,1) logical
end

properties
LampColorSucess = [0.47, 0.67, 0.19] % color of lamp if RO between thresholds after day 1
LampColorFailure = [0.85, 0.33, 0.10] % color of lamp if RO not between thresholds after day 1
end

properties ( SetAccess=private, GetAccess={?tTMDDApp} )
LampObj
end

properties ( Access=private )
Model
end

properties( Access = private )
DataListener % listener
end

methods
function obj = LampView(parent,model)

lampObj = uilamp(parent);
lampObj.Tooltip = compose("Target occupancy does not remain\n between thresholds");
lampObj.Color = obj.LampColorFailure;

% instantiate listener
dataListener = event.listener( model, 'DataChanged', ...
@obj.update );

% store listeners
obj.DataListener = dataListener;

obj.LampObj = lampObj;
obj.Model = model;

end % constructor

function set.IsOn(obj, value)
% color lamp according to whether or not RO remains between thresholds after day 1
if value
obj.LampObj.Color = obj.LampColorSucess;
obj.LampObj.Tooltip = compose("Target occupancy remains\n between thresholds");
else
obj.LampObj.Color = obj.LampColorFailure;
obj.LampObj.Tooltip = compose("Target occupancy does not remain\n between thresholds");
end

obj.LampObj.UserData = value;

end % set.IsOn

function value = get.IsOn(obj)
value = obj.LampObj.UserData;
end % get.IsOn

end % public methods

methods ( Access = private )

function update(obj,~,~)
obj.IsOn = obj.Model.ROIsBetweenThresholds;
end % update

end % private method
end
87 changes: 87 additions & 0 deletions code/NCAView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
classdef NCAView < handle

properties ( Access = private )
Model
NCApanel
GridLayout

end

properties ( SetAccess=private, GetAccess={?tTMDDApp} )
NCAtable
end

properties ( Access = public )
FontName (1,1) string = "Helvetica"
NCAoptions % options for NCA calculations
ConcentrationColumnName (1,1) string = "Complex"
end

properties( Access = private )
DataListener % listener
end

methods
function obj = NCAView(parent, model)

arguments
parent
model (1,1) SimulationModel
end

ncapanel = uipanel(parent);
ncapanel.Title = "NCA parameters for bound target ('" + ...
obj.ConcentrationColumnName + "')";
ncapanel.BackgroundColor = [1 1 1];
ncapanel.FontName = obj.FontName;
ncapanel.BorderType = 'none';

% Create GridLayout
gl = uigridlayout(ncapanel);
gl.ColumnWidth = {'1x'};
gl.RowHeight = {'1x'};
gl.Padding = [0 0 0 0];
gl.BackgroundColor = [1 1 1];

% Create NCAtable
ncat = uitable(gl);

% save NCA options
opt = sbioncaoptions;
opt.concentrationColumnName = obj.ConcentrationColumnName;
opt.timeColumnName = 'Time';
opt.IVDoseColumnName = 'Dose';

% instantiate listener
dataListener = event.listener( model, 'DataChanged', ...
@obj.update );

% store listeners
obj.DataListener = dataListener;

% save objects
obj.Model = model;
obj.NCAoptions = opt;
obj.NCApanel = ncapanel;
obj.GridLayout = gl;
obj.NCAtable = ncat;

end % constructor


end % public methods

methods ( Access = private )

function update(obj,~,~)

% compute NCA parameters and display them in table
ncaParameters = sbionca(obj.Model.SimDataTable, obj.NCAoptions);
obj.NCAtable.ColumnName = ncaParameters.Properties.VariableNames(2:end);
obj.NCAtable.Data = ncaParameters(:,2:end);

end % update

end % private method
end % class

Loading
Loading