Options available for geom_text() and geom_label() are also available for geom_text_repel() and geom_label_repel(), including size, angle, family, fontface, etc.
ggrepel provides additional options for geom_text_repel and geom_label_repel:
Option
Default
Description
force
1
force of repulsion between overlapping text labels
direction
"both"
move text labels “both” (default), “x”, or “y” directions
max.iter
2000
maximum number of iterations to try to resolve overlaps
nudge_x
0
adjust the starting x position of the text label
nudge_y
0
adjust the starting y position of the text label
box.padding
0.25 lines
padding around the text label
point.padding
0 lines
padding around the labeled data point
segment.color
"black"
line segment color
segment.size
0.5 mm
line segment thickness
segment.alpha
1.0
line segment transparency
arrow
NULL
render line segment as an arrow with grid::arrow()
Examples
Hide some of the labels
Set labels to the empty string "" to hide them. All data points repel the non-empty labels.
Use options xlim and ylim to constrain the labels to a specific area. Limits are specified in data coordinates. Use NA when there is no lower or upper bound in a particular direction.
Here we also use grid::arrow() to render the segments as arrows.
Sometimes the labels do not align perfectly. Try using direction = "x" to limit label movement to the x-axis (left and right) or direction = "y" to limit movement to the y-axis (up and down). The default is direction = "both".
Also try using xlim() and ylim() to increase the size of the plotting area so all of the labels fit comfortably.
ggrepel/tests/ 0000755 0001762 0000144 00000000000 13464162272 013050 5 ustar ligges users ggrepel/tests/testthat.R 0000644 0001762 0000144 00000000072 13464162272 015032 0 ustar ligges users library(testthat)
library(ggrepel)
test_check("ggrepel")
ggrepel/tests/testthat/ 0000755 0001762 0000144 00000000000 13464235406 014710 5 ustar ligges users ggrepel/tests/testthat/test-seed.R 0000644 0001762 0000144 00000012737 13464162353 016742 0 ustar ligges users # We should be able to reproduce identical plots by setting the random seed.
#
# ggplot(...) + geom_text_repel(..., seed = 1)
#
context("seed")
library(grid)
# Get the positions of text labels from geom_text_repel or geom_label_repel
pos_df <- function(pos) {
data.frame(
x = sapply(pos, "[[", "x"),
y = sapply(pos, "[[", "y"),
x.orig = sapply(pos, "[[", "x.orig"),
y.orig = sapply(pos, "[[", "y.orig")
)
}
test_that("calling geom_text_repel without seed creates different plots", {
ix <- seq(1, nrow(mtcars), 4)
dat1 <- mtcars[ix,]
dat1$label <- rownames(mtcars)[ix]
# Make a plot with no seed and get the label positions.
png("testthat_test-seed1.png")
p1 <- ggplot(dat1) + geom_text_repel(aes(wt, mpg, label = label))
print(p1)
grid.force()
pos1 <- pos_df(grid.get("textrepelgrob", grep = TRUE, global = TRUE))
dev.off()
unlink("testthat_test-seed1.png")
# Make a second plot with no seed and get the label positions.
png("testthat_test-seed2.png")
p2 <- ggplot(dat1) + geom_text_repel(aes(wt, mpg, label = label))
print(p2)
grid.force()
pos2 <- pos_df(grid.get("textrepelgrob", grep = TRUE, global = TRUE))
dev.off()
unlink("testthat_test-seed2.png")
# Confirm that the label positions are identical.
expect_true(nrow(pos1) == nrow(dat1))
expect_true(nrow(pos2) == nrow(dat1))
expect_true(!identical(pos1, pos2))
})
test_that("calling geom_text_repel with seed creates identical plots", {
ix <- seq(1, nrow(mtcars), 4)
dat1 <- mtcars[ix,]
dat1$label <- rownames(mtcars)[ix]
# Make a plot with no seed and get the label positions.
png("testthat_test-seed1.png")
p1 <- ggplot(dat1) + geom_text_repel(aes(wt, mpg, label = label), seed = 10)
print(p1)
grid.force()
pos1 <- pos_df(grid.get("textrepelgrob", grep = TRUE, global = TRUE))
dev.off()
unlink("testthat_test-seed1.png")
# Make a second plot with no seed and get the label positions.
png("testthat_test-seed2.png")
p2 <- ggplot(dat1) + geom_text_repel(aes(wt, mpg, label = label), seed = 10)
print(p2)
grid.force()
pos2 <- pos_df(grid.get("textrepelgrob", grep = TRUE, global = TRUE))
dev.off()
unlink("testthat_test-seed2.png")
# Confirm that the label positions are identical.
expect_true(nrow(pos1) == nrow(dat1))
expect_true(nrow(pos2) == nrow(dat1))
expect_true(identical(pos1, pos2))
})
test_that("calling geom_text_repel without seed does not remove entropy", {
ix <- seq(1, nrow(mtcars), 4)
dat1 <- mtcars[ix,]
dat1$label <- rownames(mtcars)[ix]
# One random number will be generated after each plot and accumulated
random_seq = c()
for(s in 1:2) {
set.seed(s)
png("testthat_test-seed1.png")
p1 <- ggplot(dat1) + geom_label_repel(aes(wt, mpg, label = label))
print(p1)
dev.off()
unlink("testthat_test-seed1.png")
random_seq = c(random_seq, rnorm(1))
}
# The random numbers are expected to be all different
expect_true(length(unique(random_seq))==length(random_seq))
})
test_that("calling geom_label_repel without seed creates different plots", {
ix <- seq(1, nrow(mtcars), 4)
dat1 <- mtcars[ix,]
dat1$label <- rownames(mtcars)[ix]
# Make a plot with no seed and get the label positions.
png("testthat_test-seed1.png")
p1 <- ggplot(dat1) + geom_label_repel(aes(wt, mpg, label = label))
print(p1)
grid.force()
pos1 <- pos_df(grid.get("labelrepelgrob", grep = TRUE, global = TRUE))
dev.off()
unlink("testthat_test-seed1.png")
# Make a second plot with no seed and get the label positions.
png("testthat_test-seed2.png")
p2 <- ggplot(dat1) + geom_label_repel(aes(wt, mpg, label = label))
print(p2)
grid.force()
pos2 <- pos_df(grid.get("labelrepelgrob", grep = TRUE, global = TRUE))
dev.off()
unlink("testthat_test-seed2.png")
# Confirm that the label positions are identical.
expect_true(nrow(pos1) == nrow(dat1))
expect_true(nrow(pos2) == nrow(dat1))
expect_true(!identical(pos1, pos2))
})
test_that("calling geom_label_repel with seed creates identical plots", {
ix <- seq(1, nrow(mtcars), 4)
dat1 <- mtcars[ix,]
dat1$label <- rownames(mtcars)[ix]
# Make a plot with no seed and get the label positions.
png("testthat_test-seed1.png")
p1 <- ggplot(dat1) + geom_label_repel(aes(wt, mpg, label = label), seed = 10)
print(p1)
grid.force()
pos1 <- pos_df(grid.get("labelrepelgrob", grep = TRUE, global = TRUE))
dev.off()
unlink("testthat_test-seed1.png")
# Make a second plot with no seed and get the label positions.
png("testthat_test-seed2.png")
p2 <- ggplot(dat1) + geom_label_repel(aes(wt, mpg, label = label), seed = 10)
print(p2)
grid.force()
pos2 <- pos_df(grid.get("labelrepelgrob", grep = TRUE, global = TRUE))
dev.off()
unlink("testthat_test-seed2.png")
# Confirm that the label positions are identical.
expect_true(nrow(pos1) == nrow(dat1))
expect_true(nrow(pos2) == nrow(dat1))
expect_true(identical(pos1, pos2))
})
test_that("calling geom_label_repel without seed does not remove entropy", {
ix <- seq(1, nrow(mtcars), 4)
dat1 <- mtcars[ix,]
dat1$label <- rownames(mtcars)[ix]
# One random number will be generated after each plot and accumulated
random_seq = c()
for(s in 1:2) {
set.seed(s)
png("testthat_test-seed1.png")
p1 <- ggplot(dat1) + geom_label_repel(aes(wt, mpg, label = label))
print(p1)
dev.off()
unlink("testthat_test-seed1.png")
random_seq = c(random_seq, rnorm(1))
}
# The random numbers are expected to be all different
expect_true(length(unique(random_seq))==length(random_seq))
})
ggrepel/tests/testthat/test-to_unit.R 0000644 0001762 0000144 00000023233 13464162272 017474 0 ustar ligges users # It should be possible to pass some arguments as either unit() objects or as
# numbers. Numbers should be converted to unit(n, "lines"), while unit() objects
# should remain unchanged.
# A combination of units for some arguments and numbers for others should be
# possible.
#
# These arguments are:
# box.padding (both)
# label.padding (geom_label_repel)
# point.padding (both)
# label.r (geom_label_repel)
# min.segment.length (both)
context("to_unit")
# Given a ggplot where the second layer is the geom_*_repel layer,
# return the given param
extract_param <- function(pl, param) {
pl$layers[[2]]$geom_params[[param]]
}
test_that("defaults work with geom_label_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, label = b)) + geom_point() + geom_label_repel()
expect_identical(extract_param(p, "box.padding"), unit(0.25, "lines"))
expect_identical(extract_param(p, "label.padding"), unit(0.25, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(1e-06, "lines"))
expect_identical(extract_param(p, "label.r"), unit(0.15, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(0.5, "lines"))
})
test_that("defaults work with geom_text_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, text = b)) + geom_point() + geom_text_repel()
expect_identical(extract_param(p, "box.padding"), unit(0.25, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(1e-06, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(0.5, "lines"))
})
test_that("non-default values as units work with geom_label_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, label = b)) + geom_point() +
geom_label_repel(box.padding = unit(1, "lines"),
label.padding = unit(2, "lines"),
point.padding = unit(3, "lines"), label.r = unit(4, "lines"),
min.segment.length = unit(5, "lines"))
expect_identical(extract_param(p, "box.padding"), unit(1, "lines"))
expect_identical(extract_param(p, "label.padding"), unit(2, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(3, "lines"))
expect_identical(extract_param(p, "label.r"), unit(4, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(5, "lines"))
})
test_that("non-default values as units work with geom_text_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_text_repel(box.padding = unit(1, "lines"),
point.padding = unit(2, "lines"),
min.segment.length = unit(3, "lines"))
expect_identical(extract_param(p, "box.padding"), unit(1, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(2, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(3, "lines"))
})
test_that("default values as numbers work with geom_label_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, label = b)) + geom_point() +
geom_label_repel(box.padding = 0.25, label.padding = 0.25,
point.padding = 1e-06, label.r = 0.15, min.segment.length = 0.5)
expect_identical(extract_param(p, "box.padding"), unit(0.25, "lines"))
expect_identical(extract_param(p, "label.padding"), unit(0.25, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(1e-06, "lines"))
expect_identical(extract_param(p, "label.r"), unit(0.15, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(0.5, "lines"))
expect_equal(p,
ggplot(d, aes(x, y, label = b)) + geom_point() + geom_label_repel())
})
test_that("default values as numbers work with geom_text_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_text_repel(box.padding = 0.25, point.padding = 1e-06,
min.segment.length = 0.5)
expect_identical(extract_param(p, "box.padding"), unit(0.25, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(1e-06, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(0.5, "lines"))
expect_equal(p,
ggplot(d, aes(x, y, text = b)) + geom_point() + geom_text_repel())
})
test_that("non-default values as numbers work with geom_label_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, label = b)) + geom_point() +
geom_label_repel(box.padding = 1, label.padding = 2,
point.padding = 3, label.r = 4, min.segment.length = 5)
expect_identical(extract_param(p, "box.padding"), unit(1, "lines"))
expect_identical(extract_param(p, "label.padding"), unit(2, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(3, "lines"))
expect_identical(extract_param(p, "label.r"), unit(4, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(5, "lines"))
expect_equal(p, ggplot(d, aes(x, y, label = b)) + geom_point() +
geom_label_repel(box.padding = unit(1, "lines"),
label.padding = unit(2, "lines"),
point.padding = unit(3, "lines"), label.r = unit(4, "lines"),
min.segment.length = unit(5, "lines")))
})
test_that("non-default values as numbers work with geom_text_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_text_repel(box.padding = 1, point.padding = 2,
min.segment.length = 3)
expect_identical(extract_param(p, "box.padding"), unit(1, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(2, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(3, "lines"))
expect_equal(p, ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_text_repel(box.padding = unit(1, "lines"),
point.padding = unit(2, "lines"),
min.segment.length = unit(3, "lines")))
})
test_that("non-default values as mix of units and numbers work with geom_label_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, label = b)) + geom_point() +
geom_label_repel(box.padding = unit(1, "lines"), label.padding = 2,
point.padding = 3, label.r = unit(4, "lines"), min.segment.length = 5)
expect_identical(extract_param(p, "box.padding"), unit(1, "lines"))
expect_identical(extract_param(p, "label.padding"), unit(2, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(3, "lines"))
expect_identical(extract_param(p, "label.r"), unit(4, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(5, "lines"))
expect_equal(p, ggplot(d, aes(x, y, label = b)) + geom_point() +
geom_label_repel(box.padding = unit(1, "lines"),
label.padding = unit(2, "lines"),
point.padding = unit(3, "lines"), label.r = unit(4, "lines"),
min.segment.length = unit(5, "lines")))
})
test_that("non-default values as mix of units and numbers work with geom_text_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_text_repel(box.padding = 1, point.padding = unit(2, "lines"),
min.segment.length = 3)
expect_identical(extract_param(p, "box.padding"), unit(1, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(2, "lines"))
expect_identical(extract_param(p, "min.segment.length"), unit(3, "lines"))
expect_equal(p, ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_text_repel(box.padding = unit(1, "lines"),
point.padding = unit(2, "lines"),
min.segment.length = unit(3, "lines")))
})
test_that("non-line units work with geom_label_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, label = b)) + geom_point() +
geom_label_repel(box.padding = unit(1, "cm"), label.padding = 2,
point.padding = 3, label.r = unit(4, "cm"), min.segment.length = 5)
expect_identical(extract_param(p, "box.padding"), unit(1, "cm"))
expect_identical(extract_param(p, "label.padding"), unit(2, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(3, "lines"))
expect_identical(extract_param(p, "label.r"), unit(4, "cm"))
expect_identical(extract_param(p, "min.segment.length"), unit(5, "lines"))
})
test_that("non-line units work with geom_text_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_text_repel(box.padding = 1, point.padding = unit(2, "cm"),
min.segment.length = 3)
expect_identical(extract_param(p, "box.padding"), unit(1, "lines"))
expect_identical(extract_param(p, "point.padding"), unit(2, "cm"))
expect_identical(extract_param(p, "min.segment.length"), unit(3, "lines"))
})
test_that("returns NA, not unit(NA, 'lines') when given NA in geom_label_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_label_repel(box.padding = NA)
# returns TRUE even is unit(NA, "lines")
expect_true(is.na(extract_param(p, "box.padding")))
# returns TRUE for NA, but not unit(NA, "lines")
expect_true(class(extract_param(p, "box.padding")) != "unit")
})
test_that("returns NA, not unit(NA, 'lines') when given NA in geom_text_repel", {
d <- data.frame(x = rnorm(10), y = rnorm(10), b = letters[1:10])
p <- ggplot(d, aes(x, y, text = b)) + geom_point() +
geom_text_repel(box.padding = NA)
# returns TRUE even is unit(NA, "lines")
expect_true(is.na(extract_param(p, "box.padding")))
# returns TRUE for NA, but not unit(NA, "lines")
expect_true(class(extract_param(p, "box.padding")) != "unit")
})
ggrepel/src/ 0000755 0001762 0000144 00000000000 13464164104 012471 5 ustar ligges users ggrepel/src/repel_boxes.cpp 0000644 0001762 0000144 00000045010 13464164104 015504 0 ustar ligges users #include
#include
using namespace Rcpp;
// Exported convenience functions ---------------------------------------------
//' Euclidean distance between two points.
//' @param a A numeric vector.
//' @param b A numeric vector.
//' @return The distance between two points.
//' @noRd
// [[Rcpp::export]]
double euclid(NumericVector a, NumericVector b) {
return sqrt(
(a[0] - b[0]) * (a[0] - b[0]) +
(a[1] - b[1]) * (a[1] - b[1])
);
}
//' Get the coordinates of the center of a box.
//' @param b A box like \code{c(x1, y1, x2, y2)}
//' @noRd
// [[Rcpp::export]]
NumericVector centroid(NumericVector b, double hjust, double vjust) {
return NumericVector::create(b[0] + (b[2] - b[0]) * hjust, b[1] + (b[3] - b[1]) * vjust);
}
//' Find the intersections between a line and a rectangle.
//' @param p1 A point like \code{c(x, y)}
//' @param p2 A point like \code{c(x, y)}
//' @param b A rectangle like \code{c(x1, y1, x2, y2)}
//' @noRd
// [[Rcpp::export]]
NumericVector intersect_line_rectangle(
NumericVector p1, NumericVector p2, NumericVector b
) {
// Sorry for the ugly code :(
double dy = p2[1] - p1[1];
double dx = p2[0] - p1[0];
double slope = dy / dx;
double intercept = p2[1] - p2[0] * slope;
NumericMatrix retval(4, 2);
std::fill(retval.begin(), retval.end(), -INFINITY);
double x, y;
// +----------+ < b[3]
// | |
// | | < y
// | |
// +----------+ < b[1]
// ^ ^ ^
// b[0] x b[2]
if (dx != 0) {
// Left boundary
x = b[0];
y = dy == 0 ? p1[1] : slope * x + intercept;
if (b[1] <= y && y <= b[3]) {
retval(0, _) = NumericVector::create(x, y);
}
// Right boundary
x = b[2];
y = dy == 0 ? p1[1] : slope * x + intercept;
if (b[1] <= y && y <= b[3]) {
retval(1, _) = NumericVector::create(x, y);
}
}
if (dy != 0) {
// Bottom boundary
y = b[1];
x = dx == 0 ? p1[0] : (y - intercept) / slope;
if (b[0] <= x && x <= b[2]) {
retval(2, _) = NumericVector::create(x, y);
}
// Top boundary
y = b[3];
x = dx == 0 ? p1[0] : (y - intercept) / slope;
if (b[0] <= x && x <= b[2]) {
retval(3, _) = NumericVector::create(x, y);
}
}
int i = 0;
int imin = 0;
double d;
double dmin = INFINITY;
for (i = 0; i < 4; i++) {
d = euclid(retval(i, _), p1);
if (d < dmin) {
dmin = d;
imin = i;
}
}
return retval(imin, _);
}
// [[Rcpp::export]]
NumericVector select_line_connection(
NumericVector p1, NumericVector b
) {
NumericVector out(2);
// Find shortest path
// +----------+ < b[3]
// | |
// | |
// | |
// +----------+ < b[1]
// ^ ^
// b[0] b[2]
bool top = false;
bool left = false;
bool right = false;
bool bottom = false;
if ((p1[0] >= b[0]) & (p1[0] <= b[2])) {
out[0] = p1[0];
} else if (p1[0] > b[2]) {
out[0] = b[2];
right = true;
} else{
out[0] = b[0];
left = true;
}
if ((p1[1] >= b[1]) & (p1[1] <= b[3])) {
out[1] = p1[1];
} else if (p1[1] > b[3]) {
out[1] = b[3];
top = true;
} else{
out[1] = b[1];
bottom = true;
}
// Nudge to center
double midx = (b[0] + b[2]) * 0.5;
double midy = (b[3] + b[1]) * 0.5;
double d = std::sqrt(
std::pow(p1[0] - out[0], 2) +
std::pow(p1[1] - out[1], 2)
);
if ((top || bottom) && !(left || right)) {
// top or bottom
double altd = std::sqrt(
std::pow(p1[0] - midx, 2) +
std::pow(p1[1] - out[1], 2)
);
out[0] = out[0] + (midx - out[0]) * d / altd;
} else if ((left || right) && !(top || bottom)) {
// left or right
double altd = std::sqrt(
std::pow(p1[0] - out[0], 2) +
std::pow(p1[1] - midy, 2)
);
out[1] = out[1] + (midy - out[1]) * d / altd;
} else if ((left || right) && (top || bottom)) {
double altd1 = std::sqrt(
std::pow(p1[0] - midx, 2) +
std::pow(p1[1] - out[1], 2)
);
double altd2 = std::sqrt(
std::pow(p1[0] - out[0], 2) +
std::pow(p1[1] - midy, 2)
);
if (altd1 < altd2) {
out[0] = out[0] + (midx - out[0]) * d / altd1;
} else {
out[1] = out[1] + (midy - out[1]) * d / altd2;
}
}
return out;
}
// Main code for text label placement -----------------------------------------
typedef struct {
double x, y;
} Point;
Point operator -(const Point& a, const Point& b) {
Point p = {a.x - b.x, a.y - b.y};
return p;
}
Point operator +(const Point& a, const Point& b) {
Point p = {a.x + b.x, a.y + b.y};
return p;
}
Point operator /(const Point& a, const double& b) {
Point p = {a.x / b, a.y / b};
return p;
}
Point operator *(const double& b, const Point& a) {
Point p = {a.x * b, a.y * b};
return p;
}
Point operator *(const Point& a, const double& b) {
Point p = {a.x * b, a.y * b};
return p;
}
typedef struct {
double x1, y1, x2, y2;
} Box;
Box operator +(const Box& b, const Point& p) {
Box c = {b.x1 + p.x, b.y1 + p.y, b.x2 + p.x, b.y2 + p.y};
return c;
}
//' Euclidean distance between two points.
//' @param a A point.
//' @param b A point.
//' @return The distance between two points.
//' @noRd
double euclid(Point a, Point b) {
Point dist = a - b;
return sqrt(dist.x * dist.x + dist.y * dist.y);
}
//' Squared Euclidean distance between two points.
//' @param a A point.
//' @param b A point.
//' @return The distance between two points.
//' @noRd
double euclid2(Point a, Point b) {
Point dist = a - b;
return dist.x * dist.x + dist.y * dist.y;
}
// [[Rcpp::export]]
bool approximately_equal(double x1, double x2) {
return std::abs(x2 - x1) < (std::numeric_limits::epsilon() * 100);
}
bool line_intersect(Point p1, Point q1, Point p2, Point q2) {
// Special exception, where q1 and q2 are equal (do intersect)
if (q1.x == q2.x && q1.y == q2.y)
return false;
// If line is point
if (p1.x == q1.x && p1.y == q1.y)
return false;
if (p2.x == q2.x && p2.y == q2.y)
return false;
double dy1 = q1.y - p1.y;
double dx1 = q1.x - p1.x;
double slope1 = dy1 / dx1;
double intercept1 = q1.y - q1.x * slope1;
double dy2 = q2.y - p2.y;
double dx2 = q2.x - p2.x;
double slope2 = dy2 / dx2;
double intercept2 = q2.y - q2.x * slope2;
double x,y;
// check if lines vertical
if (approximately_equal(dx1,0.0)) {
if (approximately_equal(dx2,0.0)) {
return false;
} else {
x = p1.x;
y = slope2 * x + intercept2;
}
} else if (approximately_equal(dx2,0.0)) {
x = p2.x;
y = slope1 * x + intercept1;
} else {
if (approximately_equal(slope1,slope2)) {
return false;
}
x = (intercept2 - intercept1) / (slope1 - slope2);
y = slope1 * x + intercept1;
}
if (x < p1.x && x < q1.x) {
return false;
} else if (x > p1.x && x > q1.x) {
return false;
} else if (y < p1.y && y < q1.y) {
return false;
} else if (y > p1.y && y > q1.y) {
return false;
} else if (x < p2.x && x < q2.x) {
return false;
} else if (x > p2.x && x > q2.x) {
return false;
} else if (y < p2.y && y < q2.y) {
return false;
} else if (y > p2.y && y > q2.y) {
return false;
} else{
return true;
}
}
//' Move a box into the area specificied by x limits and y limits.
//' @param b A box like \code{c(x1, y1, x2, y2)}
//' @param xlim A Point with limits on the x axis like \code{c(xmin, xmax)}
//' @param ylim A Point with limits on the y axis like \code{c(xmin, xmax)}
//' @param force Magnitude of the force (defaults to \code{1e-6})
//' @noRd
Box put_within_bounds(Box b, Point xlim, Point ylim, double force = 1e-5) {
//double d;
//if (b.x1 < xlim.x) {
// d = std::max(fabs(b.x1 - xlim.x), 0.02);
// b.x1 += force / pow(d, 2);
// b.x2 += force / pow(d, 2);
//} else if (b.x2 > xlim.y) {
// d = std::max(fabs(b.x2 - xlim.y), 0.02);
// b.x1 -= force / pow(d, 2);
// b.x2 -= force / pow(d, 2);
//}
//if (b.y1 < ylim.x) {
// d = std::max(fabs(b.y1 - ylim.x), 0.02);
// b.y1 += force / pow(d, 2);
// b.y2 += force / pow(d, 2);
//} else if (b.y2 > ylim.y) {
// d = std::max(fabs(b.y2 - ylim.y), 0.02);
// b.y1 -= force / pow(d, 2);
// b.y2 -= force / pow(d, 2);
//}
double width = fabs(b.x1 - b.x2);
double height = fabs(b.y1 - b.y2);
if (b.x1 < xlim.x) {
b.x1 = xlim.x;
b.x2 = b.x1 + width;
} else if (b.x2 > xlim.y) {
b.x2 = xlim.y;
b.x1 = b.x2 - width;
}
if (b.y1 < ylim.x) {
b.y1 = ylim.x;
b.y2 = b.y1 + height;
} else if (b.y2 > ylim.y) {
b.y2 = ylim.y;
b.y1 = b.y2 - height;
}
return b;
}
//' Get the coordinates of the center of a box.
//' @param b A box like \code{c(x1, y1, x2, y2)}
//' @noRd
Point centroid(Box b, double hjust, double vjust) {
Point p = {(b.x1 + (b.x2 - b.x1) * hjust), b.y1 + (b.y2 - b.y1) * vjust};
return p;
}
//' Test if a box overlaps another box.
//' @param a A box like \code{c(x1, y1, x2, y2)}
//' @param b A box like \code{c(x1, y1, x2, y2)}
//' @noRd
bool overlaps(Box a, Box b) {
return
b.x1 <= a.x2 &&
b.y1 <= a.y2 &&
b.x2 >= a.x1 &&
b.y2 >= a.y1;
}
Point repel_force_both(
Point a, Point b, double force = 0.000001
) {
double dx = fabs(a.x - b.x);
double dy = fabs(a.y - b.y);
// Constrain the minimum distance, so it is never 0.
double d2 = std::max(dx * dx + dy * dy, 0.0004);
// Compute a unit vector in the direction of the force.
Point v = (a - b) / sqrt(d2);
// Divide the force by the squared distance.
Point f = force * v / d2;
if (dx > dy) {
// f.y = f.y * dx / dy;
f.y = f.y * 2;
} else {
// f.x = f.x * dy / dx;
f.x = f.x * 2;
}
return f;
}
Point repel_force_y(
Point a, Point b, double force = 0.000001
) {
double dx = fabs(a.x - b.x);
double dy = fabs(a.y - b.y);
// Constrain the minimum distance, so it is never 0.
double d2 = std::max(dx * dx + dy * dy, 0.0004);
// Compute a unit vector in the direction of the force.
Point v = {0,1};
if (a.y < b.y) {
v.y = -1;
}
// Divide the force by the distance.
Point f = force * v / d2 * 2;
return f;
}
Point repel_force_x(
Point a, Point b, double force = 0.000001
) {
double dx = fabs(a.x - b.x);
double dy = fabs(a.y - b.y);
// Constrain the minimum distance, so it is never 0.
double d2 = std::max(dx * dx + dy * dy, 0.0004);
// Compute a unit vector in the direction of the force.
Point v = {1,0};
if (a.x < b.x) {
v.x = -1;
}
// Divide the force by the squared distance.
Point f = force * v / d2 * 2;
return f;
}
//' Compute the repulsion force upon point \code{a} from point \code{b}.
//'
//' The force decays with the squared distance between the points, similar
//' to the force of repulsion between magnets.
//'
//' @param a A point like \code{c(x, y)}
//' @param b A point like \code{c(x, y)}
//' @param force Magnitude of the force (defaults to \code{1e-6})
//' @param direction direction in which to exert force, either "both", "x", or "y"
//' @noRd
Point repel_force(
Point a, Point b, double force = 0.000001, std::string direction = "both"
) {
Point out;
if (direction == "x") {
out = repel_force_x(a, b, force);
} else if (direction == "y") {
out = repel_force_y(a, b, force);
} else{
out = repel_force_both(a, b, force);
}
return out;
}
Point spring_force_both(
Point a, Point b, double force = 0.000001
) {
Point f = {0, 0};
Point v = (a - b) ;
f = force * v;
return f;
}
Point spring_force_y(
Point a, Point b, double force = 0.000001
) {
Point f = {0, 0};
Point v = {0, (a.y - b.y)};
f = force * v;
return f;
}
Point spring_force_x(
Point a, Point b, double force = 0.000001
) {
Point f = {0, 0};
Point v = {(a.x - b.x), 0};
f = force * v ;
return f;
}
//' Compute the spring force upon point \code{a} from point \code{b}.
//'
//' The force increases with the distance between the points, similar
//' to Hooke's law for springs.
//'
//' @param a A point like \code{c(x, y)}
//' @param b A point like \code{c(x, y)}
//' @param force Magnitude of the force (defaults to \code{1e-6})
//' @param direction direction in which to exert force, either "both", "x", or "y"
//' @noRd
Point spring_force(
Point a, Point b, double force = 0.000001, std::string direction = "both"
) {
Point out;
if (direction == "x") {
out = spring_force_x(a, b, force);
} else if (direction == "y") {
out = spring_force_y(a, b, force);
} else{
out = spring_force_both(a, b, force);
}
return out;
}
//' Adjust the layout of a list of potentially overlapping boxes.
//' @param data_points A numeric matrix with rows representing points like
//' \code{rbind(c(x, y), c(x, y), ...)}
//' @param point_padding_x Padding around each data point on the x axis.
//' @param point_padding_y Padding around each data point on the y axis.
//' @param boxes A numeric matrix with rows representing boxes like
//' \code{rbind(c(x1, y1, x2, y2), c(x1, y1, x2, y2), ...)}
//' @param xlim A numeric vector representing the limits on the x axis like
//' \code{c(xmin, xmax)}
//' @param ylim A numeric vector representing the limits on the y axis like
//' \code{c(ymin, ymax)}
//' @param force Magnitude of the force (defaults to \code{1e-6})
//' @param maxiter Maximum number of iterations to try to resolve overlaps
//' (defaults to 2000)
//' @noRd
// [[Rcpp::export]]
DataFrame repel_boxes(
NumericMatrix data_points,
double point_padding_x, double point_padding_y,
NumericMatrix boxes,
NumericVector xlim, NumericVector ylim,
NumericVector hjust, NumericVector vjust,
double force = 1e-6, int maxiter = 2000,
std::string direction = "both"
) {
int n_points = data_points.nrow();
int n_texts = boxes.nrow();
// assert(n_points >= n_texts);
int iter = 0;
bool any_overlaps = true;
bool i_overlaps = true;
if (NumericVector::is_na(force)) {
force = 1e-6;
}
Point xbounds, ybounds;
xbounds.x = xlim[0];
xbounds.y = xlim[1];
ybounds.x = ylim[0];
ybounds.y = ylim[1];
// Each data point gets a bounding box.
std::vector DataBoxes(n_points);
for (int i = 0; i < n_points; i++) {
DataBoxes[i].x1 = data_points(i, 0) - point_padding_x;
DataBoxes[i].y1 = data_points(i, 1) - point_padding_y;
DataBoxes[i].x2 = data_points(i, 0) + point_padding_x;
DataBoxes[i].y2 = data_points(i, 1) + point_padding_y;
}
std::vector Points(n_points);
for (int i = 0; i < n_points; i++) {
Points[i].x = data_points(i, 0);
Points[i].y = data_points(i, 1);
}
// Add a tiny bit of jitter to each text box at the start.
NumericVector r = rnorm(n_texts, 0, force);
std::vector TextBoxes(n_texts);
std::vector ratios(n_texts);
std::vector original_centroids(n_texts);
for (int i = 0; i < n_texts; i++) {
TextBoxes[i].x1 = boxes(i, 0);
TextBoxes[i].x2 = boxes(i, 2);
TextBoxes[i].y1 = boxes(i, 1);
TextBoxes[i].y2 = boxes(i, 3);
// Don't add jitter if the user wants to repel in just one direction.
if (direction != "y") {
TextBoxes[i].x1 += r[i];
TextBoxes[i].x2 += r[i];
}
if (direction != "x") {
TextBoxes[i].y1 += r[i];
TextBoxes[i].y2 += r[i];
}
// height over width
ratios[i] = (TextBoxes[i].y2 - TextBoxes[i].y1)
/ (TextBoxes[i].x2 - TextBoxes[i].x1);
original_centroids[i] = centroid(TextBoxes[i], hjust[i], vjust[i]);
}
Point f, ci, cj;
while (any_overlaps && iter < maxiter) {
iter += 1;
any_overlaps = false;
for (int i = 0; i < n_texts; i++) {
i_overlaps = false;
f.x = 0;
f.y = 0;
ci = centroid(TextBoxes[i], hjust[i], vjust[i]);
for (int j = 0; j < n_points; j++) {
if (i == j) {
// Skip the data points if the padding is 0.
if (point_padding_x == 0 && point_padding_y == 0) {
continue;
}
// Repel the box from its data point.
if (overlaps(DataBoxes[i], TextBoxes[i])) {
any_overlaps = true;
i_overlaps = true;
f = f + repel_force(ci, Points[i], force, direction);
}
} else {
// Repel the box from overlapping boxes.
if (j < n_texts && overlaps(TextBoxes[i], TextBoxes[j])) {
cj = centroid(TextBoxes[j], hjust[j], vjust[j]);
any_overlaps = true;
i_overlaps = true;
f = f + repel_force(ci, cj, force * 3, direction);
}
// Skip the data points if the padding is 0.
if (point_padding_x == 0 && point_padding_y == 0) {
continue;
}
// Repel the box from other data points.
if (overlaps(DataBoxes[j], TextBoxes[i])) {
any_overlaps = true;
i_overlaps = true;
f = f + repel_force(ci, Points[j], force, direction);
}
}
}
// Pull the box toward its original position.
if (!i_overlaps) {
f = f + spring_force(original_centroids[i], ci, force * 2e3, direction);
}
TextBoxes[i] = TextBoxes[i] + f;
// Put boxes within bounds
TextBoxes[i] = put_within_bounds(TextBoxes[i], xbounds, ybounds);
// look for line clashes
if (!any_overlaps || iter % 5 == 0) {
for (int j = 0; j < n_texts; j++) {
cj = centroid(TextBoxes[j], hjust[j], vjust[j]);
ci = centroid(TextBoxes[i], hjust[i], vjust[i]);
// Switch label positions if lines overlap
if (
i != j &&
line_intersect(ci, Points[i], cj, Points[j])
) {
any_overlaps = true;
TextBoxes[i] = TextBoxes[i] + spring_force(cj, ci, 1, direction);
TextBoxes[j] = TextBoxes[j] + spring_force(ci, cj, 1, direction);
// Check if resolved
ci = centroid(TextBoxes[i], hjust[i], vjust[i]);
cj = centroid(TextBoxes[j], hjust[j], vjust[j]);
if (line_intersect(ci, Points[i], cj, Points[j])) {
//Rcout << "unresolved overlap in iter " << iter << std::endl;
TextBoxes[i] = TextBoxes[i] +
spring_force(cj, ci, 1.25, direction);
TextBoxes[j] = TextBoxes[j] +
spring_force(ci, cj, 1.25, direction);
}
}
}
}
// Dampen force after each iteration.
// force = force * 0.9999;
} // loop through all text labels
} // while any overlaps exist and we haven't reached max iterations
NumericVector xs(n_texts);
NumericVector ys(n_texts);
for (int i = 0; i < n_texts; i++) {
xs[i] = (TextBoxes[i].x1 + TextBoxes[i].x2) / 2;
ys[i] = (TextBoxes[i].y1 + TextBoxes[i].y2) / 2;
}
return Rcpp::DataFrame::create(
Rcpp::Named("x") = xs,
Rcpp::Named("y") = ys
);
}
ggrepel/src/RcppExports.cpp 0000644 0001762 0000144 00000011616 13464164104 015473 0 ustar ligges users // Generated by using Rcpp::compileAttributes() -> do not edit by hand
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
#include
using namespace Rcpp;
// euclid
double euclid(NumericVector a, NumericVector b);
RcppExport SEXP _ggrepel_euclid(SEXP aSEXP, SEXP bSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< NumericVector >::type a(aSEXP);
Rcpp::traits::input_parameter< NumericVector >::type b(bSEXP);
rcpp_result_gen = Rcpp::wrap(euclid(a, b));
return rcpp_result_gen;
END_RCPP
}
// centroid
NumericVector centroid(NumericVector b, double hjust, double vjust);
RcppExport SEXP _ggrepel_centroid(SEXP bSEXP, SEXP hjustSEXP, SEXP vjustSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< NumericVector >::type b(bSEXP);
Rcpp::traits::input_parameter< double >::type hjust(hjustSEXP);
Rcpp::traits::input_parameter< double >::type vjust(vjustSEXP);
rcpp_result_gen = Rcpp::wrap(centroid(b, hjust, vjust));
return rcpp_result_gen;
END_RCPP
}
// intersect_line_rectangle
NumericVector intersect_line_rectangle(NumericVector p1, NumericVector p2, NumericVector b);
RcppExport SEXP _ggrepel_intersect_line_rectangle(SEXP p1SEXP, SEXP p2SEXP, SEXP bSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< NumericVector >::type p1(p1SEXP);
Rcpp::traits::input_parameter< NumericVector >::type p2(p2SEXP);
Rcpp::traits::input_parameter< NumericVector >::type b(bSEXP);
rcpp_result_gen = Rcpp::wrap(intersect_line_rectangle(p1, p2, b));
return rcpp_result_gen;
END_RCPP
}
// select_line_connection
NumericVector select_line_connection(NumericVector p1, NumericVector b);
RcppExport SEXP _ggrepel_select_line_connection(SEXP p1SEXP, SEXP bSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< NumericVector >::type p1(p1SEXP);
Rcpp::traits::input_parameter< NumericVector >::type b(bSEXP);
rcpp_result_gen = Rcpp::wrap(select_line_connection(p1, b));
return rcpp_result_gen;
END_RCPP
}
// approximately_equal
bool approximately_equal(double x1, double x2);
RcppExport SEXP _ggrepel_approximately_equal(SEXP x1SEXP, SEXP x2SEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< double >::type x1(x1SEXP);
Rcpp::traits::input_parameter< double >::type x2(x2SEXP);
rcpp_result_gen = Rcpp::wrap(approximately_equal(x1, x2));
return rcpp_result_gen;
END_RCPP
}
// repel_boxes
DataFrame repel_boxes(NumericMatrix data_points, double point_padding_x, double point_padding_y, NumericMatrix boxes, NumericVector xlim, NumericVector ylim, NumericVector hjust, NumericVector vjust, double force, int maxiter, std::string direction);
RcppExport SEXP _ggrepel_repel_boxes(SEXP data_pointsSEXP, SEXP point_padding_xSEXP, SEXP point_padding_ySEXP, SEXP boxesSEXP, SEXP xlimSEXP, SEXP ylimSEXP, SEXP hjustSEXP, SEXP vjustSEXP, SEXP forceSEXP, SEXP maxiterSEXP, SEXP directionSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< NumericMatrix >::type data_points(data_pointsSEXP);
Rcpp::traits::input_parameter< double >::type point_padding_x(point_padding_xSEXP);
Rcpp::traits::input_parameter< double >::type point_padding_y(point_padding_ySEXP);
Rcpp::traits::input_parameter< NumericMatrix >::type boxes(boxesSEXP);
Rcpp::traits::input_parameter< NumericVector >::type xlim(xlimSEXP);
Rcpp::traits::input_parameter< NumericVector >::type ylim(ylimSEXP);
Rcpp::traits::input_parameter< NumericVector >::type hjust(hjustSEXP);
Rcpp::traits::input_parameter< NumericVector >::type vjust(vjustSEXP);
Rcpp::traits::input_parameter< double >::type force(forceSEXP);
Rcpp::traits::input_parameter< int >::type maxiter(maxiterSEXP);
Rcpp::traits::input_parameter< std::string >::type direction(directionSEXP);
rcpp_result_gen = Rcpp::wrap(repel_boxes(data_points, point_padding_x, point_padding_y, boxes, xlim, ylim, hjust, vjust, force, maxiter, direction));
return rcpp_result_gen;
END_RCPP
}
static const R_CallMethodDef CallEntries[] = {
{"_ggrepel_euclid", (DL_FUNC) &_ggrepel_euclid, 2},
{"_ggrepel_centroid", (DL_FUNC) &_ggrepel_centroid, 3},
{"_ggrepel_intersect_line_rectangle", (DL_FUNC) &_ggrepel_intersect_line_rectangle, 3},
{"_ggrepel_select_line_connection", (DL_FUNC) &_ggrepel_select_line_connection, 2},
{"_ggrepel_approximately_equal", (DL_FUNC) &_ggrepel_approximately_equal, 2},
{"_ggrepel_repel_boxes", (DL_FUNC) &_ggrepel_repel_boxes, 11},
{NULL, NULL, 0}
};
RcppExport void R_init_ggrepel(DllInfo *dll) {
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
}
ggrepel/NAMESPACE 0000644 0001762 0000144 00000001563 13464162353 013132 0 ustar ligges users # Generated by roxygen2: do not edit by hand
S3method(makeContent,labelrepelgrob)
S3method(makeContent,labelrepeltree)
S3method(makeContent,textrepelgrob)
S3method(makeContent,textrepeltree)
export(GeomLabelRepel)
export(GeomTextRepel)
export(geom_label_repel)
export(geom_text_repel)
import(ggplot2)
importFrom(Rcpp,evalCpp)
importFrom(grid,convertHeight)
importFrom(grid,convertWidth)
importFrom(grid,gList)
importFrom(grid,gTree)
importFrom(grid,gpar)
importFrom(grid,grobHeight)
importFrom(grid,grobName)
importFrom(grid,grobTree)
importFrom(grid,grobWidth)
importFrom(grid,grobX)
importFrom(grid,grobY)
importFrom(grid,is.grob)
importFrom(grid,is.unit)
importFrom(grid,makeContent)
importFrom(grid,resolveHJust)
importFrom(grid,resolveVJust)
importFrom(grid,roundrectGrob)
importFrom(grid,segmentsGrob)
importFrom(grid,setChildren)
importFrom(grid,textGrob)
useDynLib(ggrepel)
ggrepel/NEWS.md 0000644 0001762 0000144 00000023266 13464162353 013015 0 ustar ligges users
ggrepel 0.8.0 2018-05-09
========================
## Bug fixes and improvements
* Fix `geom_label_repel(..., point.padding=NA)`. Reported by @mlell in
[issue 104].
* See below for other changes made after ggrepel 0.7.0
[issue 104]: https://github.com/slowkow/ggrepel/issues/104
ggrepel 0.7.3 2018-02-09
========================
NEW FEATURES
* Add support for `position` parameter. See [issue 69]. This allows us to
add text labels to points positioned with `position_jitter()`,
`position_dodge()`, `position_jitterdodge()`, etc.
Please note that this feature will not work with ggplot2 2.2.1 or older.
[issue 69]: https://github.com/slowkow/ggrepel/issues/69
ggrepel 0.7.2 2018-01-14
========================
FIXES (thanks to @AliciaSchep and @aphalo)
* Fix warning about `hjust`. See [issue 93].
* Fix bug when subset of points is labeled in `geom_label_repel`.
See [issue 92].
[issue 92]: https://github.com/slowkow/ggrepel/issues/92
[issue 93]: https://github.com/slowkow/ggrepel/issues/93
ggrepel 0.7.1 2017-11-18
========================
CHANGES (thanks to @AliciaSchep)
* Add support for `hjust` and `vjust` parameters. See [issue 69].
Also see new examples in the [vignette].
* Add code to avoid intersecting line segments. See [issue 34].
[issue 69]: https://github.com/slowkow/ggrepel/issues/69
[issue 34]: https://github.com/slowkow/ggrepel/issues/34
ggrepel 0.7.0 2017-09-28
========================
FIXES
* Fix intersection between lines and rectangles, to reproduce the same
aesthetically pleasant behavior as in version 0.6.5.
This is an improvement on the sloppy implementation introduced in 0.6.8. See
[commit 28633d] for more information.
[commit 28633d]: https://github.com/slowkow/ggrepel/commit/28633db5eb3d3cc2bd935bd438a8bb36b5673951
ggrepel 0.6.12 2017-07-16
========================
NEW FEATURE
* Reproduce identical plots by usign `seed = 1` to set the seed in
`geom_text_repel()` or `geom_label_repel()`. By default, no seed will be set.
This is an improvement on the sloppy implementation introduced in 0.6.2. See
[issue 33] and [issue 73] for more discussion of this feature. Thanks to
Pierre Gramme for reminding me about this via email.
[issue 33]: https://github.com/slowkow/ggrepel/issues/33
[issue 73]: https://github.com/slowkow/ggrepel/issues/73
ggrepel 0.6.11 2017-07-08
========================
CHANGES (thanks to @seaaan)
* Allow certain parameters to be passed as numbers or `unit()`
instead of only units. See [issue 79].
[issue 79]: https://github.com/slowkow/ggrepel/issues/79
ggrepel 0.6.10 2017-03-07
========================
FIXES (thanks to @zkamvar)
* Fix the crash for plots that do not specify `xlim` or `ylim`.
See [pull 74].
[pull 74]: https://github.com/slowkow/ggrepel/pull/74
ggrepel 0.6.9 2017-03-07
========================
FIXES (thanks to @pcroteau)
* Fix the crash for plots with `facet_wrap` or `facet_grid` that have no
labeled points. See [pull 70].
[pull 70]: https://github.com/slowkow/ggrepel/pull/70
ggrepel 0.6.8 2017-02-12
========================
NEW FEATURE (thanks to @AliciaSchep)
* Constrain repulsion force to x-axis "x" or y-axis "y" with `direction` in
`geom_text_repel()` and `geom_label_repel()`. See [pull 68].
[pull 68]: https://github.com/slowkow/ggrepel/pull/68
ggrepel 0.6.7 2017-01-09
========================
CHANGES (thanks to @lukauskas)
* Constrain text labels to specific areas of the plot with `xlim` and `ylim` in
`geom_text_repel()` and `geom_label_repel()`. See [pull 67].
[pull 67]: https://github.com/slowkow/ggrepel/pull/67
ggrepel 0.6.6 2016-11-28
========================
FIXES (thanks to @fawda123)
* Mathematical expressions as labels with `parse = TRUE` in
`geom_text_repel()` and `geom_label_repel()`. See [issue 60].
[issue 60]: https://github.com/slowkow/ggrepel/issues/60
ggrepel 0.6.5 2016-11-22
========================
CHANGES (thanks to @jiho)
* changed `alpha` in `geom_label_repel()` to control text, label
background, label border, and segment.
* Allow `segment.colour` as well as `segment.color`.
* By default, map text color and text alpha to the segment color unless they
are overridden.
FIXES (thanks to @jiho)
* Call `scales::alpha()` instead of `alpha()`.
ggrepel 0.6.4 2016-11-08
========================
FIXES
* Fix a bug that caused ggrepel to fail on polar coordinates `coord_polar()`.
See [issue 56].
[issue 56]: https://github.com/slowkow/ggrepel/issues/56
ggrepel 0.6.3 2016-10-14
========================
NEW FEATURES
* Use `point.padding=NA` to ignore data points in repulsion calculations.
ggrepel 0.6.2 2016-10-06
========================
FIXES
* Stop the labels from escaping the plot boundaries instead of applying
a force at the boundary.
* Call `set.seed` within `geom_text_repel()` and `geom_label_repel()` to
allow recreating identical plots. Fixes [issue 33].
[issue 33]: https://github.com/slowkow/ggrepel/issues/33
NEW FEATURES
* Add `min.segment.length` to `geom_text_repel()` and `geom_label_repel()`.
ggrepel 0.6.1 2016-10-04
========================
CHANGES
* Tweak `repel_boxes.cpp`. Dampen forces to tune how the labels move. The
result looks better, at least for the examples in the [vignette].
ggrepel 0.6.0 2016-10-03
========================
NEW FEATURES
* Do not draw labels with empty strings. When a label is an empty string,
the text will not be shown, the segment will not be drawn, but the
corresponding data point will repel other labels. See [issue 51].
[issue 51]: https://github.com/slowkow/ggrepel/issues/51
* Add `segment.alpha` as an option for `geom_text_repel()` and
`geom_label_repel()`.
* Implement `angle` aesthetic for `geom_text_repel()`, the same way as done in
ggplot2 `geom_text()`.
CHANGES
* Move `nudge_x` and `nudge_y` out of the aesthetics function `aes()`. This
makes ggrepel consistent with ggplot2 functions `geom_text()` and
`geom_label()`. Backwards incompatible with 0.5.1.
* Restore `segment.color` as an option for `geom_text_repel()` and
`geom_label_repel()`.
* Tweak `repel_boxes.cpp`. Do not weight repulsion force by ratios of bounding
box heights and widths. This seems to perform better, especially after
rotating text labels.
ggrepel 0.5.1 2016-02-22
========================
* Optimize C++ code further by reducing number of calls to `rnorm()`.
ggrepel 0.5 2016-02-08
========================
* First push to CRAN.
ggrepel 0.4.6 2016-02-07
========================
CHANGES
* Tweak `point.padding` so that users can configure how far labels are pushed
away from data points.
ggrepel 0.4.5 2016-02-06
========================
CHANGES
* Optimize C++ code for a 2.5X speed improvment.
* Delete unnecessary .Rd files.
ggrepel 0.4.4 2016-02-05
========================
FIXES
* Fix the bug when the line segment from the data point points to the origin
at (0,0) instead of the text label.
CHANGES
* Automatically recompute repulsion between labels after resizing the plot.
ggrepel 0.4.3 2016-01-18
========================
CHANGES
* Change distance between segment and label in `geom_label_repel()`. Now there
is no gap between the end of the segment and the label border.
ggrepel 0.4.2 2016-01-15
========================
FIXES
* Fix `spring_force()` so that it never returns NaN.
CHANGES
* Add `nudge_x` and `nudge_y` to better control positioning of labels.
ggrepel 0.4.1 2016-01-13
========================
CHANGES
* Add `arrow` parameter to allow plotting arrows that point to the labeled data
points rather than plain line segments.
* Always draw segments, even if the labeled point is very close to the label.
FIXES
* Fix `point.padding` so that horizontal and vertical padding is calculated
correctly.
* Tweak forces to improve layout near borders and in crowded areas.
ggrepel 0.4 2016-01-12
========================
FIXES
* Fix [issue 7]. Labels can now be placed anywhere in the plotting area
instead of being limited to the x and y ranges of their corresponding data
points.
[issue 7]: https://github.com/slowkow/ggrepel/issues/7
* Fix DESCRIPTION to require ggplot2 >= 2.0.0
CHANGES
* Add new parameter `point.padding` to add padding around the labeled points.
The line segment will stop before reaching the coordinates of the point. The
text labels are also now padded from the line segment to improve legibility.
* Add volcano plot to the [vignette] usage examples.
* Add Travis continuous integration to test against R-devel, R-release, and
R-oldrel.
* Dampen repulsion force to slightly improve algorithm efficiency.
* Move `intersect_line_rectangle()` to `src/repel_boxes.cpp`.
ggrepel 0.3 2016-01-08
========================
CHANGES
* Remove unused imports: `colorspace`.
* Update NAMESPACE with new version of roxygen.
* Use spring force to attract each label to its own point.
* Change default maximum iterations from 10,000 to 2000.
* Update man pages.
* Remove unused code.
ggrepel 0.2.0 2016-01-07
========================
CHANGES
* Update `geom_text_repel()` and `geom_label_repel()`.
* Change `label.padding` to `box.padding`.
* Remove unsupported parameters:
* position
* nudge_x
* nudge_y
* hjust
* vjust
* Remove unused imports.
DOCUMENTATION
* Add roxygen docs to all functions.
ggrepel 0.1.0 2016-01-05
========================
NEW FEATURES
* Add `geom_label_repel()`.
* Add fudge width to help with legends.
* Add `expand=TRUE` to allow text to be placed in the expanded plot area.
* Add man/ folder.
* Add links to ggplot2 docs in [vignette].
MINOR FEATURES
* Add unused R implementation of `repel_boxes()`, just for your reference.
ggrepel 0.0.1 2016-01-04
========================
* Initial release to github.
[vignette]: https://github.com/slowkow/ggrepel/blob/master/vignettes/ggrepel.md
ggrepel/R/ 0000755 0001762 0000144 00000000000 13464162353 012107 5 ustar ligges users ggrepel/R/utilities-grid.R 0000644 0001762 0000144 00000001260 13464162272 015167 0 ustar ligges users # Name ggplot grid object
# Convenience function to name grid objects
#
# @keyword internal
ggname <- function(prefix, grob) {
grob$name <- grobName(grob, prefix)
grob
}
width_cm <- function(x) {
if (is.grob(x)) {
convertWidth(grobWidth(x), "cm", TRUE)
} else if (is.list(x)) {
vapply(x, width_cm, numeric(1))
} else if (is.unit(x)) {
convertWidth(x, "cm", TRUE)
} else {
stop("Unknown input")
}
}
height_cm <- function(x) {
if (is.grob(x)) {
convertWidth(grobHeight(x), "cm", TRUE)
} else if (is.list(x)) {
vapply(x, height_cm, numeric(1))
} else if (is.unit(x)) {
convertHeight(x, "cm", TRUE)
} else {
stop("Unknown input")
}
}
ggrepel/R/ggrepel-package.R 0000644 0001762 0000144 00000001526 13464162353 015254 0 ustar ligges users #' ggrepel
#'
#' This package contains extra geoms for \pkg{ggplot2}.
#'
#' Please see the help pages listed below:
#'
#' \itemize{
#' \item \code{\link{geom_text_repel}}
#' \item \code{\link{geom_label_repel}}
#' }
#'
#' Also see the vignette for more usage examples:
#'
#' \code{browseVignettes("ggrepel")}
#'
#' Please report issues and suggest improvements at Github:
#'
#' \url{https://github.com/slowkow/ggrepel}
#'
#' @name ggrepel
#' @docType package
#' @import ggplot2
#' @importFrom grid
#' convertHeight
#' convertWidth
#' gList
#' gTree
#' gpar
#' grobHeight
#' grobName
#' grobTree
#' grobWidth
#' grobX
#' grobY
#' is.grob
#' is.unit
#' makeContent
#' resolveHJust
#' resolveVJust
#' roundrectGrob
#' segmentsGrob
#' setChildren
#' textGrob
#' @useDynLib ggrepel
#' @importFrom Rcpp evalCpp
NULL
ggrepel/R/utilities.R 0000644 0001762 0000144 00000001574 13464162353 014254 0 ustar ligges users "%||%" <- function(a, b) {
if (!is.null(a)) a else b
}
#' Return a boolean vector of non-empty items.
#'
#' @param xs Vector with a mix of "expression" items, "character" items,
#' and items from other classes.
#' @return Boolean vector indicating which items are not empty.
#' @noRd
not_empty <- function(xs) {
sapply(seq_along(xs), function(i) {
if (is.expression(xs[i])) {
return(length(nchar(xs[i])) > 0)
} else {
return(xs[i] != "")
}
})
}
#' Return a unit version of the argument.
#'
#' @param x Number or unit object.
#' @return unit(x, "lines") if number or the unchanged argument if it's already
#' a unit object.
#' @noRd
to_unit <- function(x) {
# don't change arg if already unit
if (class(x) == "unit") {
return(x)
}
# NA used to exclude points from repulsion calculations
if (is.na(x)) {
return(NA)
}
unit(x, "lines")
}
ggrepel/R/RcppExports.R 0000644 0001762 0000144 00000007732 13464162353 014534 0 ustar ligges users # Generated by using Rcpp::compileAttributes() -> do not edit by hand
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
#' Euclidean distance between two points.
#' @param a A point.
#' @param b A point.
#' @return The distance between two points.
#' @noRd
NULL
#' Squared Euclidean distance between two points.
#' @param a A point.
#' @param b A point.
#' @return The distance between two points.
#' @noRd
NULL
#' Move a box into the area specificied by x limits and y limits.
#' @param b A box like \code{c(x1, y1, x2, y2)}
#' @param xlim A Point with limits on the x axis like \code{c(xmin, xmax)}
#' @param ylim A Point with limits on the y axis like \code{c(xmin, xmax)}
#' @param force Magnitude of the force (defaults to \code{1e-6})
#' @noRd
NULL
#' Get the coordinates of the center of a box.
#' @param b A box like \code{c(x1, y1, x2, y2)}
#' @noRd
NULL
#' Test if a box overlaps another box.
#' @param a A box like \code{c(x1, y1, x2, y2)}
#' @param b A box like \code{c(x1, y1, x2, y2)}
#' @noRd
NULL
#' Compute the repulsion force upon point \code{a} from point \code{b}.
#'
#' The force decays with the squared distance between the points, similar
#' to the force of repulsion between magnets.
#'
#' @param a A point like \code{c(x, y)}
#' @param b A point like \code{c(x, y)}
#' @param force Magnitude of the force (defaults to \code{1e-6})
#' @param direction direction in which to exert force, either "both", "x", or "y"
#' @noRd
NULL
#' Compute the spring force upon point \code{a} from point \code{b}.
#'
#' The force increases with the distance between the points, similar
#' to Hooke's law for springs.
#'
#' @param a A point like \code{c(x, y)}
#' @param b A point like \code{c(x, y)}
#' @param force Magnitude of the force (defaults to \code{1e-6})
#' @param direction direction in which to exert force, either "both", "x", or "y"
#' @noRd
NULL
#' Euclidean distance between two points.
#' @param a A numeric vector.
#' @param b A numeric vector.
#' @return The distance between two points.
#' @noRd
euclid <- function(a, b) {
.Call('_ggrepel_euclid', PACKAGE = 'ggrepel', a, b)
}
#' Get the coordinates of the center of a box.
#' @param b A box like \code{c(x1, y1, x2, y2)}
#' @noRd
centroid <- function(b, hjust, vjust) {
.Call('_ggrepel_centroid', PACKAGE = 'ggrepel', b, hjust, vjust)
}
#' Find the intersections between a line and a rectangle.
#' @param p1 A point like \code{c(x, y)}
#' @param p2 A point like \code{c(x, y)}
#' @param b A rectangle like \code{c(x1, y1, x2, y2)}
#' @noRd
intersect_line_rectangle <- function(p1, p2, b) {
.Call('_ggrepel_intersect_line_rectangle', PACKAGE = 'ggrepel', p1, p2, b)
}
select_line_connection <- function(p1, b) {
.Call('_ggrepel_select_line_connection', PACKAGE = 'ggrepel', p1, b)
}
approximately_equal <- function(x1, x2) {
.Call('_ggrepel_approximately_equal', PACKAGE = 'ggrepel', x1, x2)
}
#' Adjust the layout of a list of potentially overlapping boxes.
#' @param data_points A numeric matrix with rows representing points like
#' \code{rbind(c(x, y), c(x, y), ...)}
#' @param point_padding_x Padding around each data point on the x axis.
#' @param point_padding_y Padding around each data point on the y axis.
#' @param boxes A numeric matrix with rows representing boxes like
#' \code{rbind(c(x1, y1, x2, y2), c(x1, y1, x2, y2), ...)}
#' @param xlim A numeric vector representing the limits on the x axis like
#' \code{c(xmin, xmax)}
#' @param ylim A numeric vector representing the limits on the y axis like
#' \code{c(ymin, ymax)}
#' @param force Magnitude of the force (defaults to \code{1e-6})
#' @param maxiter Maximum number of iterations to try to resolve overlaps
#' (defaults to 2000)
#' @noRd
repel_boxes <- function(data_points, point_padding_x, point_padding_y, boxes, xlim, ylim, hjust, vjust, force = 1e-6, maxiter = 2000L, direction = "both") {
.Call('_ggrepel_repel_boxes', PACKAGE = 'ggrepel', data_points, point_padding_x, point_padding_y, boxes, xlim, ylim, hjust, vjust, force, maxiter, direction)
}
ggrepel/R/geom-label-repel.R 0000644 0001762 0000144 00000031244 13464162353 015347 0 ustar ligges users #' @rdname geom_text_repel
#' @param label.padding Amount of padding around label, as unit or number.
#' Defaults to 0.25. (Default unit is lines, but other units can be specified
#' by passing \code{unit(x, "units")}).
#' @param label.r Radius of rounded corners, as unit or number. Defaults
#' to 0.15. (Default unit is lines, but other units can be specified by
#' passing \code{unit(x, "units")}).
#' @param label.size Size of label border, in mm.
#' @export
geom_label_repel <- function(
mapping = NULL, data = NULL, stat = "identity", position = "identity",
parse = FALSE,
...,
box.padding = 0.25,
label.padding = 0.25,
point.padding = 1e-6,
label.r = 0.15,
label.size = 0.25,
segment.colour = NULL,
segment.color = NULL,
segment.size = 0.5,
segment.alpha = NULL,
min.segment.length = 0.5,
arrow = NULL,
force = 1,
max.iter = 2000,
nudge_x = 0,
nudge_y = 0,
xlim = c(NA, NA),
ylim = c(NA, NA),
na.rm = FALSE,
show.legend = NA,
direction = c("both","y","x"),
seed = NA,
inherit.aes = TRUE
) {
if (!missing(nudge_x) || !missing(nudge_y)) {
if (!missing(position)) {
stop("Specify either `position` or `nudge_x`/`nudge_y`", call. = FALSE)
}
#position <- position_nudge(nudge_x, nudge_y)
}
layer(
data = data,
mapping = mapping,
stat = stat,
geom = GeomLabelRepel,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(
parse = parse,
box.padding = to_unit(box.padding),
label.padding = to_unit(label.padding),
point.padding = to_unit(point.padding),
label.r = to_unit(label.r),
label.size = label.size,
segment.colour = segment.color %||% segment.colour,
segment.size = segment.size,
segment.alpha = segment.alpha,
min.segment.length = to_unit(min.segment.length),
arrow = arrow,
na.rm = na.rm,
force = force,
max.iter = max.iter,
nudge_x = nudge_x,
nudge_y = nudge_y,
xlim = xlim,
ylim = ylim,
direction = match.arg(direction),
seed = seed,
...
)
)
}
#' GeomLabelRepel
#' @rdname ggplot2-ggproto
#' @format NULL
#' @usage NULL
#' @export
GeomLabelRepel <- ggproto(
"GeomLabelRepel", Geom,
required_aes = c("x", "y", "label"),
default_aes = aes(
colour = "black", fill = "white", size = 3.88, angle = 0,
alpha = NA, family = "", fontface = 1, lineheight = 1.2,
hjust = 0.5, vjust = 0.5
),
draw_panel = function(
self, data, panel_scales, coord,
parse = FALSE,
na.rm = FALSE,
box.padding = 0.25,
label.padding = 0.25,
point.padding = 1e-6,
label.r = 0.15,
label.size = 0.25,
segment.colour = NULL,
segment.size = 0.5,
segment.alpha = NULL,
min.segment.length = 0.5,
arrow = NULL,
force = 1,
nudge_x = 0,
nudge_y = 0,
xlim = c(NA, NA),
ylim = c(NA, NA),
max.iter = 2000,
direction = "both",
seed = NA
) {
lab <- data$label
if (parse) {
lab <- parse(text = as.character(lab))
}
if (!length(which(not_empty(lab)))) {
return()
}
# Transform the nudges to the panel scales.
nudges <- data.frame(
x = data$x + nudge_x,
y = data$y + nudge_y
)
nudges <- coord$transform(nudges, panel_scales)
# Transform the raw data to the panel scales.
data <- coord$transform(data, panel_scales)
# The nudge is relative to the data.
nudges$x <- nudges$x - data$x
nudges$y <- nudges$y - data$y
# Transform limits to panel scales.
limits <- data.frame(x = xlim, y = ylim)
limits <- coord$transform(limits, panel_scales)
# Fill NAs with defaults.
limits$x[is.na(limits$x)] <- c(0, 1)[is.na(limits$x)]
limits$y[is.na(limits$y)] <- c(0, 1)[is.na(limits$y)]
# Convert hjust and vjust to numeric if character
if (is.character(data$vjust)) {
data$vjust <- compute_just(data$vjust, data$y)
}
if (is.character(data$hjust)) {
data$hjust <- compute_just(data$hjust, data$x)
}
ggname("geom_label_repel", gTree(
limits = limits,
data = data,
lab = lab,
nudges = nudges,
box.padding = to_unit(box.padding),
label.padding = to_unit(label.padding),
point.padding = to_unit(point.padding),
label.r = to_unit(label.r),
label.size = label.size,
segment.colour = segment.colour,
segment.size = segment.size,
segment.alpha = segment.alpha,
min.segment.length = to_unit(min.segment.length),
arrow = arrow,
force = force,
max.iter = max.iter,
direction = direction,
seed = seed,
cl = "labelrepeltree"
))
},
draw_key = draw_key_label
)
#' grid::makeContent function for the grobTree of textRepelGrob objects
#' @param x A grid grobTree.
#' @export
#' @noRd
makeContent.labelrepeltree <- function(x) {
# The padding around each bounding box.
box_padding_x <- convertWidth(x$box.padding, "npc", valueOnly = TRUE)
box_padding_y <- convertHeight(x$box.padding, "npc", valueOnly = TRUE)
# The padding around each point.
if (is.na(x$point.padding)) {
x$point.padding = unit(0, "lines")
}
point_padding_x <- convertWidth(x$point.padding, "native", valueOnly = TRUE)
point_padding_y <- convertHeight(x$point.padding, "native", valueOnly = TRUE)
# Do not create text labels for empty strings.
valid_strings <- which(not_empty(x$lab))
invalid_strings <- which(!not_empty(x$lab))
# Create a dataframe with x y width height
boxes <- lapply(valid_strings, function(i) {
row <- x$data[i, , drop = FALSE]
hj <- x$data$hjust[i]
vj <- x$data$vjust[i]
t <- textGrob(
x$lab[i],
unit(row$x, "native") + x$label.padding,
unit(row$y, "native") + x$label.padding,
gp = gpar(
fontsize = row$size * .pt,
fontfamily = row$family,
fontface = row$fontface,
lineheight = row$lineheight
),
name = "text"
)
r <- roundrectGrob(
row$x, row$y, default.units = "native",
width = grobWidth(t) + 2 * x$label.padding,
height = grobHeight(t) + 2 * x$label.padding,
r = x$label.r,
gp = gpar(
col = scales::alpha(row$colour, row$alpha),
fill = scales::alpha(row$fill, row$alpha),
lwd = x$label.size * .pt
),
name = "box"
)
gw <- convertWidth(grobWidth(r), "native", TRUE)
gh <- convertHeight(grobHeight(r), "native", TRUE)
c(
"x1" = row$x - gw * hj - box_padding_x + x$nudges$x[i],
"y1" = row$y - gh * vj - box_padding_y + x$nudges$y[i],
"x2" = row$x + gw * (1 - hj) + box_padding_x + x$nudges$x[i],
"y2" = row$y + gh * (1 - vj) + box_padding_y + x$nudges$y[i]
)
})
# Make the repulsion reproducible if desired.
if (is.null(x$seed) || !is.na(x$seed)) {
set.seed(x$seed)
}
points_valid_first <- cbind(c(x$data$x[valid_strings],
x$data$x[invalid_strings]),
c(x$data$y[valid_strings],
x$data$y[invalid_strings]))
# Repel overlapping bounding boxes away from each other.
repel <- repel_boxes(
data_points = points_valid_first,
point_padding_x = point_padding_x,
point_padding_y = point_padding_y,
boxes = do.call(rbind, boxes),
xlim = range(x$limits$x),
ylim = range(x$limits$y),
hjust = x$data$hjust,
vjust = x$data$vjust,
force = x$force * 1e-6,
maxiter = x$max.iter,
direction = x$direction
)
grobs <- lapply(seq_along(valid_strings), function(i) {
xi <- valid_strings[i]
row <- x$data[xi, , drop = FALSE]
labelRepelGrob(
x$lab[xi],
x = unit(repel$x[i], "native"),
y = unit(repel$y[i], "native"),
x.orig = unit(x$data$x[xi], "native"),
y.orig = unit(x$data$y[xi], "native"),
box.padding = x$box.padding,
label.padding = x$label.padding,
point.padding = x$point.padding,
r = x$label.r,
text.gp = gpar(
col = scales::alpha(row$colour, row$alpha),
fontsize = row$size * .pt,
fontfamily = row$family,
fontface = row$fontface,
lineheight = row$lineheight
),
rect.gp = gpar(
col = scales::alpha(row$colour, row$alpha),
fill = scales::alpha(row$fill, row$alpha),
lwd = x$label.size * .pt
),
segment.gp = gpar(
col = scales::alpha(x$segment.colour %||% row$colour, x$segment.alpha %||% row$alpha),
lwd = x$segment.size * .pt
),
arrow = x$arrow,
min.segment.length = x$min.segment.length,
hjust = x$data$hjust[i],
vjust = x$data$vjust[i]
)
})
class(grobs) <- "gList"
setChildren(x, grobs)
}
labelRepelGrob <- function(
label,
x = unit(0.5, "npc"),
y = unit(0.5, "npc"),
x.orig = unit(0.5, "npc"),
y.orig = unit(0.5, "npc"),
default.units = "npc",
just = "center",
box.padding = 0.25,
label.padding = 0.25,
point.padding = 1e-6,
name = NULL,
text.gp = gpar(),
rect.gp = gpar(fill = "white"),
r = unit(0.1, "snpc"),
segment.gp = gpar(),
vp = NULL,
arrow = NULL,
min.segment.length = 0.5,
hjust = 0.5,
vjust = 0.5
) {
stopifnot(length(label) == 1)
if (!is.unit(x))
x <- unit(x, default.units)
if (!is.unit(y))
y <- unit(y, default.units)
gTree(
label = label,
# Position of text bounding boxes.
x = x,
y = y,
# Position of original data points.
x.orig = x.orig,
y.orig = y.orig,
just = just,
box.padding = box.padding,
label.padding = label.padding,
point.padding = point.padding,
r = r,
name = name,
text.gp = text.gp,
rect.gp = rect.gp,
segment.gp = segment.gp,
vp = vp,
cl = "labelrepelgrob",
arrow = arrow,
min.segment.length = min.segment.length,
hjust = hjust,
vjust = vjust
)
}
#' grid::makeContent function for labelRepelGrob.
#'
#' @param x A grid grob.
#' @export
#' @noRd
makeContent.labelrepelgrob <- function(x) {
hj <- resolveHJust(x$just, NULL)
vj <- resolveVJust(x$just, NULL)
t <- textGrob(
x$label,
x$x + 2 * (0.5 - hj) * x$box.padding,
x$y + 2 * (0.5 - vj) * x$box.padding,
just = c(hj, vj),
gp = x$text.gp,
name = "text"
)
r <- roundrectGrob(
x$x + 2 * (0.5 - hj) * x$box.padding,
x$y + 2 * (0.5 - vj) * x$box.padding,
default.units = "native",
width = grobWidth(t) + 2 * x$label.padding,
height = grobHeight(t) + 2 * x$label.padding,
just = c(hj, vj),
r = x$r,
gp = x$rect.gp,
name = "box"
)
x1 <- convertWidth(x$x - 0.5 * grobWidth(r), "native", TRUE)
x2 <- convertWidth(x$x + 0.5 * grobWidth(r), "native", TRUE)
y1 <- convertHeight(x$y - 0.5 * grobHeight(r), "native", TRUE)
y2 <- convertHeight(x$y + 0.5 * grobHeight(r), "native", TRUE)
point_pos <- c(
convertWidth(x$x.orig, "native", TRUE),
convertHeight(x$y.orig, "native", TRUE)
)
center <- centroid(c(x1, y1, x2, y2), x$hjust, x$vjust)
# Get the coordinates of the intersection between the line from the
# original data point to the centroid and the rectangle's edges.
text_box <- c(x1, y1, x2, y2)
#int <- intersect_line_rectangle(point_pos, center, c(x1, y1, x2, y2))
int <- select_line_connection(point_pos, text_box)
# Check if the data point is inside the label box.
point_inside <- FALSE
if (text_box[1] <= point_pos[1] && point_pos[1] <= text_box[3] &&
text_box[2] <= point_pos[2] && point_pos[2] <= text_box[4]) {
point_inside <- TRUE
}
# Nudge the original data point toward the label with point.padding.
point_padding_x <- convertWidth(x$point.padding, "native", TRUE) / 2
point_padding_y <- convertHeight(x$point.padding, "native", TRUE) / 2
point_padding <- point_padding_x > 0 & point_padding_y > 0
if (point_padding) {
point_box <- c(
point_pos[1] - point_padding_x, point_pos[2] - point_padding_y,
point_pos[1] + point_padding_x, point_pos[2] + point_padding_y
)
point_pos <- intersect_line_rectangle(center, point_pos, point_box)
}
# Compute the distance between the data point and the edge of the text box.
dx <- abs(int[1] - point_pos[1])
dy <- abs(int[2] - point_pos[2])
d <- sqrt(dx * dx + dy * dy)
# Scale the unit vector by the minimum segment length.
if (d > 0) {
mx <- convertWidth(x$min.segment.length, "native", TRUE)
my <- convertHeight(x$min.segment.length, "native", TRUE)
min.segment.length <- sqrt((mx * dx / d) ^ 2 + (my * dy / d) ^ 2)
}
if (!point_inside && d > 0 && euclid(int, point_pos) > min.segment.length) {
s <- segmentsGrob(
x0 = int[1],
y0 = int[2],
x1 = point_pos[1],
y1 = point_pos[2],
default.units = "native",
gp = x$segment.gp,
name = "segment",
arrow = x$arrow
)
setChildren(x, gList(s, r, t))
} else {
setChildren(x, gList(r, t))
}
}
ggrepel/R/geom-text-repel.R 0000644 0001762 0000144 00000045416 13464162353 015262 0 ustar ligges users #' Repulsive textual annotations.
#'
#' \code{geom_text_repel} adds text directly to the plot.
#' \code{geom_label_repel} draws a rectangle underneath the text, making it
#' easier to read. The text labels repel away from each other and away from
#' the data points.
#'
#' These geoms are based on \code{\link[ggplot2]{geom_text}} and
#' \code{\link[ggplot2]{geom_label}}. See the documentation for those
#' functions for more details. Differences from those functions are noted
#' here.
#'
#' Text labels have height and width, but they are physical units, not data
#' units. The amount of space they occupy on that plot is not constant in data
#' units: when you resize a plot, labels stay the same size, but the size of
#' the axes changes. The text labels are repositioned after resizing a plot.
#'
#' @section \code{geom_label_repel}:
#' Currently \code{geom_label_repel} does not support the \code{rot} argument
#' and is considerably slower than \code{geom_text_repel}. The \code{fill}
#' aesthetic controls the background colour of the label.
#'
#' @section Alignment with \code{hjust} or \code{vjust}:
#' The arguments \code{hjust} and \code{vjust} are supported, but they only
#' control the initial positioning, so repulsive forces may disrupt alignment.
#' Alignment with \code{hjust} will be preserved if labels only move up and down
#' by using \code{direction="y"}. For \code{vjust}, use \code{direction="x"}.
#'
#' @param mapping Set of aesthetic mappings created by \code{\link[ggplot2]{aes}} or
#' \code{\link[ggplot2]{aes_}}. If specified and \code{inherit.aes = TRUE} (the
#' default), is combined with the default mapping at the top level of the
#' plot. You only need to supply \code{mapping} if there isn't a mapping
#' defined for the plot.
#' @param data A data frame. If specified, overrides the default data frame
#' defined at the top level of the plot.
#' @param stat The statistical transformation to use on the data for this
#' layer, as a string.
#' @param position Position adjustment, either as a string, or the result of
#' a call to a position adjustment function.
#' @param parse If TRUE, the labels will be parsed into expressions and
#' displayed as described in ?plotmath
#' @param na.rm If \code{FALSE} (the default), removes missing values with
#' a warning. If \code{TRUE} silently removes missing values.
#' @param show.legend logical. Should this layer be included in the legends?
#' \code{NA}, the default, includes if any aesthetics are mapped.
#' \code{FALSE} never includes, and \code{TRUE} always includes.
#' @param inherit.aes If \code{FALSE}, overrides the default aesthetics,
#' rather than combining with them. This is most useful for helper functions
#' that define both data and aesthetics and shouldn't inherit behaviour from
#' the default plot specification, e.g. \code{\link[ggplot2]{borders}}.
#' @param ... other arguments passed on to \code{\link[ggplot2]{layer}}. There are
#' three types of arguments you can use here:
#'
#' \itemize{
#' \item Aesthetics: to set an aesthetic to a fixed value, like
#' \code{colour = "red"} or \code{size = 3}.
#' \item Other arguments to the layer, for example you override the
#' default \code{stat} associated with the layer.
#' \item Other arguments passed on to the stat.
#' }
#' @param nudge_x,nudge_y Horizontal and vertical adjustments to nudge the
#' starting position of each text label.
#' @param xlim,ylim Limits for the x and y axes. Text labels will be constrained
#' to these limits. By default, text labels are constrained to the entire plot
#' area.
#' @param box.padding Amount of padding around bounding box, as unit or number.
#' Defaults to 0.25. (Default unit is lines, but other units can be specified
#' by passing \code{unit(x, "units")}).
#' @param point.padding Amount of padding around labeled point, as unit or
#' number. Defaults to 0. (Default unit is lines, but other units can be
#' specified by passing \code{unit(x, "units")}).
#' @param segment.size Width of line segment connecting the data point to
#' the text label, in mm.
#' @param segment.colour,segment.color Colour of the line segment. Defaults to the same colour
#' as the text. In the unlikely event you specify both US and UK spellings of colour, the
#' US spelling will take precedence.
#' @param segment.alpha Transparency of the line segment. Defaults to the same
#' transparency as the text.
#' @param min.segment.length Skip drawing segments shorter than this, as unit or
#' number. Defaults to 0.5. (Default unit is lines, but other units can be
#' specified by passing \code{unit(x, "units")}).
#' @param arrow specification for arrow heads, as created by \code{\link[grid]{arrow}}
#' @param force Force of repulsion between overlapping text labels. Defaults
#' to 1.
#' @param max.iter Maximum number of iterations to try to resolve overlaps.
#' Defaults to 2000.
#' @param direction "both", "x", or "y" -- direction in which to adjust position of labels
#' @param seed Random seed passed to \code{\link[base]{set.seed}}. Defaults to
#' \code{NA}, which means that \code{set.seed} will not be called.
#'
#' @examples
#'
#' p <- ggplot(mtcars,
#' aes(wt, mpg, label = rownames(mtcars), colour = factor(cyl))) +
#' geom_point()
#'
#' # Avoid overlaps by repelling text labels
#' p + geom_text_repel()
#' # Labels with background
#' p + geom_label_repel()
#'
#' \dontrun{
#' p + geom_text_repel(family = "Times New Roman",
#' box.padding = 0.5)
#'
#' # Add aesthetic mappings
#' p + geom_text_repel(aes(alpha=wt, size=mpg))
#' p + geom_label_repel(aes(fill=factor(cyl)), colour="white", segment.colour="black")
#'
#' # Draw all line segments
#' p + geom_text_repel(min.segment.length = 0)
#'
#' # Omit short line segments (default behavior)
#' p + geom_text_repel(min.segment.length = 0.5)
#'
#' # Omit all line segments
#' p + geom_text_repel(segment.colour = NA)
#'
#' # Repel just the labels and totally ignore the data points
#' p + geom_text_repel(point.padding = NA)
#'
#' # Hide some of the labels, but repel from all data points
#' mtcars$label <- rownames(mtcars)
#' mtcars$label[1:15] <- ""
#' p + geom_text_repel(data = mtcars, aes(wt, mpg, label = label))
#'
#' # Nudge the starting positions
#' p + geom_text_repel(nudge_x = ifelse(mtcars$cyl == 6, 1, 0),
#' nudge_y = ifelse(mtcars$cyl == 6, 8, 0))
#'
#' # Change the text size
#' p + geom_text_repel(aes(size = wt))
#' # Scale height of text, rather than sqrt(height)
#' p + geom_text_repel(aes(size = wt)) + scale_radius(range = c(3,6))
#'
#' # You can display expressions by setting parse = TRUE. The
#' # details of the display are described in ?plotmath, but note that
#' # geom_text_repel uses strings, not expressions.
#' p + geom_text_repel(aes(label = paste(wt, "^(", cyl, ")", sep = "")),
#' parse = TRUE)
#'
#' # Add a text annotation
#' p +
#' geom_text_repel() +
#' annotate(
#' "text", label = "plot mpg vs. wt",
#' x = 2, y = 15, size = 8, colour = "red"
#' )
#'
#' # Add arrows
#' p +
#' geom_point(colour = "red") +
#' geom_text_repel(
#' arrow = arrow(length = unit(0.02, "npc")),
#' box.padding = 1
#' )
#'
#' }
#' @export
geom_text_repel <- function(
mapping = NULL, data = NULL, stat = "identity", position = "identity",
parse = FALSE,
...,
box.padding = 0.25,
point.padding = 1e-6,
segment.colour = NULL,
segment.color = NULL,
segment.size = 0.5,
segment.alpha = NULL,
min.segment.length = 0.5,
arrow = NULL,
force = 1,
max.iter = 2000,
nudge_x = 0,
nudge_y = 0,
xlim = c(NA, NA),
ylim = c(NA, NA),
na.rm = FALSE,
show.legend = NA,
direction = c("both","y","x"),
seed = NA,
inherit.aes = TRUE
) {
if (!missing(nudge_x) || !missing(nudge_y)) {
if (!missing(position)) {
stop("Specify either `position` or `nudge_x`/`nudge_y`", call. = FALSE)
}
#position <- position_nudge(nudge_x, nudge_y)
}
layer(
data = data,
mapping = mapping,
stat = stat,
geom = GeomTextRepel,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(
parse = parse,
na.rm = na.rm,
box.padding = to_unit(box.padding),
point.padding = to_unit(point.padding),
segment.colour = segment.color %||% segment.colour,
segment.size = segment.size,
segment.alpha = segment.alpha,
min.segment.length = to_unit(min.segment.length),
arrow = arrow,
force = force,
max.iter = max.iter,
nudge_x = nudge_x,
nudge_y = nudge_y,
xlim = xlim,
ylim = ylim,
direction = match.arg(direction),
seed = seed,
...
)
)
}
#' GeomTextRepel
#' @rdname ggplot2-ggproto
#' @format NULL
#' @usage NULL
#' @export
GeomTextRepel <- ggproto("GeomTextRepel", Geom,
required_aes = c("x", "y", "label"),
default_aes = aes(
colour = "black", size = 3.88, angle = 0,
alpha = NA, family = "", fontface = 1, lineheight = 1.2,
hjust = 0.5, vjust = 0.5
),
draw_panel = function(
data, panel_scales, coord,
parse = FALSE,
na.rm = FALSE,
box.padding = 0.25,
point.padding = 1e-6,
segment.colour = NULL,
segment.size = 0.5,
segment.alpha = NULL,
min.segment.length = 0.5,
arrow = NULL,
force = 1,
max.iter = 2000,
nudge_x = 0,
nudge_y = 0,
xlim = c(NA, NA),
ylim = c(NA, NA),
direction = "both",
seed = NA
) {
lab <- data$label
if (parse) {
lab <- parse(text = as.character(lab))
}
if (!length(which(not_empty(lab)))) {
return()
}
# Transform the nudges to the panel scales.
nudges <- data.frame(
x = data$x + nudge_x,
y = data$y + nudge_y
)
nudges <- coord$transform(nudges, panel_scales)
# Transform the raw data to the panel scales.
data <- coord$transform(data, panel_scales)
# The nudge is relative to the data.
nudges$x <- nudges$x - data$x
nudges$y <- nudges$y - data$y
# Transform limits to panel scales.
limits <- data.frame(x = xlim, y = ylim)
limits <- coord$transform(limits, panel_scales)
# Fill NAs with defaults.
limits$x[is.na(limits$x)] <- c(0, 1)[is.na(limits$x)]
limits$y[is.na(limits$y)] <- c(0, 1)[is.na(limits$y)]
# Convert hjust and vjust to numeric if character
if (is.character(data$vjust)) {
data$vjust <- compute_just(data$vjust, data$y)
}
if (is.character(data$hjust)) {
data$hjust <- compute_just(data$hjust, data$x)
}
ggname("geom_text_repel", gTree(
limits = limits,
data = data,
lab = lab,
nudges = nudges,
box.padding = to_unit(box.padding),
point.padding = to_unit(point.padding),
segment.colour = segment.colour,
segment.size = segment.size,
segment.alpha = segment.alpha,
min.segment.length = to_unit(min.segment.length),
arrow = arrow,
force = force,
max.iter = max.iter,
direction = direction,
seed = seed,
cl = "textrepeltree"
))
},
draw_key = draw_key_text
)
#' grid::makeContent function for the grobTree of textRepelGrob objects
#' @param x A grid grobTree.
#' @export
#' @noRd
makeContent.textrepeltree <- function(x) {
# The padding around each bounding box.
box_padding_x <- convertWidth(x$box.padding, "native", valueOnly = TRUE)
box_padding_y <- convertHeight(x$box.padding, "native", valueOnly = TRUE)
# The padding around each point.
if (is.na(x$point.padding)) {
x$point.padding = unit(0, "lines")
}
point_padding_x <- convertWidth(x$point.padding, "native", valueOnly = TRUE)
point_padding_y <- convertHeight(x$point.padding, "native", valueOnly = TRUE)
# Do not create text labels for empty strings.
valid_strings <- which(not_empty(x$lab))
invalid_strings <- which(!not_empty(x$lab))
# Create a dataframe with x1 y1 x2 y2
boxes <- lapply(valid_strings, function(i) {
row <- x$data[i, , drop = FALSE]
hj <- x$data$hjust[i]
vj <- x$data$vjust[i]
tg <- textGrob(
x$lab[i],
row$x, row$y, default.units = "native",
rot = row$angle,
gp = gpar(
fontsize = row$size * .pt,
fontfamily = row$family,
fontface = row$fontface,
lineheight = row$lineheight
)
)
gw <- convertWidth(grobWidth(tg), "native", TRUE)
gh <- convertHeight(grobHeight(tg), "native", TRUE)
c(
"x1" = row$x - gw * hj - box_padding_x + x$nudges$x[i],
"y1" = row$y - gh * vj - box_padding_y + x$nudges$y[i],
"x2" = row$x + gw * (1 - hj) + box_padding_x + x$nudges$x[i],
"y2" = row$y + gh * (1 - vj) + box_padding_y + x$nudges$y[i]
)
})
# Make the repulsion reproducible if desired.
if (is.null(x$seed) || !is.na(x$seed)) {
set.seed(x$seed)
}
points_valid_first <- cbind(c(x$data$x[valid_strings],
x$data$x[invalid_strings]),
c(x$data$y[valid_strings],
x$data$y[invalid_strings]))
# Repel overlapping bounding boxes away from each other.
repel <- repel_boxes(
data_points = points_valid_first,
point_padding_x = point_padding_x,
point_padding_y = point_padding_y,
boxes = do.call(rbind, boxes),
xlim = range(x$limits$x),
ylim = range(x$limits$y),
hjust = x$data$hjust,
vjust = x$data$vjust,
force = x$force * 1e-6,
maxiter = x$max.iter,
direction = x$direction
)
grobs <- lapply(seq_along(valid_strings), function(i) {
xi <- valid_strings[i]
row <- x$data[xi, , drop = FALSE]
# browser()
textRepelGrob(
x$lab[xi],
# Position of text bounding boxes.
x = unit(repel$x[i], "native"),
y = unit(repel$y[i], "native"),
# Position of original data points.
x.orig = unit(x$data$x[xi], "native"),
y.orig = unit(x$data$y[xi], "native"),
rot = row$angle,
box.padding = x$box.padding,
point.padding = x$point.padding,
text.gp = gpar(
col = scales::alpha(row$colour, row$alpha),
fontsize = row$size * .pt,
fontfamily = row$family,
fontface = row$fontface,
lineheight = row$lineheight
),
segment.gp = gpar(
col = scales::alpha(x$segment.colour %||% row$colour, x$segment.alpha %||% row$alpha),
lwd = x$segment.size * .pt
),
arrow = x$arrow,
min.segment.length = x$min.segment.length,
hjust = x$data$hjust[i],
vjust = x$data$vjust[i]
)
})
class(grobs) <- "gList"
setChildren(x, grobs)
}
textRepelGrob <- function(
label,
# Position of text bounding boxes.
x = unit(0.5, "npc"),
y = unit(0.5, "npc"),
# Position of original data points.
x.orig = unit(0.5, "npc"),
y.orig = unit(0.5, "npc"),
rot = 0,
default.units = "npc",
just = "center",
box.padding = 0.25,
point.padding = 1e-6,
name = NULL,
text.gp = gpar(),
segment.gp = gpar(),
vp = NULL,
arrow = NULL,
min.segment.length = 0.5,
hjust = 0.5,
vjust = 0.5
) {
stopifnot(length(label) == 1)
if (!is.unit(x))
x <- unit(x, default.units)
if (!is.unit(y))
y <- unit(y, default.units)
gTree(
label = label,
# Position of text bounding boxes.
x = x,
y = y,
# Position of original data points.
x.orig = x.orig,
y.orig = y.orig,
rot = rot,
just = just,
box.padding = box.padding,
point.padding = point.padding,
name = name,
text.gp = text.gp,
segment.gp = segment.gp,
vp = vp,
cl = "textrepelgrob",
arrow = arrow,
min.segment.length = min.segment.length,
hjust = hjust,
vjust = vjust
)
}
#' grid::makeContent function for textRepelGrob.
#'
#' @param x A grid grob.
#' @export
#' @noRd
makeContent.textrepelgrob <- function(x) {
hj <- resolveHJust(x$just, NULL)
vj <- resolveVJust(x$just, NULL)
t <- textGrob(
x$label,
x$x + 2 * (0.5 - hj) * x$box.padding,
x$y + 2 * (0.5 - vj) * x$box.padding,
rot = x$rot,
just = c(hj, vj),
gp = x$text.gp,
name = "text"
)
x1 <- convertWidth(x$x - 0.5 * grobWidth(t), "native", TRUE)
x2 <- convertWidth(x$x + 0.5 * grobWidth(t), "native", TRUE)
y1 <- convertHeight(x$y - 0.5 * grobHeight(t), "native", TRUE)
y2 <- convertHeight(x$y + 0.5 * grobHeight(t), "native", TRUE)
point_pos <- c(
convertWidth(x$x.orig, "native", TRUE),
convertHeight(x$y.orig, "native", TRUE)
)
center <- centroid(c(x1, y1, x2, y2), x$hjust, x$vjust)
# Get the coordinates of the intersection between the line from the
# original data point to the centroid and the rectangle's edges.
extra_padding_x <- convertWidth(unit(0.25, "lines"), "native", TRUE) / 2
extra_padding_y <- convertHeight(unit(0.25, "lines"), "native", TRUE) / 2
text_box <- c(
x1 - extra_padding_x, y1 - extra_padding_y,
x2 + extra_padding_x, y2 + extra_padding_y
)
#int <- intersect_line_rectangle(point_pos, center, text_box)
int <- select_line_connection(point_pos, text_box)
# Check if the data point is inside the label box.
point_inside <- FALSE
if (text_box[1] <= point_pos[1] && point_pos[1] <= text_box[3] &&
text_box[2] <= point_pos[2] && point_pos[2] <= text_box[4]) {
point_inside <- TRUE
}
# Nudge the original data point toward the label with point.padding.
point_padding_x <- convertWidth(x$point.padding, "native", TRUE) / 2
point_padding_y <- convertHeight(x$point.padding, "native", TRUE) / 2
point_padding <- point_padding_x > 0 & point_padding_y > 0
if (point_padding) {
point_box <- c(
point_pos[1] - point_padding_x, point_pos[2] - point_padding_y,
point_pos[1] + point_padding_x, point_pos[2] + point_padding_y
)
point_pos <- intersect_line_rectangle(center, point_pos, point_box)
}
# Compute the distance between the data point and the edge of the text box.
dx <- abs(int[1] - point_pos[1])
dy <- abs(int[2] - point_pos[2])
d <- sqrt(dx * dx + dy * dy)
# Scale the unit vector by the minimum segment length.
if (d > 0) {
mx <- convertWidth(x$min.segment.length, "native", TRUE)
my <- convertHeight(x$min.segment.length, "native", TRUE)
min.segment.length <- sqrt((mx * dx / d) ^ 2 + (my * dy / d) ^ 2)
}
if (!point_inside && d > 0 && euclid(int, point_pos) > min.segment.length) {
s <- segmentsGrob(
x0 = int[1],
y0 = int[2],
x1 = point_pos[1],
y1 = point_pos[2],
default.units = "native",
gp = x$segment.gp,
name = "segment",
arrow = x$arrow
)
setChildren(x, gList(s, t))
} else {
setChildren(x, gList(t))
}
}
# copied from ggplot2
compute_just <- function(just, x) {
inward <- just == "inward"
just[inward] <- c("left", "middle", "right")[just_dir(x[inward])]
outward <- just == "outward"
just[outward] <- c("right", "middle", "left")[just_dir(x[outward])]
unname(c(left = 0, center = 0.5, right = 1,
bottom = 0, middle = 0.5, top = 1)[just])
}
# copied from ggplot2
just_dir <- function(x, tol = 0.001) {
out <- rep(2L, length(x))
out[x < 0.5 - tol] <- 1L
out[x > 0.5 + tol] <- 3L
out
}
ggrepel/vignettes/ 0000755 0001762 0000144 00000000000 13464164104 013712 5 ustar ligges users ggrepel/vignettes/ggrepel.Rmd 0000644 0001762 0000144 00000025330 13464162353 016012 0 ustar ligges users ---
title: "ggrepel examples"
author: "Kamil Slowikowski"
date: "`r Sys.Date()`"
output:
prettydoc::html_pretty:
theme: hpstr
highlight: github
toc: true
mathjax: null
self_contained: true
vignette: >
%\VignetteIndexEntry{ggrepel examples}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, echo=FALSE, results='hide', warning=FALSE, error=FALSE, message=FALSE, cache=FALSE}
library(knitr)
opts_chunk$set(
cache = TRUE,
autodep = TRUE,
echo = FALSE,
warning = FALSE,
error = FALSE,
message = FALSE,
out.width = 700,
fig.width = 12,
fig.height = 8,
dpi = 300,
cache.path = "cache/ggrepel/",
fig.path = "figures/ggrepel/",
pngquant = "--speed=1 --quality=0-10",
concordance = TRUE
)
knit_hooks$set(
pngquant = hook_pngquant
)
library(gridExtra)
library(ggplot2)
theme_set(theme_classic(base_size = 18) %+replace% theme(
axis.line.y = element_line(colour = "black", size = 0.2),
axis.line.x = element_line(colour = "black", size = 0.2),
axis.ticks = element_line(colour = "black", size = 0.2)
))
```
## Overview
ggrepel provides geoms for [ggplot2] to repel overlapping text labels:
- `geom_text_repel()`
- `geom_label_repel()`
[ggplot2]: http://ggplot2.tidyverse.org/
Text labels repel away from each other, away from data points, and away
from edges of the plotting area.
Let's compare `geom_text()` and `geom_text_repel()`:
```{r comparison, echo=TRUE, fig.width=9, fig.height=4}
library(ggrepel)
set.seed(42)
dat <- subset(mtcars, wt > 2.75 & wt < 3.45)
dat$car <- rownames(dat)
p <- ggplot(dat, aes(wt, mpg, label = car)) +
geom_point(color = "red")
p1 <- p + geom_text() + labs(title = "geom_text()")
p2 <- p + geom_text_repel() + labs(title = "geom_text_repel()")
gridExtra::grid.arrange(p1, p2, ncol = 2)
```
## Installation
[ggrepel version `r packageVersion("ggrepel")`][cran] is available on CRAN:
```{r install-cran, echo=TRUE, eval=FALSE}
install.packages("ggrepel")
```
The [latest development version][github] may have new features, and you can get
it from GitHub:
```{r install-github, echo=TRUE, eval=FALSE}
# Use the devtools package
# install.packages("devtools")
devtools::install_github("slowkow/ggrepel")
# Or use the install-github.me service
source("https://install-github.me/slowkow/ggrepel")
```
[cran]: https://CRAN.R-project.org/package=ggrepel
[github]: https://github.com/slowkow/ggrepel
## Options
Options available for [geom_text()][geom_text] and [geom_label()][geom_text]
are also available for `geom_text_repel()` and `geom_label_repel()`,
including `size`, `angle`, `family`, `fontface`, etc.
[geom_text]: http://ggplot2.tidyverse.org/reference/geom_text.html
ggrepel provides additional options for `geom_text_repel` and `geom_label_repel`:
|Option | Default | Description
|--------------- | --------- | ------------------------------------------------
|`force` | `1` | force of repulsion between overlapping text labels
|`direction` | `"both"` | move text labels "both" (default), "x", or "y" directions
|`max.iter` | `2000` | maximum number of iterations to try to resolve overlaps
|`nudge_x` | `0` | adjust the starting x position of the text label
|`nudge_y` | `0` | adjust the starting y position of the text label
|`box.padding` | `0.25 lines` | padding around the text label
|`point.padding` | `0 lines` | padding around the labeled data point
|`segment.color` | `"black"` | line segment color
|`segment.size` | `0.5 mm` | line segment thickness
|`segment.alpha` | `1.0` | line segment transparency
|`arrow` | `NULL` | render line segment as an arrow with `grid::arrow()`
## Examples
### Hide some of the labels
Set labels to the empty string `""` to hide them. All data points repel the
non-empty labels.
```{r empty_string, echo=TRUE, fig.width=5, fig.height=4}
set.seed(42)
dat2 <- subset(mtcars, wt > 3 & wt < 4)
# Hide all of the text labels.
dat2$car <- ""
# Let's just label these items.
ix_label <- c(2,3,16)
dat2$car[ix_label] <- rownames(dat2)[ix_label]
ggplot(dat2, aes(wt, mpg, label = car)) +
geom_point(color = ifelse(dat2$car == "", "grey50", "red")) +
geom_text_repel()
```
### Do not repel labels from data points
Set `point.padding = NA` to prevent label repulsion away from data points.
Now labels move away from each other and away from the edges of the plot.
```{r point_padding_na, echo=TRUE, fig.width=5, fig.height=4}
set.seed(42)
ggplot(dat, aes(wt, mpg, label = car)) +
geom_point(color = "red") +
geom_text_repel(point.padding = NA)
```
### Limit labels to a specific area
Use options `xlim` and `ylim` to constrain the labels to a specific area.
Limits are specified in data coordinates. Use `NA` when there is no lower or
upper bound in a particular direction.
Here we also use `grid::arrow()` to render the segments as arrows.
```{r xlim, echo=TRUE, fig.width=5, fig.height=4}
set.seed(42)
# All labels should be to the right of 3.
x_limits <- c(3, NA)
ggplot(dat, aes(wt, mpg, label = car, color = factor(cyl))) +
geom_vline(xintercept = x_limits, linetype = 3) +
geom_point() +
geom_label_repel(
arrow = arrow(length = unit(0.03, "npc"), type = "closed", ends = "first"),
force = 10,
xlim = x_limits
) +
scale_color_discrete(name = "cyl")
```
### Align labels on the top or bottom edge
Use `hjust` or `vjust` to justify the text neatly:
- `hjust = 0` for left-align
- `hjust = 0.5` for center
- `hjust = 1` for right-align
Sometimes the labels do not align perfectly. Try using `direction = "x"` to
limit label movement to the x-axis (left and right) or `direction = "y"` to
limit movement to the y-axis (up and down). The default is `direction =
"both"`.
Also try using [xlim()][xlim] and [ylim()][ylim] to increase the size of the
plotting area so all of the labels fit comfortably.
```{r direction_x, echo=TRUE, fig.width=9, fig.height=3}
set.seed(42)
ggplot(mtcars, aes(x = wt, y = 1, label = rownames(mtcars))) +
geom_point(color = "red") +
geom_text_repel(
nudge_y = 0.05,
direction = "x",
angle = 90,
vjust = 0,
segment.size = 0.2
) +
xlim(1, 6) +
ylim(1, 0.8) +
theme(
axis.line.y = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank()
)
```
Align text vertically with `nudge_y` and allow the labels to move horizontally
with `direction = "x"`:
```{r neat-offset-x, echo=TRUE, fig.width=7, fig.height=4}
set.seed(42)
dat <- mtcars
dat$car <- rownames(dat)
ggplot(dat, aes(qsec, mpg, label = car)) +
geom_text_repel(
data = subset(dat, mpg > 30),
nudge_y = 36 - subset(dat, mpg > 30)$mpg,
segment.size = 0.2,
segment.color = "grey50",
direction = "x"
) +
geom_point(color = ifelse(dat$mpg > 30, "red", "black")) +
scale_x_continuous(expand = c(0.05, 0.05)) +
scale_y_continuous(limits = c(NA, 36))
```
### Align labels on the left or right edge
Set `direction` to "y" and try `hjust` 0.5, 0, and 1:
```{r direction_y, echo=TRUE, fig.width=10, fig.height=8}
set.seed(42)
p <- ggplot(mtcars, aes(y = wt, x = 1, label = rownames(mtcars))) +
geom_point(color = "red") +
ylim(1, 5.5) +
theme(
axis.line.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.x = element_blank(),
axis.title.x = element_blank(),
plot.title = element_text(hjust = 0.5)
)
p1 <- p +
xlim(1, 1.375) +
geom_text_repel(
nudge_x = 0.15,
direction = "y",
hjust = 0,
segment.size = 0.2
) +
ggtitle("hjust = 0")
p2 <- p +
xlim(1, 1.375) +
geom_text_repel(
nudge_x = 0.2,
direction = "y",
hjust = 0.5,
segment.size = 0.2
) +
ggtitle("hjust = 0.5 (default)")
p3 <- p +
xlim(0.25, 1) +
scale_y_continuous(position = "right") +
geom_text_repel(
nudge_x = -0.35,
direction = "y",
hjust = 1,
segment.size = 0.2
) +
ggtitle("hjust = 1")
gridExtra::grid.arrange(p1, p2, p3, ncol = 3)
```
Align text horizontally with `nudge_x` and `hjust`, and allow the labels to
move vertically with `direction = "y"`:
```{r neat-offset-y, echo=TRUE, fig.width=7, fig.height=4}
set.seed(42)
dat <- subset(mtcars, wt > 2.75 & wt < 3.45)
dat$car <- rownames(dat)
ggplot(dat, aes(wt, mpg, label = car)) +
geom_text_repel(
data = subset(dat, wt > 3),
nudge_x = 3.5 - subset(dat, wt > 3)$wt,
segment.size = 0.2,
segment.color = "grey50",
direction = "y",
hjust = 0
) +
geom_text_repel(
data = subset(dat, wt < 3),
nudge_x = 2.7 - subset(dat, wt < 3)$wt,
segment.size = 0.2,
segment.color = "grey50",
direction = "y",
hjust = 1
) +
scale_x_continuous(
breaks = c(2.5, 2.75, 3, 3.25, 3.5),
limits = c(2.4, 3.8)
) +
geom_point(color = "red")
```
### Polar coordinates
```{r polar, echo=TRUE, fig.width=5, fig.height=4}
set.seed(42)
mtcars$label <- rownames(mtcars)
mtcars$label[mtcars$mpg < 25] <- ""
ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl), label = label)) +
coord_polar(theta = "x") +
geom_point(size = 2) +
scale_color_discrete(name = "cyl") +
geom_text_repel(show.legend = FALSE) + # Don't display "a" in the legend.
theme_bw(base_size = 18)
```
### Mathematical expressions
```{r math, echo=TRUE, fig.width=5, fig.height=4}
d <- data.frame(
x = c(1, 2, 2, 1.75, 1.25),
y = c(1, 3, 1, 2.65, 1.25),
math = c(
NA,
"integral(f(x) * dx, a, b)",
NA,
"lim(f(x), x %->% 0)",
NA
)
)
ggplot(d, aes(x, y, label = math)) +
geom_point() +
geom_label_repel(
parse = TRUE, # Parse mathematical expressions.
size = 8,
box.padding = 2
)
```
### Animation
```{r animated, echo=TRUE, eval=FALSE}
# This chunk of code will take a minute or two to run.
library(ggrepel)
library(animation)
plot_frame <- function(n) {
set.seed(42)
p <- ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars))) +
geom_point(color = "red") +
geom_text_repel(
size = 5, force = 3, max.iter = n
) +
theme_minimal(base_size = 16)
print(p)
}
saveGIF(
lapply(ceiling(1.75^(1:12)), function(i) {
plot_frame(i)
}),
interval = 0.20,
ani.width = 800,
ani.heigth = 600,
movie.name = "animated.gif"
)
```
Click here to see the animation.
## Source code
View the [source code for this vignette][source] on GitHub.
[source]: https://github.com/slowkow/ggrepel/blob/master/vignettes/ggrepel.Rmd
## R Session Info
```{r session_info, echo=TRUE}
sessionInfo()
```
[xlim]: http://ggplot2.tidyverse.org/reference/lims.html
[ylim]: http://ggplot2.tidyverse.org/reference/lims.html
ggrepel/README.md 0000644 0001762 0000144 00000012110 13464162353 013160 0 ustar ligges users ggrepel
============================================
[![GitHub Version][gb]][releases] [![Build Status][bb]][travis] [![CRAN_Status_Badge][cb]][cran] [![CRAN_Downloads_Badge][db]][r-pkg]
[cb]: http://www.r-pkg.org/badges/version/ggrepel?color=blue
[cran]: https://CRAN.R-project.org/package=ggrepel
[db]: http://cranlogs.r-pkg.org/badges/grand-total/ggrepel?color=blue
[r-pkg]: https://www.r-pkg.org/pkg/ggrepel
[gb]: https://badge.fury.io/gh/slowkow%2Fggrepel.svg?text=Github
[releases]: https://github.com/slowkow/ggrepel/releases
[bb]: https://travis-ci.org/slowkow/ggrepel.svg?branch=master
[travis]: https://travis-ci.org/slowkow/ggrepel
Overview
--------
ggrepel provides geoms for [ggplot2] to repel overlapping text labels:
- `geom_text_repel()`
- `geom_label_repel()`
Text labels repel away from each other, away from data points, and away
from edges of the plotting area.
```r
library(ggrepel)
ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars))) +
geom_text_repel() +
geom_point(color = 'red') +
theme_classic(base_size = 16)
```
Installation
------------
```r
# The easiest way to get ggrepel is to install it from CRAN:
install.packages("ggrepel")
# Or get the the development version from GitHub:
# install.packages("devtools")
devtools::install_github("slowkow/ggrepel")
# Or use the install-github.me service
source("https://install-github.me/slowkow/ggrepel")
```
Usage
-----
See the [vignette] for the code behind these examples:
Contributing
------------
Please [submit an issue][issues] to report bugs or ask questions.
Please contribute bug fixes or new features with a [pull request][pull] to this
repository.
[issues]: https://github.com/slowkow/ggrepel/issues
[pull]: https://help.github.com/articles/using-pull-requests/
Related work
------------
### Academic Papers
[An Efficient Algorithm for Scatter Chart Labeling][aaai]
Sebastian Theophil, Arno Schödl
> This paper presents an efficient algorithm for a new variation of the point
> feature labeling problem. The goal is to position the largest number of point
> labels such that they do not intersect each other or their points. First we
> present an algorithm using a greedy algorithm with limited lookahead. We then
> present an algorithm that iteratively regroups labels, calling the first
> algorithm on each group, thereby identifying a close to optimal labeling
> order. The presented algorithm is being used in a commercial product to label
> charts, and our evaluation shows that it produces results far superior to
> those of other labeling algorithms.
This might be a good start for a revision of ggrepel.
[aaai]: http://www.aaai.org/Papers/AAAI/2006/AAAI06-167.pdf
### Python
[adjustText]
> A small library for automatically adjusting text position in matplotlib plots to minimize overlaps.
Ilya Flyamer's Python library that extends [matplotlib].
[adjustText]: https://github.com/Phlya/adjustText
[matplotlib]: https://matplotlib.org/
### R
[directlabels]
> An extensible framework for automatically placing direct labels onto
> multicolor 'lattice' or 'ggplot2' plots. Label positions are described
> using Positioning Methods which can be re-used across several different
> plots. There are heuristics for examining "trellis" and "ggplot" objects
> and inferring an appropriate Positioning Method.
[wordcloud]
> Pretty word clouds.
The `wordcloud` package implements a spiraling algorithm to prevent text
labels from overlapping each other.
[FField]
> Force field simulation of interaction of set of points. Very useful for
> placing text labels on graphs, such as scatterplots.
I found that functions in the `FField` package were not ideal for repelling
overlapping rectangles, so I wrote my own.
See [this gist][1] for examples of how to use the `wordcloud` and `FField`
packages with `ggplot2`.
[1]: https://gist.github.com/slowkow/003b4d9f3f59cee8551c
[ggplot2]: http://ggplot2.tidyverse.org
[vignette]: https://github.com/slowkow/ggrepel/blob/master/vignettes/ggrepel.md
[directlabels]: https://cran.r-project.org/package=directlabels
[wordcloud]: https://cran.r-project.org/package=wordcloud
[FField]: https://cran.r-project.org/package=FField
ggrepel/MD5 0000644 0001762 0000144 00000002505 13464235406 012220 0 ustar ligges users d817112747d1155bb586b742905efd08 *DESCRIPTION
84dcc94da3adb52b53ae4fa38fe49e5d *LICENSE
c3d761040d680cf96901e3a385da7a87 *NAMESPACE
43777c458aaca546cc42028441d3212d *NEWS.md
4fa12a1c08600210ee39524e527d9406 *R/RcppExports.R
0588f4afab4407cc937aabdc5dc83e1e *R/geom-label-repel.R
2d4f7d722fc2e0c3a79970c0471d8b04 *R/geom-text-repel.R
1830dcf623762df1a023e2dd3c8d82fc *R/ggrepel-package.R
6f89979ca5d0f7ce720ef198286714ab *R/utilities-grid.R
ea2aad613ce4e113a72348188e4d44b5 *R/utilities.R
08d481deeb871240f3aa6609310db8d5 *README.md
db52991adfd69faa88dd422bf6a7851d *build/vignette.rds
4859daab05f7de1617ba48e51bf42c03 *inst/doc/ggrepel.R
e51f005bbf21b3b71dfcbc5e2bb971d4 *inst/doc/ggrepel.Rmd
1f9dd373f86fed38c0e5e254bfd82854 *inst/doc/ggrepel.html
c3cc6c17479c6dc53049bb6c361c8f35 *man/geom_text_repel.Rd
1f6a8b18b06c74020caf457f983bf192 *man/ggplot2-ggproto.Rd
350a4430a59a7a93e9761c1476bb53db *man/ggrepel.Rd
1e98100139c4548b9b6a420ddd0ba574 *src/RcppExports.cpp
728203eb307cbfed2b324164a3937b0c *src/repel_boxes.cpp
a45df5c61fb4700ff320daac04e62925 *tests/testthat.R
4ff949191a6aff10afb6c123488bcc79 *tests/testthat/test-seed.R
451e1c31f240f9567b17269ee82f68d9 *tests/testthat/test-to_unit.R
8e266b876a42b8720a2a126bdbb8f778 *tools/fig.png
7616edfebe8fb1850677ca4ddf703093 *tools/logo.svg
e51f005bbf21b3b71dfcbc5e2bb971d4 *vignettes/ggrepel.Rmd
ggrepel/build/ 0000755 0001762 0000144 00000000000 13464164104 013001 5 ustar ligges users ggrepel/build/vignette.rds 0000644 0001762 0000144 00000000312 13464164104 015334 0 ustar ligges users b```b`fed`b2 1#'NO/J-HMAJ)V$fq:A,Q ,LH
Ys1LfwK î?"5lP5,n90{C2K 7(1
棸(\^P7 @btr$$
.X ggrepel/DESCRIPTION 0000644 0001762 0000144 00000004124 13464235406 013415 0 ustar ligges users Package: ggrepel
Version: 0.8.1
Authors@R: c(
person("Kamil", "Slowikowski", email = "kslowikowski@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-2843-6370")),
person("Alicia", "Schep", role = "ctb", comment = c(ORCID = "0000-0002-3915-0618")),
person("Sean", "Hughes", role = "ctb"),
person("Saulius", "Lukauskas", role = "ctb"),
person("Jean-Olivier", "Irisson", role = "ctb", comment = c(ORCID = "0000-0003-4920-3880")),
person("Zhian N", "Kamvar", role = "ctb", comment = c(ORCID = "0000-0003-1458-7108")),
person("Thompson", "Ryan", role = "ctb", comment = c(ORCID = "0000-0002-0450-8181")),
person("Dervieux", "Christophe", role = "ctb", comment = c(ORCID = "0000-0003-4474-2498")),
person("Yutani", "Hiroaki", role = "ctb"),
person("Pierre", "Gramme", role = "ctb")
)
Title: Automatically Position Non-Overlapping Text Labels with
'ggplot2'
Description: Provides text and label geoms for 'ggplot2' that help to avoid
overlapping text labels. Labels repel away from each other and away from the
data points.
Depends: R (>= 3.0.0), ggplot2 (>= 2.2.0)
Imports: grid, Rcpp, scales (>= 0.3.0)
Suggests: knitr, rmarkdown, testthat, gridExtra, devtools, prettydoc
VignetteBuilder: knitr
License: GPL-3 | file LICENSE
URL: http://github.com/slowkow/ggrepel
BugReports: http://github.com/slowkow/ggrepel/issues
RoxygenNote: 6.0.1
LinkingTo: Rcpp
Encoding: UTF-8
NeedsCompilation: yes
Packaged: 2019-05-07 02:07:00 UTC; slowikow
Author: Kamil Slowikowski [aut, cre] (),
Alicia Schep [ctb] (),
Sean Hughes [ctb],
Saulius Lukauskas [ctb],
Jean-Olivier Irisson [ctb] (),
Zhian N Kamvar [ctb] (),
Thompson Ryan [ctb] (),
Dervieux Christophe [ctb] (),
Yutani Hiroaki [ctb],
Pierre Gramme [ctb]
Maintainer: Kamil Slowikowski
Repository: CRAN
Date/Publication: 2019-05-07 08:00:06 UTC
ggrepel/man/ 0000755 0001762 0000144 00000000000 13464162353 012461 5 ustar ligges users ggrepel/man/ggrepel.Rd 0000644 0001762 0000144 00000001103 13464162272 014370 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/ggrepel-package.R
\docType{package}
\name{ggrepel}
\alias{ggrepel}
\alias{ggrepel-package}
\title{ggrepel}
\description{
This package contains extra geoms for \pkg{ggplot2}.
}
\details{
Please see the help pages listed below:
\itemize{
\item \code{\link{geom_text_repel}}
\item \code{\link{geom_label_repel}}
}
Also see the vignette for more usage examples:
\code{browseVignettes("ggrepel")}
Please report issues and suggest improvements at Github:
\url{https://github.com/slowkow/ggrepel}
}
ggrepel/man/ggplot2-ggproto.Rd 0000644 0001762 0000144 00000000440 13464162272 016003 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/geom-label-repel.R, R/geom-text-repel.R
\docType{data}
\name{GeomLabelRepel}
\alias{GeomLabelRepel}
\alias{GeomTextRepel}
\title{GeomLabelRepel}
\description{
GeomLabelRepel
GeomTextRepel
}
\keyword{datasets}
ggrepel/man/geom_text_repel.Rd 0000644 0001762 0000144 00000020415 13464162353 016134 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/geom-label-repel.R, R/geom-text-repel.R
\name{geom_label_repel}
\alias{geom_label_repel}
\alias{geom_text_repel}
\title{Repulsive textual annotations.}
\usage{
geom_label_repel(mapping = NULL, data = NULL, stat = "identity",
position = "identity", parse = FALSE, ..., box.padding = 0.25,
label.padding = 0.25, point.padding = 1e-06, label.r = 0.15,
label.size = 0.25, segment.colour = NULL, segment.color = NULL,
segment.size = 0.5, segment.alpha = NULL, min.segment.length = 0.5,
arrow = NULL, force = 1, max.iter = 2000, nudge_x = 0, nudge_y = 0,
xlim = c(NA, NA), ylim = c(NA, NA), na.rm = FALSE, show.legend = NA,
direction = c("both", "y", "x"), seed = NA, inherit.aes = TRUE)
geom_text_repel(mapping = NULL, data = NULL, stat = "identity",
position = "identity", parse = FALSE, ..., box.padding = 0.25,
point.padding = 1e-06, segment.colour = NULL, segment.color = NULL,
segment.size = 0.5, segment.alpha = NULL, min.segment.length = 0.5,
arrow = NULL, force = 1, max.iter = 2000, nudge_x = 0, nudge_y = 0,
xlim = c(NA, NA), ylim = c(NA, NA), na.rm = FALSE, show.legend = NA,
direction = c("both", "y", "x"), seed = NA, inherit.aes = TRUE)
}
\arguments{
\item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2]{aes}} or
\code{\link[ggplot2]{aes_}}. If specified and \code{inherit.aes = TRUE} (the
default), is combined with the default mapping at the top level of the
plot. You only need to supply \code{mapping} if there isn't a mapping
defined for the plot.}
\item{data}{A data frame. If specified, overrides the default data frame
defined at the top level of the plot.}
\item{stat}{The statistical transformation to use on the data for this
layer, as a string.}
\item{position}{Position adjustment, either as a string, or the result of
a call to a position adjustment function.}
\item{parse}{If TRUE, the labels will be parsed into expressions and
displayed as described in ?plotmath}
\item{...}{other arguments passed on to \code{\link[ggplot2]{layer}}. There are
three types of arguments you can use here:
\itemize{
\item Aesthetics: to set an aesthetic to a fixed value, like
\code{colour = "red"} or \code{size = 3}.
\item Other arguments to the layer, for example you override the
default \code{stat} associated with the layer.
\item Other arguments passed on to the stat.
}}
\item{box.padding}{Amount of padding around bounding box, as unit or number.
Defaults to 0.25. (Default unit is lines, but other units can be specified
by passing \code{unit(x, "units")}).}
\item{label.padding}{Amount of padding around label, as unit or number.
Defaults to 0.25. (Default unit is lines, but other units can be specified
by passing \code{unit(x, "units")}).}
\item{point.padding}{Amount of padding around labeled point, as unit or
number. Defaults to 0. (Default unit is lines, but other units can be
specified by passing \code{unit(x, "units")}).}
\item{label.r}{Radius of rounded corners, as unit or number. Defaults
to 0.15. (Default unit is lines, but other units can be specified by
passing \code{unit(x, "units")}).}
\item{label.size}{Size of label border, in mm.}
\item{segment.colour, segment.color}{Colour of the line segment. Defaults to the same colour
as the text. In the unlikely event you specify both US and UK spellings of colour, the
US spelling will take precedence.}
\item{segment.size}{Width of line segment connecting the data point to
the text label, in mm.}
\item{segment.alpha}{Transparency of the line segment. Defaults to the same
transparency as the text.}
\item{min.segment.length}{Skip drawing segments shorter than this, as unit or
number. Defaults to 0.5. (Default unit is lines, but other units can be
specified by passing \code{unit(x, "units")}).}
\item{arrow}{specification for arrow heads, as created by \code{\link[grid]{arrow}}}
\item{force}{Force of repulsion between overlapping text labels. Defaults
to 1.}
\item{max.iter}{Maximum number of iterations to try to resolve overlaps.
Defaults to 2000.}
\item{nudge_x, nudge_y}{Horizontal and vertical adjustments to nudge the
starting position of each text label.}
\item{xlim, ylim}{Limits for the x and y axes. Text labels will be constrained
to these limits. By default, text labels are constrained to the entire plot
area.}
\item{na.rm}{If \code{FALSE} (the default), removes missing values with
a warning. If \code{TRUE} silently removes missing values.}
\item{show.legend}{logical. Should this layer be included in the legends?
\code{NA}, the default, includes if any aesthetics are mapped.
\code{FALSE} never includes, and \code{TRUE} always includes.}
\item{direction}{"both", "x", or "y" -- direction in which to adjust position of labels}
\item{seed}{Random seed passed to \code{\link[base]{set.seed}}. Defaults to
\code{NA}, which means that \code{set.seed} will not be called.}
\item{inherit.aes}{If \code{FALSE}, overrides the default aesthetics,
rather than combining with them. This is most useful for helper functions
that define both data and aesthetics and shouldn't inherit behaviour from
the default plot specification, e.g. \code{\link[ggplot2]{borders}}.}
}
\description{
\code{geom_text_repel} adds text directly to the plot.
\code{geom_label_repel} draws a rectangle underneath the text, making it
easier to read. The text labels repel away from each other and away from
the data points.
}
\details{
These geoms are based on \code{\link[ggplot2]{geom_text}} and
\code{\link[ggplot2]{geom_label}}. See the documentation for those
functions for more details. Differences from those functions are noted
here.
Text labels have height and width, but they are physical units, not data
units. The amount of space they occupy on that plot is not constant in data
units: when you resize a plot, labels stay the same size, but the size of
the axes changes. The text labels are repositioned after resizing a plot.
}
\section{\code{geom_label_repel}}{
Currently \code{geom_label_repel} does not support the \code{rot} argument
and is considerably slower than \code{geom_text_repel}. The \code{fill}
aesthetic controls the background colour of the label.
}
\section{Alignment with \code{hjust} or \code{vjust}}{
The arguments \code{hjust} and \code{vjust} are supported, but they only
control the initial positioning, so repulsive forces may disrupt alignment.
Alignment with \code{hjust} will be preserved if labels only move up and down
by using \code{direction="y"}. For \code{vjust}, use \code{direction="x"}.
}
\examples{
p <- ggplot(mtcars,
aes(wt, mpg, label = rownames(mtcars), colour = factor(cyl))) +
geom_point()
# Avoid overlaps by repelling text labels
p + geom_text_repel()
# Labels with background
p + geom_label_repel()
\dontrun{
p + geom_text_repel(family = "Times New Roman",
box.padding = 0.5)
# Add aesthetic mappings
p + geom_text_repel(aes(alpha=wt, size=mpg))
p + geom_label_repel(aes(fill=factor(cyl)), colour="white", segment.colour="black")
# Draw all line segments
p + geom_text_repel(min.segment.length = 0)
# Omit short line segments (default behavior)
p + geom_text_repel(min.segment.length = 0.5)
# Omit all line segments
p + geom_text_repel(segment.colour = NA)
# Repel just the labels and totally ignore the data points
p + geom_text_repel(point.padding = NA)
# Hide some of the labels, but repel from all data points
mtcars$label <- rownames(mtcars)
mtcars$label[1:15] <- ""
p + geom_text_repel(data = mtcars, aes(wt, mpg, label = label))
# Nudge the starting positions
p + geom_text_repel(nudge_x = ifelse(mtcars$cyl == 6, 1, 0),
nudge_y = ifelse(mtcars$cyl == 6, 8, 0))
# Change the text size
p + geom_text_repel(aes(size = wt))
# Scale height of text, rather than sqrt(height)
p + geom_text_repel(aes(size = wt)) + scale_radius(range = c(3,6))
# You can display expressions by setting parse = TRUE. The
# details of the display are described in ?plotmath, but note that
# geom_text_repel uses strings, not expressions.
p + geom_text_repel(aes(label = paste(wt, "^(", cyl, ")", sep = "")),
parse = TRUE)
# Add a text annotation
p +
geom_text_repel() +
annotate(
"text", label = "plot mpg vs. wt",
x = 2, y = 15, size = 8, colour = "red"
)
# Add arrows
p +
geom_point(colour = "red") +
geom_text_repel(
arrow = arrow(length = unit(0.02, "npc")),
box.padding = 1
)
}
}
ggrepel/tools/ 0000755 0001762 0000144 00000000000 13464162353 013046 5 ustar ligges users ggrepel/tools/logo.svg 0000644 0001762 0000144 00000000715 13464162353 014532 0 ustar ligges users