gt icon indicating copy to clipboard operation
gt copied to clipboard

[Feature Request] abbreviate long text when displaying in HTML

Open shrektan opened this issue 5 years ago • 6 comments

Hi,

First of all, I love this package. I'm really a fun of making beautiful tables. gt is so powerful and can generate almost everything I need... WoW.. A big thanks!

When the table contain very long text, gt handles them by wrapping. However, abbreviating it sometime is prefered, because users can get the full info by hovering on the abbr without being disturbed by those very wide rows.

Luckily, gt is very flexible (I actually created the below example by using gt::text_transform()) but having a gt function that can handle abbr automatically would be even better.

Thanks!

code to generate the example

Without abbr

random_text <- function(length) {
  paste0(
    sample(c(letters, LETTERS, ' '), length, replace = TRUE, prob = c(rep(0.7 / 52, 52), 0.3)),
    collapse = ''
  )
}

random_text_vec <- function(lengths) {
  sapply(lengths, random_text)
}

tbl <- data.frame(
  short_text = random_text_vec(sample(1:20, 5)),
  short_text2 = random_text_vec(sample(1:20, 5)),
  long_text = random_text_vec(sample(200:500, 5)),
  long_text2 = random_text_vec(sample(200:500, 5)),
  short_text3 = random_text_vec(sample(1:20, 5))
)

gt::gt(tbl)

image

After abbr

abbr_text <- function(x, length, suffix = "") {
  stopifnot(assertthat::is.string(x), assertthat::is.number(length), length >= 1L)
  if (isTRUE(is.na(x))) return("")
  flag_abbr <- (length < stringr::str_length(x))
  if (isTRUE(flag_abbr)) {
    res <- htmltools::tags$span(paste0(stringr::str_sub(x, 1, length), suffix), title =  x)
  } else {
    res <- htmltools::htmlEscape(x)
  }
  res <- stringr::str_replace_all(as.character(res), "\n", "/")
  as.character(res)
}

abbr_text_vec <- function(x, length, suffix = "") {
  sapply(x, abbr_text, length = length, suffix = suffix)
}

library(magrittr)
gt::gt(tbl) %>% gt::text_transform(
  gt::cells_data(c('long_text', 'long_text2')),
  function(x) sapply(abbr_text_vec(x, 20, '...'), gt::html)
)

image

shrektan avatar Feb 15 '19 11:02 shrektan

Moreover, is it possible to not wrap for certain columns?

shrektan avatar Feb 15 '19 11:02 shrektan

Hi @shrektan , I think this is a great idea! I would propose something a little different but more responsive (still rough, and doesn't use a htmltools TagList):

library(gt)

random_text <- function(length) {
  paste0(
    sample(c(letters, LETTERS, ' '), length, replace = TRUE, prob = c(rep(0.7 / 52, 52), 0.3)),
    collapse = ''
  )
}

random_text_vec <- function(lengths) {
  sapply(lengths, random_text)
}

tbl <- 
  data.frame(
    short_text = random_text_vec(sample(1:20, 5)),
    short_text2 = random_text_vec(sample(1:20, 5)),
    long_text = random_text_vec(sample(200:500, 5)),
    long_text2 = random_text_vec(sample(200:500, 5)),
    short_text3 = random_text_vec(sample(1:20, 5))
  )

abbrev_text <- function(x) {
  
  paste0(
    "<div style=\"display:table;table-layout:fixed;width:100%;\">",
    "<p title=\", x , "\", ",
    "style=\"overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap\">",
    x,
    "</p>",
    "</div>"
  )
}

tbl %>% 
  gt() %>%
  text_transform(
    locations = cells_data(columns = vars(long_text)),
    fn = abbrev_text
  )

This produces:

gt_abbreviated_ellipsis_text

Which still results in the full text appearing in the standard tooltip when hovering. Also, when resizing the table, the text won't wrap and always show the ellipsis. Let me know what you think of it.

I think that what needs to be done is to just supply the exported abbrev_text() function. It could use some refinement before that though. However, I can see a whole set of transformer functions like this being useful for within text_transform().

rich-iannone avatar Feb 19 '19 14:02 rich-iannone

It's brilliant! I love it.

Not only it's prettier but also it doesn't need to provide a fixed abbr text length... which is important when the page width is variable...

And yes, an exported abbrev_text() function is good enough.

shrektan avatar Feb 19 '19 16:02 shrektan

@rich-iannone Is there any simpler way to make other columns being no-wrap as well? Although the abbr column is not wrapped, it sometimes makes other columns being wrapped and looks not good to me...

The only way I can think of is to text_transform() all the columns with style = "white-space:nowrap"... It look tedious to me and I can't set it to the column lables.

Another way is to add the following css if using Shiny...

table.gt_table {
  white-space: nowrap;
}

Is it possible to do it in a simpler way?

I can file another Feature Requrest to address this, if there's no existing solution and you think it's a reasonable...

UPDATE Adding an option like ...nowrap in gt::tab_options() seems a good idea.

Thanks.

An example - I don't want the red line words being wrapped as well because it's already short enough...

image

shrektan avatar Feb 20 '19 02:02 shrektan

In addition, change the <p></p> tag to <div></div> in your code seems better on my computer. Using <p> may lead the cell's text not 100% horizontally aligned with others...

abbrev_text <- function(x) {
  paste0(
    "<div style=\"display:table;table-layout:fixed;width:100%;\">",
    "<div title=\"", x , "\", ", # `<p>` has been changed to `<div>` here
    "style=\"overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap\">",
    x,
    "</div>",
    "</div>"
  )
}

shrektan avatar Feb 20 '19 03:02 shrektan

@shrektan Thanks for the follow up notes.

A no-wrap option might be good as a global option (and is easy to implement). This might be also addressed when we introduce options to set the relative widths of columns.

Thanks for the advice on using a set of <div>s. That is the correct approach!

rich-iannone avatar Feb 21 '19 14:02 rich-iannone