Как построить блестящий «грузовик», часть 2 - Пусть приложение LEGO «грузовик» тянет прицеп. Пример модульного блестящего приложения.

В Сентябре 2018 я использовал автомобильную метафору, объясняющую крупномасштабное блестящее приложение R. RViews опубликовал статью. Я бы резюмировал статью одной фразой. При создании больших приложений (грузовиков) в R shiny нужно помнить о многом. Чтобы охватить все эти вопросы в одном приложении, я предлагаю это руководство.

Вы можете найти все файлы приложения в папке https://github.com/zappingseb/biowarptruck - папка: example_packaged

Резюме (пропустите, если читаете статью в RViews)

В статье, которую я написал в RViews, читателю предлагалось обратить внимание на тот факт, что любое блестящее приложение может когда-нибудь стать большим. Ab initio это должно быть хорошо спланировано. Кроме того, должна быть возможность удалить или добавить любую часть вашего приложения. Таким образом, он должен быть модульным. Каждый модуль должен работать как кирпичик LEGO. Кубики LEGO имеют разную функциональность. Эти кирпичи подчиняются определенным правилам, которые заставляют их прилипать друг к другу. Эти правила мы называем стандартными. Модули, выполненные в виде кубиков LEGO, повышают вашу гибкость. Следовательно, возможность повторного использования ваших модулей растет. Когда вы настраиваете свое приложение на это, у вас есть возможность добавлять неограниченное количество кубиков LEGO. Он может расти. Представьте себе небольшие приложения, такие как автомобили. Крупномасштабные приложения - грузовики. В статье объяснялось, как построить грузовик LEGO.

Если вы построите свою машину из LEGO / more, и из разных частей можно будет превратить ее в грузовик.

Если вы создали свое приложение из стандартизованных модулей / у вас есть возможность добавить гораздо больше функций.

Блестящее модульное приложение - с чего начать?

Изображение ниже объясняет идею модульного блестящего приложения.

Вы начинаете с простого блестящего приложения. Смотри на это как на шасси твоей машины. Он сделан из LEGO. Любая другая деталь из LEGO прилипает к вашему шасси. Такие детали могут изменить свою функциональность. Разные модули помогут вам строить разные машины. Дополнительно необходимо иметь инструкцию (план) кирпича. План подсказывает, какие части нужно брать и повысить гибкость. На последних страницах инструкции к кирпичу может быть указана другая модель из тех же кирпичей. Если вы можете создать одно приложение из своих модулей, вы также можете создать другое приложение, содержащее те же модули. Если вам это понятно, мы можем приступить к созданию нашего приложения в R-shiny:

Правила реализации:

  • Каждый модуль представляет собой пакет R
  • Базовый пакет R определяет стандартизацию кирпичей.
  • Основное приложение - это простое блестящее приложение.
  • Файл инструкции (плана) кирпича отсутствует в R

Почему существуют эти правила, станет понятно из статьи.

Приложение, которое мы хотим создать

Приложение, которое мы хотим создать, будет создавать различные типы выходных данных из панели пользовательских входов. Эти разные результаты будут отображаться внутри приложения. Кроме того, все выходные данные будут помещены в файл PDF. Пример будет включать два графика в модуле графика и одну таблицу в модуле таблицы. Поскольку каждый модуль является R-пакетом, вы можете представить, как шаг за шагом добавлять еще много R-пакетов. В блестящем корпусе возможно множество выходов. Главная особенность этого приложения - возможность добавлять все больше и больше модулей. Больше модулей не испортят функцию отчетов в формате PDF или функцию просмотра. Модули внутри этого приложения вообще не взаимодействуют.

Основной R-пакет

Базовый пакет содержит структуру, которой должны следовать модули, чтобы соответствовать основному приложению. Есть два типа структур, которые мы определим как классы R-S4. Один представляет модули, а второй - элементы вывода в этих модулях.

