Skip to content

Commit f9d0ce7

Browse files
authored
Merge pull request #164 from ImperialCollegeLondon/fix/various
fix: Various issues
2 parents a6f4d2b + 23bb619 commit f9d0ce7

File tree

27 files changed

+283
-33
lines changed

27 files changed

+283
-33
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ See [internal documentation](https://imperialcollege.atlassian.net/wiki/spaces/P
3535

3636
## Development
3737

38+
To get started, clone this repository and install the package for development:
39+
40+
``` matlab
41+
mpminstall(pwd(), Authoring = true);
42+
```
43+
3844
When developing new features or fixing issues, create a new branch. After finishing development, make sure to write tests to cover any new changes.
3945

4046
To change the version of the toolbox, modify the package definition file in `resources/mpackage.json`. This will automatically update the toolbox version and create a new toolbox with the correct version.

app/DataVisualization.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ function selectMission(app, mission)
126126
mission = app.SelectMissionDialog.waitForSelection();
127127

128128
if app.SelectMissionDialog.Aborted
129-
error("mag:app:abort", "User aborted.");
129+
error("mag:app:Abort", "User aborted.");
130130
else
131131
app.SelectMissionDialog.delete();
132132
end
@@ -144,7 +144,7 @@ function selectMission(app, mission)
144144
case mag.meta.Mission.IMAP
145145
app.Provider = mag.app.imap.Provider();
146146
otherwise
147-
error("mag:app:unsupportedMission", "%s mission not supported.", app.Mission.DisplayName);
147+
error("mag:app:UnsupportedMission", "%s mission not supported.", app.Mission.DisplayName);
148148
end
149149

150150
% Set managers.

app/core/+mag/+app/+control/SignalAnalyzer.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ function instantiate(this, parent)
7070
data = results.(selectedInput);
7171

7272
if ~data.isPlottable()
73-
error("mag:app:emptySignal", "Not enough data for plotting ""%s"".", selectedInput);
73+
error("mag:app:EmptySignal", "Not enough data for plotting ""%s"".", selectedInput);
7474
end
7575

7676
selectedData = timetable(data.Time - data.Time(1), data.(selectedSignal), VariableNames = selectedInput + "_" + selectedSignal);

app/core/+mag/+app/+control/WaveletAnalyzer.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function instantiate(this, parent)
8585
data = results.(selectedInput);
8686

8787
if ~data.isPlottable()
88-
error("mag:app:emptySignal", "Not enough data for plotting ""%s"".", selectedInput);
88+
error("mag:app:EmptySignal", "Not enough data for plotting ""%s"".", selectedInput);
8989
end
9090

9191
propertyName = selectedInput + "_" + selectedSignal;

app/core/+mag/+app/+internal/removeNonFiniteData.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
if any(locNonFinite, "all")
1515

16-
warning("mag:app:nonFiniteData", "Removing non-finite data.");
16+
warning("mag:app:NonFiniteData", "Removing non-finite data.");
1717
data(any(locNonFinite, 2), :) = [];
1818
end
1919
end

app/core/+mag/+app/+internal/resampleIrregularData.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
dt = diff(data.Properties.RowTimes);
1515
frequencies = 1 ./ seconds(dt);
1616

17-
warning("mag:app:nonFiniteData", "Resampling data as not uniformely sampled (%.3f ± %.3g Hz).", mode(frequencies), std(frequencies, 0, "omitmissing"));
17+
warning("mag:app:NonFiniteData", "Resampling data as not uniformly sampled (%.3f ± %.3g Hz).", mode(frequencies), std(frequencies, 0, "omitmissing"));
1818

1919
data = resample(data);
2020
end

app/core/+mag/+app/+manage/ToolbarManager.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ function setLatestErrorMessage(this, exception)
9191

9292
function unlock(this)
9393

94+
if ~isvalid(this) || ~isvalid(this.Toolbar)
95+
return;
96+
end
97+
9498
[this.MissionPushTool.Enable, this.ImportPushTool.Enable, ...
9599
this.DebugToggleTool.Enable, this.HelpPushTool.Enable] = deal(true);
96100
end

app/core/+mag/+app/+manage/VisualizationManager.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function reset(this)
6060
% VISUALIZE Visualize analysis using selected view.
6161

6262
if isempty(this.SelectedControl)
63-
error("mag:app:noViewSelected", "No view selected.");
63+
error("mag:app:NoViewSelected", "No view selected.");
6464
end
6565

6666
command = this.SelectedControl.getVisualizeCommand(analysis.Results);

app/mission/bart/+mag/+app/+bart/AnalysisManager.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ function reset(this)
9191
location = this.LocationEditField.Value;
9292

9393
if isempty(location)
94-
error("mag:app:emptyLocation", "Location is empty.");
94+
error("mag:app:EmptyLocation", "Location is empty.");
9595
elseif ~isfolder(location)
96-
error("mag:app:nonexistentLocation", "Location ""%s"" does not exist.", location);
96+
error("mag:app:NonexistentLocation", "Location ""%s"" does not exist.", location);
9797
end
9898

9999
options = {"Location", this.LocationEditField.Value, ...

app/mission/hs/+mag/+app/+hs/AnalysisManager.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ function reset(this)
9999
location = this.LocationEditField.Value;
100100

101101
if isempty(location)
102-
error("mag:app:emptyLocation", "Location is empty.");
102+
error("mag:app:EmptyLocation", "Location is empty.");
103103
elseif ~isfolder(location)
104-
error("mag:app:nonexistentLocation", "Location ""%s"" does not exist.", location);
104+
error("mag:app:NonexistentLocation", "Location ""%s"" does not exist.", location);
105105
end
106106

107107
% Retrieve data file patterns.

app/mission/imap/+mag/+app/+imap/+control/CPT.m

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ function validatePattern(~, changingData)
9999
pattern = asManyOfPattern(digitsPattern() + optionalPattern(characterListPattern(",") + whitespacePattern()));
100100

101101
if ~matches(value, pattern)
102-
error("mag:app:invalidPattern", "Value must match the pattern ""1, 2, 3"".");
102+
error("mag:app:InvalidPattern", "Value must match the pattern ""1, 2, 3"".");
103103
end
104104
end
105105
end
@@ -115,5 +115,3 @@ function validatePattern(~, changingData)
115115
end
116116
end
117117
end
118-
119-

app/mission/imap/+mag/+app/+imap/AnalysisManager.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ function reset(this)
128128
location = this.LocationEditField.Value;
129129

130130
if isempty(location)
131-
error("mag:app:emptyLocation", "Location is empty.");
131+
error("mag:app:EmptyLocation", "Location is empty.");
132132
elseif ~isfolder(location)
133-
error("mag:app:nonexistentLocation", "Location ""%s"" does not exist.", location);
133+
error("mag:app:NonexistentLocation", "Location ""%s"" does not exist.", location);
134134
end
135135

136136
% Retrieve data file patterns.

app/mission/imap/+mag/+app/+imap/VisualizationManager.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
function figures = visualize(this, analysis)
2525

2626
if isempty(this.SelectedControl)
27-
error("mag:app:noViewSelected", "No view selected.");
27+
error("mag:app:NoViewSelected", "No view selected.");
2828
end
2929

3030
if isa(this.SelectedControl, "mag.app.imap.control.AT") || isa(this.SelectedControl, "mag.app.imap.control.CPT")

buildfile.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
if isMATLABReleaseOlderThan("R2025a")
77

8-
warning("mag:buildtool:path", "MATLAB release ""%s"" not supported for development purposes. " + ...
8+
warning("mag:buildtool:Path", "MATLAB release ""%s"" not supported for development purposes. " + ...
99
"Adding all folders to the path for testing purposes.", matlabRelease().Release);
1010

1111
% Brute-force add all folders to the path.

resources/release-notes.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
## App
22

33
- (All) Add shortcut to open app from toolbar (quick access)
4+
- (All) Fix errors when closing app during mission selection
5+
6+
## Software
7+
8+
- (All) Add `mag.mixin.TimeSeriesOperationSupport` for simplified operations support
9+
- (All) Fix issues with colors and legends for multiple stackedplots
10+
- (IMAP) Check there is data before enabling auto-compression event in `mag.imap.view.Field`
411

512
## Package
613

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
classdef (Abstract, HandleCompatible) TimeSeriesOperationSupport
2+
% TIMESERIESOPERATIONSUPPORT Interface adding support for operations on
3+
% "mag.TimeSeries" subclasses.
4+
5+
% Assume that this class has access to methods and properties of
6+
% "mag.TimeSeries":
7+
%#ok<*MCNPN>
8+
9+
methods (Sealed)
10+
11+
function result = join(this, those)
12+
13+
arguments
14+
this {mustBeNonempty}
15+
end
16+
17+
arguments (Repeating)
18+
those
19+
end
20+
21+
% If no "those" is provided and "this" is an array, combine all
22+
% "these" data.
23+
if isempty(those) && ~isscalar(this)
24+
25+
result = this(1).copy();
26+
result.Data = sortrows(vertcat(this.Data));
27+
28+
return;
29+
end
30+
31+
expectedClass = class(this);
32+
33+
if isscalar(this) && all(cellfun(@(x) isa(x, expectedClass), those))
34+
35+
thoseData = cellfun(@(x) x.Data, those, UniformOutput = false);
36+
joinedData = sortrows(vertcat(this.Data, thoseData{:}));
37+
38+
result = this.copy();
39+
result.Data = joinedData;
40+
else
41+
error("mag:join:UnsupportedType", """join"" is only supported between mag.TimeSeries objects.");
42+
end
43+
end
44+
end
45+
46+
methods (Hidden, Sealed)
47+
48+
function result = plus(this, that)
49+
50+
arguments
51+
this (1, 1)
52+
that
53+
end
54+
55+
operation = @(this, that) sortrows(this.Data + that.Data);
56+
result = this.performOperationOrFallbackToBuiltin(that, operation, "plus");
57+
end
58+
59+
function result = minus(this, that)
60+
61+
arguments
62+
this (1, 1)
63+
that
64+
end
65+
66+
operation = @(this, that) sortrows(this.Data - that.Data);
67+
result = this.performOperationOrFallbackToBuiltin(that, operation, "minus");
68+
end
69+
end
70+
71+
methods (Access = private)
72+
73+
function result = performOperationOrFallbackToBuiltin(this, that, operation, functionName, varargin)
74+
75+
result = [];
76+
77+
if isscalar(this) && all(isa(that, class(this)))
78+
79+
result = this.copy();
80+
result.Data = operation(this, that);
81+
82+
return;
83+
end
84+
85+
try
86+
result = builtin(functionName, this, that, varargin{:});
87+
catch exception
88+
exception.throwAsCaller();
89+
end
90+
end
91+
end
92+
end

src/mission/imap/+mag/+imap/+view/Field.m

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ function visualize(this)
9393

9494
selectedEvents = this.Events;
9595

96-
if isempty(selectedEvents) && (any(diff(primary.Compression) ~= 0) || any(diff(secondary.Compression) ~= 0))
96+
if isempty(selectedEvents) && ...
97+
((primary.HasData && any(diff(primary.Compression) ~= 0)) || ...
98+
(secondary.HasData && any(diff(secondary.Compression) ~= 0)))
99+
97100
selectedEvents = "Compression";
98101
end
99102

src/utility/+mag/+time/decodeDate.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
end
3333
end
3434

35-
error("mag:time:parseError", "Unable to parse date '%s' using the formats %s.", date, join(compose("'%s'", formats), ", "));
35+
error("mag:time:ParseError", "Unable to parse date '%s' using the formats %s.", date, join(compose("'%s'", formats), ", "));
3636
end

src/visualize/+mag/+graphics/+chart/Stackedplot.m

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@
3838

3939
Ny = width(yData);
4040

41-
if isempty(this.Colors) || (Ny > size(this.Colors, 1))
41+
if height(this.Colors) == 1
42+
colors = repmat(this.Colors, Ny, 1);
43+
elseif isempty(this.Colors) || (Ny > height(this.Colors))
4244
error("Mismatch in number of colors for number of plots.");
45+
else
46+
colors = this.Colors;
4347
end
4448

4549
% Check if layout already has a stack layout.
@@ -61,7 +65,7 @@
6165
hold(ax, "on");
6266
resetAxesHold = onCleanup(@() hold(ax, "off"));
6367

64-
graph(y) = plot(ax, xData, yData(:, y), this.MarkerStyle{:}, this.LineCustomization{:}, Color = this.Colors(y, :));
68+
graph(y) = plot(ax, xData, yData(:, y), this.MarkerStyle{:}, this.LineCustomization{:}, Color = colors(y, :));
6569

6670
if this.EventsVisible && ~isempty(data.Properties.Events)
6771
this.addEventsData(ax, data);

src/visualize/+mag/+graphics/+factory/DefaultFactory.m

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030

3131
% In MATLAB R2025a and above, force figures in the figures
3232
% container.
33-
if matlabRelease().Release >= "R2025a"
34-
windowStyle = "docked";
35-
else
33+
if isMATLABReleaseOlderThan("R2025a")
3634
windowStyle = "normal";
35+
else
36+
windowStyle = "docked";
3737
end
3838

3939
% Create and populate figure.

src/visualize/+mag/+graphics/+style/Stackedplot.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@
4949
xlabel(axes, this.XLabel);
5050
xlim(axes, this.XLimits);
5151

52-
l = matlab.graphics.primitive.Text.empty(0, numel(axes));
52+
t = matlab.graphics.primitive.Text.empty(0, numel(axes));
5353

5454
if ~isempty(this.YLabels)
5555

5656
for i = 1:numel(this.YLabels)
57-
l(i) = ylabel(axes(i), this.YLabels(i));
57+
t(i) = ylabel(axes(i), this.YLabels(i));
5858
end
5959
end
6060

6161
if this.RotateLabels
62-
[l.Rotation] = deal(0);
62+
[t.Rotation] = deal(0);
6363
end
6464

6565
ylim(axes, this.YLimits);
@@ -72,7 +72,7 @@
7272
this.applyGridStyle(axes);
7373

7474
% Add legend.
75-
l = this.applyLegendStyle(axes);
75+
l = this.applyLegendStyle(axes(1));
7676

7777
if ~isempty(l)
7878
l.Layout.Tile = this.LegendLocation;

tests/tool/+mag/+test/runTests.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function testResults = runTests()
2+
% RUNTESTS Run all MAG tests with coverage.
3+
4+
root = mfilename("fullpath");
5+
root = fullfile(fileparts(root), "..", "..", "..");
6+
7+
testResults = runtests(root, IncludeSubfolders = true, ReportCoverageFor = ["app", "src"]);
8+
end

tests/unit/app/tSignalAnalyzer.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function getVisualizeCommand_nonFiniteData(testCase)
7575
expectedData = expectedData([1, 3:4, 6:end], :);
7676

7777
% Exercise.
78-
command = testCase.verifyWarning(@() signalAnalyzer.getVisualizeCommand(results), "mag:app:nonFiniteData", ...
78+
command = testCase.verifyWarning(@() signalAnalyzer.getVisualizeCommand(results), "mag:app:NonFiniteData", ...
7979
"Warning should be issued when removing non-finite data.");
8080

8181
% Verify.
@@ -98,7 +98,7 @@ function getVisualizeCommand_noData(testCase)
9898
results = mag.bart.Instrument();
9999

100100
% Exercise and verify.
101-
testCase.verifyError(@() signalAnalyzer.getVisualizeCommand(results), "mag:app:emptySignal", ...
101+
testCase.verifyError(@() signalAnalyzer.getVisualizeCommand(results), "mag:app:EmptySignal", ...
102102
"Error should be thrown when there is no data to plot.");
103103
end
104104
end

tests/unit/app/tVisualizationManager.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function visualize_noSelection(testCase, VisualzationClass)
3535
manager.instantiate(panel);
3636

3737
% Exercise and verify.
38-
testCase.verifyError(@() manager.visualize([]), "mag:app:noViewSelected", ...
38+
testCase.verifyError(@() manager.visualize([]), "mag:app:NoViewSelected", ...
3939
"Error should be thrown when no view is selected.");
4040
end
4141
end

tests/unit/app/tWaveletAnalyzer.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ function getVisualizeCommand_noData(testCase)
103103
results = mag.bart.Instrument();
104104

105105
% Exercise and verify.
106-
testCase.verifyError(@() waveletAnalyzer.getVisualizeCommand(results), "mag:app:emptySignal", ...
106+
testCase.verifyError(@() waveletAnalyzer.getVisualizeCommand(results), "mag:app:EmptySignal", ...
107107
"Error should be thrown when there is no data to plot.");
108108
end
109109
end

0 commit comments

Comments
 (0)