Column

{data-height=600}

{{ env_id }} <- readRDS(file.path(datadir, "{{ env_id }}.rds"))
is_shiny <- identical(knitr::opts_knit$get("rmarkdown.runtime"), "shiny")

library(magrittr)

data_{{ env_id }} <- {{ env_id }}$data
group_filter_{{ env_id }} <- {{ env_id }}$group_filter

exprs_values_{{ env_id }} <- round({{ env_id }}$exprs_values,3)
featurenames <- rownames(exprs_values_{{ env_id }})

reduced_dim_{{ env_id }} <- {{ env_id }}$use_dimred
group_by_{{ env_id }} <- {{ env_id }}$group_by

string_counts_{{ env_id }} <- apply(exprs_values_{{ env_id }}, 1, paste0, collapse = ";")
string_group_by_{{ env_id }} <- paste0(group_by_{{ env_id }}[,1], collapse = ";")
#
# Store expression data in <div> elements
#
data_divs <- lapply(featurenames, function(feature) {
  e <- string_counts_{{ env_id }}[[feature]]
  htmltools::div(id = paste0("expression-", feature), `data-feature` = feature, `data-expression` = e)
})
htmltools::div(data_divs)

htmltools::HTML(paste0("<div id='group_by_{{ env_id }}' data-grouping='",string_group_by_{{ env_id }},"'></div>"))

```{js, eval=!is_shiny} /* * Function to link a div-container with a plotly scatterplot (needs to have only two traces), change the color values and the title of the plot. * @param {string} plot_id The id of the scatterplot that should be changed. * @param {string} div_id The id of the div container. * @param {string} color_by_tag The tag of the div container containing the values for the colorchange (currently as a semicolon separated string). * @param {string} title The new title of the scatterplot. / function linking_plotly_scatter(plot_id, div_id, color_by_tag, title = "") {

color_by = document.getElementById(div_id).getAttribute(color_by_tag);

// (decode the data) or split the datastring var color_by = color_by.split(';').map(function(item){return parseFloat(item);});

// get min and max values from expression var min = Math.min(...color_by); var max = Math.max(...color_by);

// update scatterplot data var scatter_update_0 = { 'marker.color': [color_by], 'marker.cmax': max, 'marker.cmin': min, 'marker.line.cmax': max, 'marker.line.cmin': min, 'marker.line.color': [color_by]};

var scatter_update_1 = { 'marker.color': [min, max], 'marker.cmax': max, 'marker.cmin': min};

// update scatterplot layout var scatter_layout_update = {title: title};

// restyle and relayout of the scatterplot Plotly.restyle(plot_id, scatter_update_0, [0]); Plotly.restyle(plot_id, scatter_update_1, [1]); Plotly.relayout(plot_id, scatter_layout_update); console.log(document.getElementById(plot_id).data) }

```{js, eval=!is_shiny}
/**
 * Function to link a div-container with a plotly scatterplot (needs to have only two traces), change the color values and the title of the plot.
 * @param {string} plot_id The id of the scatterplot that should be changed.
 * @param {string} samples_div_id 
 * @param {string} samples_attribute 
 * @param {string} group_div_id
 * @param {string} group_attribute
 * @param {string} title The new title of the scatterplot.
 */
function linking_plotly_violin(plot_id, samples_div_id, samples_attribute, group_div_id, group_attribute, title = "") {
  plot_div = document.getElementById(plot_id)
  values_str = document.getElementById(samples_div_id).getAttribute(samples_attribute);
  grouping_str = document.getElementById(group_div_id).getAttribute(group_attribute);

  // (decode the data) or split the datastring
  var values = values_str.split(';').map(function(item){return parseFloat(item);});
  var group = grouping_str.split(';');

  // update scatterplot layout
  var violin_layout_update = {'title': title,
      'xaxis.type': 'category'};

  // create dict; key = number of level of group_by; value = indexes extractet from the whole expression array according to the level
  level_indices = {}
  for (i = 0; i < plot_div.data.length; i++){
    level_name = plot_div.data[i].name;
    level_indices[i] = getAllIndexes(group, level_name);
  }

  // update each trace of the violinplot according to the indexes
  traces_values = []
  for (var key in level_indices){
    var values_of_level = level_indices[key].map(key => values[key]);
    traces_values.push(values_of_level);
  }

  var violin_update = {
    y: traces_values
  }

  // restyle and relayout of the violinplot
  Plotly.restyle(plot_id, violin_update);
  Plotly.relayout(plot_id, violin_layout_update);
  console.log(document.getElementById(plot_id).data)
}

