Highlight word in DT in shiny based on regex
Asked Answered
K

2

9

Using DT in shiny, I want to be able to highlight the selected word. Setting searchHighlight = TRUE is close to what I want, but this will also highlight words that include the search. For example, if I am searching for "on" it will also match "stone", highlighting the "on" in the middle.

EXAMPLE IMAGE:

Words within words being highlighted

I can refine the search options so regex = TRUE, but then no highlighting occurs. This is also true if I want to use regex like "on|in", for example.

EXAMPLE (including regex):

library(shiny)
library(DT)
library(data.table)

example_data <- data.table(words = c("on", "scone", "wrong", "stone"), 
                           description = c("The word on", "Scone is not on.", "Not on either", "Not here at all"))

ui = shinyUI(fluidPage(

  sidebarLayout(
    sidebarPanel(
      textInput("word_select", label = "Word to search")
      ),
    mainPanel(
      dataTableOutput("word_searched")
    )
  )
))

server = shinyServer(function(input, output, session) {

  output$word_searched <- renderDataTable({
    datatable(
      example_data, 
      options = list(searchHighlight = TRUE, 
                     search = list(regex = TRUE, 
                                   search = paste0("\\b", tolower(input$word_select), "\\b")))
    )
  })

  })

shinyApp(ui = ui, server = server)

The DT is already being filtered on the word by a reactive expression, so all the fields will definitely include the selected word, but I just want to avoid confusion from users thinking that longer words are being included in the search erroneously. I haven't done this in the example but just confirming this is not the element I'm concerned about.

Thanks for your help.

(EDITED to add an example of a word with punctuation in the example data.)

Kansu answered 17/6, 2019 at 14:42 Comment(0)
T
9

Instead of relying on datatable's search functionality you can create a reactive element that first filters by the input, and then replaces the matching words with the same word embedded in a <span style="background-color:yellow;"> tag. This should allow more search flexibility via more complex regex.

You'll need to add escape = F to datatable so the HTML tag is interpreted properly. I've added options = list(dom = "lt") to datatable to remove the datatable's search field and direct attention to the left search field.

The filtering criteria are left fairly fuzzy to keep the table from disappearing until a perfect match is found – i.e. the table shouldn't disappear when you type "o" because there's no perfect match, and then reappear at "on". The highlights then only appear if a matching word is found, i.e. on, On, and on., but not stone, scone, etc. Here's a glimpse of what it looks like:

enter image description here

And here's the code. Note that I use dplyr's filtering and mutating functions because they can easily be applied to multiple columns via their *_all variants:

library(shiny)
library(DT)
library(data.table)
library(dplyr) # For `filter_all` and `mutate_all`.

example_data <- iris
    # data.table(words = c("on", "scone", "wrong", "stone"), 
    #                        description = c("The word on", "Scone is not on.", "Not on either", "Not here at all"))

ui = shinyUI(fluidPage(

    sidebarLayout(
        sidebarPanel(
            textInput("word_select", label = "Word to search")
        ),
        mainPanel(
            dataTableOutput("word_searched")
        )
    )
))

server = shinyServer(function(input, output, session) {

    # This is your reactive element.
    df_reactive <- reactive({
            example_data %>%
                # Filter if input is anywhere, even in other words.
                filter_all(any_vars(grepl(input$word_select, ., T, T))) %>% 
                # Replace complete words with same in HTML.
                mutate_all(~ gsub(
                              paste(c("\\b(", input$word_select, ")\\b"), collapse = ""),
                              "<span style='background-color:yellow;'>\\1</span>",
                              .,
                              TRUE,
                              TRUE
                              )
                          )
    })

    # Render your reactive element here.
    output$word_searched <- renderDataTable({
        datatable(df_reactive(), escape = F, options = list(dom = "lt"))
    })

})

shinyApp(ui = ui, server = server)
Taxable answered 11/7, 2019 at 8:33 Comment(4)
Thanks, this basically works! It doesn't necessarily work for the regex element but I think I can work out something for that with this approach. NB I used the following code for the reactive to keep using data.table (doesn't filter because I didn't need that but similar approaches are possible): example_data_dt[, lapply(.SD, function(x) gsub( paste0("\\b(", input$word_select, ")\\b"), "<span style='background-color:yellow;'>\\1</span>", x, TRUE, TRUE ))]Kansu
I really appreciate that this looks at the problem from a different angle to how I was trying to do it (from within DT itself) :)Kansu
@Megan happy to hear it works! This strategy also has the advantage that there are fewer constraints on what you can do. For example, using if-else conditions you could use one color for partial matches and another for full matches, etc. I might be mistaken, but datatable's built-in search highlighting doesn't look very versatile.Taxable
@gersht thanks a lot for this regex, it's really useful! I've slightly adapted your code to make it work just with one column of my dataset: mutate(caption = gsub(pattern = paste(c("\\b(", input$filter_captions, ")\\b"), collapse = ""), replacement = "<span style='background-color: #F08080;'>\\1</span>", x = data_insta_filtered$caption)). However, I have a question: do you know how I can highlight not just the entire word, but just a small part of a word that matches with my selection? Thanks a lot!Welltimed
Q
2

I'm not sure this is what you exactly want but this is close I think: this does not perform an exact search (e.g. "on" will match "stone") but this only highlights the exact match (e.g. "on" will not be highlighted). This uses the mark.js library.

dtable <- datatable(iris[c(1,2,51,52,101,102),], 
                    options = list(
                      mark = list(accuracy = "exactly")
                    )
)
dep1 <- htmltools::htmlDependency(
  "datatables.mark", "2.0.1", 
  src = c(href = "https://cdn.datatables.net/plug-ins/1.10.19/features/mark.js"),
  script = "datatables.mark.min.js")
dep2 <- htmltools::htmlDependency(
  "jquery.mark", "8.11.1", 
  src = c(href = "https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1"), 
  script = "jquery.mark.min.js")
dtable$dependencies <- c(dtable$dependencies, list(dep1, dep2))
dtable

enter image description here

Quent answered 19/6, 2019 at 16:36 Comment(2)
Thanks! This looks promising. I've checked this within the shiny app and it almost works. One thing I've noticed is that it won't highlight words with punctuation - for example if the word is something like "(on)" or at the end of a sentence like "on." Do you know any way to deal with that? Separately (and less importantly) is there a way to change the highlight colour? Mine is a very pale yellow on the screen that isn't bright enough to really stand out.Kansu
@Megan See the doc here. You can control the colour with CSS. For the punctuation I don't know.Rutilant

© 2022 - 2024 — McMap. All rights reserved.