Newer
Older
//**************************************************************************************************
//* FRIDA: fast reliable interactive data analysis
//* (C) Joachim Wuttke 1990-, v2(C++) 2001-
//* http://apps.jcns.fz-juelich.de/frida
//**************************************************************************************************
//! \brief CAxis: One axis of a coordinate frame.
#include <cmath>
#include <iomanip>
#include <iostream>
#include "../readplus/macro.hpp"
#include "../trivia/math.hpp"
using std::string;
using std::vector;
using std::cout;
#define S(a) triv::strg((a))
//! Sets infinite limits so that next plot call will recompute them.
void CAxis::set_auto()
{
inf = -INFINITY;
sup = +INFINITY;
}
//! Sets limits to arguments.
void CAxis::set_limits(double _inf, double _sup)
if (_inf >= _sup)
throw "Invalid limits " + S(_inf) + " .. " + S(_sup) + " for axis " + name;
if (logflag && _inf <= 0)
throw "Invalid nonpositive limit " + S(_inf) + " for axis " + name;
//! Sets log flag to argument, resets limits to infinite defaults.
void CAxis::set_log(bool _log)
set_auto();
//! Asks for new limits.
void CAxis::ask_and_set(const string& quest)
for (;;) {
double inf_in, sup_in;
string resp1 = NMacro::readwd(quest + " [" + str() + "] ? ");
if (resp1 == "\\q") {
} else if (resp1 == "") {
return; // don't change current value
} else if (resp1 == "*") {
set_auto();
} else if (resp1 == "\\EOL") {
} else if (triv::any2dbl(resp1, &inf_in)) {
} else {
cout << "required input: plot axis range\n"
"examples:\n"
" 0.1 1000 two floating-point numbers\n"
" \\ 1000 let lower bond unchanged\n"
" 0.1 \\ let upper bound unchanged\n"
" * redetermine bounds from data\n"
"just press ENTER to let present range unchanged ["
<< str() << "]\n";
continue;
if (logflag && inf_in <= 0) {
cout << "log axis requires lower bound>0\n";
continue;
}
string resp2 = NMacro::readwd(".. and upper limit [" + S(sup) + "] ? ");
if (resp2 == "\\q") {
} else if (resp2 == "" || resp2 == "\\EOL") {
} else if (triv::any2dbl(resp2, &sup_in)) {
} else {
cout << "invalid upper bound; use '?' for help\n";
continue;
}
if (sup_in <= inf_in) {
cout << "invalid input, does not fulfill lower<upper\n";
continue;
}
if (logflag && (sup_in > 1e199 || inf_in < 1e-199)) {
cout << "log range currently restricted to 1E-199..1E199\n";
if (!logflag && (sup_in > 3e38 || inf_in < -3e38)) {
cout << "lin range currently restricted to -3e38..3e38\n";
continue;
}
inf = inf_in;
sup = sup_in;
//! Sets limits to rounded values of arguments.
void CAxis::set_rounded_limits(double _inf, double _sup)
{
static double relmargin = 0.05;
static double digits = 2;
if (_inf == -INFINITY || _sup == +INFINITY)
throw "BUG: rounding " + name + " limits: unexpected inf limits";
if (_inf > _sup)
throw "BUG: rounding " + name + " limits: invalid plot range " + S(_inf) + " > " + S(_sup);
if (logflag && _inf <= 0)
throw "BUG: rounding " + name + " limits: nonpositive log inf " + S(_inf);
if (inf == sup) {
if (logflag) {
} else if (inf > 0) {
} else if (sup < 0) {
}
double inf2, sup2;
double mydigits = digits;
if (!logflag) { // lin limits
double margin = relmargin * (sup - inf);
do {
sup2 = triv::round_decimal(sup + margin, mydigits);
inf2 = triv::round_decimal(inf - margin, mydigits);
if (sup < 0 && sup2 > 0)
sup2 = 0;
if (inf2 < 0 && inf > 0)
inf2 = 0;
mydigits += 0.1;
// printf("DEB [%g,%g] -> [%g,%g] (%g)\n", inf, sup, inf2, sup2, mydigits);
if (mydigits > 17) {
std::cerr << std::setprecision(17)
<< "PLEASE REPORT THIS BUG: escaping from endless loop in Axis::"
"set_rounded_limits("
<< inf << "," << sup << ")\n";
inf2 = inf - margin;
sup2 = sup - margin;
break;
}
} while (inf2 > inf || sup2 < sup
|| (sup2 - inf2) > (1.33 * (sup - inf) + 1e-14 * (sup + inf)));
} else { // log limits
double ratio = sup / inf;
double margin = exp(relmargin * log(ratio));
sup2 = triv::round_decimal(sup * margin, mydigits);
inf2 = triv::round_decimal(inf / margin, mydigits);
} while (!((inf2 < inf) && (sup < sup2)) && mydigits < 18);
set_limits(inf2, sup2);
//! Are both limits finite ?
bool CAxis::finite() const { return inf != -INFINITY && sup != +INFINITY; }
//! Is argument val contained in present plot range?
bool CAxis::contains(double val) const { return inf <= val && val <= sup; }
//! Do arguments v1 and v2 agree within tolerance tol, relative to plot range?
bool CAxis::close_enough(double v1, double v2, double tol) const
if (!finite())
if (logflag) {
if (v1 <= 0 || v2 <= 0)
return fabs(log(v1 / v2)) <= tol * log(sup / inf);
return fabs(v1 - v2) <= tol * (sup - inf);
}
}
//! Returns plot coordinate (between 0 and 1) of argument.
double CAxis::value2plotcoord(double v) const
if (!finite())
if (logflag) {
if (inf < 0 || v < 0)
return log(v / inf) / log(sup / inf);
return (v - inf) / (sup - inf);
//! Returns half length of error bar (in plot coordinates 0..1).
double CAxis::value2ploterror(double v, double dv) const
if (!finite())
if (dv < 0)
if (dv == 0)
if (logflag) {
if (inf < 0 || v < 0)
return dv / v / log(sup / inf);
return dv / (sup - inf);
//! Sets vector x such that its plot coordinates form an equidistent grid.
void CAxis::set_xgrid(vector<double>& x, int n) const
if (!finite())
if (n < 2)
throw S("call to set_xgrid with less than two points");
if (inf >= sup)
throw S("call to set_xgrid with invalid limits");
x.resize(n);
x[0] = inf;
x[n - 1] = sup;
for (int i = 1; i < n - 1; ++i) {
if (logflag) {
double c = ((double)(i)) / ((double)(n - 1));
x[i] = pow(inf, 1 - c) * pow(sup, c);
} else {
x[i] = ((n - 1 - i) * inf + i * sup) / (n - 1);
}
//! Returns string representation of plot range.
if (inf == -INFINITY || sup == +INFINITY)
return S(inf) + " " + S(sup);
//! Returns summary (log flag, plot limits) for use in table of plot frames.
string CAxis::info() const
{
string ret;
ret = logflag ? "log" : "lin";
ret += " " + S(inf);
ret += " " + S(sup);
//! Returns argument converted to my PostScript units (0..10).
double CAxis::pc(double v) const { return 10 * value2plotcoord(v); }
//! Returns half length of error bar (in my Postscript coordinates 0..10).
double CAxis::pcerr(double v, double dv) const { return 10 * value2ploterror(v, dv); }
//! Sets Tacks, ntpt, nt4t, *ticklim to a nice division of the plot range.
void CAxis::calc_ticks(vector<double>& Tacks, int& ntpt, int& nt4t, double* ticklim) const
calc_logticks(Tacks, ntpt, nt4t, ticklim);
calc_linticks(Tacks, ntpt, nt4t, ticklim);
//! Sets Tacks, ntpt, nt4t, *ticklim to a nice division of the plot range.
void CAxis::calc_linticks(vector<double>& Tacks, int& ntpt, int& nt4t, double* ticklim) const
{
double dtack;
calc_lintacks(Tacks, ntpt, dtack);
ticklim[0] = Tacks.front() - dtack;
ticklim[1] = Tacks.back() + dtack;
nt4t = Tacks.size() + 2;
}
//! Sets Tacks, *ntpt, and dtack
void CAxis::calc_lintacks(vector<double>& Tacks, int& ntpt, double& dtack) const
double range = sup-inf;
if (range <= 0)
throw "BUG detected by calc_linticks: inf=" + S(inf) + " >= sup=" + S(sup);
double r = log10(range);
int ir = (int)((r < 0) ? r - 1 : r);
double rd = r - ir; // fractional part of r
double rdr = pow(10., rd);
if (rdr > 9.99999) {
dtack = 2.5 * pow(10., ir); // spacing between tacks
ntpt = 5; // ticks per tack
} else if (rdr > 5.00001) {
dtack = 2 * pow(10., ir);
ntpt = 4;
} else if (rdr > 2.500005) {
dtack = 1 * pow(10., ir);
ntpt = 5;
} else if (rdr > 1.600003) {
dtack = .5 * pow(10., ir);
ntpt = 5;
} else if (rdr > 1.250002) {
dtack = .4 * pow(10., ir);
ntpt = 4;
} else {
dtack = .25 * pow(10., ir);
ntpt = 5;
}
printf( "DEBUG (%g,%g) => (%g,%g) step %g\n",
inf, sup, dtack * (floor(inf/dtack)-1), dtack * (ceil(sup/dtack)+1), dtack);
for (double d = dtack * (floor(inf/dtack)-1); d <= dtack * (ceil(sup/dtack)+1); d += dtack)
if (inf-1e-4*range <= d && d <= sup+1e-4*range)
//! Sets Tacks, ntpt, nt4t, *ticklim to a nice division of the plot range.
void CAxis::calc_logticks(vector<double>& Tacks, int& ntpt, int& nt4t, double* ticklim) const
{
if (inf >= sup)
throw "BUG detected by calc_logticks: inf=" + S(inf) + " >= sup=" + S(sup);
if (inf <= 0)
throw S("BUG detected by calc_logticks: negative log argument");
double rlgmin = log10(inf);
double rlgmax = log10(sup);
double rlgrel = rlgmax - rlgmin;
if (!std::isfinite(rlgrel) || rlgrel > 100)
throw "Excessive log range " + S(inf) + " .. " + S(sup);
int incr; // -> decades per tack
if (rlgrel <= 10) {
incr = 1;
} else {
incr = (int)(rlgrel / 7 + 1);
if (incr > 3)
incr = 3 * (incr / 3); // preferred values 3, 6, ...
}
// first and last tack
int minexp = (int)(rlgmin > 0 ? rlgmin + 1e-6 : rlgmin - 1e-6 - 1);
int maxexp = (int)(rlgmax > 0 ? rlgmax + 1e-6 : rlgmax - 1e-6 - 1);
if (incr > 1) {
minexp -= minexp % incr;
maxexp -= maxexp % incr;
}
// set Tacks
double d1 = inf / eins;
double d2 = sup * eins;
Tacks.clear();
for (int i = minexp; i <= maxexp; i += incr) {
double r0 = pow(10., i);
if (d1 <= r0 && r0 <= d2)
Tacks.push_back(r0);
}
nt4t = Tacks.size() + 2;
// set tick limits (may exceed actual tick range)
int ntack = Tacks.size();
if (ntack >= 1) {
ticklim[0] = Tacks.front() / pow(10., incr);
ticklim[1] = Tacks.back() * pow(10., incr);
} else {
// untested. just to allow plots when no tacks in window
ticklim[0] = pow(10., minexp);
ticklim[1] = pow(10., maxexp + 1);
}
// determine #ticks / tack
if (incr == 1 && rlgrel <= 3) {
ntpt = 9;
} else if (incr == 1 && rlgrel <= 7) {
ntpt = 3; // 1-2-5-10 - Schritte
} else if (incr == 1) {
ntpt = 1;
} else {
ntpt = -incr; // this one has special semantics: it means 'incr' ticks per tack
// for narrow ranges, overwrite tacks
if (ntack<2) {
double dtack;
int ntpt_dummy;
calc_lintacks(Tacks, ntpt_dummy, dtack);
}