coordenadas npc de geom_point no ggplot2

10

Como posso obter as coordenadas x , y de um geom_point em um ggplot , onde o quadro de referência é toda a imagem plotada?

Eu posso criar um ggplot com alguns geom_point s usando:

library(ggplot2)

my.plot <- ggplot(data.frame(x = c(0, 0.456, 1), y = c(0, 0.123, 1))) +
             geom_point(aes(x, y), color = "red")

Isto dá:

insira a descrição da imagem aqui

Ao converter isso em um grob , posso extrair algumas informações adicionais sobre este ggplot , como as coordenadas em relação ao painel de plotagem, marcadas pela seta roxa. No entanto, isso ignora o espaço ocupado pelos eixos.

my.grob <- ggplotGrob(my.plot)
my.grob$grobs[[6]]$children[[3]]$x
# [1] 0.0454545454545455native 0.46native 0.954545454545454native 
my.grob$grobs[[6]]$children[[3]]$y
# [1] 0.0454545454545455native 0.157272727272727native 0.954545454545454native

Como posso obter os valores das coordenadas x , y quando começo a medir no canto inferior esquerdo de toda a imagem, marcado pela seta verde?

Se possível, eu gostaria que a solução levasse em consideração o tema do ggplot . Adicionar um tema como + theme_void()afeta os eixos e também altera a localização dos pontos em relação a toda a imagem plotada.

Atualização : percebi que o tamanho da fonte dos eixos muda dependendo da largura e altura da plotagem, afetando o tamanho relativo do painel da plotagem . Portanto, não será trivial fornecer a localização em unidades npc sem definir a largura e a altura da plotagem . Se possível, forneça a localização dos geom_points como uma função da largura e altura da plotagem .

LBogaardt
fonte
5
Por que 2 votos negativos?
markus
2
Sim, por favor, comente se você tiver dúvidas ou sugestões.
LBogaardt 22/03
11
Considere que o gráfico em si não possui dimensões fixas - as posições absolutas dos pontos não dependem apenas do tamanho do dispositivo no qual você imprime, mas também da proporção de seus eixos. Portanto, bastante curioso para o que você precisa
Tjebo 22/03
11
Em relação aos votos negativos, observe que não preciso justificar minha pergunta para que seja uma pergunta válida no SE. Por que eu quero saber? Porque eu faço.
LBogaardt 22/03
11
esse também. mas nunca recebi uma resposta real stackoverflow.com/questions/48710478/…
Tjebo 22/03

Respostas:

5

Quando você redimensiona um ggplot, a posição dos elementos no painel não está em posições fixas no espaço npc. Isso ocorre porque alguns dos componentes do gráfico têm tamanhos fixos e alguns deles (por exemplo, o painel) alteram as dimensões de acordo com o tamanho do dispositivo.

Isso significa que qualquer solução deve levar em consideração o tamanho do dispositivo e, se você quiser redimensionar a plotagem, precisará executar o cálculo novamente. Dito isto, para a maioria dos aplicativos (incluindo o seu, pelo som das coisas), isso não é um problema.

Outra dificuldade é garantir que você esteja identificando os grobs corretos no grob do painel e é difícil ver como isso pode ser facilmente generalizado. O uso das funções de subconjunto de lista [[6]]e [[3]]no seu exemplo não é generalizável para outros gráficos.

De qualquer forma, esta solução funciona medindo o tamanho e a posição do painel dentro do gtable e convertendo todos os tamanhos em milimetros antes de dividir pelas dimensões da plotagem em milimetros para converter em espaço npc. Tentei torná-lo um pouco mais geral, extraindo o painel e os pontos pelo nome, em vez do índice numérico.

library(ggplot2)
library(grid)
require(gtable)

get_x_y_values <- function(gg_plot)
{
  img_dim      <- grDevices::dev.size("cm") * 10
  gt           <- ggplot2::ggplotGrob(gg_plot)
  to_mm        <- function(x) grid::convertUnit(x, "mm", valueOnly = TRUE)
  n_panel      <- which(gt$layout$name == "panel")
  panel_pos    <- gt$layout[n_panel, ]
  panel_kids   <- gtable::gtable_filter(gt, "panel")$grobs[[1]]$children
  point_grobs  <- panel_kids[[grep("point", names(panel_kids))]]
  from_top     <- sum(to_mm(gt$heights[seq(panel_pos$t - 1)]))
  from_left    <- sum(to_mm(gt$widths[seq(panel_pos$l - 1)]))
  from_right   <- sum(to_mm(gt$widths[-seq(panel_pos$l)]))
  from_bottom  <- sum(to_mm(gt$heights[-seq(panel_pos$t)]))
  panel_height <- img_dim[2] - from_top - from_bottom
  panel_width  <- img_dim[1] - from_left - from_right
  xvals        <- as.numeric(point_grobs$x)
  yvals        <- as.numeric(point_grobs$y)
  yvals        <- yvals * panel_height + from_bottom
  xvals        <- xvals * panel_width + from_left
  data.frame(x = xvals/img_dim[1], y = yvals/img_dim[2])
}

Agora podemos testá-lo com seu exemplo:

my.plot <- ggplot(data.frame(x = c(0, 0.456, 1), y = c(0, 0.123, 1))) +
             geom_point(aes(x, y), color = "red")

my.points <- get_x_y_values(my.plot)
my.points
#>           x         y
#> 1 0.1252647 0.1333251
#> 2 0.5004282 0.2330669
#> 3 0.9479917 0.9442339

E podemos confirmar que esses valores estão corretos, plotando alguns pontos sobre seus pontos vermelhos, usando nossos valores como coordenadas npc:

my.plot
grid::grid.draw(pointsGrob(x = my.points$x, y = my.points$y, default.units = "npc"))

Criado em 2020-03-25 pelo pacote reprex (v0.3.0)

Allan Cameron
fonte
11
Esta sum(to_mm(gt$heights[seq(panel_pos$t - 1)]))parte é a mais estranha. Você poderia explicar o que está resumindo aqui?
LBogaardt 26/03
11
O @LBogaardt panel_pos$tfornece a linha da grade na qual o painel (flexível) fica, assim seq(panel_pos$t -1)como todos os números de linha acima disso e, portanto, sum(to_mm(gt$heights[seq(panel_pos$t - 1)]))é a soma das alturas dessas linhas.
Allan Cameron
@LBogaardt Eu estava tentando encontrar um equilíbrio entre brevidade e clareza, mas talvez eu não tenha entendido direito ...
Allan Cameron
Ah, então um ggplot é como uma tabela, com uma coluna para o rótulo y, uma para a numeração do eixo y etc. Da mesma forma para linhas, cada uma com largura / altura. O painel é uma célula nesta tabela? Entendi. Obrigado Allan.
LBogaardt 26/03
11
Exatamente, @LBogaardt. De fato, o ggplotGrob produz uma tabela gTable ou grob
Allan Cameron