---
title: "html"
---
# HTML customization

```{=html}
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax = {
  tex: {
    inlineMath: [['$', '$'], ['\\(', '\\)']]
  },
  svg: {
    fontCache: 'global'
  }
};
</script>
```

```{r}
library(tinytable)
options(tinytable_tt_digits = 3)
x <- mtcars[1:4, 1:5]
```

## `tinytable` subclasses

The `tinytable` package comes with built-in CSS subclasses that provide Bootstrap-style functionality without requiring the Bootstrap framework. These subclasses can be applied using `theme_html()` with the `class` argument.

The available subclasses can be viewed by calling `cat(tinytable:::get_css())`. Here are the main options:

### Small padding (`tinytable-sm`)

```{r, eval = knitr::is_html_output()}
tt(x) |> theme_html(class = "tinytable tinytable-sm")
```

### Bordered table (`tinytable-bordered`)

```{r, eval = knitr::is_html_output()}
tt(x) |> theme_html(class = "tinytable tinytable-bordered")
```

### Striped rows (`tinytable-striped`)

```{r, eval = knitr::is_html_output()}
tt(x) |> theme_html(class = "tinytable tinytable-striped")
```

### Hover effects (`tinytable-hover`)

```{r, eval = knitr::is_html_output()}
tt(x) |> theme_html(class = "tinytable tinytable-hover")
```

### Multiple classes combined

```{r, eval = knitr::is_html_output()}
tt(x) |> theme_html(class = "tinytable tinytable-sm tinytable-hover tinytable-bordered")
```

## CSS: cells

The `style_tt()` function allows us to declare CSS properties and values for individual cells, columns, or rows of a table. For example, if we want to make the first column bold, we could do:

```{r}
tt(x) |>
  theme_html(j = 1, css = "font-weight: bold; color: red;")
```

## CSS: table

The default HTML class for `tinytable` is `.tinytable`. We can use this class to customize the overall appearance of the table using CSS. The default CSS style sheet can be viewed here:

[https://github.com/vincentarelbundock/tinytable/blob/main/inst/tinytable.css](https://github.com/vincentarelbundock/tinytable/blob/main/inst/tinytable.css)

For extensive customization, users can modify this file, read it as a string in `R`, and supply it to the `css_rule` argument of the `theme_html()` function. Alternatively, they can write their own completely customized CSS rules, as illustrated below.

Notes:

1. Using `css_rule` will completely overwrite the default `tinytable` CSS styling. You can retrieve the original with `tinytable:::get_css()` as a string, which you can manipulate and combine as required.
2. When creating a completely new stylesheet, it is useful to assign a custom class name to the table with the `class` argument in `theme_html()`. Otherwise styles may spill over to other tables in the same document.

```{r, eval = knitr::is_html_output()}
css_rule <- "
.gradienttable {
  background: linear-gradient(45deg, #EA8D8D, #A890FE);
  width: 600px;
  border-collapse: collapse;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0,0,0,0.1);
}

.gradienttable th,
.gradienttable td {
  padding: 5px;
  background-color: rgba(255,255,255,0.2);
  color: #fff;
}

.gradienttable tbody tr:hover {
  background-color: rgba(255,255,255,0.3);
}

.gradienttable tbody td:hover:before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -9999px;
  bottom: -9999px;
  background-color: rgba(255,255,255,0.2);
  z-index: -1;
}

.gradienttable thead th {
    border-bottom: none;
}
"

tt(x, width = 2 / 3) |>
  theme_empty() |>
  theme_html(css_rule = css_rule, class = "gradienttable") |>
  style_tt(j = 1:5, align = "ccccc")
```


And here's another example:


```{r}
css <- "
.squirreltable {
  background-size: cover;
  background-position: center;
  background-image: url('https://user-images.githubusercontent.com/987057/82732352-b9aabf00-9cda-11ea-92a6-26750cf097d0.png');
  --bs-table-bg: transparent;
}
.squirreltable thead th {
    border-bottom: none;
}
"

tt(mtcars[1:10, 1:8]) |>
  theme_empty() |>
  theme_html(css_rule = css, class = "squirreltable")
```

And yet another one. Some Rmarkdown documents like `bookdown` use older versions of Bootstrap that do not have a `caption-top` class. We can recreate that functionality with CSS rules and classes. For example,

```r
rule <- ".bottomcaption {caption-side: bottom;}"
tt(head(iris), caption = "Hello world") |>
  theme_html(class = "bottomcaption", css_rule = rule)
```


## Bootstrap

The Bootstrap framework provides a number of built-in themes to style tables, using "classes." To use them, call `theme_html()` to set `engine="bootstrap"`, and then use the `class` argument. A list of available Bootstrap classes can be found here: <https://getbootstrap.com/docs/5.3/content/tables/>

For these examples we set a global option that calls `theme_empty()` in order to remove the top, mid, and bottom rules of the table. This is optional, but helps illustrate the basic Bootstrap styling. We also override some default CSS rules set by `tinytable`, using the `css_rule` argument. We also set the default `css_rule` to an empty string in order to override the default `tinytable` CSS rules.

```{r, eval = knitr::is_html_output()}
options(tinytable_tt_theme = theme_empty)
options(tinytable_html_css_rule = "")

tt(x) |> theme_html(engine = "bootstrap", class = "table")
```

To produce a "bordered" table, we use the `table-bordered` class:


```{r, eval = knitr::is_html_output()}
tt(x) |> theme_html(engine = "bootstrap", class = "table table-bordered")
```

We can also combine several Bootstrap classes. Here, we get a table with the "hover" feature:

```{r, eval = knitr::is_html_output()}
tt(x) |> theme_html(engine = "bootstrap", class = "table table-hover")
```

By default, Bootstrap 5 places captions at the bottom of the table. This can be changed by using a different class:

```{r}
tt(x, caption = "A caption on top") |>
  theme_html(engine = "bootstrap", class = "table caption-top")
```

::: {.callout-warning}
Quarto ships with its own set of Bootstrap CSS rules, which may sometimes interfere with `tinytable` styles. It is somewhat "safer" to work with the default and more vanilla `tinytable` class rather than set `engine = "bootstrap"` Also, Quarto does not appear to support all bootstrap subclasses, like `table caption-top`.
:::

```{r}
options(tinytable_tt_theme = NULL)
options(tinytable_html_css_rule = NULL)
```

## Fontawesome

We can use [the `fontawesome` package](https://rstudio.github.io/fontawesome/index.html) to include fancy icons in HTML tables:

```{r, eval = knitr::is_html_output()}
library(fontawesome)
library(tinytable)
tmp <- mtcars[1:4, 1:4]
tmp[1, 1] <- paste(fa("r-project"), "for statistics")
tt(tmp)
```

## JS Plotting Libraries

This section shows how to insert plots from JavaScript plotting libraries like Plotly, D3, etc.

[Plotly](https://plotly.com/javascript/) is a popular JavaScript library for creating interactive charts and visualizations. We can embed Plotly plots directly in `tinytable` HTML tables by combining two features:

1. **Loading the Plotly library**: Use `theme_html(script = ...)` to load the Plotly JavaScript library from a CDN
2. **Embedding plot code in cells**: Insert raw HTML and JavaScript code directly into table cells

This approach works with any JavaScript library - Plotly is just one example. The `script` parameter in `theme_html()` injects custom JavaScript into the HTML output, making external libraries available to code embedded in table cells.

Here's a complete example showing histograms, density plots, and line plots (sparklines) embedded in a table:

```{r, eval = knitr::is_html_output()}
# Prepare data
plot_data <- list(mtcars$mpg, mtcars$hp, mtcars$qsec)

# Random data for line plots (sparklines)
lines <- lapply(1:3, \(x) data.frame(x = 1:10, y = rnorm(10)))

# Okabe-Ito color palette (colorblind-friendly)
okabe_ito <- palette.colors(palette = "Okabe-Ito")

# Helper function to create inline Plotly code for a histogram
create_histogram <- function(data, color = okabe_ito[2]) {
  id <- paste0("plot_", sprintf("%08x", as.integer(runif(1) * 1e8)))
  data_json <- paste0("[", paste(data, collapse = ","), "]")
  sprintf('<div id="%s"></div>
<script>
  Plotly.newPlot("%s", [{
    x: %s,
    type: "histogram",
    marker: {color: "%s"}
  }], {
    showlegend: false,
    margin: {l: 0, r: 0, t: 0, b: 0},
    xaxis: {visible: false},
    yaxis: {visible: false},
    height: 50,
    width: 150
  }, {displayModeBar: false});
</script>', id, id, data_json, color)
}

# Helper function to create inline Plotly code for a density plot
create_density <- function(data, color = okabe_ito[4]) {
  id <- paste0("plot_", sprintf("%08x", as.integer(runif(1) * 1e8)))
  # Compute density using R
  dens <- density(data, n = 100)
  x_json <- paste0("[", paste(round(dens$x, 3), collapse = ","), "]")
  y_json <- paste0("[", paste(round(dens$y, 5), collapse = ","), "]")
  # Convert hex to rgba with 0.3 opacity
  rgb_vals <- col2rgb(color)
  fillcolor <- sprintf("rgba(%d, %d, %d, 0.3)", rgb_vals[1], rgb_vals[2], rgb_vals[3])
  sprintf('<div id="%s"></div>
<script>
  Plotly.newPlot("%s", [{
    x: %s,
    y: %s,
    type: "scatter",
    mode: "lines",
    fill: "tozeroy",
    line: {color: "%s", width: 1.5},
    fillcolor: "%s"
  }], {
    showlegend: false,
    margin: {l: 0, r: 0, t: 0, b: 0},
    xaxis: {visible: false},
    yaxis: {visible: false},
    height: 50,
    width: 150
  }, {displayModeBar: false});
</script>', id, id, x_json, y_json, color, fillcolor)
}

# Helper function to create inline Plotly code for a line plot
create_line <- function(data, color = okabe_ito[6]) {
  id <- paste0("plot_", sprintf("%08x", as.integer(runif(1) * 1e8)))
  x_json <- paste0("[", paste(data$x, collapse = ","), "]")
  y_json <- paste0("[", paste(data$y, collapse = ","), "]")
  sprintf('<div id="%s"></div>
<script>
  Plotly.newPlot("%s", [{
    x: %s,
    y: %s,
    type: "scatter",
    mode: "lines",
    line: {color: "%s"}
  }], {
    showlegend: false,
    margin: {l: 0, r: 0, t: 0, b: 0},
    xaxis: {visible: false},
    yaxis: {visible: false},
    height: 50,
    width: 150
  }, {displayModeBar: false});
</script>', id, id, x_json, y_json, color)
}

# Create data frame with inline Plotly code in each cell
dat <- data.frame(
  Variables = c("mpg", "hp", "qsec"),
  Histogram = c(
    create_histogram(plot_data[[1]]),
    create_histogram(plot_data[[2]]),
    create_histogram(plot_data[[3]])
  ),
  Density = c(
    create_density(plot_data[[1]]),
    create_density(plot_data[[2]]),
    create_density(plot_data[[3]])
  ),
  Line = c(
    create_line(lines[[1]]),
    create_line(lines[[2]]),
    create_line(lines[[3]])
  )
)

# Create table - only load Plotly library via theme_html(script = ...)
tt(dat, width = 1) |>
  theme_html(script = '<script src="https://cdn.plot.ly/plotly-3.1.0.min.js" charset="utf-8"></script>') |>
  style_tt(j = 2:4, align = "c")
```

The key pattern here is:

1. Load the external library once using `theme_html(script = "<script src='...'></script>")`
2. Create helper functions that generate HTML/JavaScript code for each plot
3. Use these functions to populate table cells with raw HTML/JavaScript
4. Each plot gets a unique ID so multiple plots can coexist in the same table

This approach works for any JavaScript visualization library (D3.js, Chart.js, etc.) or custom JavaScript code you want to include in your tables.

## Miscellaneous

* [Relative widths tables: `table-layout: fixed` vs `auto`.](https://github.com/vincentarelbundock/tinytable/issues/305)