Для первой задачи мы называем объект (класс) Отчетом. Отчет - это основной кирпичик, который мы определяем в основном приложении. Это содержит:

plots — A list of all elements shown in the report 
filename - The name of the output file (where to report to) 
obs - The handling of the input value input$obs 
rendered - Whether it shows up in the app right now

Кроме того, класс Report обладает некоторыми функциями для создания блестящего вывода. Кроме того, он позволяет создавать отчеты в формате PDF. Функциональные возможности входят в методы shinyElement () и pdfElement (). В R-S4 это выглядит так:

Теперь мы также хотели бы определить, как структурировать каждый элемент файла Таким образом. Таким образом, мы определяем класс AnyPlot, который несет выражение, поскольку это единственный слот. Метод evalElement вычислит это выражение. Метод pdfElement создает результат, который можно перейти в PDF. shinyElement создает PlotOutput, вызывая shiny::renderPlot(). Метод logElement записывает выражение в файл журнала. Код R-S4 показан ниже:

Основное приложение

Чтобы не усложнять этот пример, основное приложение будет включать все входные данные. Выходы этого приложения будут модульными. Основное приложение должно выполнять следующие задачи:

  1. есть контейнер для отображения модулей
  2. Прочитать план - добавить контейнеры
  3. включить кнопку для печати модулей в PDF
  4. представьте себе также кнопки, печатающие модули в форматах «.png», «.jpg», «.xlsx»
  5. включить входы

Отображение модулей

Для первой задачи мы используем метод shinyElement данного объекта и вставляем его в любой вывод. Я решил использовать табуляцию для каждого модуля. Таким образом, каждый модуль отображается на отдельной вкладке.

Чтение плана

А теперь самое сложное в приложении. Как я уже сказал, я хотел добавить два модуля. Один с сюжетами, другой со столом. Эту информацию должен содержать файл плана (config.xml). Поэтому я использую это как файл плана:

<?xml version="1.0" encoding="UTF-8"?>
<modules>
  <module>
    <id>module1</id>
    <name>Plot Module</name>
    <package>module1</package>
    <class>PlotReport</class>
  </module>
  <module>
    <id>module2</id>
    <name>Text Output</name>
    <package>module2</package>
    <class>TableReport</class>
  </module>
</modules>

Как видите, у меня два модуля. Для каждого модуля есть пакет. Внутри этого пакета класс определяет (см. Раздел пакеты модулей) вывод. Этот класс является дочерним по отношению к нашему классу Report.

Модуль отображается в виде вкладки в нашем приложении. Мы рассмотрим это шаг за шагом. Во-первых, нам нужна функция для загрузки пакетов для каждого модуля:

library(XML)
load_module <- function(xmlItem){
  devtools::load_all(paste0("./",xmlValue(xmlItem[["package"]]))) 
}

Во-вторых, нам нужна функция для генерации вкладки из информации модуля:

library(shiny)
module_tab <- function(xmlItem){
  tabPanel(XML::xmlValue(xmlItem[["name"]]),
           uiOutput(xmlValue(xmlItem[["id"]]))
  )
}

Поскольку теперь у нас есть эти две функции, мы можем перебирать XML-файл и создавать наше приложение. Сначала нам нужен TabPanel внутри пользовательского интерфейса, например tabPanel(id='modules'). После этого мы можем прочитать конфигурацию приложения в TabPane. Таким образом, мы используем функцию appendTab. Функция XML::xmlApply позволяет нам перебирать каждый узел XML (config.xml) и выполнять эти задачи.

configuration <- xmlApply(xmlRoot(xmlParse("config.xml")),function(xmlItem){
    load_module(xmlItem)
    
    appendTab("modules",module_tab(xmlItem),select = TRUE)
    
    list(
      name = xmlValue(xmlItem[["name"]]),
      class = xmlValue(xmlItem[["class"]]),
      id = xmlValue(xmlItem[["id"]])
    )
  })

