Skip to content
Snippets Groups Projects
Forked from mlz / Frida
2348 commits behind the upstream repository.
edif.cpp 38.12 KiB
//**************************************************************************//
//* FRIDA: fast reliable interactive data analysis                         *//
//* edif.cpp: edit on-line files (text_edit, dir, make, plot, ...)         *//
//* (C) Joachim Wuttke 1990-, v2(C++) 2001-                                *//
//* http://frida.sourceforge.net                                           *//
//**************************************************************************//

#include <stdlib.h>
#include <math.h>
#include <iostream>
#include <algorithm>

#include "mystd.h"
#include "olm.h"
#include "expr.h"
#include "asi.h"
#include "dualplot.h"
#include "edif.h"
#include "index.h"

using namespace std;

namespace NEdif {
    CParam* RFind(CEle* e, CCoord OneR);
}


//! Display list of online files.

void NEdif::Dir(void)
{
    CEle *e;
    NOlm::IterateEle iter;
    iter.All();
    size_t npts;
    string txtpts;
    while((e=iter())) {
        npts = e->nPts();
        if (npts) 
            txtpts = strg(npts);
        else
            txtpts = "?";

        char ztext[17];
        if( e->P()->nZ() ){
            if     ( e->P()->ZCo.size()==1 )
                sprintf( ztext, "%16s",
                         e->P()->ZCo[0].str().substr(0,16).c_str() );
            else if( e->P()->ZCo.size()==2 )
                sprintf( ztext, "%16s",
                         ( e->P()->ZCo[0].str().substr(0,10) + ", " +
                                  e->P()->ZCo[1].str().substr(0,4) ).c_str() );
            else if( e->P()->ZCo.size()==3 )
                sprintf( ztext, "%16s",
                         ( e->P()->ZCo[0].str().substr(0,5) + ", " +
                           e->P()->ZCo[1].str().substr(0,4) + ", " +
                           e->P()->ZCo[2].str().substr(0,3) ).c_str() );
            else
                sprintf( ztext, "%16s",
                         ( e->P()->ZCo[0].str().substr(0,4) + ", " +
                           e->P()->ZCo[1].str().substr(0,3) + ", " +
                           e->P()->ZCo[2].str().substr(0,2) + " &c" 
                             ).c_str() );
        } else {
            sprintf( ztext, "" );
        }
        printf("%3d%1s%-16s %5d %-16s %6s %-12s %-12s\n",
               iter.SelNo(),
               e->P()->as_on_disk ? "=" : " ",
               e->P()->name.substr(0,16).c_str(),
               e->nScan(), 
               ztext,
               txtpts.c_str(),
               e->P()->xco.str().substr(0,12).c_str(),
               e->P()->yco.str().substr(0,12).c_str()
            );
    }
}


//! Display list of z-values and x-ranges for scans in selected files.

void NEdif::DirZ(void)
{
    NOlm::IterateEle iter;
    const CEle *e;
    const COld *d;
    const COlc *c;
    while((e=iter())) {
        cout << "file " << iter.SelNo() << ":\n";
        printf("   ");
        c = e->C();
        d = e->D();
        for (size_t m=0; m<e->P()->ZCo.size(); m++) {
            printf(" %12s", e->P()->ZCo[m].str().c_str());
        }
        printf( "%8s %38s\n", "#pts.", "x" );
        for (size_t j=0; j<e->nScan(); j++) {
            printf("%3zu", j);
            for (size_t m=0; m<e->P()->ZCo.size(); m++) {
                printf(" %12.6g", (*(e->Z(j)))[m]);
            }
            if (d)
                printf( "%12.6g (%4zu) %12.6g\n", 
                        d->VS[j].x[0], d->VS[j].size(),
                        d->VS[j].x[d->VS[j].size()-1] );
            else
                printf("\n");
        }
    }
}


//! Display z coordinates (names and units) for selected files.

void NEdif::ListZ(void)
{
    NOlm::IterateEle eiter;
    CEle *e;
    size_t k, nz, i;
    string act;
    vector<CCoord> ZCo;

    if (!(e=eiter())) return;

    // Get current coordinates:
    ZCo = e->P()->ZCo;
    nz = ZCo.size();
    if (nz) 
        for (i=0; i<nz; ++i)
            printf("  %zu: %s\n", i, ZCo[i].str().c_str());
    else
        printf("  there are no z coordinates\n");
       
    // Check other input files:
    while((e=eiter())) {
        k = eiter.SelNo();
        if ( e->P()->ZCo.size()!=nz )
            throw "file " + strg(k) + " has different number of z coordinates";
        for (i=0; i<nz; ++i)
            if ( e->P()->ZCo[i]!=ZCo[i] )
                throw "file " + strg(k) +
                    " has different z coordinate " + strg(i);
    }

}


//! Display numeric parameters for selected files.

