#' Simple ellipse function
#'
#' @family venndir polygons
#'
#' @export
simple_ellipse <- function
(h,
k,
a,
b=a,
phi=0,
n=200L)
{
theta <- seq.int(0, 2 * pi, length.out = n)
m <- length(h)
out <- vector("list", m)
for (i in seq_along(h)) {
out[[i]]$x <- h[i] + a[i] * cos(theta) * cos(phi[i]) -
b[i] * sin(theta) * sin(phi[i])
out[[i]]$y <- k[i] + b[i] * sin(theta) * cos(phi[i]) +
a[i] * cos(theta) * sin(phi[i])
}
out
}
#' Convert eulerr output to JamPolygon
#'
#' @returns `JamPolygon` object
#'
#' @family JamPolygon
#'
#' @export
eulerr_to_JamPolygon <- function
(x)
{
# x <- va
ellipses1 <- simple_ellipse(h=x$ellipses$h,
k=x$ellipses$k,
a=x$ellipses$a,
b=x$ellipses$b,
phi=x$ellipses$phi);
names(ellipses1) <- rownames(x$ellipses);
# convert polygon_list to JamPolygon
df <- data.frame(check.names=FALSE,
name=names(ellipses1),
x=I(lapply(ellipses1, function(i){i$x})),
y=I(lapply(ellipses1, function(i){i$y})));
jp <- new("JamPolygon",
polygons=df);
return(jp);
}
#' Find Venn polygon overlaps
#'
#' Find Venn polygon overlaps
#'
#' This function takes a named list of polygons
#' and returns the combination of polygon overlaps
#' as used in a Venn diagram.
#'
#' When a vector of Venn counts is supplied, the
#' counts are associated with the respective polygon,
#' and any counts not represented by a polygon
#' are returned as an attribute `"venn_missing"`.
#'
#' @returns `JamPolygon` object, which contains columns:
#' * `"name"`
#' * `"x"`, `"y"`
#' * `"fill"`
#' * `"venn_name"`
#' * `"venn_count"`
#' * `"venn_items"`
#' * `"venn_color"`
#' * `"label"`
#' * `"label_x"`
#' * `"label_y"`
#'
#' @family JamPolygon
#'
#' @param jp `JamPolygon` that contains one polygon per set, named
#' using set names.
#' @param venn_counts `vector` with `integer` values, whose names
#' represent each Venn overlap set combination, using
#' `sep` as delimiter between set names.
#' @param venn_items `list` or `NULL` that contains items in each
#' overlap set.
#' @param sep `character` string used as a delimiter between set names.
#' @param preset,blend_preset `character` string passed to
#' `colorjam::rainbowJam()` and `colorjam::blend_colors()`,
#' respectively, to define the color hue wheel used for categorical
#' colors, and for color blending. The default `preset="dichromat2"`
#' chooses color-blindness-friendly categorical colors, and
#' `blend_preset="ryb"` blends multiple colors using a red-yellow-blue
#' color wheel, consistent with paint-type color combinations.
#' @param do_plot `logical` indicating whether to plot the output
#' `SpatialPolygonsDataFrame` object.
#' @param verbose `logical` indicating whether to print verbose output.
#' @param ... additional arguments are passed to supporting functions
#' `colorjam::group2colors()`, `colorjam::blend_colors()`, `nudge_sp()`.
#'
#' @examples
#' # simple Venn circles
#' test_counts <- c(A=5, B=10, C=3, `B&C`=2)
#' x <- eulerr::euler(test_counts)
#' jp1 <- eulerr_to_JamPolygon(x)
#' polygon_colors <- colorjam::rainbowJam(length(jp1))
#' jp1@polygons$fill <- polygon_colors;
#' plot(jp1)
#'
#' xo <- find_venn_overlaps_JamPolygon(jp=jp1, venn_counts=test_counts)
#' xo@polygons$outerborder <- jamba::makeColorDarker(darkFactor=1.2,
#' xo@polygons$venn_color)
#' xo@polygons$outerborder.lwd <- 4;
#' plot(xo);
#'
#' @export
# find_venn_polygon_list_overlaps <- function
find_venn_overlaps_JamPolygon <- function
(jp,
venn_counts=NULL,
venn_items=NULL,
venn_colors=NULL,
sep="&",
preset="dichromat2",
blend_preset="ryb",
do_plot=FALSE,
verbose=FALSE,
...)
{
# check input class
if (length(jp) == 0) {
stop("Either polygon_list or jp must be supplied.")
}
if (!inherits(jp, "JamPolygon")) {
stop("jp must be supplied as JamPolygon");
}
# determine number of sets and define colors
numSets <- length(jp);
if (length(venn_colors) == 0) {
if ("fill" %in% colnames(jp@polygons)) {
venn_colors <- jamba::nameVector(jp@polygons$fill, names(jp));
} else {
venn_colors <- colorjam::group2colors(names(jp),
preset=preset,
...);
}
}
if (length(venn_colors) != length(jp)) {
venn_colors <- rep(venn_colors, length.out=length(jp));
}
if (length(names(venn_colors)) == 0) {
names(venn_colors) <- names(jp);
}
## define incidence matrix of overlaps
el1 <- make_venn_combn_df(names(jp),
sep=sep);
if (verbose) {
jamba::printDebug("find_venn_overlaps_JamPolygon(): ",
"names(jp):", names(jp));
jamba::printDebug("find_venn_overlaps_JamPolygon(): ",
"head(el1):");
print(head(el1, 30));
}
if (length(venn_counts) > 0) {
venn_counts_names <- strsplit(names(venn_counts),
fixed=TRUE,
sep);
}
if (length(venn_items) > 0) {
venn_items_names <- strsplit(names(venn_items),
fixed=TRUE,
sep);
}
## calculate venn overlap polygons
venn_poly_coords <- lapply(seq_len(nrow(el1)), function(j){
i <- el1[j,];
if (verbose) {
jamba::printDebug("find_venn_overlaps_JamPolygon(): ",
rownames(el1)[j]);
}
whichYes <- which(i %in% 1);
whichNo <- which(i %in% 0);
i_names <- colnames(el1)[whichYes];
venn_color <- colorjam::blend_colors(venn_colors[i_names],
preset=blend_preset,
...);
border <- jamba::makeColorDarker(venn_color,
darkFactor=1.3);
poly_name <- paste(
#jamba::mixedSort(colnames(el1)[whichYes]),
(colnames(el1)[whichYes]),
collapse=sep);
if (length(venn_counts) > 0) {
venn_match <- match_list(list(i_names), venn_counts_names);
if (is.na(venn_match)) {
venn_poly_count <- 0;
} else {
venn_poly_count <- venn_counts[[venn_match]];
}
} else {
venn_poly_count <- 0;
}
if (length(venn_items) > 0) {
match_list(list(i_names), venn_counts_names)
venn_match <- match_list(list(i_names), venn_items_names);
if (is.na(venn_match)) {
venn_poly_items <- character(0);
} else {
venn_poly_items <- venn_items[[venn_match]];
}
} else {
venn_poly_items <- character(0);
}
if (verbose) {
jamba::printDebug("find_venn_overlaps_JamPolygon(): ",
"whichYes:", whichYes);
jamba::printDebug("find_venn_overlaps_JamPolygon(): ",
"whichNo:", whichNo);
}
## Intersection of relevant sets
if (length(whichYes) >= 1) {
if (verbose) {
jamba::printDebug("find_venn_overlaps_JamPolygon(): ",
"intersect_JamPolygon(jp[whichYes, ]), whichYes: ", whichYes);
# print(jp[whichYes, ]);# debug
}
ellYes <- intersect_JamPolygon(jp[whichYes, ]);
}
if (length(ellYes) == 0) {
ellUse <- list();
} else {
if (length(whichNo) >= 1) {
if (verbose) {
jamba::printDebug("find_venn_overlaps_JamPolygon(): ",
"minus_JamPolygon(jp[whichNo, ]), whichNo: ", whichNo);
# print(jp[whichNo, ]);# debug
}
ellUse <- minus_JamPolygon(rbind2(ellYes, jp[whichNo, ]));
} else {
ellNo <- NULL;
ellUse <- ellYes;
}
if (length(ellUse) > 0) {
names(ellUse) <- poly_name;
} else {
ellUse <- list();
}
}
# store into the data.frame
ellUse@polygons$venn_name <- poly_name;
ellUse@polygons$venn_counts <- venn_poly_count;
ellUse@polygons$venn_items <- I(list(venn_poly_items));
ellUse@polygons$venn_color <- venn_color;
ellUse@polygons$border <- NA;
ellUse@polygons$border.lwd <- 1;
ellUse@polygons$outerborder <- NA;
ellUse@polygons$outerborder.lwd <- 1;
ellUse@polygons$innerborder <- border;
ellUse@polygons$innerborder.lwd <- 1;
ellUse@polygons$fill <- venn_color;
# attr(ellUse, "venn_name") <- poly_name;
# attr(ellUse, "venn_count") <- venn_poly_count;
# attr(ellUse, "venn_items") <- venn_poly_items;
# attr(ellUse, "venn_color") <- venn_color;
if (verbose) {
jamba::printDebug("find_venn_overlaps_JamPolygon(): ",
"length(overlap polygon):", lengths(ellUse));
}
# jamba::printDebug("ellYes:");print(ellYes);# debug
# jamba::printDebug("ellUse:");print(ellUse);# debug
ellUse;
})
# venn_poly_coords <- do.call(rbind2, venn_poly_coords);
# jamba::printDebug("venn_poly_coords:");print(venn_poly_coords);# debug
venn_poly_coords <- rbind2.JamPolygon(venn_poly_coords);
# Note venn_poly_coords is JamPolygon
venn_poly_coords@polygons$name <- rownames(el1);
rownames(venn_poly_coords@polygons) <- names(venn_poly_coords);
venn_poly_colors <- venn_poly_coords@polygons$venn_color;
# venn_poly_counts <- venn_poly_coords@polygons$venn_count;
venn_poly_items <- venn_poly_coords@polygons$venn_items;
vennUse <- which(lengths(venn_poly_coords@polygons$x) > 0);
vennMissing <- which(lengths(venn_poly_coords@polygons$x) == 0);
if (verbose) {
jamba::printDebug("find_vennpoly_overlaps(): ",
"vennUse:", vennUse,
", vennMissing:", vennMissing);
}
venn_poly_coords@polygons$label <- names(venn_poly_coords);
# Define label position for each polygon
label_xy <- labelr_JamPolygon(venn_poly_coords);
venn_poly_coords@polygons[, c("label_x", "label_y")] <- as.data.frame(
label_xy);
return(invisible(venn_poly_coords));
}
#' Intersect one or more JamPolygon objects
#'
#' Intersect one or more JamPolygon objects
#'
#' @family JamPolygon
#'
#' @returns `JamPolygon` after applying the intersection
#'
#' @param jp `JamPolygon`
#' @param new_name `character` string used to populate the `"name"`
#' column in the output data. Only the first value is used,
#' otherwise the first existing value in `names(jp)` is kept.
#' @param ... additional arguments are ignored.
#'
#' @examples
#'
#' @export
intersect_JamPolygon <- function
(jp,
new_name=NULL,
...)
{
# Purpose is to use polyclip::polyclip(A, B, op="intersect")
# on two or more polygons
if (length(jp) == 1) {
return(jp)
}
# if any polygons are empty, return empty
blank_jp <- function(jp, new_name=NULL){
jp <- jp[1, ];
jp@polygons$x <- I(list(numeric(0)));
jp@polygons$y <- I(list(numeric(0)));
if (length(new_name) > 0) {
names(jp) <- head(new_name, 1);
}
return(jp);
}
if (any(lengths(jp) == 0)) {
return(blank_jp(jp, new_name))
}
Ax <- jp@polygons$x[[1]];
Ay <- jp@polygons$y[[1]];
if (length(jamba::rmNA(unlist(Ax))) == 0) {
# if polygon is empty, return empty
return(blank_jp(jp, new_name))
}
if (!is.list(Ax)) {
Ax <- list(Ax);
Ay <- list(Ay);
}
A <- lapply(seq_along(Ax), function(i){
list(x=Ax[[i]], y=Ay[[i]])
})
pseq <- seq(from=2, to=nrow(jp@polygons));
for (i in pseq) {
Bx <- jp@polygons$x[[i]];
By <- jp@polygons$y[[i]];
if (length(jamba::rmNA(unlist(Ax))) == 0) {
# if polygon is empty, return empty
return(blank_jp(jp, new_name))
}
if (!is.list(Bx)) {
Bx <- list(Bx);
By <- list(By);
}
B <- lapply(seq_along(Bx), function(i){
list(x=Bx[[i]], y=By[[i]])
})
A <- polyclip::polyclip(A=A,
B=B,
op="intersection")
if (length(A) == 0 || any(lengths(A) == 0)) {
# if result is empty, return empty jp
return(blank_jp(jp, new_name))
}
}
jp <- jp[1, ];
# Todo:
# - check what happens when return contains multiple polygons
newx <- lapply(A, function(i){i$x});
newy <- lapply(A, function(i){i$y});
if (length(newx) == 0) {
newx <- list(NULL);
newy <- list(NULL);
} else if (length(newx) > 1) {
# handle multipart polygons
newx <- list(newx);
newy <- list(newy);
}
jp@polygons$x <- I(newx);
jp@polygons$y <- I(newy);
if (length(new_name) > 0) {
names(jp) <- head(new_name, 1);
}
return(jp);
}
#' Union one or more JamPolygon objects
#'
#' Union one or more JamPolygon objects
#'
#' @family JamPolygon
#'
#' @returns `JamPolygon` after applying the union
#'
#' @param jp `JamPolygon`
#' @param new_name `character` string to define optional `names(jp)` of
#' the output. Otherwise it uses the first name in `jp`.
#' @param verbose `logical` indicating whether to print verbose output.
#' @param ... additional arguments are ignored.
#'
#' @examples
#' df3 <- data.frame(name=c("polygon1", "polygon2"),
#' label=c("polygon1", "polygon2"),
#' x=I(list(
#' list(c(1, 6, 6, 1),
#' c(2, 5, 5, 2),
#' c(3, 4, 4, 3)),
#' list(#c(11, 16, 16, 11),
#' c(12, 15, 15, 12),
#' c(13, 14, 14, 13))
#' )),
#' y=I(list(
#' list(c(1, 1, 6, 6),
#' c(2, 2, 5, 5),
#' c(3, 3, 4, 4)),
#' list(#c(1, 1, 6, 6),
#' c(2, 2, 5, 5),
#' c(3, 3, 4, 4))
#' )),
#' fill=c("gold", "firebrick"))
#' jp3 <- new("JamPolygon", polygons=df3);
#' plot(jp3);
#'
#' jp3b <- union_JamPolygon(jp3, new_name="polygons 1,2")
#' plot(jp3b)
#'
#' # test empty polygon
#' jp3na <- jp3;
#' jp3na@polygons[2, "x"] <- I(list(NA))
#' jp3na@polygons[2, "y"] <- I(list(NA))
#' jp3na
#' union_JamPolygon(jp3na[1,])
#' union_JamPolygon(jp3na)
#'
#' @export
union_JamPolygon <- function
(jp,
new_name=NULL,
verbose=FALSE,
...)
{
# Purpose is to use polyclip::polyclip(A, B, op="union")
# on two or more polygons
if (nrow(jp@polygons) == 1) {
return(jp)
}
# start with first non-empty polygon
for (i in seq_len(length(jp))) {
Ax <- jp@polygons$x[[i]];
Ay <- jp@polygons$y[[i]];
if (length(jamba::rmNA(unlist(Ax))) > 0) {
break;
}
}
if (length(jamba::rmNA(unlist(jp@polygons$x))) == 0) {
# if all polygons are empty, return first entry
if (verbose) {
jamba::printDebug("union_JamPolygon(): ",
"empty input.")
}
return(jp[1, ]);
}
if (!is.list(Ax)) {
Ax <- list(Ax);
Ay <- list(Ay);
}
A <- lapply(seq_along(Ax), function(i){
list(x=Ax[[i]], y=Ay[[i]])
})
pseq <- tail(seq_len(length(jp)), -i);
for (i in pseq) {
Bx <- jp@polygons$x[[i]];
By <- jp@polygons$y[[i]];
if (length(jamba::rmNA(unlist(Bx))) == 0) {
# if B is empty, keep A
next;
}
if (!is.list(Bx)) {
Bx <- list(Bx);
By <- list(By);
}
B <- lapply(seq_along(Bx), function(i){
list(x=Bx[[i]], y=By[[i]])
})
A <- polyclip::polyclip(A=A,
B=B,
op="union")
# if (length(A) == 0) {
# break;
# }
}
jp <- jp[1, ];
newx <- lapply(A, function(i){i$x});
newy <- lapply(A, function(i){i$y});
if (length(newx) == 0) {
newx <- list(NULL);
newy <- list(NULL);
} else if (length(newx) > 1) {
# handle multipart polygons
newx <- list(newx);
newy <- list(newy);
}
jp@polygons$x <- I(newx);
jp@polygons$y <- I(newy);
if (length(new_name) > 0) {
names(jp) <- head(new_name, 1);
}
return(jp);
}
#' Subtract one or more JamPolygon objects
#'
#' Subtract one or more JamPolygon objects
#'
#' @family JamPolygon
#'
#' @returns `JamPolygon` after applying the subtraction
#'
#' @param closed `logical` indicating whether the first polygon
#' is considered a closed polygon, or when `closed=FALSE`
#' only the line is maintained.
#'
#' @export
minus_JamPolygon <- function
(jp,
new_name=NULL,
closed=TRUE,
...)
{
# Purpose is to use polyclip::polyclip(A, B, op="minus")
# on two or more polygons
if (nrow(jp@polygons) == 1) {
return(jp)
}
# if first polygon is already empty, return first entry
if (0 %in% head(lengths(jp), 1)) {
return(jp[1, ])
}
Ax <- jp@polygons$x[[1]];
Ay <- jp@polygons$y[[1]];
if (!is.list(Ax)) {
Ax <- list(Ax);
Ay <- list(Ay);
}
A <- lapply(seq_along(Ax), function(i){
list(x=Ax[[i]], y=Ay[[i]])
})
pseq <- seq(from=2, to=nrow(jp@polygons));
for (i in pseq) {
Bx <- jp@polygons$x[[i]];
By <- jp@polygons$y[[i]];
if (length(jamba::rmNA(unlist(Bx))) == 0) {
# if polygon is empty, there is nothing to subtract
next;
}
if (!is.list(Bx)) {
Bx <- list(Bx);
By <- list(By);
}
B <- lapply(seq_along(Bx), function(i){
list(x=Bx[[i]], y=By[[i]])
})
A <- polyclip::polyclip(A=A,
B=B,
closed=closed,
op="minus")
if (0 %in% head(lengths(A), 1)) {
break;
}
}
jp <- jp[1, ];
newx <- lapply(A, function(i){i$x});
newy <- lapply(A, function(i){i$y});
if (length(newx) == 0) {
newx <- list(NULL);
newy <- list(NULL);
} else if (length(newx) > 1) {
# handle multipart polygons
newx <- list(newx);
newy <- list(newy);
}
jp@polygons$x <- I(newx);
jp@polygons$y <- I(newy);
if (length(new_name) == 1) {
jp@polygons$name <- new_name;
}
return(jp);
}
#' Make polygon_list circles
#'
#' Make polygon_list circles
#'
#' This function creates one or more circles as polygon_list `list` objects.
#'
#' @family JamPolygon
#'
#' @returns `JamPolygon` object
#'
#' @param xcenter,ycenter `numeric` vector that defines the x and y
#' coordinate position of the center of each circle.
#' @param setnames `vector` that contains names for each circle, stored
#' as `names()` for each polygon.
#' When `setnames` is `NULL` then index numbers are used.
#' @param radius `numeric` vector that defines the radius of each circle.
#' This `vector` is recycled to `length(xcenter)`.
#' @param n `integer` value indicating the number of subdivisions to
#' use in the circle.
#' @param ... additional arguments are ignored.
#'
#' @examples
#' circle_jp <- polygon_circles(c(3, 2), c(2, 3))
#' plot(circle_jp, fill=c("red", "gold"))
#'
#' @export
polygon_circles <- function
(xcenter,
ycenter,
setnames=NULL,
radius=1,
n=60,
...)
{
angle_seq <- head(
seq(from=0,
to=pi*2,
length.out=n+1),
n);
if (length(setnames) == 0) {
setnames <- as.character(seq_along(xcenter));
}
xvals <- sin(angle_seq);
yvals <- cos(angle_seq);
if (length(radius) == 0) {
radius <- 1;
}
radius <- rep(radius,
length.out=length(xcenter));
x_seq <- jamba::nameVector(seq_along(xcenter),
setnames);
polygon_list <- lapply(x_seq, function(i){
list(
x=xvals * radius[i] + xcenter[i],
y=yvals * radius[i] + ycenter[i])
})
cjp <- rbind2.JamPolygon(lapply(polygon_list, function(i){
polyclip_to_JamPolygon(A=list(i), ...)
}))
names(cjp) <- setnames;
rownames(cjp@polygons) <- setnames;
return(cjp);
}
#' Make polygon_list ellipses
#'
#' Make polygon_list ellipses
#'
#' This function creates one or more ellipses as polygon_list `list` objects.
#'
#' @family JamPolygon
#'
#' @returns `JamPolygon` object
#'
#' @param xcenter,ycenter `numeric` vector that defines the x and y
#' coordinate position of the center of each ellipse.
#' @param setnames `vector` that contains names for each circle, stored
#' as `names()` for each polygon.
#' When `setnames` is `NULL` then index numbers are used.
#' @param xradius,yradius `numeric` vector that defines the radius
#' of each ellipse along the x-axis and y-axis, respectively.
#' Each `vector` is recycled to `length(xcenter)`.
#' @param rotation_degree `numeric` vector representing degrees to
#' rotate each ellipse after it is created, where values are
#' conformed to between `0` and `360`, rotating clockwise.
#' @param n `integer` value indicating the number of subdivisions to
#' use in the circle.
#' @param ... additional arguments are ignored.
#'
#' @examples
#' ejp <- polygon_ellipses(c(3, 2), c(2, 3),
#' xradius=c(1, 4),
#' yradius=c(5, 2))
#' plot(ejp, fill=c("#FF000077", "#FFDD0077"))
#'
#' @export
polygon_ellipses <- function
(xcenter,
ycenter,
setnames=NULL,
xradius=1,
yradius=2,
rotation_degrees=c(0),
n=60,
...)
{
angle_seq <- head(
seq(from=0,
to=pi*2,
length.out=n+1),
n);
if (length(setnames) == 0) {
setnames <- as.character(seq_along(xcenter));
}
xvals <- sin(angle_seq);
yvals <- cos(angle_seq);
if (length(xradius) == 0) {
xradius <- 1;
}
if (length(yradius) == 0) {
yradius <- 2;
}
xradius <- rep(xradius,
length.out=length(xcenter));
yradius <- rep(yradius,
length.out=length(xcenter));
if (length(rotation_degrees) == 0) {
rotation_degrees <- 0;
}
rotation_degrees <- rep(rotation_degrees,
length.out=length(xcenter));
rotation_rad <- jamba::deg2rad(rotation_degrees);
x_seq <- jamba::nameVector(seq_along(xcenter),
setnames);
polygon_list <- lapply(x_seq, function(i){
i_xvals <- (xvals * xradius[i]);
i_yvals <- (yvals * yradius[i]);
e_xvals <- (i_xvals * cos(rotation_rad[i]) +
i_yvals * sin(rotation_rad[i]));
e_yvals <- (i_yvals * cos(rotation_rad[i]) -
i_xvals * sin(rotation_rad[i]));
list(
x=e_xvals + xcenter[i],
y=e_yvals + ycenter[i])
})
cjp <- rbind2.JamPolygon(lapply(polygon_list, function(i){
polyclip_to_JamPolygon(A=list(i), ...)
}))
names(cjp) <- setnames;
rownames(cjp@polygons) <- setnames;
return(cjp);
}
#' Nudge JamPolygon coordinates
#'
#' Nudge JamPolygon coordinates
#'
#' Polygon coordinates within a `JamPolygon` object are nudged by name
#' or polygon number, such that all parts of each polygon are adjusted
#' together. For multi-part polygons, and/or polygons with internal holes,
#' all parts are moved the identical amount.
#'
#' @family JamPolygon
#'
#' @param jp `JamPolygon` object
#' @param nudge `list` whose names match `names(jp)`, containing `numeric`
#' vector with names `"x"` and `"y"`. For example:
#' `nudge=list(polyname1=c(x=1, y=0))`
#' @param ... additional arguments are ignored
#'
#' @examples
#' DEdf <- data.frame(check.names=FALSE,
#' name=c("D", "E"),
#' x=I(list(
#' c(-3, 3, 3, 0, -3),
#' c(-4, 2, 2, -4))),
#' y=I(list(
#' c(-3, -3, 1.5, 4, 1.5),
#' c(-2, -2, 4, 4))),
#' fill=c("#FFD70055", "#B2222255"))
#' DEjp <- new("JamPolygon", polygons=DEdf)
#' plot(DEjp)
#' nudge <- list(D=c(7, 1), E=c(-1, -1));
#' DEjp_nudged <- nudge_JamPolygon(DEjp, nudge=nudge)
#' plot(DEjp_nudged)
#'
#' plot(rbind2(DEjp, DEjp_nudged),
#' fill=c("#FFD70055", "#B2222255", "gold", "firebrick"),
#' label=c("D_old", "E_old", "D_new", "E_new"),
#' border.lty=c(2, 2, 1, 1))
#'
#' @export
nudge_JamPolygon <- function
(jp,
nudge=NULL,
rotate_degrees=0,
center=NULL,
verbose=FALSE,
...)
{
#
if (length(jp) == 0) {
return(jp)
}
if (length(rotate_degrees) == 0) {
rotate_degrees <- 0;
}
rotate_degrees <- head(rotate_degrees, 1) %% 360;
if (length(nudge) == 0 && (0 %in% rotate_degrees)) {
return(jp)
}
## rotate polygons
if (rotate_degrees != 0) {
if (!length(center) == 2) {
use_bbox <- bbox_JamPolygon(jp);
center <- c(x=mean(use_bbox[1, 1:2]),
y=mean(use_bbox[2, 1:2]));
}
for (irow in seq_len(nrow(jp@polygons))) {
use_x <- jp@polygons$x[[irow]];
use_y <- jp@polygons$y[[irow]];
if (!is.list(use_x)) {
use_x <- list(use_x);
use_y <- list(use_y);
}
use_x_v <- unlist(use_x);
use_y_v <- unlist(use_y);
if (length(names(use_x)) > 0) {
use_split <- rep(jamba::makeNames(names(use_x)),
lengths(use_x));
use_split <- factor(use_split,
levels=jamba::makeNames(names(use_x)))
} else {
use_split <- rep(as.character(seq_along(use_x)),
lengths(use_x));
use_split <- factor(use_split,
levels=as.character(seq_along(use_x)))
}
new_xy <- rescale_coordinates(
x=cbind(x=use_x_v, y=use_y_v),
rotate_degrees=rotate_degrees,
scale=c(1, 1),
center=center)
new_x_v <- new_xy[, 1];
new_y_v <- new_xy[, 2];
new_x <- split(new_x_v, use_split);
new_y <- split(new_y_v, use_split);
if (length(names(use_x)) == 0) {
new_x <- unname(new_x);
new_y <- unname(new_y);
}
if (!is.list(use_x)) {
new_x <- unname(new_x[[1]]);
new_y <- unname(new_y[[1]]);
}
jp@polygons$x[[irow]] <- new_x;
jp@polygons$y[[irow]] <- new_y;
}
}
## nudge polygons by name
if (length(nudge) > 0) {
if (length(names(nudge)) == 0) {
stop("There must be names(nudge).")
}
if (length(names(nudge)) == 0 && !any(names(nudge) %in% names(jp))) {
# check for numeric names
nudge_names <- as.integer(names(nudge));
use_nudge <- (!is.na(nudge_names) &
nudge_names == as.numeric(names(nudge)) &
nudge_names %in% seq_along(jp))
if (!all(use_nudge)) {
stop("names(nudge) must match names(jp) or seq_along(jp)")
}
nudge_names <- names(jp)[nudge_names]
} else {
nudge_names <- intersect(names(nudge), names(jp))
}
# custom function to apply nudge to nested numeric list
apply_nudge <- function(i, offset) {
if (is.list(i)) {
lapply(i, function(j){
apply_nudge(j, offset)
})
} else {
i + offset
}
}
for (nudge_name in nudge_names) {
n <- match(nudge_name, names(jp));
if (all(c("x", "y") %in% names(nudge[[nudge_name]]))) {
nudge_x <- nudge[[nudge_name]][["x"]];
nudge_y <- nudge[[nudge_name]][["y"]];
} else {
nudge_x <- nudge[[nudge_name]][[1]];
nudge_y <- nudge[[nudge_name]][[2]];
}
if (verbose) {
jamba::printDebug("nudge_JamPolygon(): ",
"applying nudge (", c(nudge_x, nudge_y), ") ",
"to '", nudge_name, "'");
}
if (!all(nudge_x %in% c(NA, 0))) {
old_x <- jp@polygons$x[n];
new_x <- apply_nudge(jp[nudge_name, ]@polygons$x, offset=nudge_x)
jp@polygons$x[n] <- new_x;
} else {
new_x <- jp[nudge_name, ]@polygons$x;
}
if (!all(nudge_y %in% c(NA, 0))) {
old_y <- jp@polygons$y[n];
new_y <- apply_nudge(jp[nudge_name, ]@polygons$y, offset=nudge_y)
jp@polygons$y[n] <- new_y;
} else {
new_y <- jp[nudge_name, ]@polygons$y;
}
}
}
return(jp);
}
#' Define label positions for JamPolygon using polylabelr::poi()
#'
#' Define label positions for JamPolygon using polylabelr::poi()
#'
#' This function is a simple wrapper for `polylabelr::poi()`, which
#' recognizes holes inside polygons.
#'
#' ## Todo:
#'
#' * Consider basic support for non-overlapping label positions.
#'
#' 1. First pass might be to use non-overlapping regions of a polygon
#' among a set of polygons.
#' 2. Second pass would be to use the region with fewest overlaps
#' among other polygons in the set, to define the label position.
#' This rule could serve to solve (1) as well.
#'
#' @family JamPolygon
#'
#' @returns `matrix` with columns `"x"`, `"y"`, `"dist"`
#'
#' @param jp `JamPolygon`
#' @param precision `numeric` passed to `polylabelr::poi()`
#' @param add_to_jp `logical` indicating whether to add `"label_x"`,
#' `"label_y"` into the `jp@polygons` `data.frame`.
#' @param ... additional arguments are ignored.
#'
#' @examples
#'
#' @export
labelr_JamPolygon <- function
(jp,
precision=1,
add_to_jp=FALSE,
# add_labels=FALSE,
...)
{
# validate input
# - SKIPPED
# iterate each polygon
row_seq <- seq_len(nrow(jp@polygons));
label_xy_list <- lapply(row_seq, function(irow){
ix <- jp@polygons$x[[irow]];
iy <- jp@polygons$y[[irow]];
if (!is.list(ix)) {
ix <- list(ix);
iy <- list(iy);
}
plx <- head(unlist(lapply(seq_along(ix), function(j){
c(ix[[j]], NA)
})), -1)
ply <- head(unlist(lapply(seq_along(ix), function(j){
c(iy[[j]], NA)
})), -1)
if (length(plx) == 0) {
return(list(x=NA, y=NA, dist=NA));
}
as.data.frame(
polylabelr::poi(x=plx, y=ply, precision=1))
})
# assemble into a matrix
label_xy_dist <- as.matrix(jamba::rbindList(label_xy_list,
newColnames=c("x", "y", "dist")));
rownames(label_xy_dist) <- names(jp);
xy_coords <- label_xy_dist[, c("x", "y"), drop=FALSE];
# optionally add into the JamPolygon data
if (TRUE %in% add_to_jp) {
jp@polygons[, c("label_x", "label_y")] <- xy_coords;
return(jp);
}
return(xy_coords);
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.