Теперь каждый модуль загружается в приложение статическим образом. В следующей части мы поговорим о том, как сделать его реактивным.

Отображение контента в панелях

Для динамического рендеринга панелей необходимо знать некоторые входные данные. Сначала вкладка, которую выбрал пользователь. Переменная input$modules определяет выбранную вкладку. Кроме того, выходы нашего блестящего приложения должны обновляться еще одним входом, input$obs. Поэтому при изменении вкладки или изменения input$obs нам нужно вызвать событие. Это событие вызовет функцию конструктора нашего объекта S4. После этого метод shinyElement отображает результат.

Реактивная report_obj - это функция, которая может вызывать конструктор нашего объекта Report. Используя функцию observeEvent для input$obs и input$modules, мы называем это реактивным. Это позволяет реагировать на вводимые пользователем данные.

Получение файлов PDF из отчетов

Функция pdfElement отображает объект S4 как файл PDF. Если это сработало, элементы PDF добавляются к кнопке загрузки.

Дополнительная этикетка проверяет успешность рендеринга PDF.

Мы закончили основное приложение. Вы можете найти приложение здесь: app.R и основной пакет здесь: core.

Последний шаг - собрать весь грузовик.

Пакеты модулей

Теперь два пакета модулей будут содержать два класса. Оба должны быть дочерними по отношению к классу Report. Каждый элемент внутри этих классов должен быть дочерним классом класса AnyPlot. Красные кубики на следующем рисунке представляют Отчеты, а желтые кубики - AnyPlots.

Сюжетный пакет

Первый пакет модуля создаст диаграмму рассеяния и график гистограммы. Оба являются дочерними элементами AnyPlot на contains='AnyPlot' внутри определения этого класса. PlotReport - это класс для отчета этого пакета. Он содержит оба этих графика внутри слота plots. См. Код ниже для конструкторов этих классов.

Столовый пакет

Пакет table следует тем же правилам, что и пакет plot. Основное отличие состоит в том, что внутри слота plots находится только один элемент. Этот элемент - не сюжет. Вот почему он содержит data.frame вызов в качестве своего выражения.

Чтобы отобразить data.frame вызов внутри shiny, мы должны перезаписать shinyElement метод. Вместо того, чтобы возвращать результат renderPlot, мы вернем результат renderDataTable. Кроме того, метод pdfElement должен возвращать результат gridExtra::grid.table.

Преимущество упаковки

Основным преимуществом упаковки каждого модуля является определение зависимостей. В файле DESCRIPTION указаны все зависимости пакета модуля. Например, модулю таблицы нужен пакет gridExtra. Базовому пакету приложения требуется shiny, methods, XML, devtools. Приложению не нужны дополнительные library звонки. Любой сотрудник может установить все зависимости

Заключительные слова

Теперь у вас должны быть инструменты, чтобы начать создавать собственное крупномасштабное блестящее приложение. Модулируйте приложение с помощью пакетов. Стандартизируйте его, используя S4 или любой другой объектно-ориентированный стиль R. Настройте приложение с помощью документов XML или JSON. Тебе хорошо идти. Установите основной пакет и пакеты модулей в одном каталоге. Вы можете загрузить их с помощью devtools и начать создавать свой блестящий файл app.R. Теперь вы можете создать собственное приложение, обмениваясь пакетами модулей.

Как и любой ребенок, теперь вы можете с удовольствием поиграть со своим грузовиком, и все готово. Я не могу сказать вам, что это интереснее - строительство или более увлекательное катание.

Уважаемый читатель! Мне всегда приятно писать о своей работе по созданию модульных блестящих приложений. Благодарю вас за то, что дочитали до конца эту статью. Если вам понравилась статья, вы можете похлопать по ней на Medium или пометить репозиторий на github. В случае каких-либо комментариев, оставьте их здесь или в моем профиле LinkedIn http://linkedin.com/in/zappingseb.

дальнейшее чтение