void NEdif::DirNumpar(void)
{
    NOlm::IterateEle iter;
    const CEle *e;
    while((e=iter())) {
        cout << "file " << iter.SelNo() << ":\n";
        printf("   ");
        for (size_t m=0; m<e->P()->RPar.size(); m++) {
            cout << "  " << e->P()->RPar[m].str() << "\n" ;
        }
    }
}


//! Interactive modification of numeric parameters.

//! QUESTIONABLE. Why not use generic expressions and specific commands?

void NEdif::EditNumpar(void)
{
    NOlm::IterateEle eiter;
    CEle *e;
    vector<CCoord> AllR;
    CParam *rp;
    vector<uint> RStat; // 1: different values 2: partially undefined
    vector<double> RVal;
    string choice;
    IndexSet Choice;
    IndexSetIterator ISet;
    CCoord newco;
    double val;
    uint i, ii;
    bool first;

make_table:

    // Collate RPar entries:
    AllR.clear();
    eiter.Reset();
    while((e=eiter())) {
        for (i=0; i<e->P()->RPar.size(); ++i) {
            for (ii=0; ii<AllR.size(); ++ii)
                if (e->P()->RPar[i].Co == AllR[ii])
                    goto found;
            AllR.push_back(e->P()->RPar[i].Co);
        found:
            ;
        }
    }
    RStat.reserve(AllR.size());
    RVal.reserve(AllR.size());
    for (ii=0; ii<AllR.size(); ++ii) {
        RStat[ii] = 0;
        first = 1;
        eiter.Reset();
        while((e=eiter())) {
            if ((rp=RFind(e, AllR[ii]))) {
                if (first) {
                    RVal[ii] = rp->val;
                    first = 0;
                } else if (RVal[ii] != rp->val) {
                    RStat[ii] |= 1;
                }
            } else {
                RStat[ii] |= 2;
            }
        }
    }
	
    // Show table:
    for (ii=0; ii<AllR.size(); ++ii) {
        printf("   %3u %.40s", ii, AllR[ii].str().c_str());
        if (RStat[ii] & 1)
            printf("<different values> ");
        else
            printf("%18.10g ", RVal[ii]);
        if (RStat[ii] & 2)
            printf("<partially undefined>\n");
        else
            printf("\n");
    }
		
user_choice:
    choice = wask("Modify r-pars (h=help) ?", 3, "q");
    if (choice == "h") {
        printf("Enter either list of r-par numbers"
               " or a:add\n");
        goto user_choice;
    } else if (choice == "a") {
        newco.Ask("New parameter");
        val = dask("Value");
        eiter.Reset();
        while((e=eiter()))
            e->P()->RPar.push_back(CParam(newco, val));
        goto make_table;
    } else if (choice=="" || choice=="q") {
        return;
    } else if (Choice.parse(choice, 0, AllR.size())) {
        cout << mystd::BEL;
        goto user_choice;
    } 

    // It's a list

    // User action on list of parameters:
    ISet.reset(Choice);
    while (ISet(&ii)) {
        if (RStat[ii]) {
            // Table:
/*
  eiter.Reset();
  while((e=eiter())) {
*/					
        }
    action_on_par:
        choice = wask(("Action on r-par[" + strg(ii) +"] <val,d,q,<>>"
                          ).c_str());
        if        (choice=="h") {
            printf(" q     quit\n"
                   " d     delete entry\n"
                   " <val> set uniform value\n"
                   " f     set value per file\n");
            goto action_on_par;
        } else if (choice=="q") {
            return;
        } else if (choice=="") {
        } else if (choice=="d") {
/*				eiter.Reset();
				while((e=eiter())) {
				
				for (i=0; i<e->P()->RPar.size(); ++i)
                                if (e->P()->RPar[i] 
                                == AllR[ii]) {
                                e->P()->RPar.erase(
						
                                printf("missing ...\n");
*/
        } else if (sscanf(choice.c_str(), "%lg", &val)==1) {
            eiter.Reset();
            while((e=eiter())) {
                if ((rp=RFind(e, AllR[ii])))
                    rp->val = val;
                else 
                    e->P()->RPar.push_back(
                        CParam(AllR[ii], val));
            }
        } else {
            cout << mystd::BEL;
            goto action_on_par;
        } // action on rpar
    } // list of rpars

    goto make_table;
}


//! Auxiliary routine for EditNumpar.
	
CParam* NEdif::RFind(CEle* e, CCoord OneR)
{
    for (size_t i=0; i<e->P()->RPar.size(); ++i)
        if (e->P()->RPar[i].Co == OneR) return &(e->P()->RPar[i]);
    return 0;
}


//! Display coordinate names and units for selected files.

