I need some help with conditional formatting for DT::datatable. I would like to highlight a couple of names in the following example by printing them in italics. The names which need to be highlighted are in a vector name.highlight <- c("ABC","JKL")
require(DT)
mydf <- data.frame(name=c("ABC","DEF","GHI","JKL","MNO","PQR"), value=1:6)
DT::datatable(mydf)
Based on what I see here and here, seems like I need to use render. I have no idea how to write the JS code or how I can pass in a vector/container with all the strings which need to be highlighted.
datatable(mydf, options = list(columnDefs = list(list(
targets = 0, render = JS("function(data, type, full, meta) {", ..., "}")
))))
Thanks.
datatable(mydf, options = list(columnDefs = list(list(
targets = 0, render = JS(
"function(data, type, full, meta) {",
"italic_words=['ABC','JKL']",
"return type === 'display' && italic_words.indexOf(data) != -1 ?",
"'<i>'+data+'</i>' : data;",
"}")
))))
I defined the italic_words variable in the javascript function. The variable contains an array of all the words you want in italic. Then I used the indexOf() javascript function. If the name isn't in the variable italic_words, this function will return -1, and the name will not be italicized.
Related
I am to construct a function named read_text_file.
It takes in an argument textFilePath that is a single character and two optional parameters withBlanks and withComments that are both single
logicals;
textFilePath is the path to the text file (or R script);
if withBlanks and withComments are set to FALSE, then read_text_file() will return the text file without blank lines (i.e. lines that contain nothing or only whitespace) and commented (i.e. lines that starts with “#”) lines respectively;
it outputs a character vector of length n where each element corresponds to its respective line of text/code.
I came up with the function below:
read_text_file <- function(textFilePath, withBlanks = TRUE, withComments = TRUE){
# check that `textFilePath`: character(1)
if(!is.character(textFilePath) | length(textFilePath) != 1){
stop("`textFilePath` must be a character of length 1.")}
if(withComments==FALSE){
return(grep('^$', readLines(textFilePath),invert = TRUE, value = TRUE))
}
if(withBlanks==FALSE){
return(grep('^#', readLines(textFilePath),invert = TRUE, value = TRUE))
}
return(readLines(textFilePath))
}
The second if-statement will always be executed leaving the third if-statement unexecuted.
I'd recommend processing an imported object instead of returning it immediately:
read_text_file <- function(textFilePath, withBlanks = TRUE, withComments = TRUE){
# check that `textFilePath`: character(1)
if(!is.character(textFilePath) | length(textFilePath) != 1){
stop("`textFilePath` must be a character of length 1.")}
result = readLines(textFilePath)
if(!withComments){
result = grep('^\\s*#\\s*', result, invert = TRUE, value = TRUE)
}
if(!withBlanks){
result = grep('^\\s*$', result, invert = TRUE, value = TRUE)
}
result
}
The big change is defining the result object that we modify as needed and then return at the end. This is good both because (a) it is more concise, not repeating the readLines command multiple times, (b) it lets you easily do 0, 1, or more data cleaning steps on result before returning it.
I also made some minor changes:
I don't use return() - it is only needed if you are returning something before the end of the function code, which with these modifications is not necessary.
You had your "comment" and "blank" regex patterns switched, I corrected that.
I changed == FALSE to !, which is a little safer and good practice. You could use isFALSE() if you want more readability.
I added \\s* into your regex patterns in a couple places which will match any amount of whitespace (including none)
I want to implement DT's SearchPanes extension while explicitly including/excluding specific fields but am not sure how to accomplish it. I want all of the fields to show, but only select fields to be included in the SearchPanes interface.
In the reproducible example below (which is essentially from the documentation), how would I select just island, sex, and year for inclusion in SearchPanes?
I've tried different settings with searchPanes and the targets arguments, but I do not understand how to use these and I don't yet follow the JS documentation very well. Any guidance on how these work and how to select columns for inclusion would be much appreciated.
library(DT)
library(palmerpenguins)
datatable(
penguins,
options = list(dom = 'Pfrtip', columnDefs = list(list(
searchPanes = list(show = FALSE), targets = 1:4
))),
extensions = c('Select', 'SearchPanes'),
selection = 'none'
)
There may be a better way but this works:
datatable(
penguins,
options = list(
dom = 'Pfrtip',
columnDefs = list(
list(
searchPanes = list(show = FALSE), targets = c(1, 3:6)
),
list(
searchPanes = list(show = TRUE), targets = c(2, 7:9)
)
)
),
extensions = c('Select', 'SearchPanes'),
selection = 'none'
)
The searchPanes argument show can be set to TRUE or FALSE and the state is directed to specific target columns defined in the targets argument.
On one line, I set show=FALSE and defined all of the columns that I did not want showing. Then on the following line, I set showTRUE and defined the columns that I want showing. I found that if I set a column to show=FALSE and in the next line set it to show=TRUE it remains unshown.
The DataTables JS library documentation was helpful, but took a bit to understand. The section on columns.searchPanes.show contained the answer. Each searchPanes statement has some arguments and a set of targets. As follows:
$(document).ready(function() {
$('#example').DataTable({
dom: 'Plfrtip',
columnDefs: [
{
searchPanes: {
show: true
},
targets: [0]
},
{
searchPanes: {
show: false
},
targets: [2]
}
]
});
});
I'm new to drake but loving it so far. One thing I'm having trouble with is how to best go about experimenting with different pipeline configurations. That is, my plans consist purely of a chain of targets where the output from the first target is the input for the second, the second forms the input for the third, etc. My targets all have the same basic structure (dynamic targets with tibbles as individual entries) expected as input and supplied as output, and I want to experiment with different orderings, inclusion/exclusion of certain steps, etc. For example:
plan = drake::drake_plan(
a_transformed = target(
compute_a_transform(list_of_input_data)
, dynamic = map(list_of_input_data)
)
, b_transformed = target(
compute_b_transform(a_transformed)
, dynamic = map(a_transformed)
)
, c_transformed = target(
compute_c_transform(c_transformed)
, dynamic = map(c_transformed)
)
)
The way I've been using drake so far is that each target has a unique/meaningful name, so when I, for example, remove a target, I have to rename the input supplied to the subsequent target:
plan = drake::drake_plan(
a_transformed = target(
compute_a_transform(list_of_input_data)
, dynamic = map(list_of_input_data)
)
#, b_transformed = target(
# compute_b_transform(a_transformed)
# , dynamic = map(a_transformed)
#)
#note the b-transform step has been removed (commented-out), requiring inputs to c_transform to be changed from `b_transform` to `a_transform`
, c_transformed = target(
compute_c_transform(a_transformed) #had to rename things here
, dynamic = map(a_transformed) #and here
)
)
Would it be too much to hope that there's a better way of experimenting that doesn't require this manual commenting-out and renaming?
I worked out a method that is a bit of a hack but works for me. I simply add a skip argument to each function that triggers return of the input if TRUE:
compute_a_transform = function(x,skip=F){
if(skip){
return(x)
}
... #regular compute_a_transform stuff here
}
Then, when I want to skip a step in the processing chain, I simply set skip=TRUE without commenting-out or renaming anything
plan = drake::drake_plan(
a_transformed = target(
compute_a_transform(list_of_input_data)
, dynamic = map(list_of_input_data)
)
, b_transformed = target(
compute_b_transform(a_transformed, skip=TRUE) #skip=TRUE means the b-transform isn't actually applied
, dynamic = map(a_transformed)
)
, c_transformed = target(
compute_c_transform(c_transformed)
, dynamic = map(c_transformed)
)
)
I'm looking for a straight forward way to change the formatting of numbers into K,M in shiny dataTables. Preferably with something like formatCurrency. I don't want to write k, m functions to convert number into string in order to do the formatting as it makes it difficult to sort rows by value.
There's no built-in way to do this, but it's not too bad to write your own format function in JavaScript that doesn't break row sorting.
See Column Rendering in the DT docs for how to do this: https://rstudio.github.io/DT/options.html
And this will also help:
https://datatables.net/reference/option/columns.render
Here's an example of a custom thousands formatter that rounds to 1 decimal place:
library(DT)
formatThousands <- JS(
"function(data) {",
"return (data / 1000).toFixed(1) + 'K'",
"}")
datatable(datasets::rock, rownames = FALSE, options = list(
columnDefs = list(list(
targets = 0:1, render = formatThousands
))
))
Alternatively, if you want a non-JavaScript method, you could use the colFormat function used with the reactable package. Unfortunately, there is no automatic millions option but it's pretty easy to replicate if you divide the original data and add the labels on with colFormat.
Product <- c('Apples','Oranges','Pears')
Revenue <- c(212384903, 23438872, 26443879)
df <- data.frame(Product,Revenue)
df$Revenue_millions <- dfeg$Revenue/1000000
reactable(df,
showSortable = TRUE,
columns = list(
Revenue_millions = colDef(format = colFormat(prefix = "£", separators = TRUE,digits=1,suffix = "m"))))
The data should now sort correctly
If you are using DataTables, to get the data as Unit format i.e
10000 -> 10K
we can use render function
"render": function ( data ) {
if(data > 999 && data < 1000000) {
return data/1000+' K'
}
else if(data > 1000000){
return data/1000000+' M'
}
else{
return data
}
}
}
This is a Q on the Column rendering example (4.4)provided in this link.
http://rstudio.github.io/DT/options.html
I have implemented the example code described above to abbreviate character strings that are wider than 100 characters using the first 100 characters plus an ellipsis (…), and the full character string is displayed as a tooltip when you mouse over the cell. This works well as long as the column with custom rendering has full text in it. However, when it encounters an empty cell the table is not displayed and it shows " Processing..." on shiny browser. On disabling this custom rendering I am able to display the table with empty fields as expected.
Did anyone had similar issue, any suggestion to overcome this ?
Below is my custom column rendering code.
output$PM_output <- DT::renderDataTable(
expr = DT::datatable(PubmedOutput(PubmedSearch()),
class = 'cell-border stripe compact hover',
escape = F, selection = 'multiple',
options = list(
initComplete = JS("function(settings, json) {",
"$(this.api().table().header()).css({
'background-color': '#303030',
'color': '#FFFF00'});","}"),
autoWidth = T,
LengthMenu = c(5, 30, 50),
columnDefs = list(list(
targets = 6,
render = JS(
"function(data, type, row, meta) {",
"return type === 'display' && data.length > 100 ?",
"'<span title=\"' + data + '\">' +
data.substr(0, 100) + '...</span>' : data;", "}"))),
columnDefs = list(list(
targets = c(1:8),
className = 'dt-center')),
pageLength = 1, server = T)))
The code that generate the Column 6 that I have passed custom rendering on.
PM.ID <- c("26391251","26372702","26372699","26371045") # does not output table
fetch.pubmed <- entrez_fetch(db = "pubmed", id = PM.ID,
rettype = "xml", parsed = T)
abstracts = xpathApply(fetch.pubmed, '//PubmedArticle//Article', function(x) xmlValue(xmlChildren(x)$Abstract))
abstracts # ID 26372702, 26372699 has no abstract. and returns NA
Any inputs and suggestion.
P.S: Is there a better way to display data other than ellipsis/ tooltip ?
the code is too big to paste it all, hence picking only the parts where I noticed the issue. I hope it helps.
The condition data.length > 100 is not enough: you need to make sure data is a character string first. In your case, data may be null (converted from R's NA to JavaScript), and null.length will trigger an error. Replace
type === 'display' && data.length > 100
with a more rigorous condition:
type === 'display' && typeof data === 'string' && data.length > 100