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
|