void NEdif::DirCoord(void)
{
    NOlm::IterateEle eiter;
    CEle *e;
    string out;
    char   aux[4];
    while((e=eiter())) {
        sprintf( aux, "%3d", eiter.SelNo() );
        out = string( aux );
        out += " x: " + e->P()->xco.str();
        out += "  y: " + e->P()->yco.str();
        for (size_t i=0; i<e->P()->nZ(); i++) {
            out += "  z" + strg(i) + ": " + e->P()->ZCo[i].str() + "\n";
        }
        cout << out;
    }
}


//! Modify file name

void NEdif::EditFNam()
{
    NOlm::IterateEle eiter;
    const CEle *ein;
    COlo *f;
    string fnam;
    while( (ein=eiter()) ) {
        f = ein->P();
        fnam = wask( ("Rename " + f->name).c_str(), f->name );
        if ( fnam != f->name ){
            f->lDoc.push_back( "ef " + fnam + " # < " + f->name );
            f->name = fnam;
        }
    }
}

//! Modify coordinate names and units.

void NEdif::EditCoord( string which )
{
    NOlm::IterateEle eiter;
    const CEle *ein; CEle eout;
    COlo *fin; COlo *fout;
    CRef cref( which );
    if ( !cref.defined() ) {
        printf( "! invalid coordinate %s\n", which.c_str() );
        return;
    }
    CCoord new_co, old_co;
    new_co.Ask( (which+" coordinate").c_str() );
    // no-overwrite mode is broken since 28aug08
    while( (ein=eiter()) ) {
        fin = ein->P();
        fout = fin;
                              
        if        ( cref.var==CRef::_X ) {
            old_co = fin->xco;
            fout->xco = new_co;
        } else if ( cref.var==CRef::_Y ) {
            old_co = fin->yco;
            fout->yco = new_co;
        } else if ( cref.var==CRef::_Z ) {
            if( cref.num >= fin->nZ() ){
                printf( "! not all files in selection have %s, use oz+\n",
                        which.c_str() );
                return;
            }
            old_co = fin->ZCo[cref.num];
            fout->ZCo[cref.num] = new_co;
        }
        fout->lDoc.push_back( "ec"+string(which)+" "+new_co.str()+
                              " # old: " + old_co.str() );
    }
}


//! Display documentation lines for selected files.

//! MISSING: Interactive modification of documentation.

void NEdif::EditDoc(void)
{
    NOlm::IterateEle iter;
    CEle *e;
    while( (e=iter()) ){
        COlo* f = e->P();
        COlc* fc = e->C();
        if( iter.index()>0 )
            cout << "\n";
        cout << iter.SelNo() << "\n";
        for ( size_t i=0; i<f->lDoc.size(); i++ )
            cout << f->lDoc[i] << "\n";
        if( fc )
            cout << fc->pInfoCat();
    }
}

//! Save online file as external file (the work is done in COlo::Save).

void NEdif::Save( string fmt )
{
    COlo *f;
    string fnam;
    FILE *fd;
    CEle *e;
    NOlm::IterateEle iter;
    while((e=iter())) {

        if( !(f = e->P()) )
            throw string( "SEVERE / f ?" );
        string quest = "Save as (."+fmt+")";
        fnam = wask( quest.c_str(), f->name);
        if ( fnam==string("") ) return;
        if ( fnam != f->name && fmt=="y08" ){
            f->lDoc.push_back( "fs " + fnam + " # < " + f->name );
            f->name = fnam;
        }
        fnam += "."+fmt;

        if ( (fd = fopen(fnam.c_str(), "r")) ) {
            fclose(fd);
            if (!bask("Overwrite ?")) return;
        }
        if ( !(fd = fopen(fnam.c_str(), "w")) )
            throw "! cannot write to file " + fnam;

        e->Save( fd, fmt );

        fclose(fd);
    }
}


//! Create a new online file from interactive input.

