In this document, we describe how to generate plots of HybridArc objects using HybridPlotBuilder .

Contents

Setup

First, we create several HybridArc solution objects to use as examples

import hybrid.examples.*
config = HybridSolverConfig('Refine', 8); % 'Refine' option makes the plots smoother.
system = BouncingBall();
system_3D = Example3DHybridSystem();
sol    = system.solve([10, 0], [0 30], [0 30], config);
sol_3D = system_3D.solve([0; 1; 0], [0, 20], [0, 100], config);
sol_8D = HybridArc(sol.t, sol.j, sol.x(:, 1)*(1:8));

Basic Plotting

The Hybrid Equations Toolbox provides two approaches to plotting hybrid solutions, depending the level of control required. The first approach, designed for quickly viewing solutions, is to pass a HybridArc object to plotFlows , plotJumps , plotHybrid , or plotPhase . The plotFlows function plots each component of the solution versus discrete time \(t\) .

plotFlows(sol); % x vs. t

The plotJumps function plots each component of the solution versus discrete time \(j\) .

plotJumps(sol); % x vs. j

The plotHybrid function plots each component of the solution versus hybrid time \((t, j)\) .

plotHybrid(sol); % x vs. (t, j)

The plotPhase function plots a 2D or 3D solution vector x in phase space.

plotPhase(sol); % x1 vs. x2
title('2D Phase Plot')
snapnow; clf
plotPhase(sol_3D)
title('3D Phase Plot')
view([63.6 28.2]) % Adjust the view angle.

For solutions with four or less components, plotFlows , plotJumps , plotHybrid plot each component in separate subplots, as shown above. Solutions with five or more components are plotted in a single subplot with each colors and a legend included to label each component.

plotFlows(sol_8D)

The second plotting approach is to use the HybridPlotBuilder class explicitly (the first approach also uses HybridPlotBuilder "under the hood" to draw plots). This approach allows for extensive customization and is therefore, better suited for creation of plots for publication. The remainder of this document discusses how to use the second approach. The first step is to create an instance of HybridPlotBuilder .

hpb = HybridPlotBuilder();

Properties are set by calling functions (described below) on the HybridPlotBuilder object.

hpb.flowColor('black'); % Set the flow color to black.

To create a plot, one of the plotting functions plotFlows , plotJumps , plotHybrid , or plotPhase is called. Note that subplots are not created, by default.

hpb.plotFlows(sol)

In MATLAB R2016a and later, if the plot builder is only used once, it can be used immediately without assigning it to a variable by chaining a series of function calls.

HybridPlotBuilder().flowColor('black').plotFlows(sol);

Prior to MATLAB R2016a, function calls cannot be chained directly after a constructor, so the code above must be split into a variable assignment, followed by the function calls:

hpb = HybridPlotBuilder();
hpb.flowColor('black').plotFlows(sol);

Automatic Subplots

Automatic subplots can be enabled by calling HybridPlotBuilder.subplots('on') . When auto-subplots is 'on' and plotFlows , plotJumps , or plotHybrid is called, then a subplot is created for each plotted state component. If, on the otherhand, plotPhase is called, then a single subplot is created. If the current figure did not, previously, have the correct number of subplots, then the figure is cleared before plotting.

HybridPlotBuilder().subplots('on').plotFlows(sol)

When auto-subplots is 'off' , all plots are placed in the current axes (the default value is 'off' ).

clf % clear figure. Otherwise previous subplots are reused.
subplot(2, 1, 1)
HybridPlotBuilder().subplots('off').plotFlows(sol_8D)
subplot(2, 1, 2)
HybridPlotBuilder().subplots('off').plotPhase(sol)
axis padded
axis equal

At this point, if we call a plotting function with auto-subplots 'on' , then the figure is reset to the correct number of subplots.

%    <- No 'clf' here.
HybridPlotBuilder().subplots('on').plotPhase(sol)

Choosing Components to Plot

Often, you may wish to plot only some of the components in a HybridArc object hybrid_arc . To select particular components of hybrid_arc , call hybrid_arc.select(ndxs) where ndxs is a vector containing the indices of the components you wish to plot. Then, simply pass the output to one of the plotting functions. For example, to plot components 1 and 2, pass the array [1, 2] or (equivalently) 1:2 to select .

clf
plotPhase(sol_3D.select([1,2]));

To switch the order of components in the plot, simply switch the order in the array. Note that the labels \(x_1\) and \(x_2\) do not update to reflect the switched order.

