On this tutorial, we construct a complicated interactive dashboard utilizing Textual, and we discover how terminal-first UI frameworks can really feel as expressive and dynamic as trendy net dashboards. As we write and run every snippet, we actively assemble the interface piece by piece, widgets, layouts, reactive state, and occasion flows, so we are able to see how Textual behaves like a stay UI engine proper inside Google Colab. By the tip, we discover how naturally we are able to mix tables, bushes, kinds, and progress indicators right into a cohesive software that feels quick, clear, and responsive. Try the FULL CODES here.
!pip set up textual textual-web nest-asyncio
from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal, Vertical
from textual.widgets import (
Header, Footer, Button, DataTable, Static, Enter,
Label, ProgressBar, Tree, Choose
)
from textual.reactive import reactive
from textual import on
from datetime import datetime
import random
class StatsCard(Static):
worth = reactive(0)
def __init__(self, title: str, *args, **kwargs):
tremendous().__init__(*args, **kwargs)
self.title = title
def compose(self) -> ComposeResult:
yield Label(self.title)
yield Label(str(self.worth), id="stat-value")
def watch_value(self, new_value: int) -> None:
if self.is_mounted:
strive:
self.query_one("#stat-value", Label).replace(str(new_value))
besides Exception:
cross
We arrange the surroundings and import all the mandatory elements to construct our Textual software. As we outline the StatsCard widget, we set up a reusable part that reacts to modifications in worth and updates itself routinely. We start to see how Textual’s reactive system lets us create dynamic UI parts with minimal effort. Try the FULL CODES here.
class DataDashboard(App):
CSS = """
Display screen { background: $floor; }
#main-container { peak: 100%; padding: 1; }
#stats-row { peak: auto; margin-bottom: 1; }
StatsCard { border: strong $main; peak: 5; padding: 1; margin-right: 1; width: 1fr; }
#stat-value { text-style: daring; shade: $accent; content-align: middle center; }
#control-panel { peak: 12; border: strong $secondary; padding: 1; margin-bottom: 1; }
#data-section { peak: 1fr; }
#left-panel { width: 30; border: strong $secondary; padding: 1; margin-right: 1; }
DataTable { peak: 100%; border: strong $main; }
Enter { margin: 1 0; }
Button { margin: 1 1 1 0; }
ProgressBar { margin: 1 0; }
"""
BINDINGS = [
("d", "toggle_dark", "Toggle Dark Mode"),
("q", "quit", "Quit"),
("a", "add_row", "Add Row"),
("c", "clear_table", "Clear Table"),
]
total_rows = reactive(0)
total_sales = reactive(0)
avg_rating = reactive(0.0)
We outline the DataDashboard class and configure international types, key bindings, and reactive attributes. We resolve how the app ought to look and behave proper from the highest, giving us full management over themes and interactivity. This construction helps us create a cultured dashboard with out writing any HTML or JS. Try the FULL CODES here.
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with Container(id="main-container"):
with Horizontal(id="stats-row"):
yield StatsCard("Whole Rows", id="card-rows")
yield StatsCard("Whole Gross sales", id="card-sales")
yield StatsCard("Avg Score", id="card-rating")
with Vertical(id="control-panel"):
yield Enter(placeholder="Product Identify", id="input-name")
yield Choose(
[("Electronics", "electronics"),
("Books", "books"),
("Clothing", "clothing")],
immediate="Choose Class",
id="select-category"
)
with Horizontal():
yield Button("Add Row", variant="main", id="btn-add")
yield Button("Clear Desk", variant="warning", id="btn-clear")
yield Button("Generate Knowledge", variant="success", id="btn-generate")
yield ProgressBar(complete=100, id="progress")
with Horizontal(id="data-section"):
with Container(id="left-panel"):
yield Label("Navigation")
tree = Tree("Dashboard")
tree.root.develop()
merchandise = tree.root.add("Merchandise", develop=True)
merchandise.add_leaf("Electronics")
merchandise.add_leaf("Books")
merchandise.add_leaf("Clothes")
tree.root.add_leaf("Experiences")
tree.root.add_leaf("Settings")
yield tree
yield DataTable(id="data-table")
yield Footer()
We compose your entire UI format, arranging containers, playing cards, type inputs, buttons, a navigation tree, and a knowledge desk. As we construction these elements, we watch the interface take form precisely the way in which we envision it. This snippet lets us design the visible skeleton of the dashboard in a clear, declarative method. Try the FULL CODES here.
def on_mount(self) -> None:
desk = self.query_one(DataTable)
desk.add_columns("ID", "Product", "Class", "Value", "Gross sales", "Score")
desk.cursor_type = "row"
self.generate_sample_data(5)
self.set_interval(0.1, self.update_progress)
def generate_sample_data(self, rely: int = 5) -> None:
desk = self.query_one(DataTable)
classes = ["Electronics", "Books", "Clothing"]
merchandise = {
"Electronics": ["Laptop", "Phone", "Tablet", "Headphones"],
"Books": ["Novel", "Textbook", "Magazine", "Comic"],
"Clothes": ["Shirt", "Pants", "Jacket", "Shoes"]
}
for _ in vary(rely):
class = random.alternative(classes)
product = random.alternative(productsArtificial Intelligence)
row_id = self.total_rows + 1
value = spherical(random.uniform(10, 500), 2)
gross sales = random.randint(1, 100)
score = spherical(random.uniform(1, 5), 1)
desk.add_row(
str(row_id),
product,
class,
f"${value}",
str(gross sales),
str(score)
)
self.total_rows += 1
self.total_sales += gross sales
self.update_stats()
def update_stats(self) -> None:
self.query_one("#card-rows", StatsCard).worth = self.total_rows
self.query_one("#card-sales", StatsCard).worth = self.total_sales
if self.total_rows > 0:
desk = self.query_one(DataTable)
total_rating = sum(float(row[5]) for row in desk.rows)
self.avg_rating = spherical(total_rating / self.total_rows, 2)
self.query_one("#card-rating", StatsCard).worth = self.avg_rating
def update_progress(self) -> None:
progress = self.query_one(ProgressBar)
progress.advance(1)
if progress.progress >= 100:
progress.progress = 0
We implement all of the logic for producing information, computing statistics, animating progress, and updating playing cards. We see how rapidly we are able to bind backend logic to frontend elements utilizing Textual’s reactive mannequin. This step makes the dashboard really feel alive as numbers replace immediately and progress bars animate easily. Try the FULL CODES here.
@on(Button.Pressed, "#btn-add")
def handle_add_button(self) -> None:
name_input = self.query_one("#input-name", Enter)
class = self.query_one("#select-category", Choose).worth
if name_input.worth and class:
desk = self.query_one(DataTable)
row_id = self.total_rows + 1
value = spherical(random.uniform(10, 500), 2)
gross sales = random.randint(1, 100)
score = spherical(random.uniform(1, 5), 1)
desk.add_row(
str(row_id),
name_input.worth,
str(class),
f"${value}",
str(gross sales),
str(score)
)
self.total_rows += 1
self.total_sales += gross sales
self.update_stats()
name_input.worth = ""
@on(Button.Pressed, "#btn-clear")
def handle_clear_button(self) -> None:
desk = self.query_one(DataTable)
desk.clear()
self.total_rows = 0
self.total_sales = 0
self.avg_rating = 0
self.update_stats()
@on(Button.Pressed, "#btn-generate")
def handle_generate_button(self) -> None:
self.generate_sample_data(10)
def action_toggle_dark(self) -> None:
self.darkish = not self.darkish
def action_add_row(self) -> None:
self.handle_add_button()
def action_clear_table(self) -> None:
self.handle_clear_button()
if __name__ == "__main__":
import nest_asyncio
nest_asyncio.apply()
app = DataDashboard()
app.run()
We join UI occasions to backend actions utilizing button handlers, keyboard shortcuts, and app-level capabilities. As we run the app, we work together with a completely practical dashboard that responds immediately to each click on and command. This snippet completes the applying and demonstrates how simply Textual permits us to construct dynamic, state-driven UIs.
In conclusion, we see the entire dashboard come collectively in a completely practical, interactive type that runs instantly from a pocket book surroundings. We expertise firsthand how Textual lets us design terminal UIs with the construction and really feel of net apps, whereas staying totally in Python. This tutorial leaves us assured that we are able to lengthen this basis, even including charts, API feeds, and multi-page navigation, as we proceed to experiment with Textual’s trendy reactive UI capabilities.
Try the FULL CODES here. Be at liberty to take a look at our GitHub Page for Tutorials, Codes and Notebooks. Additionally, be happy to comply with us on Twitter and don’t neglect to affix our 100k+ ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Asif Razzaq is the CEO of Marktechpost Media Inc.. As a visionary entrepreneur and engineer, Asif is dedicated to harnessing the potential of Synthetic Intelligence for social good. His most up-to-date endeavor is the launch of an Synthetic Intelligence Media Platform, Marktechpost, which stands out for its in-depth protection of machine studying and deep studying information that’s each technically sound and simply comprehensible by a large viewers. The platform boasts of over 2 million month-to-month views, illustrating its recognition amongst audiences.