void NEdif::ReadIn(void)
{
    COld olf;
	
    // *** set file name and coordinates ***

    static string fnam;
    olf.name = wask("Save as", fnam);

    static CCoord xco, yco, zco;
    olf.xco = xco.Ask("x", xco);
    olf.yco = yco.Ask("y", yco);
    cout << "if there is only one scan, answer \"\\\" to the following:\n";
    zco.Ask("z", zco);
    if(zco.defined()) olf.ZCo.push_back(zco);

    // *** set input mode ***

    static int mInpX = -1;
    cout << "input modes:\n"
        "  (1) enter x pointwise\n"
        "  (2) set regular x values\n"
        "  (3) enter x-y pairs\n"
        "  (4) select x,y from multicolumn input\n"
        "  (5) select x,y,z from multicolumn input\n";
    mInpX = iask( "Option", mInpX );
    if( mInpX==5 && !zco.defined() )
        throw string( "need z coordinate for this option" );

    static int imcx, imcy, imcz; 
    int mInpY=-1; // ask
	
    switch(mInpX) {
    case 0: 
        return;
    case 1: case 2:
        break;
    case 3:
        imcx = 0;
        imcy = 1;
        mInpY = 0;
        break;
    case 4: case 5:
        imcx = iask("x from column #", imcx);
        if (imcx<0) return;
        imcy = iask("y from column #", imcy);
        if (imcy<0) return;
        mInpY = 0;
        break;
    default:
        throw string( "invalid option" );
    }
			
    if (mInpY<0) {
        cout << "input modes:\n"
            "  (1) enter y pointwise\n"
            "  (2) set regular y values\n"
            "  (3) select y from multicolumn input\n";
        static int mInpY_in = -1;
        mInpY_in = iask( "Option", mInpY_in );
        if ( mInpY_in<1 || mInpY_in>3 )
            throw string( "invalid option" );
        mInpY = mInpY_in;
    }

    switch(mInpY) {
    case 0: case 1: case 2:
        break;
    case 3:
        imcy = iask("y from column #", imcy);
        if (imcy<0) return;
        break;
    default:
        throw string( "PROGRAM ERROR in Make: invalid switch(mInpY)" );
    }

    static int mInpZ;
    if        ( mInpX==5 ){
        mInpZ = 3;
    } else if (zco.defined()) {
        cout << "at present, we allow only _one_ z value per scan\n";
        cout << "input modes:\n"
            "  (1) enter z per spectrum\n"
            "  (2) select z from multi-z header line\n";
        mInpZ = iask( "Option", mInpZ );
        if ( mInpZ<0 || mInpZ>2 )
            throw string( "invalid option" );
    } else {
        mInpZ = 0;
    }

    switch (mInpZ) {
    case 0: case 1:
        break;
    case 2:
        if (! (mInpX==4 || mInpY==3) )
            throw string( "invalid combination of options:"
                          " y is not read from multicolumn format" );
        break;
    case 3:
        imcz = iask( "z0 from column", imcz );
        if( imcz<0 ) return;
        break;
    default:
        throw string( "PROGRAM ERROR in Make: invalid switch(mInpZ)" );
    }

    static int imcy_delta=0; 
    static uint imcy_last;
    cout << "DEBUG "<< mInpZ << "\n";
    if (mInpZ==2) {
        imcy_delta = iask("step in number of y column", imcy_delta);
        if (imcy_delta) 
            imcy_last = iask("last y column", imcy_last);
    } else {
        imcy_delta = 0;
    }

    // *** loop over scans ***

    CScan S;
    uint n = 0;
    uint imcm;
    string quest, line;
    double val, zold;
    vector<double> linv;

    int ns = 0;
    while(1) {

        S.Clear();

        switch(mInpZ) {
        case 1:
            line = sask("Enter z value", "");
            // cout << "DEBUG: line [" << line << "]\n";
            if (mystd::str2vec(line, &linv, 2) < 0) {
                cout << "-> invalid line [" << line << "]\n";
                goto store;
            }
            // printf ("DEBUG: size = %d\n", linv.size());	
            if (linv.size()<1) {
                cout << "found no z in line [" << line << "] => assuming end-of-input\n";
                goto store;
            }
            // printf ("DEBUG: val = %g\n", linv[0]);	
            val = linv[0];
            break;
        case 2:
            line = sask("Enter header line with z values", "");
            if (mystd::str2vec(line, &linv, imcy+1)) {
                cout << "-> bad input\n";
                goto store;
            }
            if (linv.size()<=imcy) {
                cout << "-> not enough entries in header\n";
                goto store;
            }
            val = linv[imcy];
            break;
        }
        if (mInpZ<=2)
            S.z.push_back(val);

        switch(mInpX) {
        case 1:
            cout << "Enter x values (or empty line to exit):\n";
            while (1) {
                quest = "[" + strg(n) + "] ";
                line = sask(quest.c_str(), "");
                if (sscanf(line.c_str(), "%lg", &val)!=1) break;
                S.x.push_back(val);
                n++;
            }
            break;
        case 2:
            //S.x.Ask("x");
            //n = S.x.size();
            throw string( "case 2 broken" );
            break;
        case 3: case 4: case 5:
            cout<<"Enter data row (or empty line to exit):\n";
            imcm = (imcx>imcy) ? imcx : imcy;
            if( mInpY==5 && imcz>imcm )
                imcm = imcz;
            while (1) {
                quest = "[" + strg(n) + "] ";
                line = sask(quest.c_str(), "");
                if (line==string("")) break;
                if (mystd::str2vec(line, &linv, imcm+1)) {
                    printf(
                        " could not extract value no. %u"
                        " from string [%s]\n", imcm, line.c_str());
                    continue;
                }
                if (linv.size()<=imcm) {
                    cout << "-> not enough entries in line\n";
                    continue;
                }
                if( mInpZ==3 ){
                    if( n==0 ){
                        S.z.push_back(linv[imcz]);
                        zold = linv[imcz];
                    } else if( linv[imcz]!=zold ){
                        olf.VS.push_back(S);
                        ns++;
                        S.Clear();
                        S.z.push_back(linv[imcz]);
                        zold = linv[imcz];
                    }
                }
                S.push_back(linv[imcx], linv[imcy]);
                n++;
            }
            break;
        }
        if (n<=0) return;

        switch(mInpY) {
        case 0:
            break;
        case 1:
            cout << "Enter " << n << " y values:\n";
            S.y.resize(n);
            for (uint i=0; i<n; i++) {
                quest = "x[" + strg(i) + "] = " +
                    strg(S.x[i]) + " -> ";
                do {
                    line = sask(quest.c_str());
                } while (sscanf(line.c_str(), "%lg", &val)!=1);
                S.y[i] = val;
            }
            break;
        case 2:
            throw string( "case 2 broken, ygrid undefined" );
            //ygrid.Ask("y", n);
            //ygrid.AsVec(&(S.y));
            break;
        case 3:
            cout << "Enter " << n << " lines with y values:\n";
            S.y.resize(n);
            for (uint i=0; i<n; i++) {
                quest = "x[" + strg(i) + "] =" +
                    strg(S.x[i]) + " -> ";
                while (1) {
                    line = sask(quest.c_str());
                    if (mystd::str2vec(line, 
                                       &linv, imcy+1)) {
                        printf(
                            " could not extract y value no. %u"
                            " from string [%s]\n", imcy, line.c_str());
                        continue;
                    }
                    if (linv.size()<=imcy) {
                        cout << "-> not enough entries"
                            " in line\n";
                        continue;
                    }
                    break;
                }
                S.y[i] = linv[imcy];
            }
            break;
        }
        olf.VS.push_back(S);
        ns++;
		
        if (!mInpZ || mInpZ==3) goto store;
        if (imcy_delta) {
            imcy += imcy_delta;
            if (imcy_delta>0 && imcy>imcy_last ||
                imcy_delta<0 && imcy<imcy_last) goto store;
        }
    }
	
    // *** store new on-line file ***
store:
    // cout << "DEBUG: store\n";
    if (ns<=0) return;
    // cout << "DEBUG: sel\n";
    NOlm::SelNew();
    // cout << "DEBUG: add\n";
    NOlm::OloAdd(&olf);
    // cout << "DEBUG: main\n";
}