HybridPlotBuilder().plotPhase(sol_3D.select([2,1]));

Plotting Other Values

It is possible to plot values contained in an array while using the hybrid time domain from a given HybridArc . To accomplish this, pass a HybridArc object as the first argument to a plotting function and pass the array of values as the second argument. The number of rows in the values array must match the number of entries in sol.t .

clf
height = sol.x(:, 1); % Extract height component
HybridPlotBuilder().plotFlows(sol, -height) % Plot negative height
title('Negative Height')
ylabel('$x_1$')
ylim padded

Alternatively, a function handle can be evaluated and plotted at each time step in a HybridArc . The evaluation of the function is done via HybridArc.evaluateFunction() .

g = system.gamma; % gravity
HybridPlotBuilder().plotFlows(sol, @(x) g*x(1) + 0.5*x(2)^2);
title('Total Energy')
ylabel('$E$')

The function handle passed to plotting function must have the input arguments @(x) , @(x, t) or @(x, t, j) and return a column vector.

Customizing Plot Appearance

The following functions modify the appearance of flows.

HybridPlotBuilder()...
    .flowColor('black')...
    .flowLineWidth(2)...
    .flowLineStyle(':')...
    .plotFlows(sol.select(1))

Similarly, we can change the appearance of jumps.

HybridPlotBuilder()...
    .jumpColor('m')... % magenta
    .jumpLineWidth(1)...
    .jumpLineStyle('-.')...
    .jumpStartMarker('+')...
    .jumpStartMarkerSize(16)...
    .jumpEndMarker('o')...
    .jumpEndMarkerSize(10)...
    .plotPhase(sol_3D.select(1:2))
axis padded

To configure the the jump markers on both sides of jumps with a single function call, omit 'Start' and 'End' from the function names:

HybridPlotBuilder()...
    .jumpMarker('s')... % square
    .jumpMarkerSize(12)...
    .plotPhase(sol_3D.select(1:2))

To hide jumps or flows, set the corresponding color to 'none' . To hide jump markers only, but show jump lines set the marker style to 'none' . Similarly, to hide only jump lines, set the jump line style to 'none' .

HybridPlotBuilder()...
    .flowColor('none')...
    .jumpEndMarker('none')...
    .jumpLineStyle('none')...
    .plotFlows(sol.select(2))
title('Start of Each Jump') % An alternative way to add titles is shown below.
ylabel('$x_2$')

Sequences of colors can be set by passing a cell array to the color functions. When auto-subplots are off, the color that each component is plotted rotates through the given colors. The following commands create a plot where the first component is plotted with blue flows and red jumps, and the second component is plotted with black flows and green jumps. (If the solution had a third component, the colors would cycle back to blue flows and red jumps.)

clf
HybridPlotBuilder().subplots('off')...
    .flowColor({'blue', 'black'})...
    .jumpColor({'red', 'green'})...
    .legend('Component 1', 'Component 2')... % The 'legend' function is described below.
    .plotFlows(sol);

When auto-subplots are enabled, then all the plots added by a single call to a plotting function are the same color, and all the plots added by the next call to a plotting function are the next color, and so on. Note, here, we set both flow and jump color via the color function. Furthermore, the 'matlab' argument tells HybridPlotBuilder to use the default colors used by MATLAB plots.

hpb = HybridPlotBuilder().subplots('on').color('matlab');
hold on
hpb.legend('$h$', '$v$').plotFlows(sol);
hpb.legend('$2h$', '$2v$').plotFlows(sol, @(x) 2*x);
hpb.legend('$3h$', '$3v$').plotFlows(sol, @(x) 3*x);

Titles, Labels, and Legends

This section describes how to add text to plots using HybridPlotBuilder . The built-in MATLAB functions for adding labels and titles can also be used, but using HybridPlotBuilder offers the ability to easily configure default settings (text size, interpreter, etc.) and use automatically generated axis labels. The built-in legend function does not work with plots generated by HybridPlotBuilder .

Axis Labels

For state axes, labels are set via the labels function (or, optionally, the label function if there is only one label). Depending on the type of plot and whether auto-subplots is enabled, all components will either be grouped into a single label or each component will have its own label. All components are grouped into a single label if auto-subplots is 'off' and the plot is generated using plotFlows , plotJumps , or plotHybrid (in other words, any plotting function except phasePlot ).

clf
HybridPlotBuilder()...
    .subplots('off')... % This is the default
    .label('All Components')...
    .plotFlows(sol_8D)

