Skip to content

Commit

Permalink
Merge pull request #8 from andyquinterom/T7
Browse files Browse the repository at this point in the history
Adds footer to table
  • Loading branch information
pierina-ixpantia authored Jul 18, 2024
2 parents 66b2809 + 85a20eb commit 08e598f
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 101 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: spyctable
Title: What the Package Does (One Line, Title Case)
Version: 0.3.0
Version: 0.4.0
Authors@R:
person("First", "Last", , "[email protected]", role = c("aut", "cre"),
comment = c(ORCID = "YOUR-ORCID-ID"))
Expand Down
3 changes: 2 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Generated by roxygen2: do not edit by hand

export(build_spyctable_html)
export(get_spyc_table_selection)
export(renderSpyCTable)
export(spyCTableOutput)
export(spyc_header_create)
export(spyctable)
useDynLib(spyctable, .registration = TRUE)
2 changes: 1 addition & 1 deletion R/extendr-wrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ NULL
filter_from_values_vec <- function(values_vec, data) .Call(wrap__filter_from_values_vec, values_vec, data)

#' @export
spyc_header_create <- function(names) .Call(wrap__spyc_header_create, names)
build_spyctable_html <- function(data, names, nrow, format, na, id) .Call(wrap__build_spyctable_html, data, names, nrow, format, na, id)