//! Create a new online file from table.

void NEdif::ReadTab( string qualif )
{
    // ** parse qualif **
    if( qualif.find_first_not_of("hvcsm")!=string::npos )
        throw string( "ReadTab: invalid qualifier" );
    char dir = qualif[0];
    if( !( dir=='h' || dir=='v' || dir=='c' ) )
        throw string( "ReadTab: missing qualifier h or v" );
    bool horizontal = dir=='h';
    bool choosecol = dir=='c';
    static int iycol=0;
    bool fromscript = qualif.find('s')!=string::npos;
    bool multiblock = qualif.find('m')!=string::npos;

    // ** input from script or from file **
    static string script;
    vector<string> inFiles;
    if( fromscript ){
        inFiles.push_back( "/ram/tab" );
        cout << "Script must write to "<<inFiles[0]<<"\n";
        script = wask("Script", script);
        mystd::system( script );
    } else {
        string fnames = wask( "Read tab from file(s)" );
            // PROVIS, sask sobald glob besser
        if( mystd::glob_file_list( fnames, "", "", &inFiles )<=0 )
            throw "files " + fnames + " not found";
    }

    if( choosecol ){
        printf( "at present, x:=i\n" );
        iycol = iask( "Read y from column", iycol );
        if( iycol<0 ) return;
    }
    
    FILE *fd;
    string fdir, fshort, fext;
    NOlm::SelNew();
    for( uint iF=0; iF<inFiles.size(); ++iF ) {
        if( !(fd = fopen(inFiles[iF].c_str(), "r")) )
            throw string( "cannot open file " ) + inFiles[iF];
        cout << ".. reading from " << inFiles[iF] << "\n";
        COld olf;
        if( fromscript ) {
            olf.lDoc.push_back( "ft"+qualif+" " + script );
            olf.name = script;
        } else {
            mystd::fname_divide( inFiles[iF], &fdir, &fshort, &fext);
            olf.lDoc.push_back( "ft"+qualif+" " + inFiles[iF] );
            olf.name = fshort;
            if( choosecol ){
                olf.lDoc.push_back( "y from column " + strg(iycol) );
                olf.name += "_" + strg(iycol);
            }
        }

        // ** file-level settings **
        if( choosecol ){
            olf.xco = CCoord("i", "");
            olf.yco = CCoord("y"+strg(iycol), "");
        } else {
            olf.xco = CCoord("x", "");
            olf.yco = CCoord("y", "");
        }

        // *** read input ***
        string lin, s1, s2;
        vector<double> dat, zdat;
        int n_in = -1; // line number
        int nblock = 0;
        int nline = -1; // line number within block (starting out of block)
        int nz = 0;
        int nzdat = 0;
        double val;
        CScan S;
        while( mystd::freadln(fd, &lin)>-1 ) {
            ++n_in;
            if( lin.substr(0,4)=="#rpa" ){
                mystd::string_extract_word( lin.substr(4), &s1, &s2 );
                if( mystd::any2dbl( s2, &val ) )
                    printf( "invalid parameter line [%s]\n", lin.c_str() );
                else
                    olf.RPar.push_back( CParam( s2, "", val) );
                continue;
            }
            if( lin[0]=='#' )
                continue;
            
            if( nline==-1 ) { // start of block 
                if( !horizontal && nblock!=0 && S.size()>0 )
                    olf.VS.push_back(S);
                if ( multiblock ) {
                    if( mystd::str2vec(lin, &zdat, 0, false) )
                        throw "invalid header line [" + lin +
                            "] (or legitimate break ?)";
                    if( nblock==0 ){ // first header
                        nzdat = zdat.size();
                        nz += nzdat;
                        for( uint iz=0; iz<nzdat; ++iz )
                            olf.ZCo.push_back( CCoord("z"+strg(iz), "") );
                    } else if( zdat.size() != nzdat )
                        throw "line " + strg(n_in) +
                            ": header has length " + strg(zdat.size()) +
                            " instead of " + strg(nzdat);
                }
                if ( nblock==0 && horizontal ) {
                    nz += 1; 
                    olf.ZCo.push_back( CCoord("line", "") );
                }
                if( !horizontal ){
                    S.Clear();
                    S.z.resize( nz );
                    for( uint iz=0; iz<nzdat; ++iz )
                        S.z[iz] = zdat[iz];
                }
                nline = 0;
                ++nblock;
                if( multiblock ) // this was a header line
                    continue;
            }
            
            if( lin=="" && nline!=-1 ) { // end-of-block
                nline = -1;
                ++nblock;
                continue;
            }
            
            // regular data line
            if( mystd::str2vec(lin, &dat, 0, false) )
                throw "cannot parse line "+strg(nline)+" in block "+
                    strg(nblock)+" ["+lin+"]";
            if(dat.size()==0)
                throw "no data in line "+strg(nline)+" in block "+
                    strg(nblock)+" ["+lin+"]";
            
            if( horizontal ) { // new scan for every line 
                S.Clear();
                S.z.resize( nz );
                for( uint iz=0; iz<nzdat; ++iz )
                    S.z[iz] = zdat[iz];
                S.z[nz-1] = nline;
                for( int i=0; i<dat.size(); ++i )
                    S.push_back( (double)i, dat[i] );
                olf.VS.push_back(S);
            } else { // vertical
                if( choosecol ){
                    if( iycol>=dat.size() )
                        throw "line "+strg(n_in)+" (line "+strg(nline)+
                            " in block "+strg(nblock)+") ["+lin+
                            "] contains only "+strg(dat.size())+" values; "+
                            "cannot read y from col "+strg(iycol);
                    S.push_back( (double)nline, dat[iycol] );
                } else {
                    if( dat.size()!=2 )
                        throw "line "+strg(n_in)+" (line "+strg(nline)+
                            " in block "+strg(nblock)+") ["+lin+
                            "] contains "+strg(dat.size())+" values; "+
                            "at present, exactly 2 are required";
                    S.push_back( dat[0], dat[1] );
                }
            }
            ++nline;

        } // end of file input loop
        
        if( !horizontal && S.size()>0 )
            olf.VS.push_back(S);
        
        if( !(olf.nScan()) )
            throw string( "no input lines" );
        if( olf.nScan()==1 ){
            olf.ZCo.clear();
            olf.VS[0].z.clear();
        }
        NOlm::OloAdd(&olf);
    }
}