Alternatively, each component has its own label when each component has its own axis —either because each component is placed in its own subplot using auto-subplots or each component has its own axis in a single phase plot produced using plotPhase . In this case, the label for each component is set by passing multiple strings to HybridPlotBuilder.labels() .

clf
HybridPlotBuilder()...
    .labels('Component 1', 'Component 2')...
    .plotPhase(sol)

If there are fewer labels provided than the number of component axes (or no labels are provided at all), then axis labels are automatically generated. The default format is \(x_1\) , \(x_2\) , etc., but this can be modified by passing a format string to the function xLabelFormat . Any occurance of '%d' in the format string is substituted with the index number of the component.

clf
HybridPlotBuilder().subplots('on')...
    .xLabelFormat('$Z_{%d}$')...
    .plotFlows(sol_8D.select(1:4))

The labels for continuous-time and discrete-time axes (i.e., \(t\) and \(j\) ) can be modified as follows. Using an empty string removes the label.

clf
HybridPlotBuilder()...
    .tLabel('$t$ [s]')...
    .jLabel('$j$ [count]')...
    .plotHybrid(sol.select(1))

Titles

Titles can be set for each subplot via the titles functions (or, optionally, the title function if there is only one title).

clf
HybridPlotBuilder().subplots('on')...
    .titles('Component 1', 'Component 2')...
    .plotFlows(sol)

If auto-subplots is 'off' or a phase plot is generated, then there is only one subplot and thus only one title is used.

clf
HybridPlotBuilder().title('Phase Plot').plotPhase(sol)

Legend Entries

The function HybridPlotBuilder.legend is used to add legend entries to plots. When using plotPhase , only one legend entry is used.

clf
HybridPlotBuilder().legend('Phase Plot').plotPhase(sol);

Otherwise, when using plotFlows , plotJumps , or plotHybrid , one legend entry is used for each state component (this is true regardless of whether auto-subplots are 'on' or 'off' ).

clf
HybridPlotBuilder().color('matlab')...
    .legend('Component 1', 'Component 2', 'Component 3', 'Component 4')...
    .plotFlows(sol_8D.select(1:4));

Additional plots with legend entries can be added to the same figure by reusing the same HybridPlotBuilder object.

clf
hpb = HybridPlotBuilder().subplots('on').color('matlab');
hpb.legend('$x_1$', '$x_2$').plotFlows(sol);
hold on
hpb.legend('$-x_1$', '$-x_2$').plotFlows(sol.transform(@(x) -x))

To omit a component from the legend, set the label to an empty string.

clf
HybridPlotBuilder().subplots('on')...
    .legend('', 'Component 2')...
    .plotFlows(sol);

Graphic objects added to a figure without HybridPlotBuilder can be added to the legend by passing the graphic handle to addLegendEntry .

clf
pb = HybridPlotBuilder().legend('Hybrid Plot').plotPhase(sol);
hold on
axis equal

% Plot a circle.
theta = linspace(0, 2*pi);
plt_handle = plot(10*cos(theta), 10*sin(theta), 'black');

% Pass the circle to the plot builder with the desired legend label 'Circle'.
pb.addLegendEntry(plt_handle, 'Circle');

To set legend properties, such as location and number of columns, pass the legend labels to HybridPlotBuilder.legend as a cell array (enclosed with braces '{}') and pass name/value pairs in subsequent arguments. See doc('legend') for a description of legend properties.

clf
HybridPlotBuilder().subplots('on')...
    .legend({'h', 'v'}, 'Location', 'eastoutside')...
    .plotFlows(sol);

Setting the legend options via the methods above applies the same settings to the legends in all subplots. To modify legend options separately for each subplot, use the configurePlots function described below.

Summary of How Many Lables, Titles, and Legend Entries Are Used

In this subsection, we summarize and give examples of how many labels, titles, and legend entries are used, depending on whether auto-subplots are enabled and the choice of plotting function.

A * indicates that the property in that row has no effect on the text property in that column.
Component Labels Titles Legend Entries
Auto-subplots 'off' Single (unless plotPhase() ) Single *
Auto-subplots 'on' Multiple: one label per subplot Multiple: one title per subplot *
plotPhase() Multiple: one label per axis * Single
plotFlows() , plotJumps() , or plotHybrid() Single (unless auto-subplots is 'on' ) * Multiple: one legend entry per component

Example: auto-subplots 'off' and using plotFlows , plotJumps or plotHybrid .

