How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual?
In 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 fashionable 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 will see how Textual behaves like a reside UI engine proper inside Google Colab. By the tip, we discover how naturally we will mix tables, bushes, varieties, and progress indicators into a cohesive utility that feels quick, clear, and responsive. Check out 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, Input,
Label, ProgressBar, Tree, Select
)
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:
move
We arrange the setting and import all the mandatory parts to construct our Textual utility. As we outline the StatsCard widget, we set up a reusable part that reacts to adjustments in worth and updates itself robotically. We start to see how Textual’s reactive system lets us create dynamic UI parts with minimal effort. Check out the FULL CODES here.
class DataDashboard(App):
CSS = """
Screen { background: $floor; }
#main-container { top: 100%; padding: 1; }
#stats-row { top: auto; margin-bottom: 1; }
StatsCard { border: strong $major; top: 5; padding: 1; margin-right: 1; width: 1fr; }
#stat-value { text-style: daring; shade: $accent; content-align: heart center; }
#control-panel { top: 12; border: strong $secondary; padding: 1; margin-bottom: 1; }
#data-section { top: 1fr; }
#left-panel { width: 30; border: strong $secondary; padding: 1; margin-right: 1; }
DataTable { top: 100%; border: strong $major; }
Input { 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 determine 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 polished dashboard with out writing any HTML or JS. Check out 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("Total Rows", id="card-rows")
yield StatsCard("Total Sales", id="card-sales")
yield StatsCard("Avg Rating", id="card-rating")
with Vertical(id="control-panel"):
yield Input(placeholder="Product Name", id="input-name")
yield Select(
[("Electronics", "electronics"),
("Books", "books"),
("Clothing", "clothing")],
immediate="Select Category",
id="select-category"
)
with Horizontal():
yield Button("Add Row", variant="major", id="btn-add")
yield Button("Clear Table", variant="warning", id="btn-clear")
yield Button("Generate Data", 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.increase()
merchandise = tree.root.add("Products", increase=True)
merchandise.add_leaf("Electronics")
merchandise.add_leaf("Books")
merchandise.add_leaf("Clothing")
tree.root.add_leaf("Reports")
tree.root.add_leaf("Settings")
yield tree
yield DataTable(id="data-table")
yield Footer()
We compose all the UI format, arranging containers, playing cards, type inputs, buttons, a navigation tree, and a information desk. As we construction these parts, 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. Check out the FULL CODES here.
def on_mount(self) -> None:
desk = self.query_one(DataTable)
desk.add_columns("ID", "Product", "Category", "Price", "Sales", "Rating")
desk.cursor_type = "row"
self.generate_sample_data(5)
self.set_interval(0.1, self.update_progress)
def generate_sample_data(self, depend: int = 5) -> None:
desk = self.query_one(DataTable)
classes = ["Electronics", "Books", "Clothing"]
merchandise = {
"Electronics": ["Laptop", "Phone", "Tablet", "Headphones"],
"Books": ["Novel", "Textbook", "Magazine", "Comic"],
"Clothing": ["Shirt", "Pants", "Jacket", "Shoes"]
}
for _ in vary(depend):
class = random.selection(classes)
product = random.selection(merchandise[category])
row_id = self.total_rows + 1
worth = spherical(random.uniform(10, 500), 2)
gross sales = random.randint(1, 100)
ranking = spherical(random.uniform(1, 5), 1)
desk.add_row(
str(row_id),
product,
class,
f"${worth}",
str(gross sales),
str(ranking)
)
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 shortly we will bind backend logic to frontend parts utilizing Textual’s reactive mannequin. This step makes the dashboard really feel alive as numbers replace immediately and progress bars animate easily. Check out the FULL CODES here.
@on(Button.Pressed, "#btn-add")
def handle_add_button(self) -> None:
name_input = self.query_one("#input-name", Input)
class = self.query_one("#select-category", Select).worth
if name_input.worth and class:
desk = self.query_one(DataTable)
row_id = self.total_rows + 1
worth = spherical(random.uniform(10, 500), 2)
gross sales = random.randint(1, 100)
ranking = spherical(random.uniform(1, 5), 1)
desk.add_row(
str(row_id),
name_input.worth,
str(class),
f"${worth}",
str(gross sales),
str(ranking)
)
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 features. As we run the app, we work together with a absolutely practical dashboard that responds immediately to each click on and command. This snippet completes the applying and demonstrates how simply Textual allows us to construct dynamic, state-driven UIs.
In conclusion, we see the entire dashboard come collectively in a absolutely practical, interactive type that runs immediately from a pocket book setting. We expertise firsthand how Textual lets us design terminal UIs with the construction and really feel of net apps, whereas staying fully in Python. This tutorial leaves us assured that we will lengthen this basis, even including charts, API feeds, and multi-page navigation, as we proceed to experiment with Textual’s fashionable reactive UI capabilities.
Check out the FULL CODES here. Feel free to try our GitHub Page for Tutorials, Codes and Notebooks. Also, be at liberty to comply with us on Twitter and don’t neglect to be part of our 100k+ ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
The publish How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual? appeared first on MarkTechPost.