//! Create a new online file containing a regular x,z grid; let y=0.

void NEdif::MakeGrid(void)
{
    COld olf;

    static int ni=1, nj=1;
    static string fnam;

    int niIn = iask("Number of points per scan", ni);
    if (niIn<1) return;
    ni = niIn;

    int njIn = iask("Number of scans", nj);
    if (njIn<1) return;
    nj = njIn;

    fnam = "grid"+strg(ni);
    if (nj>1) fnam += "x"+strg(nj);

    olf.name = wask("Save as", fnam);

    // *** set coordinates ***
    olf.xco = CCoord("x", "");
    olf.yco = CCoord("y", "");
    if (nj>1)
        olf.ZCo.push_back(CCoord("z", ""));

    CScan S;

    // *** loop over scans ***
    for (int j=0; j<nj; ++j) {

        S.Clear();

        S.x.resize(ni);
        for( uint i=0; i<ni; ++i )
            S.x[i] = ((double)i)/(ni+1);
        S.y.clear();
        S.y.resize( ni, 0. );

        if (nj>1) S.z.push_back( (double)j );

        olf.VS.push_back(S);
    }
	
    NOlm::SelNew();
    NOlm::OloAdd(&olf);
}


//! Plot scan.

void NEdif::Plot( CPlot *plot, bool add )
{
    static CList JSel;
    CEle *e;
    size_t k, j;
    const COld *fd;
    const COlc *fc;
    CScan S; 
    static int pstyle = 0, cstyle = 0;
    static string jSel = "";
    NOlm::JSelAsk( "Plot which scans", &jSel, &JSel );

    if (!add) {

        pstyle = 0;
        cstyle = 0;

        // determine x-range ?
		
        double inf, sup;
        if (!plot->X.finite()) {
            NOlm::IterateD fiter;
            const vector<double> *vx;
            bool first = true;
            while((fd=fiter())) {
                JSel.evaluate( 0, fd->nScan()-1 );
                for( size_t iv=0; iv<JSel.size(); ++iv ) {
                    vx = &( fd->VS[JSel.V[iv]].x );
                    for( size_t i=0; i<vx->size(); ++i ){
                        if( plot->X.logflag && (*vx)[i]<=0 )
                            continue;
                        if( first ){
                            inf = (*vx)[i];
                            sup = (*vx)[i];
                            first = false;
                        } else {
                            if( (*vx)[i]<inf ) inf = (*vx)[i];
                            if( (*vx)[i]>sup ) sup = (*vx)[i];
                        }
                    }
                }
            }
            if( first )
                throw string( "x range is empty" );
            plot->X.setLimits( inf, sup );
            plot->X.round();
        }

        // determine y-range ?
        if (!plot->Y.finite()) {
            bool first=true;
            NOlm::IterateEle eiter;
            int npts = 987;
            const CScan *s;
            while( (e=eiter()) ){
                k = eiter.SelNo();
                fc = e->C();
                fd = e->D();
                JSel.evaluate( 0, e->nScan()-1 );
                for( size_t iv=0; iv<JSel.size(); ++iv ){ 
                    size_t j = JSel.V[iv];
                    if( fd ){
                        s = &(fd->VS[j]);
                    } else {
                        S.x.resize( npts );
                        for ( size_t i=0; i<npts; i++ )
                            S.x[i] = plot->X.plotcoord2value( i/(npts-1.0) );
                        fc->T->tree_curve_val_vec( &(S.y), S.x, k, j );
                        s = &S;
                    }
                    for (size_t i=0; i<s->size(); i++) {
                        if( !plot->X.contained( s->x[i] ) )
                            continue;
                        if( plot->Y.logflag && s->y[i]<=0 )
                            continue;
                        if (first) {
                            inf=s->y[i], sup=s->y[i];
                            first = false;
                        } else {
                            if (s->y[i]<inf) inf=s->y[i];
                            if (s->y[i]>sup) sup=s->y[i];
                        }
                    }
                }
            }
            if( first )
                throw string( "y range is empty" );
            plot->Y.setLimits( inf, sup );
            plot->Y.round();
        }

        // build label: (zur Zeit nur vom ersten File; definiere +=)
        plot->X.C = NOlm::MOM[*(NOlm::FSel.V.begin())].P()->xco;
        plot->Y.C = NOlm::MOM[*(NOlm::FSel.V.begin())].P()->yco;

        // draw new frame:
        plot->clearFrame();
        plot->plotFrame();
    }

    // plot data:
    NOlm::IterateEle eiter;
    while( (e=eiter()) ){
        k = eiter.SelNo();
        fc = e->C();
        fd = e->D();
        JSel.evaluate( 0, e->nScan()-1 );
        for ( size_t iv=0; iv<JSel.size(); ++iv){
            j = JSel.V[iv];
            // cout << "DEBUG edif plot k="<<k<<", j="<<j<<"\n";
            if ( fd ) {
                const CScan *s;
                s = &( fd->VS[j] );
                size_t n=s->x.size();
                if ( n!=s->y.size() )
                    throw string( "BUG: plot: x.size<>y.size" );
                // Filter: x,y -> xp,yp if inside frame
                size_t np=0, nxl=0, nxh=0, nyl=0, nyh=0;
                vector<double> xp, yp;
                for ( size_t i=0; i<n; i++ ) {
                    double x = s->x[i];
                    if( !plot->X.contained(x) ) {
                        if ( x<=plot->X.inf ){
                            x = plot->X.inf;
                            nxl++;
                        }
                        if ( x>=plot->X.sup ){
                            x = plot->X.sup;
                            nxh++;
                        }
                        if ( !plot->X.force )
                            continue;
                    }
                    double y = s->y[i];
                    if( !plot->Y.contained(y) ) {
                        if ( y<=plot->Y.inf ){
                            y = plot->Y.inf;
                            nyl++;
                        }
                        if ( y>=plot->Y.sup ){
                            y = plot->Y.sup;
                            nyh++;
                        }
                        if ( !plot->Y.force )
                            continue;
                    }
                    if( np==plot->maxpoints && np<n ) {
                        printf("reached maxpoints at %g\n", s->x[i]);
                    }
                    xp.push_back( x );
                    yp.push_back( y );
                    np = xp.size();
                    if( np > plot->maxpoints ) // continue in large steps
                        i += plot->maxpoints;
                }
                if ( np==0 ) {
                    printf( "file %zu spec %zu:" 
                            " all %zu points outside plot range:\n",
                            k, j, n );
                    if( nxl )
                        printf( "  %zu points < xmin\n", nxl );
                    if( nxh )
                        printf( "  %zu points > xmax\n", nxh );
                    if( nyl )
                        printf( "  %zu points < ymin\n", nyl );
                    if( nyh )
                        printf( "  %zu points > ymax\n", nyh );
                } else {
                    plot->plotScan( false, pstyle++,
                                    xp, yp, e->Z(j),
                                    e->P()->xco.str(), e->P()->yco.str(),
                                    "data file "+strg(k)+" scan "+strg(j) );
                }
/*
            } else if ( fc && false ) {
                // keep old vectorial version;
                // might be useful for accelerated plotting
                int npts = 987;
                vector<double> xp(npts), yp;
                for( size_t i=0; i<npts; ++i )
                    xp[i] = plot->X.plotcoord2value( i/(npts-1.0) );
                fc->T->tree_curve_val_vec( &yp, xp, k, j );
                plot->plotScan( true, cstyle++, xp, yp, e->Z(j),
                                e->P()->xco.str(), e->P()->yco.str(),
                                "curve file "+strg(k)+" scan "+strg(j) );
*/
            } else if ( fc && fc->kconv!=-1 ) {
                // plot only at x points of convolutand
                uint kconv = fc->kconv;
                uint jconv = CTree::setConvJ( k, j, kconv );
                COld *fconv = NOlm::MOM[kconv].D();
                if ( !fconv )
                    throw string( "convolutand is not a data file" );
                vector<double> xp, yp;
                for( size_t i=0; i<fconv->VS[jconv].size(); ++i ){
                    double x = fconv->VS[jconv].x[i];
                    if( plot->X.contained( x ) )
                        xp.push_back( x );
                }
                fc->T->tree_curve_val_vec( &yp, xp, k, j );
                vector<double> xo, yo;
                for( size_t i=0; i<xp.size(); ++i ){
                    if( plot->X.contained( xp[i] ) &&
                        plot->Y.contained( yp[i] )    )
                        continue;
                    xo.push_back( xp[i] );
                    yo.push_back( yp[i] );
                }
                if( xp.size()==0 ){
                    cout << "curve k="<<strg(k)<<", j="<<strg(j)<<" is empty\n";
                    return;
                }
                plot->plotScan( true, cstyle++, xp, yp, e->Z(j),
                                e->P()->xco.str(), e->P()->yco.str(),
                                "curve file "+strg(k)+" scan "+strg(j) );
            } else if ( fc ) {
                // improved line plotting:
                // - stop/start line when leaving/reentering the frame
                // - interpolate when crossing the horizontal border (TODO)
                // - interpolate at discontinuities (TODO)
                double x, y;
                int npts = 100;
                double delta0 = 1.0/(npts-1.e-10);
                double delta = delta0;
                vector<CScan> SS;
                CScan *s;
                double pos = 0;
                bool was_inside = false;
                bool is_inside;
                while(1){
                    x = plot->X.plotcoord2value( pos );
                    y = fc->T->tree_curve_val_sca( x, k, j );
                    is_inside = plot->Y.contained( y );
                    if( !is_inside ){
                        if( was_inside ){
                        }
                        goto incr_pos;
                    }
                    if( !was_inside ){
                        // start new line segment
                        SS.push_back( CScan() );
                        s = &( SS[SS.size()-1] );
                    }
                    s->push_back( x, y );
                incr_pos:
                    was_inside = is_inside;
                    if( pos>= 1 )
                        break;
                    pos += delta;
                    if( pos>1 )
                        pos = 1;
                }
                for( int iS=0; iS<SS.size(); ++iS ){
                    plot->plotScan( true, cstyle, SS[iS].x, SS[iS].y, e->Z(j),
                                    e->P()->xco.str(), e->P()->yco.str(),
                                    "curve file "+strg(k)+" scan "+strg(j)+
                                    " segment "+strg(iS) );
                }
                cstyle++;
            } else
                throw string( "PROGRAM ERROR plot invalid *e" );
        }
        plot->plotDoclines( e->P()->lDoc );
        if( fc )
            plot->plotDoclines( fc->pInfo() );
    }
}