Twitter Followers

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>