Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add points and edges through a proxy #122

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

janithwanni
Copy link
Contributor

Depends on: #121
Changes:

  • Adds a proxy that can be accessed through the R function display_scatter_proxy
  • Adds functions add_points and add_edges to add additional points and edges using the proxy returned by the above function
  • These points and edges remain separate and independent of the existing dataset.

@janithwanni janithwanni force-pushed the feat/add-points-and-edges branch from 2a3c177 to fe64a40 Compare October 28, 2024 08:27
@janithwanni
Copy link
Contributor Author

Hi @casperhart,

This PR provides the functionality to add points and edges to an existing detour instance through a proxy object (similar to the approach taken by leaflet and other Javascript libraries). The Shiny app in the demo/shiny_detourr/add_points_edges.R file contains an example of adding a box around a given point and demonstrates how to use the proxy within Shiny modules.

library(shiny)
library(detourr)

dataset <- tourr::flea |>
  dplyr::mutate(id = dplyr::row_number())

create_fake_box <- function(datum) {
  # expected tibble needs to have two rows with ncols(datum) + 1 columns
  box_dist <- rnorm(1, mean = 3)
  bounds_list <- rbind(datum + box_dist, datum - box_dist) |>
    as.list()
  do.call(tidyr::expand_grid, bounds_list)
}

main_ui <- function(id) {
  ns <- NS(id)
  fluidPage(
    column(6,
      displayScatter2dOutput(
        ns("detourr_out"), width = "100%", height = "400px"
      ),
      textOutput(ns("detour_click_output"))
    ),
    column(6,
      h1("Adding points and edges to detourr through Shiny"),
      p(
        "In this demonstration,",
        "clicking on points on the detourr instance to the left",
        "adds a box around the point.",
        "Click on the play button to view the box in different projections"
      )
    )
  )
}

main_server <- function(id) {
  moduleServer(id, function(input, output, session){
    output$detourr_out <- shinyRenderDisplayScatter2d({
      detour(dataset,
        tour_aes(projection = -c(id, species), colour = species, label = id)
      ) |>
        tour_path(grand_tour(2), fps = 60) |>
        show_scatter(
          alpha = 0.7,
          axes = TRUE
        )
    })

    output$detour_click_output <- renderText({
      input$detourr_out_detour_click
    })

    observeEvent(input$detourr_out_detour_click, {
      req(
        !is.null(input$detourr_out_detour_click),
        input$detourr_out_detour_click != -1
      )
      data_to_send <- dataset |>
        dplyr::select(-species) |>
        dplyr::filter(id == input$detourr_out_detour_click) |>
        dplyr::select(-id)

      box_to_send <- data_to_send |> create_fake_box()

      cube_box <- geozoo::cube.iterate(p = ncol(data_to_send))

      display_scatter_proxy(session$ns("detourr_out")) |>
        add_points(
          box_to_send,
          .data = dataset |> dplyr::select(-c(id, species))
        ) |>
        add_edges(
          edge_list = cube_box$edges
        )
    })

  })
}

ui <- function() {
  main_ui("main")
}

server <- function(input, output, session) {
  main_server("main")
}

shinyApp(ui, server, options = list(port = 5534))

detourr_add_points_edges_recording

@janithwanni janithwanni marked this pull request as ready for review October 30, 2024 08:16
@@ -18,7 +18,8 @@ Imports:
rlang,
purrr,
viridisLite,
grDevices
grDevices,
cli
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cli package to provide alerts and errors

Comment on lines +97 to +99
.data = NULL,
.col_means = NULL,
.scale_factor = NULL,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach had to be implemented because the tourr package does scaling internally and therefore we need to scale the points that are given to make sure that they fit in the exact required location.

Comment on lines +31 to +33
} catch (error) {
console.error(`Could not find detour widget ${x.id}`)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be catching other errors apart from the widget not being found but for the time being I'm leaving it as it is, because that is the most likely candidate of throwing errors here.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what scenarios would the widget not be found?

Comment on lines +68 to +72
protected auxPoint: THREE.Points;
protected auxData: tf.Tensor2D;
protected auxEdgeData: number[];
protected auxEdge: THREE.LineSegments;
protected auxEdgeBuffer: THREE.BufferGeometry;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented it such that only one set of additional points can be added at a given time. @casperhart, if you have suggestions for a better implementation of this, I'm open to implementing it.

@@ -386,18 +486,19 @@ export abstract class DisplayScatter {
this.scene.add(this.axisSegments);
}

private addEdgeSegments(pointsBuffer: THREE.BufferAttribute) {
private addEdgeSegments(pointsBuffer: THREE.BufferAttribute, edges: number[]): THREE.LineSegments {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extended addEdgeSegments function to enable arrays outside of the internal class variable.

Comment on lines +41 to +62
describe("add_edges", {
it("returns a proxy class object", {
# Arrange
proxy <- list(
id = "id",
session = list(
sendCustomMessage = \(x, y) "stubbed function"
)
) |> structure(class = "detour_proxy")
points <- data.frame(list(
from = c(1, 2, 3),
to = c(2, 3, 4)
))

# Act
out <- add_edges(proxy, points)

# Assert
expect_s3_class(out, "detour_proxy")
expect_vector(out$id, ptype = character(), size = 1)
})
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple tests to begin with at the moment. But can be extended to more extensive tests once the single widget setup is complete.

Copy link
Owner

@casperhart casperhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something isn't working properly for me. I have run the demo, but the box is much bigger than in the screen capture you added:
image

Also, if I zoom right in and then click a point, the new points that are added are much bigger than the existing points:
image

And do you have any other examples of how this might be used? Not code examples, just so I can better understand what the feature is for

Comment on lines +31 to +33
} catch (error) {
console.error(`Could not find detour widget ${x.id}`)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what scenarios would the widget not be found?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants