| /* |
| * R : A Computer Language for Statistical Data Analysis |
| * Copyright (C) 2001-3 Paul Murrell |
| * 2003-2013 The R Core Team |
| * |
| * 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/ |
| */ |
| |
| |
| #define GRID_MAIN |
| #include "grid.h" |
| #include <math.h> |
| #include <float.h> |
| #include <string.h> |
| |
| /* NOTE: |
| * The extensive use of L or L_ prefixes dates back to when this |
| * package used to be called "lattice" |
| */ |
| |
| void getDeviceSize(pGEDevDesc dd, double *devWidthCM, double *devHeightCM) |
| { |
| double left, right, bottom, top; |
| dd->dev->size(&left, &right, &bottom, &top, dd->dev); |
| *devWidthCM = fabs(right - left) * dd->dev->ipr[0] * 2.54; |
| *devHeightCM = fabs(top - bottom) * dd->dev->ipr[1] * 2.54; |
| } |
| |
| static Rboolean deviceChanged(double devWidthCM, double devHeightCM, |
| SEXP currentvp) |
| { |
| Rboolean result = FALSE; |
| SEXP pvpDevWidthCM, pvpDevHeightCM; |
| PROTECT(pvpDevWidthCM = VECTOR_ELT(currentvp, PVP_DEVWIDTHCM)); |
| PROTECT(pvpDevHeightCM = VECTOR_ELT(currentvp, PVP_DEVHEIGHTCM)); |
| if (fabs(REAL(pvpDevWidthCM)[0] - devWidthCM) > 1e-6) { |
| result = TRUE; |
| REAL(pvpDevWidthCM)[0] = devWidthCM; |
| SET_VECTOR_ELT(currentvp, PVP_DEVWIDTHCM, pvpDevWidthCM); |
| } |
| if (fabs(REAL(pvpDevHeightCM)[0] - devHeightCM) > 1e-6) { |
| result = TRUE; |
| REAL(pvpDevHeightCM)[0] = devHeightCM; |
| SET_VECTOR_ELT(currentvp, PVP_DEVHEIGHTCM, pvpDevHeightCM); |
| } |
| UNPROTECT(2); |
| return result; |
| } |
| |
| /* Register grid with R's graphics engine |
| */ |
| SEXP L_initGrid(SEXP GridEvalEnv) |
| { |
| R_gridEvalEnv = GridEvalEnv; |
| GEregisterSystem(gridCallback, &gridRegisterIndex); |
| return R_NilValue; |
| } |
| |
| SEXP L_killGrid() |
| { |
| GEunregisterSystem(gridRegisterIndex); |
| return R_NilValue; |
| } |
| |
| /* Get the current device (the graphics engine creates one if nec.) |
| */ |
| pGEDevDesc getDevice() |
| { |
| return GEcurrentDevice(); |
| } |
| |
| /* If this is the first time that a grid operation has occurred for |
| * this device, do some initialisation. |
| */ |
| void dirtyGridDevice(pGEDevDesc dd) { |
| if (!LOGICAL(gridStateElement(dd, GSS_GRIDDEVICE))[0]) { |
| SEXP gsd, griddev; |
| /* Record the fact that this device has now received grid output |
| */ |
| gsd = (SEXP) dd->gesd[gridRegisterIndex]->systemSpecific; |
| PROTECT(griddev = allocVector(LGLSXP, 1)); |
| LOGICAL(griddev)[0] = TRUE; |
| SET_VECTOR_ELT(gsd, GSS_GRIDDEVICE, griddev); |
| UNPROTECT(1); |
| /* |
| * Start the first page on the device |
| * (But only if no other graphics system has not already done so) |
| */ |
| if (!GEdeviceDirty(dd)) { |
| R_GE_gcontext gc; |
| SEXP currentgp = gridStateElement(dd, GSS_GPAR); |
| gcontextFromgpar(currentgp, 0, &gc, dd); |
| GENewPage(&gc, dd); |
| GEdirtyDevice(dd); |
| } |
| /* |
| * Only initialise viewport once new page has started |
| * (required for postscript output [at least]) |
| */ |
| initVP(dd); |
| initDL(dd); |
| } |
| } |
| |
| SEXP L_gridDirty() |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| dirtyGridDevice(dd); |
| return R_NilValue; |
| } |
| |
| void getViewportContext(SEXP vp, LViewportContext *vpc) |
| { |
| fillViewportContextFromViewport(vp, vpc); |
| } |
| |
| SEXP L_currentViewport() |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| return gridStateElement(dd, GSS_VP); |
| } |
| |
| SEXP doSetViewport(SEXP vp, |
| /* |
| * Are we setting the top-level viewport? |
| */ |
| Rboolean topLevelVP, |
| /* |
| * Are we pushing a new viewport? |
| * (or just revisiting an already-pushed viewport?) |
| */ |
| Rboolean pushing, |
| pGEDevDesc dd) |
| { |
| int i, j; |
| double devWidthCM, devHeightCM; |
| double xx1, yy1, xx2, yy2; |
| SEXP currentClip, widthCM, heightCM; |
| /* Get the current device size |
| */ |
| getDeviceSize((dd), &devWidthCM, &devHeightCM); |
| if (!topLevelVP && pushing) { |
| SEXP parent = gridStateElement(dd, GSS_VP); |
| /* Set the viewport's parent |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| SET_VECTOR_ELT(vp, PVP_PARENT, parent); |
| /* |
| * Make this viewport a child of its parent |
| * This involves assigning a value in the parent's |
| * children slot (which is an environment), using |
| * the viewport's name as the symbol name. |
| * NOTE that we are deliberately using defineVar to |
| * assign the vp SEXP itself, NOT a copy. |
| */ |
| defineVar(installChar(STRING_ELT(VECTOR_ELT(vp, VP_NAME), 0)), |
| vp, |
| VECTOR_ELT(parent, PVP_CHILDREN)); |
| } |
| /* Calculate the transformation for the viewport. |
| * This will hopefully only involve updating the transformation |
| * from the previous viewport. |
| * However, if the device has changed size, we will need to |
| * recalculate the transformation from the top-level viewport |
| * all the way down. |
| * NEVER incremental for top-level viewport |
| */ |
| calcViewportTransform(vp, viewportParent(vp), |
| !topLevelVP && |
| !deviceChanged(devWidthCM, devHeightCM, |
| viewportParent(vp)), dd); |
| /* |
| * We must "turn off" clipping |
| * We set the clip region to be the entire device |
| * (actually, as for the top-level viewport, we set it |
| * to be slightly larger than the device to avoid |
| * "edge effects") |
| */ |
| if (viewportClip(vp) == NA_LOGICAL) { |
| xx1 = toDeviceX(-0.5*devWidthCM/2.54, GE_INCHES, dd); |
| yy1 = toDeviceY(-0.5*devHeightCM/2.54, GE_INCHES, dd); |
| xx2 = toDeviceX(1.5*devWidthCM/2.54, GE_INCHES, dd); |
| yy2 = toDeviceY(1.5*devHeightCM/2.54, GE_INCHES, dd); |
| GESetClip(xx1, yy1, xx2, yy2, dd); |
| } |
| /* If we are supposed to clip to this viewport ... |
| * NOTE that we will only clip if there is no rotation |
| */ |
| else if (viewportClip(vp)) { |
| double rotationAngle = REAL(viewportRotation(vp))[0]; |
| if (rotationAngle != 0 && |
| rotationAngle != 90 && |
| rotationAngle != 270 && |
| rotationAngle != 360) { |
| warning(_("cannot clip to rotated viewport")); |
| /* Still need to set clip region for this viewport. |
| So "inherit" parent clip region. |
| In other words, 'clip=TRUE' + 'rot=15' = 'clip=FALSE' |
| */ |
| SEXP parentClip; |
| PROTECT(parentClip = viewportClipRect(viewportParent(vp))); |
| xx1 = REAL(parentClip)[0]; |
| yy1 = REAL(parentClip)[1]; |
| xx2 = REAL(parentClip)[2]; |
| yy2 = REAL(parentClip)[3]; |
| UNPROTECT(1); |
| } else { |
| /* Calculate a clipping region and set it |
| */ |
| SEXP x1, y1, x2, y2; |
| LViewportContext vpc; |
| double vpWidthCM = REAL(viewportWidthCM(vp))[0]; |
| double vpHeightCM = REAL(viewportHeightCM(vp))[0]; |
| R_GE_gcontext gc; |
| LTransform transform; |
| for (i=0; i<3; i++) |
| for (j=0; j<3; j++) |
| transform[i][j] = |
| REAL(viewportTransform(vp))[i + 3*j]; |
| if (!topLevelVP) { |
| PROTECT(x1 = unit(0, L_NPC)); |
| PROTECT(y1 = unit(0, L_NPC)); |
| PROTECT(x2 = unit(1, L_NPC)); |
| PROTECT(y2 = unit(1, L_NPC)); |
| } else { |
| /* Special case for top-level viewport. |
| * Set clipping region outside device boundaries. |
| * This means that we have set the clipping region to |
| * something, but avoids problems if the nominal device |
| * limits are actually within its physical limits |
| * (e.g., PostScript) |
| */ |
| PROTECT(x1 = unit(-.5, L_NPC)); |
| PROTECT(y1 = unit(-.5, L_NPC)); |
| PROTECT(x2 = unit(1.5, L_NPC)); |
| PROTECT(y2 = unit(1.5, L_NPC)); |
| } |
| getViewportContext(vp, &vpc); |
| gcontextFromViewport(vp, &gc, dd); |
| transformLocn(x1, y1, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx1, &yy1); |
| transformLocn(x2, y2, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx2, &yy2); |
| UNPROTECT(4); /* unprotect x1, y1, x2, y2 */ |
| /* The graphics engine only takes device coordinates |
| */ |
| xx1 = toDeviceX(xx1, GE_INCHES, dd); |
| yy1 = toDeviceY(yy1, GE_INCHES, dd); |
| xx2 = toDeviceX(xx2, GE_INCHES, dd); |
| yy2 = toDeviceY(yy2, GE_INCHES, dd); |
| GESetClip(xx1, yy1, xx2, yy2, dd); |
| } |
| } else { |
| /* If we haven't set the clipping region for this viewport |
| * we need to save the clipping region from its parent |
| * so that when we pop this viewport we can restore that. |
| */ |
| /* NOTE that we are relying on grid.R setting clip=TRUE |
| * for the top-level viewport, else *BOOM*! |
| */ |
| SEXP parentClip; |
| PROTECT(parentClip = viewportClipRect(viewportParent(vp))); |
| xx1 = REAL(parentClip)[0]; |
| yy1 = REAL(parentClip)[1]; |
| xx2 = REAL(parentClip)[2]; |
| yy2 = REAL(parentClip)[3]; |
| UNPROTECT(1); |
| /* If we are revisiting a viewport that inherits a clip |
| * region from a parent viewport, we may need to reset |
| * the clip region (at worst, we generate a redundant clip) |
| */ |
| if (!pushing) { |
| GESetClip(xx1, yy1, xx2, yy2, dd); |
| } |
| } |
| PROTECT(currentClip = allocVector(REALSXP, 4)); |
| REAL(currentClip)[0] = xx1; |
| REAL(currentClip)[1] = yy1; |
| REAL(currentClip)[2] = xx2; |
| REAL(currentClip)[3] = yy2; |
| SET_VECTOR_ELT(vp, PVP_CLIPRECT, currentClip); |
| /* |
| * Save the current device size |
| */ |
| PROTECT(widthCM = allocVector(REALSXP, 1)); |
| REAL(widthCM)[0] = devWidthCM; |
| SET_VECTOR_ELT(vp, PVP_DEVWIDTHCM, widthCM); |
| PROTECT(heightCM = allocVector(REALSXP, 1)); |
| REAL(heightCM)[0] = devHeightCM; |
| SET_VECTOR_ELT(vp, PVP_DEVHEIGHTCM, heightCM); |
| UNPROTECT(3); |
| return vp; |
| } |
| |
| SEXP L_setviewport(SEXP invp, SEXP hasParent) |
| { |
| SEXP vp; |
| SEXP pushedvp, fcall; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| /* |
| * Duplicate the viewport passed in because we are going |
| * to modify it to hell and gone. |
| */ |
| PROTECT(vp = duplicate(invp)); |
| /* |
| * Call R function pushedvp() |
| */ |
| PROTECT(fcall = lang2(install("pushedvp"), |
| vp)); |
| PROTECT(pushedvp = eval(fcall, R_gridEvalEnv)); |
| pushedvp = doSetViewport(pushedvp, !LOGICAL(hasParent)[0], TRUE, dd); |
| /* Set the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| setGridStateElement(dd, GSS_VP, pushedvp); |
| UNPROTECT(3); |
| return R_NilValue; |
| } |
| |
| /* |
| * Find a viewport in the current viewport tree by name |
| * |
| * Have to do this in C code so that we get THE SEXP in |
| * the tree, NOT a copy of it. |
| */ |
| |
| /* |
| * Some helper functions to call R code because I have no idea |
| * how to do this in C code |
| */ |
| static Rboolean noChildren(SEXP children) |
| { |
| SEXP result, fcall; |
| PROTECT(fcall = lang2(install("no.children"), |
| children)); |
| PROTECT(result = eval(fcall, R_gridEvalEnv)); |
| UNPROTECT(2); |
| return LOGICAL(result)[0]; |
| } |
| |
| static Rboolean childExists(SEXP name, SEXP children) |
| { |
| SEXP result, fcall; |
| PROTECT(fcall = lang3(install("child.exists"), |
| name, children)); |
| PROTECT(result = eval(fcall, R_gridEvalEnv)); |
| UNPROTECT(2); |
| return LOGICAL(result)[0]; |
| } |
| |
| static SEXP childList(SEXP children) |
| { |
| SEXP result, fcall; |
| PROTECT(fcall = lang2(install("child.list"), |
| children)); |
| PROTECT(result = eval(fcall, R_gridEvalEnv)); |
| UNPROTECT(2); |
| return result; |
| } |
| |
| /* |
| find.in.children <- function(name, children) { |
| cpvps <- ls(env=children) |
| ncpvp <- length(cpvps) |
| count <- 0 |
| found <- FALSE |
| while (count < ncpvp && !found) { |
| result <- find.viewport(name, get(cpvps[count+1], env=children)) |
| found <- result$found |
| count <- count + 1 |
| } |
| if (!found) |
| result <- list(found=FALSE, pvp=NULL) |
| return(result) |
| } |
| */ |
| static SEXP findViewport(SEXP name, SEXP strict, SEXP vp, int depth); |
| static SEXP findInChildren(SEXP name, SEXP strict, SEXP children, int depth) |
| { |
| SEXP childnames = childList(children); |
| int n = LENGTH(childnames); |
| int count = 0; |
| Rboolean found = FALSE; |
| SEXP result = R_NilValue; |
| PROTECT(childnames); |
| PROTECT(result); |
| while (count < n && !found) { |
| result = findViewport(name, strict, |
| PROTECT(findVar(installChar(STRING_ELT(childnames, count)), |
| children)), |
| depth); |
| found = INTEGER(VECTOR_ELT(result, 0))[0] > 0; |
| UNPROTECT(1); |
| count = count + 1; |
| } |
| if (!found) { |
| SEXP temp, zeroDepth; |
| PROTECT(temp = allocVector(VECSXP, 2)); |
| PROTECT(zeroDepth = allocVector(INTSXP, 1)); |
| INTEGER(zeroDepth)[0] = 0; |
| SET_VECTOR_ELT(temp, 0, zeroDepth); |
| SET_VECTOR_ELT(temp, 1, R_NilValue); |
| UNPROTECT(2); |
| result = temp; |
| } |
| UNPROTECT(2); |
| return result; |
| } |
| |
| /* |
| find.viewport <- function(name, pvp) { |
| found <- FALSE |
| if (length(ls(env=pvp$children)) == 0) |
| return(list(found=FALSE, pvp=NULL)) |
| else |
| if (exists(name, env=pvp$children, inherits=FALSE)) |
| return(list(found=TRUE, |
| pvp=get(name, env=pvp$children, inherits=FALSE))) |
| else |
| find.in.children(name, pvp$children) |
| } |
| */ |
| static SEXP findViewport(SEXP name, SEXP strict, SEXP vp, int depth) |
| { |
| SEXP result, zeroDepth, curDepth; |
| PROTECT(result = allocVector(VECSXP, 2)); |
| PROTECT(zeroDepth = allocVector(INTSXP, 1)); |
| INTEGER(zeroDepth)[0] = 0; |
| PROTECT(curDepth = allocVector(INTSXP, 1)); |
| INTEGER(curDepth)[0] = depth; |
| /* |
| * If there are no children, we fail |
| */ |
| if (noChildren(viewportChildren(vp))) { |
| SET_VECTOR_ELT(result, 0, zeroDepth); |
| SET_VECTOR_ELT(result, 1, R_NilValue); |
| } else if (childExists(name, viewportChildren(vp))) { |
| SET_VECTOR_ELT(result, 0, curDepth); |
| SET_VECTOR_ELT(result, 1, |
| /* |
| * Does this do inherits=FALSE? |
| */ |
| findVar(installChar(STRING_ELT(name, 0)), |
| viewportChildren(vp))); |
| } else { |
| /* |
| * If this is a strict match, fail |
| * Otherwise recurse into children |
| */ |
| if (LOGICAL(strict)[0]) { |
| SET_VECTOR_ELT(result, 0, zeroDepth); |
| SET_VECTOR_ELT(result, 1, R_NilValue); |
| } else { |
| result = findInChildren(name, strict, viewportChildren(vp), |
| depth + 1); |
| } |
| } |
| UNPROTECT(3); |
| return result; |
| } |
| |
| SEXP L_downviewport(SEXP name, SEXP strict) |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| /* Get the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| SEXP gvp = gridStateElement(dd, GSS_VP); |
| /* |
| * Try to find the named viewport |
| */ |
| SEXP found, vp; |
| int depth = 1; |
| PROTECT(found = findViewport(name, strict, gvp, depth)); |
| if (INTEGER(VECTOR_ELT(found, 0))[0]) { |
| vp = doSetViewport(VECTOR_ELT(found, 1), FALSE, FALSE, dd); |
| /* Set the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| setGridStateElement(dd, GSS_VP, vp); |
| UNPROTECT(1); |
| } else { |
| /* Important to have an error here, rather than back in |
| * R code AFTER this point. Otherwise, an unsuccessful |
| * downViewport() will be recorded on the engine DL! |
| */ |
| char msg[1024]; |
| snprintf(msg, 1024, "Viewport '%s' was not found", |
| CHAR(STRING_ELT(name, 0))); |
| UNPROTECT(1); |
| error(_(msg)); |
| } |
| return VECTOR_ELT(found, 0); |
| } |
| |
| /* |
| * Find a viewport PATH in the current viewport tree by name |
| * |
| * Similar to L_downviewport |
| */ |
| |
| static Rboolean pathMatch(SEXP path, SEXP pathsofar, SEXP strict) |
| { |
| SEXP result, fcall; |
| PROTECT(fcall = lang4(install("pathMatch"), |
| path, pathsofar, strict)); |
| PROTECT(result = eval(fcall, R_gridEvalEnv)); |
| UNPROTECT(2); |
| return LOGICAL(result)[0]; |
| } |
| |
| static SEXP growPath(SEXP pathsofar, SEXP name) |
| { |
| SEXP result, fcall; |
| if (isNull(pathsofar)) |
| result = name; |
| else { |
| PROTECT(fcall = lang3(install("growPath"), |
| pathsofar, name)); |
| PROTECT(result = eval(fcall, R_gridEvalEnv)); |
| UNPROTECT(2); |
| } |
| return result; |
| } |
| |
| static SEXP findvppath(SEXP path, SEXP name, SEXP strict, |
| SEXP pathsofar, SEXP vp, int depth); |
| static SEXP findvppathInChildren(SEXP path, SEXP name, |
| SEXP strict, SEXP pathsofar, |
| SEXP children, int depth) |
| { |
| SEXP childnames = childList(children); |
| int n = LENGTH(childnames); |
| int count = 0; |
| Rboolean found = FALSE; |
| SEXP result = R_NilValue; |
| PROTECT(childnames); |
| PROTECT(result); |
| while (count < n && !found) { |
| SEXP vp, newpathsofar; |
| PROTECT(vp = findVar(installChar(STRING_ELT(childnames, count)), |
| children)); |
| PROTECT(newpathsofar = growPath(pathsofar, |
| VECTOR_ELT(vp, VP_NAME))); |
| result = findvppath(path, name, strict, newpathsofar, vp, depth); |
| found = INTEGER(VECTOR_ELT(result, 0))[0] > 0; |
| count = count + 1; |
| UNPROTECT(2); |
| } |
| if (!found) { |
| SEXP temp, zeroDepth; |
| PROTECT(temp = allocVector(VECSXP, 2)); |
| PROTECT(zeroDepth = allocVector(INTSXP, 1)); |
| INTEGER(zeroDepth)[0] = 0; |
| SET_VECTOR_ELT(temp, 0, zeroDepth); |
| SET_VECTOR_ELT(temp, 1, R_NilValue); |
| UNPROTECT(2); |
| result = temp; |
| } |
| UNPROTECT(2); |
| return result; |
| } |
| |
| static SEXP findvppath(SEXP path, SEXP name, SEXP strict, |
| SEXP pathsofar, SEXP vp, int depth) |
| { |
| SEXP result, zeroDepth, curDepth; |
| PROTECT(result = allocVector(VECSXP, 2)); |
| PROTECT(zeroDepth = allocVector(INTSXP, 1)); |
| INTEGER(zeroDepth)[0] = 0; |
| PROTECT(curDepth = allocVector(INTSXP, 1)); |
| INTEGER(curDepth)[0] = depth; |
| /* |
| * If there are no children, we fail |
| */ |
| if (noChildren(viewportChildren(vp))) { |
| SET_VECTOR_ELT(result, 0, zeroDepth); |
| SET_VECTOR_ELT(result, 1, R_NilValue); |
| |
| } |
| /* |
| * Check for the viewport name AND whether the rest |
| * of the path matches (possibly strictly) |
| */ |
| else if (childExists(name, viewportChildren(vp)) && |
| pathMatch(path, pathsofar, strict)) { |
| SET_VECTOR_ELT(result, 0, curDepth); |
| SET_VECTOR_ELT(result, 1, |
| /* |
| * Does this do inherits=FALSE? |
| */ |
| findVar(installChar(STRING_ELT(name, 0)), |
| viewportChildren(vp))); |
| } else { |
| result = findvppathInChildren(path, name, strict, pathsofar, |
| viewportChildren(vp), depth + 1); |
| } |
| UNPROTECT(3); |
| return result; |
| } |
| |
| SEXP L_downvppath(SEXP path, SEXP name, SEXP strict) |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| /* Get the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| SEXP gvp = gridStateElement(dd, GSS_VP); |
| /* |
| * Try to find the named viewport |
| */ |
| SEXP found, vp; |
| int depth = 1; |
| PROTECT(found = findvppath(path, name, strict, R_NilValue, gvp, depth)); |
| if (INTEGER(VECTOR_ELT(found, 0))[0]) { |
| vp = doSetViewport(VECTOR_ELT(found, 1), FALSE, FALSE, dd); |
| /* Set the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| setGridStateElement(dd, GSS_VP, vp); |
| UNPROTECT(1); |
| } else { |
| /* Important to have an error here, rather than back in |
| * R code AFTER this point. Otherwise, an unsuccessful |
| * downViewport() will be recorded on the engine DL! |
| */ |
| char msg[1024]; |
| snprintf(msg, 1024, "Viewport '%s' was not found", |
| CHAR(STRING_ELT(name, 0))); |
| UNPROTECT(1); |
| error(_(msg)); |
| } |
| return VECTOR_ELT(found, 0); |
| } |
| |
| /* This is similar to L_setviewport, except that it will NOT |
| * recalculate the viewport transform if the device has not changed size |
| */ |
| SEXP L_unsetviewport(SEXP n) |
| { |
| int i; |
| double xx1, yy1, xx2, yy2; |
| double devWidthCM, devHeightCM; |
| SEXP parentClip; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| /* Get the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| SEXP gvp = gridStateElement(dd, GSS_VP); |
| /* NOTE that the R code has already checked that .grid.viewport$parent |
| * is non-NULL |
| * |
| * BUT this may not be called from R code !! |
| * (e.g., when the graphics engine display list is replayed; |
| * problems can occur when grid output is mixed with base output; |
| * for example, plot.new() is called between a viewport push and pop) |
| */ |
| SEXP newvp = VECTOR_ELT(gvp, PVP_PARENT); |
| if (isNull(newvp)) |
| error(_("cannot pop the top-level viewport ('grid' and 'graphics' output mixed?)")); |
| for (i = 1; i < INTEGER(n)[0]; i++) { |
| gvp = newvp; |
| newvp = VECTOR_ELT(gvp, PVP_PARENT); |
| if (isNull(newvp)) |
| error(_("cannot pop the top-level viewport ('grid' and 'graphics' output mixed?)")); |
| } |
| /* |
| * Remove the child (gvp) from the parent's (newvp) "list" of |
| * children |
| */ |
| /* |
| * This has to be done via a call to R-level ... |
| * remove(gvp$name, envir=newvp$children, inherits=FALSE) |
| * ... because RemoveVariable in envir.c is not exported (why not?) |
| * |
| * I tried to model this on the example in the section |
| * "System and foreign language interfaces ... Evaluating R expressions" |
| * in the "Writing R Extensions" manual, but the compiler didn't |
| * like CAR(t) as an lvalue. |
| */ |
| PROTECT(gvp); PROTECT(newvp); |
| { |
| SEXP fcall, false, t; |
| PROTECT(false = allocVector(LGLSXP, 1)); |
| LOGICAL(false)[0] = FALSE; |
| PROTECT(fcall = lang4(install("remove"), |
| VECTOR_ELT(gvp, VP_NAME), |
| VECTOR_ELT(newvp, PVP_CHILDREN), |
| false)); |
| t = fcall; |
| t = CDR(CDR(t)); |
| SET_TAG(t, install("envir")); |
| t = CDR(t); |
| SET_TAG(t, install("inherits")); |
| eval(fcall, R_gridEvalEnv); |
| UNPROTECT(2); /* false, fcall */ |
| } |
| /* Get the current device size |
| */ |
| getDeviceSize(dd, &devWidthCM, &devHeightCM); |
| if (deviceChanged(devWidthCM, devHeightCM, newvp)) |
| calcViewportTransform(newvp, viewportParent(newvp), 1, dd); |
| /* |
| * Enforce the current viewport settings |
| */ |
| setGridStateElement(dd, GSS_GPAR, viewportgpar(newvp)); |
| /* Set the clipping region to the parent's cur.clip |
| */ |
| parentClip = viewportClipRect(newvp); |
| xx1 = REAL(parentClip)[0]; |
| yy1 = REAL(parentClip)[1]; |
| xx2 = REAL(parentClip)[2]; |
| yy2 = REAL(parentClip)[3]; |
| GESetClip(xx1, yy1, xx2, yy2, dd); |
| /* Set the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| setGridStateElement(dd, GSS_VP, newvp); |
| /* |
| * Remove the parent from the child |
| * This is not strictly necessary, but it is conceptually |
| * more complete and makes it more likely that we will |
| * detect incorrect code elsewhere (because it is likely to |
| * trigger a segfault if other code is incorrect) |
| * |
| * NOTE: Do NOT do this any earlier or you will |
| * remove the PROTECTive benefit of newvp pointing |
| * to part of the (global) grid state |
| */ |
| SET_VECTOR_ELT(gvp, PVP_PARENT, R_NilValue); |
| UNPROTECT(2); /* gvp, newvp */ |
| return R_NilValue; |
| } |
| |
| /* This is similar to L_unsetviewport, except that it will NOT |
| * modify parent-child relations |
| */ |
| SEXP L_upviewport(SEXP n) |
| { |
| int i; |
| double xx1, yy1, xx2, yy2; |
| double devWidthCM, devHeightCM; |
| SEXP parentClip; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| /* Get the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| SEXP gvp = gridStateElement(dd, GSS_VP); |
| SEXP newvp = VECTOR_ELT(gvp, PVP_PARENT); |
| if (isNull(newvp)) |
| error(_("cannot pop the top-level viewport ('grid' and 'graphics' output mixed?)")); |
| for (i = 1; i < INTEGER(n)[0]; i++) { |
| gvp = newvp; |
| newvp = VECTOR_ELT(gvp, PVP_PARENT); |
| if (isNull(newvp)) |
| error(_("cannot pop the top-level viewport ('grid' and 'graphics' output mixed?)")); |
| } |
| /* Get the current device size |
| */ |
| getDeviceSize(dd, &devWidthCM, &devHeightCM); |
| if (deviceChanged(devWidthCM, devHeightCM, newvp)) |
| calcViewportTransform(newvp, viewportParent(newvp), 1, dd); |
| /* |
| * Enforce the current viewport settings |
| */ |
| setGridStateElement(dd, GSS_GPAR, viewportgpar(newvp)); |
| /* Set the clipping region to the parent's cur.clip |
| */ |
| parentClip = viewportClipRect(newvp); |
| xx1 = REAL(parentClip)[0]; |
| yy1 = REAL(parentClip)[1]; |
| xx2 = REAL(parentClip)[2]; |
| yy2 = REAL(parentClip)[3]; |
| GESetClip(xx1, yy1, xx2, yy2, dd); |
| #if 0 |
| /* This is a VERY short term fix to avoid mucking |
| * with the core graphics during feature freeze |
| * It should be removed post R 1.4 release |
| */ |
| dd->dev->clipLeft = fmin2(xx1, xx2); |
| dd->dev->clipRight = fmax2(xx1, xx2); |
| dd->dev->clipTop = fmax2(yy1, yy2); |
| dd->dev->clipBottom = fmin2(yy1, yy2); |
| #endif |
| /* Set the value of the current viewport for the current device |
| * Need to do this in here so that redrawing via R BASE display |
| * list works |
| */ |
| setGridStateElement(dd, GSS_VP, newvp); |
| return R_NilValue; |
| } |
| |
| SEXP L_getDisplayList() |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| return gridStateElement(dd, GSS_DL); |
| } |
| |
| SEXP L_setDisplayList(SEXP dl) |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| setGridStateElement(dd, GSS_DL, dl); |
| return R_NilValue; |
| } |
| |
| /* |
| * Get the element at index on the DL |
| */ |
| SEXP L_getDLelt(SEXP index) |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| SEXP dl, result; |
| PROTECT(dl = gridStateElement(dd, GSS_DL)); |
| result = VECTOR_ELT(dl, INTEGER(index)[0]); |
| UNPROTECT(1); |
| return result; |
| } |
| |
| /* Add an element to the display list at the current location |
| * Location is maintained in R code |
| */ |
| SEXP L_setDLelt(SEXP value) |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| SEXP dl; |
| PROTECT(dl = gridStateElement(dd, GSS_DL)); |
| SET_VECTOR_ELT(dl, INTEGER(gridStateElement(dd, GSS_DLINDEX))[0], value); |
| UNPROTECT(1); |
| return R_NilValue; |
| } |
| |
| SEXP L_getDLindex() |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| return gridStateElement(dd, GSS_DLINDEX); |
| } |
| |
| SEXP L_setDLindex(SEXP index) |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| setGridStateElement(dd, GSS_DLINDEX, index); |
| return R_NilValue; |
| } |
| |
| SEXP L_getDLon() |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| return gridStateElement(dd, GSS_DLON); |
| } |
| |
| SEXP L_setDLon(SEXP value) |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| SEXP prev; |
| prev = gridStateElement(dd, GSS_DLON); |
| setGridStateElement(dd, GSS_DLON, value); |
| return prev; |
| } |
| |
| SEXP L_getEngineDLon() |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| return gridStateElement(dd, GSS_ENGINEDLON); |
| } |
| |
| SEXP L_setEngineDLon(SEXP value) |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| setGridStateElement(dd, GSS_ENGINEDLON, value); |
| return R_NilValue; |
| } |
| |
| SEXP L_getCurrentGrob() |
| { |
| pGEDevDesc dd = getDevice(); |
| return gridStateElement(dd, GSS_CURRGROB); |
| } |
| |
| SEXP L_setCurrentGrob(SEXP value) |
| { |
| pGEDevDesc dd = getDevice(); |
| setGridStateElement(dd, GSS_CURRGROB, value); |
| return R_NilValue; |
| } |
| |
| SEXP L_getEngineRecording() |
| { |
| pGEDevDesc dd = getDevice(); |
| return gridStateElement(dd, GSS_ENGINERECORDING); |
| } |
| |
| SEXP L_setEngineRecording(SEXP value) |
| { |
| pGEDevDesc dd = getDevice(); |
| setGridStateElement(dd, GSS_ENGINERECORDING, value); |
| return R_NilValue; |
| } |
| |
| SEXP L_currentGPar() |
| { |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| return gridStateElement(dd, GSS_GPAR); |
| } |
| |
| SEXP L_newpagerecording() |
| { |
| pGEDevDesc dd = getDevice(); |
| if (dd->ask) { |
| NewFrameConfirm(dd->dev); |
| /* |
| * User may have killed device during pause for prompt |
| */ |
| if (NoDevices()) |
| error(_("attempt to plot on null device")); |
| else |
| /* |
| * Should throw an error if dd != GECurrentDevice ? |
| */ |
| dd = GEcurrentDevice(); |
| } |
| GEinitDisplayList(dd); |
| return R_NilValue; |
| } |
| |
| SEXP L_newpage() |
| { |
| pGEDevDesc dd = getDevice(); |
| R_GE_gcontext gc; |
| /* |
| * Has the device been drawn on yet? |
| */ |
| Rboolean deviceDirty = GEdeviceDirty(dd); |
| /* |
| * Has the device been drawn on BY GRID yet? |
| */ |
| Rboolean deviceGridDirty = LOGICAL(gridStateElement(dd, |
| GSS_GRIDDEVICE))[0]; |
| /* |
| * Initialise grid on device |
| * If no drawing on device yet, does a new page |
| */ |
| if (!deviceGridDirty) { |
| dirtyGridDevice(dd); |
| } |
| /* |
| * If device has previously been drawn on (by grid or other system) |
| * do a new page |
| */ |
| if (deviceGridDirty || deviceDirty) { |
| SEXP currentgp = gridStateElement(dd, GSS_GPAR); |
| gcontextFromgpar(currentgp, 0, &gc, dd); |
| GENewPage(&gc, dd); |
| } |
| return R_NilValue; |
| } |
| |
| SEXP L_initGPar() |
| { |
| pGEDevDesc dd = getDevice(); |
| initGPar(dd); |
| return R_NilValue; |
| } |
| |
| SEXP L_initViewportStack() |
| { |
| pGEDevDesc dd = getDevice(); |
| initVP(dd); |
| return R_NilValue; |
| } |
| |
| SEXP L_initDisplayList() |
| { |
| pGEDevDesc dd = getDevice(); |
| initDL(dd); |
| return R_NilValue; |
| } |
| |
| void getViewportTransform(SEXP currentvp, |
| pGEDevDesc dd, |
| double *vpWidthCM, double *vpHeightCM, |
| LTransform transform, double *rotationAngle) |
| { |
| int i, j; |
| double devWidthCM, devHeightCM; |
| getDeviceSize((dd), &devWidthCM, &devHeightCM) ; |
| if (deviceChanged(devWidthCM, devHeightCM, currentvp)) { |
| /* IF the device has changed, recalculate the viewport transform |
| */ |
| calcViewportTransform(currentvp, viewportParent(currentvp), 1, dd); |
| } |
| for (i=0; i<3; i++) |
| for (j=0; j<3; j++) |
| transform[i][j] = |
| REAL(viewportTransform(currentvp))[i + 3*j]; |
| *rotationAngle = REAL(viewportRotation(currentvp))[0]; |
| *vpWidthCM = REAL(viewportWidthCM(currentvp))[0]; |
| *vpHeightCM = REAL(viewportHeightCM(currentvp))[0]; |
| } |
| |
| |
| /*************************** |
| * CONVERSION FUNCTIONS |
| *************************** |
| */ |
| |
| /* |
| * WITHIN THE CURRENT VIEWPORT ... |
| * |
| * Given a unit object and whether it is a location/dimension, |
| * convert to location/dimension in unit B |
| * |
| * NOTE: When this is used to convert a mouse click on a device to |
| * a location/dimension, the conversion of the mouse click to |
| * a unit within the current viewport has to be done elsewhere. |
| * e.g., in interactive.R, this is done by applying the inverse |
| * of the current viewport transformation to get a location in |
| * inches within the current viewport. |
| * |
| * This should ideally create a unit object to ensure that the |
| * values it returns are treated as values in the correct |
| * coordinate system. For now, this is MUCH easier to do in |
| * R code, so it is the responsibility of the R code calling this |
| * to create the unit object correctly/honestly. |
| * |
| * NOTE also that the unitto supplied should be a "valid" integer |
| * (the best way to get that is to use the valid.units() |
| * function in unit.R) |
| * |
| * what = 0 means x, 1 means y, 2 means width, 3 means height |
| */ |
| SEXP L_convert(SEXP x, SEXP whatfrom, |
| SEXP whatto, SEXP unitto) { |
| int i, nx; |
| SEXP answer; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| int TOunit, FROMaxis, TOaxis; |
| Rboolean relConvert; |
| /* |
| * Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| /* |
| * We do not need the current transformation, but |
| * we need the side effects of calculating it in |
| * case the device has been resized (or only just created) |
| */ |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| nx = unitLength(x); |
| PROTECT(answer = allocVector(REALSXP, nx)); |
| for (i=0; i<nx; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| TOunit = INTEGER(unitto)[i % LENGTH(unitto)]; |
| FROMaxis = INTEGER(whatfrom)[0]; |
| TOaxis = INTEGER(whatto)[0]; |
| /* |
| * Special case: FROM unit is just a plain, relative unit AND |
| * TO unit is relative AND |
| * NOT converting from 'x' to 'y' (or vice versa) ... |
| * |
| * ... AND relevant widthCM or heightCM is zero |
| * |
| * In these cases do NOT transform thru INCHES |
| * (to avoid divide-by-zero, but still do something useful) |
| */ |
| relConvert = (!isUnitArithmetic(x) && !isUnitList(x) && |
| (unitUnit(x, i) == L_NATIVE || unitUnit(x, i) == L_NPC) && |
| (TOunit == L_NATIVE || TOunit == L_NPC) && |
| ((FROMaxis == TOaxis) || |
| (FROMaxis == 0 && TOaxis == 2) || |
| (FROMaxis == 2 && TOaxis == 0) || |
| (FROMaxis == 1 && TOaxis == 3) || |
| (FROMaxis == 3 && TOaxis == 1))); |
| /* |
| * First, convert the unit object passed in to a value in INCHES |
| * (within the current viewport) |
| */ |
| switch (FROMaxis) { |
| case 0: |
| if (relConvert && vpWidthCM < 1e-6) { |
| REAL(answer)[i] = |
| transformXYtoNPC(unitValue(x, i), unitUnit(x, i), |
| vpc.xscalemin, vpc.xscalemax); |
| } else { |
| relConvert = FALSE; |
| REAL(answer)[i] = |
| transformXtoINCHES(x, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| break; |
| case 1: |
| if (relConvert && vpHeightCM < 1e-6) { |
| REAL(answer)[i] = |
| transformXYtoNPC(unitValue(x, i), unitUnit(x, i), |
| vpc.yscalemin, vpc.yscalemax); |
| } else { |
| relConvert = FALSE; |
| REAL(answer)[i] = |
| transformYtoINCHES(x, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| break; |
| case 2: |
| if (relConvert && vpWidthCM < 1e-6) { |
| REAL(answer)[i] = |
| transformWHtoNPC(unitValue(x, i), unitUnit(x, i), |
| vpc.xscalemin, vpc.xscalemax); |
| } else { |
| relConvert = FALSE; |
| REAL(answer)[i] = |
| transformWidthtoINCHES(x, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| break; |
| case 3: |
| if (relConvert && vpHeightCM < 1e-6) { |
| REAL(answer)[i] = |
| transformWHtoNPC(unitValue(x, i), unitUnit(x, i), |
| vpc.yscalemin, vpc.yscalemax); |
| } else { |
| relConvert = FALSE; |
| REAL(answer)[i] = |
| transformHeighttoINCHES(x, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| break; |
| } |
| /* |
| * Now, convert the values in INCHES to a value in the specified |
| * coordinate system |
| * (within the current viewport) |
| * |
| * BUT do NOT do this step for the special "relConvert" case |
| */ |
| switch (TOaxis) { |
| case 0: |
| if (relConvert) { |
| REAL(answer)[i] = transformXYfromNPC(REAL(answer)[i], TOunit, |
| vpc.xscalemin, |
| vpc.xscalemax); |
| } else { |
| REAL(answer)[i] = |
| transformXYFromINCHES(REAL(answer)[i], TOunit, |
| vpc.xscalemin, |
| vpc.xscalemax, |
| &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| break; |
| case 1: |
| if (relConvert) { |
| REAL(answer)[i] = transformXYfromNPC(REAL(answer)[i], TOunit, |
| vpc.yscalemin, |
| vpc.yscalemax); |
| } else { |
| REAL(answer)[i] = |
| transformXYFromINCHES(REAL(answer)[i], TOunit, |
| vpc.yscalemin, |
| vpc.yscalemax, |
| &gc, |
| vpHeightCM, vpWidthCM, |
| dd); |
| } |
| break; |
| case 2: |
| if (relConvert) { |
| REAL(answer)[i] = transformWHfromNPC(REAL(answer)[i], TOunit, |
| vpc.xscalemin, |
| vpc.xscalemax); |
| } else { |
| REAL(answer)[i] = |
| transformWidthHeightFromINCHES(REAL(answer)[i], TOunit, |
| vpc.xscalemin, |
| vpc.xscalemax, |
| &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| break; |
| case 3: |
| if (relConvert) { |
| REAL(answer)[i] = transformWHfromNPC(REAL(answer)[i], TOunit, |
| vpc.yscalemin, |
| vpc.yscalemax); |
| } else { |
| REAL(answer)[i] = |
| transformWidthHeightFromINCHES(REAL(answer)[i], TOunit, |
| vpc.yscalemin, |
| vpc.yscalemax, |
| &gc, |
| vpHeightCM, vpWidthCM, |
| dd); |
| break; |
| } |
| } |
| } |
| UNPROTECT(1); |
| return answer; |
| } |
| |
| /* |
| * Convert locations or dimensions to device inches |
| */ |
| SEXP L_devLoc(SEXP x, SEXP y) { |
| double xx, yy; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| LViewportContext vpc; |
| R_GE_gcontext gc; |
| LTransform transform; |
| SEXP devx, devy, result; |
| SEXP currentvp, currentgp; |
| int i, maxn, ny; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| gcontextFromgpar(currentgp, 0, &gc, dd); |
| /* Convert the x and y values to device inches locations */ |
| maxn = unitLength(x); |
| ny = unitLength(y); |
| if (ny > maxn) |
| maxn = ny; |
| PROTECT(devx = allocVector(REALSXP, maxn)); |
| PROTECT(devy = allocVector(REALSXP, maxn)); |
| PROTECT(result = allocVector(VECSXP, 2)); |
| for (i=0; i<maxn; i++) { |
| transformLocn(x, y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx, &yy); |
| REAL(devx)[i] = xx; |
| REAL(devy)[i] = yy; |
| } |
| SET_VECTOR_ELT(result, 0, devx); |
| SET_VECTOR_ELT(result, 1, devy); |
| UNPROTECT(3); |
| return result; |
| } |
| |
| SEXP L_devDim(SEXP x, SEXP y) { |
| double xx, yy; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| LViewportContext vpc; |
| R_GE_gcontext gc; |
| LTransform transform; |
| SEXP devx, devy, result; |
| SEXP currentvp, currentgp; |
| int i, maxn, ny; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| gcontextFromgpar(currentgp, 0, &gc, dd); |
| /* Convert the x and y values to device inches locations */ |
| maxn = unitLength(x); |
| ny = unitLength(y); |
| if (ny > maxn) |
| maxn = ny; |
| PROTECT(devx = allocVector(REALSXP, maxn)); |
| PROTECT(devy = allocVector(REALSXP, maxn)); |
| PROTECT(result = allocVector(VECSXP, 2)); |
| for (i=0; i<maxn; i++) { |
| transformDimn(x, y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, rotationAngle, |
| &xx, &yy); |
| REAL(devx)[i] = xx; |
| REAL(devy)[i] = yy; |
| } |
| SET_VECTOR_ELT(result, 0, devx); |
| SET_VECTOR_ELT(result, 1, devy); |
| UNPROTECT(3); |
| return result; |
| } |
| |
| /* |
| * Given a layout.pos.row and a layout.pos.col, calculate |
| * the region allocated by the layout of the current viewport |
| * |
| * Not a conversion as such, but similarly vulnerable to device resizing |
| */ |
| SEXP L_layoutRegion(SEXP layoutPosRow, SEXP layoutPosCol) { |
| LViewportLocation vpl; |
| SEXP answer; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| LTransform transform; |
| SEXP currentvp; |
| /* |
| * Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| //currentgp = gridStateElement(dd, GSS_GPAR); |
| /* |
| * We do not need the current transformation, but |
| * we need the side effects of calculating it in |
| * case the device has been resized (or only just created) |
| */ |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| /* |
| * Only proceed if there is a layout currently defined |
| */ |
| if (isNull(viewportLayout(currentvp))) |
| error(_("there is no layout defined")); |
| /* |
| * The result is a numeric containing left, bottom, width, and height |
| */ |
| PROTECT(answer = allocVector(REALSXP, 4)); |
| /* |
| * NOTE: We are assuming here that calcViewportLocationFromLayout |
| * returns the allocated region with a ("left", "bottom") |
| * justification. This is CURRENTLY true, but ... |
| */ |
| calcViewportLocationFromLayout(layoutPosRow, |
| layoutPosCol, |
| currentvp, |
| &vpl); |
| /* |
| * I am not returning the units created in C code |
| * because they do not have the units attribute set |
| * so they do not behave nicely back in R code. |
| * Instead, I take the values and my knowledge that they |
| * are NPC units and construct real unit objects back in |
| * R code. |
| */ |
| REAL(answer)[0] = unitValue(vpl.x, 0); |
| REAL(answer)[1] = unitValue(vpl.y, 0); |
| REAL(answer)[2] = unitValue(vpl.width, 0); |
| REAL(answer)[3] = unitValue(vpl.height, 0); |
| UNPROTECT(1); |
| return answer; |
| } |
| |
| /*************************** |
| * EDGE DETECTION |
| *************************** |
| */ |
| |
| /* |
| * Calculate the point on the edge of a rectangle at angle theta |
| * 0 = East, 180 = West, etc ... |
| * Assumes that x- and y-values are in INCHES |
| * Assumes that theta is within [0, 360) |
| */ |
| static void rectEdge(double xmin, double ymin, double xmax, double ymax, |
| double theta, |
| double *edgex, double *edgey) |
| { |
| double xm = (xmin + xmax)/2; |
| double ym = (ymin + ymax)/2; |
| double dx = (xmax - xmin)/2; |
| double dy = (ymax - ymin)/2; |
| /* |
| * FIXME: Special case 0 width or 0 height |
| */ |
| /* |
| * Special case angles |
| */ |
| if (theta == 0) { |
| *edgex = xmax; |
| *edgey = ym; |
| } else if (theta == 270) { |
| *edgex = xm; |
| *edgey = ymin; |
| } else if (theta == 180) { |
| *edgex = xmin; |
| *edgey = ym; |
| } else if (theta == 90) { |
| *edgex = xm; |
| *edgey = ymax; |
| } else { |
| double cutoff = dy/dx; |
| double angle = theta/180*M_PI; |
| double tanTheta = tan(angle); |
| double cosTheta = cos(angle); |
| double sinTheta = sin(angle); |
| if (fabs(tanTheta) < cutoff) { /* Intersect with side */ |
| if (cosTheta > 0) { /* Right side */ |
| *edgex = xmax; |
| *edgey = ym + tanTheta*dx; |
| } else { /* Left side */ |
| *edgex = xmin; |
| *edgey = ym - tanTheta*dx; |
| } |
| } else { /* Intersect with top/bottom */ |
| if (sinTheta > 0) { /* Top */ |
| *edgey = ymax; |
| *edgex = xm + dy/tanTheta; |
| } else { /* Bottom */ |
| *edgey = ymin; |
| *edgex = xm - dy/tanTheta; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Calculate the point on the edge of a rectangle at angle theta |
| * 0 = East, 180 = West, etc ... |
| * Assumes that x- and y-values are in INCHES |
| * Assumes that theta is within [0, 360) |
| */ |
| static void circleEdge(double x, double y, double r, |
| double theta, |
| double *edgex, double *edgey) |
| { |
| double angle = theta/180*M_PI; |
| *edgex = x + r*cos(angle); |
| *edgey = y + r*sin(angle); |
| } |
| |
| /* |
| * Calculate the point on the edge of a *convex* polygon at angle theta |
| * 0 = East, 180 = West, etc ... |
| * Assumes that x- and y-values are in INCHES |
| * Assumes that vertices are in clock-wise order |
| * Assumes that theta is within [0, 360) |
| */ |
| static void polygonEdge(double *x, double *y, int n, |
| double theta, |
| double *edgex, double *edgey) { |
| int i, v1, v2; |
| double xm, ym; |
| double xmin = DOUBLE_XMAX; |
| double xmax = -DOUBLE_XMAX; |
| double ymin = DOUBLE_XMAX; |
| double ymax = -DOUBLE_XMAX; |
| int found = 0; |
| double angle = theta/180*M_PI; |
| double vangle1, vangle2; |
| /* |
| * Find "centre" of polygon |
| */ |
| for (i=0; i<n; i++) { |
| if (x[i] < xmin) |
| xmin = x[i]; |
| if (x[i] > xmax) |
| xmax = x[i]; |
| if (y[i] < ymin) |
| ymin = y[i]; |
| if (y[i] > ymax) |
| ymax = y[i]; |
| } |
| xm = (xmin + xmax)/2; |
| ym = (ymin + ymax)/2; |
| /* |
| * Special case zero-width or zero-height |
| */ |
| if (fabs(xmin - xmax) < 1e-6) { |
| *edgex = xmin; |
| if (theta == 90) |
| *edgey = ymax; |
| else if (theta == 270) |
| *edgey = ymin; |
| else |
| *edgey = ym; |
| return; |
| } |
| if (fabs(ymin - ymax) < 1e-6) { |
| *edgey = ymin; |
| if (theta == 0) |
| *edgex = xmax; |
| else if (theta == 180) |
| *edgex = xmin; |
| else |
| *edgex = xm; |
| return; |
| } |
| /* |
| * Find edge that intersects line from centre at angle theta |
| */ |
| for (i=0; i<n; i++) { |
| v1 = i; |
| v2 = v1 + 1; |
| if (v2 == n) |
| v2 = 0; |
| /* |
| * Result of atan2 is in range -PI, PI so convert to |
| * 0, 360 to correspond to angle |
| */ |
| vangle1 = atan2(y[v1] - ym, x[v1] - xm); |
| if (vangle1 < 0) |
| vangle1 = vangle1 + 2*M_PI; |
| vangle2 = atan2(y[v2] - ym, x[v2] - xm); |
| if (vangle2 < 0) |
| vangle2 = vangle2 + 2*M_PI; |
| /* |
| * If vangle1 < vangle2 then angles are either side of 0 |
| * so check is more complicated |
| */ |
| if ((vangle1 >= vangle2 && |
| vangle1 >= angle && vangle2 <= angle) || |
| (vangle1 < vangle2 && |
| ((vangle1 >= angle && 0 <= angle) || |
| (vangle2 <= angle && 2*M_PI >= angle)))) { |
| found = 1; |
| break; |
| } |
| } |
| /* |
| * Find intersection point of "line from centre to bounding rect" |
| * and edge |
| */ |
| if (found) { |
| double x1 = xm; |
| double y1 = ym; |
| double x2, y2; |
| double x3 = x[v1]; |
| double y3 = y[v1]; |
| double x4 = x[v2]; |
| double y4 = y[v2]; |
| double numa, denom, ua; |
| rectEdge(xmin, ymin, xmax, ymax, theta, |
| &x2, &y2); |
| numa = ((x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3)); |
| denom = ((y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1)); |
| ua = numa/denom; |
| if (!R_FINITE(ua)) { |
| /* |
| * Should only happen if lines are parallel, which |
| * shouldn't happen! Unless, perhaps the polygon has |
| * zero extent vertically or horizontally ... ? |
| */ |
| error(_("polygon edge not found (zero-width or zero-height?)")); |
| } |
| /* |
| * numb = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)); |
| * ub = numb/denom; |
| */ |
| *edgex = x1 + ua*(x2 - x1); |
| *edgey = y1 + ua*(y2 - y1); |
| } else { |
| error(_("polygon edge not found")); |
| } |
| } |
| |
| /* |
| * Given a set of points, calculate the convex hull then |
| * find the edge of that hull |
| * |
| * NOTE: assumes that 'grDevices' package has been loaded |
| * so that chull() is available (grid depends on grDevices) |
| */ |
| static void hullEdge(double *x, double *y, int n, |
| double theta, |
| double *edgex, double *edgey) |
| { |
| const void *vmax; |
| int i, nh; |
| double *hx, *hy; |
| SEXP xin, yin, chullFn, R_fcall, hull; |
| int adjust = 0; |
| double *xkeep, *ykeep; |
| vmax = vmaxget(); |
| /* Remove any NA's because chull() can't cope with them */ |
| xkeep = (double *) R_alloc(n, sizeof(double)); |
| ykeep = (double *) R_alloc(n, sizeof(double)); |
| for (i=0; i<n; i++) { |
| if (!R_FINITE(x[i]) || !R_FINITE(y[i])) { |
| adjust--; |
| } else { |
| xkeep[i + adjust] = x[i]; |
| ykeep[i + adjust] = y[i]; |
| } |
| } |
| n = n + adjust; |
| PROTECT(xin = allocVector(REALSXP, n)); |
| PROTECT(yin = allocVector(REALSXP, n)); |
| for (i=0; i<n; i++) { |
| REAL(xin)[i] = xkeep[i]; |
| REAL(yin)[i] = ykeep[i]; |
| } |
| /* |
| * Determine convex hull |
| */ |
| PROTECT(chullFn = findFun(install("chull"), R_gridEvalEnv)); |
| PROTECT(R_fcall = lang3(chullFn, xin, yin)); |
| PROTECT(hull = eval(R_fcall, R_gridEvalEnv)); |
| nh = LENGTH(hull); |
| hx = (double *) R_alloc(nh, sizeof(double)); |
| hy = (double *) R_alloc(nh, sizeof(double)); |
| for (i=0; i<nh; i++) { |
| hx[i] = x[INTEGER(hull)[i] - 1]; |
| hy[i] = y[INTEGER(hull)[i] - 1]; |
| } |
| /* |
| * Find edge of that hull |
| */ |
| polygonEdge(hx, hy, nh, theta, |
| edgex, edgey); |
| vmaxset(vmax); |
| UNPROTECT(5); |
| } |
| |
| /*************************** |
| * DRAWING PRIMITIVES |
| *************************** |
| */ |
| |
| /* |
| * Draw an arrow head, given the vertices of the arrow head. |
| * Assume vertices are in DEVICE coordinates. |
| */ |
| static void drawArrow(double *x, double *y, SEXP type, int i, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| int nt = LENGTH(type); |
| switch (INTEGER(type)[i % nt]) { |
| case 1: |
| GEPolyline(3, x, y, gc, dd); |
| break; |
| case 2: |
| GEPolygon(3, x, y, gc, dd); |
| break; |
| } |
| } |
| |
| /* |
| * Calculate vertices for drawing an arrow head. |
| * Assumes that x and y locations are in INCHES. |
| * Returns vertices in DEVICE coordinates. |
| */ |
| static void calcArrow(double x1, double y1, |
| double x2, double y2, |
| SEXP angle, SEXP length, int i, |
| LViewportContext vpc, |
| double vpWidthCM, double vpHeightCM, |
| double *vertx, double *verty, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| int na = LENGTH(angle); |
| int nl = LENGTH(length); |
| double xc, yc, rot; |
| double l1, l2, l, a; |
| l1 = transformWidthtoINCHES(length, i % nl, vpc, gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| l2 = transformHeighttoINCHES(length, i % nl, vpc, gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| l = fmin2(l1, l2); |
| a = DEG2RAD * REAL(angle)[i % na]; |
| xc = x2 - x1; |
| yc = y2 - y1; |
| rot= atan2(yc, xc); |
| vertx[0] = toDeviceX(x1 + l * cos(rot+a), |
| GE_INCHES, dd); |
| verty[0] = toDeviceY(y1 + l * sin(rot+a), |
| GE_INCHES, dd); |
| vertx[1] = toDeviceX(x1, |
| GE_INCHES, dd); |
| verty[1] = toDeviceY(y1, |
| GE_INCHES, dd); |
| vertx[2] = toDeviceX(x1 + l * cos(rot-a), |
| GE_INCHES, dd); |
| verty[2] = toDeviceY(y1 + l * sin(rot-a), |
| GE_INCHES, dd); |
| } |
| |
| /* |
| * Assumes x and y are at least length 2 |
| * Also assumes x and y are in DEVICE coordinates |
| */ |
| static void arrows(double *x, double *y, int n, |
| SEXP arrow, int i, |
| /* |
| * Which ends we are allowed to draw arrow heads on |
| * (we may be drawing a line segment that has been |
| * broken by NAs) |
| */ |
| Rboolean start, Rboolean end, |
| LViewportContext vpc, |
| double vpWidthCM, double vpHeightCM, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| /* |
| * Write a checkArrow() function to make |
| * sure 'a' is a valid arrow description ? |
| * If someone manages to sneak in a |
| * corrupt arrow description ... BOOM! |
| */ |
| SEXP ends = VECTOR_ELT(arrow, GRID_ARROWENDS); |
| int ne = LENGTH(ends); |
| double vertx[3], verty[3]; |
| Rboolean first, last; |
| if (n < 2) |
| error(_("require at least two points to draw arrow")); |
| first = TRUE; |
| last = TRUE; |
| switch (INTEGER(ends)[i % ne]) { |
| case 2: |
| first = FALSE; |
| break; |
| case 1: |
| last = FALSE; |
| break; |
| } |
| if (first && start) { |
| calcArrow(fromDeviceX(x[0], GE_INCHES, dd), |
| fromDeviceY(y[0], GE_INCHES, dd), |
| fromDeviceX(x[1], GE_INCHES, dd), |
| fromDeviceY(y[1], GE_INCHES, dd), |
| VECTOR_ELT(arrow, GRID_ARROWANGLE), |
| VECTOR_ELT(arrow, GRID_ARROWLENGTH), |
| i, vpc, vpWidthCM, vpHeightCM, vertx, verty, gc, dd); |
| drawArrow(vertx, verty, |
| VECTOR_ELT(arrow, GRID_ARROWTYPE), i, |
| gc, dd); |
| } |
| if (last && end) { |
| calcArrow(fromDeviceX(x[n - 1], GE_INCHES, dd), |
| fromDeviceY(y[n - 1], GE_INCHES, dd), |
| fromDeviceX(x[n - 2], GE_INCHES, dd), |
| fromDeviceY(y[n - 2], GE_INCHES, dd), |
| VECTOR_ELT(arrow, GRID_ARROWANGLE), |
| VECTOR_ELT(arrow, GRID_ARROWLENGTH), |
| i, vpc, vpWidthCM, vpHeightCM, vertx, verty, gc, dd); |
| drawArrow(vertx, verty, |
| VECTOR_ELT(arrow, GRID_ARROWTYPE), i, |
| gc, dd); |
| } |
| } |
| |
| SEXP L_moveTo(SEXP x, SEXP y) |
| { |
| double xx, yy; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| LViewportContext vpc; |
| R_GE_gcontext gc; |
| LTransform transform; |
| SEXP devloc, prevloc; |
| SEXP currentvp, currentgp; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| PROTECT(prevloc = gridStateElement(dd, GSS_PREVLOC)); |
| PROTECT(devloc = gridStateElement(dd, GSS_CURRLOC)); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| gcontextFromgpar(currentgp, 0, &gc, dd); |
| /* Convert the x and y values to CM locations */ |
| transformLocn(x, y, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx, &yy); |
| /* |
| * Non-finite values are ok here |
| * L_lineTo figures out what to draw |
| * when values are non-finite |
| */ |
| REAL(prevloc)[0] = REAL(devloc)[0]; |
| REAL(prevloc)[1] = REAL(devloc)[1]; |
| REAL(devloc)[0] = xx; |
| REAL(devloc)[1] = yy; |
| UNPROTECT(2); |
| return R_NilValue; |
| } |
| |
| SEXP L_lineTo(SEXP x, SEXP y, SEXP arrow) |
| { |
| double xx0, yy0, xx1, yy1; |
| double xx, yy; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| LViewportContext vpc; |
| R_GE_gcontext gc; |
| LTransform transform; |
| SEXP devloc, prevloc; |
| SEXP currentvp, currentgp; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| PROTECT(prevloc = gridStateElement(dd, GSS_PREVLOC)); |
| PROTECT(devloc = gridStateElement(dd, GSS_CURRLOC)); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| gcontextFromgpar(currentgp, 0, &gc, dd); |
| /* Convert the x and y values to CM locations */ |
| transformLocn(x, y, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx, &yy); |
| REAL(prevloc)[0] = REAL(devloc)[0]; |
| REAL(prevloc)[1] = REAL(devloc)[1]; |
| REAL(devloc)[0] = xx; |
| REAL(devloc)[1] = yy; |
| /* The graphics engine only takes device coordinates |
| */ |
| xx0 = toDeviceX(REAL(prevloc)[0], GE_INCHES, dd); |
| yy0 = toDeviceY(REAL(prevloc)[1], GE_INCHES, dd), |
| xx1 = toDeviceX(xx, GE_INCHES, dd); |
| yy1 = toDeviceY(yy, GE_INCHES, dd); |
| if (R_FINITE(xx0) && R_FINITE(yy0) && |
| R_FINITE(xx1) && R_FINITE(yy1)) { |
| GEMode(1, dd); |
| GELine(xx0, yy0, xx1, yy1, &gc, dd); |
| if (!isNull(arrow)) { |
| double ax[2], ay[2]; |
| ax[0] = xx0; |
| ax[1] = xx1; |
| ay[0] = yy0; |
| ay[1] = yy1; |
| arrows(ax, ay, 2, |
| arrow, 0, TRUE, TRUE, |
| vpc, vpWidthCM, vpHeightCM, &gc, dd); |
| } |
| GEMode(0, dd); |
| } |
| UNPROTECT(2); |
| return R_NilValue; |
| } |
| |
| /* We are assuming here that the R code has checked that x and y |
| * are unit objects and that vp is a viewport |
| */ |
| SEXP L_lines(SEXP x, SEXP y, SEXP index, SEXP arrow) |
| { |
| int i, j, nx, nl, start=0; |
| double *xx, *yy; |
| double xold, yold; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| const void *vmax; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| GEMode(1, dd); |
| /* |
| * Number of lines |
| */ |
| nl = LENGTH(index); |
| for (j=0; j<nl; j++) { |
| SEXP indices = VECTOR_ELT(index, j); |
| updateGContext(currentgp, j, &gc, dd, gpIsScalar, &gcCache); |
| /* |
| * Number of vertices |
| * |
| * x and y same length forced in R code |
| */ |
| nx = LENGTH(indices); |
| /* Convert the x and y values to CM locations */ |
| vmax = vmaxget(); |
| xx = (double *) R_alloc(nx, sizeof(double)); |
| yy = (double *) R_alloc(nx, sizeof(double)); |
| xold = NA_REAL; |
| yold = NA_REAL; |
| for (i=0; i<nx; i++) { |
| transformLocn(x, y, INTEGER(indices)[i] - 1, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &(xx[i]), &(yy[i])); |
| /* The graphics engine only takes device coordinates |
| */ |
| xx[i] = toDeviceX(xx[i], GE_INCHES, dd); |
| yy[i] = toDeviceY(yy[i], GE_INCHES, dd); |
| if ((R_FINITE(xx[i]) && R_FINITE(yy[i])) && |
| !(R_FINITE(xold) && R_FINITE(yold))) |
| start = i; |
| else if ((R_FINITE(xold) && R_FINITE(yold)) && |
| !(R_FINITE(xx[i]) && R_FINITE(yy[i]))) { |
| if (i-start > 1) { |
| GEPolyline(i-start, xx+start, yy+start, &gc, dd); |
| if (!isNull(arrow)) { |
| /* |
| * Can draw an arrow at the start if the points |
| * include the first point. |
| * CANNOT draw an arrow at the end point |
| * because we have just broken the line for an NA. |
| */ |
| arrows(xx+start, yy+start, i-start, |
| arrow, j, start == 0, FALSE, |
| vpc, vpWidthCM, vpHeightCM, &gc, dd); |
| } |
| } |
| } |
| else if ((R_FINITE(xold) && R_FINITE(yold)) && |
| (i == nx-1)) { |
| GEPolyline(nx-start, xx+start, yy+start, &gc, dd); |
| if (!isNull(arrow)) { |
| /* |
| * Can draw an arrow at the start if the points |
| * include the first point. |
| * Can draw an arrow at the end point. |
| */ |
| arrows(xx+start, yy+start, nx-start, |
| arrow, j, start == 0, TRUE, |
| vpc, vpWidthCM, vpHeightCM, &gc, dd); |
| } |
| } |
| xold = xx[i]; |
| yold = yy[i]; |
| } |
| vmaxset(vmax); |
| } |
| GEMode(0, dd); |
| return R_NilValue; |
| } |
| |
| /* We are assuming here that the R code has checked that x and y |
| * are unit objects |
| */ |
| SEXP gridXspline(SEXP x, SEXP y, SEXP s, SEXP o, SEXP a, SEXP rep, SEXP index, |
| double theta, Rboolean draw, Rboolean trace) |
| { |
| int i, j, nx, np, nloc; |
| double *xx, *yy, *ss; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| SEXP tracePts = R_NilValue; |
| SEXP result = R_NilValue; |
| double edgex, edgey; |
| double xmin = DOUBLE_XMAX; |
| double xmax = -DOUBLE_XMAX; |
| double ymin = DOUBLE_XMAX; |
| double ymax = -DOUBLE_XMAX; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| /* |
| * Number of xsplines |
| */ |
| np = LENGTH(index); |
| PROTECT(tracePts = allocVector(VECSXP, np)); |
| nloc = 0; |
| for (i=0; i<np; i++) { |
| const void *vmax; |
| SEXP indices = VECTOR_ELT(index, i); |
| SEXP points; |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| /* |
| * Number of vertices |
| * |
| * Check in R code that x and y same length |
| */ |
| nx = LENGTH(indices); |
| /* Convert the x and y values to CM locations */ |
| vmax = vmaxget(); |
| if (draw) |
| GEMode(1, dd); |
| xx = (double *) R_alloc(nx, sizeof(double)); |
| yy = (double *) R_alloc(nx, sizeof(double)); |
| ss = (double *) R_alloc(nx, sizeof(double)); |
| for (j=0; j<nx; j++) { |
| ss[j] = REAL(s)[(INTEGER(indices)[j] - 1) % LENGTH(s)]; |
| /* |
| * If drawing, convert to INCHES on device |
| * If just calculating bounds, convert to INCHES within current vp |
| */ |
| if (draw) { |
| transformLocn(x, y, INTEGER(indices)[j] - 1, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &(xx[j]), &(yy[j])); |
| } else { |
| xx[j] = transformXtoINCHES(x, INTEGER(indices)[j] - 1, |
| vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| yy[j] = transformYtoINCHES(y, INTEGER(indices)[j] - 1, |
| vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| /* The graphics engine only takes device coordinates |
| */ |
| xx[j] = toDeviceX(xx[j], GE_INCHES, dd); |
| yy[j] = toDeviceY(yy[j], GE_INCHES, dd); |
| if (!(R_FINITE(xx[j]) && R_FINITE(yy[j]))) { |
| error(_("non-finite control point in Xspline")); |
| } |
| } |
| PROTECT(points = GEXspline(nx, xx, yy, ss, |
| LOGICAL(o)[0], LOGICAL(rep)[0], |
| draw, &gc, dd)); |
| { |
| /* |
| * In some cases, GEXspline seems to produce identical points |
| * (at least observed at end of spline) |
| * so trim identical points from the ends |
| * (so arrow heads are drawn at correct angle) |
| */ |
| int np = LENGTH(VECTOR_ELT(points, 0)); |
| double *px = REAL(VECTOR_ELT(points, 0)); |
| double *py = REAL(VECTOR_ELT(points, 1)); |
| int start = 0; |
| int end = np - 1; |
| /* |
| * DEBUGGING ... |
| int k; |
| for (k=0; k<np; k++) { |
| GESymbol(px[k], py[k], 16, 3, &gc, dd); |
| } |
| * ... DEBUGGING |
| */ |
| while (np > 1 && |
| (px[start] == px[start + 1]) && |
| (py[start] == py[start + 1])) { |
| start++; |
| np--; |
| } |
| while (np > 1 && |
| (px[end] == px[end - 1]) && |
| (py[end] == py[end - 1])) { |
| end--; |
| np--; |
| } |
| if (trace) { |
| int k; |
| int count = end - start + 1; |
| double *keepXptr, *keepYptr; |
| SEXP keepPoints, keepX, keepY; |
| PROTECT(keepPoints = allocVector(VECSXP, 2)); |
| PROTECT(keepX = allocVector(REALSXP, count)); |
| PROTECT(keepY = allocVector(REALSXP, count)); |
| keepXptr = REAL(keepX); |
| keepYptr = REAL(keepY); |
| for (k=start; k<(end + 1); k++) { |
| keepXptr[k - start] = fromDeviceX(px[k], GE_INCHES, dd); |
| keepYptr[k - start] = fromDeviceY(py[k], GE_INCHES, dd); |
| } |
| SET_VECTOR_ELT(keepPoints, 0, keepX); |
| SET_VECTOR_ELT(keepPoints, 1, keepY); |
| SET_VECTOR_ELT(tracePts, i, keepPoints); |
| UNPROTECT(3); /* keepPoints & keepX & keepY */ |
| } |
| if (draw && !isNull(a) && !isNull(points)) { |
| /* |
| * Can draw an arrow at the either end. |
| */ |
| arrows(&(px[start]), &(py[start]), np, |
| a, i, TRUE, TRUE, |
| vpc, vpWidthCM, vpHeightCM, &gc, dd); |
| } |
| if (!draw && !trace && !isNull(points)) { |
| /* |
| * Update bounds |
| */ |
| int j, n = LENGTH(VECTOR_ELT(points, 0)); |
| double *pxx = (double *) R_alloc(n, sizeof(double)); |
| double *pyy = (double *) R_alloc(n, sizeof(double)); |
| for (j=0; j<n; j++) { |
| pxx[j] = fromDeviceX(px[j], GE_INCHES, dd); |
| pyy[j] = fromDeviceY(py[j], GE_INCHES, dd); |
| if (R_FINITE(pxx[j]) && R_FINITE(pyy[j])) { |
| if (pxx[j] < xmin) |
| xmin = pxx[j]; |
| if (pxx[j] > xmax) |
| xmax = pxx[j]; |
| if (pyy[j] < ymin) |
| ymin = pyy[j]; |
| if (pyy[j] > ymax) |
| ymax = pyy[j]; |
| nloc++; |
| } |
| } |
| /* |
| * Calculate edgex and edgey for case where this is |
| * the only xspline |
| */ |
| hullEdge(pxx, pyy, n, theta, &edgex, &edgey); |
| } |
| } /* End of trimming-redundant-points code */ |
| UNPROTECT(1); /* points */ |
| if (draw) |
| GEMode(0, dd); |
| vmaxset(vmax); |
| } |
| if (!draw && !trace && nloc > 0) { |
| PROTECT(result = allocVector(REALSXP, 4)); |
| /* |
| * If there is more than one xspline, just produce edge |
| * based on bounding rect of all xsplines |
| */ |
| if (np > 1) { |
| rectEdge(xmin, ymin, xmax, ymax, theta, |
| &edgex, &edgey); |
| } |
| /* |
| * Reverse the scale adjustment (zoom factor) |
| * when calculating physical value to return to user-level |
| */ |
| REAL(result)[0] = edgex / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[1] = edgey / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[2] = (xmax - xmin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[3] = (ymax - ymin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| UNPROTECT(1); /* result */ |
| } else if (trace) { |
| result = tracePts; |
| } |
| UNPROTECT(1); /* tracePts */ |
| return result; |
| } |
| |
| SEXP L_xspline(SEXP x, SEXP y, SEXP s, SEXP o, SEXP a, SEXP rep, SEXP index) |
| { |
| gridXspline(x, y, s, o, a, rep, index, 0, TRUE, FALSE); |
| return R_NilValue; |
| } |
| |
| SEXP L_xsplineBounds(SEXP x, SEXP y, SEXP s, SEXP o, SEXP a, SEXP rep, |
| SEXP index, SEXP theta) |
| { |
| return gridXspline(x, y, s, o, a, rep, index, REAL(theta)[0], |
| FALSE, FALSE); |
| } |
| |
| SEXP L_xsplinePoints(SEXP x, SEXP y, SEXP s, SEXP o, SEXP a, SEXP rep, |
| SEXP index, SEXP theta) |
| { |
| return gridXspline(x, y, s, o, a, rep, index, REAL(theta)[0], |
| FALSE, TRUE); |
| } |
| |
| SEXP L_segments(SEXP x0, SEXP y0, SEXP x1, SEXP y1, SEXP arrow) |
| { |
| int i, nx0, ny0, nx1, ny1, maxn; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| maxn = nx0 = unitLength(x0); |
| ny0 = unitLength(y0); |
| nx1 = unitLength(x1); |
| ny1 = unitLength(y1); |
| if (ny0 > maxn) |
| maxn = ny0; |
| if (nx1 > maxn) |
| maxn = nx1; |
| if (ny1 > maxn) |
| maxn = ny1; |
| /* Convert the x and y values to INCHES locations */ |
| /* FIXME: Need to check for NaN's and NA's |
| */ |
| GEMode(1, dd); |
| for (i=0; i<maxn; i++) { |
| double xx0, yy0, xx1, yy1; |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| transformLocn(x0, y0, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, transform, &xx0, &yy0); |
| transformLocn(x1, y1, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, transform, &xx1, &yy1); |
| /* The graphics engine only takes device coordinates |
| */ |
| xx0 = toDeviceX(xx0, GE_INCHES, dd); |
| yy0 = toDeviceY(yy0, GE_INCHES, dd); |
| xx1 = toDeviceX(xx1, GE_INCHES, dd); |
| yy1 = toDeviceY(yy1, GE_INCHES, dd); |
| if (R_FINITE(xx0) && R_FINITE(yy0) && |
| R_FINITE(xx1) && R_FINITE(yy1)) { |
| GELine(xx0, yy0, xx1, yy1, &gc, dd); |
| if (!isNull(arrow)) { |
| double ax[2], ay[2]; |
| ax[0] = xx0; |
| ax[1] = xx1; |
| ay[0] = yy0; |
| ay[1] = yy1; |
| arrows(ax, ay, 2, |
| arrow, i, TRUE, TRUE, |
| vpc, vpWidthCM, vpHeightCM, &gc, dd); |
| } |
| } |
| } |
| GEMode(0, dd); |
| return R_NilValue; |
| } |
| |
| static int getArrowN(SEXP x1, SEXP x2, SEXP xnm1, SEXP xn, |
| SEXP y1, SEXP y2, SEXP ynm1, SEXP yn) |
| { |
| int nx2, nxnm1, nxn, ny1, ny2, nynm1, nyn, maxn; |
| maxn = 0; |
| /* |
| * x1, y1, xnm1, and ynm1 could be NULL if this is adding |
| * arrows to a line.to |
| */ |
| if (isNull(y1)) |
| ny1 = 0; |
| else |
| ny1 = unitLength(y1); |
| nx2 = unitLength(x2); |
| ny2 = unitLength(y2); |
| if (isNull(xnm1)) |
| nxnm1 = 0; |
| else |
| nxnm1 = unitLength(xnm1); |
| if (isNull(ynm1)) |
| nynm1 = 0; |
| else |
| nynm1 = unitLength(ynm1); |
| nxn = unitLength(xn); |
| nyn = unitLength(yn); |
| if (ny1 > maxn) |
| maxn = ny1; |
| if (nx2 > maxn) |
| maxn = nx2; |
| if (ny2 > maxn) |
| maxn = ny2; |
| if (nxnm1 > maxn) |
| maxn = nxnm1; |
| if (nynm1 > maxn) |
| maxn = nynm1; |
| if (nxn > maxn) |
| maxn = nxn; |
| if (nyn > maxn) |
| maxn = nyn; |
| return maxn; |
| } |
| |
| SEXP L_arrows(SEXP x1, SEXP x2, SEXP xnm1, SEXP xn, |
| SEXP y1, SEXP y2, SEXP ynm1, SEXP yn, |
| SEXP angle, SEXP length, SEXP ends, SEXP type) |
| { |
| int i, maxn; |
| int ne; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| Rboolean first, last; |
| LViewportContext vpc; |
| R_GE_gcontext gc; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| SEXP devloc = R_NilValue; /* -Wall */ |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| maxn = getArrowN(x1, x2, xnm1, xn, |
| y1, y2, ynm1, yn); |
| ne = LENGTH(ends); |
| /* Convert the x and y values to INCHES locations */ |
| /* FIXME: Need to check for NaN's and NA's |
| */ |
| GEMode(1, dd); |
| for (i=0; i<maxn; i++) { |
| double xx1, xx2, xxnm1, xxn, yy1, yy2, yynm1, yyn; |
| double vertx[3]; |
| double verty[3]; |
| first = TRUE; |
| last = TRUE; |
| switch (INTEGER(ends)[i % ne]) { |
| case 2: |
| first = FALSE; |
| break; |
| case 1: |
| last = FALSE; |
| break; |
| } |
| gcontextFromgpar(currentgp, i, &gc, dd); |
| /* |
| * If we're adding arrows to a line.to |
| * x1 will be NULL |
| */ |
| if (isNull(x1)) |
| PROTECT(devloc = gridStateElement(dd, GSS_CURRLOC)); |
| if (first) { |
| if (isNull(x1)) { |
| xx1 = REAL(devloc)[0]; |
| yy1 = REAL(devloc)[1]; |
| } else |
| transformLocn(x1, y1, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, transform, &xx1, &yy1); |
| transformLocn(x2, y2, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, transform, &xx2, &yy2); |
| calcArrow(xx1, yy1, xx2, yy2, angle, length, i, |
| vpc, vpWidthCM, vpHeightCM, |
| vertx, verty, &gc, dd); |
| /* |
| * Only draw arrow if both ends of first segment |
| * are not non-finite |
| */ |
| if (R_FINITE(toDeviceX(xx2, GE_INCHES, dd)) && |
| R_FINITE(toDeviceY(yy2, GE_INCHES, dd)) && |
| R_FINITE(vertx[1]) && R_FINITE(verty[1])) |
| drawArrow(vertx, verty, type, i, &gc, dd); |
| } |
| if (last) { |
| if (isNull(xnm1)) { |
| xxnm1 = REAL(devloc)[0]; |
| yynm1 = REAL(devloc)[1]; |
| } else |
| transformLocn(xnm1, ynm1, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, transform, &xxnm1, &yynm1); |
| transformLocn(xn, yn, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, transform, &xxn, &yyn); |
| calcArrow(xxn, yyn, xxnm1, yynm1, angle, length, i, |
| vpc, vpWidthCM, vpHeightCM, |
| vertx, verty, &gc, dd); |
| /* |
| * Only draw arrow if both ends of laste segment are |
| * not non-finite |
| */ |
| if (R_FINITE(toDeviceX(xxnm1, GE_INCHES, dd)) && |
| R_FINITE(toDeviceY(yynm1, GE_INCHES, dd)) && |
| R_FINITE(vertx[1]) && R_FINITE(verty[1])) |
| drawArrow(vertx, verty, type, i, &gc, dd); |
| } |
| if (isNull(x1)) |
| UNPROTECT(1); |
| } |
| GEMode(0, dd); |
| return R_NilValue; |
| } |
| |
| SEXP L_polygon(SEXP x, SEXP y, SEXP index) |
| { |
| int i, j, nx, np, start=0; |
| double *xx, *yy; |
| double xold, yold; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| GEMode(1, dd); |
| /* |
| * Number of polygons |
| */ |
| np = LENGTH(index); |
| for (i=0; i<np; i++) { |
| const void *vmax; |
| SEXP indices = VECTOR_ELT(index, i); |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| /* |
| * Number of vertices |
| * |
| * Check in R code that x and y same length |
| */ |
| nx = LENGTH(indices); |
| /* Convert the x and y values to CM locations */ |
| vmax = vmaxget(); |
| xx = (double *) R_alloc(nx + 1, sizeof(double)); |
| yy = (double *) R_alloc(nx + 1, sizeof(double)); |
| xold = NA_REAL; |
| yold = NA_REAL; |
| for (j=0; j<nx; j++) { |
| transformLocn(x, y, INTEGER(indices)[j] - 1, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &(xx[j]), &(yy[j])); |
| /* The graphics engine only takes device coordinates |
| */ |
| xx[j] = toDeviceX(xx[j], GE_INCHES, dd); |
| yy[j] = toDeviceY(yy[j], GE_INCHES, dd); |
| if ((R_FINITE(xx[j]) && R_FINITE(yy[j])) && |
| !(R_FINITE(xold) && R_FINITE(yold))) |
| start = j; /* first point of current segment */ |
| else if ((R_FINITE(xold) && R_FINITE(yold)) && |
| !(R_FINITE(xx[j]) && R_FINITE(yy[j]))) { |
| if (j-start > 1) { |
| GEPolygon(j-start, xx+start, yy+start, &gc, dd); |
| } |
| } |
| else if ((R_FINITE(xold) && R_FINITE(yold)) && (j == nx-1)) { |
| /* last */ |
| GEPolygon(nx-start, xx+start, yy+start, &gc, dd); |
| } |
| xold = xx[j]; |
| yold = yy[j]; |
| } |
| vmaxset(vmax); |
| } |
| GEMode(0, dd); |
| return R_NilValue; |
| } |
| |
| static SEXP gridCircle(SEXP x, SEXP y, SEXP r, |
| double theta, Rboolean draw) |
| { |
| int i, nx, ny, nr, ncirc; |
| double xx, yy, rr1, rr2, rr = 0.0 /* -Wall */; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| SEXP result = R_NilValue; |
| double xmin = DOUBLE_XMAX; |
| double xmax = -DOUBLE_XMAX; |
| double ymin = DOUBLE_XMAX; |
| double ymax = -DOUBLE_XMAX; |
| double edgex, edgey; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| nx = unitLength(x); |
| ny = unitLength(y); |
| nr = unitLength(r); |
| if (ny > nx) |
| nx = ny; |
| if (nr > nx) |
| nx = nr; |
| if (draw) { |
| GEMode(1, dd); |
| } |
| ncirc = 0; |
| for (i=0; i<nx; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| /* |
| * If drawing, convert to INCHES on device |
| * If just calculating bounds, convert to INCHES within current vp |
| */ |
| if (draw) { |
| transformLocn(x, y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx, &yy); |
| } else { |
| xx = transformXtoINCHES(x, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| yy = transformYtoINCHES(y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| /* These two will give the same answer unless r is in "native", |
| * "npc", or some other relative units; in those cases, just |
| * take the smaller of the two values. |
| */ |
| rr1 = transformWidthtoINCHES(r, i % nr, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| rr2 = transformHeighttoINCHES(r, i % nr, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| /* |
| * A negative radius is silently converted to absolute value |
| */ |
| rr = fmin2(fabs(rr1), fabs(rr2)); |
| if (R_FINITE(xx) && R_FINITE(yy) && R_FINITE(rr)) { |
| if (draw) { |
| /* The graphics engine only takes device coordinates |
| */ |
| xx = toDeviceX(xx, GE_INCHES, dd); |
| yy = toDeviceY(yy, GE_INCHES, dd); |
| rr = toDeviceWidth(rr, GE_INCHES, dd); |
| GECircle(xx, yy, rr, &gc, dd); |
| } else { |
| if (xx + rr < xmin) |
| xmin = xx + rr; |
| if (xx + rr > xmax) |
| xmax = xx + rr; |
| if (xx - rr < xmin) |
| xmin = xx - rr; |
| if (xx - rr > xmax) |
| xmax = xx - rr; |
| if (yy + rr < ymin) |
| ymin = yy + rr; |
| if (yy + rr > ymax) |
| ymax = yy + rr; |
| if (yy - rr < ymin) |
| ymin = yy - rr; |
| if (yy - rr > ymax) |
| ymax = yy - rr; |
| ncirc++; |
| } |
| } |
| } |
| if (draw) { |
| GEMode(0, dd); |
| } else if (ncirc > 0) { |
| result = allocVector(REALSXP, 4); |
| if (ncirc == 1) { |
| /* |
| * Produce edge of actual circle |
| */ |
| circleEdge(xx, yy, rr, theta, &edgex, &edgey); |
| } else { |
| /* |
| * Produce edge of rect bounding all circles |
| */ |
| rectEdge(xmin, ymin, xmax, ymax, theta, |
| &edgex, &edgey); |
| } |
| /* |
| * Reverse the scale adjustment (zoom factor) |
| * when calculating physical value to return to user-level |
| */ |
| REAL(result)[0] = edgex / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[1] = edgey / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[2] = (xmax - xmin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[3] = (ymax - ymin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| } |
| return result; |
| } |
| |
| SEXP L_circle(SEXP x, SEXP y, SEXP r) |
| { |
| gridCircle(x, y, r, 0, TRUE); |
| return R_NilValue; |
| } |
| |
| SEXP L_circleBounds(SEXP x, SEXP y, SEXP r, SEXP theta) |
| { |
| return gridCircle(x, y, r, REAL(theta)[0], FALSE); |
| } |
| |
| /* We are assuming here that the R code has checked that |
| * x, y, w, and h are all unit objects and that vp is a viewport |
| */ |
| static SEXP gridRect(SEXP x, SEXP y, SEXP w, SEXP h, |
| SEXP hjust, SEXP vjust, double theta, Rboolean draw) |
| { |
| double xx, yy, ww, hh; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| int i, ny, nw, nh, maxn, nrect; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| SEXP result = R_NilValue; |
| double edgex, edgey; |
| double xmin = DOUBLE_XMAX; |
| double xmax = -DOUBLE_XMAX; |
| double ymin = DOUBLE_XMAX; |
| double ymax = -DOUBLE_XMAX; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| maxn = unitLength(x); |
| ny = unitLength(y); |
| nw = unitLength(w); |
| nh = unitLength(h); |
| if (ny > maxn) |
| maxn = ny; |
| if (nw > maxn) |
| maxn = nw; |
| if (nh > maxn) |
| maxn = nh; |
| if (draw) { |
| GEMode(1, dd); |
| } |
| nrect = 0; |
| for (i=0; i<maxn; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| /* |
| * If drawing, convert to INCHES on device |
| * If just calculating bounds, convert to INCHES within current vp |
| */ |
| if (draw) { |
| transformLocn(x, y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx, &yy); |
| } else { |
| xx = transformXtoINCHES(x, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| yy = transformYtoINCHES(y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| ww = transformWidthtoINCHES(w, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| hh = transformHeighttoINCHES(h, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| /* If the total rotation angle is zero then we can draw a |
| * rectangle as the devices understand rectangles |
| * Otherwise we have to draw a polygon equivalent. |
| */ |
| if (draw) { |
| if (rotationAngle == 0) { |
| xx = justifyX(xx, ww, REAL(hjust)[i % LENGTH(hjust)]); |
| yy = justifyY(yy, hh, REAL(vjust)[i % LENGTH(vjust)]); |
| /* The graphics engine only takes device coordinates |
| */ |
| xx = toDeviceX(xx, GE_INCHES, dd); |
| yy = toDeviceY(yy, GE_INCHES, dd); |
| ww = toDeviceWidth(ww, GE_INCHES, dd); |
| hh = toDeviceHeight(hh, GE_INCHES, dd); |
| if (R_FINITE(xx) && R_FINITE(yy) && |
| R_FINITE(ww) && R_FINITE(hh)) |
| GERect(xx, yy, xx + ww, yy + hh, &gc, dd); |
| } else { |
| /* We have to do a little bit of work to figure out where the |
| * corners of the rectangle are. |
| */ |
| double xxx[5], yyy[5], xadj, yadj; |
| double dw, dh; |
| SEXP zeroInches, xadjInches, yadjInches, wwInches, hhInches; |
| int tmpcol; |
| PROTECT(zeroInches = unit(0, L_INCHES)); |
| /* Find bottom-left location */ |
| justification(ww, hh, |
| REAL(hjust)[i % LENGTH(hjust)], |
| REAL(vjust)[i % LENGTH(vjust)], |
| &xadj, &yadj); |
| PROTECT(xadjInches = unit(xadj, L_INCHES)); |
| PROTECT(yadjInches = unit(yadj, L_INCHES)); |
| transformDimn(xadjInches, yadjInches, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, rotationAngle, |
| &dw, &dh); |
| xxx[0] = xx + dw; |
| yyy[0] = yy + dh; |
| /* Find top-left location */ |
| PROTECT(hhInches = unit(hh, L_INCHES)); |
| transformDimn(zeroInches, hhInches, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, rotationAngle, |
| &dw, &dh); |
| xxx[1] = xxx[0] + dw; |
| yyy[1] = yyy[0] + dh; |
| /* Find top-right location */ |
| PROTECT(wwInches = unit(ww, L_INCHES)); |
| transformDimn(wwInches, hhInches, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, rotationAngle, |
| &dw, &dh); |
| xxx[2] = xxx[0] + dw; |
| yyy[2] = yyy[0] + dh; |
| /* Find bottom-right location */ |
| transformDimn(wwInches, zeroInches, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, rotationAngle, |
| &dw, &dh); |
| xxx[3] = xxx[0] + dw; |
| yyy[3] = yyy[0] + dh; |
| if (R_FINITE(xxx[0]) && R_FINITE(yyy[0]) && |
| R_FINITE(xxx[1]) && R_FINITE(yyy[1]) && |
| R_FINITE(xxx[2]) && R_FINITE(yyy[2]) && |
| R_FINITE(xxx[3]) && R_FINITE(yyy[3])) { |
| /* The graphics engine only takes device coordinates |
| */ |
| xxx[0] = toDeviceX(xxx[0], GE_INCHES, dd); |
| yyy[0] = toDeviceY(yyy[0], GE_INCHES, dd); |
| xxx[1] = toDeviceX(xxx[1], GE_INCHES, dd); |
| yyy[1] = toDeviceY(yyy[1], GE_INCHES, dd); |
| xxx[2] = toDeviceX(xxx[2], GE_INCHES, dd); |
| yyy[2] = toDeviceY(yyy[2], GE_INCHES, dd); |
| xxx[3] = toDeviceX(xxx[3], GE_INCHES, dd); |
| yyy[3] = toDeviceY(yyy[3], GE_INCHES, dd); |
| /* Close the polygon */ |
| xxx[4] = xxx[0]; |
| yyy[4] = yyy[0]; |
| /* Do separate fill and border to avoid border being |
| * drawn on clipping boundary when there is a fill |
| */ |
| tmpcol = gc.col; |
| gc.col = R_TRANWHITE; |
| GEPolygon(5, xxx, yyy, &gc, dd); |
| gc.col = tmpcol; |
| gc.fill = R_TRANWHITE; |
| GEPolygon(5, xxx, yyy, &gc, dd); |
| } |
| UNPROTECT(5); |
| } |
| } else { /* Just calculating boundary */ |
| xx = justifyX(xx, ww, REAL(hjust)[i % LENGTH(hjust)]); |
| yy = justifyY(yy, hh, REAL(vjust)[i % LENGTH(vjust)]); |
| if (R_FINITE(xx) && R_FINITE(yy) && |
| R_FINITE(ww) && R_FINITE(hh)) { |
| if (xx < xmin) |
| xmin = xx; |
| if (xx > xmax) |
| xmax = xx; |
| if (xx + ww < xmin) |
| xmin = xx + ww; |
| if (xx + ww > xmax) |
| xmax = xx + ww; |
| if (yy < ymin) |
| ymin = yy; |
| if (yy > ymax) |
| ymax = yy; |
| if (yy + hh < ymin) |
| ymin = yy + hh; |
| if (yy + hh > ymax) |
| ymax = yy + hh; |
| /* |
| * Calculate edgex and edgey for case where this is |
| * the only rect |
| */ |
| rectEdge(xx, yy, xx + ww, yy + hh, theta, |
| &edgex, &edgey); |
| nrect++; |
| } |
| } |
| } |
| if (draw) { |
| GEMode(0, dd); |
| } |
| if (nrect > 0) { |
| result = allocVector(REALSXP, 4); |
| /* |
| * If there is more than one rect, just produce edge |
| * based on bounding rect of all rects |
| */ |
| if (nrect > 1) { |
| rectEdge(xmin, ymin, xmax, ymax, theta, |
| &edgex, &edgey); |
| } |
| /* |
| * Reverse the scale adjustment (zoom factor) |
| * when calculating physical value to return to user-level |
| */ |
| REAL(result)[0] = edgex / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[1] = edgey / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[2] = (xmax - xmin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[3] = (ymax - ymin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| } |
| return result; |
| } |
| |
| SEXP L_rect(SEXP x, SEXP y, SEXP w, SEXP h, SEXP hjust, SEXP vjust) |
| { |
| gridRect(x, y, w, h, hjust, vjust, 0, TRUE); |
| return R_NilValue; |
| } |
| |
| SEXP L_rectBounds(SEXP x, SEXP y, SEXP w, SEXP h, SEXP hjust, SEXP vjust, |
| SEXP theta) |
| { |
| return gridRect(x, y, w, h, hjust, vjust, REAL(theta)[0], FALSE); |
| } |
| |
| /* FIXME: need to add L_pathBounds ? */ |
| |
| SEXP L_path(SEXP x, SEXP y, SEXP index, SEXP rule) |
| { |
| int i, j, k, h, npoly, *nper, ntot; |
| double *xx, *yy; |
| const void *vmax; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| GEMode(1, dd); |
| /* |
| * Iterate over all paths |
| */ |
| for (h = 0; h < LENGTH(index); h++) { |
| SEXP polyInd = VECTOR_ELT(index, h); |
| /* |
| * Number of polygons |
| */ |
| npoly = LENGTH(polyInd); |
| /* |
| * Total number of points and |
| * Number of points per polygon |
| */ |
| ntot = 0; |
| nper = (int *) R_alloc(npoly, sizeof(int)); |
| for (i=0; i < npoly; i++) { |
| nper[i] = LENGTH(VECTOR_ELT(polyInd, i)); |
| ntot = ntot + nper[i]; |
| } |
| vmax = vmaxget(); |
| xx = (double *) R_alloc(ntot, sizeof(double)); |
| yy = (double *) R_alloc(ntot, sizeof(double)); |
| k = 0; |
| for (i=0; i < npoly; i++) { |
| int *indices = INTEGER(VECTOR_ELT(polyInd, i)); |
| for (j=0; j < nper[i]; j++) { |
| transformLocn(x, y, indices[j] - 1, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &(xx[k]), &(yy[k])); |
| /* The graphics engine only takes device coordinates |
| */ |
| xx[k] = toDeviceX(xx[k], GE_INCHES, dd); |
| yy[k] = toDeviceY(yy[k], GE_INCHES, dd); |
| /* NO NA values allowed in 'x' or 'y' |
| */ |
| if (!R_FINITE(xx[k]) || !R_FINITE(yy[k])) |
| error(_("non-finite x or y in graphics path")); |
| k++; |
| } |
| } |
| updateGContext(currentgp, h, &gc, dd, gpIsScalar, &gcCache); |
| GEPath(xx, yy, npoly, nper, INTEGER(rule)[0], &gc, dd); |
| vmaxset(vmax); |
| } |
| GEMode(0, dd); |
| return R_NilValue; |
| } |
| |
| /* FIXME: need to add L_rasterBounds */ |
| |
| /* FIXME: Add more checks on correct inputs, |
| e.g., Raster should be a matrix of R colors */ |
| SEXP L_raster(SEXP raster, SEXP x, SEXP y, SEXP w, SEXP h, |
| SEXP hjust, SEXP vjust, SEXP interpolate) |
| { |
| const void *vmax; |
| int i, n, ny, nw, nh, maxn; |
| double xx, yy, ww, hh; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| SEXP dim; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| unsigned int *image; |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| /* Convert the raster matrix to R internal colours */ |
| n = LENGTH(raster); |
| if (n <= 0) { |
| error(_("Empty raster")); |
| } |
| 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); |
| } |
| } |
| dim = getAttrib(raster, R_DimSymbol); |
| maxn = unitLength(x); |
| ny = unitLength(y); |
| nw = unitLength(w); |
| nh = unitLength(h); |
| if (ny > maxn) |
| maxn = ny; |
| if (nw > maxn) |
| maxn = nw; |
| if (nh > maxn) |
| maxn = nh; |
| GEMode(1, dd); |
| for (i=0; i<maxn; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| transformLocn(x, y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx, &yy); |
| ww = transformWidthtoINCHES(w, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| hh = transformHeighttoINCHES(h, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| if (rotationAngle == 0) { |
| xx = justifyX(xx, ww, REAL(hjust)[i % LENGTH(hjust)]); |
| yy = justifyY(yy, hh, REAL(vjust)[i % LENGTH(vjust)]); |
| /* The graphics engine only takes device coordinates |
| */ |
| xx = toDeviceX(xx, GE_INCHES, dd); |
| yy = toDeviceY(yy, GE_INCHES, dd); |
| ww = toDeviceWidth(ww, GE_INCHES, dd); |
| hh = toDeviceHeight(hh, GE_INCHES, dd); |
| if (R_FINITE(xx) && R_FINITE(yy) && |
| R_FINITE(ww) && R_FINITE(hh)) |
| GERaster(image, INTEGER(dim)[1], INTEGER(dim)[0], |
| xx, yy, ww, hh, rotationAngle, |
| LOGICAL(interpolate)[i % LENGTH(interpolate)], |
| &gc, dd); |
| } else { |
| /* We have to do a little bit of work to figure out where the |
| * bottom-left corner of the image is. |
| */ |
| double xbl, ybl, xadj, yadj; |
| double dw, dh; |
| SEXP xadjInches, yadjInches; |
| /* Find bottom-left location */ |
| justification(ww, hh, |
| REAL(hjust)[i % LENGTH(hjust)], |
| REAL(vjust)[i % LENGTH(vjust)], |
| &xadj, &yadj); |
| PROTECT(xadjInches = unit(xadj, L_INCHES)); |
| PROTECT(yadjInches = unit(yadj, L_INCHES)); |
| transformDimn(xadjInches, yadjInches, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, rotationAngle, |
| &dw, &dh); |
| xbl = xx + dw; |
| ybl = yy + dh; |
| xbl = toDeviceX(xbl, GE_INCHES, dd); |
| ybl = toDeviceY(ybl, GE_INCHES, dd); |
| ww = toDeviceWidth(ww, GE_INCHES, dd); |
| hh = toDeviceHeight(hh, GE_INCHES, dd); |
| if (R_FINITE(xbl) && R_FINITE(ybl) && |
| R_FINITE(ww) && R_FINITE(hh)) { |
| /* The graphics engine only takes device coordinates |
| */ |
| GERaster(image, INTEGER(dim)[1], INTEGER(dim)[0], |
| xbl, ybl, ww, hh, rotationAngle, |
| LOGICAL(interpolate)[i % LENGTH(interpolate)], |
| &gc, dd); |
| } |
| UNPROTECT(2); |
| } |
| } |
| GEMode(0, dd); |
| vmaxset(vmax); |
| return R_NilValue; |
| } |
| |
| SEXP L_cap() |
| { |
| int i, col, row, nrow, ncol, size; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| int *rint; |
| SEXP raster; |
| /* The raster is R internal colours, so convert to |
| * R external colours (strings) |
| * AND the raster is BY ROW so need to rearrange it |
| * to be BY COLUMN (though the dimensions are correct) */ |
| SEXP image, idim; |
| |
| PROTECT(raster = GECap(dd)); |
| /* Non-complying devices will return NULL */ |
| if (isNull(raster)) { |
| image = raster; |
| } else { |
| size = LENGTH(raster); |
| nrow = INTEGER(getAttrib(raster, R_DimSymbol))[0]; |
| ncol = INTEGER(getAttrib(raster, R_DimSymbol))[1]; |
| |
| PROTECT(image = allocVector(STRSXP, size)); |
| rint = INTEGER(raster); |
| for (i=0; i<size; i++) { |
| col = i % ncol + 1; |
| row = i / ncol + 1; |
| SET_STRING_ELT(image, (col - 1)*nrow + row - 1, |
| mkChar(col2name(rint[i]))); |
| } |
| |
| PROTECT(idim = allocVector(INTSXP, 2)); |
| INTEGER(idim)[0] = nrow; |
| INTEGER(idim)[1] = ncol; |
| setAttrib(image, R_DimSymbol, idim); |
| |
| UNPROTECT(2); |
| } |
| UNPROTECT(1); |
| return image; |
| } |
| |
| /* |
| * Code to draw OR size text |
| * Combined to avoid code replication |
| */ |
| static SEXP gridText(SEXP label, SEXP x, SEXP y, SEXP hjust, SEXP vjust, |
| SEXP rot, SEXP checkOverlap, double theta, Rboolean draw) |
| { |
| int i, nx, ny; |
| double *xx, *yy; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP txt, result = R_NilValue; |
| double edgex, edgey; |
| double xmin = DOUBLE_XMAX; |
| double xmax = -DOUBLE_XMAX; |
| double ymin = DOUBLE_XMAX; |
| double ymax = -DOUBLE_XMAX; |
| /* |
| * Bounding rectangles for checking overlapping |
| * Initialised to shut up compiler |
| */ |
| LRect *bounds = NULL; |
| LRect trect; |
| int numBounds = 0; |
| int overlapChecking = LOGICAL(checkOverlap)[0]; |
| const void *vmax; |
| SEXP currentvp, currentgp; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| nx = unitLength(x); |
| ny = unitLength(y); |
| if (ny > nx) |
| nx = ny; |
| vmax = vmaxget(); |
| xx = (double *) R_alloc(nx, sizeof(double)); |
| yy = (double *) R_alloc(nx, sizeof(double)); |
| for (i=0; i<nx; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| /* |
| * If drawing, convert to INCHES on device |
| * If just calculating bounds, convert to INCHES within current vp |
| */ |
| if (draw) { |
| transformLocn(x, y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &(xx[i]), &(yy[i])); |
| } else { |
| xx[i] = transformXtoINCHES(x, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| yy[i] = transformYtoINCHES(y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| } |
| } |
| /* The label can be a string or an expression |
| */ |
| PROTECT(txt = label); |
| if (isSymbol(txt) || isLanguage(txt)) |
| txt = coerceVector(txt, EXPRSXP); |
| else if (!isExpression(txt)) |
| txt = coerceVector(txt, STRSXP); |
| UNPROTECT(1); |
| PROTECT(txt); |
| if (overlapChecking || !draw) { |
| bounds = (LRect *) R_alloc(nx, sizeof(LRect)); |
| } |
| /* |
| * Check we have any text to draw |
| */ |
| if (LENGTH(txt) > 0) { |
| int ntxt = 0; |
| if (draw) { |
| /* |
| * Drawing text |
| */ |
| GEMode(1, dd); |
| } |
| for (i=0; i<nx; i++) { |
| int doDrawing = 1; |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| /* |
| * Generate bounding boxes when checking for overlap |
| * or sizing text |
| */ |
| if (overlapChecking || !draw) { |
| int j = 0; |
| textRect(xx[i], yy[i], txt, i, &gc, |
| REAL(hjust)[i % LENGTH(hjust)], |
| REAL(vjust)[i % LENGTH(vjust)], |
| /* |
| * When calculating bounding rect for text |
| * only consider rotation of text within |
| * local context, not relative to device |
| * (so don't add rotationAngle) |
| */ |
| numeric(rot, i % LENGTH(rot)), |
| dd, &trect); |
| while (doDrawing && (j < numBounds)) |
| if (intersect(trect, bounds[j++])) |
| doDrawing = 0; |
| if (doDrawing) { |
| copyRect(trect, &(bounds[numBounds])); |
| numBounds++; |
| } |
| } |
| if (draw && doDrawing) { |
| /* The graphics engine only takes device coordinates |
| */ |
| xx[i] = toDeviceX(xx[i], GE_INCHES, dd); |
| yy[i] = toDeviceY(yy[i], GE_INCHES, dd); |
| if (R_FINITE(xx[i]) && R_FINITE(yy[i])) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| if (isExpression(txt)) |
| GEMathText(xx[i], yy[i], |
| VECTOR_ELT(txt, i % LENGTH(txt)), |
| REAL(hjust)[i % LENGTH(hjust)], |
| REAL(vjust)[i % LENGTH(vjust)], |
| numeric(rot, i % LENGTH(rot)) + |
| rotationAngle, |
| &gc, dd); |
| else |
| GEText(xx[i], yy[i], |
| CHAR(STRING_ELT(txt, i % LENGTH(txt))), |
| (gc.fontface == 5) ? CE_SYMBOL : |
| getCharCE(STRING_ELT(txt, i % LENGTH(txt))), |
| REAL(hjust)[i % LENGTH(hjust)], |
| REAL(vjust)[i % LENGTH(vjust)], |
| numeric(rot, i % LENGTH(rot)) + |
| rotationAngle, |
| &gc, dd); |
| } |
| } |
| if (!draw) { |
| double minx, maxx, miny, maxy; |
| /* |
| * Sizing text |
| */ |
| if (R_FINITE(xx[i]) && R_FINITE(yy[i])) { |
| minx = fmin2(trect.x1, |
| fmin2(trect.x2, |
| fmin2(trect.x3, trect.x4))); |
| if (minx < xmin) |
| xmin = minx; |
| maxx = fmax2(trect.x1, |
| fmax2(trect.x2, |
| fmax2(trect.x3, trect.x4))); |
| if (maxx > xmax) |
| xmax = maxx; |
| miny = fmin2(trect.y1, |
| fmin2(trect.y2, |
| fmin2(trect.y3, trect.y4))); |
| if (miny < ymin) |
| ymin = miny; |
| maxy = fmax2(trect.y1, |
| fmax2(trect.y2, |
| fmax2(trect.y3, trect.y4))); |
| if (maxy > ymax) |
| ymax = maxy; |
| /* |
| * Calculate edgex and edgey for case where this is |
| * the only rect |
| */ |
| { |
| double xxx[4], yyy[4]; |
| /* |
| * Must be in clock-wise order for polygonEdge |
| */ |
| xxx[0] = trect.x4; yyy[0] = trect.y4; |
| xxx[1] = trect.x3; yyy[1] = trect.y3; |
| xxx[2] = trect.x2; yyy[2] = trect.y2; |
| xxx[3] = trect.x1; yyy[3] = trect.y1; |
| polygonEdge(xxx, yyy, 4, theta, |
| &edgex, &edgey); |
| } |
| ntxt++; |
| } |
| } |
| } |
| if (draw) { |
| GEMode(0, dd); |
| } |
| if (ntxt > 0) { |
| result = allocVector(REALSXP, 4); |
| /* |
| * If there is more than one text, just produce edge |
| * based on bounding rect of all text |
| */ |
| if (ntxt > 1) { |
| /* |
| * Produce edge of rect bounding all text |
| */ |
| rectEdge(xmin, ymin, xmax, ymax, theta, |
| &edgex, &edgey); |
| } |
| /* |
| * Reverse the scale adjustment (zoom factor) |
| * when calculating physical value to return to user-level |
| */ |
| REAL(result)[0] = edgex / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[1] = edgey / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[2] = (xmax - xmin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[3] = (ymax - ymin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| } |
| } |
| vmaxset(vmax); |
| UNPROTECT(1); |
| return result; |
| } |
| |
| SEXP L_text(SEXP label, SEXP x, SEXP y, SEXP hjust, SEXP vjust, |
| SEXP rot, SEXP checkOverlap) |
| { |
| gridText(label, x, y, hjust, vjust, rot, checkOverlap, 0, TRUE); |
| return R_NilValue; |
| } |
| |
| /* |
| * Return four values representing boundary of text (which may consist |
| * of multiple pieces of text, unaligned, and/or rotated) |
| * in INCHES. |
| * |
| * Result is (xmin, xmax, ymin, ymax) |
| * |
| * Return NULL if no text to draw; R code will generate unit from that |
| */ |
| SEXP L_textBounds(SEXP label, SEXP x, SEXP y, |
| SEXP hjust, SEXP vjust, SEXP rot, SEXP theta) |
| { |
| SEXP checkOverlap = allocVector(LGLSXP, 1); |
| LOGICAL(checkOverlap)[0] = FALSE; |
| return gridText(label, x, y, hjust, vjust, rot, checkOverlap, |
| REAL(theta)[0], FALSE); |
| } |
| |
| SEXP L_points(SEXP x, SEXP y, SEXP pch, SEXP size) |
| { |
| int i, nx, npch, nss; |
| /* double *xx, *yy;*/ |
| double *xx, *yy, *ss; |
| int *ps; |
| int pType; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| double symbolSize; |
| const void *vmax; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| nx = unitLength(x); |
| npch = LENGTH(pch); |
| /* |
| * Need to take vector gpar elements into account that may affect unit size |
| * calculations |
| */ |
| nss = unitLength(size) * LENGTH(VECTOR_ELT(currentgp, GP_FONTSIZE)) * |
| LENGTH(VECTOR_ELT(currentgp, GP_CEX)) * |
| LENGTH(VECTOR_ELT(currentgp, GP_LINEHEIGHT)); |
| nss = nss > nx ? nx : nss; |
| /* Convert the x and y values to CM locations */ |
| vmax = vmaxget(); |
| xx = (double *) R_alloc(nx, sizeof(double)); |
| yy = (double *) R_alloc(nx, sizeof(double)); |
| for (i=0; i<nx; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| transformLocn(x, y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &(xx[i]), &(yy[i])); |
| /* The graphics engine only takes device coordinates |
| */ |
| xx[i] = toDeviceX(xx[i], GE_INCHES, dd); |
| yy[i] = toDeviceY(yy[i], GE_INCHES, dd); |
| } |
| ss = (double *) R_alloc(nss, sizeof(double)); |
| for (i=0; i < nss; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| ss[i] = transformWidthtoINCHES(size, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, dd); |
| ss[i] = toDeviceWidth(ss[i], GE_INCHES, dd); |
| } |
| ps = (int *) R_alloc(npch, sizeof(int)); |
| if (isString(pch)) pType = 0; |
| else if (isInteger(pch)) pType = 1; |
| else if (isReal(pch)) pType = 2; |
| else pType = 3; |
| for (i=0; i < npch; i++) { |
| switch (pType) { |
| case 0: |
| /* |
| * FIXME: |
| * Resolve any differences between this and FixupPch() |
| * in plot.c ? |
| */ |
| ps[i] = GEstring_to_pch(STRING_ELT(pch, i)); |
| break; |
| case 1: |
| ps[i] = INTEGER(pch)[i]; |
| break; |
| case 2: |
| ps[i] = R_FINITE(REAL(pch)[i]) ? (int) REAL(pch)[i] : NA_INTEGER; |
| break; |
| } |
| } |
| GEMode(1, dd); |
| for (i=0; i<nx; i++) |
| if (R_FINITE(xx[i]) && R_FINITE(yy[i])) { |
| /* FIXME: The symbols will not respond to viewport |
| * rotations !!! |
| */ |
| int ipch = NA_INTEGER /* -Wall */; |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| symbolSize = ss[i % nss]; |
| if (R_FINITE(symbolSize)) { |
| if (pType == 3) { |
| error(_("invalid plotting symbol")); |
| } |
| ipch = ps[i % npch]; |
| /* |
| * special case for pch = "." |
| */ |
| if (ipch == 46) symbolSize = gpCex(currentgp, i); |
| /* |
| * FIXME: |
| * For character-based symbols, we need to modify |
| * gc->cex so that the FONT size corresponds to |
| * the specified symbolSize. |
| */ |
| GESymbol(xx[i], yy[i], ipch, symbolSize, &gc, dd); |
| } |
| } |
| GEMode(0, dd); |
| vmaxset(vmax); |
| return R_NilValue; |
| } |
| |
| SEXP L_clip(SEXP x, SEXP y, SEXP w, SEXP h, SEXP hjust, SEXP vjust) |
| { |
| double xx, yy, ww, hh; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| LViewportContext vpc; |
| R_GE_gcontext gc; |
| LTransform transform; |
| SEXP currentvp, currentgp, currentClip; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| GEMode(1, dd); |
| /* |
| * Only set ONE clip rectangle (i.e., NOT vectorised) |
| */ |
| gcontextFromgpar(currentgp, 0, &gc, dd); |
| transformLocn(x, y, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd, |
| transform, |
| &xx, &yy); |
| ww = transformWidthtoINCHES(w, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| hh = transformHeighttoINCHES(h, 0, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| /* |
| * We can ONLY clip if the total rotation angle is zero. |
| */ |
| if (rotationAngle == 0) { |
| xx = justifyX(xx, ww, REAL(hjust)[0]); |
| yy = justifyY(yy, hh, REAL(vjust)[0]); |
| /* The graphics engine only takes device coordinates |
| */ |
| xx = toDeviceX(xx, GE_INCHES, dd); |
| yy = toDeviceY(yy, GE_INCHES, dd); |
| ww = toDeviceWidth(ww, GE_INCHES, dd); |
| hh = toDeviceHeight(hh, GE_INCHES, dd); |
| if (R_FINITE(xx) && R_FINITE(yy) && |
| R_FINITE(ww) && R_FINITE(hh)) { |
| GESetClip(xx, yy, xx + ww, yy + hh, dd); |
| /* |
| * ALSO set the current clip region for the |
| * current viewport so that, if a viewport |
| * is pushed within the current viewport, |
| * when that viewport gets popped again, |
| * the clip region returns to what was set |
| * by THIS clipGrob (NOT to the current |
| * viewport's previous setting) |
| */ |
| PROTECT(currentClip = allocVector(REALSXP, 4)); |
| REAL(currentClip)[0] = xx; |
| REAL(currentClip)[1] = yy; |
| REAL(currentClip)[2] = xx + ww; |
| REAL(currentClip)[3] = yy + hh; |
| SET_VECTOR_ELT(currentvp, PVP_CLIPRECT, currentClip); |
| UNPROTECT(1); |
| } |
| } else { |
| warning(_("unable to clip to rotated rectangle")); |
| } |
| GEMode(0, dd); |
| return R_NilValue; |
| } |
| |
| SEXP L_pretty(SEXP scale) { |
| double min = numeric(scale, 0); |
| double max = numeric(scale, 1); |
| double temp; |
| /* FIXME: This is just a dummy pointer because we do not have |
| * log scales. This will cause death and destruction if it is |
| * not addressed when log scales are added ! |
| */ |
| double *usr = NULL; |
| double axp[3]; |
| /* FIXME: Default preferred number of ticks hard coded ! */ |
| int n = 5; |
| Rboolean swap = min > max; |
| /* |
| * Feature: |
| * like R, something like xscale = c(100,0) just works |
| */ |
| if(swap) { |
| temp = min; min = max; max = temp; |
| } |
| |
| GEPretty(&min, &max, &n); |
| |
| if(swap) { |
| temp = min; min = max; max = temp; |
| } |
| |
| axp[0] = min; |
| axp[1] = max; |
| axp[2] = n; |
| /* FIXME: "log" flag hard-coded to FALSE because we do not |
| * have log scales yet |
| */ |
| return Rf_CreateAtVector(axp, usr, n, FALSE); |
| } |
| |
| /* |
| * NOTE: This does not go through the graphics engine, but |
| * skips straight to the device to obtain a mouse click. |
| * This is because I do not want to put a GELocator in the |
| * graphics engine; that would be a crappy long term solution. |
| * I will wait for a better event-loop/call-back solution before |
| * doing something with the graphics engine. |
| * This is a stop gap in the meantime. |
| * |
| * The answer is in INCHES |
| */ |
| |
| SEXP L_locator() { |
| double x = 0; |
| double y = 0; |
| SEXP answer; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| GEMode(2, dd); |
| PROTECT(answer = allocVector(REALSXP, 2)); |
| /* |
| * Get a mouse click |
| * Fails if user did not click mouse button 1 |
| */ |
| if(dd->dev->locator && dd->dev->locator(&x, &y, dd->dev)) { |
| REAL(answer)[0] = fromDeviceX(x, GE_INCHES, dd); |
| REAL(answer)[1] = fromDeviceY(y, GE_INCHES, dd); |
| } else { |
| REAL(answer)[0] = NA_REAL; |
| REAL(answer)[1] = NA_REAL; |
| } |
| GEMode(0, dd); |
| UNPROTECT(1); |
| return answer; |
| } |
| |
| /* |
| * **************************************** |
| * Calculating boundaries of primitives |
| * |
| * **************************************** |
| */ |
| |
| /* |
| * Return four values representing boundary of set of locations |
| * in INCHES. |
| * |
| * Result is (xmin, xmax, ymin, ymax) |
| * |
| * Used for lines, segments, polygons |
| */ |
| SEXP L_locnBounds(SEXP x, SEXP y, SEXP theta) |
| { |
| int i, nx, ny, nloc; |
| double *xx, *yy; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| SEXP result = R_NilValue; |
| const void *vmax; |
| double xmin = DOUBLE_XMAX; |
| double xmax = -DOUBLE_XMAX; |
| double ymin = DOUBLE_XMAX; |
| double ymax = -DOUBLE_XMAX; |
| double edgex, edgey; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| nx = unitLength(x); |
| ny = unitLength(y); |
| if (ny > nx) |
| nx = ny; |
| nloc = 0; |
| vmax = vmaxget(); |
| if (nx > 0) { |
| xx = (double *) R_alloc(nx, sizeof(double)); |
| yy = (double *) R_alloc(nx, sizeof(double)); |
| for (i=0; i<nx; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| xx[i] = transformXtoINCHES(x, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| yy[i] = transformYtoINCHES(y, i, vpc, &gc, |
| vpWidthCM, vpHeightCM, |
| dd); |
| /* |
| * Determine min/max x/y values |
| */ |
| if (R_FINITE(xx[i]) && R_FINITE(yy[i])) { |
| if (xx[i] < xmin) |
| xmin = xx[i]; |
| if (xx[i] > xmax) |
| xmax = xx[i]; |
| if (yy[i] < ymin) |
| ymin = yy[i]; |
| if (yy[i] > ymax) |
| ymax = yy[i]; |
| nloc++; |
| } |
| } |
| } |
| if (nloc > 0) { |
| hullEdge(xx, yy, nx, REAL(theta)[0], &edgex, &edgey); |
| result = allocVector(REALSXP, 4); |
| /* |
| * Reverse the scale adjustment (zoom factor) |
| * when calculating physical value to return to user-level |
| */ |
| REAL(result)[0] = edgex / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[1] = edgey / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[2] = (xmax - xmin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(result)[3] = (ymax - ymin) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| } |
| vmaxset(vmax); |
| return result; |
| } |
| |
| /* |
| * **************************************** |
| * Calculating text metrics |
| * |
| * **************************************** |
| */ |
| SEXP L_stringMetric(SEXP label) |
| { |
| int i, n; |
| double vpWidthCM, vpHeightCM; |
| double rotationAngle; |
| int gpIsScalar[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; |
| LViewportContext vpc; |
| R_GE_gcontext gc, gcCache; |
| LTransform transform; |
| SEXP currentvp, currentgp; |
| SEXP txt; |
| SEXP result = R_NilValue; |
| SEXP ascent = R_NilValue; |
| SEXP descent = R_NilValue; |
| SEXP width = R_NilValue; |
| const void *vmax; |
| double asc, dsc, wid; |
| /* Get the current device |
| */ |
| pGEDevDesc dd = getDevice(); |
| currentvp = gridStateElement(dd, GSS_VP); |
| currentgp = gridStateElement(dd, GSS_GPAR); |
| getViewportTransform(currentvp, dd, |
| &vpWidthCM, &vpHeightCM, |
| transform, &rotationAngle); |
| getViewportContext(currentvp, &vpc); |
| initGContext(currentgp, &gc, dd, gpIsScalar, &gcCache); |
| /* The label can be a string or an expression: is protected. |
| */ |
| txt = label; |
| if (isSymbol(txt) || isLanguage(txt)) |
| txt = coerceVector(txt, EXPRSXP); |
| else if (!isExpression(txt)) |
| txt = coerceVector(txt, STRSXP); |
| PROTECT(txt); |
| n = LENGTH(txt); |
| vmax = vmaxget(); |
| PROTECT(ascent = allocVector(REALSXP, n)); |
| PROTECT(descent = allocVector(REALSXP, n)); |
| PROTECT(width = allocVector(REALSXP, n)); |
| if (n > 0) { |
| for (i=0; i<n; i++) { |
| updateGContext(currentgp, i, &gc, dd, gpIsScalar, &gcCache); |
| if (isExpression(txt)) |
| GEExpressionMetric(VECTOR_ELT(txt, i % LENGTH(txt)), &gc, |
| &asc, &dsc, &wid, |
| dd); |
| else |
| GEStrMetric(CHAR(STRING_ELT(txt, i)), |
| getCharCE(STRING_ELT(txt, i)), &gc, |
| &asc, &dsc, &wid, |
| dd); |
| /* |
| * Reverse the scale adjustment (zoom factor) |
| * when calculating physical value to return to user-level |
| */ |
| REAL(ascent)[i] = fromDeviceHeight(asc, GE_INCHES, dd) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(descent)[i] = fromDeviceHeight(dsc, GE_INCHES, dd) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| REAL(width)[i] = fromDeviceWidth(wid, GE_INCHES, dd) / |
| REAL(gridStateElement(dd, GSS_SCALE))[0]; |
| } |
| } |
| PROTECT(result = allocVector(VECSXP, 3)); |
| SET_VECTOR_ELT(result, 0, ascent); |
| SET_VECTOR_ELT(result, 1, descent); |
| SET_VECTOR_ELT(result, 2, width); |
| vmaxset(vmax); |
| UNPROTECT(5); |
| return result; |
| } |
| |