/*
* Plot.cpp, part of LatAnalyze 3
*
* Copyright (C) 2013 - 2020 Antonin Portelli
*
* LatAnalyze 3 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LatAnalyze 3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LatAnalyze 3. If not, see .
*/
#include
#include
#include
using namespace std;
using namespace Latan;
/******************************************************************************
* Plot objects *
******************************************************************************/
// PlotObject access ///////////////////////////////////////////////////////////
const string & PlotObject::getCommand(void) const
{
return command_;
}
const string & PlotObject::getHeadCommand(void) const
{
return headCommand_;
}
void PlotObject::setCommand(const string &command)
{
command_ = command;
}
void PlotObject::setHeadCommand(const string &command)
{
headCommand_ = command;
}
string PlotObject::popTmpFile(void)
{
string res = tmpFileName_.top();
tmpFileName_.pop();
return res;
}
void PlotObject::pushTmpFile(const std::string &fileName)
{
tmpFileName_.push(fileName);
}
// PlotObject dump a matrix to a temporary file ////////////////////////////////
string PlotObject::dumpToTmpFile(const DMat &m)
{
char tmpFileName[MAX_PATH_LENGTH];
int fd;
FILE *tmpFile;
for (Index j = 0; j < m.cols(); ++j)
{
}
sprintf(tmpFileName, "%s/latan_plot_tmp.XXXXXX.dat", P_tmpdir);
fd = mkstemps(tmpFileName, 4);
if (fd == -1)
{
LATAN_ERROR(System, "impossible to create a temporary file from template "
+ strFrom(tmpFileName));
}
tmpFile = fdopen(fd, "w");
for (Index i = 0; i < m.rows(); ++i)
{
for (Index j = 0; j < m.cols(); ++j)
{
fprintf(tmpFile, "%e ", m(i, j));
}
fprintf(tmpFile, "\n");
}
fclose(tmpFile);
return string(tmpFileName);
}
// PlotObject test /////////////////////////////////////////////////////////////
bool PlotObject::gotTmpFile(void) const
{
return !tmpFileName_.empty();
}
// PlotCommand constructor /////////////////////////////////////////////////////
PlotCommand::PlotCommand(const string &command)
{
setCommand(command);
}
// PlotHeadCommand constructor /////////////////////////////////////////////////
PlotHeadCommand::PlotHeadCommand(const string &command)
{
setHeadCommand(command);
}
// PlotData constructor ////////////////////////////////////////////////////////
PlotData::PlotData(const DMatSample &x, const DMatSample &y, const bool abs)
{
if (x[central].rows() != y[central].rows())
{
LATAN_ERROR(Size, "x and y vectors do not have the same size");
}
DMat d(x[central].rows(), 4);
string usingCmd, tmpFileName;
d.col(0) = x[central].col(0);
d.col(2) = y[central].col(0);
d.col(1) = x.variance().cwiseSqrt().col(0);
d.col(3) = y.variance().cwiseSqrt().col(0);
tmpFileName = dumpToTmpFile(d);
pushTmpFile(tmpFileName);
if (!abs)
{
setCommand("'" + tmpFileName + "' u 1:3:2:4 w xyerr");
}
else
{
setCommand("'" + tmpFileName + "' u 1:(abs($3)):2:4 w xyerr");
}
}
PlotData::PlotData(const DVec &x, const DMatSample &y, const bool abs)
{
if (x.rows() != y[central].rows())
{
LATAN_ERROR(Size, "x and y vector does not have the same size");
}
DMat d(x.rows(), 3);
string usingCmd, tmpFileName;
d.col(0) = x;
d.col(1) = y[central].col(0);
d.col(2) = y.variance().cwiseSqrt().col(0);
tmpFileName = dumpToTmpFile(d);
pushTmpFile(tmpFileName);
if (!abs)
{
setCommand("'" + tmpFileName + "' u 1:2:3 w yerr");
}
else
{
setCommand("'" + tmpFileName + "' u 1:(abs($2)):3 w yerr");
}
}
PlotData::PlotData(const DMatSample &x, const DVec &y, const bool abs)
{
if (x[central].rows() != y.rows())
{
LATAN_ERROR(Size, "x and y vectors do not have the same size");
}
DMat d(x[central].rows(), 3), xerr, yerr;
string usingCmd, tmpFileName;
d.col(0) = x[central].col(0);
d.col(2) = y;
d.col(1) = x.variance().cwiseSqrt().col(0);
tmpFileName = dumpToTmpFile(d);
pushTmpFile(tmpFileName);
if (!abs)
{
setCommand("'" + tmpFileName + "' u 1:3:2 w xerr");
}
else
{
setCommand("'" + tmpFileName + "' u 1:($3):2 w xerr");
}
}
PlotData::PlotData(const XYStatData &data, const Index i, const Index j, const bool abs)
{
string usingCmd, tmpFileName;
if (!abs)
{
usingCmd = (data.isXExact(i)) ? "u 1:3:4 w yerr" : "u 1:3:2:4 w xyerr";
}
else
{
usingCmd = (data.isXExact(i)) ? "u 1:(abs($3)):4 w yerr" : "u 1:(abs($3)):2:4 w xyerr";
}
tmpFileName = dumpToTmpFile(data.getTable(i, j));
pushTmpFile(tmpFileName);
setCommand("'" + tmpFileName + "' " + usingCmd);
}
// PlotLine constructor ////////////////////////////////////////////////////////
PlotLine::PlotLine(const DVec &x, const DVec &y)
{
if (x.size() != y.size())
{
LATAN_ERROR(Size, "x and y vectors do not have the same size");
}
DMat d(x.size(), 2);
string usingCmd, tmpFileName;
d.col(0) = x;
d.col(1) = y;
tmpFileName = dumpToTmpFile(d);
pushTmpFile(tmpFileName);
setCommand("'" + tmpFileName + "' u 1:2 w lines");
}
// PlotPoints constructor ////////////////////////////////////////////////////////
PlotPoints::PlotPoints(const DVec &x, const DVec &y)
{
if (x.size() != y.size())
{
LATAN_ERROR(Size, "x and y vectors do not have the same size");
}
DMat d(x.size(), 2);
string usingCmd, tmpFileName;
d.col(0) = x;
d.col(1) = y;
tmpFileName = dumpToTmpFile(d);
pushTmpFile(tmpFileName);
setCommand("'" + tmpFileName + "' u 1:2");
}
// PlotHLine constructor ///////////////////////////////////////////////////////
PlotHLine::PlotHLine(const double y)
{
setCommand(strFrom(y));
}
// PlotHBand constructor ///////////////////////////////////////////////////////
PlotBand::PlotBand(const double xMin, const double xMax, const double yMin,
const double yMax, const double opacity)
{
setCommand("'< printf \"%e %e\\n%e %e\\n%e %e\\n%e %e\\n%e %e\\n\" "
+ strFrom(xMin) + " " + strFrom(yMin) + " "
+ strFrom(xMax) + " " + strFrom(yMin) + " "
+ strFrom(xMax) + " " + strFrom(yMax) + " "
+ strFrom(xMin) + " " + strFrom(yMax) + " "
+ strFrom(xMin) + " " + strFrom(yMin)
+ "' u 1:2 w filledcurves closed fs solid " + strFrom(opacity)
+ " noborder");
}
// PlotFunction constructor ////////////////////////////////////////////////////
PlotFunction::PlotFunction(const DoubleFunction &function, const double xMin,
const double xMax, const unsigned int nPoint,
const bool abs)
{
DMat d(nPoint, 2);
string tmpFileName;
double dx = (xMax - xMin)/static_cast(nPoint - 1);
for (Index i = 0; i < nPoint; ++i)
{
d(i, 0) = xMin + i*dx;
d(i, 1) = function(d(i, 0));
}
tmpFileName = dumpToTmpFile(d);
pushTmpFile(tmpFileName);
if (!abs)
{
setCommand("'" + tmpFileName + "' u 1:2 w lines");
}
else
{
setCommand("'" + tmpFileName + "' u 1:(abs($2)) w lines");
}
}
// PlotPredBand constructor ////////////////////////////////////////////////////
void PlotPredBand::makePredBand(const DMat &low, const DMat &high, const double opacity)
{
string lowFileName, highFileName, contFileName;
DMat contour(low.rows() + high.rows() + 1, 2);
FOR_MAT(low, i, j)
{
contour(i, j) = low(i, j);
}
FOR_MAT(high, i, j)
{
contour(low.rows() + i, j) = high(high.rows() - i - 1, j);
}
contour.row(low.rows() + high.rows()) = low.row(0);
contFileName = dumpToTmpFile(contour);
pushTmpFile(contFileName);
setCommand("'" + contFileName + "' u 1:2 w filledcurves closed" +
" fs solid " + strFrom(opacity) + " noborder");
}
PlotPredBand::PlotPredBand(const DVec &x, const DVec &y, const DVec &yerr,
const double opacity)
{
if (x.size() != y.size())
{
LATAN_ERROR(Size, "x and y vectors do not have the same size");
}
if (y.size() != yerr.size())
{
LATAN_ERROR(Size, "y and y error vectors do not have the same size");
}
Index nPoint = x.size();
DMat dLow(nPoint, 2), dHigh(nPoint, 2);
dLow.col(0) = x;
dLow.col(1) = y - yerr;
dHigh.col(0) = x;
dHigh.col(1) = y + yerr;
makePredBand(dLow, dHigh, opacity);
}
PlotPredBand::PlotPredBand(const DoubleFunctionSample &function,
const double xMin, const double xMax,
const unsigned int nPoint, const double opacity)
{
DMat dLow(nPoint, 2), dHigh(nPoint, 2);
DSample pred(function.size());
double dx = (xMax - xMin)/static_cast(nPoint - 1);
string lowFileName, highFileName;
for (Index i = 0; i < nPoint; ++i)
{
double x = xMin + i*dx, err;
pred = function(x);
err = sqrt(pred.variance());
dLow(i, 0) = x;
dLow(i, 1) = pred[central] - err;
dHigh(i, 0) = x;
dHigh(i, 1) = pred[central] + err;
}
makePredBand(dLow, dHigh, opacity);
}
// PlotHistogram constructor ///////////////////////////////////////////////////
PlotHistogram::PlotHistogram(const Histogram &h)
{
DMat d(h.size(), 2);
string tmpFileName;
for (Index i = 0; i < h.size(); ++i)
{
d(i, 0) = h.getX(i);
d(i, 1) = h[i];
}
tmpFileName = dumpToTmpFile(d);
pushTmpFile(tmpFileName);
setCommand("'" + tmpFileName + "' u 1:2 w steps");
}
// PlotImpulses constructor ////////////////////////////////////////////////////
PlotImpulses::PlotImpulses(const DVec &x, const DVec &y)
{
if (x.rows() != y.rows())
{
LATAN_ERROR(Size, "x and y vector does not have the same size");
}
DMat d(x.rows(), 2);
string tmpFileName;
for (Index i = 0; i < x.rows(); ++i)
{
d(i, 0) = x(i);
d(i, 1) = y(i);
}
tmpFileName = dumpToTmpFile(d);
pushTmpFile(tmpFileName);
setCommand("'" + tmpFileName + "' u 1:2 w impulses");
}
// PlotMatrixNoRange constructor ///////////////////////////////////////////////
PlotMatrixNoRange::PlotMatrixNoRange(const DMat &m)
{
string tmpFileName = dumpToTmpFile(m);
pushTmpFile(tmpFileName);
setCommand("'" + tmpFileName + "' matrix w image");
}
/******************************************************************************
* Plot modifiers *
******************************************************************************/
// Caption constructor /////////////////////////////////////////////////////////
Caption::Caption(const string &caption)
: caption_(caption)
{}
// Caption modifier ////////////////////////////////////////////////////////////
void Caption::operator()(PlotOptions &option) const
{
option.caption = caption_;
}
// Label constructor ///////////////////////////////////////////////////////////
Label::Label(const string &label, const Axis axis)
: label_(label)
, axis_(axis)
{}
// Label modifier //////////////////////////////////////////////////////////////
void Label::operator()(PlotOptions &option) const
{
option.label[static_cast(axis_)] = label_;
}
// Color constructor ///////////////////////////////////////////////////////////
Color::Color(const string &color)
: color_(color)
{}
// Color modifier //////////////////////////////////////////////////////////////
void Color::operator()(PlotOptions &option) const
{
option.lineColor = color_;
}
// LineWidth constructor ///////////////////////////////////////////////////////
LineWidth::LineWidth(const unsigned int width)
: width_(width)
{}
// LineWidth modifier //////////////////////////////////////////////////////////
void LineWidth::operator()(PlotOptions &option) const
{
option.lineWidth = static_cast(width_);
}
// Dash constructor ///////////////////////////////////////////////////////////
Dash::Dash(const string &dash)
: dash_(dash)
{}
// Dash modifier //////////////////////////////////////////////////////////////
void Dash::operator()(PlotOptions &option) const
{
option.dashType = dash_;
}
// LogScale constructor ////////////////////////////////////////////////////////
LogScale::LogScale(const Axis axis)
: axis_(axis)
{}
// Logscale modifier ///////////////////////////////////////////////////////////
void LogScale::operator()(PlotOptions &option) const
{
option.scaleMode[static_cast(axis_)] |= Plot::Scale::log;
}
// PlotRange constructors //////////////////////////////////////////////////////
PlotRange::PlotRange(const Axis axis)
: axis_(axis)
, reset_(true)
, min_(0.)
, max_(0.)
{}
PlotRange::PlotRange(const Axis axis, const double min, const double max)
: axis_(axis)
, reset_(false)
, min_(min)
, max_(max)
{}
// PlotRange modifier ///////////////////////////////////////////////////////////
void PlotRange::operator()(PlotOptions &option) const
{
int a = static_cast(axis_);
if (!reset_)
{
option.scaleMode[a] |= Plot::Scale::manual;
option.scale[a].min = min_;
option.scale[a].max = max_;
}
else
{
option.scaleMode[a] = Plot::Scale::reset;
}
}
// Terminal constructor ////////////////////////////////////////////////////////
Terminal::Terminal(const string &terminal, const std::string &options)
: terminalCmd_(terminal + " " + options)
{}
// Terminal modifier ///////////////////////////////////////////////////////////
void Terminal::operator()(PlotOptions &option) const
{
option.terminal = terminalCmd_;
}
// Title constructor ///////////////////////////////////////////////////////////
Title::Title(const string &title)
: title_(title)
{}
// Title modifier //////////////////////////////////////////////////////////////
void Title::operator()(PlotOptions &option) const
{
option.title = title_;
}
// Palette constructor /////////////////////////////////////////////////////////
Palette::Palette(const std::vector &palette)
: palette_(palette)
{}
// Palette modifier ////////////////////////////////////////////////////////////
void Palette::operator()(PlotOptions &option) const
{
option.palette = palette_;
}
// category10 palette //////////////////////////////////////////////////////////
const std::vector Palette::category10 =
{
"rgb '#1f77b4'",
"rgb '#ff7f0e'",
"rgb '#2ca02c'",
"rgb '#d62728'",
"rgb '#9467bd'",
"rgb '#8c564b'",
"rgb '#e377c2'",
"rgb '#7f7f7f'",
"rgb '#bcbd22'"
};
/******************************************************************************
* Plot implementation *
******************************************************************************/
// constructor /////////////////////////////////////////////////////////////////
Plot::Plot(void)
{
initOptions();
}
// default options /////////////////////////////////////////////////////////////
void Plot::initOptions(void)
{
options_.terminal = "qt noenhanced font 'Arial,12'";
options_.output = "";
options_.caption = "";
options_.title = "";
options_.scaleMode[0] = Plot::Scale::reset;
options_.scaleMode[1] = Plot::Scale::reset;
options_.scale[0] = {0.0, 0.0};
options_.scale[1] = {0.0, 0.0};
options_.label[0] = "";
options_.label[1] = "";
options_.lineColor = "";
options_.lineWidth = -1;
options_.dashType = "";
options_.palette = Palette::category10;
}
// plot reset //////////////////////////////////////////////////////////////////
void Plot::reset(void)
{
headCommand_.clear();
plotCommand_.clear();
tmpFileName_.clear();
initOptions();
}
// plot objects ////////////////////////////////////////////////////////////////
Plot & Plot::operator<<(PlotObject &&command)
{
string commandStr;
while (command.gotTmpFile())
{
tmpFileName_.push_back(command.popTmpFile());
commandStr += "'" + tmpFileName_.back() + "' ";
}
commandStr = command.getCommand();
if (!commandStr.empty())
{
if (!options_.lineColor.empty())
{
commandStr += " lc " + options_.lineColor;
options_.lineColor = "";
}
if (options_.lineWidth > 0)
{
commandStr += " lw " + strFrom(options_.lineWidth);
options_.lineWidth = -1;
}
if (!options_.dashType.empty())
{
commandStr += " dt " + options_.dashType;
options_.dashType = "";
}
if (options_.title.empty())
{
commandStr += " notitle";
}
else
{
commandStr += " t '" + options_.title + "'";
options_.title = "";
}
plotCommand_.push_back(commandStr);
}
if (!command.getHeadCommand().empty())
{
headCommand_.push_back(command.getHeadCommand());
}
return *this;
}
Plot & Plot::operator<<(PlotModifier &&modifier)
{
modifier(options_);
return *this;
}
// find gnuplot ////////////////////////////////////////////////////////////////
#define SEARCH_DIR(dir) \
sprintf(buf, "%s/%s", dir, gnuplotBin_.c_str());\
if (access(buf, X_OK) == 0)\
{\
return dir;\
}
std::string Plot::getProgramPath(void)
{
int i, j, lg;
char *path;
static char buf[MAX_PATH_LENGTH];
// try out in all paths given in the PATH variable
buf[0] = 0;
path = getenv("PATH") ;
if (path)
{
for (i=0;path[i];)
{
for (j=i;(path[j]) and (path[j]!=':');j++);
lg = j - i;
strncpy(buf,path + i,(size_t)(lg));
if (lg == 0)
{
buf[lg++] = '.';
}
buf[lg++] = '/';
strcpy(buf + lg, gnuplotBin_.c_str());
if (access(buf, X_OK) == 0)
{
// found it!
break ;
}
buf[0] = 0;
i = j;
if (path[i] == ':') i++ ;
}
}
// if the buffer is still empty, the command was not found
if (buf[0] != 0)
{
lg = (int)(strlen(buf) - 1);
while (buf[lg]!='/')
{
buf[lg] = 0;
lg--;
}
buf[lg] = 0;
gnuplotPath_ = buf;
return gnuplotPath_;
}
// try in CWD, /usr/bin & /usr/local/bin
SEARCH_DIR(".");
SEARCH_DIR("/usr/bin");
SEARCH_DIR("/usr/local/bin");
// if this code is reached nothing was found
LATAN_ERROR(System, "cannot find gnuplot");
return "";
}
// plot parsing and output /////////////////////////////////////////////////////
void Plot::display(void)
{
int pid = fork();
if (pid == 0)
{
FILE *gnuplotPipe;
string command;
ostringstream scriptBuf;
getProgramPath();
command = gnuplotPath_ + "/" + gnuplotBin_ + " 2>/dev/null";
gnuplotPipe = popen(command.c_str(), "w");
if (!gnuplotPipe)
{
LATAN_ERROR(System, "error starting gnuplot (command was '"
+ command + "')");
}
commandBuffer_.str("");
commandBuffer_ << *this << endl;
commandBuffer_ << "pause mouse close" << endl;
fprintf(gnuplotPipe, "%s", commandBuffer_.str().c_str());
if (pclose(gnuplotPipe) == -1)
{
LATAN_ERROR(System, "problem closing communication to gnuplot");
}
exit(EXIT_SUCCESS);
}
else if (pid == -1)
{
perror("fork error");
LATAN_ERROR(System, "problem forking to the process handling gnuplot");
}
}
void Plot::save(string dirName, bool savePdf)
{
vector commandBack;
string path, terminalBack, outputBack, gpCommand, scriptName;
mode_t mode755;
ofstream script;
mode755 = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
// generate directory
if (mkdir(dirName))
{
LATAN_ERROR(Io, "impossible to create directory '" + dirName + "'");
}
// backup I/O parameters
terminalBack = options_.terminal;
outputBack = options_.output;
commandBack = plotCommand_;
// save PDF
if (savePdf)
{
options_.terminal = "pdf";
options_.output = dirName + "/plot.pdf";
display();
options_.terminal = terminalBack;
options_.output = outputBack;
}
// save script and datafiles
for (unsigned int i = 0; i < tmpFileName_.size(); ++i)
{
ofstream dataFile;
ifstream tmpFile;
string dataFileName = "points_" + strFrom(i) + ".dat";
dataFile.open(dirName + "/" + dataFileName);
tmpFile.open(tmpFileName_[i]);
dataFile << tmpFile.rdbuf();
dataFile.close();
tmpFile.close();
for (string &command: plotCommand_)
{
auto pos = command.find(tmpFileName_[i]);
while (pos != string::npos)
{
command.replace(pos, tmpFileName_[i].size(), dataFileName);
pos = command.find(tmpFileName_[i], pos + 1);
}
}
}
scriptName = dirName + "/source.plt";
script.open(scriptName);
getProgramPath();
gpCommand = gnuplotPath_ + "/" + gnuplotBin_ + " " + gnuplotArgs_;
script << "#!/usr/bin/env " << gpCommand << "\n" << endl;
script << "# script generated by " << Env::fullName << "\n" << endl;
script << *this;
script.close();
if (chmod(scriptName.c_str(), mode755))
{
LATAN_ERROR(Io, "impossible to set file '" + scriptName +
"' in mode 755");
}
plotCommand_ = commandBack;
}
ostream & Latan::operator<<(ostream &out, const Plot &plot)
{
std::string begin, end;
int x = static_cast(Axis::x), y = static_cast(Axis::y);
if (!plot.options_.terminal.empty())
{
out << "set term " << plot.options_.terminal << endl;
}
if (!plot.options_.output.empty())
{
out << "set output '" << plot.options_.output << "'" << endl;
}
if (!plot.options_.caption.empty())
{
out << "set title '" << plot.options_.caption << "'" << endl;
}
if (plot.options_.scaleMode[x] & Plot::Scale::manual)
{
out << "xMin = " << plot.options_.scale[x].min << endl;
out << "xMax = " << plot.options_.scale[x].max << endl;
}
if (plot.options_.scaleMode[y] & Plot::Scale::manual)
{
out << "yMin = " << plot.options_.scale[y].min << endl;
out << "yMax = " << plot.options_.scale[y].max << endl;
}
out << "unset xrange" << endl;
if (plot.options_.scaleMode[x] & Plot::Scale::manual)
{
out << "set xrange [xMin:xMax]" << endl;
}
else
{
out << "set xrange [:]" << endl;
}
out << "unset yrange" << endl;
if (plot.options_.scaleMode[y] & Plot::Scale::manual)
{
out << "set yrange [yMin:yMax]" << endl;
}
else
{
out << "set yrange [:]" << endl;
}
out << "unset log" << endl;
if (plot.options_.scaleMode[x] & Plot::Scale::log)
{
out << "set log x" << endl;
}
if (plot.options_.scaleMode[y] & Plot::Scale::log)
{
out << "set log y" << endl;
}
if (!plot.options_.label[x].empty())
{
out << "set xlabel '" << plot.options_.label[x] << "'" << endl;
}
if (!plot.options_.label[y].empty())
{
out << "set ylabel '" << plot.options_.label[y] << "'" << endl;
}
for (unsigned int i = 0; i < plot.options_.palette.size(); ++i)
{
out << "set linetype " << i + 1 << " lc "
<< plot.options_.palette[i] << endl;
}
for (unsigned int i = 0; i < plot.headCommand_.size(); ++i)
{
out << plot.headCommand_[i] << endl;
}
for (unsigned int i = 0; i < plot.plotCommand_.size(); ++i)
{
begin = (i == 0) ? "plot " : " ";
end = (i == plot.plotCommand_.size() - 1) ? "" : ",\\";
out << begin << plot.plotCommand_[i] << end << endl;
}
return out;
}