How to plot std::vector data from C++ program to figure window using Engine API for C++?
22 vues (au cours des 30 derniers jours)
Afficher commentaires plus anciens
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
0 commentaires
Réponses (0)
Voir également
Catégories
En savoir plus sur Call MATLAB from C++ dans Help Center et File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!