Clear Filters
Clear Filters

How to plot std::vector data from C++ program to figure window using Engine API for C++?

26 views (last 30 days)
This post is more a tutorial, sharing what I found that worked, than it is a question since I couldn't find a simple example to do the job. The task is simple:
Given two std::vector<double>:
std::vector<double> xvals;
std::vector<double> yvals;
representing x and y values for a sinusoidal function, use the Engine API for C++ to plot them to a figure window. I'm using Visual Studio 2019 and followed this tutorial verbatim [1] to install the headers and precompiled libraries to my VS project.
And the source code:
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <numeric>
#include "MatlabEngine.hpp"
#include "MatlabDataArray.hpp"
using std::cout;
using std::vector;
using std::string;
using std::ofstream;
using std::ios_base;
class Sinewave
{
public:
vector<double> yvals;
vector<double> xvals;
// yvals = sin(a*t + b) and ownership of t is transferred to this object (as xvals) if it's an R-value
// a = angular frequency; the value resulting from 2*pi*f
// b = initial phase, in radians
Sinewave(double a, double b, vector<double>&& t): xvals(std::move(t))
{
yvals = vector<double>(xvals.size());
// Compute the angle values
std::transform(xvals.begin(), xvals.end(), yvals.begin(), [a, b](double xval) -> double {return a * xval + b; });
// Compute the sin-values
std::transform(yvals.begin(), yvals.end(), yvals.begin(), [](double angle) -> double {return sin(angle); });
}
// Transfer ownership of other SineSignal object's yvals, xvals
Sinewave(const Sinewave&& other) noexcept
: yvals(std::move(yvals)), xvals(std::move(xvals)) {}
// Output to csv file for quick plotting in matlab
bool ToCsvFile(const string path)
{
ofstream csvFile(path, ios_base::trunc);
if (!csvFile) return false; // Failure
vector<string> valPairs;
for (size_t i = 0; i < xvals.size(); i++)
{
std::stringstream ss;
ss << xvals[i] << "," << yvals[i] << '\n';
valPairs.push_back(ss.str());
}
for (auto pair : valPairs) csvFile << pair.c_str();
csvFile.close();
return true;
}
// Uses the Matlab Engine API for C++ to invoke Matlab's plot command and open a figure window
void PlotInMatlab()
{
std::unique_ptr<matlab::engine::MATLABEngine> MLPtr = matlab::engine::startMATLAB();
matlab::data::ArrayFactory factory;
// Create arguments for the call to Matlab's plot function
std::vector<matlab::data::Array> args({
factory.createArray({ xvals.size(), 1 }, xvals.begin(), xvals.end()),
factory.createArray({ yvals.size(), 1 }, yvals.begin(), yvals.end()),
factory.createCharArray(std::string("ro"))
});
// Invoke the plot command
MLPtr->feval(u"plot", args);
}
};
/* Returns multiples of Ts, the sampling period in seconds, beginning at tStart. */
vector<double> SampledTime(int nSamples, double Ts, double tStart = 0)
{
vector<double> tvals(nSamples);
std::iota(tvals.begin(), tvals.end(), tStart);
std::transform(tvals.begin(), tvals.end(), tvals.begin(), [Ts](double sampleVal) -> double {return sampleVal * Ts; });
return tvals;
}
int main()
{
double fs = 300; // Sampling frequency, in Hz
int nCycles = 2, nSamples = (int)((double)nCycles * fs); // Number of sine-wave cycles
vector<double> tvals = SampledTime(nSamples, 1 / fs);
const double pi = 3.141592653589793;
double w = 2 * pi * 1, poff = pi; // 1 Hz sinewave, 180 deg. out of phase
auto sw = Sinewave(w, poff, std::move(tvals));
sw.PlotInMatlab();
}
In main(), you define the sinusoidal parameters and use the SampledTime() method to get a sequence of time values (i.e., nT where n = 0, 1, ..., N and T is your sampling period). The sinusoidal angular frequency is given by w, the phase offset by poff. Then create a Sinewave object, passing in those sinusoidal parameters, which will then use cmath's sin() function to find the corresponding sin values. Call Sinewave.PlotInMatlab() and a figure window pops up with the sinwave plotted.
Since it's written in C++, you can change the sinewave parameters at runtime (i.e., prompt user for new sinuisoidal parameters), and have the program regenerate the plot. This to avoid writing the vector<double> data to file and then manually opening ML and manually invoking the matlab's plot command.
There's no good reason why Sinewave(double a, double b, vector<double>&& t) takes an R-value type as a third parameter. This was initially an experiment to see if using R-Value avoids copying the tvals array, which could potentially store a lot of doubles. I'm also playing around with move constructors, which is what
Sinewave(const Sinewave&& other) noexcept
: yvals(std::move(yvals)), xvals(std::move(xvals)) {}
is all about.
Comments/suggestions appreciated!
[1] I also had to add "C:\Program Files\MATLAB\_<ver>_\extern\bin\win64" to User environment variable.
% OS: Windows 10 Home, 64-bit
% IDE: Visual Studio Community 2019

Answers (0)

Categories

Find more on Interactive Control and Callbacks in Help Center and File Exchange

Products


Release

R2019a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!