ambiorix icon indicating copy to clipboard operation
ambiorix copied to clipboard

Question: Arrow integration?

Open JosiahParry opened this issue 1 year ago • 6 comments

I'm currently developing bindings to Calcite components at https://github.com/R-ArcGIS/calcite/. Right now this is using Shiny's .setInputValue() which, AFAIK, uses {jsonlite} to deserialize json? This is incredibly slow.

I think it would be incredibly useful to be able to use arrow to handle this. From the R side nanoarrow and from the JS side arrow-js-ffi could be used https://www.npmjs.com/package/arrow-js-ffi.

Am I right in thinking that the way this would have to be handled is with a websocket: https://ambiorix.dev/docs/ambiorix/websocket/?

JosiahParry avatar Jan 13 '25 21:01 JosiahParry

Calcite looks pretty cool!

Right now this is using Shiny's .setInputValue() which, AFAIK, uses {jsonlite} to deserialize json? This is incredibly slow.

That is also my understanding, also, since setInputValue()/sendCustomMessage is used for a lot of internal stuff the serialiser/deserialiser cannot be customised so you're stuck with {jsonlite}.

With ambiorix you should be able to have control over this. By default I think the websocket connection is binary. Also the request/response in ambiorix should be able to handle any valid format, see https://github.com/ambiorix-web/ambiorix/issues/44 for an example.

Note that there is a way to open endpoints in Shiny too (where you could bypass serialiser, see this horrid video).

I'd love to help more when you have an example!

JohnCoene avatar Jan 13 '25 22:01 JohnCoene

@JosiahParry congrats for the {caclite} package. It would be awesome to be able to use its components from {ambiorix}. It's a thing we have been dsicussing about with @JohnCoene @kennedymwavu: Be able to reuse htmlwigtes and shiny components in ambiorix (plotly, DT, etc) and have some examples of their implementation in {ambiorix}, so any developer can do it for other components too.

jrosell avatar Jan 15 '25 07:01 jrosell

@jrosell yes, we desperately need something.

Early days I think I used to render the HTML to temp file then read the file in but it felt funny, so I later replaced that with as.character which was improved by @kennedymwavu who changed it for htmltools::renderTags but we're not rendering it entirely correctly.

I think we should be able to get any {htmltools} working fine in ambiorix.

library(ambiorix)
library(htmltools)
library(calcite)

# core app
app <- Ambiorix$new()

app$get("/", \(req, res){
  shell <- calcite_shell(
    calcite_navigation(
      slot = "header",
      calcite_navigation_logo(
        slot = "logo",
        heading = "Snow Plow Map",
        description = "City of AcmeCo"
      ),
      calcite_menu(
        slot = "content-end",
        calcite_menu_item(text = "Drivers", `icon-start` = "license", `text-enabled` = TRUE),
        calcite_menu_item(text = "Routes", `icon-start` = "road-sign", `text-enabled` = TRUE),
        calcite_menu_item(text = "Forecast", `icon-start` = "snow", `text-enabled` = TRUE)
      ),
      calcite_navigation(
        slot = "navigation-secondary",
        calcite_menu(
          slot = "content-start",
          calcite_menu_item(breadcrumb = TRUE, text = "All Routes", `icon-start` = "book", `text-enabled` = TRUE),
          calcite_menu_item(breadcrumb = TRUE, text = "South Hills", `icon-start` = "apps", `text-enabled` = TRUE, active = TRUE)
        )
      ),
      calcite_navigation_user(slot = "user", `full-name` = "Wendell Berry", username = "w_berry")
    )
  )

  tgs <- htmltools::renderTags(shell)
  dps <- htmltools::renderDependencies(tgs$dependencies)

  page <- tags$html(
    tags$head(dps),
    tags$body(tgs)
  )

  res$send(page)
})

app$start()

JohnCoene avatar Jan 15 '25 10:01 JohnCoene

This looks great! Works for me locally.

A helper function like:

#' returns html to be sent 
render_htmltools <- function(x) {
  tgs <- htmltools::renderTags(x)
  dps <- htmltools::renderDependencies(tgs$dependencies)
  tags$html(
    tags$head(dps),
    tags$body(tgs)
  )
}

could be handy here so that res$send(render_htmltools(shell)).

For me, the bit I'd like to figure out is how to set and fetch properties from the components and then using their custom event observers.

JosiahParry avatar Jan 15 '25 18:01 JosiahParry

For me, the bit I'd like to figure out is how to set and fetch properties from the components and then using their custom event observers.

Do you mind pointing me to something in the docs? I genuinely love web components, happy to see ArcGIS went with that.

JohnCoene avatar Jan 15 '25 20:01 JohnCoene

@JohnCoene of course!

Here is the one I worked off of the most: https://developers.arcgis.com/calcite-design-system/components/filter/

Here's a really bad app that uses the filteredItems property

library(shiny)
library(calcite)
library(htmltools)

ui <- calcite_shell(
  calcite_filter(id = "filter"),
  htmlOutput("cards")
)

make_card <- function(.x) {
  calcite_card(
    span(slot = "heading", .x$Species),
    span(
      slot = "description",
      sprintf("This plant has a petal width of %s", .x$Petal.Width)
    )
  )
}

server <- function(input, output, session) {
  .iris <- iris
  .iris[["Species"]] <- as.character(iris$Species)
  update_calcite("filter", items = purrr::transpose(.iris))

  observeEvent(input$filter_value, {
      items <- input$filter_filteredItems$values
      if (length(items) > 0) {
        cat(str(items))
        output$cards <- renderUI(div(tagList(!!!lapply(items, make_card))))
      } else {
        output$cards <- renderUI(div())
      }
  })
}

shinyApp(ui, server)

JosiahParry avatar Jan 15 '25 21:01 JosiahParry