diff --git a/DESCRIPTION b/DESCRIPTION index e184b67..e105fe9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -8,7 +8,7 @@ Authors@R: c(person("Paul", "Murrell", role = c("aut", "cre"), Description: Functions for performing polygon geometry with 'grid' grobs. This allows complex shapes to be defined by combining simpler shapes. -URL: https://github.com/pmur002/gridgeometry, https://stattech.wordpress.fos.auckland.ac.nz/2019/03/04/2019-01-a-geometry-engine-interface-for-grid/, https://stattech.blogs.auckland.ac.nz/2022/06/01/2022-02-constructive-geometry-for-complex-grobs +URL: https://github.com/pmur002/gridgeometry, https://stattech.wordpress.fos.auckland.ac.nz/2019/03/04/2019-01-a-geometry-engine-interface-for-grid/, https://stattech.blogs.auckland.ac.nz/2022/06/01/2022-02-constructive-geometry-for-complex-grobs/ Depends: R (>= 3.6.0), grid Imports: grDevices, polyclip (>= 1.10-0) Suggests: graphics, lattice diff --git a/R/minkowski.R b/R/minkowski.R index 6400db0..fe02f99 100644 --- a/R/minkowski.R +++ b/R/minkowski.R @@ -10,62 +10,84 @@ polyminkowski.default <- function(A, B, ...) { polyclip::polyminkowski(A, B, ...) } -polyminkowskiGrob <- function(A, B, reduceA, reduceB, ...) { - if (inherits(B, "gPath") || is.character(B)) { - B <- grid.get(B, ...) - } +polyminkowskiGridGrob <- function(A, B, closed, reduceA, reduceB, ...) { if (!(inherits(B, "grob") || inherits(B, "gList"))) stop("Argument 'B' must be a grob") polyA <- xyListFromGrob(A, op = reduceA, closed = TRUE, ...) - polyB <- xyListFromGrob(B, op = reduceB, closed = TRUE, ...) - polyclip::polyminkowski(polyA, polyB, ...) + polyB <- xyListFromGrob(B, op = reduceB, closed = closed, ...) + polyclip::polyminkowski(polyA, polyB, closed = closed, ...) } -polyminkowski.grob <- function(A, B, +polyminkowski.grob <- function(A, B, closed=isClosedShape(B), reduceA = "union", reduceB = "union", ...) { - polyminkowskiGrob(A, B, reduceA, reduceB, ...) + polyminkowskiGridGrob(A, B, closed, reduceA, reduceB, ...) } -polyminkowski.gList <- function(A, B, +polyminkowski.gList <- function(A, B, closed=isClosedShape(B), reduceA = "union", reduceB = "union", ...) { - polyminkowskiGrob(A, B, reduceA, reduceB, ...) + polyminkowskiGridGrob(A, B, closed, reduceA, reduceB, ...) } -polyminkowski.gPath <- function(A, B, +polyminkowski.gPath <- function(A, B, closed, strict=FALSE, grep=FALSE, global=FALSE, reduceA = "union", reduceB = "union", ...) { A <- grid.get(A, strict, grep, global) - polyminkowskiGrob(A, B, reduceA, reduceB, ...) + if (inherits(B, "gPath") || is.character(B)) { + B <- grid.get(B, ...) + } + if (missing(closed)) + closed <- isClosedShape(B) + polyminkowskiGridGrob(A, B, closed, reduceA, reduceB, ...) } -polyminkowski.character <- function(A, B, +polyminkowski.character <- function(A, B, closed, strict=FALSE, grep=FALSE, global=FALSE, reduceA = "union", reduceB = "union", ...) { A <- grid.get(A, strict, grep, global) - polyminkowskiGrob(A, B, reduceA, reduceB, ...) + if (inherits(B, "gPath") || is.character(B)) { + B <- grid.get(B, ...) + } + if (missing(closed)) + closed <- isClosedShape(B) + polyminkowskiGridGrob(A, B, closed, reduceA, reduceB, ...) } ################################################################################ ## High level grob interface makeContent.minkowskiGrob <- function(x) { - offsetpts <- do.call(polyminkowski, c(list(A=x$A, B=x$B), x$minkowskiArgs)) - setChildren(x, gList(xyListToPath(offsetpts))) + children <- vector("list", 2) + closedPaths <- do.call(polyminkowski, + c(list(A=x$A, B=x$B, closed=TRUE), + x$polyclipArgs)) + if (length(closedPaths)) { + children[[1]] <- x$grobFn(closedPaths, + name=paste0(x$name, ".closed")) + } + openPaths <- do.call(polyminkowski, + c(list(A=x$A, B=x$B, closed=FALSE), + x$polyclipArgs)) + if (length(openPaths)) { + children[[2]] <- x$grobFn(openPaths, + name=paste0(x$name, ".open")) + } + setChildren(x, do.call(gList, children[!is.null(children)])) } minkowskiGrob <- function(A, B, + grobFn=xyListToPath, name=NULL, gp=gpar(), ...) { if (!(grobArg(A) && grobArg(B))) stop("Invalid argument") - gTree(A=A, B=B, + gTree(A=A, B=B, grobFn=grobFn, polyclipArgs=list(...), gp=gp, name=name, cl="minkowskiGrob") } diff --git a/inst/NEWS.Rd b/inst/NEWS.Rd index 6e79a02..50e68da 100755 --- a/inst/NEWS.Rd +++ b/inst/NEWS.Rd @@ -5,7 +5,10 @@ \section{Changes in version 0.4-0}{ \itemize{ \item New functions \code{grid.polyoffset()} and - \code{grid.polylineoffset()}. + \code{grid.polylineoffset()} for generating offset regions. + + \item New function \code{grid.minkowski()} for generating + Minkowski sums of grobs. } } diff --git a/man/grid.minkowski.Rd b/man/grid.minkowski.Rd new file mode 100644 index 0000000..049f664 --- /dev/null +++ b/man/grid.minkowski.Rd @@ -0,0 +1,71 @@ +\name{grid.minkowski} +\alias{grid.minkowski} +\alias{minkowskiGrob} +\title{ + Generate Minkowski Sums of Grobs +} +\description{ + Given a polygonal \dfn{pattern} and a polygonal \dfn{path}, + generate the Minkowski Sum by adding the pattern to the path. +} +\usage{ +minkowskiGrob(A, B, + grobFn=xyListToPath, + name=NULL, gp=gpar(), ...) +grid.minkowski(A, B, ...) +} +\arguments{ + \item{A}{ + A grob, gList, or gTree, or a gPath or a character value + identifying a grob that has already been drawn. This is known as the + \dfn{pattern} grob. + } + \item{B}{ + A grob, gList, or gTree, or a gPath or a character value + identifying a grob that has already been drawn. This is known as the + \dfn{path} grob. + } + \item{grobFn}{ + The function that is used to create the final grob result. + Predefined options are: \code{\link{xyListToPath}}, + \code{\link{xyListToPolygon}}, and + \code{\link{xyListToLine}}. + } + \item{name}{ + A name for the resulting grob. + } + \item{gp}{ + Graphical parameter settings for the resulting grob. + } + \item{\dots}{ + For \code{minkowskiGrob}, arguments passed on to + \code{polyclip::polyminkowski}. + } +} +\details{ + Both \code{A} and \code{B} should not contain self-intersections, + though they can be non-convex. +} +\value{ + \code{minkowskiGrob} returns a gTree. + + \code{grid.minkowski} is only used for its side-effect of drawing + on the current graphics device. +} +\author{ + Jack Wong +} +\seealso{ + \code{\link{xyListToPath}}, + \code{\link{xyListToPolygon}}, + \code{\link{xyListToLine}}, + \code{\link{polyminkowski}} +} +\examples{ +pattern <- circleGrob(x = 0, y = 0, r = .1) +path <- rectGrob(width = 0.5, height = 0.5) +minkowski <- minkowskiGrob(pattern, path) +grid.draw(minkowski) +} +\keyword{ dplot } +\keyword{ aplot } diff --git a/man/polyminkowski.Rd b/man/polyminkowski.Rd new file mode 100644 index 0000000..686e5ab --- /dev/null +++ b/man/polyminkowski.Rd @@ -0,0 +1,89 @@ +\name{polyminkowski} +\alias{polyminkowski} +\alias{polyminkowski.grob} +\alias{polyminkowski.gList} +\alias{polyminkowski.gPath} +\alias{polyminkowski.character} +\title{ + Generate Minkowski Sums on Coordinates +} +\description{ + This function generates the Minkowski Sum of two sets of coordinates. +} +\usage{ +polyminkowski(A, B, ...) +\method{polyminkowski}{grob}(A, B, closed=isClosedShape(B), + reduceA = "union", + reduceB = "union", + ...) +\method{polyminkowski}{gList}(A, B, closed=isClosedShape(B), + reduceA = "union", + reduceB = "union", + ...) +\method{polyminkowski}{gPath}(A, B, closed, + strict=FALSE, grep=FALSE, global=FALSE, + reduceA = "union", + reduceB = "union", + ...) +\method{polyminkowski}{character}(A, B, closed, + strict=FALSE, grep=FALSE, global=FALSE, + reduceA = "union", + reduceB = "union", + ...) +} +\arguments{ + \item{A}{ + A set of coordinates describing a + \dfn{pattern} shape. + Or a grob, gList, or a gPath (or a character value) + identifying a grob that has already been drawn from which + coordinates are generated. + } + \item{B}{ + A set of coordinates describing a + \dfn{path} shape. + Or a grob, gList, or a gPath (or a character value) + identifying a grob that has already been drawn from which + coordinates are generated. + } + \item{closed}{ + A logical value indicating whether the \code{B} coordinates describe + a closed shape or an open shape. + } + \item{reduceA, reduceB}{ + A character value describing the operation to be used if either + \code{A} or \code{B} need to be reduced to a single set of + coordinates. One of + \code{"intersection"}, \code{"minus"}, \code{"union"}, or + \code{"xor"}, in which case \code{polyminkowski} is used to + reduce multiple shapes, or \code{"flatten"}, in which case + coordinates for all shapes are returned. + } + \item{strict, grep, global}{ + Arguments controlling the interpretation of the gPath + (passed to \code{grid.get}). + } + \item{\dots}{ + Arguments used by methods. + } +} +\details{ + The shape described by the pattern coordinates is added + to the shape described by the path coordinates. +} +\value{ + The result is a new set of coordinates. +} +\author{ + Paul Murrell +} +\seealso{ + \code{\link{grid.minkowski}} +} +\examples{ +c <- circleGrob(x=0, y=0, r=.1) +r <- rectGrob(width=.5, height=.5) +polyminkowski(c, r) +} +\keyword{ dplot } +\keyword{ aplot }