Skip to content

Commit 7e5d94e

Browse files
committed
Add: include new display classes Canvas, PlotComposition, and PlotFigure
1 parent 671a331 commit 7e5d94e

3 files changed

Lines changed: 959 additions & 0 deletions

File tree

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
classdef (Abstract) Canvas < handle
2+
% The :mat:class:`Canvas` class is an abstract base class used to manage
3+
% figure creation and plotting layout configuration in the :mat:pkg:`+display` module.
4+
%
5+
% The :mat:class:`Canvas` class standardizes figure and axis formatting,
6+
% provides tiled layout management, and offers utilities for axis selection and cycling.
7+
%
8+
% A :mat:class:`Canvas` subclass can be initialized as follows: ::
9+
%
10+
% config = combustiontoolbox.utils.display.PlotConfig();
11+
% fig = combustiontoolbox.utils.display.PlotFigure(config);
12+
%
13+
% See also: :mat:class:`PlotConfig`, :mat:class:`PlotFigure`, :mat:class:`PlotComposition`
14+
15+
properties
16+
ax matlab.graphics.axis.Axes % Axis handle
17+
tiled matlab.graphics.layout.TiledChartLayout % Tiled layout for multiple axes
18+
fig matlab.ui.Figure % Figure handle
19+
config (1, 1) combustiontoolbox.utils.display.PlotConfig % Plot configuration object
20+
end
21+
22+
properties (Access = private)
23+
currentIndex = 1 % Current index for sequential filling of axes
24+
end
25+
26+
properties (Dependent)
27+
size % Size of the axis array
28+
numAxis % Number of axes in the figure
29+
end
30+
31+
methods
32+
33+
function value = get.size(obj)
34+
% Get the size axis array
35+
value = size(obj.ax);
36+
end
37+
38+
function value = get.numAxis(obj)
39+
% Get the number of axes in the figure
40+
value = numel(obj.ax);
41+
end
42+
43+
end
44+
45+
methods (Access = public)
46+
47+
function obj = Canvas(config)
48+
% Constructor
49+
if nargin > 0
50+
obj.config = config;
51+
return
52+
end
53+
54+
% Default configuration
55+
obj.config = combustiontoolbox.utils.display.PlotConfig();
56+
end
57+
58+
function obj = add(obj)
59+
% Add a new axis to the figure
60+
% This method is intended to be overridden in subclasses
61+
% to add specific axes or plots.
62+
63+
% Definitions
64+
config = obj.config;
65+
66+
% Check if fig is already created
67+
if isempty(obj.fig)
68+
obj.fig = figure;
69+
set(obj.fig, 'units', 'normalized', ...
70+
'innerposition', config.innerpositionLayout, ...
71+
'outerposition', config.outerpositionLayout)
72+
end
73+
74+
% Check if tiled layout is already created
75+
if isempty(obj.tiled)
76+
obj.tiled = tiledlayout(obj.fig, 'flow', ...
77+
'Padding', obj.config.padding, ...
78+
'TileSpacing', obj.config.tilespacing);
79+
end
80+
81+
% Create a new axis in the tiled layout
82+
ax = nexttile(obj.tiled);
83+
84+
% Set the axes properties
85+
setFormat(obj, ax);
86+
87+
% Append new axis
88+
obj.ax(end + 1) = ax;
89+
90+
% Add a listener so when the user closes the figure, we reset
91+
addlistener(obj.fig, 'ObjectBeingDestroyed', @(~,~) obj.onFigureClose());
92+
end
93+
94+
function [ax, config, fig] = new(obj)
95+
% Create and configure a new figure
96+
97+
% Definitions
98+
config = obj.config;
99+
100+
% Create figure
101+
fig = figure;
102+
set(fig, 'units', 'normalized', ...
103+
'innerposition', config.innerposition, ...
104+
'outerposition', config.outerposition)
105+
106+
% Create axes
107+
ax = axes(fig);
108+
109+
% Set axes properties
110+
setFormat(obj, ax);
111+
112+
% Store the axes and figure in the object
113+
obj.ax = ax;
114+
obj.fig = fig;
115+
116+
% Add a listener so when the user closes the figure, we reset
117+
addlistener(fig, 'ObjectBeingDestroyed', @(~,~) obj.onFigureClose());
118+
end
119+
120+
function [axArray, config, fig] = newArray(obj, varargin)
121+
% Create a figure with an nrows x ncols tiled layout
122+
% and return an array of PlotFigure subclass instances
123+
124+
% Definitions
125+
config = obj.config;
126+
127+
% Initialization
128+
nrows = [];
129+
ncols = [];
130+
131+
% Parse input arguments
132+
if nargin > 1
133+
nrows = varargin{1};
134+
ncols = varargin{2};
135+
end
136+
137+
% Create parent figure and tiled layout
138+
fig = figure;
139+
set(fig, 'units', 'normalized', ...
140+
'innerposition', config.innerpositionLayout, ...
141+
'outerposition', config.outerpositionLayout)
142+
143+
% Check if nrows and ncols are provided
144+
if isempty(nrows) || isempty(ncols)
145+
nrows = 1; ncols = 1;
146+
tiled = tiledlayout(fig, 'flow', ...
147+
'Padding', config.padding, ...
148+
'TileSpacing', config.tilespacing);
149+
else
150+
tiled = tiledlayout(fig, nrows, ncols, ...
151+
'Padding', config.padding, ...
152+
'TileSpacing', config.tilespacing);
153+
end
154+
155+
% Initialize array of objects
156+
axArray = gobjects(ncols, nrows); % Preallocate array of axes
157+
158+
% Loop through rows and columns to create axes
159+
for i = 1:ncols
160+
161+
for j = 1:nrows
162+
% Create new axes in the tiled layout
163+
ax = nexttile(tiled);
164+
165+
% Set the axes properties
166+
setFormat(obj, ax);
167+
168+
% Save the axis in the array
169+
axArray(i, j) = ax;
170+
end
171+
172+
end
173+
174+
% Store the axes and figure in the object
175+
obj.ax = axArray;
176+
obj.tiled = tiled;
177+
obj.fig = fig;
178+
179+
% Add a listener so when the user closes the figure, we reset
180+
addlistener(fig, 'ObjectBeingDestroyed', @(~,~) obj.onFigureClose());
181+
end
182+
183+
function next(obj)
184+
% Cycle to the next index
185+
obj.currentIndex = mod(obj.currentIndex, obj.numAxis) + 1;
186+
end
187+
188+
function ax = getAxis(obj, varargin)
189+
% Return axis to use for plotting
190+
%
191+
% Args:
192+
% obj (PlotFigure): Instance of the PlotFigure superclass
193+
%
194+
% Optional Args:
195+
% index (vector): Index of the axis to use [row, col]
196+
% FLAG_SEQUENTIAL (bool): If true, use sequential filling of axes
197+
%
198+
% Returns:
199+
% ax (Axes): Axis to use for plotting
200+
%
201+
% Examples:
202+
% * ax = obj.getAxis();
203+
% * ax = obj.getAxis('index', [1, 2]);
204+
% * ax = obj.getAxis('FLAG_SEQUENTIAL', true);
205+
% * ax = obj.getAxis('index', [1, 2], 'FLAG_SEQUENTIAL', true); % Start sequence at (1, 2)
206+
207+
% Check if obj.ax exists and is valid
208+
if isempty(obj.ax)
209+
ax = new(obj);
210+
return
211+
end
212+
213+
% If varargin is empty, return current axis
214+
if nargin < 1
215+
ax = obj.ax(1, 1);
216+
return
217+
end
218+
219+
% Parse input
220+
p = inputParser;
221+
addParameter(p, 'index', [], @(x) isvector(x) && length(x) == 2);
222+
addParameter(p, 'FLAG_SEQUENTIAL', false, @(x) islogical(x) && isscalar(x));
223+
parse(p, varargin{:});
224+
225+
% Set properties
226+
index = p.Results.index;
227+
FLAG_SEQUENTIAL = p.Results.FLAG_SEQUENTIAL;
228+
229+
% Get axis based on index
230+
if ~FLAG_SEQUENTIAL
231+
if isempty(index)
232+
index1 = ceil(obj.currentIndex / obj.size(2));
233+
index2 = mod(obj.currentIndex - 1, obj.size(2)) + 1;
234+
end
235+
236+
ax = obj.ax(index1, index2);
237+
return
238+
end
239+
240+
% Use sequential filling if multiple axes exist
241+
if ~isempty(index)
242+
obj.currentIndex = sub2ind(size(obj.ax), index(1), index(2));
243+
end
244+
245+
% Get axis index
246+
k = obj.currentIndex;
247+
248+
% Get axis
249+
ax = obj.ax(k);
250+
251+
% Increment the current index for sequential filling
252+
next(obj)
253+
end
254+
255+
function ax = setFormat(obj, ax)
256+
% Set the format of the axes based on the configuration
257+
%
258+
% Args:
259+
% obj (PlotFigure): Instance of the PlotFigure superclass
260+
% ax (Axes): Axes to format
261+
%
262+
% Returns:
263+
% ax (Axes): Formatted axes
264+
%
265+
% Example:
266+
% ax = obj.setFormat(gca);
267+
268+
% Definitions
269+
config = obj.config;
270+
271+
% Set axes properties
272+
set(ax, 'LineWidth', config.linewidth, 'FontSize', config.fontsize - 2);
273+
set(ax, 'BoxStyle', 'full', 'TickLabelInterpreter', 'latex');
274+
set(ax, 'Layer', 'Top');
275+
set(ax, 'XScale', config.xscale, 'YScale', config.yscale);
276+
set(ax, 'XDir', config.xdir, 'YDir', config.ydir);
277+
xlim(ax, config.axis_x);
278+
ylim(ax, config.axis_y);
279+
box(ax, config.box);
280+
grid(ax, config.grid);
281+
hold(ax, config.hold);
282+
xlabel(ax, config.labelx, 'FontSize', config.fontsize, 'Interpreter', 'latex');
283+
ylabel(ax, config.labely, 'FontSize', config.fontsize, 'Interpreter', 'latex');
284+
end
285+
286+
function setTitle(obj, titleText)
287+
% Set figure title with LaTeX interpreter
288+
if nargin < 2 || isempty(titleText)
289+
titleText = obj.config.title;
290+
end
291+
292+
if isempty(titleText)
293+
return
294+
end
295+
296+
title(obj.ax, titleText, 'Interpreter', 'latex', 'FontSize', obj.config.fontsize + 4);
297+
end
298+
299+
function setLegend(obj, legendLabels)
300+
% Set legend on the axis
301+
legend(obj.ax, legendLabels, 'FontSize', obj.config.fontsize - 2, ...
302+
'Location', obj.config.legend_location, 'Interpreter', 'latex');
303+
end
304+
305+
end
306+
307+
methods (Abstract)
308+
plot(obj, varargin)
309+
end
310+
311+
methods (Access = private)
312+
313+
function onFigureClose(obj)
314+
% Reset the axes and figure when the figure is closed
315+
obj.ax = matlab.graphics.axis.Axes.empty(1,0);
316+
obj.tiled = matlab.graphics.layout.TiledChartLayout.empty(1,0);
317+
obj.fig = matlab.ui.Figure.empty(1,0);
318+
obj.currentIndex = 1;
319+
end
320+
321+
end
322+
323+
end

0 commit comments

Comments
 (0)