Настройка вертикального пространства для повернутых меток оси x в ggplot2 и plotly для приложения Shiny

тл;др версия: Для многогранного сюжета мне нужен способ

  1. Оцените относительный размер элементов графика (область графика, метки)
  2. Манипулировать относительным пространством, разрешенным для различных элементов на графике
  3. На основе критерия, например. минимальный размер области графика, определите абсолютный размер графика.

В настоящее время я создаю приложение Shiny для выполнения базового исследовательского анализа различных наборов данных. Я использую ggplot2 вместе с plotly для создания гистограмм для различных факторов. Однако я столкнулся с проблемой, когда дело доходит до отображения длинных имен факторов на оси x. Я попытался повернуть метки, чтобы они лучше подходили, но сюжет, похоже, не выделяет для них достаточно вертикального пространства, из-за чего метки пересекаются с областью графика. Вот скриншот, чтобы лучше проиллюстрировать мою проблему:

Скриншот

Итак, мои вопросы:

  1. Как я могу настроить вертикальное пространство для меток?
  2. Как я могу сделать это динамически, в зависимости от длины самого длинного имени фактора?

Или, может быть, я задаю неправильный вопрос. Плохо ли вообще возиться с размерами графика вручную, и я должен просто попробовать что-то еще?

Кроме того, я заметил, что в некоторых случаях площадь сюжета становится довольно маленькой. Итак, вот бонусный вопрос: как я могу оценить размер, который потребуется всему графику, чтобы я мог динамически масштабировать общую высоту, чтобы ничего не обрезалось.

Ниже приведен минимальный рабочий пример с фиктивными данными из пакета synthpop:

library(shiny)
library(tidyverse)
library(plotly)
library(synthpop) # for example data

# generate example data frame
data <- synthpop::SD2011 |>
  select(where(is.factor)) |>
  slice(1:6)

ui <- fluidPage(
    sidebarLayout(
        sidebarPanel(),
        mainPanel(
           plotlyOutput("hist_plot")
        )
    )
)

server <- function(input, output) {
  hist_plot <- reactive({
    data |>
      pivot_longer(cols=everything(), names_to = "key", values_to = "value") |>
      ggplot(aes(x=value)) +
      geom_bar() +
      facet_wrap(~key, nrow = 2, scales = "free_x") +
      theme(
        axis.text.x = element_text(angle = 90))
  })

    output$hist_plot <- renderPlotly({
      req(hist_plot)
      ggplotly(hist_plot())
    })
}
shinyApp(ui = ui, server = server)

Вот некоторые вещи, которые я пробовал/рассматривал:

  • Не использовать сюжет, а просто придерживаться ggplot2 и использовать renderPlot(). Однако из-за этого графики в приложении выглядят размытыми, и у него есть собственные проблемы с масштабированием и перекрытием.
  • изменение поворота метки и увеличение горизонтального пространства за счет увеличения ширины приложения по умолчанию. Однако в том случае, который у меня есть, мне нужно иметь возможность запускать приложение с помощью кнопки запуска приложения в Rstudio, и это не позволяет изменить размер по умолчанию, см. здесь
  • ручная регулировка высоты графика, например. с plotlyOutput("hist_plot", height = "1000px"). Это несколько решает проблему, когда я знаю, какой общий размер мне нужен, но также увеличивает размер области графика и меток. В идеале мы должны иметь возможность контролировать размер как общего размера диаграммы, так и графика независимо друг от друга.
  • Я рассмотрел все, что объяснено в этом замечательном руководстве здесь, но ничего из этого не применимо в моем случае.

Обновлено: добавлен раздел tl:dr

Посмотрите, приведет ли это вас туда, куда вы пытаетесь попасть: см. здесь. Если это не так, дайте мне знать. Я могу предоставить не только более подробную информацию, но и другие рабочие способы.

Kat 20.04.2023 01:23

Привет спасибо за подсказку! Я поиграл с решением @SBista, которое, я полагаю, было тем, что вы имели в виду. В моем случае это не очень помогает, потому что 1. Не позволяет изменять какие-либо размеры на графике и 2. Не позволяет проверить, сколько места «нужно» графику без какого-либо перекрытия.

