//**************************************************************************// //* FRIDA: fast reliable interactive data analysis *// //* dualplot.cpp: different mechanisms for screen and paper output *// //* (C) Joachim Wuttke 1990-, v2(C++) 2001- *// //* http://frida.sourceforge.net *// //**************************************************************************// #include <stdlib.h> #include <string.h> #include <iostream> #include <math.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <boost/format.hpp> #include "mystd.h" #include "plotaux.h" #include "coord.h" #include <ask_simple_value.h> #include <ask_and_callback.h> #include "dualplot.h" using boost::format; //**************************************************************************// //* CAxis *// //**************************************************************************// //! Set invalid limits. // This means: next plot call will automatically recalculate them. void CAxis::setAuto() { inf = -INFINITY; sup = +INFINITY; } //! Check and set limits. void CAxis::setLimits( string axnam, double _inf, double _sup ) { if( _inf>=_sup ) throw "Cannot set " + axnam + " plot range " + strg(_inf) + " .. " + strg(_sup); if( logflag && _inf<=0 ) throw "Cannot set log " + axnam + " plot range " + strg(_inf) + " .. " + strg(_sup); inf = _inf; sup = _sup; } //! Set lin or log, reset ranges to auto. void CAxis::setLog( bool _log ) { logflag = _log; setAuto(); } //! Parse string to set bounds. void CAxis::set_from_string( string in ) { if ( in=="*" ) { setAuto(); return; } string s1, s2; mystd::string_extract_word( in, &s1, &s2 ); if ( !mystd::any2dbl(s1,&inf) ) throw string( "invalid lower bound, expecting real number" ); if ( !mystd::any2dbl(s2,&sup) ) throw string( "invalid upper bound, expecting real number" ); } //! Replace limits by rounded values. void CAxis::setRoundedLimits( string axnam, double _inf, double _sup ) { static double relmargin = 0.05; static double digits = 2; if ( _inf==-INFINITY || _sup==+INFINITY ) throw "unexpected inf limits when rounding " + axnam + " plot range"; if( _inf>=_sup ) throw "Cannot round " + axnam + " plot range " + strg(_inf) + " .. " + strg(_sup); if( logflag && _inf<=0 ) throw "Cannot round log " + axnam + " plot range " + strg(_inf) + " .. " + strg(_sup); inf = _inf; sup = _sup; if ( inf==sup ) { if( logflag ){ inf /= 10; sup *= 10; } else { inf -= 1; sup += 1; } return; } double inf2, sup2; double mydigits = digits; if ( !logflag ) { // lin limits double margin = relmargin * (sup - inf); do { if (mydigits>=8.5) return; // do not round sup2 = mystd::round_decimal( sup+margin, mydigits ); inf2 = mystd::round_decimal( inf-margin, mydigits ); if(sup<0 && sup2>0) sup2 = 0; if(inf2<0 && inf>0) inf2 = 0; mydigits += 0.5; } while (!((inf2<inf) && (sup<sup2))); } else { // log limits double ratio = sup / inf; double margin = exp( relmargin*log(ratio) ); do { if (mydigits>=8.5) return; // do not round sup2 = mystd::round_decimal( sup*margin, mydigits ); inf2 = mystd::round_decimal( inf/margin, mydigits ); mydigits += 0.5; } while ( !((inf2<inf) && (sup<sup2)) ); } setLimits( axnam, inf2, sup2 ); } //! Have finite limits been set ? bool CAxis::finite() const { return inf!=-INFINITY && sup!=+INFINITY; } //! Is value val contained in this range? bool CAxis::contains(double val) const { return inf<=val && val<=sup; } //! Map application scale (inf..sup) to linear plot scale (0..1). double CAxis::value2plotcoord( double v ) const { if ( !finite() ) throw string( "undefined plot range" ); if ( logflag ) { if( inf<0 || v<0 ) throw string( "negative value in log range" ); return log(v/inf) / log(sup/inf); } else { return (v-inf) / (sup-inf); } } //! Map linear plot scale (0..1) to application scale (inf..sup). double CAxis::plotcoord2value( double c ) const { if ( !finite() ) throw string( "undefined plot range" ); if ( logflag ) { return inf * exp( c*log(sup/inf) ); } else { return inf + c*(sup-inf); } } //! String representation. string CAxis::str() const { if( inf==-INFINITY || sup== +INFINITY ) return "*"; else return strg(inf) + " " + strg(sup); } //! Query and check range. void CAxis::Ask( string quest ) { string def, in; // query input def = str(); in = sask( quest, def ); if (in==def) return; if (in!="") set_from_string( in ); // check new range if (logflag && inf<=0 && inf!=-INFINITY) { setAuto(); throw string( "log scale requires range above 0" ); } if (!logflag && ( inf!=-INFINITY && inf<-3e38 || sup!=+INFINITY && sup>3e38 ) ) { setAuto(); throw string( "lin scale exceeds typical PS implementation range " "(abs<3e38)" ); } } //! Describe range, for use in table of plot frames. string CAxis::info() const { string ret; ret = logflag ? "log" : "lin"; ret += " " + strg(inf); ret += " " + strg(sup); return ret; } //! Convert value -> plot_coordinate. double CAxis::pc( double v ) const { return CPLOT_PSMAX * value2plotcoord( v ); } //**************************************************************************// //* CPlot *// //**************************************************************************// //! Constructor for plot window: setup for gnuplot and postscript. CPlot::CPlot( uint _iPlot, bool _logx, bool _logy ) : iPlot( _iPlot ), maxpoints(12000), X( _logx ), Y( _logy ) { // Start gnuplot. Use input redirection so that gnuplot receives // commands from a gp_fifo which is created here. string fn_fifo = string("/tmp/gnuplot-") + getenv( "LOGNAME" ); mystd::system( "rm -f "+fn_fifo ); if (mkfifo( fn_fifo.c_str(), 0666 )) { printf("SEVERE SYSTEM ERROR cannot make fifo %s" " - will not be able to print\n", fn_fifo.c_str() ); return; } mystd::system( "gnuplot -title " + strg(iPlot) + " -noraise" + " < " + fn_fifo + " &" ); // we use open instead of fopen or ofstream, // because we need non-blocking mode. if (!(gp_fifo = open(fn_fifo.c_str(), O_WRONLY))) { printf("SEVERE SYSTEM ERROR cannot open gp_fifo" " - will not be able to print\n"); return; } fcntl(gp_fifo,F_SETFL,O_NONBLOCK); gp_write( string("set terminal x11") ); ps_fnum=0; } //! Clear plot frame and buffer. void CPlot::clearFrame() { // clear gnuplot tmp files gp_fno = 0; gp_fnames = ""; // reset buffers for postscript output string cmd; ps_accu.clear(); ps_accu.push_back( "\n%% output created by frida2\n"); ps_snum = 0; ps_pnum = 0; ps_cnum = 0; ps_Doc.clear(); } //! Change one setup parameter per command. void CPlot::setAux( string cmd ) { if ( cmd=="gnp" ) maxpoints = iask("Max # points in plot", maxpoints); else if ( cmd=="gxl" ) { X.setLog( !X.logflag ); printf( "set x %s\n", X.logflag ? "log" : "lin" ); } else if ( cmd=="gxl+" ) X.setLog( true ); else if ( cmd=="gxl-" ) X.setLog( false ); else if ( cmd=="gyl" ) { Y.setLog( !Y.logflag ); printf( "set y %s\n", Y.logflag ? "log" : "lin" ); } else if ( cmd=="gyl+" ) Y.setLog( true ); else if ( cmd=="gyl-" ) Y.setLog( false ); else if ( cmd=="gxf" ) { X.force = !X.force; printf( "force x %s\n", X.force ? "on" : "off" ); } else if ( cmd=="gxf+" ) X.force = true; else if ( cmd=="gxf-" ) X.force = false; else if ( cmd=="gyf" ) { Y.force = !Y.force; printf( "force y %s\n", Y.force ? "on" : "off" ); } else if ( cmd=="gyf+" ) Y.force = true; else if ( cmd=="gyf-" ) Y.force = false; else throw "unknown command " + cmd; } //! Plot coordinate frame (axes, ticks, labels). void CPlot::plotFrame() { gp_write( "set nologscale" ); string whichlog=""; if (X.logflag) whichlog += "x"; if (Y.logflag) whichlog += "y"; if (whichlog!="") gp_write( "set logscale " + whichlog ); // wups: snprintf( outlin, CPLOT_LINSIZ, "\n%d %g %g xSetCoord\n", X.logflag, X.inf, X.sup ); ps_accu.push_back( outlin ); snprintf( outlin, CPLOT_LINSIZ, "%d %g %g ySetCoord\n", Y.logflag, Y.inf, Y.sup ); ps_accu.push_back( outlin ); snprintf( outlin, CPLOT_LINSIZ, "%% %d %g %g %d zSetCoord\n\n", 0, 0., 0., 0 ); ps_accu.push_back( outlin ); int ntack, ntpt; double *tack, ticklim[2]; ps_accu.push_back( "\n/xPlotFrame {" ); if ( X.logflag && X.inf<= 0 ) throw "BUG: x log incompatible with limits " + X.str(); plotaux::calc_ticks( X.logflag, X.inf, X.sup, &ntack, &tack, &ntpt, ticklim ); ps_ticktack(ntack, tack, ntpt, ticklim, &X); free(tack); snprintf( outlin, CPLOT_LINSIZ-4, " {(%s", X.C.ps_str().c_str() ); strncat( outlin, ")}\n", CPLOT_LINSIZ ); ps_accu.push_back( outlin ); ps_accu.push_back( " 0 10 0 0 0 90 " "OneAxx Axx Tic xTacL xNumL %% low x axis\n" ); ps_accu.push_back( " 0 10 0 10 0 270 " "OneAxx Axx Tic xTacH %% top x axis\n" ); ps_accu.push_back( " xCL\n" ); ps_accu.push_back( "} def\n" ); ps_accu.push_back( "\n/yPlotFrame {" ); if ( Y.logflag && Y.inf<= 0 ) throw "BUG: y log incompatible with limits " + Y.str(); plotaux::calc_ticks( Y.logflag, Y.inf, Y.sup, &ntack, &tack, &ntpt, ticklim ); ps_ticktack(ntack, tack, ntpt, ticklim, &Y); free(tack); snprintf( outlin, CPLOT_LINSIZ, " {(%s", Y.C.ps_str().c_str() ); strncat( outlin, ")}\n", CPLOT_LINSIZ ); ps_accu.push_back( outlin ); ps_accu.push_back( " 0 10 0 0 90 0 " "OneAxx Axx Tic yTacL yNumL %% left y axis\n" ); ps_accu.push_back( " 0 10 10 0 90 180 " "OneAxx Axx Tic yTacH % yNumH %% right yaxis\n" ); ps_accu.push_back( " yCL\n" ); ps_accu.push_back( "} def\n" ); ps_accu.push_back( "\n%% modeDD\nplotbefore\n" ); } //! Plot one scan. void CPlot::addScan( bool as_line, int style_no, const vector<double>& xp, const vector<double>& yp, const vector<double>& dyp, const vector<double>& z, string xco, string yco, string info ) { // Checks: uint np=xp.size(); if ( np<0 ) throw string( "PROG ERR NPLot::Line x.size=0" ); if ( np!=yp.size() ) throw string( "PROG ERR NPLot::Line x.size<>y.size" ); // Prepare for live display, to be shown by showScans(): string gp_fnam = str( format( "/tmp/%s-%d-%03d.gnu" ) % getenv("LOGNAME") % iPlot % gp_fno++ ); if (gp_fnames!="") gp_fnames += ", "; gp_fnames += string("\"") + gp_fnam + "\" notitle"; if ( as_line ) gp_fnames += " with lines"; else if( dyp.size() ) gp_fnames += " with errorbars"; FILE *gp_fd; if (!(gp_fd = fopen(gp_fnam.c_str(), "w"))) throw "cannot save gnuplot data to " + gp_fnam; uint nout = 0; for (uint i=0; i<np; i++){ if( xp[i]<X.inf || xp[i]>X.sup ) throw "Plot::Line: x["+strg(i)+"]="+strg(xp[i])+" out of range"; if( yp[i]<Y.inf || yp[i]>Y.sup ) throw "Plot::Line: y["+strg(i)+"]="+strg(yp[i])+" out of range"; if( dyp.size() ) fprintf(gp_fd, "%16.8g %16.8g %16.8g\n", xp[i], yp[i], dyp[i] ); else fprintf(gp_fd, "%16.8g %16.8g\n", xp[i], yp[i]); nout++; } fclose(gp_fd); if( !nout ){ cout << "no points in frame: " << info << "\n"; return; } // Postscript copy: snprintf( outlin, CPLOT_LINSIZ, "\n%3u [", ++ps_snum ); ps_accu.push_back( outlin ); for (uint i=0; i<z.size(); i++){ snprintf( outlin, CPLOT_LINSIZ, " %12g", z[i]); ps_accu.push_back( outlin ); } snprintf( outlin, CPLOT_LINSIZ, " ] zValues\n" ); ps_accu.push_back( outlin ); if ( as_line ) snprintf( outlin, CPLOT_LINSIZ, "%2d cstyle", 1 ); else snprintf( outlin, CPLOT_LINSIZ, "%2d pstyle", style_no+1 ); ps_accu.push_back( outlin ); snprintf( outlin, CPLOT_LINSIZ-2, " %% (%s -> %s)", xco.c_str(), yco.c_str() ); strncat( outlin, "\n", CPLOT_LINSIZ ); ps_accu.push_back( outlin ); for (uint i=0; i<np; i++) { snprintf( outlin, CPLOT_LINSIZ, "%7.3f%7.3f%7.3f t%c %% %14.7g wx %14.7g wy\n", X.pc(xp[i]), Y.pc(yp[i]), dyp.size() ? Y.pc(dyp[i]) : 0, i==0 ? 'i' : i==np-1 ? 'f' : ' ', xp[i], yp[i] ); ps_accu.push_back( outlin ); } } //! Live display as prepared by addScan. void CPlot::showScans() { if ( gp_fnames!="" ) gp_write( "plot " + str( format( "[%12.8g:%12.8g] [%12.8g:%12.8g] " ) % X.inf % X.sup % Y.inf % Y.sup ) + gp_fnames ); } //! Add documentation lines to postscript output. void CPlot::plotDoclines( const vector<string>& lDoc ) { for (uint i=0; i<lDoc.size(); i++) ps_Doc.push_back(lDoc[i]); } //! Write buffered plot to postscript file. void CPlot::writePostscript( bool full_outfile ) { string ps_outdir, ps_head, ps_dict; // read configuration parameters: ps_outdir = NRead::wofmac( "\\psdir" ); ps_head = NRead::wofmac( "\\pshead" ); if ( full_outfile ) ps_dict = NRead::wofmac( "\\psdict" ); // construct output file name: FILE *pssav; string flong, cmd, outf; while(1) { if (ps_fnum>=999) throw string( "graph file number overflow" ); outf = ps_outdir + str( format( "l%d." ) % ++ps_fnum ) + ( full_outfile ? "ps" : "psa" ); if (!(pssav = mystd::glob_fopen(outf.c_str(), "", "", "r"))) break; // legal exit fclose(pssav); } cout << "save plot in " << outf << "\n"; // copy headers to output file: cmd = string("cat ") + ( full_outfile ? ps_dict : "" ) + " " + ps_head + " > " + outf + "\n"; mystd::system( cmd ); // a redundant check of existence of the outfile: if (!(pssav = mystd::glob_fopen(outf.c_str(), "", "", "r", &flong))) { throw "have not created file " + outf; return; } fclose(pssav); // append specific output to output file: if (!(pssav = fopen(flong.c_str(), "a+"))) { printf ("cannot write (append) to file %s\n", flong.c_str()); return; } for( uint i=0; i<ps_accu.size(); ++i ){ // fprintf does not work here because output line may contain "%" fwrite( ps_accu[i].c_str(), 1, ps_accu[i].size(), pssav ); } // additional output (do not append this to ps_accu to allow // further incrementation of ps_accu): fprintf(pssav, "\nblack 0 -4 13 newlist\n"); for (uint i=0; i<ps_Doc.size(); i++) fprintf(pssav, "{(%s)} infline\n", ps_Doc[i].c_str()); fprintf(pssav, "\n{(%s)} /filename exch def 10 -2.8 18 showfilename\n\n" "EndFrame\n", outf.c_str() ); // output completed: fclose(pssav); } //! Endless loop: query for gnuplot commands. void CPlot::feedGnuplot() { string cmd; cout << "entering mygnuplot - to leave, type q\n"; while(1) { cmd = sask("mygnuplot> "); if (cmd.substr(0,1)=="q") return; gp_write(cmd); } } //! Info line to characterize this plot window. string CPlot::info() const { string ret; ret = "x: " + X.info(); ret += " y: " + Y.info(); return ret; } //! Send one line to gnuplot fifo. void CPlot::gp_write( string in ) { string out = in + "\n"; // printf( "DEBUG GNUPLOT '%s'\n", out.c_str() ); if( write( gp_fifo, out.c_str(), out.size() ) <= 0 ) throw string( "could not write to gp_fifo" ); } //! Format ticks and tacks for postscript file. void CPlot::ps_ticktack(uint ntack, double *tack, int ntpt, double *ticklim, CAxis *A) { uint i; ps_accu.push_back( "[\n" ); if (A->logflag && ( tack[0]<1e-3 || tack[ntack-1]>1e3 )) { for (i=0; i<ntack; i++) { snprintf( outlin, CPLOT_LINSIZ, " %9.6f {(10)(%d)sp()} %%{(%g)}\n", A->pc(tack[i]), (int)(log10(tack[i])), (float) tack[i]); ps_accu.push_back( outlin ); } } else { for (i=0; i<ntack; i++) { snprintf( outlin, CPLOT_LINSIZ, " %9.6f {(%g)}\n", A->pc(tack[i]), (float) tack[i]); ps_accu.push_back( outlin ); } } ps_accu.push_back( " ] SetTacVec\n" ); snprintf( outlin, CPLOT_LINSIZ, " %g %g %d %d SetTicVec%s\n", A->pc(ticklim[0]), A->pc(ticklim[1]), ntack+2, ntpt, (A->logflag? "Log" : "Lin")); ps_accu.push_back( outlin ); }