Skip to content

Commit

Permalink
feat: Finalizes implementation of table
Browse files Browse the repository at this point in the history
  • Loading branch information
andyquinterom committed Jul 18, 2024
1 parent 8f40758 commit a88055e
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 120 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export(build_spyctable_html)
export(get_spyc_table_selection)
export(renderSpyCTable)
export(spyCTableOutput)
export(spyctable)
useDynLib(spyctable, .registration = TRUE)
24 changes: 16 additions & 8 deletions R/table.R
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
#' @export
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();
session <- shiny::getDefaultReactiveDomain()
shiny::reactive({
to_render <- func()
list(
# data = to_render,
html = jsonlite::unbox(
build_spyctable_html(
to_render,
colnames(to_render),
nrow(to_render),
"K",
"dash",
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 @@ -48,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)
}
42 changes: 30 additions & 12 deletions inst/example/app.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +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")
)

server <- function(input, output, session) {
table_module_server <- function(id) {
shiny::moduleServer(id, function(input, output, session) {
output$tabla <- renderSpyCTable({
spyctable(
iris_char,
format = "default",
na = "dash"
)
})

output$tabla <- renderSpyCTable({
iris |>
dplyr::mutate(Species = as.character(Species))
}) |>
bindEvent(input$rerender)
observe({
print(
get_spyc_table_selection(input$tabla_cells_selected, iris_char)
)
})

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


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

table_module_server("my_module")

}

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
21 changes: 13 additions & 8 deletions src/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,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 @@ -29,12 +29,14 @@ 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"))
Expand All @@ -51,9 +53,12 @@ fn build_spyctable_html(
id: &str,
) -> String {
let mut buffer = Vec::new();
let _ = write!(&mut buffer, r#"<table id="{id}_inner_table">"#);
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, &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) }
Expand Down
78 changes: 41 additions & 37 deletions src/rust/src/tbody.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use extendr_api::prelude::*;
use extendr_api::AsTypedSlice;
use std::io::Write;

#[derive(Copy, Clone)]
pub enum Formatting {
Millions,
Thousands,
Expand All @@ -22,6 +23,7 @@ impl<'a> FromRobj<'a> for Formatting {
}
}

#[derive(Copy, Clone)]
pub enum NAFormatting {
Dash,
Zero,
Expand All @@ -40,11 +42,44 @@ impl<'a> FromRobj<'a> for NAFormatting {
}
}

fn build_td_na(na: NAFormatting, buffer: &mut Vec<u8>) {
let _ = match na {
NAFormatting::Dash => write!(
buffer,
r#"<td class="border text-center align-middle user-select-none">-</td>"#
),
NAFormatting::Zero => write!(
buffer,
r#"<td class="border text-center align-middle user-select-none">0</td>"#
),
};
}

fn build_td_non_na(format: Formatting, val: f64, buffer: &mut Vec<u8>) {
let _ = match format {
Formatting::Default => write!(
buffer,
r#"<td class="border text-center align-middle user-select-none">{val:.2}</td>"#
),
Formatting::Millions => write!(
buffer,
r#"<td class="border text-center align-middle user-select-none">{val:.0}MM</td>"#,
val = val / 1_000_000.0
),
Formatting::Thousands => write!(
buffer,
r#"<td class="border text-center align-middle user-select-none">{val:.0}K</td>"#,
val = val / 1_000.0
),
};
}

pub fn build_tbody_and_foot(
nrow: i32,
data: List,
format: Formatting,
na: NAFormatting,
table_id: &str,
buffer: &mut Vec<u8>,
) {
let nrow = nrow as usize;
Expand All @@ -67,34 +102,10 @@ pub fn build_tbody_and_foot(
if let Some(real_slice) = data[c].as_real_slice() {
let val = real_slice[i];
if val.is_na() {
let _ = match na {
NAFormatting::Dash => write!(
buffer,
r#"<td class="border text-center align-middle">-</td>"#
),
NAFormatting::Zero => write!(
buffer,
r#"<td class="border text-center align-middle">0</td>"#
),
};
build_td_na(na, buffer);
} else {
total_sum_lookup[c] += val;
let _ = match format {
Formatting::Default => write!(
buffer,
r#"<td class="border text-center align-middle">{val:.2}</td>"#
),
Formatting::Millions => write!(
buffer,
r#"<td class="border text-center align-middle">{val:.0}MM</td>"#,
val = val / 1_000_000.0
),
Formatting::Thousands => write!(
buffer,
r#"<td class="border text-center align-middle">{val:.0}K</td>"#,
val = val / 1_000.0
),
};
build_td_non_na(format, val, buffer);
}
}
} else {
Expand All @@ -104,13 +115,13 @@ pub fn build_tbody_and_foot(
write!(
buffer,
r#"
<td class="border text-center align-middle" onmouseover="mouse_over_event()" onmousedown="mouse_down_event()">{val}</td>
<td data-coords="{c},{i}" data-table_id="{table_id}" class="user-select-none border text-center align-middle" onmouseover="mouse_over_event(this)" onmousedown="mouse_down_event(this)">{val}</td>
"#
)
}
None => write!(
buffer,
r#"<td class="border text-center align-middle" onmouseover="mouse_over_event()" onmousedown="mouse_down_event()"></td>"#
r#"<td data-coords="{c},{i}" data-table_id="{table_id}" class="user-select-none border text-center align-middle" onmouseover="mouse_over_event(this)" onmousedown="mouse_down_event(this)"></td>"#
),
};
}
Expand All @@ -134,10 +145,7 @@ pub fn build_tbody_and_foot(
r#"<td class="border text-center align-middle"></td>"#
);
} else {
let _ = write!(
buffer,
r#"<td class="border text-center align-middle">{val}</td>"#
);
build_td_non_na(format, val, buffer);
}
});
let _ = write!(buffer, "</tr>");
Expand All @@ -155,11 +163,7 @@ pub fn build_tbody_and_foot(
r#"<td class="border text-center align-middle"></td>"#
);
} else {
let _ = write!(
buffer,
r#"<td class="border text-center align-middle">{val}</td>"#,
val = val / nrow as f64
);
build_td_non_na(format, val / nrow as f64, buffer);
}
});
let _ = write!(buffer, "</tr>");
Expand Down

0 comments on commit a88055e

Please sign in to comment.