blob: 3b72ce3fd9625dc0464ed0f587a807bf28bddd4a [file] [log] [blame]
# File src/library/graphics/R/polygon.R
# Part of the R package, https://www.R-project.org
#
# Copyright 1995-2016 The R Core Team
# In part (C) 2001 Kevin Buhr
#
# 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.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
### polyhatch - a pure R implementation of polygon hatching
### Copyright (C) 2001 Kevin Buhr
### Provided to the R project for release under GPL.
### Original nice clean structure destroyed by Ross Ihaka
polygon <-
function(x, y = NULL, density = NULL, angle = 45,
border = NULL, col = NA, lty = par("lty"), ..., fillOddEven=FALSE)
{
## FIXME: remove this eventually
..debug.hatch <- FALSE
##-- FIXME: what if `log' is active, for x or y?
xy <- xy.coords(x, y, setLab = FALSE)
if (is.numeric(density) && all(is.na(density) | density < 0))
density <- NULL
if (!is.null(angle) && !is.null(density)) {
## hatch helper functions
polygon.onehatch <-
function(x, y, x0, y0, xd, yd, ..debug.hatch = FALSE, ...)
{
## draw the intersection of one line with polygon
##
## x,y - points of polygon (MUST have first and last points equal)
## x0,y0 - origin of line
## xd,yd - vector giving direction of line
## ... - other parameters to pass to "segments"
if (..debug.hatch) {
points(x0, y0)
arrows(x0, y0, x0 + xd, y0 + yd)
}
## halfplane[i] is 0 or 1 as (x[i], y[i]) lies in left or right
## half-plane of the line
halfplane <- as.integer(xd * (y - y0) - yd * (x - x0) <= 0)
## cross[i] is -1,0, or 1 as segment (x[i], y[i]) -- (x[i+1], y[i+1])
## crosses right-to-left, doesn't cross, or crosses left-to-right
cross <- halfplane[-1L] - halfplane[-length(halfplane)]
does.cross <- cross != 0
if (!any(does.cross)) return() # nothing to draw?
## calculate where crossings occur
x1 <- x[-length(x)][does.cross]; y1 <- y[-length(y)][does.cross]
x2 <- x[-1L][does.cross]; y2 <- y[-1L][does.cross]
## t[i] is "timepoint" on line at which segment (x1, y1)--(x2, y2)
## crosses such that (x0,y0) + t*(xd,yd) is point of intersection
t <- (((x1 - x0) * (y2 - y1) - (y1 - y0) * (x2 - x1))/
(xd * (y2 - y1) - yd * (x2 - x1)))
## sort timepoints along line
o <- order(t)
tsort <- t[o]
## we draw the part of line from t[i] to t[i+1] whenever it lies
## "inside" the polygon --- the definition of this depends on
## fillOddEven: if FALSE, we crossed
## unequal numbers of left-to-right and right-to-left polygon
## segments to get there. if TRUE, an odd number of crossings.
##
crossings <- cumsum(cross[does.cross][o])
if (fillOddEven) crossings <- crossings %% 2
drawline <- crossings != 0
## draw those segments
lx <- x0 + xd * tsort
ly <- y0 + yd * tsort
lx1 <- lx[-length(lx)][drawline]; ly1 <- ly[-length(ly)][drawline]
lx2 <- lx[-1L][drawline]; ly2 <- ly[-1L][drawline]
segments(lx1, ly1, lx2, ly2, ...)
}
polygon.fullhatch <-
function(x, y, density, angle, ..debug.hatch = FALSE, ...)
{
## draw the hatching for a given polygon
##
## x,y - points of polygon (need not have first and last points
## equal, but no NAs are allowed)
## density,angle - of hatching
## ... - other parameters to pass to "segments"
x <- c(x, x[1L])
y <- c(y, y[1L])
angle <- angle %% 180
if (par("xlog") || par("ylog")) {
warning("cannot hatch with logarithmic scale active")
return()
}
usr <- par("usr"); pin <- par("pin")
## usr coords per inch
upi <- c(usr[2L] - usr[1L], usr[4L] - usr[3L]) / pin
## handle "flipped" usr coords
if (upi[1L] < 0) angle <- 180 - angle
if (upi[2L] < 0) angle <- 180 - angle
upi <- abs(upi)
## usr-coords direction vector for hatching
xd <- cos(angle / 180 * pi) * upi[1L]
yd <- sin(angle / 180 * pi) * upi[2L]
## to generate candidate hatching lines for polygon.onehatch,
## we generate those lines necessary to cover the rectangle
## (min(x),min(y)) to (max(x),max(y)) depending on the
## hatching angle
## (Note: We choose hatch line origins such that the hatching,
## if extended outside polygon, would pass through usr-coordinate
## origin. This ensures that all hatching with same density,
## angle in figure will be aligned.)
if (angle < 45 || angle > 135) {
## first.x and last.x are x-coords of first and last points
## of rectangle to hit, as y-coord moves from bottom up
if (angle < 45) {
first.x <- max(x)
last.x <- min(x)
}
else {
first.x <- min(x)
last.x <- max(x)
}
## y.shift is vertical shift between parallel hatching lines
y.shift <- upi[2L] / density / abs(cos(angle / 180 * pi))
## choose line origin (of first line) to align hatching
## with usr origin
x0 <- 0
y0 <- floor((min(y) - first.x * yd / xd) / y.shift) * y.shift
## line origins above y.end won't hit figure
y.end <- max(y) - last.x * yd / xd
## hatch against all candidate lines
while (y0 < y.end) {
polygon.onehatch(x, y, x0, y0, xd, yd,
..debug.hatch=..debug.hatch,...)
y0 <- y0 + y.shift
}
}
else {
## first.y, last.y are y-coords of first and last points
## of rectangle to hit, as x-coord moves from left to right
if (angle < 90) {
first.y <- max(y)
last.y <- min(y)
}
else {
first.y <- min(y)
last.y <- max(y)
}
## x.shift is horizontal shift between parallel hatching lines
x.shift <- upi[1L] / density / abs(sin(angle / 180 * pi))
## choose line origin to align with usr origin
x0 <- floor((min(x) - first.y * xd / yd) / x.shift) * x.shift
y0 <- 0
## line origins to right of x.end won't hit figure
x.end <- max(x) - last.y * xd / yd
## hatch!
while (x0 < x.end) {
polygon.onehatch(x, y, x0, y0, xd, yd,
..debug.hatch=..debug.hatch,...)
x0 <- x0 + x.shift
}
}
}
## end of hatch helper functions
if (missing(col) || is.null(col)) {
col <- par("fg")
} else if (any(is.na(col))) {
col[is.na(col)] <- par("fg")
}
if (is.null(border)) border <- col
if (is.logical(border)) {
if (!is.na(border) && border) border <- col
else border <- NA
}
## process multiple polygons separated by NAs
start <- 1
ends <- c(seq_along(xy$x)[is.na(xy$x) | is.na(xy$y)], length(xy$x) + 1)
num.polygons <- length(ends)
col <- rep_len(col, num.polygons)
if(length(border))
border <- rep_len(border, num.polygons)
if(length(lty))
lty <- rep_len(lty, num.polygons)
if(length(density))
density <- rep_len(density, num.polygons)
angle <- rep_len(angle, num.polygons)
i <- 1L
for (end in ends) {
if (end > start) {
if(is.null(density) || is.na(density[i]) || density[i] < 0)
.External.graphics(C_polygon, xy$x[start:(end - 1)],
xy$y[start:(end - 1)],
col[i], NA, lty[i], ...)
else if (density[i] > 0) {
## note: if col[i]==NA, "segments" will fill with par("fg")
polygon.fullhatch(xy$x[start:(end - 1)],
xy$y[start:(end - 1)],
col = col[i], lty = lty[i],
density = density[i],
angle = angle[i],
..debug.hatch = ..debug.hatch, ...)
}
## compatible with C_polygon:
## only cycle through col, lty, etc. on non-empty polygons
i <- i + 1
}
start <- end + 1
}
.External.graphics(C_polygon, xy$x, xy$y, NA, border, lty, ...)
}
else {
if (is.logical(border)) {
if (!is.na(border) && border) border <- par("fg")
else border <- NA
}
.External.graphics(C_polygon, xy$x, xy$y, col, border, lty, ...)
}
invisible()
}
xspline <-
function(x, y = NULL, shape = 0, open = TRUE, repEnds = TRUE,
draw = TRUE, border = par("fg"), col = NA, ...)
{
xy <- xy.coords(x, y, setLab = FALSE)
s <- rep.int(shape, length(xy$x))
if(open) s[1L] <- s[length(x)] <- 0
invisible(.External.graphics(C_xspline, xy$x, xy$y, s, open, repEnds,
draw, col, border, ...))
}
polypath <-
function(x, y = NULL,
border = NULL, col = NA, lty = par("lty"),
rule = "winding", ...)
{
xy <- xy.coords(x, y, setLab = FALSE)
if (is.logical(border)) {
if (!is.na(border) && border) border <- par("fg")
else border <- NA
}
rule <- match(rule, c("winding", "evenodd"))
if (is.na(rule))
stop("Invalid fill rule for graphics path")
# Determine path components
breaks <- which(is.na(xy$x) | is.na(xy$y))
if (length(breaks) == 0) { # Only one path
.External.graphics(C_path, xy$x, xy$y,
as.integer(length(xy$x)), as.integer(rule),
col, border, lty, ...)
} else {
nb <- length(breaks)
lengths <- c(breaks[1] - 1,
diff(breaks) - 1,
length(xy$x) - breaks[nb])
.External.graphics(C_path, xy$x[-breaks], xy$y[-breaks],
as.integer(lengths), as.integer(rule),
col, border, lty, ...)
}
invisible()
}