Skip to content
Snippets Groups Projects
dualplot.cpp 16.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • //**************************************************************************//
    //* 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 "mystd.h"
    #include "plotaux.h"
    #include "coord.h"
    #include "asi.h"
    
    #include "gar.h"
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
    //**************************************************************************//
    
    //*  CAxis                                                                 *//
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
    //**************************************************************************//
    
    
    //! 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( double _inf, double _sup )
    
        if( logflag && _inf<=0 || _inf>=_sup )
            throw string( "Got invalid plot range limits" );
        inf = _inf;
        sup = _sup;
    }
    
    //! Set lin or log, reset ranges to auto.
    
    void CAxis::setLog( const bool _log )
    {
        logflag = _log;
        setAuto();
    }
    
    //! Parse string to set bounds.
    
    
    void CAxis::set_from_string( const string& in )
    
            setAuto();
    
        string s1, s2;
        mystd::string_extract_word( in, &s1, &s2 );
    
        if ( !mystd::any2dbl(s1,&inf) )
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
            throw string( "invalid lower bound, expecting real number" );
    
        if ( !mystd::any2dbl(s2,&sup) )
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
            throw string( "invalid upper bound, expecting real number" );
    
    //! Replace limits by rounded values.
    
    
    void CAxis::round()
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
    {
    
        static double relmargin = 0.05;
        static double digits = 2;
    
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
        if        ( inf>sup )
            throw string( "BUG: Plot/Range/Round: inf>sup" );
    
        if        (inf==sup) {
    
            if( logflag ){
                inf /= 10;
                sup *= 10;
            } else {
                inf -= 1;
                sup += 1;
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
            }
    
        } 
    
        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)) );
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
        }
    
        setLimits( 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 ) {
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
            if( inf<0 || v<0 )
    
                throw string( "negative value in log range" );
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
            return log(v/inf) / log(sup/inf);
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
        } 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 "*";
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
        else
    
            return strg(inf) + " " + strg(sup);
    
    //! Query and check range.
    
    
    void CAxis::Ask( const string& quest )
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        string def, in;
        // query input
    
        def = str();
    
        in = sask( quest, def );
        if        (in==def)
            return;
        if (in!="")
    
            set_from_string( in );
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        // 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()
    {
        string ret;
    
        ret  =   logflag ? "log" : "lin";
        ret += " " + strg(inf);
        ret += " " + strg(sup);
    
    //! Convert value -> plot_coordinate.
    
    double CAxis::pc(double v) const
    
        return CPLOT_PSMAX * value2plotcoord( v );
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
    
    //**************************************************************************//
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
    //**************************************************************************//
    
    
    //! 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" );
    
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        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;
        }
    
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        mystd::system( "gnuplot -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);
    
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        gp_write( string("set terminal x11") );
    
    //! Clear plot frame and buffer.
    
    void CPlot::clearFrame()
    
        // clear gnuplot tmp files
    
        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;
    
    //! Plot coordinate frame (axes, ticks, labels).
    
    void CPlot::plotFrame()
    
    Wuttke, Joachim's avatar
    c  
    Wuttke, Joachim committed
        gp_write( "set nologscale" );
        string whichlog="";
    
        if (X.logflag) whichlog += "x";
        if (Y.logflag) whichlog += "y";
    
    Wuttke, Joachim's avatar
    c  
    Wuttke, Joachim committed
        if (whichlog!="")
            gp_write( "set logscale " + whichlog );
    
        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::plotScan( const bool as_line, const int style_no,
                          const vector<double> xp, const vector<double> yp,
                          const vector<double>* z,
                          const string xco, const string yco,
                          const 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" );
    
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        // Data to tmp file:
    
        char gp_fnam[40];
    
        sprintf( gp_fnam, "/tmp/%s-%d-%03d.gnu", getenv("LOGNAME"),
                 iPlot, gp_fno++);
    
        if (gp_fnames!="") gp_fnames += ", ";
        gp_fnames += string("\"") + gp_fnam + "\"" + 
    
            //		string(" title \"") + xco + string (" -> ") 
            //		                    + yco + string ("\"");
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        if ( as_line )
    
            gp_fnames += " with lines";
        FILE *gp_fd;
    
        if (!(gp_fd = fopen(gp_fnam, "w")))
            throw string("cannot save gnuplot data to ") + gp_fnam;
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        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";
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
            fprintf(gp_fd, "%16.8g %16.8g\n", xp[i], yp[i]);
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
            nout++;
    
        fclose(gp_fd);
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        if( !nout ){
            cout << "no points in frame: " << info << "\n";
            return;
        }
    
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        // Live display:
        string cmd = "plot ";
    
        char aux[80];
    
        sprintf( aux, "[%12.8g:%12.8g] [%12.8g:%12.8g] ", 
    
                 X.inf, X.sup, Y.inf, Y.sup);
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        cmd += aux + gp_fnames;
        gp_write( cmd );
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        // 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 );
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        if ( as_line )
    
            snprintf( outlin, CPLOT_LINSIZ, "%2d cstyle", 1 );
    
            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]), 0.0,
    
            ps_accu.push_back( outlin );
        }
    
    //! Add documentation lines to postscript output.
    
    void CPlot::plotDoclines( 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:
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        ps_outdir = NRead::wofmac( "\\psdir" );
        ps_head = NRead::wofmac( "\\pshead" );
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
        if ( full_outfile )
            ps_dict = NRead::wofmac( "\\psdict" );
    
    
        // construct output file name:
        FILE *pssav;
        char outf[20];
        string flong, cmd;
        while(1) {
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
            if (ps_fnum>=999)
                throw string( "graph file number overflow" );
    
            sprintf(outf, "%sl%d.%s", ps_outdir.c_str(), ++ps_fnum, 
                    full_outfile ? "ps" : "psa" );
            if (!(pssav = mystd::glob_fopen(outf, "", "", "r")))
                break; // legal exit
            fclose(pssav);
        }
        printf("save plot in %s\n", outf);
    
        // copy headers to output file:
        cmd = string("cat ") + ( full_outfile ? ps_dict : "" ) + " " +
            ps_head + " > " + outf + "\n";
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        mystd::system( cmd );
    
        
        // a redundant check of existence of the outfile:
        if (!(pssav = mystd::glob_fopen(outf, "", "", "r", &flong))) {
            printf("have not created file %s\n", 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);
    
        // 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()
    {
        string ret;
        ret  =   "x: " + X.info();
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        ret += "  y: " + Y.info();
    
    //! Send one line to gnuplot fifo.
    
    
    void CPlot::gp_write( string in )
    
        string out = in + "\n";
    
    Wuttke, Joachim's avatar
    Wuttke, Joachim committed
        // 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,
    
        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", 
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
                          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", 
    
    Wuttke, Joachim's avatar
    ?  
    Wuttke, Joachim committed
                  A->pc(ticklim[0]), A->pc(ticklim[1]), ntack+2, ntpt,
    
                  (A->logflag? "Log" : "Lin"));
    
        ps_accu.push_back( outlin );