# nolint end
26 changes: 22 additions & 4 deletions R/table.R
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
#' @export
renderSpyCTable <- function(expr, env = parent.frame(), quoted = FALSE) {
spyctable <- function(data, format = "default", na = "zero") {
list(
data = data,
format = format,
na = na
)
}

#' @export
renderSpyCTable <- function(expr, env = parent.frame(), quoted = FALSE, id) {
func <- shiny::exprToFunction(expr, env, quoted)
session <- shiny::getDefaultReactiveDomain()
shiny::reactive({
to_render <- func()
list(
data = to_render,
thead = jsonlite::unbox(spyc_header_create(colnames(to_render)))
html = jsonlite::unbox(
build_spyctable_html(
to_render$data,
colnames(to_render$data),
nrow(to_render$data),
to_render$format,
to_render$na,
id = shiny::getCurrentOutputInfo()$name
)
)
)
})
}
Expand Down Expand Up @@ -38,5 +56,5 @@ spyCTableOutput <- function(id, scroll_y = "50vh") {

#' @export
get_spyc_table_selection <- function(input, dataset) {
filter_from_values_vec(as.integer(input), dataset)
filter_from_values_vec(as.character(input), dataset)
}
43 changes: 29 additions & 14 deletions inst/example/app.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,43 @@ library(shiny)
library(tidyselect)
library(dplyr)

iris_char <- iris |>
dplyr::mutate(Species = as.character(Species))

table_module_ui <- function(id) {
ns <- shiny::NS(id)
spyCTableOutput(ns("tabla"))
}

ui <- fluidPage(
theme = bslib::bs_theme(version = 5),
actionButton("rerender", "Rerender"),
spyCTableOutput("tabla")
table_module_ui("my_module")
)

char_iris <- 1:100 |>
purrr::map_df(~ iris) |>
dplyr::mutate(dplyr::across(tidyselect::everything(), as.character))
table_module_server <- function(id) {
shiny::moduleServer(id, function(input, output, session) {
output$tabla <- renderSpyCTable({
spyctable(
iris_char,
format = "default",
na = "dash"
)
})

observe({
print(
get_spyc_table_selection(input$tabla_cells_selected, iris_char)
)
})

})
}


server <- function(input, output, session) {

output$tabla <- renderSpyCTable({
char_iris
}) |>
bindEvent(input$rerender)
table_module_server("my_module")

observe({
print(
get_spyc_table_selection(input$tabla_cells_selected, char_iris)
)
})
}

shinyApp(ui, server)
65 changes: 10 additions & 55 deletions inst/table/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function enable_dragging() {

function selected_deselected(el) {
// This is a pointer to the selection array
const selection = globalSpyCTableIndex.get(el.tableId);
const selection = globalSpyCTableIndex.get(el.dataset.table_id);
let is_selected = el.classList.contains('selected');
if (is_selected) {
selection.delete(el);
Expand All @@ -33,19 +33,19 @@ function selected_deselected(el) {
}
spyCTableSelectionBuffer.length = 0;
for (const element of selection) {
spyCTableSelectionBuffer.push(element.coords);
spyCTableSelectionBuffer.push(element.dataset.coords);
}
Shiny.setInputValue(el.inputId, spyCTableSelectionBuffer);
Shiny.setInputValue(el.dataset.table_id + '_cells_selected', spyCTableSelectionBuffer);
}

function mouse_over_event() {
function mouse_over_event(el) {
if (is_dragging) {
selected_deselected(this)
selected_deselected(el)
}
}

function mouse_down_event() {
selected_deselected(this)
function mouse_down_event(el) {
selected_deselected(el)
}

// This function is to deselect everything in the table
Expand All @@ -58,7 +58,7 @@ function spyctable_deselect_all(tableId) {
}
selection.clear();
spyCTableSelectionBuffer.length = 0;
Shiny.setInputValue(el.inputId, spyCTableSelectionBuffer);
Shiny.setInputValue(tableId + '_cells_selected', spyCTableSelectionBuffer);
}
}

Expand All @@ -68,43 +68,6 @@ addEventListener("mouseup", (_event) => {
disable_dragging();
});

function build_tbody(tableId, inputId, len_x, len_y, data, keys) {
var tbody = document.createElement("tbody");

// If the user clicks then we enable dragging
tbody.onmousedown = enable_dragging;

// If the user's mouse leaves the table we disable dragging
tbody.onmouseleave = disable_dragging;

// Just in case, if the mouse just entered the table we
// disable dragging aswell
tbody.onmouseenter = disable_dragging;

const fragment = document.createDocumentFragment();

for (var i = 0; i < len_y; i++) {
var current_row = document.createElement("tr");
for (var c = 0; c < len_x; c++) {
var current_cel = document.createElement("td");
current_cel.coords = [c, i];
current_cel.innerText = data[keys[c]][i];
current_cel.classList.add("user-select-none");
//We passed the pointer to every single cell
current_cel.tableId = tableId;
current_cel.onmouseover = mouse_over_event;
current_cel.onmousedown = mouse_down_event;
current_cel.inputId = inputId;
current_row.appendChild(current_cel);
}
fragment.appendChild(current_row);
}

tbody.appendChild(fragment);

return tbody;
}

function fromHTML(html, trim = true) {
// Process the HTML string.
html = trim ? html.trim() : html;
Expand Down Expand Up @@ -137,16 +100,8 @@ spyCTableBinding.renderValue = function(el, msg) {
selection = new Set();
globalSpyCTableIndex.set(id, selection);
}
let data = msg.data;
let thead_content = msg.thead;
let keys = Object.keys(data);
let len_x = keys.length;
let len_y = data[keys[0]].length;
var table = document.createElement("table");
table.classList.add("table");
table.id = id + '_inner_table';
table.appendChild(fromHTML(thead_content));
table.appendChild(build_tbody(id, inputId, len_x, len_y, data, keys));

var table = fromHTML(msg.html);
el.appendChild(table);

let scroll_y = el.getAttribute("scroll-y");
Expand Down
26 changes: 8 additions & 18 deletions src/rust/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,39 +188,29 @@ impl<'a> ContainerBuilder<'a> {
}
Some((rowspan, colspan))
}
fn build(mut self) -> String {
let mut buffer = Vec::<u8>::new();
let _ = write!(&mut buffer, "<thead>");
fn build(mut self, buffer: &mut Vec<u8>) {
let _ = write!(buffer, "<thead>");

for row in 0..self.data.nrow {
let _ = write!(&mut buffer, "<tr>");
let _ = write!(buffer, "<tr>");
for col in 0..self.data.ncol {
if let Some((rowspan, colspan)) = self.render_th(row, col) {
let _ = write!(
&mut buffer,
buffer,
r#"
<th colspan={colspan} rowspan={rowspan} class="border text-center align-middle">{}</th>
"#,
self.data.get(row, col).unwrap()
);
}
}
let _ = write!(&mut buffer, "</tr>");
let _ = write!(buffer, "</tr>");
}

let _ = write!(&mut buffer, "</thead>");

unsafe { String::from_utf8_unchecked(buffer) }
let _ = write!(buffer, "</thead>");
}
}

/// @export
#[extendr]
fn spyc_header_create(names: Strings) -> String {
ContainerBuilder::new(&names).build()
}

extendr_module! {
mod header;
fn spyc_header_create;
pub fn spyc_header_create(names: Strings, buffer: &mut Vec<u8>) {
ContainerBuilder::new(&names).build(buffer);
}
43 changes: 36 additions & 7 deletions src/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use extendr_api::prelude::*;

use crate::tbody::{Formatting, NAFormatting};
use std::io::Write;
mod header;
mod tbody;
mod tfoot;

fn list_from_vec_vec(values: Vec<Vec<&str>>, names: StrIter) -> List {
let mut result_list = List::from_iter(values.into_iter().map(|mut values| {
Expand All @@ -14,7 +19,7 @@ fn list_from_vec_vec(values: Vec<Vec<&str>>, names: StrIter) -> List {
}

#[extendr]
fn filter_from_values_vec(values_vec: Integers, data: List) -> List {
fn filter_from_values_vec(values_vec: Strings, data: List) -> List {
let mut result_list = Vec::new();
result_list.resize_with(data.len(), Vec::new);

Expand All @@ -24,22 +29,46 @@ fn filter_from_values_vec(values_vec: Integers, data: List) -> List {

let mut i = 0;
while i < values_vec.len() {
let x = values_vec[i].inner() as usize;
let y = values_vec[i + 1].inner() as usize;
if let Some(str_value) = data[x].index(y + 1).expect("Value must exists").as_str() {
result_list[x].push(str_value);
if let Some((x, y)) = values_vec[i].split_once(',') {
let x: usize = x.parse().expect("X Must be a valid number");
let y: usize = y.parse().expect("X Must be a valid number");
if let Some(str_value) = data[x].index(y + 1).expect("Value must exists").as_str() {
result_list[x].push(str_value);
}
}
i += 2;
i += 1;
}

list_from_vec_vec(result_list, data.names().expect("Must have names"))
}

/// @export
#[extendr]
fn build_spyctable_html(
data: List,
names: Strings,
nrow: i32,
format: Formatting,
na: NAFormatting,
id: &str,
) -> String {
let mut buffer = Vec::new();
let _ = write!(
&mut buffer,
r#"<table id="{id}_inner_table" class="user-select-none table">"#
);
header::spyc_header_create(names, &mut buffer);
tbody::build_tbody_and_foot(nrow, data, format, na, id, &mut buffer);
let _ = write!(&mut buffer, "</table>");

unsafe { String::from_utf8_unchecked(buffer) }
}

// Macro to generate exports.
// This ensures exported functions are registered with R.
// See corresponding C code in `entrypoint.c`.
extendr_module! {
mod spyctable;
use header;
fn filter_from_values_vec;
fn build_spyctable_html;
}
Loading

0 comments on commit 08e598f

Please sign in to comment.