clf
HybridPlotBuilder().color('matlab')...
    .subplots('off')... % (default)
    .label('My Label')... % Single: auto-subplots is 'off' and using plotFlows.
    .title('My Title')... % Single: auto-subplots is 'off'.
    .legend('Legend 1', 'Legend 2')... % One per component: using plotFlows
    .plotFlows(sol);

Example: auto-subplots 'on' and using plotFlows , plotJumps or plotHybrid .

clf
HybridPlotBuilder()...
    .subplots('on')...
    .labels('Label 1', 'Label 2')... % One per component: auto-subplots is 'on'
    .titles('Title 1', 'Title 2')... % One per component: auto-subplots is 'on'
    .legend('Legend 1', 'Legend 2')... % One per component: using plotFlows
    .plotFlows(sol);

Example: Using plotPhase (auto-subplots has no effect).

clf
HybridPlotBuilder()...
    .labels('Label 1', 'Label 2')... % One per component: using plotPhase
    .title('My Title')... % Single: using plotPhase
    .legend('My legend')... % Single: using plotPhase
    .plotPhase(sol);

Text Interpreters

The default text interpreter in HybridPlotBuilder is latex . This can be changed by calling HybridPlotBuilder.titleInterpreter() or HybridPlotBuilder.labelInterpreter() with one of these values: 'none' , 'tex' , or 'latex' . The default labels automatically change to match the label interpreter.

HybridPlotBuilder().subplots('on')...
    .titleInterpreter('none')...
    .labelInterpreter('tex')...
    .titles('In this figure, ''tex'' is used for labels and ''none'' for titles',...
            'In ''tex'', dollar signs do not indicate a switch to math mode') ...
    .labels('z_1', '$z_2$')...
    .plotFlows(sol)

Filtering Solutions

Portions of solutions can be hidden with the filter function. In this example, we create a filter that only includes points where the second component (velocity) is negative. (If the time-step size along flows is large, deleted lines connected to filtered points may extends a noticible distance into the portion of the solution that should be visible.)

is_falling = sol.x(:,2) < 0;
HybridPlotBuilder().subplots('on')...
    .filter(is_falling)...
    .plotFlows(sol)

Example: Plotting System Modes

Filtering is useful for plotting systems where an integer-value component indicates the mode of the system. Here, we create a 3D system with a continuous variable \(z \in \mathbb{R}^2\) and a discrete variable \(q \in \{0, 1\}\) . Points in the solution where \(q = 0\) are plotted in blue, and points where \(q = 1\) are plotted in black. The same HybridPlotBuilder object is used for both plots by saving it to the builder variable (this allows us to specify the labels only once and add a legend for both plots).

clf
system_with_modes = hybrid.examples.ExampleModesHybridSystem();


% Create initial condition and solve.
z0 = [-7; 7];
q0 = 0;
sol_modes = system_with_modes.solve([z0; q0], [0, 10], [0, 10], config);

% Extract values for q-component.
q = sol_modes.x(:, 3);

% Plot the [1, 2] components (that is, the first two components) of
% sol_modes at all time steps where q == 0.
builder = HybridPlotBuilder();
builder.title('Phase Portrait') ...
    .labels('$x_1$', '$x_2$') ...
    .legend('$q = 0$') ...
    .filter(q == 0) ... % Only plot points where q is 0.
    .plotPhase(sol_modes.select([1,2]))
hold on % See the section below about 'hold on'
% Plot in black the solution (still only the [1,2] components) for all time
% steps where q == 1.
builder.flowColor('black') ...
    .jumpColor('none') ...
    .legend('$q = 1$') ...
    .filter(q == 1) ... % Only plot points where q is 1.
    .plotPhase(sol_modes.select([1,2]))
axis padded
axis equal

Example: Showing when Bouncing Ball is Rising and Falling

For the bouncing ball system, we can change the color of the plot based on whether the ball is falling.

clf
is_falling = sol.x(:, 2) < 0;
pb = HybridPlotBuilder()....
    .subplots('on')...
    .filter(is_falling)...
    .jumpColor('none')...
    .flowColor('k')...
    .legend('Falling', 'Falling')...
    .plotFlows(sol);
hold on
pb.filter(~is_falling)...
    .flowColor('g')...
    .legend('Rising', 'Rising')...
    .plotFlows(sol);

Replacing vs. Adding Plots to a Figure

