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 trendy internet 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 reside UI engine proper inside Google Colab. By the top, we discover how naturally we are able to mix tables, timber, types, and progress indicators into a cohesive software 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:
attempt:
self.query_one("#stat-value", Label).replace(str(new_value))
besides Exception:
cross
We arrange the atmosphere 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 adjustments in worth and updates itself robotically. We start to see how Textual’s reactive system lets us create dynamic UI components with minimal effort. Check out the FULL CODES here.
class DataDashboard(App):
CSS = """
Screen { background: $floor; }
#main-container { peak: 100%; padding: 1; }
#stats-row { peak: auto; margin-bottom: 1; }
StatsCard { border: stable $major; peak: 5; padding: 1; margin-right: 1; width: 1fr; }
#stat-value { text-style: daring; shade: $accent; content-align: heart center; }
#control-panel { peak: 12; border: stable $secondary; padding: 1; margin-bottom: 1; }
#data-section { peak: 1fr; }
#left-panel { width: 30; border: stable $secondary; padding: 1; margin-right: 1; }
DataTable { peak: 100%; border: stable $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 kinds, 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 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.broaden()
merchandise = tree.root.add("Products", broaden=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 structure, arranging containers, playing cards, type inputs, buttons, a navigation tree, and a information desk. As we construction these elements, we watch the interface take form precisely the best way 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
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 shortly 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. 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
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 features. As we run the app, we work together with a totally practical dashboard that responds immediately to each click on and command. This snippet completes the appliance and demonstrates how simply Textual allows us to construct dynamic, state-driven UIs.
In conclusion, we see the entire dashboard come collectively in a totally practical, interactive type that runs instantly from a pocket book atmosphere. We expertise firsthand how Textual lets us design terminal UIs with the construction and really feel of internet apps, whereas staying solely in Python. This tutorial leaves us assured that we are able to prolong this basis, even including charts, API feeds, and multi-page navigation, as we proceed to experiment with Textual’s trendy reactive UI capabilities.
Check out the FULL CODES here. Feel free to take a look at our GitHub Page for Tutorials, Codes and Notebooks. Also, be at liberty to comply with us on Twitter and don’t overlook to be a 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 submit How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual? appeared first on MarkTechPost.
