%% Creating a Flight Tracking Dashboard, Part 2: Developing an Aircraft Chart
% MATLAB Graphics and App Building Blog
% Guest Writer: Ken Deeley
% Joining us again is Ken Deeley, an Application Engineer based in Cambridge, UK. Discover more about Ken on our contributors bio page.
% Overview
% In the first blog post of this series, we saw how to visualize a model of the aircraft together with its current attitude. In this post, we'll see how to encapsulate the 3D graphics and helper functions used to visualize the aircraft in a custom chart.
% Throughout this post, we'll use the term chart to refer to a MATLAB class that provides a convenient visualization API for end users. Charts are also more suitable for modular application development, since they can be dynamically updated after creation, and reused between multiple applications. For more information about charts, see the technical article [1].
% By the end of this post, we'll have an aircraft chart equipped with the roll, pitch, and yaw functions for setting the attitude of the aircraft.
%
% Table of Contents
% Create a new chart class.
% Define the setup and update methods.
% Add data to the chart.
% Add graphics objects to the chart.
% Customize the chart's constructor.
% Update the chart's graphics.
% Equip the chart with rotation methods.
% Test the chart.
% Further charts.
% Summary
% References
%
%% Create a new chart class.
% Since R2019b, MATLAB provides a framework for developing custom charts. We'll start by creating a new chart class within this framework, named AircraftChart.
% classdef AircraftChart < matlab.graphics.chartcontainer.ChartContainer
%
% end
% In this example, the chart will only contain graphics objects. If you need your chart to contain control objects, such as buttons or check boxes, or if you need to share your chart as a reusable component in App Designer, you should use the ComponentContainer framework (introduced in R2020b). We’ll see some examples of components in part 3 of this blog series.
%
%% Define the setup and update methods.
% If we try to create an AircraftChart object from our class, we'll get the following error.
% >> AC = AircraftChart
% Abstract classes cannot be instantiated. Class 'AircraftChart' inherits abstract methods or properties but does not implement
% them. See the list of methods and properties that 'AircraftChart' must implement if you do not intend the class to be abstract.
% This is because charts derived from ChartContainer must implement the following two methods:
% setup - responsible for initializing the chart's graphics as part of the construction process.
% update - responsible for refreshing the chart's graphics when the chart's data or properties are changed by an end user.
% Let's add these methods to our AircraftChart. Note that we need to make these methods protected (i.e., accessible within the current class and any subclasses) for consistency with ChartContainer.
% classdef AircraftChart < matlab.graphics.chartcontainer.ChartContainer
%
%     methods ( Access = protected )
%
%         function setup( obj )
%
%         end
%
%         function update( obj )
%
%         end
%
%     end
%
% end
% We can now create a chart object:
% >> AC = AircraftChart
% This is not very useful though - we don't yet have any data or graphics.
%
%% Add data to the chart.
% As we saw in the first blog post, the data required for the visualization can be represented conveniently as a single triangulation object. Let's add this as a property in the AircraftChart class. As the default value, we'll use the triangulation obtained from the avion31 STL file we used previously.
% classdef AircraftChart < matlab.graphics.chartcontainer.ChartContainer
%
%     properties
%         Triangulation(:, 3) triangulation = stlread( "avion31.stl" )
%     end
%
%     methods ( Access = protected )
%
%         function setup( obj )
%
%         end
%
%         function update( obj )
%
%         end
%
%     end
%
% end
% The end user of the chart can easily change the aircraft STL file by setting the Triangulation property.
% >> AC = AircraftChart
% >> AC.Triangulation = stlread( "NewSTLFile.stl" );
% The update method is called automatically when the end user sets any public property of the chart. This is the mechanism we'll use to ensure the chart updates dynamically in response to changes made by the end user. Before we do this, we need to add graphics objects to the chart.
%
%% Add graphics objects to the chart.
% The ChartContainer framework provides a tiled layout in which to add the chart's graphics objects. We can access this tiled layout using the getLayout method. The tiled layout provides the flexibility to add multiple axes, or axes with different types such as geoaxes or polaraxes.
% function setup( obj )
%
% tl = obj.getLayout();
%
% end
%  In this example, we need to initialize the following graphics objects:
% An axes object, that we'll parent to the tiled layout.
% A transform object created using hgtransform, that we'll parent to the axes. This is used to enable easy rotation of the aircraft around the three coordinate axes.
% A patch object, that we'll parent to the transform object. Recall that we used the trisurf function to create the patch.
% First, we'll add the graphics objects as chart properties.
% classdef AircraftChart < matlab.graphics.chartcontainer.ChartContainer
%
%     properties ( Access = private )
%         Axes(:, 1) matlab.graphics.axis.Axes {mustBeScalarOrEmpty}
%         Transform(:, 1) matlab.graphics.primitive.Transform {mustBeScalarOrEmpty}
%         Patch(:, 1) matlab.graphics.primitive.Patch {mustBeScalarOrEmpty}
%     end
%
% end
% There's no reason for an end user to retrieve or modify these objects directly, so we declare the attribute Access = private for these properties.
% We also add property validation to ensure that these properties are used correctly within the class. Each of these properties should either be empty (when the class is first loaded) or a scalar value (when the setup method is called). Using the size validation (:, 1) combined with the validator mustBeScalarOrEmpty ensures that these properties either have size 0-by-1 or 1-by-1.
% The type validation (e.g., matlab.graphics.axis.Axes) ensures that code within the class assigns values of the correct data type, e.g., to avoid confusion between the different types of axes. Conveniently, adding this type validation also enables tab-completion on properties of the graphics objects when these properties are referenced in methods.
% Don't worry if these class names seem long - it's easy to discover the appropriate type to use by creating a graphics object at the command line and calling the class function.
% >> class(axes)
%
% ans =
%
%     'matlab.graphics.axis.Axes'
% Next, we'll create and assign the graphics objects in the chart's setup method. This is called automatically whenever a chart object is created. Here, we place the code we used in the first blog post for creating the axes, transform, and patch objects.
% function setup( obj )
%
% % Obtain the chart's tiled layout.
% tl = obj.getLayout();
%
% % Define the color scheme used for the aircraft.
% blue = [0, 0.447, 0.741];
% gray = 0.95 * ones(1, 3);
% onesMatrix = ones(250, 3);
% paintJob = [gray .* onesMatrix; blue .* onesMatrix];
%
% % Create and customize the axes.
% x = [-2000, 2000];
% y = [-2500, 4500];
% z = [-2500, 2500];
% obj.Axes = axes( "Parent", tl, ...
%     "Colormap", paintJob, ...
%     "View", [225, 25], ...
%     "DataAspectRatio", [1, 1, 1], ...
%     "XLim", x, ...
%     "YLim", y, ...
%     "ZLim", z, ...
%     "XTickLabel", [], ...
%     "YTickLabel", [], ...
%     "ZTickLabel", [], ...
%     "NextPlot", "add");
%
% % Add the transform and patch.
% obj.Transform = hgtransform( "Parent", obj.Axes );
% obj.Patch = trisurf( obj.Triangulation, ...
%     "Parent", obj.Transform, ...
%     "FaceAlpha", 0.9, ...
%     "FaceColor", "interp", ...
%     "EdgeAlpha", 0, ...
%     "FaceLighting", "gouraud" );
%
% % Add a title, lighting from above, and give the patch a shiny appearance by adjusting lighting properties.
% title( obj.Axes, "Aircraft Attitude" )
% light( obj.Axes, "Position", [0, 0, 1] )
% material( obj.Axes, "shiny" )
%
% end
% Note that we do not include creation of the figure within the chart class. This is because a chart does not own or maintain a figure. Rather, it's best to think of a chart as a graphics object that may be placed in a figure, or any other appropriate container, e.g., a tab within a tab group, a panel, another tiled layout, or a grid layout. This is an important concept when using the chart in a modular application.
%
%% Customize the chart's constructor.
% We'd like our chart to have a convenient, user-friendly API for end users to work with. To enable autocompletion on the input arguments accepted by the chart, we'll modify the default constructor of AircraftChart.
% Chart constructors should accept name-value pair arguments, so we maintain this behavior using an arguments block, validating the name-value pairs from properties of the AircraftChart class.
% We also set any name-value pairs on the chart that the end user has passed in to the constructor.
% function obj = AircraftChart( namedArgs )
%
% arguments ( Input )
%     namedArgs.?AircraftChart
% end
%
% % Set any user-defined properties.
% set( obj, namedArgs )
%
% end
%
%% Update the chart's graphics.
% The AircraftChart has one public property (Triangulation). When the end user sets this property, we expect the chart to update dynamically. This is the purpose of the update method. In our example, to avoid deleting and recreating the patch object, which could impair performance, we update the properties of the existing patch. This code uses the face-vertex representation of the patch object.
% function update( obj )
%
% faces = obj.Triangulation(:, :);
% points = obj.Triangulation.Points;
% set( obj.Patch, "Faces", faces, "Vertices", points, "FaceVertexCData", points(:, 3) )
%
% end
% In general, we can provide end users with a rich API comprising many properties for customizing the style and appearance of the chart. The update method handles changes made by the end user to these properties by refreshing the chart's private graphics properties.
%
%% Equip the chart with rotation methods.
% Next, we'll transfer the roll, pitch, and yaw functions we discussed in the first blog post to the AircraftChart. These will become public methods of the chart, available for end users to call.
% function roll( obj, theta )
%
% obj.rotate( "roll", theta )
%
% end % roll
%
% function pitch( obj, theta )
%
% obj.rotate( "pitch", theta )
%
% end % pitch
%
% function yaw( obj, theta )
%
% obj.rotate( "yaw", theta )
%
% end % yaw
% On the other hand, the rotate helper function will become a private chart method, since an end user should not call this method directly.
% function rotate( obj, type, theta )
%
% arguments
%     % Chart object.
%     obj(1, 1) AircraftChart
%     % Possible angles of rotation: roll, pitch and yaw.
%     type(1, 1) string ...
%         {mustBeMember( type, ["roll", "pitch", "yaw"] )}
%     % Angular value to rotate by.
%     theta(1, 1) double {mustBeReal, mustBeFinite} = 0
% end % arguments
%
% switch type
%     case "roll"
%         rotAxis = "y";
%     case "pitch"
%         rotAxis = "x";
%     case "yaw"
%         rotAxis = "z";
% end % switch/case
%
% % Convert the angle to radians and then update the
% % transformation matrix.
% theta = deg2rad( theta );
% obj.Transform.Matrix = obj.Transform.Matrix * ...
%     makehgtform( rotAxis + "rotate", theta );
%
% end % rotate
% It's good practice to include a method for restoring the original state of the chart. We'll do this by adding a reset method that sets the transformation matrix back to its original value of I_4, the 4-by-4 identify matrix.
% function reset( obj )
%
% obj.Transform.Matrix = eye( 4 );
%
% end % reset
%
%% Test the chart.
% We're now ready to test the chart! Let's try out a 360-degree roll. Note that we’ve added a pause command to slow down the animation speed and ensure that each iteration of the loop is captured as a single frame in the resulting animation.

f = figure;
AC = AircraftChart( "Parent", f );

for k = 1 : 72
    AC.roll( 5 )    
    pause( 0.05 )
end

%
%% Further charts.
% Taking a similar approach, we could wrap up the graphics required for the map plot used on the dashboard into a custom chart named LocationChart. This chart encapsulates the following graphics objects:
% A geographics axes (see geoaxes)
% Line objects used for the flight path of the aircraft and the current position marker (see geoplot).
% In the third part of this series, we’ll provide all the code for the components that make up the dashboard.
%
%
%% Summary
% We've seen how to take our initial visualization code for the aircraft and encapsulate it into a reusable chart. The chart can be used by colleagues as a standalone visualization within a script or function, or integrated into a larger application. We've also introduced some best programming practices for developing a chart class in MATLAB.
% In the next post, we'll see how to build up the dashboard by writing further modular components and integrating them into a MATLAB app.
%
%% References
% [1] Creating Specialized Charts with MATLAB Object-Oriented Programming, Ken Deeley and David Sampson (2018).