Negative bars!

This commit is contained in:
Ryan Timpe
2019-07-12 14:27:21 -04:00
parent f698bbe0b6
commit 595fbb070c
2 changed files with 256 additions and 234 deletions
+1 -2
View File
@@ -13,14 +13,13 @@
**ggplot Extension**
* `geom_brick_col` for bar charts in the shape of bricks.
* `coord_brick` to prevent chart brick distortion.
* `coord_brick` to prevent chart brick distortion. `coord_brick_flip` for horizontal bars.
### TO DO
* negative bars (knobs at 0)
* negative bars (partial bricks lowest)
* LEGO color themes
* coord_brick(flipped)
* DOCUMENTATION
* Website
* Check()
+255 -232
View File
@@ -1,13 +1,13 @@
#' @export
#' @rdname geom_brick_rect
geom_brick_col <- function(mapping = NULL, data = NULL,
position = "dodge", two_knob = TRUE,
min_radius_for_text = 0.02,
...,
width = NULL,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE) {
position = "dodge", two_knob = TRUE, split_bricks = TRUE,
min_radius_for_text = 0.02,
...,
width = NULL,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE) {
layer(
data = data,
@@ -19,7 +19,8 @@ geom_brick_col <- function(mapping = NULL, data = NULL,
inherit.aes = inherit.aes,
params = list(
width = width,
two_knob = two_knob,
two_knob = two_knob,
split_bricks = split_bricks,
min_radius_for_text = min_radius_for_text,
na.rm = na.rm,
...
@@ -36,232 +37,254 @@ GeomBrickCol <- ggproto("GeomCol", GeomBrick,
default_aes = aes(colour = "#333333", fill = "#C4281B", size = 0.25, linetype = 1,
alpha = NA, label = "LEGO",
angle = 0, family = "", fontface = 1, lineheight = 1.2),
required_aes = c("x", "y"),
# These aes columns are created by setup_data(). They need to be listed here so
# that GeomRect$handle_na() properly removes any bars that fall outside the defined
# limits, not just those for which x and y are outside the limits
non_missing_aes = c("xmin", "xmax", "ymin", "ymax"),
setup_data = function(data, params) {
data$width <- data$width %||%
params$width %||% (resolution(data$x, FALSE) * 0.9)
data$y_sign = sign(data$y)
data$y_abs = abs(data$y)
transform(data,
ymin = pmin(y, 0), ymax = pmax(y, 0),
xmin = x - width / 2, xmax = x + width / 2, width = NULL
)
},
draw_panel = function(self, data, panel_params, coord, linejoin = "mitre",
min_radius_for_text = 0.02, width=NULL, two_knob = TRUE) {
#This happens to EACH panel
if (!coord$is_linear()) {
stop("geom_brick_rect must be used with linear coordinates")
} else {
# print(coord)
# print(coord$is_flipped())
#Brick border ----
required_aes = c("x", "y"),
# These aes columns are created by setup_data(). They need to be listed here so
# that GeomRect$handle_na() properly removes any bars that fall outside the defined
# limits, not just those for which x and y are outside the limits
non_missing_aes = c("xmin", "xmax", "ymin", "ymax"),
setup_data = function(data, params) {
data$width <- data$width %||%
params$width %||% (resolution(data$x, FALSE) * 0.9)
data$sign = sign(data$y)
transform(data,
ymin = pmin(y, 0), ymax = pmax(y, 0),
xmin = x - width / 2, xmax = x + width / 2, width = NULL
)
},
draw_panel = function(self, data, panel_params, coord, linejoin = "mitre",
min_radius_for_text = 0.02, width=NULL,
two_knob = TRUE, split_bricks = TRUE) {
#This happens to EACH panel
if (!coord$is_linear()) {
stop("geom_brick_rect must be used with linear coordinates")
} else {
#Parameters ----
if(two_knob) n_knob <- 2 else n_knob <- 1
coords_rect <- coord$transform(data, panel_params) %>%
dplyr::mutate(size = data$size[1], linetype = data$linetype[1],
colour = data$colour[1], alpha = data$alpha[1])
#Reverse calc for flipped
if(coord$is_flipped()){
coords_rect <- flip_coords(coords_rect)
}
test_coords_rect <<- coords_rect
if(two_knob) n_knob <- 2 else n_knob <- 1
hmm <- coords_rect %>%
dplyr::mutate( brick_width = abs(xmax - xmin)/n_knob,
num_of_1xs = ymax %/% brick_width,
num_of_4xs = ymax %/% (brick_width*4) + 1) #1 for main, 2 of excess
coords_rect_complete_bricks <- 1:max(hmm$num_of_4xs) %>%
purrr::map_dfr(function(kk){
hmm %>%
dplyr::filter(num_of_4xs >= kk) %>%
dplyr::mutate(ymin_orig = ymin, ymax_orig = ymax) %>%
dplyr::rowwise() %>%
dplyr::mutate(ymin_ideal = ymin_orig + (kk-1)*4*brick_width,
ymax_ideal = min(ymax_orig, ymin_ideal + 4*brick_width),
num_of_knobs_in_this_brick = (ymax_ideal - ymin_ideal + 0.001) %/% brick_width,
ymin = ymin_orig + (kk-1)*4*brick_width,
ymax = min(ymax_orig, ymin + num_of_knobs_in_this_brick*brick_width)) %>%
dplyr::ungroup()
})
test_coords_rect2 <<- coords_rect_complete_bricks
coords_rect <- dplyr::bind_rows(
#Knobbed-bricks
coords_rect_complete_bricks %>%
dplyr::filter(num_of_knobs_in_this_brick > 0),
#Unknobbed caps
coords_rect_complete_bricks %>%
dplyr::group_by(PANEL, group) %>%
dplyr::filter((n() > 1 && num_of_knobs_in_this_brick > 0) |
n() == 1) %>%
dplyr::filter(ymax == max(ymax)) %>%
dplyr::ungroup() %>%
dplyr::mutate(ymin = ymax,
ymax = ymax_orig)
)
#Brick border ----
coords_rect <- coord$transform(data, panel_params) %>%
dplyr::mutate(size = data$size[1], linetype = data$linetype[1],
colour = data$colour[1], alpha = data$alpha[1])
#Reverse calc for flipped
if(coord$is_flipped()){
coords_rect <- flip_coords(coords_rect)
}
test_coords_rect <<- coords_rect
# Split the bricks into 4-knob long bricks. This can be turned on and off
if(split_bricks){
brick_dims <- coords_rect %>%
dplyr::mutate( brick_width = abs(xmax - xmin) / n_knob,
num_of_1xs = abs(ymax - ymin + 0.001) %/% brick_width,
num_of_plates = abs(ymax - ymin + 0.001) %/% (brick_width*4) + 1) %>% #Always at least 1 plate
#For negative bars, temporarily make them positive
dplyr::mutate(y_swap = ymin,
ymin = ifelse(sign == -1, ymax, ymin),
ymax = ifelse(sign == -1, ymax + (ymax - y_swap), ymax)) %>%
dplyr::select(-y_swap)
coords_rect_complete_bricks <- 1:max(brick_dims$num_of_plates) %>%
purrr::map_dfr(function(kk){
brick_dims %>%
dplyr::filter(num_of_plates >= kk) %>%
dplyr::mutate(ymin_orig = ymin, ymax_orig = ymax) %>%
dplyr::rowwise() %>%
dplyr::mutate(ystart_ideal = ymin_orig + (kk-1)*4*brick_width,
yend_ideal = min(ymax_orig, ystart_ideal + 4*brick_width),
num_of_knobs_in_this_brick = (abs(ystart_ideal - yend_ideal) + 0.001) %/% brick_width,
ymin = ymin_orig + (kk-1)*4*brick_width,
ymax = min(ymax_orig, ymin + num_of_knobs_in_this_brick*brick_width)) %>%
dplyr::ungroup()
})
test_coords_rect2 <<- coords_rect_complete_bricks
coords_rect_unflipped <- dplyr::bind_rows(
#Knobbed-bricks
coords_rect_complete_bricks %>%
dplyr::filter(num_of_knobs_in_this_brick > 0),
#Unknobbed caps
coords_rect_complete_bricks %>%
dplyr::group_by(PANEL, group) %>%
dplyr::filter((n() > 1 && num_of_knobs_in_this_brick > 0) |
n() == 1) %>%
dplyr::filter(ymax == max(ymax)) %>%
dplyr::ungroup() %>%
dplyr::mutate(ymin = ymax,
ymax = ymax_orig)
)
coords_rect <- coords_rect_unflipped %>%
#Now need to unswap negative bars
dplyr::mutate(
y_swap = ymax,
ymax = ifelse(sign == 1, ymax,
ymin_orig - (ymin - ymin_orig)),
ymin = ifelse(sign == 1, ymin,
ymin_orig - (y_swap - ymin_orig))
) %>%
dplyr::select(-y_swap)
} #End split_bricks
test_coords_rect3 <<- coords_rect
coords_rect$color_intensity <- as.numeric(colSums(col2rgb(coords_rect$fill)))
coords_rect$outline_col <- ifelse(coords_rect$color_intensity < 200, "#CCCCCC", "#333333")
#Un-Reverse calc for flipped
if(coord$is_flipped()){
coords_rect <- flip_coords(coords_rect)
}
gm_brick <- grid::rectGrob(
coords_rect$xmin, coords_rect$ymax,
width = coords_rect$xmax - coords_rect$xmin,
height = coords_rect$ymax - coords_rect$ymin,
default.units = "native",
just = c("left", "top"),
gp = grid::gpar(
col = alpha(coords_rect$outline_col, 0.3),
fill = alpha(coords_rect$fill, coords_rect$alpha),
lwd = coords_rect$size * .pt,
lty = coords_rect$linetype,
linejoin = linejoin,
# `lineend` is a workaround for Windows and intentionally kept unexposed
# as an argument. (c.f. https://github.com/tidyverse/ggplot2/issues/3037#issuecomment-457504667)
lineend = if (identical(linejoin, "round")) "round" else "square"
)
)
# Knob ----
coords <- coord$transform(data, panel_params)
coords_knobs0 <<- coords
#Reverse calc for flipped
if(coord$is_flipped()){
coords <- flip_coords(coords)
}
hmm <- coords %>%
dplyr::mutate(brick_width = abs(xmax - xmin)/n_knob,
num_of_1x1s = (ymax-ymin) %/% brick_width,
knob_radius = brick_width * (5/8) * (1/2) )
coords_knobs0a <<- hmm
coords_knobs <- 1:max(hmm$num_of_1x1s) %>%
purrr::map_dfr(function(kk){
dat <- hmm %>%
dplyr::filter(num_of_1x1s >= kk) %>%
dplyr::mutate(y = ymin + (kk * brick_width) - brick_width/2)
if(two_knob){
dplyr::bind_rows(
dat %>% dplyr::mutate(x = xmin + brick_width/2),
dat %>% dplyr::mutate(x = xmax - brick_width/2)
)
} else {
dat %>% dplyr::mutate(x = xmin + brick_width/2)
}
})
coords_knobs1 <<- coords_knobs
#Outline and text for dark colors
coords_knobs$color_intensity <- as.numeric(colSums(col2rgb(coords_knobs$fill)))
coords_knobs$text_alpha <- ifelse(coords_knobs$color_intensity < 200, 0.3, 0.3)
coords_knobs$text_col <- ifelse(coords_knobs$color_intensity < 200, "#CCCCCC", "#333333")
#Un-Reverse calc for flipped
if(coord$is_flipped()){
coords_knobs <- flip_coords_xy(coords_knobs)
}
gm_knob_shadow <- grid::circleGrob(
coords_knobs$x + (1/4)*coords_knobs$knob_radius,
coords_knobs$y - (1/4)*coords_knobs$knob_radius,
r= coords_knobs$knob_radius,
default.units = "native",
gp = grid::gpar(
col = NA,
fill = alpha("#333333", 0.3),
size = coords_knobs$size * .pt,
lty = coords_knobs$linetype
)
)
gm_knob_base <- grid::circleGrob(
coords_knobs$x, coords_knobs$y,
r= coords_knobs$knob_radius,
default.units = "native",
gp = grid::gpar(
col = alpha(coords_knobs$text_col, coords_knobs$text_alpha),
fill = alpha(coords_knobs$fill, coords_knobs$alpha),
size = coords_knobs$size * .pt,
lty = coords_knobs$linetype
)
)
#Text ----
#Don't draw if there are more knobs than threshold size
n <- nrow(coords_knobs)
if (coords_knobs$knob_radius[1] < min_radius_for_text ) {
gm_knob_text <- grid::nullGrob()
} else {
lab <- data$label
if(any(nchar(lab) > 6)){
warning("aes `label` is too long and will be truncated. Please limit to 6 characters or less.")
lab <- substr(lab, 1, 6)
}
label_num <- nchar(lab)[1]
#Get view port size for initial text drawing...
vp_width = grid::convertWidth(unit(1, "snpc"), "mm", valueOnly=TRUE)
fs <- scales::rescale(vp_width, to=c(20, 7), from=c(120, 20))
gm_knob_text <- grid::textGrob(
lab,
coords_knobs$x, coords_knobs$y,
default.units = "native",
# hjust = data$hjust, vjust = data$vjust,
# rot = data$angle,
gp = grid::gpar(
col = alpha(coords_knobs$text_col, coords_knobs$text_alpha),
fontsize = fs,
cex = (3/8) * 0.5 * ((coords_knobs$knob_radius / 0.03)^(1/2)),
# fontfamily = data$family,
fontface = "bold"#,
# lineheight = data$lineheight
)
)
}
# Combine ----
ggplot2:::ggname("geom_brick_rect",
grid::grobTree(gm_brick,
gm_knob_shadow,
gm_knob_base,
gm_knob_text
))
}
},
draw_key = draw_key_brick
test_coords_rect3 <<- coords_rect_unflipped
test_coords_rect4 <<- coords_rect
#Brighter colors for darker bricks
coords_rect$color_intensity <- as.numeric(colSums(col2rgb(coords_rect$fill)))
coords_rect$outline_col <- ifelse(coords_rect$color_intensity < 200, "#CCCCCC", "#333333")
#Un-Reverse calc for flipped coords
if(coord$is_flipped()){
coords_rect <- flip_coords(coords_rect)
}
gm_brick <- grid::rectGrob(
coords_rect$xmin, coords_rect$ymax,
width = coords_rect$xmax - coords_rect$xmin,
height = coords_rect$ymax - coords_rect$ymin,
default.units = "native",
just = c("left", "top"),
gp = grid::gpar(
col = alpha(coords_rect$outline_col, 0.3),
fill = alpha(coords_rect$fill, coords_rect$alpha),
lwd = coords_rect$size * .pt,
lty = coords_rect$linetype,
linejoin = linejoin,
lineend = if (identical(linejoin, "round")) "round" else "square"
)
)
# Knobs ----
coords <- coord$transform(data, panel_params)
coords_knobs0 <<- coords
#Reverse calc for flipped
if(coord$is_flipped()){
coords <- flip_coords(coords)
}
knobs_dims <- coords %>%
dplyr::mutate(brick_width = abs(xmax - xmin)/n_knob,
num_of_1x1s = (ymax-ymin) %/% brick_width,
knob_radius = brick_width * (5/8) * (1/2) )
coords_knobs0a <<- knobs_dims
coords_knobs <- 1:max(knobs_dims$num_of_1x1s) %>%
purrr::map_dfr(function(kk){
dat <- knobs_dims %>%
dplyr::filter(num_of_1x1s >= kk) %>%
dplyr::mutate(y = ifelse(sign == 1,
ymin + (kk * brick_width) - brick_width/2,
ymax - (kk * brick_width) + brick_width/2))
if(two_knob){
dplyr::bind_rows(
dat %>% dplyr::mutate(x = xmin + brick_width/2),
dat %>% dplyr::mutate(x = xmax - brick_width/2)
)
} else {
dat %>% dplyr::mutate(x = xmin + brick_width/2)
}
})
coords_knobs1 <<- coords_knobs
#Outline and text for dark colors
coords_knobs$color_intensity <- as.numeric(colSums(col2rgb(coords_knobs$fill)))
coords_knobs$text_alpha <- ifelse(coords_knobs$color_intensity < 200, 0.3, 0.3)
coords_knobs$text_col <- ifelse(coords_knobs$color_intensity < 200, "#CCCCCC", "#333333")
#Un-Reverse calc for flipped
if(coord$is_flipped()){
coords_knobs <- flip_coords_xy(coords_knobs)
}
gm_knob_shadow <- grid::circleGrob(
coords_knobs$x + (1/4)*coords_knobs$knob_radius,
coords_knobs$y - (1/4)*coords_knobs$knob_radius,
r= coords_knobs$knob_radius,
default.units = "native",
gp = grid::gpar(
col = NA,
fill = alpha("#333333", 0.3),
size = coords_knobs$size * .pt,
lty = coords_knobs$linetype
)
)
gm_knob_base <- grid::circleGrob(
coords_knobs$x, coords_knobs$y,
r= coords_knobs$knob_radius,
default.units = "native",
gp = grid::gpar(
col = alpha(coords_knobs$text_col, coords_knobs$text_alpha),
fill = alpha(coords_knobs$fill, coords_knobs$alpha),
size = coords_knobs$size * .pt,
lty = coords_knobs$linetype
)
)
#Text ----
#Don't draw if there are more knobs than threshold size
n <- nrow(coords_knobs)
if (coords_knobs$knob_radius[1] < min_radius_for_text ) {
gm_knob_text <- grid::nullGrob()
} else {
lab <- data$label
if(any(nchar(lab) > 6)){
warning("aes `label` is too long and will be truncated. Please limit to 6 characters or less.")
lab <- substr(lab, 1, 6)
}
label_num <- nchar(lab)[1]
#Get view port size for initial text drawing...
vp_width = grid::convertWidth(unit(1, "snpc"), "mm", valueOnly=TRUE)
fs <- scales::rescale(vp_width, to=c(20, 7), from=c(120, 20))
gm_knob_text <- grid::textGrob(
lab,
coords_knobs$x, coords_knobs$y,
default.units = "native",
# hjust = data$hjust, vjust = data$vjust,
# rot = data$angle,
gp = grid::gpar(
col = alpha(coords_knobs$text_col, coords_knobs$text_alpha),
fontsize = fs,
cex = (3/8) * 0.5 * ((coords_knobs$knob_radius / 0.03)^(1/2)),
# fontfamily = data$family,
fontface = "bold"#,
# lineheight = data$lineheight
)
)
}
# Combine ----
ggplot2:::ggname("geom_brick_rect",
grid::grobTree(gm_brick,
gm_knob_shadow,
gm_knob_base,
gm_knob_text
))
}
},
draw_key = draw_key_brick
)
flip_coords <- function(dat){