By default, each call to a HybridPlotBuilder plot function overwrites the previous plots. In the following code, we call plotFlows twice. The first call plots a solution in blue and red, but the second call resets the figure and plots a solution in black and green.

tspan = [0 10];
jspan = [0 30];
sol1 = system.solve([10, 0], tspan, jspan, config);
sol2 = system.solve([ 5, 10], tspan, jspan, config);

clf
HybridPlotBuilder()... % Plots blue flows and red jumps by default.
    .plotFlows(sol1.select(1))
HybridPlotBuilder().flowColor('black').jumpColor('green')...
    .title('Multiple Calls to $\texttt{plotFlows}$ with \texttt{hold off}') ...
    .plotFlows(sol2.select(1))

To plot multiple graphs on the same figure, use hold on , similar to standard MATLAB plotting functions.

clf
HybridPlotBuilder().plotFlows(sol1.select(1)) % Plots blue flows and red jumps by default.
hold on
HybridPlotBuilder().flowColor('black').jumpColor('green')...
    .title('Multiple Calls to $\texttt{plotFlows}$ with \texttt{hold on}')...
    .plotFlows(sol2.select(1))

Modifying Defaults

The default values of all HybridPlotBuilder settings can be modified by calling 'set' on the 'defaults' property. The arguments are must be name-value pairs, where the name is a string that matches one of the properties in PlotSettings (names are case insensitive and underscores can be replaced with spaces).

clf
HybridPlotBuilder.defaults.set('auto_subplots', 'on', ...
                             'Label Size', 14, ...
                             'Title Size', 16, ...
                             'label interpreter', 'tex', ...
                             'title interpreter', 'none', ...
                             'flow_color', 'k', ...
                             'flow line width', 2, ...
                             'jump_color', 'k', ...
                             'jump line width', 2, ...
                             'jump line style', ':', ...
                             'jump start marker', 's', ...
                             'jump start marker size', 10, ...
                             'jump end marker', 'none', ...
                             'x_label_format', 'z_{%d}', ...
                             't_label', 't [s]')
HybridPlotBuilder()...
    .title('Title')...
    .legend('Legend A', 'Legend B')...
    .plotFlows(sol);

The defaults can be reset to their original value with the following command.

HybridPlotBuilder.defaults.reset()

Setting Default Scaling

MATLAB is inconsistent about the size of text and graphics in plots on different devices. To mitigate this difference, three values are included in the settings to adjust the size of text, lines, and markers.

clf
HybridPlotBuilder.defaults.set('text scale', 1.5, ...
                                'line scale', 3, ...
                                'marker scale', 2)
% Example output:
HybridPlotBuilder()...
    .title('Title')...
    .legend('height')...
    .plotFlows(sol.select(1));

HybridPlotBuilder.defaults.reset() % Cleanup

To set the default values in every MATLAB session, create a script named 'startup.m' in the folder returned by the userpath() command. The commands in this script will run every time MATLAB starts.

Additional Configuration

There are numerous options for configuring the appearance of MATLAB plots that are not included explicitly in HybridPlotBuilder (see here ). For plots with a single subplot, the appearance can be modified just like any other MATLAB plot.

HybridPlotBuilder().plotPhase(sol);
grid on
ax = gca;
ax.YAxisLocation = 'right';

Plots with multiple subplots can also be configured as described above by calling subplot(2, 1, 1) and making the desired modifications, then calling subplot(2, 1, 2) , etc., but that approach is messy and tediuous. Instead, the configurePlots function provides a cleaner alternative. A function handle is passed to configurePlots , which is then called by HybridPlotBuilder within each (sub)plot. The function handle passed to configurePlots must take two input arguments. The first is the axes for the subplot and the second is the index for the state component(s) plotted in the plot. For plotFlows , plotJumps , and plotHybrid , this will be one integer, and for phase plots generated with plot , this will be a vector of two or three integers, depending on the dimension of the plot.

clf
HybridPlotBuilder()...
    .subplots('on')...
    .legend('A', 'B')...
    .configurePlots(@apply_plot_settings)...
    .plotFlows(sol);

function apply_plot_settings(ax, component_ndxs)
    title(ax, sprintf('This is the plot of component %d', component_ndxs))
    % Set the location of the legend in each plot to different positions.
    switch component_ndxs
        case 1
            ax.Legend.Location = 'northeast';
        case 2
            ax.Legend.Location = 'southeast';
    end
    % Configure grid
    grid(ax, 'on')
    grid(ax, 'minor')
    ax.GridLineStyle = '--';
end

Updated: