mirror of
https://github.com/ryantimpe/brickr.git
synced 2026-05-03 17:19:55 -05:00
Negative bars!
This commit is contained in:
@@ -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
@@ -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){
|
||||
|
||||
Reference in New Issue
Block a user