```{js, eval=!is_shiny} // https://stackoverflow.com/a/20798567/7508349 function getAllIndexes(arr, val) { var indexes = [], i; for(i = 0; i < arr.length; i++) if (arr[i] === val) indexes.push(i); return indexes; }

```r
data_{{ env_id }} %>%
  dplyr::filter(group_by == !!group_filter_{{ env_id }}) %>%
  dplyr::group_by(level, feature) %>%
  dplyr::mutate(is_non_zero_exprs = expression > 0) %>%
  dplyr::summarise(n_samples = dplyr::n(), n_nonzero = sum(is_non_zero_exprs)) %>%
  dplyr::mutate(freq = round(n_nonzero / n_samples * 100, 2)) %>%
  dplyr::mutate(info = paste0(n_nonzero, " / ", n_samples, " (", freq, "%)")) %>%
  dplyr::select(level, feature, info) %>%
  tidyr::spread(level, info) %>%
  as.data.frame -> metadata_{{ env_id }}

  row.names(metadata_{{ env_id }}) <- row.names(exprs_values_{{ env_id }})
  DT::datatable(metadata_{{ env_id }}, 
                elementId = "dt_{{ env_id }}", 
                filter = 'top', 
                caption = 'Table: No. of cells with positive expression (>0)', 
                selection = list(mode = 'single', target = 'row'), 
                options = list(
    columnDefs = list(list(visible = FALSE, targets = c(1))),
    pageLength = 100
    ),
    callback=DT::JS("
      table.on('click.dt', 'tr', function() {

      // highlight selected row of the table
      if ( $(this).hasClass('selected') ) {
          $(this).removeClass('selected');
      } else {
          table.$('tr.selected').removeClass('selected');
          $(this).addClass('selected');
      }

      var feature_name = table.row(this).data()[0],
      feature_div_id = 'expression-'.concat(feature_name);

      // changing the scatterplot
      var id_scatter = 'scatter_{{ env_id }}';

      linking_plotly_scatter(plot_id = id_scatter, div_id = feature_div_id, color_by_tag = 'data-expression', title = feature_name);

      //changing the violinplot
      var id_violin = 'violin_{{ env_id }}',
      id_group = 'group_by_{{ env_id }}';

      linking_plotly_violin(plot_id = id_violin, samples_div_id = feature_div_id, samples_attribute = 'data-expression', group_div_id = id_group, group_attribute = 'data-grouping', title = feature_name);
      })
")
)
ui_list <- list()

# selection field for grouping
ui_list <- rlist::list.append(ui_list,
                                selectInput("select_grouping_{{ env_id }}", label = "Select column for grouping:",
                                            choices = unique(data_{{ env_id }}$group_by), selected = group_filter_{{ env_id }}))
#
# subsetting data according to the selected grouping column
#
df_table_{{ env_id }} <- shiny::reactive({
  selected_group <- input$select_grouping_{{ env_id }}

  data_{{ env_id }} %>%
    dplyr::filter(group_by == !!selected_group) 
})

#
# Dropdown menu with shiny input-widgets
#
shinyWidgets::dropdownButton(div(style='height: 300px; overflow-x: auto;',do.call(shiny::inputPanel, ui_list)),
                 circle = TRUE, status = "danger", icon = icon("gear"), width = "300px",
                 tooltip = shinyWidgets::tooltipOptions(title = "Click, to change plot settings:"))
#
# DT::datatable with calculated statistic
#
output$tbl_{{ env_id }} <- DT::renderDataTable({
  #options(DT.options = list(scrollY="400px",scrollX="300px", pageLength = 100, autoWidth = TRUE))
  options(DT.options = list(scrollY="20vh", scrollX="300px", pageLength = 100, autoWidth = TRUE))
  df_table_{{ env_id }}() %>%
    dplyr::group_by(level, feature) %>%
    dplyr::mutate(is_non_zero_exprs = expression > 0) %>%
    dplyr::summarise(n_samples = dplyr::n(), n_nonzero = sum(is_non_zero_exprs)) %>%
    dplyr::mutate(freq = round(n_nonzero / n_samples * 100, 2)) %>%
    dplyr::mutate(info = paste0(n_nonzero, " / ", n_samples, " (", freq, "%)")) %>%
    dplyr::select(level, feature, info) %>%
    tidyr::spread(level, info) %>%
    dplyr::mutate(feature = row.names(exprs_values_{{ env_id }})) %>%
    DT::datatable(filter = 'top', selection = list(mode = 'single', selected = 1, target = 'row'), caption = 'Table: No. of cells with positive expression (>0)')
})
renderUI({DT::dataTableOutput('tbl_{{ env_id }}')})

# compare with colormaps
if(is.factor(group_by_{{ env_id }}[,1])){
  if(colnames(group_by_{{ env_id }})[1] %in% names(colormaps)) colors_{{ env_id }} <- colormaps[[colnames(group_by_{{ env_id }})[1]]] else colors_{{ env_id }} <- "Set1"
}

i2dash.scrnaseq::plotly_violinplot(y = exprs_values_{{ env_id }}[1,], group_by = group_by_{{ env_id }}[,1], y_title = "Expression", group_by_title = "Group", , colors = colors_{{ env_id }}) %>%
  plotly::layout(
    title = row.names(exprs_values_{{ env_id }})[1]
  ) -> violin_{{ env_id }}
# %>%
  # plotly::config(fillFrame = TRUE) -> violin_{{ env_id }}
violin_{{ env_id }}$elementId <- "violin_{{ env_id }}"
violin_{{ env_id }}
#
# subsetting the data.frame for the violinplot according to the transmitted keys from scatterplot
#
df2_{{ env_id }} <- shiny::reactive({
  keys <- plotly::event_data("plotly_selected", source = "scatter_{{ env_id }}")$key
  if(!is.null(plotly::event_data("plotly_selected", source = "scatter_2_{{ env_id }}")$key)) keys <- plotly::event_data("plotly_selected", source = "scatter_2_{{ env_id }}")$key
  df <- data.frame(df_{{ env_id }}()$df, "level" = as.factor(group_by_{{ env_id }}[[input$select_grouping_{{ env_id }}]]))
  if(!is.null(keys)) {
    return(df[keys, ])
  }
  return(df)
})

#
# creating a violinplot with the primitive function of i2dash.scrnaseq::plotly_violinplot
#
output$violin_{{ env_id }} <- plotly::renderPlotly({
    # compare with colormaps
  if(input$select_grouping_{{ env_id }} %in% names(colormaps)) colors <- colormaps[[input$select_grouping_{{ env_id }}]] else colors <- "Set1"

  i2dash.scrnaseq::plotly_violinplot(y = df2_{{ env_id }}()$expression, group_by = df2_{{ env_id }}()$level, y_title = "Expression", group_by_title = input$select_grouping_{{ env_id }}, colors = colors) %>%
  plotly::layout(title = df_{{ env_id }}()$feature) -> violin
})
renderUI({plotly::plotlyOutput("violin_{{ env_id }}", height = "100%")})

Column

Select points to recalculate the violin plot according to selection:

i2dash.scrnaseq::plotly_scatterplot(x =reduced_dim_{{ env_id }}[,1], y = reduced_dim_{{ env_id }}[,2], color = exprs_values_{{ env_id }}[1,], colors = "YlOrRd", text = row.names(reduced_dim_{{ env_id }}), hoverinfo = "x+y+text", y_title = "Dimension 2", x_title = "Dimension 1") %>%
  plotly::layout(
    title = row.names(exprs_values_{{ env_id }})[1],
    autosize = F, 
    xaxis = list(title = "Dimension 1", automargin = T),
    yaxis = list(title = "Dimension 2", automargin = T)
  ) %>%
  htmlwidgets::onRender("function(el) {
    // --------------------
    // workaround for plotly.js bug (https://github.com/ropensci/plotly/issues/1546)
    // get size of parents div container
    document.getElementById('scatter_{{ env_id }}').parentElement.id = 'parent_scatter_{{ env_id }}'
    var clientHeight = document.getElementById('parent_scatter_{{ env_id }}').clientHeight;
    var clientWidth = document.getElementById('parent_scatter_{{ env_id }}').clientWidth;

    // avoid errors at 0 width and height
    if (clientHeight === 0){
      clientHeight = 450 // plotly's default value 
    }
    if (clientWidth === 0){
      clientWidth = 700 // plotly's default value 
    }

    // get current layout and relayout plotly chart
    var layout = el.layout;
    layout.height = clientHeight;
    layout.width = clientWidth;

    Plotly.relayout(el, layout)
    // --------------------

    var id_violin = 'violin_{{ env_id }}';
    var violin_div = document.getElementById(id_violin);

    var div_group_by = document.getElementById('group_by_{{ env_id }}');
    var group_str = div_group_by.getAttribute('data-grouping');
    var group = group_str.split(';');

    level_indices = {}
    for (i = 0; i < violin_div.data.length; i++){
      level_name = violin_div.data[i].name;
      level_indices[i] = getAllIndexes(group, level_name);
    }

    el.on('plotly_selected', function(eventData){
      if(eventData == undefined){
        console.log('Nothing is selected');
      } else {
        feature_name = el.layout.title.text;
        var div_exprs = document.getElementById('expression-'.concat(feature_name));
        var exprs_str = div_exprs.getAttribute('data-expression');
        var expr = exprs_str.split(';').map(function(item){return parseFloat(item);});

        // get the array of the selected points
        var selection = eventData.points[0].data.selectedpoints;

        // create dict with subsetted trace indexes
        updated_dict = {}
        for (var key in level_indices){
          updated_dict[key] = selection.filter(value => -1 !== level_indices[key].indexOf(value))
        }

        // restyle each trace of the violinplot according to the indexes
        traces_exprs = []
        for (var key in updated_dict){
          if (updated_dict[key].length === 0){
            var exprs_of_level = [0];
            traces_exprs.push(exprs_of_level);
          } else {
            var exprs_of_level = updated_dict[key].map(key => expr[key]);
            traces_exprs.push(exprs_of_level);
          }
        }
        var violin_update = {
          y: traces_exprs
        }

        Plotly.restyle(id_violin, violin_update);

        var violin_layout_update = {'xaxis.type': 'category'};
        Plotly.relayout(id_violin, violin_layout_update);

        // when deselected the violin plot is restyled with the old values
        el.on('plotly_deselect', function(eventData){
          feature_name = el.layout.title.text;
          var div_exprs = document.getElementById('expression-'.concat(feature_name));
          var exprs_str = div_exprs.getAttribute('data-expression');
          var expr = exprs_str.split(';').map(function(item){return parseFloat(item);});

          traces_exprs = [];
          // restyle each trace of the violinplot according to the indexes
          for (var key in level_indices){
            var exprs_of_level = level_indices[key].map(key => expr[key]);
            traces_exprs.push(exprs_of_level);
          };
          var violin_update = {
            y: traces_exprs
          };
          // all_traces = [...range(0, violin_div.data.length - 1)];
          Plotly.restyle(id_violin, violin_update);

          var violin_layout_update = {'xaxis.type': 'category'};
          Plotly.relayout(id_violin, violin_layout_update);
        })
      }
    });
  }") %>% 
  plotly::toWebGL() -> scatter_{{ env_id }}

# %>%
#   plotly::config(fillFrame = TRUE) -> scatter_{{ env_id }}

scatter_{{ env_id }}$elementId <- "scatter_{{ env_id }}"
scatter_{{ env_id }}
#
# Create reactive data table for the scatterplot according to the selected feature
#
df_{{ env_id }} <- shiny::reactive({
  selected_gene <- input$tbl_{{ env_id }}_row_last_clicked
  if(is.null(selected_gene)){
    expression <- exprs_values_{{ env_id }}[1,] 
    featurename <- featurenames[1]
  } else {
    expression <- exprs_values_{{ env_id }}[selected_gene,]
    featurename <- featurenames[selected_gene]
  }
  df <- data.frame("x" = reduced_dim_{{ env_id }}[,1], "y" = reduced_dim_{{ env_id }}[,2], "expression" = expression)
  return(list("df" = df, "feature" = featurename))
})

#
# Output
#
output$scatter_{{ env_id }} <- plotly::renderPlotly({
  i2dash.scrnaseq::plotly_scatterplot(x = df_{{ env_id }}()$df$x, y = df_{{ env_id }}()$df$y, color = df_{{ env_id }}()$df$expression, colors = "YlOrRd", text = rownames(df_{{ env_id }}()$df), hoverinfo = "x+y+text", source = "scatter_{{ env_id }}", key = rownames(df_{{ env_id }}()$df), y_title = "Dimension 2", x_title = "Dimension 1") %>%
    plotly::layout(
      title = df_{{ env_id }}()$feature
    ) %>% 
    plotly::event_register("plotly_selected") %>%
    plotly::toWebGL() -> plot
  plot
})
renderUI({plotly::plotlyOutput("scatter_{{ env_id }}", height = "100%")})

# compare with colormaps
if(is.factor(group_by_{{ env_id }}[,1])){
  if(colnames(group_by_{{ env_id }})[1] %in% names(colormaps)) colors_{{ env_id }} <- colormaps[[colnames(group_by_{{ env_id }})[1]]] else colors_{{ env_id }} <- "Set1"
}

i2dash.scrnaseq::plotly_scatterplot(x =reduced_dim_{{ env_id }}[,1], y = reduced_dim_{{ env_id }}[,2], color = group_by_{{ env_id }}[,1], text = row.names(reduced_dim_{{ env_id }}), hoverinfo = "x+y+text", y_title = "Dimension 2", x_title = "Dimension 1", colors = colors_{{ env_id }}) %>%
  plotly::layout(
    title = paste0("Coloured by: ", colnames(group_by_{{ env_id }})[1])
  ) -> scatter_2_{{ env_id }}

# %>%
#   plotly::config(fillFrame = TRUE) -> scatter_{{ env_id }}

scatter_2_{{ env_id }}$elementId <- "scatter_2_{{ env_id }}"
scatter_2_{{ env_id }}
#
# Output
#
output$scatter_2_{{ env_id }} <- plotly::renderPlotly({
    # compare with colormaps
    if(input$select_grouping_{{ env_id }} %in% names(colormaps)) colors <- colormaps[[input$select_grouping_{{ env_id }}]] else colors <- "Set1"

    i2dash.scrnaseq::plotly_scatterplot(x = reduced_dim_{{ env_id }}[,1], y = reduced_dim_{{ env_id }}[,2], color = as.factor(group_by_{{ env_id }}[[input$select_grouping_{{ env_id }}]]), text = rownames(reduced_dim_{{ env_id }}[,1]), hoverinfo = "x+y+text", source = "scatter_2_{{ env_id }}", key = rownames(reduced_dim_{{ env_id }}), y_title = "Dimension 2", x_title = "Dimension 1", colors = colors) %>% 
    plotly::layout(
      title = paste0("Coloured by: ", input$select_grouping_{{ env_id }})
    ) %>% 
    plotly::event_register("plotly_selected") %>%
    plotly::toWebGL() -> plot
  plot
})
renderUI({plotly::plotlyOutput("scatter_2_{{ env_id }}", height = "100%")})


loosolab/i2dash.scrnaseq documentation built on Jan. 1, 2021, 8:23 a.m.