LCOV - code coverage report
Current view: top level - source/math/plot - gnuPlot.cpp (source / functions) Coverage Total Hit
Test: mxlib Lines: 0.0 % 324 0
Test Date: 2026-08-30 22:23:37 Functions: 0.0 % 45 0

            Line data    Source code
       1              : /** \file gnuPlot.cpp
       2              :  * \author Jared R. Males (jaredmales@gmail.com)
       3              :  * \brief Implementation of an interface to the gnuplot program
       4              :  * \ingroup plotting_files
       5              :  *
       6              :  */
       7              : 
       8              : //***********************************************************************//
       9              : // Copyright 2020-2023 Jared R. Males (jaredmales@gmail.com)
      10              : //
      11              : // This file is part of mxlib.
      12              : //
      13              : // mxlib is free software: you can redistribute it and/or modify
      14              : // it under the terms of the GNU General Public License as published by
      15              : // the Free Software Foundation, either version 3 of the License, or
      16              : // (at your option) any later version.
      17              : //
      18              : // mxlib is distributed in the hope that it will be useful,
      19              : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      20              : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      21              : // GNU General Public License for more details.
      22              : //
      23              : // You should have received a copy of the GNU General Public License
      24              : // along with mxlib.  If not, see <http://www.gnu.org/licenses/>.
      25              : //***********************************************************************//
      26              : 
      27              : #include "math/plot/gnuPlot.hpp"
      28              : 
      29              : namespace mx
      30              : {
      31              : namespace math
      32              : {
      33              : 
      34            0 : gnuPlot::gnuPlot()
      35              : {
      36            0 :     init();
      37            0 : }
      38              : 
      39            0 : void gnuPlot::init()
      40              : {
      41            0 :     _errLocation = "/dev/shm";
      42            0 :     _tempLocation = "/dev/shm";
      43            0 : }
      44              : 
      45            0 : gnuPlot::~gnuPlot()
      46              : {
      47            0 :     command( "exit", true );
      48              : 
      49            0 :     if( _pipeH )
      50              :     {
      51            0 :         pclose( _pipeH );
      52              :     }
      53              : 
      54            0 :     if( _errFD > 0 )
      55              :     {
      56            0 :         close( _errFD );
      57              :     }
      58              : 
      59            0 :     if( _errFName.length() > 0 )
      60              :     {
      61            0 :         remove( _errFName.c_str() );
      62              :     }
      63              : 
      64            0 :     for( size_t i = 0; i < _tempFiles.size(); ++i )
      65              :     {
      66            0 :         remove( _tempFiles[i].c_str() );
      67              :     }
      68            0 : }
      69              : 
      70            0 : int gnuPlot::connect()
      71              : {
      72              : 
      73            0 :     if( _connected )
      74            0 :         return 0;
      75              : 
      76            0 :     srand( (unsigned long)this ); // We don't need random, just different from all other instances in this process
      77              : 
      78              :     char tmpStr[MX_GP_FNAME_SZ];
      79              : 
      80            0 :     snprintf( tmpStr, MX_GP_FNAME_SZ, "/gperr%d_%d", getpid(), rand() );
      81              : 
      82            0 :     _errFName = _errLocation + tmpStr;
      83              : 
      84            0 :     std::string comm = "gnuplot -persist 2>";
      85            0 :     comm += _errFName;
      86              : 
      87            0 :     errno = 0;
      88            0 :     _pipeH = popen( comm.c_str(), "w" );
      89              : 
      90            0 :     if( _pipeH == NULL )
      91              :     {
      92            0 :         if( errno )
      93              :         {
      94            0 :             internal::mxlib_error_report<verboseT>( errno2error_t( errno ),
      95            0 :                                                     "Error starting the gnuplot program with popen." );
      96              :             // mxPError( "gnuPlot", errno, "Error starting the gnuplot program with popen." );
      97              :         }
      98              :         else
      99              :         {
     100            0 :             internal::mxlib_error_report<verboseT>( error_t::procerr,
     101            0 :                                                     "Error starting the gnuplot program with popen." );
     102              :             // mxError( "gnuPlot", MXE_PROCERR, "Error starting the gnuplot program with popen." );
     103              :         }
     104              : 
     105            0 :         return -1;
     106              :     }
     107              : 
     108            0 :     usleep( MX_GP_FC_TIME ); // allow file creation to finish before opening stderr file
     109              : 
     110            0 :     errno = 0;
     111            0 :     _errFD = open( _errFName.c_str(), O_RDONLY );
     112              : 
     113            0 :     int n = 0;
     114            0 :     while( _errFD <= 0 && n < MX_GP_FC_RETRIES )
     115              :     {
     116              :         // First we try again after sleeping again.
     117            0 :         usleep( MX_GP_FC_TIME ); // allow file creation to finish before opening stderr file
     118            0 :         _errFD = open( _errFName.c_str(), O_RDONLY );
     119            0 :         ++n;
     120              :     }
     121              : 
     122            0 :     if( _errFD <= 0 )
     123              :     {
     124            0 :         internal::mxlib_error_report<verboseT>( errno2error_t( errno ), "gnuPlot failed to open stderr file" );
     125              :         //        mxPError( "gnuPlot", errno, "gnuPlot failed to open stderr file: " );
     126            0 :         return -1;
     127              :     }
     128              : 
     129            0 :     _connected = 1;
     130              : 
     131            0 :     return 0;
     132            0 : }
     133              : 
     134            0 : bool gnuPlot::gpError()
     135              : {
     136            0 :     return _gpError;
     137              : }
     138              : 
     139            0 : std::string gnuPlot::gpErrorMsg()
     140              : {
     141            0 :     return _gpErrorMsg;
     142              : }
     143              : 
     144            0 : int gnuPlot::command( const std::string &com, bool flush )
     145              : {
     146              : 
     147            0 :     if( !_connected )
     148            0 :         connect();
     149              : 
     150            0 :     fprintf( _pipeH, "%s", ( com + "\n" ).c_str() );
     151            0 :     if( flush )
     152            0 :         fflush( _pipeH );
     153              : 
     154            0 :     return 0;
     155              : }
     156              : 
     157            0 : int gnuPlot::checkResponse( std::string &response, double timeout )
     158              : {
     159            0 :     _gpError = false;
     160            0 :     if( _errFD )
     161              :     {
     162              :         char errstr[1024];
     163            0 :         int rv = 0;
     164              : 
     165            0 :         double t0 = sys::get_curr_time();
     166              : 
     167            0 :         errno = 0;
     168            0 :         rv = read( _errFD, errstr, 1024 );
     169              : 
     170            0 :         while( rv == 0 && sys::get_curr_time() - t0 <= timeout )
     171              :         {
     172            0 :             usleep( 10 ); // Give up the thread
     173            0 :             rv = read( _errFD, errstr, 1024 );
     174              :         }
     175              : 
     176            0 :         if( rv < 0 )
     177              :         {
     178            0 :             internal::mxlib_error_report<verboseT>(errno2error_t(errno), "Occurred while reading gnuplot stderr" );
     179              :             //mxPError( "gnuPlot", errno, "Occurred while reading gnuplot stderr" );
     180              : 
     181            0 :             response = "";
     182            0 :             return -1;
     183              :         }
     184              : 
     185            0 :         if( rv == 0 )
     186              :         {
     187            0 :             internal::mxlib_error_report<verboseT>(error_t::timeout, "Timed out while reading from gnuplot stderr" );
     188              :             //mxError( "gnuPlot", MXE_TIMEOUT, "Timed out while reading from gnuplot stderr" );
     189            0 :             response = "";
     190            0 :             return 0;
     191              :         }
     192              : 
     193            0 :         errstr[rv] = '\0';
     194              : 
     195            0 :         response = errstr;
     196              : 
     197            0 :         bool done = false;
     198            0 :         if( response.length() > 1 )
     199              :         {
     200            0 :             if( response[response.length() - 1] == '\n' && response[response.length() - 2] == '\n' )
     201            0 :                 done = true;
     202              :         }
     203              : 
     204            0 :         while( !done && sys::get_curr_time() - t0 < timeout )
     205              :         {
     206            0 :             usleep( 10 ); // Give up the thread
     207            0 :             rv = read( _errFD, errstr, 1024 );
     208              : 
     209            0 :             if( rv > 0 )
     210              :             {
     211            0 :                 errstr[rv] = '\0';
     212            0 :                 response += errstr;
     213              : 
     214            0 :                 if( response.length() > 1 )
     215              :                 {
     216            0 :                     if( response[response.length() - 1] == '\n' && response[response.length() - 2] == '\n' )
     217            0 :                         done = true;
     218              :                 }
     219              :             }
     220              :         }
     221              : 
     222            0 :         size_t first = 0;
     223            0 :         while( isspace( response[first] ) && first < response.length() - 1 )
     224            0 :             ++first;
     225            0 :         response.erase( 0, first );
     226              : 
     227            0 :         int last = response.length() - 1;
     228            0 :         while( response[last] == '\n' && last > 0 )
     229            0 :             --last;
     230            0 :         if( response[last] != '\n' )
     231            0 :             ++last;
     232              : 
     233            0 :         response.erase( last );
     234              : 
     235            0 :         if( response.length() > 8 )
     236              :         {
     237            0 :             if( response.substr( 0, 8 ) == "gnuplot>" )
     238              :             {
     239            0 :                 _gpError = true;
     240            0 :                 _gpErrorMsg = response;
     241              : 
     242            0 :                 internal::mxlib_error_report<verboseT>(error_t::gnuploterr, "gnuplot says: " + response);
     243              :                 //mxError( "gnuPlot", MXE_GNUPLOTERR, "gnuplot says:\n" + response );
     244              : 
     245            0 :                 response = "";
     246            0 :                 return -1;
     247              :             }
     248              :         }
     249              : 
     250            0 :         return 0;
     251              :     }
     252              : 
     253              :     // If errFD is not open for some reason
     254            0 :     std::cerr << "gnuplot stderr is not open\n";
     255              : 
     256            0 :     return 1;
     257              : }
     258              : 
     259            0 : std::string gnuPlot::getResponse( const std::string &com, double timeout )
     260              : {
     261            0 :     std::string response;
     262              : 
     263            0 :     if( command( com, true ) < 0 )
     264            0 :         return response;
     265              : 
     266            0 :     checkResponse( response, timeout );
     267              : 
     268            0 :     return response;
     269            0 : }
     270              : 
     271            0 : int gnuPlot::replot()
     272              : {
     273            0 :     return doPlotCommand();
     274              : }
     275              : 
     276            0 : int gnuPlot::xrange( double x0, double x1 )
     277              : {
     278            0 :     std::ostringstream s;
     279            0 :     s << "set xrange [";
     280            0 :     s << x0 << ":" << x1 << "]";
     281              : 
     282            0 :     return command( s.str() );
     283            0 : }
     284              : 
     285            0 : int gnuPlot::xrange()
     286              : {
     287            0 :     return command( "unset xrange" );
     288              : }
     289              : 
     290            0 : int gnuPlot::yrange( double y0, double y1 )
     291              : {
     292            0 :     std::ostringstream s;
     293            0 :     s << "set yrange [";
     294            0 :     s << y0 << ":" << y1 << "]";
     295              : 
     296            0 :     return command( s.str() );
     297            0 : }
     298              : 
     299            0 : int gnuPlot::yrange()
     300              : {
     301            0 :     return command( "unset yrange" );
     302              : }
     303              : 
     304            0 : int gnuPlot::logy()
     305              : {
     306            0 :     return command( "set logscale y" );
     307              : }
     308              : 
     309            0 : int gnuPlot::logx()
     310              : {
     311            0 :     return command( "set logscale x" );
     312              : }
     313              : 
     314            0 : int gnuPlot::logxy()
     315              : {
     316              :     int rv;
     317            0 :     rv = logx();
     318            0 :     if( rv != 0 )
     319            0 :         return rv;
     320              : 
     321            0 :     return logy();
     322              : }
     323              : 
     324            0 : int gnuPlot::ulogy()
     325              : {
     326            0 :     return command( "unset logscale y" );
     327              : }
     328              : 
     329            0 : int gnuPlot::ulogx()
     330              : {
     331            0 :     return command( "unset logscale x" );
     332              : }
     333              : 
     334            0 : int gnuPlot::ulogxy()
     335              : {
     336              :     int rv;
     337            0 :     rv = ulogx();
     338            0 :     if( rv != 0 )
     339            0 :         return rv;
     340              : 
     341            0 :     return ulogy();
     342              : }
     343              : 
     344            0 : int gnuPlot::plot( const std::string &fname,
     345              :                    const std::string &modifiers,
     346              :                    const std::string &title,
     347              :                    const std::string &name )
     348              : {
     349            0 :     std::string nname;
     350            0 :     if( name == "" )
     351              :     {
     352            0 :         nname = "curve" + std::to_string( m_curveMap.size() );
     353              :     }
     354              :     else
     355              :     {
     356            0 :         nname = name;
     357              :     }
     358              : 
     359            0 :     if( m_curveMap.count( nname ) > 0 )
     360              :     {
     361            0 :         m_curveMap[nname].m_file = fname;
     362            0 :         m_curveMap[nname].m_modifiers = modifiers;
     363            0 :         m_curveMap[nname].m_title = title;
     364            0 :         m_curveMap[nname].m_binary = "";
     365              :     }
     366              :     else
     367              :     {
     368            0 :         gpCurve gpc( fname, "", modifiers );
     369            0 :         m_curveMap.insert( { nname, gpc } );
     370            0 :     }
     371              : 
     372            0 :     return doPlotCommand();
     373            0 : }
     374              : 
     375            0 : int gnuPlot::plot( const std::string &fname, const std::string &modifiers, const std::string &name )
     376              : {
     377            0 :     return plot( fname, modifiers, "", name );
     378              : }
     379              : 
     380            0 : int gnuPlot::plot( const std::string &fname, const std::string &modifiers )
     381              : {
     382            0 :     return plot( fname, modifiers, "", "" );
     383              : }
     384              : 
     385            0 : int gnuPlot::plot( const std::string &fname )
     386              : {
     387            0 :     return plot( fname, "", "", "" );
     388              : }
     389              : 
     390            0 : int gnuPlot::circle(
     391              :     double xcen, double ycen, double radius, const std::string &modifiers, const std::string &title, int npoints )
     392              : {
     393            0 :     int halfCirc = ( 0.5 * 2.0 * math::pi<double>() * radius * npoints + 0.5 );
     394              : 
     395            0 :     std::vector<double> xpts( 2 * halfCirc ), ypts( 2 * halfCirc );
     396              : 
     397            0 :     for( int i = 0; i < halfCirc; ++i )
     398              :     {
     399            0 :         double x = radius * cos( ( (double)i ) / ( (double)halfCirc ) * math::pi<double>() );
     400              : 
     401            0 :         double y = sqrt( radius * radius - x * x );
     402              : 
     403            0 :         xpts[i] = xcen + x;
     404            0 :         ypts[i] = ycen + y;
     405              : 
     406            0 :         xpts[halfCirc + i] = xcen - x;
     407            0 :         ypts[halfCirc + i] = ycen - y;
     408              :     }
     409              : 
     410            0 :     return plot( xpts, ypts, modifiers, title );
     411            0 : }
     412              : 
     413            0 : int gnuPlot::circle( double radius, const std::string &modifiers, const std::string &title, int npoints )
     414              : {
     415            0 :     return circle( 0.0, 0.0, radius, modifiers, title, npoints );
     416              : }
     417              : 
     418            0 : void gnuPlot::clear()
     419              : {
     420            0 :     m_curveMap.clear();
     421            0 : }
     422            0 : void gnuPlot::reset()
     423              : {
     424            0 :     m_curveMap.clear();
     425            0 :     command( "reset" );
     426            0 : }
     427            0 : void gnuPlot::listCurves()
     428              : {
     429            0 :     for( auto it = m_curveMap.begin(); it != m_curveMap.end(); ++it )
     430              :     {
     431            0 :         std::cout << it->first << ": " << it->second.m_file << " " << it->second.m_modifiers << " "
     432            0 :                   << it->second.m_title << "\n";
     433              :     }
     434            0 : }
     435            0 : void gnuPlot::modifiers( const std::string &name, const std::string &modifiers )
     436              : {
     437            0 :     if( m_curveMap.count( name ) == 0 )
     438            0 :         return;
     439            0 :     m_curveMap[name].m_modifiers = modifiers;
     440              : }
     441            0 : void gnuPlot::title( const std::string &name, const std::string &title )
     442              : {
     443            0 :     if( m_curveMap.count( name ) == 0 )
     444            0 :         return;
     445            0 :     m_curveMap[name].m_title = title;
     446              : }
     447              : 
     448            0 : int gnuPlot::plotImpl( const void *y,
     449              :                        size_t Nbytes,
     450              :                        const std::string &binary,
     451              :                        const std::string &modifiers,
     452              :                        const std::string &title,
     453              :                        const std::string &name )
     454              : {
     455              :     FILE *fout;
     456              :     char temp[MX_GP_TEMP_SZ];
     457              : 
     458            0 :     std::string fname;
     459              : 
     460            0 :     std::string nname;
     461            0 :     if( m_curveMap.count( name ) > 0 )
     462              :     {
     463            0 :         nname = name;
     464              : 
     465            0 :         fname = m_curveMap[name].m_file;
     466            0 :         m_curveMap[name].m_title = title;
     467            0 :         m_curveMap[name].m_modifiers = modifiers;
     468              : 
     469            0 :         fout = fopen( fname.c_str(), "wb" );
     470            0 :         if( fout == 0 )
     471            0 :             return -1;
     472              :     }
     473              :     else
     474              :     {
     475            0 :         fout = openTempFile( temp );
     476            0 :         if( fout == 0 )
     477            0 :             return -1;
     478              : 
     479            0 :         fname = temp;
     480              : 
     481            0 :         if( name == "" )
     482            0 :             nname = "curve" + std::to_string( m_curveMap.size() );
     483              :         else
     484            0 :             nname = name;
     485              : 
     486            0 :         gpCurve gpc( fname, title, modifiers );
     487            0 :         m_curveMap.insert( { nname, gpc } );
     488            0 :     }
     489              : 
     490            0 :     int rv = fwrite( y, 1, Nbytes, fout );
     491            0 :     if( rv != Nbytes )
     492              :     {
     493            0 :         std::cerr << "Error writing to temporary file\n";
     494            0 :         perror( "gnuPlot: " );
     495            0 :         return -1;
     496              :     }
     497            0 :     fflush( fout );
     498            0 :     fclose( fout );
     499              : 
     500            0 :     m_curveMap[nname].m_binary = "binary format=\"";
     501            0 :     m_curveMap[nname].m_binary += binary + "\" u 1 ";
     502              : 
     503            0 :     return doPlotCommand();
     504            0 : }
     505              : 
     506            0 : int gnuPlot::plotImpl( const void *x,
     507              :                        const void *y,
     508              :                        size_t Npts,
     509              :                        size_t sizex,
     510              :                        size_t sizey,
     511              :                        const std::string &binaryx,
     512              :                        const std::string &binaryy,
     513              :                        const std::string &modifiers,
     514              :                        const std::string &title,
     515              :                        const std::string &name )
     516              : {
     517              :     FILE *fout;
     518              :     char temp[MX_GP_TEMP_SZ];
     519              : 
     520            0 :     std::string fname;
     521              : 
     522            0 :     std::string nname;
     523              : 
     524            0 :     if( m_curveMap.count( name ) > 0 )
     525              :     {
     526            0 :         nname = name;
     527            0 :         fname = m_curveMap[name].m_file;
     528            0 :         m_curveMap[name].m_title = title;
     529            0 :         m_curveMap[name].m_modifiers = modifiers;
     530              : 
     531            0 :         fout = fopen( fname.c_str(), "wb" );
     532            0 :         if( fout == 0 )
     533            0 :             return -1;
     534              :     }
     535              :     else
     536              :     {
     537            0 :         fout = openTempFile( temp );
     538            0 :         if( fout == 0 )
     539            0 :             return -1;
     540              : 
     541            0 :         fname = temp;
     542              : 
     543            0 :         if( name == "" )
     544            0 :             nname = "curve" + std::to_string( m_curveMap.size() );
     545              :         else
     546            0 :             nname = name;
     547              : 
     548            0 :         gpCurve gpc( fname, title, modifiers );
     549            0 :         m_curveMap.insert( { nname, gpc } );
     550            0 :     }
     551              : 
     552            0 :     for( int i = 0; i < Npts; ++i )
     553              :     {
     554            0 :         int rv = fwrite( (char *)x + i * sizex, sizex, 1, fout );
     555            0 :         rv += fwrite( (char *)y + i * sizey, sizey, 1, fout );
     556              : 
     557            0 :         if( rv != 2 )
     558              :         {
     559            0 :             std::cerr << "Error writing to temporary file\n";
     560            0 :             perror( "gnuPlot: " );
     561            0 :             return -1;
     562              :         }
     563              :     }
     564            0 :     fflush( fout );
     565            0 :     fclose( fout );
     566              : 
     567            0 :     std::string com;
     568              : 
     569            0 :     m_curveMap[nname].m_binary = "binary format=\"";
     570            0 :     m_curveMap[nname].m_binary += binaryx + binaryy + "\" u 1:2 ";
     571              : 
     572            0 :     return doPlotCommand();
     573            0 : }
     574              : 
     575            0 : int gnuPlot::doPlotCommand()
     576              : {
     577            0 :     std::string com = "plot ";
     578              : 
     579            0 :     int n = 0;
     580            0 :     for( auto it = m_curveMap.begin(); it != m_curveMap.end(); ++it )
     581              :     {
     582            0 :         com += "'" + it->second.m_file + "'";
     583            0 :         com += " " + it->second.m_binary;
     584            0 :         com += " " + it->second.m_modifiers;
     585            0 :         com += " t '" + it->second.m_title + "'";
     586            0 :         if( n < m_curveMap.size() - 1 )
     587            0 :             com += ",";
     588            0 :         ++n;
     589              :     }
     590              : 
     591            0 :     return command( com, true );
     592            0 : }
     593              : 
     594            0 : FILE *gnuPlot::openTempFile( char *temp )
     595              : {
     596              :     FILE *fout;
     597              : 
     598            0 :     snprintf( temp, MX_GP_TEMP_SZ, "%s/gpplot_%d_XXXXXX", _tempLocation.c_str(), getpid() );
     599            0 :     int rv = mkstemp( temp );
     600            0 :     if( rv < 0 )
     601            0 :         return 0;
     602              : 
     603            0 :     close( rv );
     604              : 
     605            0 :     fout = fopen( temp, "wb" );
     606              : 
     607            0 :     if( fout == NULL )
     608              :     {
     609            0 :         std::cerr << "Could not open tempoary file (" << temp << ") for writing\n";
     610            0 :         perror( "gnuPlot: " );
     611            0 :         return 0;
     612              :     }
     613              : 
     614            0 :     _tempFiles.push_back( temp );
     615              : 
     616            0 :     return fout;
     617              : } // FILE * gnuPlot::openTempFile(char * temp)
     618              : 
     619              : template <>
     620            0 : std::string gpBinaryFormat<char>()
     621              : {
     622            0 :     return "%char";
     623              : }
     624              : 
     625              : template <>
     626            0 : std::string gpBinaryFormat<unsigned char>()
     627              : {
     628            0 :     return "%uchar";
     629              : }
     630              : 
     631              : template <>
     632            0 : std::string gpBinaryFormat<short>()
     633              : {
     634            0 :     return "%short";
     635              : }
     636              : 
     637              : template <>
     638            0 : std::string gpBinaryFormat<unsigned short>()
     639              : {
     640            0 :     return "%ushort";
     641              : }
     642              : 
     643              : template <>
     644            0 : std::string gpBinaryFormat<int>()
     645              : {
     646            0 :     return "%int";
     647              : }
     648              : 
     649              : template <>
     650            0 : std::string gpBinaryFormat<unsigned int>()
     651              : {
     652            0 :     return "%uint";
     653              : }
     654              : 
     655              : template <>
     656            0 : std::string gpBinaryFormat<long>()
     657              : {
     658            0 :     return "%long";
     659              : }
     660              : 
     661              : template <>
     662            0 : std::string gpBinaryFormat<unsigned long>()
     663              : {
     664            0 :     return "%ulong";
     665              : }
     666              : 
     667              : template <>
     668            0 : std::string gpBinaryFormat<float>()
     669              : {
     670            0 :     return "%float";
     671              : }
     672              : 
     673              : template <>
     674            0 : std::string gpBinaryFormat<double>()
     675              : {
     676            0 :     return "%double";
     677              : }
     678              : 
     679              : } // namespace math
     680              : } // namespace mx
        

Generated by: LCOV version 2.0-1