from reactable import embed_css
embed_css()
Cookbook
Insert links
import polars as pl
import htmltools as ht
from reactable import Reactable, Column
= pl.DataFrame(
data
{"Address": ["https://google.com", "https://yahoo.com", "https://duckduckgo.com"],
"Site": ["Google", "Yahoo", "DuckDuckGo"],
}
)
Reactable(
data,={
columns"Address": Column(
=lambda info: ht.a(info.value, href=info.value, target="_blank"),
cell
),"Site": Column(
=True,
html=lambda info: f'<a href="{info.value}" target="_blank">{info.value}</a>',
cell
),
}, )
Format color scales
Single column
from reactable import Reactable, Column, CellInfo
from reactable.data import cars_93
from mizani.palettes import gradient_n_pal
= cars_93[["manufacturer", "model", "price"]]
data
= gradient_n_pal(["#ffe4cc", "#ff9500"])
pal
def fmt_fill(ci: CellInfo):
= max(data["price"]) - min(data["price"])
val_range = (ci.value - min(data["price"])) / val_range
normalized return {"background": pal(normalized)}
Reactable(
data,={"price": Column(style=fmt_fill)},
columns=5,
default_page_size )
Grid
from reactable import Reactable, Column, ColFormat, CellInfo
from reactable.data import nottem
from mizani.palettes import gradient_n_pal
= gradient_n_pal(["#7fb7d7", "#ffffbf", "#fc8d59"])
pal
# flatten out monthly columns into a single list
# this lets us calculate the overall min and max
= sum(nottem[:, 1:].to_dict().values(), [])
flat_vals
def fmt_fill(ci: CellInfo):
if not isinstance(ci.value, float):
return
= max(flat_vals) - min(flat_vals)
val_range = (ci.value - min(flat_vals)) / val_range
normalized = pal(normalized)
color
return {"background": color}
Reactable(
nottem,=Column(
default_col_def=fmt_fill,
styleformat=ColFormat(digits=1),
=50,
min_width
),# TODO: make year rowname
={
columns"year": Column(
format=ColFormat(digits=0),
=True,
row_header
),
},=True,
bordered=5,
default_page_size )
Format changes
import polars as pl
from reactable import Reactable, Column, CellInfo
= pl.DataFrame(
data
{"Symbol": ["GOOG", "FB", "AMZN", "NFLX", "TSLA"],
"Price": [1265.13, 187.89, 1761.33, 276.82, 328.13],
"Change": [4.14, 1.51, -19.45, 5.32, -12.45],
}
)
Reactable(
data,={
columns"Change": Column(
# TODO: we should stringify, so people can
# return ci.value directly
=lambda ci: f"+{ci.value}" if ci.value >= 0 else str(ci.value),
cell=lambda ci: {
style"font-weight": 600,
"color": "#008000" if ci.value > 0 else "#e00000",
},
)
}, )
Bar charts
import htmltools
from reactable import Reactable, Column, CellInfo
from reactable.data import cars_93
= cars_93[:5, ["make", "mpg_city", "mpg_highway"]]
data
def html_barchart(label, width="100%", height="1rem", fill="#00bfc4", background=None):
"""Create general purpose html fill bar."""
= htmltools.div(style=f"background: {fill}; width: {width}; height: {height}")
bar = htmltools.div(
chart
bar,=htmltools.css(
style=1,
flex_grow="0.5rem",
margin_left=background,
background
),
)return htmltools.div(
label,
chart,=htmltools.css(
style="flex",
display="center",
align_items
),
)
def fmt_barchart(ci: CellInfo, **kwargs):
"""Format cell value into html fill bar."""
= f"{ci.value / max(data['mpg_city']) * 100}%"
width return html_barchart(ci.value, width=width, **kwargs)
Reactable(
data,={
columns"mpg_city": Column(
="MPG (city)",
name="left",
align=fmt_barchart,
cell
),"mpg_highway": Column(
="MPG (highway)",
name="left",
align=lambda ci: fmt_barchart(ci, fill="#fc5185", background="#e1e1e1"),
cell
),
},=5,
default_page_size )
Positive and negative values
TODO
Background bar charts
TODO
Embed images
import polars as pl
import htmltools
from reactable import Reactable, Column, CellInfo
= pl.DataFrame(
data
{"Animal": ["beaver", "cow", "wolf", "goat"],
"Body": [1.35, 465, 36.33, 27.66],
"Brain": [8.1, 423, 119.5, 115],
}
)
def fmt_image(ci: CellInfo):
= htmltools.img(
image =f"/demos/cookbook/images/{ci.value}.png",
src="height: 24px;",
style=ci.value,
alt
)return htmltools.TagList(
htmltools.div(
image,="display: inline-block; width: 45px;",
style
),
ci.value,
)
Reactable(
data,={
columns"Animal": Column(cell=fmt_image),
"Body": Column(name="Body (kg)"),
"Brain": Column(name="Brain (g)"),
}, )
Note that this example assumes the images are available (we did that by setting the resources:
field in quarto).
Rating stars
# pip install faicons
import polars as pl
import htmltools
from faicons import icon_svg
from reactable import Reactable, Column, CellInfo
= pl.DataFrame(
ratings
{"Movie": [
"Silent Serpent",
"Nowhere to Hyde",
"The Ape-Man Goes to Mars",
"A Menace in Venice",
],"Rating": [3.65, 2.35, 4.5, 1.4],
"Votes": [115, 37, 60, 99],
}
)
def rating_stars(ci: CellInfo):
= round(ci.value)
to_fill # TODO: how to set aria?
= [
stars
icon_svg("star", stroke="orange" if ii <= to_fill else "#edf0f2", stroke_width=100, fill="white"
)for ii in range(1, 6)
]return htmltools.div(*stars, title="{ci.value} out of 5 stars")
Reactable(
ratings,={
columns"Rating": Column(
=rating_stars,
cell=True,
html
)
}, )
Show data from other columns
import htmltools
from reactable import Reactable, Column, CellInfo
from reactable.data import starwars
= starwars[["name", "height", "mass", "gender", "homeworld", "species"]]
data
def fmt_name(ci: CellInfo):
= data["species"][ci.row_index]
species = species if species is not None else "Unknown"
species
return htmltools.div(
="font-weight: 600;"),
htmltools.div(ci.value, style="font-size: 0.75rem;"),
htmltools.div(species, style
)
Reactable(
data,={
columns"name": Column(
=fmt_name,
cell="Character",
name
),"species": Column(show=False),
},=Column(v_align="center"),
default_col_def=4,
default_page_size )
from reactable import Reactable, Column, JS
from reactable.data import starwars
= starwars[["name", "height", "mass", "gender", "homeworld", "species"]]
data
= JS(
js_name """
function(cellInfo) {
const species = cellInfo.row["species"] || "Unknown"
return `
<div>
<div style="font-weight: 600">${cellInfo.value}</div>
<div style="font-size: 0.75rem">${species}</div>
</div>
`
}
"""
)
Reactable(
data,={
columns"name": Column(
=js_name,
cell=True,
html
),"species": Column(show=False),
},=Column(v_align="center"),
default_col_def=6,
default_page_size )
Total rows
Fixed
from reactable import Reactable, Column
from reactable.data import cars_93
= cars_93[["manufacturer", "model", "type", "price"]]
data
Reactable(
data,=5,
default_page_size={
columns"manufacturer": Column(footer="Total"),
"price": Column(footer=f"${sum(data['price']):.2f}"),
},=Column(footer_style={"font-weight": "bold"}),
default_col_def )
Dynamic
from reactable import JS
= JS(
js_sum_price """
function(column, state) {
let total = 0
state.sortedData.forEach(function(row) {
total += row[column.id]
})
return total.toLocaleString('en-US', { style: 'currency', currency: 'USD' })
}
"""
)
Reactable(
data,=True,
searchable=5,
default_page_size=5,
min_rows={
columns"manufacturer": Column(footer="Total"),
"price": Column(footer=js_sum_price),
},=Column(footer_style={"font-weight": "bold"}),
default_col_def )
Nested tables
import polars as pl
import polars.selectors as cs
from reactable import Reactable, Column
from reactable.data import us_expenditures
= (
data
us_expenditures.to_polars()# tidy years from columns into rows
"19"), index="index")
.unpivot(cs.starts_with(
)
= list(g for k, g in data.group_by("variable"))
year_dfs = data.group_by("variable").agg(n=pl.count())
summary_df
Reactable(
summary_df,# TODO: details should accept a function
=Column(
details=lambda ri: Reactable(year_dfs[ri.row_index]).to_widget(),
details
), )
/tmp/ipykernel_2087/1028257606.py:14: DeprecationWarning:
`pl.count()` is deprecated. Please use `pl.len()` instead.
Units on first row only
from reactable.data import cars_93
from reactable import Reactable, Column
= cars_93[40:44, ["make", "length", "luggage_room"]]
data
def fmt_length(ci):
return f"{ci.value}″"
def fmt_ft(ci):
return f"{ci.value} <div class='units'>ft³</div>"
Reactable(
data,="car-specs",
class_={
columns"length": Column(
=lambda ci: fmt_length(ci) if ci.row_index == 0 else str(ci.value),
cell="number",
class_
),"luggage_room": Column(
="Luggage Room",
name=lambda ci: fmt_ft(ci) if ci.row_index == 0 else str(ci.value),
cell=True,
html
),
}, )