blob: 4ad9bb23d0da31e2203ffe9876522243b0d899bd [file]
/*
* R : A Computer Language for Statistical Data Analysis
* Copyright (C) 1997--2021 The R Core Team
* Copyright (C) 2002--2009 The R Foundation
* Copyright (C) 1995, 1996 Robert Gentleman and Ross Ihaka
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, a copy is available at
* https://www.R-project.org/Licenses/
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <Defn.h> // Rexp10 (et al)
#include <float.h> /* for DBL_MAX */
#include <Graphics.h>
#include <Print.h>
#include <Rmath.h> // for imax2
/* used in graphics and grid */
SEXP CreateAtVector(double axp[], const double usr[], int nint, Rboolean logflag)
{
/* Create an 'at = ...' vector for axis(.)
* i.e., the vector of tick mark locations,
* when none has been specified (= default).
*
* axp[0:2] = (x1, x2, nInt), where x1..x2 are the extreme tick marks
* {unless in log case, where nInt \in {1,2,3 ; -1,-2,....}
* and the `nint' argument is used *instead*.}
*
* only if(logflag && axp[2] >= 0)
* usr[0:1] is used, additionally
*
* The resulting REAL vector must have length >= 1, ideally >= 2
*/
SEXP at = R_NilValue;/* -Wall*/
double dn, rng, small;
int i, n;
// "arbitrary" threshold: |delta_tick| / SMALL is "barely visible" in plot
#define SMALL_F 100.
if (!logflag || axp[2] < 0) { /* --- linear axis --- Only use axp[] arg. */
n = (int)(fabs(axp[2]) + 0.25);/* >= 0 */
dn = imax2(1, n);
rng = axp[1] - axp[0];
at = allocVector(REALSXP, n + 1);
double a_i;
if(!R_FINITE(rng)) { // need to carefully work around overflow
double at_ = axp[0]/dn; // 2021-07: "/dn" avoids overflow
rng = axp[1]/dn - at_;
small = fabs(rng)/SMALL_F;
#ifdef DEBUG_axis
REprintf("CreateAtVector(axp=(%g,%g, %g), log=F, diff(*)=Inf: at_=%g, rng=%g, small=%g\n",
axp[0],axp[1], axp[2], at_, rng, small);
#endif
int n2 = n/2; // integer division
for (i = 0; i <= n2; i++) { // from the left
a_i = axp[0] + i * rng;
// REprintf(" at[i=%2d]=%g\n", i+1, a_i);
REAL(at)[i] = (fabs(a_i) < small) ? 0. : a_i;
}
for (int i2 = 0; i2 < n-n2; i2++) { // from the right
i = n-i2; // for(i in n:k) where k = n-(n-n2-1) = n2+1
a_i = axp[1] - i2 * rng;
// REprintf(" at[i=%2d]=%g\n", i+1, a_i);
REAL(at)[i] = (fabs(a_i) < small) ? 0. : a_i;
}
}
else { // rng is finite (normal case):
small = fabs(rng)/SMALL_F/dn;
for (i = 0; i <= n; i++) {
a_i = axp[0] + (i / dn) * rng;
REAL(at)[i] = (fabs(a_i) < small) ? 0. : a_i;
}
}
}
else { /* ------ log axis ----- */
Rboolean reversed = FALSE;
double
umin = usr[0],
umax = usr[1];
n = (int)(axp[2] + 0.5);
/* {xy}axp[2] for 'log': GLpretty() [./graphics.c] sets
n < 0: very small scale ==> linear axis, above, or
n = 1,2,3. see switch() below */
#ifdef DEBUG_axis
REprintf("CreateAtVector(axp=(%g,%g,%g), usr=(%g,%g), _log_):",
axp[0],axp[1],axp[2], usr[0],usr[1]);
#endif
if (umin > umax) {
reversed = (axp[0] > axp[1]);
if (reversed) {
/* have *reversed* log axis -- whereas
* the switch(n) { .. } below assumes *increasing* values
* --> reverse axis direction here, and reverse back at end */
umin = usr[1];
umax = usr[0];
dn = axp[0]; axp[0] = axp[1]; axp[1] = dn;
}
else {
/* can the following still happen... ? */
warning("CreateAtVector \"log\"(from axis()): "
"usr[0] = %g > %g = usr[1] !", umin, umax);
}
}
/* allow a fuzz (iff we don't under-/over-flow) since we will do things like 0.2*dn >= umin */
dn = 1 - 1e-12; if(fabs(umin*dn) > 0. ) umin *= dn;
dn = 1 + 1e-12; if(fabs(umax*dn) <= DBL_MAX) umax *= dn;
dn = axp[0];
if (dn < DBL_MIN) {/* was 1e-300; now seems too cautious */
if (dn <= 0) /* real trouble (once for Solaris) later on */
error("CreateAtVector [log-axis()]: axp[0] = %g < 0!", dn);
else
warning("CreateAtVector [log-axis()]: small axp[0] = %g", dn);
}
/* You get the 3 cases below by
* for (y in 1e-5*c(1,2,8)) plot(y, log = "y")
*/
switch(n) {
case 1: /* large range: 1 * 10^k */
{
i = (int)(floor(log10(axp[1])) - ceil(log10(axp[0])) + 0.25);
// want nint intervals, i.e. typically nint+1 breaks :
int ne = i / nint;
/* for nint breaks, i.e. typically nint-1 intervals, would be
* ne = i / imax2(1, nint - 1); *PLUS* replace s/nint/nint-1/ below !! */
#ifdef DEBUG_axis
REprintf(" .. case 1: umin,umax= %g,%g;\n (nint=%d, ne=%d); ",
umin, umax, nint, ne);
if (ne < 1) {
REprintf("ne = %d <= 0 !!\n\t axp[0:1]=(%g,%g) ==> i = %d, nint = %d; ",
ne, axp[0],axp[1], i, nint);
}
#endif
double l10_max = log10(umax),
d0 = l10_max - log10(dn);
#ifdef DEBUG_axis
REprintf("exponent diff d0=%g\n", d0);
#endif
if(ne < 1) ne = 1;
else // if ne is too large, i.e, the "final tick" is beyond umax, reduce it :
while(ne > 1 && nint*ne > d0) {
ne--;
#ifdef DEBUG_axis
REprintf(" last > umax ==> ne--: ne=%d\n", ne);
#endif
}
int k = 1 + ne / 308; // >= 1, typically == 1.
if(k > 1) {// i.e. ne > 308: 10^ne overflows; must split the multiplication
ne = k*(ne/k); // <= ne_{previous}
#ifdef DEBUG_axis
REprintf(" original ne > 308: split in k=%d parts; new ne=%d\n", k,ne);
#endif
}
/* Now, still in exponent-10 range: nint*ne <= d0 = l10_max - log10(dn)
* If difference (=: d1) is "large", say > 3, increase the first at[] =: d0
*/
double d1 = d0 - nint*ne; // >= 0
#ifdef DEBUG_axis
REprintf("expo.diff d0 - nint*ne =: d1=%g\n", d1);
#endif
d0 = dn;
#define Large_D1 5
// === was '3' all up into R 4.1.0
if(d1 > Large_D1) {
d0 = dn * Rexp10(floor(d1/2));
#ifdef DEBUG_axis
REprintf("large d1 => d0 := dn * 10 ^ fl(d1/2) = dn * 10^%d = %g\n",
(int)floor(d1/2), d0);
#endif
}
rng = Rexp10((double)ne/k); // = 10^(ne/k) >= 10
n=0;
dn=d0;
while(dn < umax) {
for(int j=0; j < k; j++)
dn *= rng;
n++;
}
#ifdef DEBUG_axis
REprintf(" rng:=10^(ne/(k=%d)) = %g => n=%d, final dn=%g\n", k, rng, n, dn);
#endif
if (!n)
error("log - axis(), 'at' creation, _LARGE_ range: "
"invalid {xy}axp or par; nint=%d\n"
" axp[0:1]=(%g,%g), usr[0:1]=(%g,%g); i=%d, ni=%d",
nint, axp[0],axp[1], umin,umax, i,ne);
at = allocVector(REALSXP, n);
dn=d0;
for(int i=0; i < n; i++) {
REAL(at)[i] = dn;
for(int j=0; j < k; j++)
dn *= rng;
}
break;
}
case 2: /* medium range: 1, 5 * 10^k */
n = 0;
if (0.5 * dn >= umin) n++;
#ifdef DEBUG_axis
REprintf(" .. case 2: (dn, umin,umax, n) = (%g, %g,%g, %d)\n",
dn, umin, umax, n);
#endif
for (;;) {
if (dn > umax) break;
n++;
if (5 * dn > umax) break;
n++;
dn *= 10;
}
if (!n)
error("log - axis(), 'at' creation, _MEDIUM_ range: "
"invalid {xy}axp or par;\n"
" axp[0]= %g, usr[0:1]=(%g,%g)",
axp[0], umin,umax);
at = allocVector(REALSXP, n);
dn = axp[0];
n = 0;
if (0.5 * dn >= umin) REAL(at)[n++] = 0.5 * dn;
for (;;) {
if (dn > umax) break;
REAL(at)[n++] = dn;
if (5 * dn > umax) break;
REAL(at)[n++] = 5 * dn;
dn *= 10;
}
break;
case 3: /* small range: 1,2,5,10 * 10^k */
n = 0;
if (0.2 * dn >= umin) n++;
if (0.5 * dn >= umin) n++;
for (;;) {
if (dn > umax) break;
n++;
if (2 * dn > umax) break;
n++;
if (5 * dn > umax) break;
n++;
dn *= 10;
}
#ifdef DEBUG_axis
REprintf(" .. case 3: (umin,umax)-usr[*] = (%g, %g); n=%d, dn=%g\n",
umin-usr[reversed? 1: 0],
umax-usr[reversed? 0: 1], n, dn);
#endif
if (!n)
error("log - axis(), 'at' creation, _SMALL_ range: "
"invalid {xy}axp or par;\n"
" axp[0]= %g, usr[0:1]=(%g,%g)",
axp[0], umin,umax);
at = allocVector(REALSXP, n);
dn = axp[0];
n = 0;
if (0.2 * dn >= umin) REAL(at)[n++] = 0.2 * dn;
if (0.5 * dn >= umin) REAL(at)[n++] = 0.5 * dn;
for (;;) {
if (dn > umax) break;
REAL(at)[n++] = dn;
if (2 * dn > umax) break;
REAL(at)[n++] = 2 * dn;
if (5 * dn > umax) break;
REAL(at)[n++] = 5 * dn;
dn *= 10;
}
break;
default:
error("log - axis(), 'at' creation: INVALID {xy}axp[3] = %g",
axp[2]);
}
if (reversed) {/* reverse back again - last assignment was at[n++]= . */
for (i = 0; i < n/2; i++) { /* swap( at[i], at[n-i-1] ) : */
dn = REAL(at)[i];
REAL(at)[i] = REAL(at)[n-i-1];
REAL(at)[n-i-1] = dn;
}
}
} /* linear / log */
return at;
}