Code
from IPython.display import display, HTML
html = """
<link href = "https://fonts.googleapis.com/css?family=Karla:400,700|Fira+Mono&display=fallback" rel = "stylesheet" />
<style>
.twitter-followers {
margin: 0 auto;
width: 575px;
font-family: Karla, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.followers-header {
margin: 1.125rem 0;
font-size: 1rem;
}
.followers-title {
font-size: 1.25rem;
font-weight: 600;
}
.followers-tbl {
font-size: 0.875rem;
line-height: 1.125rem;
}
.followers-tbl a {
color: inherit;
text-decoration: none;
}
.followers-tbl a:hover,
.followers-tbl a:focus {
text-decoration: underline;
text-decoration-thickness: max(1px, 0.0625rem);
}
.header {
border-bottom: 2px solid #555;
font-size: 0.8125rem;
font-weight: 400;
text-transform: uppercase;
}
.header:hover {
background-color: #eee;
}
.bar-cell {
display: flex;
align-items: center;
}
.number {
font-family: "Fira Mono", Consolas, Monaco, monospace;
font-size: 0.84375rem;
white-space: pre;
}
.bar-chart {
flex-grow: 1;
margin-left: 0.375rem;
height: 0.875rem;
}
.bar {
height: 100%;
}
</style>
"""
display(HTML(html))
Code
from reactable import embed_css
embed_css()
Code
from reactable import Reactable, embed_css
from reactable.tags import to_widget
from reactable.models import Column, CellInfo, JS
from htmltools import tags
import polars as pl
data = pl.read_csv("twitter_followers.csv")
def f_followers(ci: CellInfo):
width = f"{ci.value * 100 / data['followers'].max()}%"
val = f"{ci.value:,}".rjust(9)
bar = tags.div(
tags.div(
class_="bar",
style=f"width: {width}; background-color: #3fc1c9",
),
class_="bar-chart",
style="margin-right: 0.375rem",
)
return tags.div(
tags.span(class_="number", content=val),
bar,
class_="bar-cell",
)
js_exclusive_percent = JS(
"""
function(cellInfo) {
// Format as percentage
const pct = (cellInfo.value * 100).toFixed(1) + "%"
// Pad single-digit numbers
let value = pct.padStart(5)
// Show % on first row only
if (cellInfo.viewIndex > 0) {
value = value.replace("%", " ")
}
// Render bar chart
return `
<div class="bar-cell">
<span class="number">${value}</span>
<div class="bar-chart" style="background-color: #e1e1e1">
<div class="bar" style="width: ${pct}; background-color: #fc5185"></div>
</div>
</div>
`
}"""
)
tbl = Reactable(
data,
pagination=False,
default_sorted=["exclusive_followers_pct"],
default_col_def=Column(header_class="header", align="left"),
columns=dict(
account=Column(
cell=lambda ci: (
tags.a(
f"@{ci.value}",
href=f"https://twitter.com/{ci.value}",
target="_blank",
)
),
width=150,
),
followers=Column(default_sort_order="desc", cell=f_followers),
exclusive_followers_pct=Column(
name="Exclusive Followers",
default_sort_order="desc",
cell=js_exclusive_percent,
html=True,
),
),
compact=True,
class_="followers-tbl",
static=True,
)
to_widget(
tags.div(
tags.div(
tags.h2("Candidates whose followers are loyal only to them", class_="followers-title"),
"Share of each 2020 candidate's followers who don't follow any other candidates",
class_="followers-header",
),
tbl,
class_="twitter-followers",
)
)
Source: FiveThirtyEight
Raw data: twitter_followers.csv
How it was made: Building the Twitter Followers Demo
Source Code
from reactable import Reactable, embed_css
from reactable.tags import to_widget
from reactable.models import Column, CellInfo, JS
from htmltools import tags
import polars as pl
data = pl.read_csv("twitter_followers.csv")
def f_followers(ci: CellInfo):
width = f"{ci.value * 100 / data['followers'].max()}%"
val = f"{ci.value:,}".rjust(9)
bar = tags.div(
tags.div(
class_="bar",
style=f"width: {width}; background-color: #3fc1c9",
),
class_="bar-chart",
style="margin-right: 0.375rem",
)
return tags.div(
tags.span(class_="number", content=val),
bar,
class_="bar-cell",
)
js_exclusive_percent = JS(
"""
function(cellInfo) {
// Format as percentage
const pct = (cellInfo.value * 100).toFixed(1) + "%"
// Pad single-digit numbers
let value = pct.padStart(5)
// Show % on first row only
if (cellInfo.viewIndex > 0) {
value = value.replace("%", " ")
}
// Render bar chart
return `
<div class="bar-cell">
<span class="number">${value}</span>
<div class="bar-chart" style="background-color: #e1e1e1">
<div class="bar" style="width: ${pct}; background-color: #fc5185"></div>
</div>
</div>
`
}"""
)
tbl = Reactable(
data,
pagination=False,
default_sorted=["exclusive_followers_pct"],
default_col_def=Column(header_class="header", align="left"),
columns=dict(
account=Column(
cell=lambda ci: (
tags.a(
f"@{ci.value}",
href=f"https://twitter.com/{ci.value}",
target="_blank",
)
),
width=150,
),
followers=Column(default_sort_order="desc", cell=f_followers),
exclusive_followers_pct=Column(
name="Exclusive Followers",
default_sort_order="desc",
cell=js_exclusive_percent,
html=True,
),
),
compact=True,
class_="followers-tbl",
static=True,
)
to_widget(
tags.div(
tags.div(
tags.h2("Candidates whose followers are loyal only to them", class_="followers-title"),
"Share of each 2020 candidate's followers who don't follow any other candidates",
class_="followers-header",
),
tbl,
class_="twitter-followers",
)
)
<link href = "https://fonts.googleapis.com/css?family=Karla:400,700|Fira+Mono&display=fallback" rel = "stylesheet" />
<style>
.twitter-followers {
margin: 0 auto;
width: 575px;
font-family: Karla, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.followers-header {
margin: 1.125rem 0;
font-size: 1rem;
}
.followers-title {
font-size: 1.25rem;
font-weight: 600;
}
.followers-tbl {
font-size: 0.875rem;
line-height: 1.125rem;
}
.followers-tbl a {
color: inherit;
text-decoration: none;
}
.followers-tbl a:hover,
.followers-tbl a:focus {
text-decoration: underline;
text-decoration-thickness: max(1px, 0.0625rem);
}
.header {
border-bottom: 2px solid #555;
font-size: 0.8125rem;
font-weight: 400;
text-transform: uppercase;
}
.header:hover {
background-color: #eee;
}
.bar-cell {
display: flex;
align-items: center;
}
.number {
font-family: "Fira Mono", Consolas, Monaco, monospace;
font-size: 0.84375rem;
white-space: pre;
}
.bar-chart {
flex-grow: 1;
margin-left: 0.375rem;
height: 0.875rem;
}
.bar {
height: 100%;
}
</style>