ikk89 21.04.2023 08:57
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
116
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поскольку я не знаю, что еще есть в вашем приложении, я сомневаюсь, что это «одноразовый» ответ. После того, как вы прошли через это, дайте мне знать, что вы думаете, если это не работает для вас. Я уверен, что это не идеально. Для этого слишком много статического размера.

Несколько замечаний, относящихся к этому ответу

  • Когда вы перенесли это из ggplot в plotly, оно сделало некоторые странные, но ожидаемые вещи.
  • этикетки на гранях plotly.annotations
  • коробки с фасетными этикетками plotly.shapes
  • отдельные графики установлены на противоположных концах домена оси Y
    • Каждая ось по умолчанию имеет домен [0, 1] (вы могли бы изменить его, но зачем??)
    • С нижним рядом графиков в нижней части домена ваши метки никогда не будут легко адресованы...

Это то, что я сделал

В стиле
  • Я добавил масштабированный размер текста к меткам по оси x (в зависимости от размера экрана).
  • Я не добавлял масштабированный размер текста к меткам фасетов, меткам оси Y или заголовкам осей.
  • Я изменил высоту графика, чтобы она была динамической (в зависимости от высоты экрана)
В функции fixer()
  • захватил верхнюю часть графика в области оси Y
  • изменены положения всех объектов, контролируемых или относящихся к позициям нижних графиков
    • формы yanchor
    • положение аннотаций y
    • позиции отдельных доменов для «нижних» рядов графиков
Код

Большая часть кода не отличается от кода в вашем вопросе. Ищите мои комментарии в коде, чтобы отметить изменения. Если у вас есть вопросы, я всего лишь комментарий.

library(shiny)
library(tidyverse)
library(plotly)
library(synthpop) # for example data

# generate example data frame
data <- synthpop::SD2011 |>
  select(where(is.factor)) |>
  slice(1:6)

fixer <- function(pt) {                              # <--- I added
  rg <- pt$x$layout$yaxis2$domain[2]                           # graph size based on domain
  lapply(1:length(pt$x$layout$shapes), function(i) {           # modify shapes
    if (isTRUE(pt$x$layout$shapes[[i]]$yanchor == rg)) {        # isTRUE for errors
      pt$x$layout$shapes[[i]]$yanchor <<- 2.5 * rg             # move grey squares up
    }
  })
  lapply(1:length(pt$x$layout$annotations), function(j) {      # modify annotations
    if (isTRUE(round(pt$x$layout$annotations[[j]]$y, 6) == round(rg, 6))) { ## isTRUE for errors
      pt$x$layout$annotations[[j]]$y <<- 2.5 * rg              # move facet labels up
    }
  })
  pt$x$layout$yaxis2$domain <- c(1.5 * rg, 2.5 * rg)          # modify plot positions
  pt
}

ui <- fluidPage(
  tags$head(                                        # <--- I added
    tags$style(HTML(  # dynamic x-axis labels' size; dynamic plot height
      ".xaxislayer-above text{
        font-size: calc(6px + 6 * ((100vw - 300px) / (1600 - 300))) !important;
      }
      #hist_plot{
        height: 75vh !important;
      }"))
  ),
  sidebarLayout(
    sidebarPanel(),
    mainPanel(
      plotlyOutput("hist_plot")
    )
  )
)

server <- function(input, output) {
  hist_plot <- reactive({
    data |>
      pivot_longer(cols=everything(), names_to = "key", values_to = "value") |>
      ggplot(aes(x=value)) +
      geom_bar() +
      facet_wrap(~key, nrow = 2, scales = "free_x") +
      theme(
        axis.text.x = element_text(angle = 90))
  })
  
  output$hist_plot <- renderPlotly({
    req(hist_plot)
    ggplotly(hist_plot()) %>% 
      layout(margin = list(b = 100)) %>% fixer()        # <--- I added
  })
}
shinyApp(ui = ui, server = server)

Ух ты! Я поражен, что вы действительно знаете все эти параметры для настройки макета графика на таком детальном уровне. Как вы сказали, это все еще не супер гибкое, но это рабочее решение, так что большое спасибо!

ikk89 01.05.2023 10:47

Другие вопросы по теме