| /* |
| * R : A Computer Language for Statistical Data Analysis |
| * Copyright (C) 1997--2018 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 2 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> |
| #include <float.h> /* for DBL_MAX */ |
| #include <Graphics.h> |
| #include <Print.h> |
| #include <Rmath.h> // Rexp10, fmin2, fmax2, imax2 |
| |
| #include "graphics.h" |
| |
| static R_INLINE void TypeCheck(SEXP s, SEXPTYPE type) |
| { |
| if (TYPEOF(s) != type) |
| error("invalid type passed to graphics function"); |
| } |
| |
| |
| /* |
| * Is element i of a colour object NA (or NULL)? |
| */ |
| Rboolean isNAcol(SEXP col, int index, int ncol) |
| { |
| Rboolean result = TRUE; /* -Wall */ |
| |
| if (isNull(col)) |
| result = TRUE; |
| else { |
| if (isLogical(col)) |
| result = LOGICAL(col)[index % ncol] == NA_LOGICAL; |
| else if (isString(col)) |
| result = strcmp(CHAR(STRING_ELT(col, index % ncol)), "NA") == 0; |
| else if (isInteger(col)) |
| result = INTEGER(col)[index % ncol] == NA_INTEGER; |
| else if (isReal(col)) |
| result = !R_FINITE(REAL(col)[index % ncol]); |
| else |
| error(_("invalid color specification")); |
| } |
| return result; |
| } |
| |
| |
| /* P A R A M E T E R U T I L I T I E S */ |
| |
| /* |
| * Extract specified par from list of inline pars |
| */ |
| static SEXP getInlinePar(SEXP s, char *name) |
| { |
| SEXP result = R_NilValue; |
| int found = 0; |
| if (isList(s) && !found) { |
| while (s != R_NilValue) { |
| if (isList(CAR(s))) { |
| result = getInlinePar(CAR(s), name); |
| if (result) |
| found = 1; |
| } else |
| if (TAG(s) != R_NilValue) |
| if (!strcmp(CHAR(PRINTNAME(TAG(s))), name)) { |
| result = CAR(s); |
| found = 1; |
| } |
| s = CDR(s); |
| } |
| } |
| return result; |
| } |
| |
| /* dflt used to be used for < 0 values in R < 2.7.0, |
| now just used for NULL */ |
| static SEXP FixupPch(SEXP pch, int dflt) |
| { |
| int i, n; |
| SEXP ans = R_NilValue;/* -Wall*/ |
| |
| n = length(pch); |
| if (n == 0) return ans = ScalarInteger(dflt); |
| |
| PROTECT(ans = allocVector(INTSXP, n)); |
| if (isList(pch)) { |
| for (i = 0; pch != R_NilValue; pch = CDR(pch)) |
| INTEGER(ans)[i++] = asInteger(CAR(pch)); |
| } |
| else if (isInteger(pch)) { |
| for (i = 0; i < n; i++) |
| INTEGER(ans)[i] = INTEGER(pch)[i]; |
| } |
| else if (isReal(pch)) { |
| for (i = 0; i < n; i++) |
| INTEGER(ans)[i] = R_FINITE(REAL(pch)[i]) ? |
| (int) REAL(pch)[i] : NA_INTEGER; |
| } |
| else if (isString(pch)) { |
| for (i = 0; i < n; i++) { |
| /* New in 2.7.0: negative values indicate Unicode points. */ |
| INTEGER(ans)[i] = GEstring_to_pch(STRING_ELT(pch, i)); |
| } |
| } |
| else if (isLogical(pch)) {/* NA, but not TRUE/FALSE */ |
| for (i = 0; i < n; i++) |
| if(LOGICAL(pch)[i] == NA_LOGICAL) INTEGER(ans)[i] = NA_INTEGER; |
| else error(_("only NA allowed in logical plotting symbol")); |
| } |
| else error(_("invalid plotting symbol")); |
| UNPROTECT(1); |
| return ans; |
| } |
| |
| SEXP FixupLty(SEXP lty, int dflt) |
| { |
| int i, n; |
| SEXP ans; |
| n = length(lty); |
| if (n == 0) { |
| ans = ScalarInteger(dflt); |
| } |
| else { |
| ans = allocVector(INTSXP, n); |
| for (i = 0; i < n; i++) |
| INTEGER(ans)[i] = GE_LTYpar(lty, i); |
| } |
| return ans; |
| } |
| |
| SEXP FixupLwd(SEXP lwd, double dflt) |
| { |
| int i, n; |
| double w; |
| SEXP ans = NULL; |
| |
| n = length(lwd); |
| if (n == 0) |
| ans = ScalarReal(dflt); |
| else { |
| PROTECT(lwd = coerceVector(lwd, REALSXP)); |
| n = length(lwd); |
| ans = allocVector(REALSXP, n); |
| for (i = 0; i < n; i++) { |
| w = REAL(lwd)[i]; |
| if (w < 0) w = NA_REAL; |
| REAL(ans)[i] = w; |
| |
| } |
| UNPROTECT(1); |
| } |
| return ans; |
| } |
| |
| static SEXP FixupFont(SEXP font, int dflt) |
| { |
| int i, k, n; |
| SEXP ans = R_NilValue;/* -Wall*/ |
| n = length(font); |
| if (n == 0) { |
| ans = ScalarInteger(dflt); |
| } |
| else if (isLogical(font)) { |
| ans = allocVector(INTSXP, n); |
| for (i = 0; i < n; i++) { |
| k = LOGICAL(font)[i]; |
| #ifndef Win32 |
| if (k < 1 || k > 5) k = NA_INTEGER; |
| #else |
| if (k < 1 || k > 32) k = NA_INTEGER; |
| #endif |
| INTEGER(ans)[i] = k; |
| } |
| } |
| else if (isInteger(font)) { |
| ans = allocVector(INTSXP, n); |
| for (i = 0; i < n; i++) { |
| k = INTEGER(font)[i]; |
| #ifndef Win32 |
| if (k < 1 || k > 5) k = NA_INTEGER; |
| #else |
| if (k < 1 || k > 32) k = NA_INTEGER; |
| #endif |
| INTEGER(ans)[i] = k; |
| } |
| } |
| else if (isReal(font)) { |
| ans = allocVector(INTSXP, n); |
| for (i = 0; i < n; i++) { |
| k = (int) REAL(font)[i]; |
| #ifndef Win32 |
| if (k < 1 || k > 5) k = NA_INTEGER; |
| #else |
| if (k < 1 || k > 32) k = NA_INTEGER; |
| #endif |
| INTEGER(ans)[i] = k; |
| } |
| } |
| else error(_("invalid font specification")); |
| return ans; |
| } |
| |
| SEXP FixupCol(SEXP col, unsigned int dflt) |
| { |
| int i, n; |
| SEXP ans; |
| unsigned int bg = dpptr(GEcurrentDevice())->bg; /* col = 0 */ |
| |
| n = length(col); |
| if (n == 0) { |
| PROTECT(ans = ScalarInteger(dflt)); |
| } else { |
| ans = PROTECT(allocVector(INTSXP, n)); |
| if (isList(col)) |
| for (i = 0; i < n; i++) { |
| INTEGER(ans)[i] = RGBpar3(CAR(col), 0, bg); |
| col = CDR(col); |
| } |
| else |
| for (i = 0; i < n; i++) |
| INTEGER(ans)[i] = RGBpar3(col, i, bg); |
| } |
| UNPROTECT(1); |
| return ans; |
| } |
| |
| static SEXP FixupCex(SEXP cex, double dflt) |
| { |
| SEXP ans; |
| int i, n; |
| n = length(cex); |
| if (n == 0) { |
| ans = allocVector(REALSXP, 1); |
| if (R_FINITE(dflt) && dflt > 0) |
| REAL(ans)[0] = dflt; |
| else |
| REAL(ans)[0] = NA_REAL; |
| } |
| else { |
| double c; |
| ans = allocVector(REALSXP, n); |
| if (isReal(cex)) |
| for (i = 0; i < n; i++) { |
| c = REAL(cex)[i]; |
| if (R_FINITE(c) && c > 0) |
| REAL(ans)[i] = c; |
| else |
| REAL(ans)[i] = NA_REAL; |
| } |
| else if (isInteger(cex) || isLogical(cex)) |
| for (i = 0; i < n; i++) { |
| c = INTEGER(cex)[i]; |
| if (c == NA_INTEGER || c <= 0) |
| c = NA_REAL; |
| REAL(ans)[i] = c; |
| } |
| else |
| error(_("invalid '%s' value"), "cex"); |
| } |
| return ans; |
| } |
| |
| SEXP FixupVFont(SEXP vfont) { |
| SEXP ans = R_NilValue; |
| if (!isNull(vfont)) { |
| SEXP vf; |
| int typeface, fontindex; |
| int minindex, maxindex=0;/* -Wall*/ |
| int i; |
| PROTECT(vf = coerceVector(vfont, INTSXP)); |
| if (length(vf) != 2) |
| error(_("invalid '%s' value"), "vfont"); |
| typeface = INTEGER(vf)[0]; |
| if (typeface < 1 || typeface > 8) |
| error(_("invalid 'vfont' value [typeface %d]"), typeface); |
| /* For each of the typefaces {1..8}, there are several fontindices |
| available; how many depends on the typeface. |
| The possible combinations are "given" in ./g_fontdb.c |
| and also listed in help(Hershey). |
| */ |
| minindex = 1; |
| switch (typeface) { |
| case 1: /* serif */ |
| maxindex = 7; break; |
| case 2: /* sans serif */ |
| case 7: /* serif symbol */ |
| maxindex = 4; break; |
| case 3: /* script */ |
| maxindex = 3; break; |
| case 4: /* gothic english */ |
| case 5: /* gothic german */ |
| case 6: /* gothic italian */ |
| maxindex = 1; break; |
| case 8: /* sans serif symbol */ |
| maxindex = 2; |
| } |
| fontindex = INTEGER(vf)[1]; |
| if (fontindex < minindex || fontindex > maxindex) |
| error(_("invalid 'vfont' value [typeface = %d, fontindex = %d]"), |
| typeface, fontindex); |
| ans = allocVector(INTSXP, 2); |
| for (i = 0; i < 2; i++) INTEGER(ans)[i] = INTEGER(vf)[i]; |
| UNPROTECT(1); |
| } |
| return ans; |
| } |
| |
| |
| /* GetTextArg() : extract and possibly set text arguments |
| * ("label", col=, cex=, font=) |
| * |
| * Main purpose: Treat things like title(main = list("This Title", font= 4)) |
| * |
| * Called from Title() [only, currently] |
| */ |
| static void |
| GetTextArg(SEXP spec, SEXP *ptxt, rcolor *pcol, double *pcex, int *pfont) |
| { |
| int i, n, font, colspecd; |
| rcolor col; |
| double cex; |
| SEXP txt, nms; |
| PROTECT_INDEX pi; |
| |
| txt = R_NilValue; |
| cex = NA_REAL; |
| col = R_TRANWHITE; |
| colspecd = 0; |
| font = NA_INTEGER; |
| /* It doesn't look as if this protection is needed */ |
| PROTECT_WITH_INDEX(txt, &pi); |
| |
| switch (TYPEOF(spec)) { |
| case LANGSXP: |
| case SYMSXP: |
| REPROTECT(txt = coerceVector(spec, EXPRSXP), pi); |
| break; |
| case VECSXP: |
| if (length(spec) == 0) { |
| *ptxt = R_NilValue; |
| } |
| else { |
| nms = getAttrib(spec, R_NamesSymbol); |
| if (nms == R_NilValue){ /* PR#1939 */ |
| txt = VECTOR_ELT(spec, 0); |
| if (TYPEOF(txt) == LANGSXP || TYPEOF(txt) == SYMSXP ) |
| REPROTECT(txt = coerceVector(txt, EXPRSXP), pi); |
| else if (!isExpression(txt)) |
| REPROTECT(txt = coerceVector(txt, STRSXP), pi); |
| } else { |
| n = length(nms); |
| for (i = 0; i < n; i++) { |
| if (!strcmp(CHAR(STRING_ELT(nms, i)), "cex")) { |
| cex = asReal(VECTOR_ELT(spec, i)); |
| } |
| else if (!strcmp(CHAR(STRING_ELT(nms, i)), "col")) { |
| SEXP colsxp = VECTOR_ELT(spec, i); |
| if (!isNAcol(colsxp, 0, LENGTH(colsxp))) { |
| col = asInteger(FixupCol(colsxp, R_TRANWHITE)); |
| colspecd = 1; |
| } |
| } |
| else if (!strcmp(CHAR(STRING_ELT(nms, i)), "font")) { |
| font = asInteger(FixupFont(VECTOR_ELT(spec, i), NA_INTEGER)); |
| } |
| else if (!strcmp(CHAR(STRING_ELT(nms, i)), "")) { |
| txt = VECTOR_ELT(spec, i); |
| if (TYPEOF(txt) == LANGSXP || TYPEOF(txt) == SYMSXP) |
| REPROTECT(txt = coerceVector(txt, EXPRSXP), pi); |
| else if (!isExpression(txt)) |
| REPROTECT(txt = coerceVector(txt, STRSXP), pi); |
| } |
| else error(_("invalid graphics parameter")); |
| } |
| } |
| } |
| break; |
| case STRSXP: |
| case EXPRSXP: |
| txt = spec; |
| break; |
| default: |
| REPROTECT(txt = coerceVector(spec, STRSXP), pi); |
| break; |
| } |
| UNPROTECT(1); |
| if (txt != R_NilValue) { |
| *ptxt = txt; |
| if (R_FINITE(cex)) *pcex = cex; |
| if (colspecd) *pcol = col; |
| if (font != NA_INTEGER) *pfont = font; |
| } |
| }/* GetTextArg */ |
| |
| |
| /* GRAPHICS FUNCTION ENTRY POINTS */ |
| |
| SEXP C_plot_new(SEXP call, SEXP op, SEXP args, SEXP rho) |
| { |
| /* plot.new() - create a new plot "frame" */ |
| |
| pGEDevDesc dd; |
| |
| dd = GEcurrentDevice(); |
| /* |
| * If user is prompted before new page, user has opportunity |
| * to kill current device. GNewPlot returns (potentially new) |
| * current device. |
| */ |
| dd = GNewPlot(GRecording(call, dd)); |
| |
| dpptr(dd)->xlog = gpptr(dd)->xlog = FALSE; |
| dpptr(dd)->ylog = gpptr(dd)->ylog = FALSE; |
| |
| GScale(0.0, 1.0, 1, dd); |
| GScale(0.0, 1.0, 2, dd); |
| GMapWin2Fig(dd); |
| GSetState(1, dd); |
| |
| if (GRecording(call, dd)) |
| GErecordGraphicOperation(op, args, dd); |
| return R_NilValue; |
| } |
| |
| |
| /* |
| * SYNOPSIS |
| * |
| * plot.window(xlim, ylim, log="", asp=NA) |
| * |
| * DESCRIPTION |
| * |
| * This function sets up the world coordinates for a graphics |
| * window. Note that if asp is a finite positive value then |
| * the window is set up so that one data unit in the y direction |
| * is equal in length to one data unit in the x direction divided |
| * by asp. |
| * |
| * The special case asp == 1 produces plots where distances |
| * between points are represented accurately on screen. |
| * |
| * NOTE |
| * |
| * The use of asp can have weird effects when axis is an |
| * interpreted function. It has to be internal so that the |
| * full computation is captured in the display list. |
| */ |
| |
| SEXP C_plot_window(SEXP args) |
| { |
| SEXP xlim, ylim, logarg; |
| |
| args = CDR(args); |
| if (length(args) < 3) |
| error(_("at least 3 arguments required")); |
| |
| xlim = CAR(args); |
| if (!isNumeric(xlim) || LENGTH(xlim) != 2) |
| error(_("invalid '%s' value"), "xlim"); |
| args = CDR(args); |
| |
| ylim = CAR(args); |
| if (!isNumeric(ylim) || LENGTH(ylim) != 2) |
| error(_("invalid '%s' value"), "ylim"); |
| args = CDR(args); |
| |
| logarg = CAR(args); |
| if (!isString(logarg)) |
| error(_("\"log=\" specification must be character")); |
| Rboolean logscale = FALSE; |
| pGEDevDesc dd = GEcurrentDevice(); |
| const char *p = CHAR(STRING_ELT(logarg, 0)); |
| while (*p) { |
| switch (*p) { |
| case 'x': |
| dpptr(dd)->xlog = gpptr(dd)->xlog = logscale = TRUE; |
| break; |
| case 'y': |
| dpptr(dd)->ylog = gpptr(dd)->ylog = logscale = TRUE; |
| break; |
| default: |
| error(_("invalid \"log=%s\" specification"), p); |
| } |
| p++; |
| } |
| args = CDR(args); |
| |
| double asp = (logscale) ? NA_REAL : asReal(CAR(args));; |
| args = CDR(args); |
| |
| /* This reads [xy]axs and lab, used in GScale */ |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| double xmin, xmax, ymin, ymax; |
| if (isInteger(xlim)) { |
| if (INTEGER(xlim)[0] == NA_INTEGER || INTEGER(xlim)[1] == NA_INTEGER) |
| error(_("NAs not allowed in 'xlim'")); |
| xmin = INTEGER(xlim)[0]; |
| xmax = INTEGER(xlim)[1]; |
| } |
| else { |
| if (!R_FINITE(REAL(xlim)[0]) || !R_FINITE(REAL(xlim)[1])) |
| error(_("need finite 'xlim' values")); |
| xmin = REAL(xlim)[0]; |
| xmax = REAL(xlim)[1]; |
| } |
| if (isInteger(ylim)) { |
| if (INTEGER(ylim)[0] == NA_INTEGER || INTEGER(ylim)[1] == NA_INTEGER) |
| error(_("NAs not allowed in 'ylim'")); |
| ymin = INTEGER(ylim)[0]; |
| ymax = INTEGER(ylim)[1]; |
| } |
| else { |
| if (!R_FINITE(REAL(ylim)[0]) || !R_FINITE(REAL(ylim)[1])) |
| error(_("need finite 'ylim' values")); |
| ymin = REAL(ylim)[0]; |
| ymax = REAL(ylim)[1]; |
| } |
| if ((dpptr(dd)->xlog && (xmin < 0 || xmax < 0)) || |
| (dpptr(dd)->ylog && (ymin < 0 || ymax < 0))) |
| error(_("Logarithmic axis must have positive limits")); |
| |
| if (R_FINITE(asp) && asp > 0) { |
| double pin1, pin2, scale, xdelta, ydelta, xscale, yscale, xadd, yadd; |
| pin1 = GConvertXUnits(1.0, NPC, INCHES, dd); |
| pin2 = GConvertYUnits(1.0, NPC, INCHES, dd); |
| xdelta = fabs(xmax - xmin) / asp; |
| ydelta = fabs(ymax - ymin); |
| if(xdelta == 0.0 && ydelta == 0.0) { |
| /* We really do mean zero: small non-zero values work. |
| Mimic the behaviour of GScale for the x axis. */ |
| xadd = yadd = ((xmin == 0.0) ? 1 : 0.4) * asp; |
| xadd *= asp; |
| } else { |
| xscale = pin1 / xdelta; |
| yscale = pin2 / ydelta; |
| scale = (xscale < yscale) ? xscale : yscale; |
| xadd = .5 * (pin1 / scale - xdelta) * asp; |
| yadd = .5 * (pin2 / scale - ydelta); |
| } |
| if(xmax < xmin) xadd *= -1; |
| if(ymax < ymin) yadd *= -1; |
| GScale(xmin - xadd, xmax + xadd, 1, dd); |
| GScale(ymin - yadd, ymax + yadd, 2, dd); |
| } |
| else { /* asp <= 0 or not finite -- includes logscale ! */ |
| GScale(xmin, xmax, 1, dd); |
| GScale(ymin, ymax, 2, dd); |
| } |
| /* GScale set the [xy]axp parameters */ |
| GMapWin2Fig(dd); |
| GRestorePars(dd); |
| /* This has now clobbered the Rf_ggptr settings for coord system */ |
| return R_NilValue; |
| } |
| |
| |
| //--- axis(side, at, labels, ...) ----------------------------------------- |
| |
| static void GetAxisLimits(double left, double right, Rboolean logflag, |
| // => compute |
| double *low, double *high) |
| { |
| /* Called from C_axis() such as |
| * GetAxisLimits(gpptr(dd)->usr[0], gpptr(dd)->usr[1], &low, &high) |
| * |
| * Computes *low < left, right < *high (even if left=right) |
| */ |
| double eps; |
| if (logflag) { |
| left = log(left); |
| right = log(right); |
| } |
| if (left > right) {/* swap */ |
| eps = left; left = right; right = eps; |
| } |
| eps = right - left; |
| if (eps == 0.) |
| eps = 0.5 * FLT_EPSILON; |
| else |
| eps *= FLT_EPSILON; |
| *low = left - eps; |
| *high = right + eps; |
| |
| if (logflag) { |
| *low = exp(*low); |
| *high = exp(*high); |
| } |
| } |
| |
| SEXP labelformat(SEXP labels) |
| { |
| /* format(labels): i.e. from numbers to strings */ |
| SEXP ans = R_NilValue;/* -Wall*/ |
| int i, n, w, d, e, wi, di, ei; |
| const char *strp; |
| n = length(labels); |
| R_print.digits = 7;/* maximally 7 digits -- ``burnt in''; |
| S-PLUS <= 5.x has about 6 |
| (but really uses single precision..) */ |
| switch(TYPEOF(labels)) { |
| case LGLSXP: |
| PROTECT(ans = allocVector(STRSXP, n)); |
| for (i = 0; i < n; i++) { |
| strp = EncodeLogical(LOGICAL(labels)[i], 0); |
| SET_STRING_ELT(ans, i, mkChar(strp)); |
| } |
| UNPROTECT(1); |
| break; |
| case INTSXP: |
| PROTECT(ans = allocVector(STRSXP, n)); |
| for (i = 0; i < n; i++) { |
| strp = EncodeInteger(INTEGER(labels)[i], 0); |
| SET_STRING_ELT(ans, i, mkChar(strp)); |
| } |
| UNPROTECT(1); |
| break; |
| case REALSXP: |
| formatReal(REAL(labels), n, &w, &d, &e, 0); |
| PROTECT(ans = allocVector(STRSXP, n)); |
| for (i = 0; i < n; i++) { |
| strp = EncodeReal0(REAL(labels)[i], 0, d, e, OutDec); |
| SET_STRING_ELT(ans, i, mkChar(strp)); |
| } |
| UNPROTECT(1); |
| break; |
| case CPLXSXP: |
| formatComplex(COMPLEX(labels), n, &w, &d, &e, &wi, &di, &ei, 0); |
| PROTECT(ans = allocVector(STRSXP, n)); |
| for (i = 0; i < n; i++) { |
| strp = EncodeComplex(COMPLEX(labels)[i], 0, d, e, 0, di, ei, |
| OutDec); |
| SET_STRING_ELT(ans, i, mkChar(strp)); |
| } |
| UNPROTECT(1); |
| break; |
| case STRSXP: |
| PROTECT(ans = allocVector(STRSXP, n)); |
| for (i = 0; i < n; i++) { |
| SET_STRING_ELT(ans, i, STRING_ELT(labels, i)); |
| } |
| UNPROTECT(1); |
| break; |
| default: |
| error(_("invalid type for axis labels")); |
| } |
| return ans; |
| } |
| |
| |
| static double ComputePAdjValue(double padj, int side, int las) |
| { |
| if (!R_FINITE(padj)) { |
| switch(las) { |
| case 0:/* parallel to axis */ |
| padj = 0.0; break; |
| case 1:/* horizontal */ |
| switch(side) { |
| case 1: |
| case 3: padj = 0.0; break; |
| case 2: |
| case 4: padj = 0.5; break; |
| } |
| break; |
| case 2:/* perpendicular to axis */ |
| padj = 0.5; break; |
| case 3:/* vertical */ |
| switch(side) { |
| case 1: |
| case 3: padj = 0.5; break; |
| case 2: |
| case 4: padj = 0.0; break; |
| } |
| break; |
| } |
| } |
| return padj; |
| } |
| |
| static void getxlimits(double *x, pGEDevDesc dd) { |
| /* |
| * xpd = 0 means clip to current plot region |
| * xpd = 1 means clip to current figure region |
| * xpd = 2 means clip to device region |
| */ |
| switch (gpptr(dd)->xpd) { |
| case 0: |
| x[0] = gpptr(dd)->usr[0]; |
| x[1] = gpptr(dd)->usr[1]; |
| break; |
| case 1: |
| x[0] = GConvertX(0, NFC, USER, dd); |
| x[1] = GConvertX(1, NFC, USER, dd); |
| break; |
| case 2: |
| x[0] = GConvertX(0, NDC, USER, dd); |
| x[1] = GConvertX(1, NDC, USER, dd); |
| break; |
| } |
| } |
| |
| static void getylimits(double *y, pGEDevDesc dd) { |
| switch (gpptr(dd)->xpd) { |
| case 0: |
| y[0] = gpptr(dd)->usr[2]; |
| y[1] = gpptr(dd)->usr[3]; |
| break; |
| case 1: |
| y[0] = GConvertY(0, NFC, USER, dd); |
| y[1] = GConvertY(1, NFC, USER, dd); |
| break; |
| case 2: |
| y[0] = GConvertY(0, NDC, USER, dd); |
| y[1] = GConvertY(1, NDC, USER, dd); |
| break; |
| } |
| } |
| |
| SEXP C_axis(SEXP args) |
| { |
| /* axis(side, at, labels, tick, line, pos, |
| outer, font, lty, lwd, lwd.ticks, col, col.ticks, |
| hadj, padj, gap.axis, ...) |
| */ |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| /* Arity Check */ |
| /* This is a builtin function, so it should always have */ |
| /* the correct arity, but it doesn't hurt to be defensive. */ |
| |
| args = CDR(args); |
| if (length(args) < 16) |
| error(_("too few arguments")); |
| GCheckState(dd); |
| |
| PrintDefaults(); /* prepare for labelformat */ |
| |
| /* Required argument: "side" */ |
| /* Which side of the plot the axis is to appear on. */ |
| /* side = 1 | 2 | 3 | 4. */ |
| |
| int side = asInteger(CAR(args)); |
| if (side < 1 || side > 4) |
| error(_("invalid axis number %d"), side); |
| Rboolean x_axis = (side == 1 || side == 3); |
| args = CDR(args); |
| |
| /* Required argument: "at" */ |
| /* This gives the tick-label locations. */ |
| /* Note that these are coerced to the correct type below. */ |
| |
| SEXP at = CAR(args); args = CDR(args); |
| |
| /* Required argument: "labels" */ |
| /* Labels can be a logical, indicating whether or not */ |
| /* to label the axis; or it can be a vector of character */ |
| /* strings or expressions which give the labels explicitly. */ |
| /* The expressions are used to set mathematical labelling. */ |
| |
| Rboolean dolabels = TRUE; |
| SEXP lab = CAR(args); |
| int i; |
| if (isLogical(lab) && length(lab) > 0) { |
| i = asLogical(lab); |
| if (i == 0 || i == NA_LOGICAL) |
| dolabels = FALSE; |
| lab = R_NilValue; |
| } else if (TYPEOF(lab) == LANGSXP || TYPEOF(lab) == SYMSXP) { |
| lab = coerceVector(lab, EXPRSXP); |
| } else if (isExpression(lab)) { |
| } else { |
| lab = coerceVector(lab, STRSXP); |
| } |
| PROTECT(lab); |
| args = CDR(args); |
| |
| /* Required argument "tick" : |
| * doticks := whether or not ticks and the axis line should be plotted: |
| * TRUE => show, FALSE => don't show. */ |
| int larg = asLogical(CAR(args)); |
| Rboolean doticks = (larg == NA_LOGICAL) ? TRUE : (Rboolean) larg; |
| args = CDR(args); |
| |
| /* Optional argument: "line" */ |
| |
| /* Specifies an offset outward from the plot for the axis. |
| * The values in the par value "mgp" are interpreted |
| * relative to this value. */ |
| double line = asReal(CAR(args)); |
| /* defer processing until after in-line pars */ |
| args = CDR(args); |
| |
| /* Optional argument: "pos" */ |
| /* Specifies a user coordinate at which the axis should be drawn. */ |
| /* This overrides the value of "line". Again the "mgp" par values */ |
| /* are interpreted relative to this value. */ |
| |
| double pos = asReal(CAR(args)); |
| /* defer processing until after in-line pars */ |
| args = CDR(args); |
| |
| /* Optional argument: "outer" */ |
| /* Should the axis be drawn in the outer margin. */ |
| /* This only affects the computation of axis_base. */ |
| larg = asLogical(CAR(args)); |
| GUnit outer = (larg == NA_LOGICAL || larg == 0) ? NPC : NIC ; |
| args = CDR(args); |
| |
| /* Optional argument: "font" */ |
| int font = asInteger(FixupFont(CAR(args), NA_INTEGER)); |
| args = CDR(args); |
| |
| /* Optional argument: "lty" */ |
| int lty = asInteger(FixupLty(CAR(args), 0)); |
| args = CDR(args); |
| |
| // Optional arguments "lwd", "lwd.ticks" : |
| double lwd = asReal(FixupLwd(CAR(args), 1)); |
| args = CDR(args); |
| double lwdticks = asReal(FixupLwd(CAR(args), 1)); |
| args = CDR(args); |
| |
| // Optional arguments "col", "col.ticks" : |
| rcolor col = asInteger(FixupCol(CAR(args), gpptr(dd)->fg)); |
| args = CDR(args); |
| rcolor colticks = asInteger(FixupCol(CAR(args), col)); |
| args = CDR(args); |
| |
| /* Optional argument: "hadj" */ |
| if (length(CAR(args)) != 1) |
| error(_("'hadj' must be of length one")); |
| double hadj = asReal(CAR(args)); |
| args = CDR(args); |
| |
| /* Optional argument: "padj" */ |
| SEXP padj = PROTECT(coerceVector(CAR(args), REALSXP)); |
| int npadj = length(padj); |
| if (npadj <= 0) error(_("zero-length '%s' specified"), "padj"); |
| args = CDR(args); |
| |
| /* Optional argument: "gap.axis" */ |
| if (length(CAR(args)) != 1) |
| error(_("'gap.axis' must be of length one")); |
| double gap = asReal(CAR(args)); |
| |
| /* Now we process all the remaining inline par values: |
| we need to do it now as x/yaxp are retrieved next. |
| That will set gpptr, so we update that first - do_plotwindow |
| clobbered the gpptr settings. */ |
| GSavePars(dd); |
| gpptr(dd)->xaxp[0] = dpptr(dd)->xaxp[0]; |
| gpptr(dd)->xaxp[1] = dpptr(dd)->xaxp[1]; |
| gpptr(dd)->xaxp[2] = dpptr(dd)->xaxp[2]; |
| gpptr(dd)->yaxp[0] = dpptr(dd)->yaxp[0]; |
| gpptr(dd)->yaxp[1] = dpptr(dd)->yaxp[1]; |
| gpptr(dd)->yaxp[2] = dpptr(dd)->yaxp[2]; |
| ProcessInlinePars(args, dd); |
| /* Notably, the axis-labels-only relevant |
| R's par() C code below |
| ----------- ------------------- |
| "font.axis" => gpptr(dd)->fontaxis |
| "cex.axis" => gpptr(dd)->cexaxis |
| "col.axis" => gpptr(dd)->colaxis |
| */ |
| |
| Rboolean perpendicular = |
| x_axis |
| ? (gpptr(dd)->las == 2 || gpptr(dd)->las == 3) |
| : (gpptr(dd)->las == 1 || gpptr(dd)->las == 2); |
| if (ISNAN(gap)) // default |
| gap = perpendicular ? 0.25 : 1.0; |
| else if (!R_FINITE(gap)) |
| error(_("'gap.axis' must be NA or a finite number")); |
| |
| /* Retrieve relevant "par" values. */ |
| double axp[3], usr[2]; |
| Rboolean logflag = FALSE; |
| int nint = 0; |
| if(x_axis) { |
| axp[0] = gpptr(dd)->xaxp[0]; |
| axp[1] = gpptr(dd)->xaxp[1]; |
| axp[2] = gpptr(dd)->xaxp[2]; |
| usr[0] = dpptr(dd)->usr[0]; |
| usr[1] = dpptr(dd)->usr[1]; |
| logflag = dpptr(dd)->xlog; |
| nint = dpptr(dd)->lab[0]; |
| } else { // y axis |
| axp[0] = gpptr(dd)->yaxp[0]; |
| axp[1] = gpptr(dd)->yaxp[1]; |
| axp[2] = gpptr(dd)->yaxp[2]; |
| usr[0] = dpptr(dd)->usr[2]; |
| usr[1] = dpptr(dd)->usr[3]; |
| logflag = dpptr(dd)->ylog; |
| nint = dpptr(dd)->lab[1]; |
| } |
| |
| /* Deferred processing */ |
| int lineoff = 0; |
| if (!R_FINITE(line)) { |
| /* Except that here mgp values are not relative to themselves */ |
| line = gpptr(dd)->mgp[2]; |
| lineoff = (int) line; |
| } |
| if (!R_FINITE(pos)) pos = NA_REAL; else lineoff = 0; |
| |
| /* Determine the tickmark positions. Note that these may fall */ |
| /* outside the plot window. We will clip them in the code below. */ |
| |
| Rboolean create_at = isNull(at); |
| if (create_at) |
| at = CreateAtVector(axp, usr, nint, logflag); |
| else |
| at = isReal(at) ? duplicate(at) : coerceVector(at, REALSXP); |
| PROTECT(at); |
| int n = length(at); // to become #{finite 'at'} below |
| |
| /* Check/setup the tick labels. This can mean using user-specified */ |
| /* labels, or encoding the "at" positions as strings. */ |
| |
| if (dolabels) { |
| if (length(lab) == 0) |
| lab = labelformat(at); |
| else { |
| if (create_at) |
| error(_("'labels' is supplied and not 'at'")); |
| if (!isExpression(lab)) lab = labelformat(lab); |
| } |
| if (length(at) != length(lab)) |
| error(_("'at' and 'labels' lengths differ, %d != %d"), |
| length(at), length(lab)); |
| |
| gpptr(dd)->font = (font == NA_INTEGER)? gpptr(dd)->fontaxis : font; |
| gpptr(dd)->cex = gpptr(dd)->cexbase * gpptr(dd)->cexaxis; |
| } |
| PROTECT(lab); |
| |
| /* Check there are no NA, Inf or -Inf values for tick positions. */ |
| /* The code here is long-winded. Couldn't we just inline things */ |
| /* below. Hmmm - we need the min and max of the finite values ... */ |
| |
| int *ind = (int *) R_alloc(n, sizeof(int)); |
| for(i = 0; i < n; i++) ind[i] = i; |
| rsort_with_index(REAL(at), ind, n); |
| int ntmp = 0; |
| for(i = 0; i < n; i++) { |
| if(R_FINITE(REAL(at)[i])) ntmp = i+1; |
| } |
| if (n > 0 && ntmp == 0) |
| error(_("no locations are finite")); |
| n = ntmp; |
| |
| /* Ok, all systems are "GO". Let's get to it. */ |
| #ifdef DEBUG_axis |
| REprintf("C_axis(side=%d): n=%d finite 'at' locations = (%g <= .. <= %g);\n" |
| " x_ax=%s, las=%d, perpendicular=%s, gap = %g,\n", |
| side, n, REAL(at)[0], REAL(at)[n-1], |
| x_axis?"TRUE":"FALSE", gpptr(dd)->las, |
| perpendicular?"TRUE":"FALSE", gap); |
| #endif |
| /* At this point we know the value of "xaxt" and "yaxt", |
| * so we test to see whether the relevant one is "n". |
| * If it is, we just bail out at this point. */ |
| if ((n == 0) || |
| ( x_axis && gpptr(dd)->xaxt == 'n') || |
| (!x_axis && gpptr(dd)->yaxt == 'n')) { |
| GRestorePars(dd); |
| UNPROTECT(4); |
| return R_NilValue; |
| } |
| |
| gpptr(dd)->lty = lty; |
| gpptr(dd)->lwd = lwd; |
| |
| double low, high, limits[2]; |
| /* Draw the axis */ |
| GMode(1, dd); |
| if(x_axis) { //--- x-axis -- horizontal ====================================== |
| /* First set the clipping limits */ |
| getxlimits(limits, dd); |
| /* Now override par("xpd") and force clipping to device region. */ |
| gpptr(dd)->xpd = 2; |
| GetAxisLimits(limits[0], limits[1], logflag, &low, &high); |
| double axis_base, tck_offset, |
| axis_low = GConvertX(fmin2(high, fmax2(low, REAL(at)[ 0 ])), USER, NFC, dd), |
| axis_high = GConvertX(fmin2(high, fmax2(low, REAL(at)[n-1])), USER, NFC, dd); |
| if (side == 1) { |
| if (R_FINITE(pos)) |
| axis_base = GConvertY(pos, USER, NFC, dd); |
| else |
| axis_base = GConvertY(0.0, outer, NFC, dd) |
| - GConvertYUnits(line, LINES, NFC, dd); |
| if (doticks) { |
| if (R_FINITE(gpptr(dd)->tck)) { |
| double len; |
| if(gpptr(dd)->tck > 0.5) |
| len = GConvertYUnits(gpptr(dd)->tck, NPC, NFC, dd); |
| else { |
| double |
| xu = GConvertXUnits(gpptr(dd)->tck, NPC, INCHES, dd), |
| yu = GConvertYUnits(gpptr(dd)->tck, NPC, INCHES, dd); |
| xu = (fabs(xu) < fabs(yu)) ? xu : yu; |
| len = GConvertYUnits(xu, INCHES, NFC, dd); |
| } |
| tck_offset = + len; |
| } else |
| tck_offset = + GConvertYUnits(gpptr(dd)->tcl, LINES, NFC, dd); |
| } |
| } |
| else { // side == 3 : |
| if (R_FINITE(pos)) |
| axis_base = GConvertY(pos, USER, NFC, dd); |
| else |
| axis_base = GConvertY(1.0, outer, NFC, dd) |
| + GConvertYUnits(line, LINES, NFC, dd); |
| if (doticks) { |
| if (R_FINITE(gpptr(dd)->tck)) { |
| double len; |
| if(gpptr(dd)->tck > 0.5) |
| len = GConvertYUnits(gpptr(dd)->tck, NPC, NFC, dd); |
| else { |
| double |
| xu = GConvertXUnits(gpptr(dd)->tck, NPC, INCHES, dd), |
| yu = GConvertYUnits(gpptr(dd)->tck, NPC, INCHES, dd); |
| xu = (fabs(xu) < fabs(yu)) ? xu : yu; |
| len = GConvertYUnits(xu, INCHES, NFC, dd); |
| } |
| tck_offset = - len; |
| } else |
| tck_offset = - GConvertYUnits(gpptr(dd)->tcl, LINES, NFC, dd); |
| } |
| } |
| if (doticks) { |
| gpptr(dd)->col = col; |
| if (lwd > 0.0) |
| GLine(axis_low, axis_base, axis_high, axis_base, NFC, dd); |
| gpptr(dd)->col = colticks; |
| gpptr(dd)->lwd = lwdticks; |
| double axis_tick = axis_base + tck_offset; |
| if (lwdticks > 0) { |
| for (i = 0; i < n; i++) { |
| double x = REAL(at)[i]; |
| if (low <= x && x <= high) { |
| x = GConvertX(x, USER, NFC, dd); |
| GLine(x, axis_base, x, axis_tick, NFC, dd); |
| } |
| } |
| } |
| } |
| |
| if (dolabels) { // Tickmark labels. ------------------------------------ |
| double axis_lab, tlast = -1.0; |
| gap *= (perpendicular |
| ? GConvertXUnits(GStrHeight("m", CE_ANY, DEVICE, dd), |
| DEVICE, NFC, dd) |
| : GStrWidth ("m", CE_ANY, NFC, dd)); |
| #ifdef DEBUG_axis |
| REprintf(" gap=%g\n", gap); |
| #endif |
| gpptr(dd)->col = gpptr(dd)->colaxis; |
| gpptr(dd)->adj = |
| R_FINITE(hadj) ? hadj |
| : (perpendicular ? ((side == 1) ? 1 : 0) |
| : 0.5); |
| if (side == 1) { |
| axis_lab = - axis_base |
| + GConvertYUnits(gpptr(dd)->mgp[1]-lineoff, LINES, NFC, dd) |
| + GConvertY(0.0, NPC, NFC, dd); |
| } |
| else { /* side == 3 */ |
| axis_lab = axis_base |
| + GConvertYUnits(gpptr(dd)->mgp[1]-lineoff, LINES, NFC, dd) |
| - GConvertY(1.0, NPC, NFC, dd); |
| } |
| axis_lab = GConvertYUnits(axis_lab, NFC, LINES, dd); |
| |
| /* The order of processing is important here. */ |
| /* We must ensure that the labels are drawn left-to-right. */ |
| /* The logic here is getting way too convoluted. */ |
| /* This needs a serious rewrite. */ |
| int istart, iend, incr; |
| if (gpptr(dd)->usr[0] > gpptr(dd)->usr[1]) { |
| istart = n - 1; |
| iend = -1; |
| incr = -1; |
| } |
| else { |
| istart = 0; |
| iend = n; |
| incr = 1; |
| } |
| for (i = istart; i != iend; i += incr) { |
| double x = REAL(at)[i]; |
| if (!R_FINITE(x)) continue; |
| double padjval = REAL(padj)[i % npadj]; |
| padjval = ComputePAdjValue(padjval, side, gpptr(dd)->las); |
| /* Clip tick labels to user coordinates. */ |
| if (low < x && x < high) { |
| if (isExpression(lab)) { |
| GMMathText(VECTOR_ELT(lab, ind[i]), side, |
| axis_lab, 0, x, gpptr(dd)->las, |
| padjval, dd); |
| } |
| else { |
| SEXP label = STRING_ELT(lab, ind[i]); |
| if(label != NA_STRING) { |
| const char *ss = CHAR(label); |
| double // NFC coord |
| temp = GConvertX(x, USER, NFC, dd), |
| labw = (perpendicular |
| ? GConvertXUnits( |
| GStrHeight(ss, getCharCE(label), DEVICE, dd), |
| DEVICE, NFC, dd) |
| : GStrWidth (ss, getCharCE(label), NFC, dd)), |
| tnew = temp - 0.5 * labw; |
| #ifdef DEBUG_axis |
| REprintf("tnew-tlast = %9g-%9g=%9g %2s gap\n", tnew, tlast, |
| tnew-tlast, (tnew - tlast >= gap) ? ">=" : "<"); |
| #endif |
| if (tnew - tlast >= gap) { |
| GMtext(ss, getCharCE(label), |
| side, axis_lab, 0, x, |
| gpptr(dd)->las, padjval, dd); |
| tlast = temp + 0.5 *labw;// == tnew + labw |
| } |
| } |
| } |
| } |
| } |
| } // if(dolabels) |
| } |
| else { //--- y-axis -- vertical ============================================= |
| /* First set the clipping limits */ |
| getylimits(limits, dd); |
| /* Now override par("xpd") and force clipping to device region. */ |
| gpptr(dd)->xpd = 2; |
| GetAxisLimits(limits[0], limits[1], logflag, &low, &high); |
| double axis_base, tck_offset, |
| axis_low = GConvertY(fmin2(high, fmax2(low, REAL(at)[ 0 ])), USER, NFC, dd), |
| axis_high = GConvertY(fmin2(high, fmax2(low, REAL(at)[n-1])), USER, NFC, dd); |
| if (side == 2) { |
| if (R_FINITE(pos)) |
| axis_base = GConvertX(pos, USER, NFC, dd); |
| else |
| axis_base = GConvertX(0.0, outer, NFC, dd) |
| - GConvertXUnits(line, LINES, NFC, dd); |
| if (doticks) { |
| if (R_FINITE(gpptr(dd)->tck)) { |
| double len; |
| if(gpptr(dd)->tck > 0.5) |
| len = GConvertXUnits(gpptr(dd)->tck, NPC, NFC, dd); |
| else { |
| double |
| xu = GConvertXUnits(gpptr(dd)->tck, NPC, INCHES, dd), |
| yu = GConvertYUnits(gpptr(dd)->tck, NPC, INCHES, dd); |
| xu = (fabs(xu) < fabs(yu)) ? xu : yu; |
| len = GConvertXUnits(xu, INCHES, NFC, dd); |
| } |
| tck_offset = + len; |
| } else |
| tck_offset = + GConvertXUnits(gpptr(dd)->tcl, LINES, NFC, dd); |
| } |
| } |
| else { // side == 4 : |
| if (R_FINITE(pos)) |
| axis_base = GConvertX(pos, USER, NFC, dd); |
| else |
| axis_base = GConvertX(1.0, outer, NFC, dd) |
| + GConvertXUnits(line, LINES, NFC, dd); |
| if (doticks) { |
| if (R_FINITE(gpptr(dd)->tck)) { |
| double len; |
| if(gpptr(dd)->tck > 0.5) |
| len = GConvertXUnits(gpptr(dd)->tck, NPC, NFC, dd); |
| else { |
| double |
| xu = GConvertXUnits(gpptr(dd)->tck, NPC, INCHES, dd), |
| yu = GConvertYUnits(gpptr(dd)->tck, NPC, INCHES, dd); |
| xu = (fabs(xu) < fabs(yu)) ? xu : yu; |
| len = GConvertXUnits(xu, INCHES, NFC, dd); |
| } |
| tck_offset = - len; |
| } else |
| tck_offset = - GConvertXUnits(gpptr(dd)->tcl, LINES, NFC, dd); |
| } |
| } |
| if (doticks) { |
| gpptr(dd)->col = col; |
| if (lwd > 0.0) |
| GLine(axis_base, axis_low, axis_base, axis_high, NFC, dd); |
| gpptr(dd)->col = colticks; |
| gpptr(dd)->lwd = lwdticks; |
| double axis_tick = axis_base + tck_offset; |
| if (lwdticks > 0) { |
| for (i = 0; i < n; i++) { |
| double y = REAL(at)[i]; |
| if (low <= y && y <= high) { |
| y = GConvertY(y, USER, NFC, dd); |
| GLine(axis_base, y, axis_tick, y, NFC, dd); |
| } |
| } |
| } |
| } |
| if (dolabels) { // Tickmark labels. ------------------------------------ |
| double axis_lab, tlast = -1.0; |
| gap *= (perpendicular |
| ? GStrHeight("m", CE_ANY, NFC, dd) |
| : GConvertYUnits(GStrWidth ("m", CE_ANY, DEVICE, dd), |
| DEVICE, NFC, dd)); |
| # ifdef DEBUG_axis |
| REprintf(" gap=%g\n", gap); |
| # endif |
| gpptr(dd)->col = gpptr(dd)->colaxis; |
| gpptr(dd)->adj = |
| R_FINITE(hadj) ? hadj |
| : (perpendicular ? ((side == 2) ? 1 : 0) |
| : 0.5); |
| if (side == 2) { |
| axis_lab = - axis_base |
| + GConvertXUnits(gpptr(dd)->mgp[1]-lineoff, LINES, NFC, dd) |
| + GConvertX(0.0, NPC, NFC, dd); |
| } |
| else { /* side == 4 */ |
| axis_lab = axis_base |
| + GConvertXUnits(gpptr(dd)->mgp[1]-lineoff, LINES, NFC, dd) |
| - GConvertX(1.0, NPC, NFC, dd); |
| } |
| axis_lab = GConvertXUnits(axis_lab, NFC, LINES, dd); |
| |
| /* The order of processing is important here. */ |
| /* We must ensure that the labels are drawn left-to-right. */ |
| /* The logic here is getting way too convoluted. */ |
| /* This needs a serious rewrite. */ |
| int istart, iend, incr; |
| if (gpptr(dd)->usr[2] > gpptr(dd)->usr[3]) { |
| istart = n - 1; |
| iend = -1; |
| incr = -1; |
| } |
| else { |
| istart = 0; |
| iend = n; |
| incr = 1; |
| } |
| for (i = istart; i != iend; i += incr) { |
| double y = REAL(at)[i]; |
| if (!R_FINITE(y)) continue; |
| double padjval = REAL(padj)[i % npadj]; |
| padjval = ComputePAdjValue(padjval, side, gpptr(dd)->las); |
| /* Clip tick labels to user coordinates. */ |
| if (low < y && y < high) { |
| if (isExpression(lab)) { |
| GMMathText(VECTOR_ELT(lab, ind[i]), side, |
| axis_lab, 0, y, gpptr(dd)->las, |
| padjval, dd); |
| } |
| else { |
| SEXP label = STRING_ELT(lab, ind[i]); |
| if(label != NA_STRING) { |
| const char *ss = CHAR(label); |
| double // NFC coord |
| temp = GConvertY(y, USER, NFC, dd), |
| labw = (perpendicular |
| ? GStrHeight(ss, getCharCE(label), NFC, dd) |
| : GConvertYUnits( |
| GStrWidth (ss, getCharCE(label), DEVICE, dd), |
| DEVICE, NFC, dd)), |
| tnew = temp - 0.5 * labw; |
| #ifdef DEBUG_axis |
| REprintf("tnew-tlast = %9g-%9g=%9g %2s gap\n", tnew, tlast, |
| tnew-tlast, (tnew - tlast >= gap) ? ">=" : "<"); |
| #endif |
| if (tnew - tlast >= gap) { |
| GMtext(ss, getCharCE(label), |
| side, axis_lab, 0, y, |
| gpptr(dd)->las, padjval, dd); |
| tlast = temp + 0.5 *labw;// == tnew + labw |
| } |
| } |
| } |
| } |
| } |
| } // if(dolabels) |
| } // else (y - axis) |
| GMode(0, dd); |
| GRestorePars(dd); |
| UNPROTECT(4); /* lab, at, lab, padj again */ |
| return at; |
| } /* C_axis */ |
| |
| |
| SEXP C_plotXY(SEXP args) |
| { |
| /* plot.xy(xy, type, pch, lty, col, bg, cex, lwd, ...) |
| |
| * plot points or lines of various types |
| */ |
| SEXP sxy, sx, sy, pch, cex, col, bg, lty, lwd; |
| double *x, *y, xold, yold, xx, yy, thiscex, thislwd; |
| int i, n, npch, ncex, ncol, nbg, nlwd, type=0, start=0, thispch; |
| rcolor thiscol, thisbg; |
| const void *vmax = NULL /* -Wall */; |
| |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| /* Basic Checks */ |
| GCheckState(dd); |
| args = CDR(args); |
| if (length(args) < 7) |
| error(_("too few arguments")); |
| |
| /* Required Arguments */ |
| #define PLOT_XY_DEALING(subname) \ |
| sx = R_NilValue; /* -Wall */ \ |
| sy = R_NilValue; /* -Wall */ \ |
| sxy = CAR(args); \ |
| if (isNewList(sxy) && length(sxy) >= 2) { \ |
| TypeCheck(sx = VECTOR_ELT(sxy, 0), REALSXP); \ |
| TypeCheck(sy = VECTOR_ELT(sxy, 1), REALSXP); \ |
| } \ |
| else if (isList(sxy) && length(sxy) >= 2) { \ |
| TypeCheck(sx = CAR(sxy), REALSXP); \ |
| TypeCheck(sy = CADR(sxy), REALSXP); \ |
| } \ |
| else \ |
| error(_("invalid plotting structure")); \ |
| if (LENGTH(sx) != LENGTH(sy)) \ |
| error(_("'x' and 'y' lengths differ in %s()"), subname);\ |
| n = LENGTH(sx); \ |
| args = CDR(args) |
| |
| PLOT_XY_DEALING("plot.xy"); |
| |
| if (isNull(CAR(args))) type = 'p'; |
| else { |
| if (isString(CAR(args)) && LENGTH(CAR(args)) == 1 && |
| LENGTH(pch = STRING_ELT(CAR(args), 0)) >= 1) { |
| if(LENGTH(pch) > 1) |
| warning(_("plot type '%s' will be truncated to first character"), |
| CHAR(pch)); |
| type = CHAR(pch)[0]; |
| } |
| else error(_("invalid plot type")); |
| } |
| args = CDR(args); |
| |
| PROTECT(pch = FixupPch(CAR(args), gpptr(dd)->pch)); |
| npch = length(pch); |
| args = CDR(args); |
| |
| PROTECT(lty = FixupLty(CAR(args), gpptr(dd)->lty)); |
| args = CDR(args); |
| |
| /* Default col was NA_INTEGER (0x80000000) which was interpreted |
| as zero (black) or "don't draw" depending on line/rect/circle |
| situation. Now we set the default to zero and don't plot at all |
| if col==NA. |
| |
| FIXME: bg needs similar change, but that requires changes to |
| the specific drivers. */ |
| |
| PROTECT(col = FixupCol(CAR(args), 0)); args = CDR(args); |
| ncol = LENGTH(col); |
| |
| PROTECT(bg = FixupCol(CAR(args), R_TRANWHITE)); args = CDR(args); |
| nbg = LENGTH(bg); |
| |
| PROTECT(cex = FixupCex(CAR(args), 1.0)); args = CDR(args); |
| ncex = LENGTH(cex); |
| |
| PROTECT(lwd = FixupLwd(CAR(args), gpptr(dd)->lwd)); args = CDR(args); |
| nlwd = LENGTH(lwd); |
| |
| /* Miscellaneous Graphical Parameters */ |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| x = REAL(sx); |
| y = REAL(sy); |
| |
| if (INTEGER(lty)[0] != NA_INTEGER) |
| gpptr(dd)->lty = INTEGER(lty)[0]; |
| if (R_FINITE( (thislwd = REAL(lwd)[0]) )) |
| gpptr(dd)->lwd = thislwd; /* but do recycle for "p" etc */ |
| |
| GMode(1, dd); |
| |
| /* Line drawing :*/ |
| switch(type) { |
| case 'l': |
| case 'o': |
| /* lines and overplotted lines and points */ |
| gpptr(dd)->col = INTEGER(col)[0]; |
| xold = NA_REAL; |
| yold = NA_REAL; |
| for (i = 0; i < n; i++) { |
| xx = x[i]; |
| yy = y[i]; |
| /* do the conversion now to check for non-finite */ |
| GConvert(&xx, &yy, USER, DEVICE, dd); |
| if ((R_FINITE(xx) && R_FINITE(yy)) && |
| !(R_FINITE(xold) && R_FINITE(yold))) |
| start = i; |
| else if ((R_FINITE(xold) && R_FINITE(yold)) && |
| !(R_FINITE(xx) && R_FINITE(yy))) { |
| if (i-start > 1) |
| GPolyline(i-start, x+start, y+start, USER, dd); |
| } |
| else if ((R_FINITE(xold) && R_FINITE(yold)) && (i == n-1)) |
| GPolyline(n-start, x+start, y+start, USER, dd); |
| xold = xx; |
| yold = yy; |
| } |
| break; |
| |
| case 'b': |
| case 'c': /* broken lines (with points in between if 'b') */ |
| { |
| double d, f; |
| d = GConvertYUnits(0.5, CHARS, INCHES, dd); |
| gpptr(dd)->col = INTEGER(col)[0]; |
| xold = NA_REAL; |
| yold = NA_REAL; |
| for (i = 0; i < n; i++) { |
| xx = x[i]; |
| yy = y[i]; |
| GConvert(&xx, &yy, USER, INCHES, dd); |
| if (R_FINITE(xold) && R_FINITE(yold) && |
| R_FINITE(xx) && R_FINITE(yy)) { |
| // might divide by zero |
| if (d < 0.5 * hypot(xx-xold, yy-yold)) { |
| f = d/hypot(xx-xold, yy-yold); |
| GLine(xold + f * (xx - xold), |
| yold + f * (yy - yold), |
| xx + f * (xold - xx), |
| yy + f * (yold - yy), |
| INCHES, dd); |
| } |
| } |
| xold = xx; |
| yold = yy; |
| } |
| } |
| break; |
| |
| case 's': /* step function I */ |
| { |
| double *xtemp, *ytemp; |
| int n0 = 0; |
| if(n <= 1000) { |
| R_CheckStack2(4*n*sizeof(double)); |
| xtemp = (double *) alloca(2*n*sizeof(double)); |
| ytemp = (double *) alloca(2*n*sizeof(double)); |
| } else { |
| vmax = vmaxget(); |
| xtemp = (double *) R_alloc(2*n, sizeof(double)); |
| ytemp = (double *) R_alloc(2*n, sizeof(double)); |
| } |
| gpptr(dd)->col = INTEGER(col)[0]; |
| xold = NA_REAL; |
| yold = NA_REAL; |
| for (i = 0; i < n; i++) { |
| xx = x[i]; |
| yy = y[i]; |
| GConvert(&xx, &yy, USER, DEVICE, dd); |
| if ((R_FINITE(xx) && R_FINITE(yy)) && |
| (R_FINITE(xold) && R_FINITE(yold))) { |
| if(n0 == 0) { xtemp[n0] = xold; ytemp[n0++] = yold; } |
| xtemp[n0] = xx; ytemp[n0++] = yold;/* <-only diff 's' <-> 'S' */ |
| xtemp[n0] = xx; ytemp[n0++] = yy; |
| } else if( (R_FINITE(xold) && R_FINITE(yold)) && |
| !(R_FINITE(xx) && R_FINITE(yy)) && n0 > 0) { |
| GPolyline(n0, xtemp, ytemp, DEVICE, dd); |
| n0 = 0; |
| } |
| xold = xx; |
| yold = yy; |
| } |
| if(n0 > 0) GPolyline(n0, xtemp, ytemp, DEVICE, dd); |
| if(n > 1000) vmaxset(vmax); |
| } |
| break; |
| |
| case 'S': /* step function II */ |
| { |
| double *xtemp, *ytemp; |
| int n0 = 0; |
| if(n < 1000) { |
| R_CheckStack2(4*n*sizeof(double)); |
| xtemp = (double *) alloca(2*n*sizeof(double)); |
| ytemp = (double *) alloca(2*n*sizeof(double)); |
| } else { |
| vmax = vmaxget(); |
| xtemp = (double *) R_alloc(2*n, sizeof(double)); |
| ytemp = (double *) R_alloc(2*n, sizeof(double)); |
| } |
| gpptr(dd)->col = INTEGER(col)[0]; |
| xold = NA_REAL; |
| yold = NA_REAL; |
| for (i = 0; i < n; i++) { |
| xx = x[i]; |
| yy = y[i]; |
| GConvert(&xx, &yy, USER, DEVICE, dd); |
| if ((R_FINITE(xx) && R_FINITE(yy)) && |
| (R_FINITE(xold) && R_FINITE(yold))) { |
| if(n0 == 0) {xtemp[n0] = xold; ytemp[n0++] = yold;} |
| xtemp[n0] = xold; ytemp[n0++] = yy; |
| xtemp[n0] = xx; ytemp[n0++] = yy; |
| } else if( (R_FINITE(xold) && R_FINITE(yold)) && |
| !(R_FINITE(xx) && R_FINITE(yy)) && n0 > 0) { |
| GPolyline(n0, xtemp, ytemp, DEVICE, dd); |
| n0 = 0; |
| } |
| xold = xx; |
| yold = yy; |
| } |
| if(n0 > 0) GPolyline(n0, xtemp, ytemp, DEVICE, dd); |
| if(n > 1000) vmaxset(vmax); |
| } |
| break; |
| |
| case 'h': /* h[istogram] (bar plot) */ |
| if (gpptr(dd)->ylog) |
| yold = gpptr(dd)->usr[2];/* DBL_MIN fails.. why ???? */ |
| else |
| yold = 0.0; |
| yold = GConvertY(yold, USER, DEVICE, dd); |
| for (i = 0; i < n; i++) { |
| xx = x[i]; |
| yy = y[i]; |
| GConvert(&xx, &yy, USER, DEVICE, dd); |
| if (R_FINITE(xx) && R_FINITE(yy) |
| && !R_TRANSPARENT(thiscol = INTEGER(col)[i % ncol])) { |
| gpptr(dd)->col = thiscol; |
| GLine(xx, yold, xx, yy, DEVICE, dd); |
| } |
| } |
| break; |
| |
| case 'p': |
| case 'n': /* nothing here */ |
| break; |
| |
| default:/* OTHERWISE */ |
| error(_("invalid plot type '%c'"), type); |
| |
| } /* End {switch(type)} - for lines */ |
| |
| /* Points : */ |
| if (type == 'p' || type == 'b' || type == 'o') { |
| for (i = 0; i < n; i++) { |
| xx = x[i]; |
| yy = y[i]; |
| GConvert(&xx, &yy, USER, DEVICE, dd); |
| if (R_FINITE(xx) && R_FINITE(yy)) { |
| if (R_FINITE( (thiscex = REAL(cex)[i % ncex]) ) && |
| (thispch = INTEGER(pch)[i % npch]) != NA_INTEGER) { |
| /* FIXME: should this skip 0-sized symbols? */ |
| thiscol = INTEGER(col)[i % ncol]; |
| thisbg = INTEGER(bg)[i % nbg]; |
| if (!(R_TRANSPARENT(thiscol) && |
| R_TRANSPARENT(thisbg))) { |
| gpptr(dd)->cex = thiscex * gpptr(dd)->cexbase; |
| gpptr(dd)->col = thiscol; |
| if(nlwd > 1 && |
| R_FINITE((thislwd = REAL(lwd)[i % nlwd]))) |
| gpptr(dd)->lwd = thislwd; |
| gpptr(dd)->bg = thisbg; |
| GSymbol(xx, yy, DEVICE, thispch, dd); |
| } |
| } |
| } |
| } |
| } |
| GMode(0, dd); |
| GRestorePars(dd); |
| UNPROTECT(6); |
| return R_NilValue; |
| } /* C_plotXY */ |
| |
| /* Checks for ... , x0, y0, x1, y1 ... */ |
| |
| static void xypoints(SEXP args, int *n) |
| { |
| int k=0,/* -Wall */ kmin; |
| |
| if (!isNumeric(CAR(args))) |
| error(_("invalid first argument")); |
| SETCAR(args, coerceVector(CAR(args), REALSXP)); |
| k = LENGTH(CAR(args)); |
| *n = k; kmin = k; |
| args = CDR(args); |
| |
| if (!isNumeric(CAR(args))) |
| error(_("invalid second argument")); |
| k = LENGTH(CAR(args)); |
| SETCAR(args, coerceVector(CAR(args), REALSXP)); |
| if (k > *n) *n = k; |
| if (k < kmin) kmin = k; |
| args = CDR(args); |
| |
| if (!isNumeric(CAR(args))) |
| error(_("invalid third argument")); |
| SETCAR(args, coerceVector(CAR(args), REALSXP)); |
| k = LENGTH(CAR(args)); |
| if (k > *n) *n = k; |
| if (k < kmin) kmin = k; |
| args = CDR(args); |
| |
| if (!isNumeric(CAR(args))) |
| error(_("invalid fourth argument")); |
| SETCAR(args, coerceVector(CAR(args), REALSXP)); |
| k = LENGTH(CAR(args)); |
| if (k > *n) *n = k; |
| if (k < kmin) kmin = k; |
| args = CDR(args); |
| |
| if (*n > 0 && kmin == 0) |
| error(_("cannot mix zero-length and non-zero-length coordinates")); |
| } |
| |
| |
| SEXP C_segments(SEXP args) |
| { |
| /* segments(x0, y0, x1, y1, col, lty, lwd, ...) */ |
| SEXP sx0, sx1, sy0, sy1, col, lty, lwd; |
| double *x0, *x1, *y0, *y1; |
| double xx[2], yy[2]; |
| int nx0, nx1, ny0, ny1, i, n, ncol, nlty, nlwd; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| args = CDR(args); |
| if (length(args) < 4) error(_("too few arguments")); |
| GCheckState(dd); |
| |
| xypoints(args, &n); |
| if(n == 0) return R_NilValue; |
| |
| sx0 = CAR(args); nx0 = length(sx0); args = CDR(args); |
| sy0 = CAR(args); ny0 = length(sy0); args = CDR(args); |
| sx1 = CAR(args); nx1 = length(sx1); args = CDR(args); |
| sy1 = CAR(args); ny1 = length(sy1); args = CDR(args); |
| |
| PROTECT(col = FixupCol(CAR(args), R_TRANWHITE)); |
| ncol = LENGTH(col); args = CDR(args); |
| |
| PROTECT(lty = FixupLty(CAR(args), gpptr(dd)->lty)); |
| nlty = length(lty); args = CDR(args); |
| |
| PROTECT(lwd = FixupLwd(CAR(args), gpptr(dd)->lwd)); |
| nlwd = length(lwd); args = CDR(args); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| x0 = REAL(sx0); |
| y0 = REAL(sy0); |
| x1 = REAL(sx1); |
| y1 = REAL(sy1); |
| |
| GMode(1, dd); |
| for (i = 0; i < n; i++) { |
| xx[0] = x0[i % nx0]; |
| yy[0] = y0[i % ny0]; |
| xx[1] = x1[i % nx1]; |
| yy[1] = y1[i % ny1]; |
| GConvert(xx, yy, USER, DEVICE, dd); |
| GConvert(xx+1, yy+1, USER, DEVICE, dd); |
| if (R_FINITE(xx[0]) && R_FINITE(yy[0]) && |
| R_FINITE(xx[1]) && R_FINITE(yy[1])) |
| { |
| int thiscol = INTEGER(col)[i % ncol]; |
| if(!R_TRANSPARENT(thiscol)) { |
| gpptr(dd)->col = thiscol; |
| gpptr(dd)->lty = INTEGER(lty)[i % nlty]; |
| gpptr(dd)->lwd = REAL(lwd)[i % nlwd]; |
| GLine(xx[0], yy[0], xx[1], yy[1], DEVICE, dd); |
| } |
| } |
| } |
| GMode(0, dd); |
| GRestorePars(dd); |
| |
| UNPROTECT(3); |
| return R_NilValue; |
| } |
| |
| |
| SEXP C_rect(SEXP args) |
| { |
| /* rect(xl, yb, xr, yt, col, border, lty, ...) */ |
| SEXP sxl, sxr, syb, syt, col, lty, lwd, border; |
| double *xl, *xr, *yb, *yt, x0, y0, x1, y1; |
| int i, n, nxl, nxr, nyb, nyt, ncol, nlty, nlwd, nborder; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| args = CDR(args); |
| if (length(args) < 4) error(_("too few arguments")); |
| GCheckState(dd); |
| |
| xypoints(args, &n); |
| if(n == 0) return R_NilValue; |
| |
| sxl = CAR(args); nxl = length(sxl); args = CDR(args);/* x_left */ |
| syb = CAR(args); nyb = length(syb); args = CDR(args);/* y_bottom */ |
| sxr = CAR(args); nxr = length(sxr); args = CDR(args);/* x_right */ |
| syt = CAR(args); nyt = length(syt); args = CDR(args);/* y_top */ |
| |
| PROTECT(col = FixupCol(CAR(args), R_TRANWHITE)); |
| ncol = LENGTH(col); |
| args = CDR(args); |
| |
| PROTECT(border = FixupCol(CAR(args), gpptr(dd)->fg)); |
| nborder = LENGTH(border); |
| args = CDR(args); |
| |
| PROTECT(lty = FixupLty(CAR(args), gpptr(dd)->lty)); |
| nlty = length(lty); |
| args = CDR(args); |
| |
| PROTECT(lwd = FixupLwd(CAR(args), gpptr(dd)->lwd)); |
| nlwd = length(lwd); |
| args = CDR(args); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| xl = REAL(sxl); |
| xr = REAL(sxr); |
| yb = REAL(syb); |
| yt = REAL(syt); |
| |
| GMode(1, dd); |
| for (i = 0; i < n; i++) { |
| if (nlty && INTEGER(lty)[i % nlty] != NA_INTEGER) |
| gpptr(dd)->lty = INTEGER(lty)[i % nlty]; |
| else |
| gpptr(dd)->lty = dpptr(dd)->lty; |
| if (nlwd && REAL(lwd)[i % nlwd] != NA_REAL) |
| gpptr(dd)->lwd = REAL(lwd)[i % nlwd]; |
| else |
| gpptr(dd)->lwd = dpptr(dd)->lwd; |
| x0 = xl[i % nxl]; |
| y0 = yb[i % nyb]; |
| x1 = xr[i % nxr]; |
| y1 = yt[i % nyt]; |
| GConvert(&x0, &y0, USER, DEVICE, dd); |
| GConvert(&x1, &y1, USER, DEVICE, dd); |
| if (R_FINITE(x0) && R_FINITE(y0) && R_FINITE(x1) && R_FINITE(y1)) |
| GRect(x0, y0, x1, y1, DEVICE, INTEGER(col)[i % ncol], |
| INTEGER(border)[i % nborder], dd); |
| } |
| GMode(0, dd); |
| |
| GRestorePars(dd); |
| UNPROTECT(4); |
| return R_NilValue; |
| } |
| |
| SEXP C_path(SEXP args) |
| { |
| /* path(x, y, col, border, lty, ...) */ |
| SEXP sx, sy, nper, rule, col, border, lty; |
| int i, nx, npoly; |
| double *xx, *yy; |
| const void *vmax = NULL /* -Wall */; |
| |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| GCheckState(dd); |
| |
| args = CDR(args); |
| if (length(args) < 2) error(_("too few arguments")); |
| /* (x,y) is checked in R via xy.coords() ; no need here : */ |
| sx = SETCAR(args, coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| sy = SETCAR(args, coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| nx = LENGTH(sx); |
| |
| PROTECT(nper = CAR(args)); args = CDR(args); |
| npoly = LENGTH(nper); |
| |
| PROTECT(rule = CAR(args)); args = CDR(args); |
| |
| PROTECT(col = FixupCol(CAR(args), R_TRANWHITE)); args = CDR(args); |
| PROTECT(border = FixupCol(CAR(args), gpptr(dd)->fg)); args = CDR(args); |
| PROTECT(lty = FixupLty(CAR(args), gpptr(dd)->lty)); args = CDR(args); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| GMode(1, dd); |
| |
| vmax = vmaxget(); |
| |
| /* |
| * Work in device coordinates because that is what the |
| * graphics engine needs. |
| */ |
| xx = (double*) R_alloc(nx, sizeof(double)); |
| yy = (double*) R_alloc(nx, sizeof(double)); |
| if (!xx || !yy) |
| error("unable to allocate memory (in GPath)"); |
| for (i=0; i<nx; i++) { |
| xx[i] = REAL(sx)[i]; |
| yy[i] = REAL(sy)[i]; |
| GConvert(&(xx[i]), &(yy[i]), USER, DEVICE, dd); |
| if (!(R_FINITE(xx[i]) && R_FINITE(yy[i]))) |
| error("invalid 'x' or 'y' (in 'GPath')"); |
| } |
| |
| if (INTEGER(lty)[0] == NA_INTEGER) |
| gpptr(dd)->lty = dpptr(dd)->lty; |
| else |
| gpptr(dd)->lty = INTEGER(lty)[0]; |
| |
| GPath(xx, yy, npoly, INTEGER(nper), INTEGER(rule)[0] == 1, |
| INTEGER(col)[0], INTEGER(border)[0], dd); |
| |
| GMode(0, dd); |
| |
| GRestorePars(dd); |
| UNPROTECT(5); |
| |
| vmaxset(vmax); |
| return R_NilValue; |
| } |
| |
| SEXP C_raster(SEXP args) |
| { |
| /* raster(image, xl, yb, xr, yt, angle, interpolate, ...) */ |
| const void *vmax; |
| unsigned int *image; |
| SEXP raster, dim, sxl, sxr, syb, syt, angle, interpolate; |
| double *xl, *xr, *yb, *yt, x0, y0, x1, y1; |
| int i, n, nxl, nxr, nyb, nyt; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| args = CDR(args); |
| if (length(args) < 7) error(_("too few arguments")); |
| GCheckState(dd); |
| |
| raster = CAR(args); args = CDR(args); |
| n = LENGTH(raster); |
| if (n <= 0) error(_("Empty raster")); |
| dim = getAttrib(raster, R_DimSymbol); |
| |
| vmax = vmaxget(); |
| /* raster is rather inefficient so allow a native representation as |
| an integer array which requires no conversion */ |
| if (inherits(raster, "nativeRaster") && isInteger(raster)) |
| image = (unsigned int *) INTEGER(raster); |
| else { |
| image = (unsigned int *) R_alloc(n, sizeof(unsigned int)); |
| for (i = 0; i < n; i++) |
| image[i] = RGBpar3(raster, i, R_TRANWHITE); |
| } |
| |
| xypoints(args, &n); |
| if(n == 0) return R_NilValue; |
| |
| sxl = CAR(args); nxl = length(sxl); args = CDR(args);/* x_left */ |
| syb = CAR(args); nyb = length(syb); args = CDR(args);/* y_bottom */ |
| sxr = CAR(args); nxr = length(sxr); args = CDR(args);/* x_right */ |
| syt = CAR(args); nyt = length(syt); args = CDR(args);/* y_top */ |
| |
| angle = CAR(args); args = CDR(args); |
| interpolate = CAR(args); args = CDR(args); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| xl = REAL(sxl); |
| xr = REAL(sxr); |
| yb = REAL(syb); |
| yt = REAL(syt); |
| |
| GMode(1, dd); |
| for (i = 0; i < n; i++) { |
| x0 = xl[i % nxl]; |
| y0 = yb[i % nyb]; |
| x1 = xr[i % nxr]; |
| y1 = yt[i % nyt]; |
| GConvert(&x0, &y0, USER, DEVICE, dd); |
| GConvert(&x1, &y1, USER, DEVICE, dd); |
| if (R_FINITE(x0) && R_FINITE(y0) && R_FINITE(x1) && R_FINITE(y1)) |
| GRaster(image, INTEGER(dim)[1], INTEGER(dim)[0], |
| x0, y0, x1 - x0, y1 - y0, |
| REAL(angle)[i % LENGTH(angle)], |
| LOGICAL(interpolate)[i % LENGTH(interpolate)], dd); |
| } |
| GMode(0, dd); |
| |
| GRestorePars(dd); |
| |
| vmaxset(vmax); |
| return R_NilValue; |
| } |
| |
| |
| SEXP C_arrows(SEXP args) |
| { |
| /* arrows(x0, y0, x1, y1, length, angle, code, col, lty, lwd, ...) */ |
| SEXP sx0, sx1, sy0, sy1, col, lty, lwd; |
| double *x0, *x1, *y0, *y1; |
| double xx0, yy0, xx1, yy1; |
| double hlength, angle; |
| int code; |
| int nx0, nx1, ny0, ny1, i, n, ncol, nlty, nlwd; |
| rcolor thiscol; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| args = CDR(args); |
| if (length(args) < 4) error(_("too few arguments")); |
| GCheckState(dd); |
| |
| xypoints(args, &n); |
| if(n == 0) return R_NilValue; |
| |
| sx0 = CAR(args); nx0 = length(sx0); args = CDR(args); |
| sy0 = CAR(args); ny0 = length(sy0); args = CDR(args); |
| sx1 = CAR(args); nx1 = length(sx1); args = CDR(args); |
| sy1 = CAR(args); ny1 = length(sy1); args = CDR(args); |
| |
| hlength = asReal(CAR(args)); |
| if (!R_FINITE(hlength) || hlength < 0) |
| error(_("invalid arrow head length")); |
| args = CDR(args); |
| |
| angle = asReal(CAR(args)); |
| if (!R_FINITE(angle)) |
| error(_("invalid arrow head angle")); |
| args = CDR(args); |
| |
| code = asInteger(CAR(args)); |
| if (code == NA_INTEGER || code < 0 || code > 3) |
| error(_("invalid arrow head specification")); |
| args = CDR(args); |
| |
| PROTECT(col = FixupCol(CAR(args), R_TRANWHITE)); |
| ncol = LENGTH(col); |
| args = CDR(args); |
| |
| PROTECT(lty = FixupLty(CAR(args), gpptr(dd)->lty)); |
| nlty = length(lty); |
| args = CDR(args); |
| |
| PROTECT(lwd = FixupLwd(CAR(args), gpptr(dd)->lwd)); |
| nlwd = length(lwd); |
| args = CDR(args); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| x0 = REAL(sx0); |
| y0 = REAL(sy0); |
| x1 = REAL(sx1); |
| y1 = REAL(sy1); |
| |
| GMode(1, dd); |
| for (i = 0; i < n; i++) { |
| xx0 = x0[i % nx0]; |
| yy0 = y0[i % ny0]; |
| xx1 = x1[i % nx1]; |
| yy1 = y1[i % ny1]; |
| GConvert(&xx0, &yy0, USER, DEVICE, dd); |
| GConvert(&xx1, &yy1, USER, DEVICE, dd); |
| if (R_FINITE(xx0) && R_FINITE(yy0) && R_FINITE(xx1) && R_FINITE(yy1) |
| && !R_TRANSPARENT(thiscol = INTEGER(col)[i % ncol])) { |
| gpptr(dd)->col = thiscol; |
| gpptr(dd)->lty = INTEGER(lty)[i % nlty]; |
| gpptr(dd)->lwd = REAL(lwd)[i % nlwd]; |
| GArrow(xx0, yy0, xx1, yy1, DEVICE, |
| hlength, angle, code, dd); |
| } |
| } |
| GMode(0, dd); |
| GRestorePars(dd); |
| |
| UNPROTECT(3); |
| return R_NilValue; |
| } |
| |
| |
| static void drawPolygon(int n, double *x, double *y, |
| int lty, int fill, int border, pGEDevDesc dd) |
| { |
| if (lty == NA_INTEGER) |
| gpptr(dd)->lty = dpptr(dd)->lty; |
| else |
| gpptr(dd)->lty = lty; |
| GPolygon(n, x, y, USER, fill, border, dd); |
| } |
| |
| SEXP C_polygon(SEXP args) |
| { |
| /* polygon(x, y, col, border, lty, ...) */ |
| SEXP sx, sy, col, border, lty; |
| int nx; |
| int ncol, nborder, nlty, i, start=0; |
| int num = 0; |
| double *x, *y, xx, yy, xold, yold; |
| |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| GCheckState(dd); |
| |
| args = CDR(args); |
| if (length(args) < 2) error(_("too few arguments")); |
| /* (x,y) is checked in R via xy.coords() ; no need here : */ |
| sx = SETCAR(args, coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| sy = SETCAR(args, coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| nx = LENGTH(sx); |
| |
| PROTECT(col = FixupCol(CAR(args), R_TRANWHITE)); args = CDR(args); |
| ncol = LENGTH(col); |
| |
| PROTECT(border = FixupCol(CAR(args), gpptr(dd)->fg)); args = CDR(args); |
| nborder = LENGTH(border); |
| |
| PROTECT(lty = FixupLty(CAR(args), gpptr(dd)->lty)); args = CDR(args); |
| nlty = length(lty); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| GMode(1, dd); |
| |
| x = REAL(sx); |
| y = REAL(sy); |
| xold = NA_REAL; |
| yold = NA_REAL; |
| for (i = 0; i < nx; i++) { |
| xx = x[i]; |
| yy = y[i]; |
| GConvert(&xx, &yy, USER, DEVICE, dd); |
| if ((R_FINITE(xx) && R_FINITE(yy)) && |
| !(R_FINITE(xold) && R_FINITE(yold))) |
| start = i; /* first point of current segment */ |
| else if ((R_FINITE(xold) && R_FINITE(yold)) && |
| !(R_FINITE(xx) && R_FINITE(yy))) { |
| if (i-start > 1) { |
| drawPolygon(i-start, x+start, y+start, |
| INTEGER(lty)[num%nlty], |
| INTEGER(col)[num%ncol], |
| INTEGER(border)[num%nborder], dd); |
| num++; |
| } |
| } |
| else if ((R_FINITE(xold) && R_FINITE(yold)) && (i == nx-1)) { /* last */ |
| drawPolygon(nx-start, x+start, y+start, |
| INTEGER(lty)[num%nlty], |
| INTEGER(col)[num%ncol], |
| INTEGER(border)[num%nborder], dd); |
| num++; |
| } |
| xold = xx; |
| yold = yy; |
| } |
| |
| GMode(0, dd); |
| |
| GRestorePars(dd); |
| UNPROTECT(3); |
| return R_NilValue; |
| } |
| |
| SEXP C_text(SEXP args) |
| { |
| /* text(xy, labels, adj, pos, offset, |
| * vfont, cex, col, font, ...) |
| */ |
| SEXP sx, sy, sxy, txt, adj, pos, cex, col, rawcol, font, vfont; |
| int i, n, npos, ncex, ncol, nfont, ntxt; |
| double adjx = 0, adjy = 0, offset = 0.5; |
| double *x, *y; |
| double xx, yy; |
| Rboolean vectorFonts = FALSE; |
| SEXP string; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| GCheckState(dd); |
| |
| args = CDR(args); |
| if (length(args) < 3) error(_("too few arguments")); |
| |
| PLOT_XY_DEALING("text"); |
| |
| /* labels */ |
| txt = CAR(args); |
| if (isSymbol(txt) || isLanguage(txt)) |
| txt = coerceVector(txt, EXPRSXP); |
| else if (!isExpression(txt)) |
| txt = coerceVector(txt, STRSXP); |
| PROTECT(txt); |
| if (length(txt) <= 0) |
| error(_("zero-length '%s' specified"), "labels"); |
| args = CDR(args); |
| |
| PROTECT(adj = CAR(args)); |
| if (isNull(adj) || (isNumeric(adj) && length(adj) == 0)) { |
| adjx = gpptr(dd)->adj; |
| adjy = NA_REAL; |
| } |
| else if (isReal(adj)) { |
| if (LENGTH(adj) == 1) { |
| adjx = REAL(adj)[0]; |
| adjy = NA_REAL; |
| } |
| else { |
| adjx = REAL(adj)[0]; |
| adjy = REAL(adj)[1]; |
| } |
| } |
| else if (isInteger(adj)) { |
| if (LENGTH(adj) == 1) { |
| adjx = INTEGER(adj)[0]; |
| adjy = NA_REAL; |
| } |
| else { |
| adjx = INTEGER(adj)[0]; |
| adjy = INTEGER(adj)[1]; |
| } |
| } |
| else error(_("invalid '%s' value"), "adj"); |
| args = CDR(args); |
| |
| PROTECT(pos = coerceVector(CAR(args), INTSXP)); |
| npos = length(pos); |
| for (i = 0; i < npos; i++) |
| if (INTEGER(pos)[i] < 1 || INTEGER(pos)[i] > 4) |
| error(_("invalid '%s' value"), "pos"); |
| args = CDR(args); |
| |
| offset = GConvertXUnits(asReal(CAR(args)), CHARS, INCHES, dd); |
| args = CDR(args); |
| |
| PROTECT(vfont = FixupVFont(CAR(args))); |
| args = CDR(args); |
| |
| PROTECT(cex = FixupCex(CAR(args), 1.0)); |
| ncex = LENGTH(cex); |
| args = CDR(args); |
| |
| rawcol = CAR(args); |
| PROTECT(col = FixupCol(rawcol, R_TRANWHITE)); |
| ncol = LENGTH(col); |
| args = CDR(args); |
| |
| PROTECT(font = FixupFont(CAR(args), NA_INTEGER)); |
| nfont = LENGTH(font); |
| args = CDR(args); |
| |
| x = REAL(sx); |
| y = REAL(sy); |
| /* n = LENGTH(sx) = LENGTH(sy) */ |
| ntxt = LENGTH(txt); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| /* Done here so 'vfont' trumps inline 'family' */ |
| if (!isNull(vfont) && !isExpression(txt)) { |
| strncpy(gpptr(dd)->family, "Hershey ", 201); |
| gpptr(dd)->family[7] = (char) INTEGER(vfont)[0]; |
| vectorFonts = TRUE; |
| } |
| |
| GMode(1, dd); |
| if (n == 0 && ntxt > 0) |
| error(_("no coordinates were supplied")); |
| for (i = 0; i < imax2(n,ntxt); i++) { |
| xx = x[i % n]; |
| yy = y[i % n]; |
| GConvert(&xx, &yy, USER, INCHES, dd); |
| if (R_FINITE(xx) && R_FINITE(yy)) { |
| if (ncol && !isNAcol(rawcol, i, ncol)) |
| gpptr(dd)->col = INTEGER(col)[i % ncol]; |
| else |
| gpptr(dd)->col = dpptr(dd)->col; |
| if (ncex && R_FINITE(REAL(cex)[i % ncex])) |
| gpptr(dd)->cex = gpptr(dd)->cexbase * REAL(cex)[i % ncex]; |
| else |
| gpptr(dd)->cex = gpptr(dd)->cexbase; |
| |
| if (vectorFonts) gpptr(dd)->font = INTEGER(vfont)[1]; |
| else if (nfont && INTEGER(font)[i % nfont] != NA_INTEGER) |
| gpptr(dd)->font = INTEGER(font)[i % nfont]; |
| else |
| gpptr(dd)->font = dpptr(dd)->font; |
| |
| if (npos > 0) { |
| switch(INTEGER(pos)[i % npos]) { |
| case 1: |
| yy = yy - offset; |
| adjx = 0.5; |
| adjy = 1 - (0.5 - dd->dev->yCharOffset); |
| break; |
| case 2: |
| xx = xx - offset; |
| adjx = 1; |
| adjy = dd->dev->yCharOffset; |
| break; |
| case 3: |
| yy = yy + offset; |
| adjx = 0.5; |
| adjy = 0; |
| break; |
| case 4: |
| xx = xx + offset; |
| adjx = 0; |
| adjy = dd->dev->yCharOffset; |
| break; |
| } |
| } |
| if (isExpression(txt)) { |
| GMathText(xx, yy, INCHES, VECTOR_ELT(txt, i % ntxt), |
| adjx, adjy, gpptr(dd)->srt, dd); |
| } else { |
| string = STRING_ELT(txt, i % ntxt); |
| if(string != NA_STRING) |
| GText(xx, yy, INCHES, CHAR(string), getCharCE(string), |
| adjx, adjy, gpptr(dd)->srt, dd); |
| } |
| } |
| } |
| GMode(0, dd); |
| |
| GRestorePars(dd); |
| UNPROTECT(7); |
| return R_NilValue; |
| } |
| |
| static double ComputeAdjValue(double adj, int side, int las) |
| { |
| if (!R_FINITE(adj)) { |
| switch(las) { |
| case 0:/* parallel to axis */ |
| adj = 0.5; break; |
| case 1:/* horizontal */ |
| switch(side) { |
| case 1: |
| case 3: adj = 0.5; break; |
| case 2: adj = 1.0; break; |
| case 4: adj = 0.0; break; |
| } |
| break; |
| case 2:/* perpendicular to axis */ |
| switch(side) { |
| case 1: |
| case 2: adj = 1.0; break; |
| case 3: |
| case 4: adj = 0.0; break; |
| } |
| break; |
| case 3:/* vertical */ |
| switch(side) { |
| case 1: adj = 1.0; break; |
| case 3: adj = 0.0; break; |
| case 2: |
| case 4: adj = 0.5; break; |
| } |
| break; |
| } |
| } |
| return adj; |
| } |
| |
| static double ComputeAtValueFromAdj(double adj, int side, Rboolean outer, |
| pGEDevDesc dd) |
| { |
| double at = 0; /* -Wall */ |
| switch(side % 2) { |
| case 0: |
| at = outer ? adj : yNPCtoUsr(adj, dd); |
| break; |
| case 1: |
| at = outer ? adj : xNPCtoUsr(adj, dd); |
| break; |
| } |
| return at; |
| } |
| |
| static double ComputeAtValue(double at, double adj, |
| int side, int las, Rboolean outer, |
| pGEDevDesc dd) |
| { |
| if (!R_FINITE(at)) { |
| /* If the text is parallel to the axis, use "adj" for "at" |
| * Otherwise, centre the text |
| */ |
| switch(las) { |
| case 0:/* parallel to axis */ |
| at = ComputeAtValueFromAdj(adj, side, outer, dd); |
| break; |
| case 1:/* horizontal */ |
| switch(side) { |
| case 1: |
| case 3: |
| at = ComputeAtValueFromAdj(adj, side, outer, dd); |
| break; |
| case 2: |
| case 4: |
| at = outer ? 0.5 : yNPCtoUsr(0.5, dd); |
| break; |
| } |
| break; |
| case 2:/* perpendicular to axis */ |
| switch(side) { |
| case 1: |
| case 3: |
| at = outer ? 0.5 : xNPCtoUsr(0.5, dd); |
| break; |
| case 2: |
| case 4: |
| at = outer ? 0.5 : yNPCtoUsr(0.5, dd); |
| break; |
| } |
| break; |
| case 3:/* vertical */ |
| switch(side) { |
| case 1: |
| case 3: |
| at = outer ? 0.5 : xNPCtoUsr(0.5, dd); |
| break; |
| case 2: |
| case 4: |
| at = ComputeAtValueFromAdj(adj, side, outer, dd); |
| break; |
| } |
| break; |
| } |
| } |
| return at; |
| } |
| |
| /* mtext(text, |
| side = 3, |
| line = 0, |
| outer = TRUE, |
| at = NA, |
| adj = NA, |
| padj = NA, |
| cex = NA, |
| col = NA, |
| font = NA, |
| ...) */ |
| |
| SEXP C_mtext(SEXP args) |
| { |
| SEXP text, side, line, outer, at, adj, padj, cex, col, font, string; |
| SEXP rawcol; |
| int ntext, nside, nline, nouter, nat, nadj, npadj, ncex, ncol, nfont; |
| Rboolean dirtyplot = FALSE, gpnewsave = FALSE, dpnewsave = FALSE; |
| int i, n, fontsave, colsave; |
| double cexsave; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| GCheckState(dd); |
| |
| args = CDR(args); |
| if (length(args) < 9) |
| error(_("too few arguments")); |
| |
| /* Arg1 : text= */ |
| text = CAR(args); |
| if (isSymbol(text) || isLanguage(text)) |
| text = coerceVector(text, EXPRSXP); |
| else if (!isExpression(text)) |
| text = coerceVector(text, STRSXP); |
| PROTECT(text); |
| n = ntext = length(text); |
| if (ntext <= 0) |
| error(_("zero-length '%s' specified"), "text"); |
| args = CDR(args); |
| |
| /* Arg2 : side= */ |
| PROTECT(side = coerceVector(CAR(args), INTSXP)); |
| nside = length(side); |
| if (nside <= 0) error(_("zero-length '%s' specified"), "side"); |
| if (n < nside) n = nside; |
| args = CDR(args); |
| |
| /* Arg3 : line= */ |
| PROTECT(line = coerceVector(CAR(args), REALSXP)); |
| nline = length(line); |
| if (nline <= 0) error(_("zero-length '%s' specified"), "line"); |
| if (n < nline) n = nline; |
| args = CDR(args); |
| |
| /* Arg4 : outer= */ |
| /* outer == NA => outer <- 0 */ |
| PROTECT(outer = coerceVector(CAR(args), INTSXP)); |
| nouter = length(outer); |
| if (nouter <= 0) error(_("zero-length '%s' specified"), "outer"); |
| if (n < nouter) n = nouter; |
| args = CDR(args); |
| |
| /* Arg5 : at= */ |
| PROTECT(at = coerceVector(CAR(args), REALSXP)); |
| nat = length(at); |
| if (nat <= 0) error(_("zero-length '%s' specified"), "at"); |
| if (n < nat) n = nat; |
| args = CDR(args); |
| |
| /* Arg6 : adj= */ |
| PROTECT(adj = coerceVector(CAR(args), REALSXP)); |
| nadj = length(adj); |
| if (nadj <= 0) error(_("zero-length '%s' specified"), "adj"); |
| if (n < nadj) n = nadj; |
| args = CDR(args); |
| |
| /* Arg7 : padj= */ |
| PROTECT(padj = coerceVector(CAR(args), REALSXP)); |
| npadj = length(padj); |
| if (npadj <= 0) error(_("zero-length '%s' specified"), "padj"); |
| if (n < npadj) n = npadj; |
| args = CDR(args); |
| |
| /* Arg8 : cex */ |
| PROTECT(cex = FixupCex(CAR(args), 1.0)); |
| ncex = length(cex); |
| if (ncex <= 0) error(_("zero-length '%s' specified"), "cex"); |
| if (n < ncex) n = ncex; |
| args = CDR(args); |
| |
| /* Arg9 : col */ |
| rawcol = CAR(args); |
| PROTECT(col = FixupCol(rawcol, R_TRANWHITE)); |
| ncol = length(col); |
| if (ncol <= 0) error(_("zero-length '%s' specified"), "col"); |
| if (n < ncol) n = ncol; |
| args = CDR(args); |
| |
| /* Arg10 : font */ |
| PROTECT(font = FixupFont(CAR(args), NA_INTEGER)); |
| nfont = length(font); |
| if (nfont <= 0) error(_("zero-length '%s' specified"), "font"); |
| if (n < nfont) n = nfont; |
| args = CDR(args); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| /* If we only scribble in the outer margins, */ |
| /* we don't want to mark the plot as dirty. */ |
| |
| dirtyplot = FALSE; |
| gpnewsave = gpptr(dd)->new; |
| dpnewsave = dpptr(dd)->new; |
| cexsave = gpptr(dd)->cex; |
| fontsave = gpptr(dd)->font; |
| colsave = gpptr(dd)->col; |
| |
| /* override par("xpd") and force clipping to figure region |
| NOTE: don't override to _reduce_ clipping region */ |
| if (gpptr(dd)->xpd < 1) |
| gpptr(dd)->xpd = 1; |
| |
| if (outer) { |
| gpnewsave = gpptr(dd)->new; |
| dpnewsave = dpptr(dd)->new; |
| /* override par("xpd") and force clipping to device region */ |
| gpptr(dd)->xpd = 2; |
| } |
| GMode(1, dd); |
| |
| for (i = 0; i < n; i++) { |
| double atval = REAL(at)[i % nat]; |
| double adjval = REAL(adj)[i % nadj]; |
| double padjval = REAL(padj)[i % npadj]; |
| double cexval = REAL(cex)[i % ncex]; |
| double lineval = REAL(line)[i % nline]; |
| int outerval = INTEGER(outer)[i % nouter]; |
| int sideval = INTEGER(side)[i % nside]; |
| int fontval = INTEGER(font)[i % nfont]; |
| int colval = INTEGER(col)[i % ncol]; |
| |
| if (outerval == NA_INTEGER) outerval = 0; |
| /* Note : we ignore any shrinking produced */ |
| /* by mfrow / mfcol specs here. I.e. don't */ |
| /* gpptr(dd)->cexbase. */ |
| if (R_FINITE(cexval)) gpptr(dd)->cex = cexval; |
| else cexval = cexsave; |
| gpptr(dd)->font = (fontval == NA_INTEGER) ? fontsave : fontval; |
| if (isNAcol(rawcol, i, ncol)) |
| gpptr(dd)->col = colsave; |
| else |
| gpptr(dd)->col = colval; |
| gpptr(dd)->adj = ComputeAdjValue(adjval, sideval, gpptr(dd)->las); |
| padjval = ComputePAdjValue(padjval, sideval, gpptr(dd)->las); |
| atval = ComputeAtValue(atval, gpptr(dd)->adj, sideval, gpptr(dd)->las, |
| outerval, dd); |
| |
| if (isExpression(text)) |
| GMMathText(VECTOR_ELT(text, i % ntext), |
| sideval, lineval, outerval, atval, gpptr(dd)->las, |
| padjval, dd); |
| else { |
| string = STRING_ELT(text, i % ntext); |
| if(string != NA_STRING) |
| GMtext(CHAR(string), getCharCE(string), sideval, lineval, |
| outerval, atval, gpptr(dd)->las, padjval, dd); |
| } |
| |
| if (outerval == 0) dirtyplot = TRUE; |
| } |
| GMode(0, dd); |
| |
| GRestorePars(dd); |
| if (!dirtyplot) { |
| gpptr(dd)->new = gpnewsave; |
| dpptr(dd)->new = dpnewsave; |
| } |
| UNPROTECT(10); |
| return R_NilValue; |
| } /* C_mtext */ |
| |
| |
| SEXP C_title(SEXP args) |
| { |
| /* Annotation for plots : |
| |
| title(main, sub, xlab, ylab, |
| line, outer, |
| ...) */ |
| |
| SEXP Main, xlab, ylab, sub, string; |
| double adj, adjy, cex, offset, line, hpos, vpos; |
| int i, n, font, outer, where; |
| rcolor col; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| GCheckState(dd); |
| |
| args = CDR(args); |
| if (length(args) < 6) error(_("too few arguments")); |
| |
| Main = sub = xlab = ylab = R_NilValue; |
| |
| if (CAR(args) != R_NilValue && length(CAR(args)) > 0) |
| Main = CAR(args); |
| args = CDR(args); |
| |
| if (CAR(args) != R_NilValue && length(CAR(args)) > 0) |
| sub = CAR(args); |
| args = CDR(args); |
| |
| if (CAR(args) != R_NilValue && length(CAR(args)) > 0) |
| xlab = CAR(args); |
| args = CDR(args); |
| |
| if (CAR(args) != R_NilValue && length(CAR(args)) > 0) |
| ylab = CAR(args); |
| args = CDR(args); |
| |
| line = asReal(CAR(args)); |
| args = CDR(args); |
| |
| outer = asLogical(CAR(args)); |
| if (outer == NA_LOGICAL) outer = 0; |
| args = CDR(args); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| /* override par("xpd") and force clipping to figure region |
| NOTE: don't override to _reduce_ clipping region */ |
| if (gpptr(dd)->xpd < 1) |
| gpptr(dd)->xpd = 1; |
| if (outer) |
| gpptr(dd)->xpd = 2; |
| adj = gpptr(dd)->adj; |
| |
| GMode(1, dd); |
| if (Main != R_NilValue) { |
| cex = gpptr(dd)->cexmain; |
| col = gpptr(dd)->colmain; |
| font = gpptr(dd)->fontmain; |
| /* GetTextArg may coerce, so protect the result */ |
| GetTextArg(Main, &Main, &col, &cex, &font); |
| PROTECT(Main); |
| gpptr(dd)->col = col; |
| gpptr(dd)->cex = gpptr(dd)->cexbase * cex; |
| gpptr(dd)->font = font; |
| if (outer) { |
| if (R_FINITE(line)) { |
| vpos = line; |
| adjy = 0; |
| } |
| else { |
| vpos = 0.5 * gpptr(dd)->oma[2]; |
| adjy = 0.5; |
| } |
| hpos = adj; |
| where = OMA3; |
| } |
| else { |
| if (R_FINITE(line)) { |
| vpos = line; |
| adjy = 0; |
| } |
| else { |
| vpos = 0.5 * gpptr(dd)->mar[2]; |
| adjy = 0.5; |
| } |
| hpos = GConvertX(adj, NPC, USER, dd); |
| where = MAR3; |
| } |
| if (isExpression(Main)) { |
| GMathText(hpos, vpos, where, VECTOR_ELT(Main, 0), |
| adj, 0.5, 0.0, dd); |
| } |
| else { |
| n = length(Main); |
| offset = 0.5 * (n - 1) + vpos; |
| for (i = 0; i < n; i++) { |
| string = STRING_ELT(Main, i); |
| if(string != NA_STRING) |
| GText(hpos, offset - i, where, CHAR(string), getCharCE(string), |
| adj, adjy, 0.0, dd); |
| } |
| } |
| UNPROTECT(1); |
| } |
| if (sub != R_NilValue) { |
| cex = gpptr(dd)->cexsub; |
| col = gpptr(dd)->colsub; |
| font = gpptr(dd)->fontsub; |
| /* GetTextArg may coerce, so protect the result */ |
| GetTextArg(sub, &sub, &col, &cex, &font); |
| PROTECT(sub); |
| gpptr(dd)->col = col; |
| gpptr(dd)->cex = gpptr(dd)->cexbase * cex; |
| gpptr(dd)->font = font; |
| if (R_FINITE(line)) |
| vpos = line; |
| else |
| vpos = gpptr(dd)->mgp[0] + 1; |
| if (outer) { |
| hpos = adj; |
| where = 1; |
| } |
| else { |
| hpos = GConvertX(adj, NPC, USER, dd); |
| where = 0; |
| } |
| if (isExpression(sub)) |
| GMMathText(VECTOR_ELT(sub, 0), 1, vpos, where, |
| hpos, 0, 0.0, dd); |
| else { |
| n = length(sub); |
| for (i = 0; i < n; i++) { |
| string = STRING_ELT(sub, i); |
| if(string != NA_STRING) |
| GMtext(CHAR(string), getCharCE(string), 1, vpos, where, |
| hpos, 0, 0.0, dd); |
| } |
| } |
| UNPROTECT(1); |
| } |
| if (xlab != R_NilValue) { |
| cex = gpptr(dd)->cexlab; |
| col = gpptr(dd)->collab; |
| font = gpptr(dd)->fontlab; |
| /* GetTextArg may coerce, so protect the result */ |
| GetTextArg(xlab, &xlab, &col, &cex, &font); |
| PROTECT(xlab); |
| gpptr(dd)->cex = gpptr(dd)->cexbase * cex; |
| gpptr(dd)->col = col; |
| gpptr(dd)->font = font; |
| if (R_FINITE(line)) |
| vpos = line; |
| else |
| vpos = gpptr(dd)->mgp[0]; |
| if (outer) { |
| hpos = adj; |
| where = 1; |
| } |
| else { |
| hpos = GConvertX(adj, NPC, USER, dd); |
| where = 0; |
| } |
| if (isExpression(xlab)) |
| GMMathText(VECTOR_ELT(xlab, 0), 1, vpos, where, |
| hpos, 0, 0.0, dd); |
| else { |
| n = length(xlab); |
| for (i = 0; i < n; i++) { |
| string = STRING_ELT(xlab, i); |
| if(string != NA_STRING) |
| GMtext(CHAR(string), getCharCE(string), 1, vpos + i, |
| where, hpos, 0, 0.0, dd); |
| } |
| } |
| UNPROTECT(1); |
| } |
| if (ylab != R_NilValue) { |
| cex = gpptr(dd)->cexlab; |
| col = gpptr(dd)->collab; |
| font = gpptr(dd)->fontlab; |
| /* GetTextArg may coerce, so protect the result */ |
| GetTextArg(ylab, &ylab, &col, &cex, &font); |
| PROTECT(ylab); |
| gpptr(dd)->cex = gpptr(dd)->cexbase * cex; |
| gpptr(dd)->col = col; |
| gpptr(dd)->font = font; |
| if (R_FINITE(line)) |
| vpos = line; |
| else |
| vpos = gpptr(dd)->mgp[0]; |
| if (outer) { |
| hpos = adj; |
| where = 1; |
| } |
| else { |
| hpos = GConvertY(adj, NPC, USER, dd); |
| where = 0; |
| } |
| if (isExpression(ylab)) |
| GMMathText(VECTOR_ELT(ylab, 0), 2, vpos, where, |
| hpos, 0, 0.0, dd); |
| else { |
| n = length(ylab); |
| for (i = 0; i < n; i++) { |
| string = STRING_ELT(ylab, i); |
| if(string != NA_STRING) |
| GMtext(CHAR(string), getCharCE(string), 2, vpos - i, |
| where, hpos, 0, 0.0, dd); |
| } |
| } |
| UNPROTECT(1); |
| } |
| GMode(0, dd); |
| GRestorePars(dd); |
| return R_NilValue; |
| } /* C_title */ |
| |
| |
| /* abline(a, b, h, v, col, lty, lwd, ...) |
| draw lines in intercept/slope form. */ |
| |
| SEXP C_abline(SEXP args) |
| { |
| SEXP a, b, h, v, untf, col, lty, lwd; |
| int i, ncol, nlines, nlty, nlwd, lstart, lstop; |
| double aa, bb, x[2], y[2]={0.,0.} /* -Wall */; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| GCheckState(dd); |
| |
| args = CDR(args); |
| if (length(args) < 5) error(_("too few arguments")); |
| |
| if ((a = CAR(args)) != R_NilValue) |
| SETCAR(args, a = coerceVector(a, REALSXP)); |
| args = CDR(args); |
| |
| if ((b = CAR(args)) != R_NilValue) |
| SETCAR(args, b = coerceVector(b, REALSXP)); |
| args = CDR(args); |
| |
| if ((h = CAR(args)) != R_NilValue) |
| SETCAR(args, h = coerceVector(h, REALSXP)); |
| args = CDR(args); |
| |
| if ((v = CAR(args)) != R_NilValue) |
| SETCAR(args, v = coerceVector(v, REALSXP)); |
| args = CDR(args); |
| |
| if ((untf = CAR(args)) != R_NilValue) |
| SETCAR(args, untf = coerceVector(untf, LGLSXP)); |
| args = CDR(args); |
| |
| |
| PROTECT(col = FixupCol(CAR(args), R_TRANWHITE)); args = CDR(args); |
| ncol = LENGTH(col); |
| |
| PROTECT(lty = FixupLty(CAR(args), gpptr(dd)->lty)); args = CDR(args); |
| nlty = length(lty); |
| |
| PROTECT(lwd = FixupLwd(CAR(args), gpptr(dd)->lwd)); args = CDR(args); |
| nlwd = length(lwd); |
| |
| GSavePars(dd); |
| |
| ProcessInlinePars(args, dd); |
| |
| nlines = 0; |
| |
| if (a != R_NilValue) { /* case where a ans b are supplied */ |
| if (b == R_NilValue) { |
| if (LENGTH(a) != 2) |
| error(_("invalid a=, b= specification")); |
| aa = REAL(a)[0]; |
| bb = REAL(a)[1]; |
| } |
| else { |
| aa = asReal(a); |
| bb = asReal(b); |
| } |
| if (!R_FINITE(aa) || !R_FINITE(bb)) |
| error(_("'a' and 'b' must be finite")); |
| gpptr(dd)->col = INTEGER(col)[0]; |
| gpptr(dd)->lwd = REAL(lwd)[0]; |
| if (nlty && INTEGER(lty)[0] != NA_INTEGER) |
| gpptr(dd)->lty = INTEGER(lty)[0]; |
| else |
| gpptr(dd)->lty = dpptr(dd)->lty; |
| GMode(1, dd); |
| |
| /* FIXME? |
| * Seems like the logic here is just draw from xmin to xmax |
| * and you're guaranteed to draw at least from ymin to ymax |
| * This MAY cause a problem at some stage when the line being |
| * drawn is VERY steep -- and the problem is worse now that |
| * abline will potentially draw to the extents of the device |
| * (when xpd = NA). NOTE that R's internal clipping protects the |
| * device drivers from stupidly large numbers, BUT there is |
| * still a risk that we could produce a number which is too |
| * big for the computer's brain. |
| * Paul. |
| * |
| * The problem is worse -- you could get NaN, which at least the |
| * X11 device coerces to -2^31 <TSL> |
| */ |
| getxlimits(x, dd);/* -> (x[0], x[1]) */ |
| if (R_FINITE(gpptr(dd)->lwd)) { |
| Rboolean xlog = gpptr(dd)->xlog, ylog = gpptr(dd)->ylog; |
| if (LOGICAL(untf)[0] && (xlog || ylog)) { |
| #define NS 100 |
| /* Plot curve, linear on original scales */ |
| double xx[NS+1], yy[NS+1]; |
| if(xlog) { |
| /* x_i should be equidistant in log-scale, i.e., equi-ratio */ |
| double x_f = x[1] / DBL_MAX; |
| xx[0] = x[0] = fmax2(x[0], 1.01 *x_f); /* > 0 */ |
| x_f = pow(x[1]/x[0], 1./NS); |
| for (i = 1; i < NS; i++) |
| xx[i] = xx[i-1] * x_f; |
| } else { |
| double xstep = (x[1] - x[0])/NS; |
| for (i = 0; i < NS; i++) |
| xx[i] = x[0] + i*xstep; |
| } |
| xx[NS] = x[1]; |
| for (i = 0; i <= NS; i++) |
| yy[i] = aa + xx[i] * bb; |
| |
| /* now get rid of -ve values */ |
| lstart = 0;lstop = NS; |
| if (xlog) { |
| for(; lstart < NS+1 && xx[lstart] <= 0 ; lstart++); |
| for(; lstop > 0 && xx[lstop] <= 0 ; lstop--); |
| } |
| if (ylog) { |
| for(; lstart < NS+1 && yy[lstart] <= 0 ; lstart++); |
| for(; lstop > 0 && yy[lstop] <= 0 ; lstop--); |
| } |
| |
| GPolyline(lstop-lstart+1, xx+lstart, yy+lstart, USER, dd); |
| #undef NS |
| } else { /* non-log plots, possibly with log scales */ |
| |
| y[0] = aa + (xlog ? log10(x[0]) : x[0]) * bb; |
| y[1] = aa + (xlog ? log10(x[1]) : x[1]) * bb; |
| if (ylog) { |
| y[0] = Rexp10(y[0]); |
| y[1] = Rexp10(y[1]); |
| } |
| |
| GLine(x[0], y[0], x[1], y[1], USER, dd); |
| } |
| } |
| GMode(0, dd); |
| nlines++; |
| } |
| if (h != R_NilValue) { /* horizontal line */ |
| GMode(1, dd); |
| for (i = 0; i < LENGTH(h); i++) { |
| gpptr(dd)->col = INTEGER(col)[nlines % ncol]; |
| if (nlty && INTEGER(lty)[nlines % nlty] != NA_INTEGER) |
| gpptr(dd)->lty = INTEGER(lty)[nlines % nlty]; |
| else |
| gpptr(dd)->lty = dpptr(dd)->lty; |
| gpptr(dd)->lwd = REAL(lwd)[nlines % nlwd]; |
| aa = REAL(h)[i]; |
| if (R_FINITE(aa) && R_FINITE(gpptr(dd)->lwd)) { |
| getxlimits(x, dd); |
| y[0] = aa; |
| y[1] = aa; |
| GLine(x[0], y[0], x[1], y[1], USER, dd); |
| } |
| nlines++; |
| } |
| GMode(0, dd); |
| } |
| if (v != R_NilValue) { /* vertical line */ |
| GMode(1, dd); |
| for (i = 0; i < LENGTH(v); i++) { |
| gpptr(dd)->col = INTEGER(col)[nlines % ncol]; |
| if (nlty && INTEGER(lty)[nlines % nlty] != NA_INTEGER) |
| gpptr(dd)->lty = INTEGER(lty)[nlines % nlty]; |
| else |
| gpptr(dd)->lty = dpptr(dd)->lty; |
| gpptr(dd)->lwd = REAL(lwd)[nlines % nlwd]; |
| aa = REAL(v)[i]; |
| if (R_FINITE(aa) && R_FINITE(gpptr(dd)->lwd)) { |
| getylimits(y, dd); |
| x[0] = aa; |
| x[1] = aa; |
| GLine(x[0], y[0], x[1], y[1], USER, dd); |
| } |
| nlines++; |
| } |
| GMode(0, dd); |
| } |
| UNPROTECT(3); |
| GRestorePars(dd); |
| return R_NilValue; |
| } /* C_abline */ |
| |
| |
| SEXP C_box(SEXP args) |
| { |
| /* box(which="plot", lty="solid", ...) |
| --- which is coded, 1 = plot, 2 = figure, 3 = inner, 4 = outer. |
| */ |
| int which, col; |
| SEXP colsxp, fgsxp; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| GCheckState(dd); |
| GSavePars(dd); |
| args = CDR(args); |
| which = asInteger(CAR(args)); args = CDR(args); |
| if (which < 1 || which > 4) |
| error(_("invalid '%s' argument"), "which"); |
| /* |
| * If specified non-NA col then use that, else ... |
| * |
| * if specified non-NA fg then use that, else ... |
| * |
| * else use par("col") |
| */ |
| col= gpptr(dd)->col; |
| ProcessInlinePars(args, dd); |
| colsxp = getInlinePar(args, "col"); |
| if (isNAcol(colsxp, 0, 1)) { |
| fgsxp = getInlinePar(args, "fg"); |
| if (isNAcol(fgsxp, 0, 1)) |
| gpptr(dd)->col = col; |
| else |
| gpptr(dd)->col = gpptr(dd)->fg; |
| } |
| /* override par("xpd") and force clipping to device region */ |
| gpptr(dd)->xpd = 2; |
| GMode(1, dd); |
| GBox(which, dd); |
| GMode(0, dd); |
| GRestorePars(dd); |
| return R_NilValue; |
| } |
| |
| static void drawPointsLines(double xp, double yp, double xold, double yold, |
| char type, int first, pGEDevDesc dd) |
| { |
| if (type == 'p' || type == 'o') |
| GSymbol(xp, yp, DEVICE, gpptr(dd)->pch, dd); |
| if ((type == 'l' || type == 'o') && !first) |
| GLine(xold, yold, xp, yp, DEVICE, dd); |
| } |
| |
| SEXP C_locator(SEXP call, SEXP op, SEXP args, SEXP rho) |
| { |
| SEXP x, y, nobs, ans, saveans, stype = R_NilValue; |
| int i, n; |
| char type = 'p'; |
| double xp, yp, xold=0, yold=0; |
| pGEDevDesc dd = GEcurrentDevice(); |
| SEXP name = CAR(args); |
| |
| args = CDR(args); |
| /* If replaying, just draw the points and lines that were recorded */ |
| if (call == R_NilValue) { |
| x = CAR(args); args = CDR(args); |
| y = CAR(args); args = CDR(args); |
| nobs = CAR(args); args = CDR(args); |
| n = INTEGER(nobs)[0]; |
| stype = CAR(args); args = CDR(args); |
| type = CHAR(STRING_ELT(stype, 0))[0]; |
| if (type != 'n') { |
| GMode(1, dd); |
| for (i = 0; i < n; i++) { |
| xp = REAL(x)[i]; |
| yp = REAL(y)[i]; |
| GConvert(&xp, &yp, USER, DEVICE, dd); |
| drawPointsLines(xp, yp, xold, yold, type, i==0, dd); |
| xold = xp; |
| yold = yp; |
| } |
| GMode(0, dd); |
| } |
| return R_NilValue; |
| } else { |
| GCheckState(dd); |
| |
| n = asInteger(CAR(args)); |
| if (n <= 0 || n == NA_INTEGER) |
| error(_("invalid number of points in %s"), "locator()"); |
| args = CDR(args); |
| if (isString(CAR(args)) && LENGTH(CAR(args)) == 1) |
| stype = CAR(args); |
| else |
| error(_("invalid plot type")); |
| type = CHAR(STRING_ELT(stype, 0))[0]; |
| PROTECT(x = allocVector(REALSXP, n)); |
| PROTECT(y = allocVector(REALSXP, n)); |
| PROTECT(nobs=allocVector(INTSXP,1)); |
| |
| GMode(2, dd); |
| for (i = 0; i < n; i++) { |
| if (!GLocator(&(REAL(x)[i]), &(REAL(y)[i]), USER, dd)) break; |
| if (type != 'n') { |
| GMode(1, dd); |
| xp = REAL(x)[i]; |
| yp = REAL(y)[i]; |
| GConvert(&xp, &yp, USER, DEVICE, dd); |
| drawPointsLines(xp, yp, xold, yold, type, i==0, dd); |
| GMode(0, dd); |
| GMode(2, dd); |
| xold = xp; yold = yp; |
| } |
| } |
| GMode(0, dd); |
| INTEGER(nobs)[0] = i; |
| for (; i < n; i++) { |
| REAL(x)[i] = NA_REAL; |
| REAL(y)[i] = NA_REAL; |
| } |
| PROTECT(ans = allocList(3)); |
| SETCAR(ans, x); |
| SETCADR(ans, y); |
| SETCADDR(ans, nobs); |
| if (GRecording(call, dd)) { |
| PROTECT(saveans = allocList(5)); |
| SETCAR(saveans, name); |
| SETCADR(saveans, x); |
| SETCADDR(saveans, y); |
| SETCADDDR(saveans, nobs); |
| SETCAD4R(saveans, CAR(args)); |
| /* Record the points and lines that were drawn in the display list */ |
| GErecordGraphicOperation(op, saveans, dd); |
| UNPROTECT(1); |
| } |
| UNPROTECT(4); |
| return ans; |
| } |
| } |
| |
| static void drawLabel(double xi, double yi, int pos, double offset, |
| const char *l, cetype_t enc, pGEDevDesc dd) |
| { |
| switch (pos) { |
| case 4: |
| xi = xi+offset; |
| GText(xi, yi, INCHES, l, enc, 0.0, |
| dd->dev->yCharOffset, 0.0, dd); |
| break; |
| case 2: |
| xi = xi-offset; |
| GText(xi, yi, INCHES, l, enc, 1.0, |
| dd->dev->yCharOffset, 0.0, dd); |
| break; |
| case 3: |
| yi = yi+offset; |
| GText(xi, yi, INCHES, l, enc, 0.5, |
| 0.0, 0.0, dd); |
| break; |
| case 1: |
| yi = yi-offset; |
| GText(xi, yi, INCHES, l, enc, 0.5, |
| 1-(0.5-dd->dev->yCharOffset), |
| 0.0, dd); |
| break; |
| case 0: |
| GText(xi, yi, INCHES, l, enc, 0.0, 0.0, 0.0, dd); |
| break; |
| } |
| } |
| |
| SEXP C_identify(SEXP call, SEXP op, SEXP args, SEXP rho) |
| { |
| SEXP ans, x, y, l, ind, pos, order, Offset, draw, saveans; |
| double xi, yi, xp, yp, d, dmin, offset, tol; |
| int atpen, i, imin, k, n, nl, npts, plot, posi, warn; |
| pGEDevDesc dd = GEcurrentDevice(); |
| SEXP name = CAR(args); |
| |
| args = CDR(args); |
| /* If we are replaying the display list, then just redraw the |
| labels beside the identified points */ |
| if (call == R_NilValue) { |
| ind = CAR(args); args = CDR(args); |
| pos = CAR(args); args = CDR(args); |
| x = CAR(args); args = CDR(args); |
| y = CAR(args); args = CDR(args); |
| Offset = CAR(args); args = CDR(args); |
| l = CAR(args); args = CDR(args); |
| draw = CAR(args); |
| n = LENGTH(x); |
| nl = LENGTH(l); |
| /* |
| * Most of the appropriate settings have been set up in |
| * R code by par(...) |
| * Hence no GSavePars() or ProcessInlinePars() here |
| * (also because this function is unusual in that it does |
| * different things when run by a user compared to when |
| * run from the display list) |
| * BUT par(cex) only sets cexbase, so here we set cex from cexbase |
| */ |
| gpptr(dd)->cex = gpptr(dd)->cexbase; |
| offset = GConvertXUnits(asReal(Offset), CHARS, INCHES, dd); |
| for (i = 0; i < n; i++) { |
| plot = LOGICAL(ind)[i]; |
| if (LOGICAL(draw)[0] && plot) { |
| xi = REAL(x)[i]; |
| yi = REAL(y)[i]; |
| GConvert(&xi, &yi, USER, INCHES, dd); |
| posi = INTEGER(pos)[i]; |
| drawLabel(xi, yi, posi, offset, |
| CHAR(STRING_ELT(l, i % nl)), |
| getCharCE(STRING_ELT(l, i % nl)), dd); |
| } |
| } |
| return R_NilValue; |
| } |
| else { |
| GCheckState(dd); |
| |
| x = CAR(args); args = CDR(args); |
| y = CAR(args); args = CDR(args); |
| l = CAR(args); args = CDR(args); |
| npts = asInteger(CAR(args)); args = CDR(args); |
| plot = asLogical(CAR(args)); args = CDR(args); |
| Offset = CAR(args); args = CDR(args); |
| tol = asReal(CAR(args)); args = CDR(args); |
| atpen = asLogical(CAR(args)); |
| if (npts <= 0 || npts == NA_INTEGER) |
| error(_("invalid number of points in %s"), "identify()"); |
| if (!isReal(x) || !isReal(y) || !isString(l) || !isReal(Offset)) |
| error(_("incorrect argument type")); |
| if (tol <= 0 || ISNAN(tol)) |
| error(_("invalid '%s' value"), "tolerance"); |
| if (plot == NA_LOGICAL) |
| error(_("invalid '%s' value"), "plot"); |
| if (atpen == NA_LOGICAL) |
| error(_("invalid '%s' value"), "atpen"); |
| nl = LENGTH(l); |
| if (nl <= 0) |
| error(_("zero-length '%s' specified"), "labels"); |
| n = LENGTH(x); |
| if (n != LENGTH(y)) |
| error(_("different argument lengths")); |
| if (nl > n) |
| warning(_("more 'labels' than points")); |
| |
| /* |
| * Most of the appropriate settings have been set up in |
| * R code by par(...) |
| * Hence no GSavePars() or ProcessInlinePars() here |
| * (also because this function is unusual in that it does |
| * different things when run by a user compared to when |
| * run from the display list) |
| * BUT par(cex) only sets cexbase, so here we set cex from cexbase |
| */ |
| gpptr(dd)->cex = gpptr(dd)->cexbase; |
| offset = GConvertXUnits(asReal(Offset), CHARS, INCHES, dd); |
| PROTECT(ind = allocVector(LGLSXP, n)); |
| PROTECT(pos = allocVector(INTSXP, n)); |
| PROTECT(order = allocVector(INTSXP, n)); |
| for (i = 0; i < n; i++) { |
| LOGICAL(ind)[i] = 0; |
| INTEGER(order)[i] = 0; |
| } |
| k = 0; |
| GMode(2, dd); |
| PROTECT(x = duplicate(x)); |
| PROTECT(y = duplicate(y)); |
| while (k < npts) { |
| if (!GLocator(&xp, &yp, INCHES, dd)) break; |
| /* |
| * Repeat cex setting from cexbase within loop |
| * so that if window is redrawn |
| * (e.g., conver/uncover window) |
| * during identifying (i.e., between clicks) |
| * we reset cex properly. |
| */ |
| gpptr(dd)->cex = gpptr(dd)->cexbase; |
| dmin = DBL_MAX; |
| imin = -1; |
| for (i = 0; i < n; i++) { |
| xi = REAL(x)[i]; |
| yi = REAL(y)[i]; |
| GConvert(&xi, &yi, USER, INCHES, dd); |
| if (!R_FINITE(xi) || !R_FINITE(yi)) continue; |
| d = hypot(xp-xi, yp-yi); |
| if (d < dmin) { |
| imin = i; |
| dmin = d; |
| } |
| } |
| /* can't use warning because we want to print immediately */ |
| /* might want to handle warn=2? */ |
| warn = asInteger(GetOption1(install("warn"))); |
| if (dmin > tol) { |
| if(warn >= 0) { |
| REprintf(_("warning: no point within %.2f inches\n"), tol); |
| R_FlushConsole(); |
| } |
| } |
| else if (LOGICAL(ind)[imin]) { |
| if(warn >= 0 ) { |
| REprintf(_("warning: nearest point already identified\n")); |
| R_FlushConsole(); |
| } |
| } |
| else { |
| k++; |
| LOGICAL(ind)[imin] = 1; |
| INTEGER(order)[imin] = k; |
| if (atpen) { |
| xi = xp; |
| yi = yp; |
| INTEGER(pos)[imin] = 0; |
| /* now record where to replot if necessary */ |
| GConvert(&xp, &yp, INCHES, USER, dd); |
| REAL(x)[imin] = xp; REAL(y)[imin] = yp; |
| } else { |
| xi = REAL(x)[imin]; |
| yi = REAL(y)[imin]; |
| GConvert(&xi, &yi, USER, INCHES, dd); |
| if (fabs(xp-xi) >= fabs(yp-yi)) { |
| if (xp >= xi) |
| INTEGER(pos)[imin] = 4; |
| else |
| INTEGER(pos)[imin] = 2; |
| } else { |
| if (yp >= yi) |
| INTEGER(pos)[imin] = 3; |
| else |
| INTEGER(pos)[imin] = 1; |
| } |
| } |
| if (plot) { |
| drawLabel(xi, yi, INTEGER(pos)[imin], offset, |
| CHAR(STRING_ELT(l, imin % nl)), |
| getCharCE(STRING_ELT(l, imin % nl)), dd); |
| GMode(0, dd); |
| GMode(2, dd); |
| } |
| } |
| } |
| GMode(0, dd); |
| PROTECT(ans = allocList(3)); |
| SETCAR(ans, ind); |
| SETCADR(ans, pos); |
| SETCADDR(ans, order); |
| if (GRecording(call, dd)) { |
| /* If we are recording, save enough information to be able to |
| redraw the text labels beside identified points */ |
| PROTECT(saveans = allocList(8)); |
| SETCAR(saveans, name); |
| SETCADR(saveans, ind); |
| SETCADDR(saveans, pos); |
| SETCADDDR(saveans, x); |
| SETCAD4R(saveans, y); |
| SETCAR(nthcdr(saveans,5), Offset); |
| SETCAR(nthcdr(saveans,6), l); |
| SETCAR(nthcdr(saveans,7), ScalarLogical(plot)); |
| |
| GErecordGraphicOperation(op, saveans, dd); |
| UNPROTECT(1); |
| } |
| UNPROTECT(6); |
| |
| return ans; |
| } |
| } |
| |
| /* strheight(str, units, cex, font, vfont, ...) || strwidth() */ |
| #define DO_STR_DIM(KIND) \ |
| { \ |
| SEXP ans, str, ch, font, vfont; \ |
| int i, n, units; \ |
| double cex, cexsave; \ |
| pGEDevDesc dd = GEcurrentDevice(); \ |
| args = CDR(args); \ |
| if (length(args) < 5) error(_("too few arguments")); \ |
| \ |
| str = CAR(args); \ |
| if (isSymbol(str) || isLanguage(str)) \ |
| str = coerceVector(str, EXPRSXP); \ |
| else if (!isExpression(str)) \ |
| str = coerceVector(str, STRSXP); \ |
| PROTECT(str); \ |
| args = CDR(args); \ |
| \ |
| if ((units = asInteger(CAR(args))) == NA_INTEGER || units < 0) \ |
| error(_("invalid units")); \ |
| if(units == 1) GCheckState(dd); \ |
| args = CDR(args); \ |
| \ |
| if (isNull(CAR(args))) \ |
| cex = gpptr(dd)->cex; \ |
| else if (!R_FINITE((cex = asReal(CAR(args)))) || cex <= 0.0) \ |
| error(_("invalid '%s' value"), "cex"); \ |
| args = CDR(args); \ |
| PROTECT(font = FixupFont(CAR(args), NA_INTEGER)); args = CDR(args); \ |
| PROTECT(vfont = FixupVFont(CAR(args))); args = CDR(args); \ |
| GSavePars(dd); \ |
| ProcessInlinePars(args, dd); \ |
| \ |
| /* 'vfont' trumps inline 'family' */ \ |
| if (!isNull(vfont) && !isExpression(str)) { \ |
| strncpy(gpptr(dd)->family, "Hershey ", 201); \ |
| gpptr(dd)->family[7] = (char)INTEGER(vfont)[0]; \ |
| gpptr(dd)->font = INTEGER(vfont)[1]; \ |
| } else if (INTEGER(font)[0] != NA_INTEGER) { \ |
| gpptr(dd)->font = INTEGER(font)[0]; \ |
| } \ |
| \ |
| n = LENGTH(str); \ |
| PROTECT(ans = allocVector(REALSXP, n)); \ |
| cexsave = gpptr(dd)->cex; \ |
| gpptr(dd)->cex = cex * gpptr(dd)->cexbase; \ |
| for (i = 0; i < n; i++) \ |
| if (isExpression(str)) \ |
| REAL(ans)[i] = GExpression ## KIND(VECTOR_ELT(str, i), \ |
| GMapUnits(units), dd); \ |
| else { \ |
| ch = STRING_ELT(str, i); \ |
| REAL(ans)[i] = (ch == NA_STRING) ? 0.0 : \ |
| GStr ## KIND(CHAR(ch), getCharCE(ch), GMapUnits(units), dd); \ |
| } \ |
| gpptr(dd)->cex = cexsave; \ |
| GRestorePars(dd); \ |
| UNPROTECT(4); \ |
| return ans; \ |
| } |
| |
| SEXP C_strHeight(SEXP args) |
| DO_STR_DIM(Height) |
| |
| SEXP C_strWidth (SEXP args) |
| DO_STR_DIM(Width) |
| |
| #undef DO_STR_DIM |
| |
| |
| /* C_dend() and C_dendwindow() called only from plotHclust() from stats:::plot.hclust() : |
| * |
| * ==> all *vertical* dendrograms |
| * |
| * In contrast: plot.dendrogram() only calls R level graphics functions |
| */ |
| |
| static int *dnd_lptr; |
| static int *dnd_rptr; |
| static double *dnd_hght; |
| static double *dnd_xpos; |
| static double dnd_hang; |
| static double dnd_offset; |
| |
| static void drawdend(int node, double *x, double *y, SEXP dnd_llabels, |
| pGEDevDesc dd) |
| { |
| /* Recursive function for 'hclust' dendrogram drawing: |
| * Do left + Do right + Do myself |
| * "do" : 1) label leafs (if there are) and __ |
| * 2) find coordinates to draw the | | |
| * 3) return (*x,*y) of "my anchor" |
| */ |
| double xl, xr, yl, yr; |
| double xx[4], yy[4]; |
| int k; |
| |
| *y = dnd_hght[node-1]; |
| /* left part */ |
| k = dnd_lptr[node-1]; |
| if (k > 0) drawdend(k, &xl, &yl, dnd_llabels, dd); |
| else { |
| xl = dnd_xpos[-k-1]; |
| yl = (dnd_hang >= 0) ? *y - dnd_hang : 0; |
| if(STRING_ELT(dnd_llabels, -k-1) != NA_STRING) |
| GText(xl, yl-dnd_offset, USER, |
| CHAR(STRING_ELT(dnd_llabels, -k-1)), |
| getCharCE(STRING_ELT(dnd_llabels, -k-1)), |
| 1.0, 0.3, 90.0, dd); |
| } |
| /* right part */ |
| k = dnd_rptr[node-1]; |
| if (k > 0) drawdend(k, &xr, &yr, dnd_llabels, dd); |
| else { |
| xr = dnd_xpos[-k-1]; |
| yr = (dnd_hang >= 0) ? *y - dnd_hang : 0; |
| if(STRING_ELT(dnd_llabels, -k-1) != NA_STRING) |
| GText(xr, yr-dnd_offset, USER, |
| CHAR(STRING_ELT(dnd_llabels, -k-1)), |
| getCharCE(STRING_ELT(dnd_llabels, -k-1)), |
| 1.0, 0.3, 90.0, dd); |
| } |
| xx[0] = xl; yy[0] = yl; |
| xx[1] = xl; yy[1] = *y; |
| xx[2] = xr; yy[2] = *y; |
| xx[3] = xr; yy[3] = yr; |
| GPolyline(4, xx, yy, USER, dd); |
| *x = 0.5 * (xl + xr); |
| } |
| |
| SEXP C_dend(SEXP args) |
| { |
| double x, y; |
| int n; |
| |
| SEXP dnd_llabels, xpos; |
| pGEDevDesc dd; |
| |
| dd = GEcurrentDevice(); |
| GCheckState(dd); |
| |
| args = CDR(args); |
| if (length(args) < 6) |
| error(_("too few arguments")); |
| |
| /* n */ |
| n = asInteger(CAR(args)); |
| if (n == NA_INTEGER || n < 2) |
| goto badargs; |
| args = CDR(args); |
| |
| /* merge */ |
| if (TYPEOF(CAR(args)) != INTSXP || length(CAR(args)) != 2*n) |
| goto badargs; |
| dnd_lptr = &(INTEGER(CAR(args))[0]); |
| dnd_rptr = &(INTEGER(CAR(args))[n]); |
| args = CDR(args); |
| |
| /* height */ |
| if (TYPEOF(CAR(args)) != REALSXP || length(CAR(args)) != n) |
| goto badargs; |
| dnd_hght = REAL(CAR(args)); |
| args = CDR(args); |
| |
| /* ord = order(x$order) */ |
| if (length(CAR(args)) != n+1) |
| goto badargs; |
| PROTECT(xpos = coerceVector(CAR(args), REALSXP)); |
| dnd_xpos = REAL(xpos); |
| args = CDR(args); |
| |
| /* hang */ |
| dnd_hang = asReal(CAR(args)); |
| if (!R_FINITE(dnd_hang)) |
| goto badargs; |
| dnd_hang = dnd_hang * (dnd_hght[n-1] - dnd_hght[0]); |
| args = CDR(args); |
| |
| /* labels */ |
| if (TYPEOF(CAR(args)) != STRSXP || length(CAR(args)) != n+1) |
| goto badargs; |
| dnd_llabels = CAR(args); |
| args = CDR(args); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| gpptr(dd)->cex = gpptr(dd)->cexbase * gpptr(dd)->cex; |
| dnd_offset = GConvertYUnits(GStrWidth("m", CE_ANY, INCHES, dd), INCHES, |
| USER, dd); |
| |
| /* override par("xpd") and force clipping to figure region |
| NOTE: don't override to _reduce_ clipping region */ |
| if (gpptr(dd)->xpd < 1) |
| gpptr(dd)->xpd = 1; |
| |
| GMode(1, dd); |
| drawdend(n, &x, &y, dnd_llabels, dd); |
| GMode(0, dd); |
| GRestorePars(dd); |
| UNPROTECT(1); |
| return R_NilValue; |
| |
| badargs: |
| error(_("invalid dendrogram input")); |
| return R_NilValue;/* never used; to keep -Wall happy */ |
| } |
| |
| SEXP C_dendwindow(SEXP args) |
| { |
| int i, imax, n; |
| double pin, *ll, tmp, yval, *y, ymin, ymax, yrange, m; |
| SEXP merge, height, llabels, str; |
| const void *vmax; |
| pGEDevDesc dd; |
| |
| dd = GEcurrentDevice(); |
| GCheckState(dd); |
| args = CDR(args); |
| if (length(args) < 5) |
| error(_("too few arguments")); |
| n = asInteger(CAR(args)); |
| if (n == NA_INTEGER || n < 2) |
| goto badargs; |
| args = CDR(args); |
| if (TYPEOF(CAR(args)) != INTSXP || length(CAR(args)) != 2 * n) |
| goto badargs; |
| merge = CAR(args); |
| |
| args = CDR(args); |
| if (TYPEOF(CAR(args)) != REALSXP || length(CAR(args)) != n) |
| goto badargs; |
| height = CAR(args); |
| |
| args = CDR(args); |
| dnd_hang = asReal(CAR(args)); |
| if (!R_FINITE(dnd_hang)) |
| goto badargs; |
| |
| args = CDR(args); |
| if (TYPEOF(CAR(args)) != STRSXP || length(CAR(args)) != n + 1) |
| goto badargs; |
| llabels = CAR(args); |
| |
| args = CDR(args); |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| gpptr(dd)->cex = gpptr(dd)->cexbase * gpptr(dd)->cex; |
| dnd_offset = GStrWidth("m", CE_ANY, INCHES, dd); |
| vmax = vmaxget(); |
| /* n is the number of merges, so the points are labelled 1 ... n+1 */ |
| y = (double*)R_alloc(n+1, sizeof(double)); |
| ll = (double*)R_alloc(n+1, sizeof(double)); |
| dnd_lptr = &(INTEGER(merge)[0]); |
| dnd_rptr = &(INTEGER(merge)[n]); |
| ymax = ymin = REAL(height)[0]; |
| for (i = 1; i < n; i++) { |
| m = REAL(height)[i]; |
| if (m > ymax) |
| ymax = m; |
| else if (m < ymin) |
| ymin = m; |
| } |
| pin = gpptr(dd)->pin[1]; |
| for (i = 0; i <= n; i++) { |
| str = STRING_ELT(llabels, i); |
| ll[i] = (str == NA_STRING) ? 0.0 : |
| GStrWidth(CHAR(str), getCharCE(str), INCHES, dd) + dnd_offset; |
| } |
| |
| imax = -1; yval = -DBL_MAX; |
| if (dnd_hang >= 0) { |
| ymin = ymax - (1 + dnd_hang) * (ymax - ymin); |
| yrange = ymax - ymin; |
| /* determine leaf heights */ |
| for (i = 0; i < n; i++) { |
| if (dnd_lptr[i] < 0) |
| y[-dnd_lptr[i] - 1] = REAL(height)[i]; |
| if (dnd_rptr[i] < 0) |
| y[-dnd_rptr[i] - 1] = REAL(height)[i]; |
| } |
| /* determine the most extreme label depth */ |
| /* assuming that we are using the full plot */ |
| /* window for the tree itself */ |
| for (i = 0; i <= n; i++) { |
| tmp = ((ymax - y[i]) / yrange) * pin + ll[i]; |
| if (tmp > yval) { |
| yval = tmp; |
| imax = i; |
| } |
| } |
| } |
| else { |
| yrange = ymax; |
| for (i = 0; i <= n; i++) { |
| tmp = pin + ll[i]; |
| if (tmp > yval) { |
| yval = tmp; |
| imax = i; |
| } |
| } |
| } |
| /* now determine how much to scale */ |
| ymin = ymax - (pin/(pin - ll[imax])) * yrange; |
| GScale(1.0, n+1.0, 1 /* x */, dd); |
| GScale(ymin, ymax, 2 /* y */, dd); |
| GMapWin2Fig(dd); |
| GRestorePars(dd); |
| vmaxset(vmax); |
| return R_NilValue; |
| badargs: |
| error(_("invalid dendrogram input")); |
| return R_NilValue;/* never used; to keep -Wall happy */ |
| } |
| |
| SEXP C_erase(SEXP args) |
| { |
| SEXP col; |
| pGEDevDesc dd = GEcurrentDevice(); |
| args = CDR(args); |
| PROTECT(col = FixupCol(CAR(args), R_TRANWHITE)); |
| GSavePars(dd); |
| GMode(1, dd); |
| GRect(0.0, 0.0, 1.0, 1.0, NDC, INTEGER(col)[0], R_TRANWHITE, dd); |
| GMode(0, dd); |
| GRestorePars(dd); |
| UNPROTECT(1); |
| return R_NilValue; |
| } |
| |
| |
| /* symbols(..) in ../R/symbols.R : */ |
| |
| /* utility just computing range() */ |
| static Rboolean SymbolRange(double *x, int n, double *xmax, double *xmin) |
| { |
| int i; |
| *xmax = -DBL_MAX; |
| *xmin = DBL_MAX; |
| for(i = 0; i < n; i++) |
| if (R_FINITE(x[i])) { |
| if (*xmax < x[i]) *xmax = x[i]; |
| if (*xmin > x[i]) *xmin = x[i]; |
| } |
| return(*xmax >= *xmin && *xmin >= 0); |
| } |
| |
| static void CheckSymbolPar(SEXP p, int *nr, int *nc) |
| { |
| SEXP dim = getAttrib(p, R_DimSymbol); |
| switch(length(dim)) { |
| case 0: |
| *nr = LENGTH(p); |
| *nc = 1; |
| break; |
| case 1: |
| *nr = INTEGER(dim)[0]; |
| *nc = 1; |
| break; |
| case 2: |
| *nr = INTEGER(dim)[0]; |
| *nc = INTEGER(dim)[1]; |
| break; |
| default: |
| *nr = 0; |
| *nc = 0; |
| } |
| if (*nr == 0 || *nc == 0) |
| error(_("invalid symbol parameter vector")); |
| } |
| |
| /* Internal symbols(x, y, type, data, inches, bg, fg, ...) */ |
| SEXP C_symbols(SEXP args) |
| { |
| SEXP x, y, p, fg, bg; |
| int i, j, nr, nc, nbg, nfg, type; |
| double pmax, pmin, inches, rx, ry; |
| double xx, yy, p0, p1, p2, p3, p4; |
| double *pp, *xp, *yp; |
| const void *vmax; |
| |
| pGEDevDesc dd = GEcurrentDevice(); |
| GCheckState(dd); |
| args = CDR(args); |
| |
| if (length(args) < 7) |
| error(_("too few arguments")); |
| |
| PROTECT(x = coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| PROTECT(y = coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| if (!isNumeric(x) || !isNumeric(y) || length(x) <= 0 || LENGTH(x) <= 0) |
| error(_("invalid symbol coordinates")); |
| |
| type = asInteger(CAR(args)); args = CDR(args); |
| |
| /* data: */ |
| p = PROTECT(coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| CheckSymbolPar(p, &nr, &nc); |
| if (LENGTH(x) != nr || LENGTH(y) != nr) |
| error(_("x/y/parameter length mismatch")); |
| |
| inches = asReal(CAR(args)); args = CDR(args); |
| if (!R_FINITE(inches) || inches < 0) |
| inches = 0; |
| |
| PROTECT(bg = FixupCol(CAR(args), R_TRANWHITE)); args = CDR(args); |
| nbg = LENGTH(bg); |
| |
| PROTECT(fg = FixupCol(CAR(args), R_TRANWHITE)); args = CDR(args); |
| nfg = LENGTH(fg); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| |
| GMode(1, dd); |
| switch (type) { |
| case 1: /* circles */ |
| if (nc != 1) |
| error(_("invalid circles data")); |
| if (!SymbolRange(REAL(p), nr, &pmax, &pmin)) |
| error(_("invalid symbol parameter")); |
| for (i = 0; i < nr; i++) { |
| if (R_FINITE(REAL(x)[i]) && R_FINITE(REAL(y)[i]) && |
| R_FINITE(REAL(p)[i])) { |
| rx = REAL(p)[i]; |
| /* For GCircle the radius is always in INCHES */ |
| if (inches > 0) |
| rx *= inches / pmax; |
| else |
| rx = GConvertXUnits(rx, USER, INCHES, dd); |
| /* GCircle sets radius zero to one pixel, but does |
| not change very small non-zero radii */ |
| GCircle(REAL(x)[i], REAL(y)[i], USER, rx, |
| INTEGER(bg)[i % nbg], INTEGER(fg)[i % nfg], dd); |
| } |
| } |
| break; |
| case 2: /* squares */ |
| if(nc != 1) |
| error(_("invalid squares data")); |
| if(!SymbolRange(REAL(p), nr, &pmax, &pmin)) |
| error(_("invalid symbol parameter")); |
| for (i = 0; i < nr; i++) { |
| if (R_FINITE(REAL(x)[i]) && R_FINITE(REAL(y)[i]) && |
| R_FINITE(REAL(p)[i])) { |
| p0 = REAL(p)[i]; |
| xx = REAL(x)[i]; |
| yy = REAL(y)[i]; |
| GConvert(&xx, &yy, USER, DEVICE, dd); |
| if (inches > 0) { |
| p0 *= inches / pmax; |
| rx = GConvertXUnits(0.5 * p0, INCHES, DEVICE, dd); |
| } |
| else { |
| rx = GConvertXUnits(0.5 * p0, USER, DEVICE, dd); |
| } |
| /* FIXME: should this skip 0-sized symbols? */ |
| GRect(xx - rx, yy - rx, xx + rx, yy + rx, DEVICE, |
| INTEGER(bg)[i % nbg], INTEGER(fg)[i % nfg], dd); |
| } |
| } |
| break; |
| case 3: /* rectangles */ |
| if (nc != 2) |
| error(_("invalid rectangles data (need 2 columns)")); |
| if (!SymbolRange(REAL(p), 2 * nr, &pmax, &pmin)) |
| error(_("invalid symbol parameter")); |
| for (i = 0; i < nr; i++) { |
| if (R_FINITE(REAL(x)[i]) && R_FINITE(REAL(y)[i]) && |
| R_FINITE(REAL(p)[i]) && R_FINITE(REAL(p)[i+nr])) { |
| xx = REAL(x)[i]; |
| yy = REAL(y)[i]; |
| GConvert(&xx, &yy, USER, DEVICE, dd); |
| p0 = REAL(p)[i]; |
| p1 = REAL(p)[i+nr]; |
| if (inches > 0) { |
| p0 *= inches / pmax; |
| p1 *= inches / pmax; |
| rx = GConvertXUnits(0.5 * p0, INCHES, DEVICE, dd); |
| ry = GConvertYUnits(0.5 * p1, INCHES, DEVICE, dd); |
| } |
| else { |
| rx = GConvertXUnits(0.5 * p0, USER, DEVICE, dd); |
| ry = GConvertYUnits(0.5 * p1, USER, DEVICE, dd); |
| } |
| /* FIXME: should this skip 0-sized symbols? */ |
| GRect(xx - rx, yy - ry, xx + rx, yy + ry, DEVICE, |
| INTEGER(bg)[i % nbg], INTEGER(fg)[i % nfg], dd); |
| |
| } |
| } |
| break; |
| case 4: /* stars */ |
| if (nc < 3) |
| error(_("invalid stars data")); |
| if (!SymbolRange(REAL(p), nc * nr, &pmax, &pmin)) |
| error(_("invalid symbol parameter")); |
| vmax = vmaxget(); |
| pp = (double*)R_alloc(nc, sizeof(double)); |
| xp = (double*)R_alloc(nc, sizeof(double)); |
| yp = (double*)R_alloc(nc, sizeof(double)); |
| p1 = 2.0 * M_PI / nc; |
| for (i = 0; i < nr; i++) { |
| xx = REAL(x)[i]; |
| yy = REAL(y)[i]; |
| if (R_FINITE(xx) && R_FINITE(yy)) { |
| GConvert(&xx, &yy, USER, NDC, dd); |
| if (inches > 0) { |
| for(j = 0; j < nc; j++) { |
| p0 = REAL(p)[i + j * nr]; |
| if (!R_FINITE(p0)) p0 = 0; |
| pp[j] = (p0 / pmax) * inches; |
| } |
| } |
| else { |
| for(j = 0; j < nc; j++) { |
| p0 = REAL(p)[i + j * nr]; |
| if (!R_FINITE(p0)) p0 = 0; |
| pp[j] = GConvertXUnits(p0, USER, INCHES, dd); |
| } |
| } |
| /* FIXME: should this skip 0-sized symbols? */ |
| for(j = 0; j < nc; j++) { |
| xp[j] = GConvertXUnits(pp[j] * cos(j * p1), |
| INCHES, NDC, dd) + xx; |
| yp[j] = GConvertYUnits(pp[j] * sin(j * p1), |
| INCHES, NDC, dd) + yy; |
| } |
| GPolygon(nc, xp, yp, NDC, |
| INTEGER(bg)[i % nbg], INTEGER(fg)[i % nfg], dd); |
| } |
| } |
| vmaxset(vmax); |
| break; |
| case 5: /* thermometers */ |
| if (nc != 3 && nc != 4) |
| error(_("invalid thermometers data (need 3 or 4 columns)")); |
| SymbolRange(REAL(p)+2*nr/* <-- pointer arith*/, nr, &pmax, &pmin); |
| if (pmax < pmin) |
| error(_("invalid 'thermometers[, %s]'"), |
| (nc == 4)? "3:4" : "3"); |
| if (pmin < 0. || pmax > 1.) /* S-PLUS has an error here */ |
| warning(_("'thermometers[, %s]' not in [0,1] -- may look funny"), |
| (nc == 4)? "3:4" : "3"); |
| if (!SymbolRange(REAL(p), 2 * nr, &pmax, &pmin)) |
| error(_("invalid 'thermometers[, 1:2]'")); |
| for (i = 0; i < nr; i++) { |
| xx = REAL(x)[i]; |
| yy = REAL(y)[i]; |
| if (R_FINITE(xx) && R_FINITE(yy)) { |
| p0 = REAL(p)[i]; |
| p1 = REAL(p)[i + nr]; |
| p2 = REAL(p)[i + 2 * nr]; |
| p3 = (nc == 4)? REAL(p)[i + 3 * nr] : 0.; |
| if (R_FINITE(p0) && R_FINITE(p1) && |
| R_FINITE(p2) && R_FINITE(p3)) { |
| if (p2 < 0) p2 = 0; else if (p2 > 1) p2 = 1; |
| if (p3 < 0) p3 = 0; else if (p3 > 1) p3 = 1; |
| GConvert(&xx, &yy, USER, NDC, dd); |
| if (inches > 0) { |
| p0 *= inches / pmax; |
| p1 *= inches / pmax; |
| rx = GConvertXUnits(0.5 * p0, INCHES, NDC, dd); |
| ry = GConvertYUnits(0.5 * p1, INCHES, NDC, dd); |
| } |
| else { |
| rx = GConvertXUnits(0.5 * p0, USER, NDC, dd); |
| ry = GConvertYUnits(0.5 * p1, USER, NDC, dd); |
| } |
| GRect(xx - rx, yy - ry, xx + rx, yy + ry, NDC, |
| INTEGER(bg)[i % nbg], INTEGER(fg)[i % nfg], dd); |
| GRect(xx - rx, yy - (1 - 2 * p2) * ry, |
| xx + rx, yy - (1 - 2 * p3) * ry, |
| NDC, |
| INTEGER(fg)[i % nfg], INTEGER(fg)[i % nfg], dd); |
| GLine(xx - rx, yy, xx - 1.5 * rx, yy, NDC, dd); |
| GLine(xx + rx, yy, xx + 1.5 * rx, yy, NDC, dd); |
| |
| } |
| } |
| } |
| break; |
| case 6: /* boxplots (wid, hei, loWhsk, upWhsk, medProp) */ |
| if (nc != 5) |
| error(_("invalid 'boxplots' data (need 5 columns)")); |
| pmax = -DBL_MAX; |
| pmin = DBL_MAX; |
| for(i = 0; i < nr; i++) { |
| p4 = REAL(p)[i + 4 * nr]; /* median proport. in [0,1] */ |
| if (pmax < p4) pmax = p4; |
| if (pmin > p4) pmin = p4; |
| } |
| if (pmin < 0. || pmax > 1.) /* S-PLUS has an error here */ |
| warning(_("'boxplots[, 5]' outside [0,1] -- may look funny")); |
| if (!SymbolRange(REAL(p), 4 * nr, &pmax, &pmin)) |
| error(_("invalid 'boxplots[, 1:4]'")); |
| for (i = 0; i < nr; i++) { |
| xx = REAL(x)[i]; |
| yy = REAL(y)[i]; |
| if (R_FINITE(xx) && R_FINITE(yy)) { |
| p0 = REAL(p)[i]; /* width */ |
| p1 = REAL(p)[i + nr]; /* height */ |
| p2 = REAL(p)[i + 2 * nr];/* lower whisker */ |
| p3 = REAL(p)[i + 3 * nr];/* upper whisker */ |
| p4 = REAL(p)[i + 4 * nr];/* median proport. in [0,1] */ |
| if (R_FINITE(p0) && R_FINITE(p1) && |
| R_FINITE(p2) && R_FINITE(p3) && R_FINITE(p4)) { |
| GConvert(&xx, &yy, USER, NDC, dd); |
| if (inches > 0) { |
| p0 *= inches / pmax; |
| p1 *= inches / pmax; |
| p2 *= inches / pmax; |
| p3 *= inches / pmax; |
| p0 = GConvertXUnits(p0, INCHES, NDC, dd); |
| p1 = GConvertYUnits(p1, INCHES, NDC, dd); |
| p2 = GConvertYUnits(p2, INCHES, NDC, dd); |
| p3 = GConvertYUnits(p3, INCHES, NDC, dd); |
| } |
| else { |
| p0 = GConvertXUnits(p0, USER, NDC, dd); |
| p1 = GConvertYUnits(p1, USER, NDC, dd); |
| p2 = GConvertYUnits(p2, USER, NDC, dd); |
| p3 = GConvertYUnits(p3, USER, NDC, dd); |
| } |
| rx = 0.5 * p0; |
| ry = 0.5 * p1; |
| p4 = (1 - p4) * (yy - ry) + p4 * (yy + ry); |
| /* Box */ |
| GRect(xx - rx, yy - ry, xx + rx, yy + ry, NDC, |
| INTEGER(bg)[i % nbg], INTEGER(fg)[i % nfg], dd); |
| /* Median */ |
| GLine(xx - rx, p4, xx + rx, p4, NDC, dd); |
| /* Lower Whisker */ |
| GLine(xx, yy - ry, xx, yy - ry - p2, NDC, dd); |
| /* Upper Whisker */ |
| GLine(xx, yy + ry, xx, yy + ry + p3, NDC, dd); |
| } |
| } |
| } |
| break; |
| default: |
| error(_("invalid symbol type")); |
| } |
| GMode(0, dd); |
| GRestorePars(dd); |
| UNPROTECT(5); |
| return R_NilValue; |
| } |
| |
| SEXP C_xspline(SEXP args) |
| { |
| SEXP sx, sy, ss, col, border, res, ans = R_NilValue; |
| int i, nx; |
| int ncol, nborder; |
| double *x, *y; |
| Rboolean open, repEnds, draw; |
| double *xx; |
| double *yy; |
| const void *vmaxsave; |
| R_GE_gcontext gc; |
| |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| GCheckState(dd); |
| args = CDR(args); |
| |
| if (length(args) < 6) error(_("too few arguments")); |
| /* (x,y) is checked in R via xy.coords() ; no need here : */ |
| sx = SETCAR(args, coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| sy = SETCAR(args, coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| nx = LENGTH(sx); |
| ss = SETCAR(args, coerceVector(CAR(args), REALSXP)); args = CDR(args); |
| open = asLogical(CAR(args)); args = CDR(args); |
| repEnds = asLogical(CAR(args)); args = CDR(args); |
| draw = asLogical(CAR(args)); args = CDR(args); |
| |
| PROTECT(col = FixupCol(CAR(args), R_TRANWHITE)); args = CDR(args); |
| ncol = LENGTH(col); |
| if(ncol < 1) |
| error(_("incorrect length for '%s' argument"), "col"); |
| if(ncol > 1) |
| warning(_("incorrect length for '%s' argument"), "col"); |
| |
| PROTECT(border = FixupCol(CAR(args), gpptr(dd)->fg)); args = CDR(args); |
| nborder = LENGTH(border); |
| if(nborder < 1) |
| error(_("incorrect length for '%s' argument"), "border"); |
| if(nborder > 1) |
| warning(_("incorrect length for '%s' argument"), "border"); |
| |
| GSavePars(dd); |
| ProcessInlinePars(args, dd); |
| /* Paul 2008-12-05 |
| * Convert GP to gcontext AFTER ProcessInlinePars |
| */ |
| gcontextFromGP(&gc, dd); |
| |
| GMode(1, dd); |
| |
| x = REAL(sx); |
| y = REAL(sy); |
| vmaxsave = vmaxget(); |
| xx = (double *) R_alloc(nx, sizeof(double)); |
| yy = (double *) R_alloc(nx, sizeof(double)); |
| if (!xx || !yy) |
| error("unable to allocate memory (in xspline)"); |
| for (i = 0; i < nx; i++) { |
| xx[i] = x[i]; |
| yy[i] = y[i]; |
| GConvert(&(xx[i]), &(yy[i]), USER, DEVICE, dd); |
| } |
| GClip(dd); |
| gc.col = INTEGER(border)[0]; |
| gc.fill = INTEGER(col)[0]; |
| res = GEXspline(nx, xx, yy, REAL(ss), open, repEnds, draw, &gc, dd); |
| vmaxset(vmaxsave); |
| UNPROTECT(2); |
| |
| if(!draw) { |
| SEXP nm, tmpx, tmpy; |
| double *xx, *yy, *x0, *y0; |
| PROTECT(ans = res); |
| PROTECT(nm = allocVector(STRSXP, 2)); |
| SET_STRING_ELT(nm, 0, mkChar("x")); |
| SET_STRING_ELT(nm, 1, mkChar("y")); |
| setAttrib(ans, R_NamesSymbol, nm); |
| nx = LENGTH(VECTOR_ELT(ans, 0)); |
| x0 = REAL(VECTOR_ELT(ans, 0)); |
| y0 = REAL(VECTOR_ELT(ans, 1)); |
| PROTECT(tmpx = allocVector(REALSXP, nx)); |
| PROTECT(tmpy = allocVector(REALSXP, nx)); |
| xx = REAL(tmpx); |
| yy = REAL(tmpy); |
| for (i = 0; i < nx; i++) { |
| xx[i] = x0[i]; |
| yy[i] = y0[i]; |
| GConvert(&(xx[i]), &(yy[i]), DEVICE, USER, dd); |
| } |
| SET_VECTOR_ELT(ans, 0, tmpx); |
| SET_VECTOR_ELT(ans, 1, tmpy); |
| UNPROTECT(4); |
| } |
| |
| GMode(0, dd); |
| GRestorePars(dd); |
| return ans; |
| } |
| |
| /* clip(x1, x2, y1, y2) */ |
| SEXP C_clip(SEXP args) |
| { |
| SEXP ans = R_NilValue; |
| double x1, x2, y1, y2; |
| pGEDevDesc dd = GEcurrentDevice(); |
| |
| args = CDR(args); |
| x1 = asReal(CAR(args)); |
| if(!R_FINITE(x1)) error("invalid '%s' argument", "x1"); |
| args = CDR(args); |
| x2 = asReal(CAR(args)); |
| if(!R_FINITE(x2)) error("invalid '%s' argument", "x2"); |
| args = CDR(args); |
| y1 = asReal(CAR(args)); |
| if(!R_FINITE(y1)) error("invalid '%s' argument", "y1"); |
| args = CDR(args); |
| y2 = asReal(CAR(args)); |
| if(!R_FINITE(y2)) error("invalid '%s' argument", "y2"); |
| |
| GConvert(&x1, &y1, USER, DEVICE, dd); |
| GConvert(&x2, &y2, USER, DEVICE, dd); |
| GESetClip(x1, y1, x2, y2, dd); |
| /* avoid GClip resetting this */ |
| gpptr(dd)->oldxpd = gpptr(dd)->xpd; |
| return ans; |
| } |
| |
| /* convert[XY](x, from to) */ |
| SEXP C_convertX(SEXP args) |
| { |
| SEXP ans = R_NilValue, x; |
| int from, to, i, n; |
| double *rx; |
| pGEDevDesc gdd = GEcurrentDevice(); |
| |
| args = CDR(args); |
| x = CAR(args); |
| if (TYPEOF(x) != REALSXP) error(_("invalid '%s' argument"), "x"); |
| n = LENGTH(x); |
| from = asInteger(CADR(args)); |
| if (from == NA_INTEGER || from <= 0 || from > 17 ) |
| error(_("invalid '%s' argument"), "from"); |
| to = asInteger(CADDR(args)); |
| if (to == NA_INTEGER || to <= 0 || to > 17 ) |
| error(_("invalid '%s' argument"), "to"); |
| from--; to--; |
| |
| PROTECT(ans = duplicate(x)); |
| rx = REAL(ans); |
| for (i = 0; i < n; i++) rx[i] = GConvertX(rx[i], from, to, gdd); |
| UNPROTECT(1); |
| |
| return ans; |
| } |
| |
| SEXP C_convertY(SEXP args) |
| { |
| SEXP ans = R_NilValue, x; |
| int from, to, i, n; |
| double *rx; |
| pGEDevDesc gdd = GEcurrentDevice(); |
| |
| args = CDR(args); |
| x = CAR(args); |
| if (TYPEOF(x) != REALSXP) error(_("invalid '%s' argument"), "x"); |
| n = LENGTH(x); |
| from = asInteger(CADR(args)); |
| if (from == NA_INTEGER || from <= 0 || from > 17 ) |
| error(_("invalid '%s' argument"), "from"); |
| to = asInteger(CADDR(args)); |
| if (to == NA_INTEGER || to <= 0 || to > 17 ) |
| error(_("invalid '%s' argument"), "to"); |
| from--; to--; |
| |
| PROTECT(ans = duplicate(x)); |
| rx = REAL(ans); |
| for (i = 0; i < n; i++) rx[i] = GConvertY(rx[i], from, to, gdd); |
| UNPROTECT(1); |
| |
| return ans; |
| } |