, before there was Streamlit, before there was Taipy, there was Tkinter. Tkinter was and is the unique Python GUI builder, and, until just a few years ago, it was one among the few ways you can produce any style of dashboard or GUI using Python.
As newer, web-based frameworks just like the ones mentioned above have taken the limelight for the desktop presentation of data-centric and machine learning applications, we ask the query, “Is there still mileage left in using the Tkinter library?”.
My answer to this query is a powerful Yes! I hope to show in this text that Tkinter stays a strong, lightweight, and highly relevant tool for creating native desktop GUI and data dashboard applications.
For developers who must create internal tools, easy utilities, or educational software, Tkinter will be the best alternative. It doesn’t require complex web servers, JavaScript knowledge, or heavy dependencies. It’s Python, pure and straightforward. And as I show later, you may produce some pretty complex, modern-looking dashboards with it.
In the remainder of this text, we are going to journey from the basic principles of Tkinter to the sensible construction of a dynamic, data-driven dashboard, proving that this “OG” GUI library still has plenty of recent tricks up its sleeve.
What’s Tkinter and Why Should You Still Care?
Tkinter is the usual, built-in Graphical User Interface (GUI) toolkit for Python. The name is a play on words of “Tk Interface.” It’s a wrapper around Tcl/Tk, a strong and cross-platform GUI toolkit that has been around because the early Nineties.
Its single most vital advantage is its inclusion within the Python standard library. This implies if you could have Python installed, you could have Tkinter. There are not any pip install commands to run, no virtual environment dependency conflicts to resolve. It really works out of the box on Windows, macOS, and Linux.
So, why select Tkinter in an age of flashy web frameworks?
- Simplicity and Speed: For small to medium-sized applications, Tkinter is fast to develop with. You possibly can have a functional window with interactive elements in only just a few lines of code.
- Lightweight: Tkinter applications have a tiny footprint. They don’t require a browser or an online server, making them ideal for easy utilities that must run efficiently on any machine.
- Native Look and Feel (to an extent): While classic Tkinter has a famously dated look, the ttk themed widget set provides access to more modern, native-looking controls that higher match the host operating system.
- Excellent for Learning: Tkinter teaches the basic concepts of event-driven programming — the core of all GUI development. Understanding tips on how to manage widgets, layouts, and user events in Tkinter provides a solid foundation for learning another GUI framework.
In fact, it has its drawbacks. Complex, aesthetically demanding applications will be difficult to construct, and their design philosophy can feel more verbose in comparison with the declarative variety of Streamlit or Gradio. Nonetheless, for its intended purpose — creating functional, standalone desktop applications — it excels.
Over time, though, additional libraries have been written that make Tkinter GUIs more modern-looking. One in every of these, which we’ll use, is named ttkbootstrap. That is built on top of Tkinter, adds extra widgets and may give your GUIs a Bootstrap-inspired look.
The Core Concepts of a Tkinter Application
Every Tkinter application is built upon just a few key pillars. Grasping these concepts is important before you may create anything meaningful.
1/ The Root Window
The foundation window is the principal container on your entire application. It’s the top-level window that has a title bar, minimise, maximise, and shut buttons. You create it with a single line of code like this.
import tkinter as tk
root = tk.Tk()
root.title("My First Tkinter App")
root.mainloop()
That code produces this. Not essentially the most exciting thing to take a look at, nevertheless it is a start.
Every little thing else in your application — buttons, labels, input fields , and so forth — will live inside this root window.
2/ Widgets
Widgets are the constructing blocks of your GUI. They’re the weather the user sees and interacts with. Among the most typical widgets include:
- Label: Displays static text or images.
- Button: A clickable button that may trigger a function.
- Entry: A single-line text input field.
- Text: A multi-line text input and display area.
- Frame: An invisible rectangular container used to group other widgets. That is crucial for organising complex layouts.
- Canvas: A flexible widget for drawing shapes, creating graphs, or displaying images.
- Checkbutton and Radiobutton: For boolean or multiple-choice selections.
3/ Geometry Managers
When you’ve created your widgets, it’s good to tell Tkinter where to place them contained in the window. That is the job of geometry managers. Note you could’t mix and match different managers inside the same parent container (like a root or a Frame).
- pack(): The only manager. It “packs” widgets into the window, either vertically or horizontally. It’s quick for straightforward layouts but offers little precise control.
- place(): Probably the most precise manager. It means that you can specify the precise pixel coordinates (x, y) and size (width, height) of a widget. This is mostly to be avoided since it makes your application rigid and never conscious of window resizing.
- grid(): Probably the most powerful and versatile manager, and the one we are going to use for our dashboard. It organises widgets in a table-like structure of rows and columns, making it perfect for creating aligned, structured layouts.
4/ The Major Loop
The road root.mainloop() is the ultimate and most crucial a part of any Tkinter application. This method starts the event loop. The appliance enters a waiting state, listening for user actions like mouse clicks, key presses, or window resizing. When an event occurs, Tkinter processes it (e.g., calling a function tied to a button click) after which returns to the loop. The appliance will only close when this loop is terminated, often by closing the window.
Establishing a dev environment
Before we begin to code, let’s arrange a development environment. I’m slowly switching to the UV command line tool for my environment setup, replacing conda, and that’s what we’ll use here.
# initialise our project
uv init tktest
cd tktest
# create a brand new venv
uv venv tktest
# switch to it
tktestScriptsactivate
# Install required external libraries
(tktest) uv pip install matplotlib ttkbootstrap pandas
Example 1: A Easy “Hello, Tkinter!” app
Let’s put these concepts into practice. We’ll create a window with a label and a button. When the button is clicked, the label’s text will change.
import tkinter as tk
# 1. Create the basis window
root = tk.Tk()
root.title("Easy Interactive App")
root.geometry("300x150") # Set window size: width x height
# This function might be called when the button is clicked
def on_button_click():
# Update the text of the label widget
label.config(text="Hello, Tkinter!")
# 2. Create the widgets
label = tk.Label(root, text="Click the button below.")
button = tk.Button(root, text="Click Me!", command=on_button_click)
# 3. Use a geometry manager to put the widgets
# We use pack() for this straightforward layout
label.pack(pady=20) # pady adds some vertical padding
button.pack()
# 4. Start the principal event loop
root.mainloop()
It should appear like this, with the image on the correct what you get if you click the button.

Thus far, so straightforward; nonetheless, you create modern, visually appealing GUIs and dashboards with Tkinter. For example this, we’ll create a more comprehensive and complicated app that showcases what Tkinter can do.
Example 2 — A contemporary data dashboard
For this instance, we’ll create an information dashboard using a dataset from Kaggle called CarsForSale. This comes with a CC0:Public Domain licence, meaning it might probably be freely used for many purposes.
It’s a US-centric data set containing sales and performance details for about 9300 different automobile models from about 40 different manufacturers spanning the period 2001–2022. You possibly can get it using the link below:
https://www.kaggle.com/datasets/chancev/carsforsale/data
Download the information set and put it aside to a CSV file in your local system.
NB: This data set is provided under the CC0: Public Domain licence, subsequently it’s high-quality to make use of on this context.

This instance might be way more complex than the primary, but I wanted to offer you idea of exactly what was possible with Tkinter, so here goes. I’ll present the code and describe its general functionality before we examine the GUI it produces.
###############################################################################
# USED-CAR MARKETPLACE DASHBOARD
#
#
###############################################################################
import tkinter as tk
import ttkbootstrap as tb
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import pandas as pd, numpy as np, re, sys
from pathlib import Path
from textwrap import shorten
# ───────────────────────── CONFIG ──────────────────────────
CSV_PATH = r"C:Usersthomatempcarsforsale.csv"
COLUMN_ALIASES = {
"brand": "make", "manufacturer": "make", "carname": "model",
"rating": "consumerrating", "safety": "reliabilityrating",
}
REQUIRED = {"make", "price"}
# ──────────────────────────────────────────────────────────────
class Dashboard:
# ═══════════════════════════════════════════════════════════
def __init__(self, root: tb.Window):
self.root = root
self.style = tb.Style("darkly")
self._make_spinbox_style()
self.clr = self.style.colours
self.current_analysis_plot_func = None
self._load_data()
self._build_gui()
self._apply_filters()
# ─────────── spin-box style (white arrows) ────────────────
def _make_spinbox_style(self):
try:
self.style.configure("White.TSpinbox",
arrowcolor="white",
arrowsize=12)
self.style.map("White.TSpinbox",
arrowcolor=[("disabled", "white"),
("active", "white"),
("pressed", "white")])
except tk.TclError:
pass
# ───────────────────── DATA LOAD ───────────────────────────
def _load_data(self):
csv = Path(CSV_PATH)
if not csv.exists():
tb.dialogs.Messagebox.show_error("CSV not found", str(csv))
sys.exit()
df = pd.read_csv(csv, encoding="utf-8-sig", skipinitialspace=True)
df.columns = [
COLUMN_ALIASES.get(
re.sub(r"[^0-9a-z]", "", c.lower().replace("ufeff", "")),
c.lower()
)
for c in df.columns
]
if "12 months" not in df.columns:
for col in df.columns:
nums = pd.to_numeric(df[col], errors="coerce")
if nums.dropna().between(1900, 2035).all():
df.rename(columns={col: "12 months"}, inplace=True)
break
for col in ("price", "minmpg", "maxmpg",
"12 months", "mileage", "consumerrating"):
if col in df.columns:
df[col] = pd.to_numeric(
df[col].astype(str)
.str.replace(r"[^d.]", "", =True),
errors="coerce"
)
if any(c not in df.columns for c in REQUIRED):
tb.dialogs.Messagebox.show_error(
"Bad CSV", "Missing required columns.")
sys.exit()
self.df = df.dropna(subset=["make", "price"])
# ───────────────────── GUI BUILD ───────────────────────────
def _build_gui(self):
header = tb.Frame(self.root, width=600, height=60, bootstyle="dark")
header.pack_propagate(False)
header.pack(side="top", anchor="w", padx=8, pady=(4, 2))
tb.Label(header, text="🚗 USED-CAR DASHBOARD",
font=("Segoe UI", 16, "daring"), anchor="w")
.pack(fill="each", padx=8, pady=4)
self.nb = tb.Notebook(self.root); self.nb.pack(fill="each", expand=True)
self._overview_tab()
self._analysis_tab()
self._data_tab()
# ───────────────── OVERVIEW TAB ─────────────────────────
def _overview_tab(self):
tab = tb.Frame(self.nb); self.nb.add(tab, text="Overview")
self._filters(tab)
self._cards(tab)
self._overview_fig(tab)
def _spin(self, parent, **kw):
return tb.Spinbox(parent, style="White.TSpinbox", **kw)
def _filters(self, parent):
f = tb.Labelframe(parent, text="Filters", padding=6)
f.pack(fill="x", padx=8, pady=6)
tk.Label(f, text="Make").grid(row=0, column=0, sticky="w", padx=4)
self.make = tk.StringVar(value="All")
tb.Combobox(f, textvariable=self.make, state="readonly", width=14,
values=["All"] + sorted(self.df["make"].unique()),
bootstyle="dark")
.grid(row=0, column=1)
self.make.trace_add("write", self._apply_filters)
if "drivetrain" in self.df.columns:
tk.Label(f, text="Drivetrain").grid(row=0, column=2, padx=(20, 4))
self.drive = tk.StringVar(value="All")
tb.Combobox(f, textvariable=self.drive, state="readonly", width=14,
values=["All"] + sorted(self.df["drivetrain"].dropna()
.unique()),
bootstyle="dark")
.grid(row=0, column=3)
self.drive.trace_add("write", self._apply_filters)
pr_min, pr_max = self.df["price"].min(), self.df["price"].max()
tk.Label(f, text="Price $").grid(row=0, column=4, padx=(20, 4))
self.pmin = tk.DoubleVar(value=float(pr_min))
self.pmax = tk.DoubleVar(value=float(pr_max))
for col, var in [(5, self.pmin), (6, self.pmax)]:
self._spin(f, from_=0, to=float(pr_max), textvariable=var,
width=10, increment=1000, bootstyle="secondary")
.grid(row=0, column=col)
if "12 months" in self.df.columns:
yr_min, yr_max = int(self.df["year"].min()), int(self.df["year"].max())
tk.Label(f, text="Yr").grid(row=0, column=7, padx=(20, 4))
self.ymin = tk.IntVar(value=yr_min)
self.ymax = tk.IntVar(value=yr_max)
for col, var in [(8, self.ymin), (9, self.ymax)]:
self._spin(f, from_=1900, to=2035, textvariable=var,
width=6, bootstyle="secondary")
.grid(row=0, column=col)
tb.Button(f, text="Apply Yr/Price Filters",
bootstyle="primary-outline",
command=self._apply_filters)
.grid(row=0, column=10, padx=(30, 4))
def _cards(self, parent):
wrap = tb.Frame(parent); wrap.pack(fill="x", padx=8)
self.cards = {}
for lbl in ("Total Cars", "Average Price",
"Average Mileage", "Avg Rating"):
card = tb.Frame(wrap, padding=6, relief="ridge", bootstyle="dark")
card.pack(side="left", fill="x", expand=True, padx=4, pady=4)
val = tb.Label(card, text="-", font=("Segoe UI", 16, "daring"),
foreground=self.clr.info)
val.pack()
tb.Label(card, text=lbl, foreground="white").pack()
self.cards[lbl] = val
def _overview_fig(self, parent):
fr = tb.Frame(parent); fr.pack(fill="each", expand=True, padx=8, pady=6)
self.ov_fig = plt.Figure(figsize=(18, 10), facecolor="#1e1e1e",
constrained_layout=True)
self.ov_canvas = FigureCanvasTkAgg(self.ov_fig, master=fr)
self.ov_canvas.get_tk_widget().pack(fill="each", expand=True)
# ───────────────── ANALYSIS TAB ──────────────────────────
def _analysis_tab(self):
tab = tb.Frame(self.nb); self.nb.add(tab, text="Evaluation")
ctl = tb.Frame(tab); ctl.pack(fill="x", padx=8, pady=6)
def set_and_run_analysis(plot_function):
self.current_analysis_plot_func = plot_function
plot_function()
for txt, fn in (("Correlation", self._corr),
("Price by Make", self._price_make),
("MPG", self._mpg),
("Rankings", self._ratings)):
tb.Button(ctl, text=txt, command=lambda f=fn: set_and_run_analysis(f),
bootstyle="info-outline").pack(side="left", padx=4)
self.an_fig = plt.Figure(figsize=(12, 7), facecolor="#1e1e1e",
constrained_layout=True)
self.an_canvas = FigureCanvasTkAgg(self.an_fig, master=tab)
w = self.an_canvas.get_tk_widget()
w.configure(width=1200, height=700)
w.pack(padx=8, pady=4)
# ───────────────── DATA TAB ────────────────────────────────
def _data_tab(self):
tab = tb.Frame(self.nb); self.nb.add(tab, text="Data")
top = tb.Frame(tab); top.pack(fill="x", padx=8, pady=6)
tk.Label(top, text="Search").pack(side="left")
self.search = tk.StringVar()
tk.Entry(top, textvariable=self.search, width=25)
.pack(side="left", padx=4)
self.search.trace_add("write", self._search_tree)
cols = list(self.df.columns)
self.tree = tb.Treeview(tab, columns=cols, show="headings",
bootstyle="dark")
for c in cols:
self.tree.heading(c, text=c.title())
self.tree.column(c, width=120, anchor="w")
ysb = tb.Scrollbar(tab, orient="vertical", command=self.tree.yview)
xsb = tb.Scrollbar(tab, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscroll=ysb.set, xscroll=xsb.set)
self.tree.pack(side="left", fill="each", expand=True)
ysb.pack(side="right", fill="y"); xsb.pack(side="bottom", fill="x")
# ───────────────── FILTER & STATS ──────────────────────────
def _apply_filters(self, *_):
df = self.df.copy()
if self.make.get() != "All":
df = df[df["make"] == self.make.get()]
if hasattr(self, "drive") and self.drive.get() != "All":
df = df[df["drivetrain"] == self.drive.get()]
try:
pmin, pmax = float(self.pmin.get()), float(self.pmax.get())
except ValueError:
pmin, pmax = df["price"].min(), df["price"].max()
df = df[(df["price"] >= pmin) & (df["price"] <= pmax)]
if "year" in df.columns and hasattr(self, "ymin"):
try:
ymin, ymax = int(self.ymin.get()), int(self.ymax.get())
except ValueError:
ymin, ymax = df["year"].min(), df["year"].max()
df = df[(df["year"] >= ymin) & (df["year"] <= ymax)]
self.filtered = df
self._update_cards()
self._draw_overview()
self._fill_tree()
if self.current_analysis_plot_func:
self.current_analysis_plot_func()
def _update_cards(self):
d = self.filtered
self.cards["Total Cars"].configure(text=f"{len(d):,}")
self.cards["Average Price"].configure(
text=f"${d['price'].mean():,.0f}" if not d.empty else "$0")
m = d["mileage"].mean() if "mileage" in d.columns else np.nan
self.cards["Average Mileage"].configure(
text=f"{m:,.0f} mi" if not np.isnan(m) else "-")
r = d["consumerrating"].mean() if "consumerrating" in d.columns else np.nan
self.cards["Avg Rating"].configure(
text=f"{r:.2f}" if not np.isnan(r) else "-")
# ───────────────── OVERVIEW PLOTS (clickable) ──────────────
def _draw_overview(self):
if hasattr(self, "_ov_pick_id"):
self.ov_fig.canvas.mpl_disconnect(self._ov_pick_id)
self.ov_fig.clear()
self._ov_annot = None
df = self.filtered
if df.empty:
ax = self.ov_fig.add_subplot(111)
ax.axis("off")
ax.text(0.5, 0.5, "No data", ha="center", va="center", color="white", fontsize=16)
self.ov_canvas.draw(); return
gs = self.ov_fig.add_gridspec(2, 2)
ax_hist = self.ov_fig.add_subplot(gs[0, 0])
ax_scatter = self.ov_fig.add_subplot(gs[0, 1])
ax_pie = self.ov_fig.add_subplot(gs[1, 0])
ax_bar = self.ov_fig.add_subplot(gs[1, 1])
ax_hist.hist(df["price"], bins=30, color=self.clr.info)
ax_hist.set_title("Price Distribution", color="w")
ax_hist.set_xlabel("Price ($)", color="w"); ax_hist.set_ylabel("Cars", color="w")
ax_hist.tick_params(colours="w")
df_scatter_data = df.dropna(subset=["mileage", "price"])
self._ov_scatter_map = {}
if not df_scatter_data.empty:
sc = ax_scatter.scatter(df_scatter_data["mileage"], df_scatter_data["price"],
s=45, alpha=0.8, c=df_scatter_data["year"], cmap="viridis")
sc.set_picker(True); sc.set_pickradius(10)
self._ov_scatter_map[sc] = df_scatter_data.reset_index(drop=True)
cb = self.ov_fig.colorbar(sc, ax=ax_scatter)
cb.ax.yaxis.set_major_locator(MaxNLocator(integer=True))
cb.ax.tick_params(colours="w"); cb.set_label("Yr", color="w")
def _on_pick(event):
if len(event.ind) == 0:
return
row = self._ov_scatter_map[event.artist].iloc[event.ind[0]]
label = shorten(f"{row['make']} {row.get('model','')}", width=40, placeholder="…")
if self._ov_annot:
self._ov_annot.remove()
self._ov_annot = ax_scatter.annotate(
label, (row["mileage"], row["price"]),
xytext=(10, 10), textcoords="offset points",
bbox=dict(boxstyle="round", fc="white", alpha=0.9), color="black")
self.ov_canvas.draw_idle()
self._ov_pick_id = self.ov_fig.canvas.mpl_connect("pick_event", _on_pick)
ax_scatter.set_title("Mileage vs Price", color="w")
ax_scatter.set_xlabel("Mileage", color="w"); ax_scatter.set_ylabel("Price ($)", color="w")
ax_scatter.tick_params(colours="w")
if "drivetrain" in df.columns:
cnt = df["drivetrain"].value_counts()
if not cnt.empty:
ax_pie.pie(cnt, labels=cnt.index, autopct="%1.0f%%", textprops={'color': 'w'})
ax_pie.set_title("Cars by Drivetrain", color="w")
if not df.empty:
top = df.groupby("make")["price"].mean().nlargest(10).sort_values()
if not top.empty:
top.plot(kind="barh", ax=ax_bar, color=self.clr.primary)
ax_bar.set_title("Top-10 Makes by Avg Price", color="w")
ax_bar.set_xlabel("Average Price ($)", color="w"); ax_bar.set_ylabel("Make", color="w")
ax_bar.tick_params(colours="w")
self.ov_canvas.draw()
# ───────────────── ANALYSIS PLOTS ──────────────────────────
def _corr(self):
self.an_fig.clear()
ax = self.an_fig.add_subplot(111)
num = self.filtered.select_dtypes(include=np.number)
if num.shape[1] < 2:
ax.text(0.5, 0.5, "Not Enough Numeric Data", ha="center", va="center", color="white", fontsize=16)
ax.axis('off')
self.an_canvas.draw(); return
im = ax.imshow(num.corr(), cmap="RdYlBu_r", vmin=-1, vmax=1)
ax.set_xticks(range(num.shape[1])); ax.set_yticks(range(num.shape[1]))
ax.set_xticklabels(num.columns, rotation=45, ha="right", color="w")
ax.set_yticklabels(num.columns, color="w")
cb = self.an_fig.colorbar(im, ax=ax, fraction=0.046)
cb.ax.tick_params(colors="w"); cb.set_label("Correlation", color="w")
ax.set_title("Feature Correlation Heat-map", color="w")
self.an_canvas.draw()
def _price_make(self):
self.an_fig.clear()
ax = self.an_fig.add_subplot(111)
df = self.filtered
if df.empty or {"make","price"}.issubset(df.columns) is False:
ax.text(0.5, 0.5, "No Data for this Filter", ha="center", va="center", color="white", fontsize=16)
ax.axis('off')
self.an_canvas.draw(); return
makes = df["make"].value_counts().nlargest(15).index
if makes.empty:
ax.text(0.5, 0.5, "No Makes to Display", ha="center", va="center", color="white", fontsize=16)
ax.axis('off')
self.an_canvas.draw(); return
data = [df[df["make"] == m]["price"] for m in makes]
# ### FIX: Use 'labels' instead of 'tick_labels' ###
ax.boxplot(data, labels=makes, vert=False, patch_artist=True,
boxprops=dict(facecolor=self.clr.info),
medianprops=dict(color=self.clr.danger))
ax.set_title("Price Distribution by Make", color="w")
ax.set_xlabel("Price ($)", color="w"); ax.set_ylabel("Make", color="w")
ax.tick_params(colors="w")
self.an_canvas.draw()
def _ratings(self):
self.an_fig.clear()
ax = self.an_fig.add_subplot(111)
cols = [c for c in (
"consumerrating","comfortrating","interiordesignrating",
"performancerating","valueformoneyrating","reliabilityrating")
if c in self.filtered.columns]
if not cols:
ax.text(0.5, 0.5, "No Rating Data in CSV", ha="center", va="center", color="white", fontsize=16)
ax.axis('off')
self.an_canvas.draw(); return
data = self.filtered[cols].dropna()
if data.empty:
ax.text(0.5, 0.5, "No Rating Data for this Filter", ha="center", va="center", color="white", fontsize=16)
ax.axis('off')
self.an_canvas.draw(); return
ax.boxplot(data.values,
labels=[c.replace("rating","") for c in cols],
patch_artist=True,
boxprops=dict(facecolor=self.clr.warning),
medianprops=dict(color=self.clr.danger))
ax.set_title("Ratings Distribution", color="w")
ax.set_ylabel("Rating (out of 5)", color="w"); ax.set_xlabel("Rating Type", color="w")
ax.tick_params(colors="w", rotation=45)
self.an_canvas.draw()
def _mpg(self):
if hasattr(self, "_mpg_pick_id"):
self.an_fig.canvas.mpl_disconnect(self._mpg_pick_id)
self.an_fig.clear()
ax = self.an_fig.add_subplot(111)
self._mpg_annot = None
raw = self.filtered
if {"minmpg","maxmpg","make"}.issubset(raw.columns) is False:
ax.text(0.5,0.5,"No MPG Data in CSV",ha="center",va="center",color="w", fontsize=16)
ax.axis('off')
self.an_canvas.draw(); return
df = raw.dropna(subset=["minmpg","maxmpg"])
if df.empty:
ax.text(0.5,0.5,"No MPG Data for this Filter",ha="center",va="center",color="w", fontsize=16)
ax.axis('off')
self.an_canvas.draw(); return
top = df["make"].value_counts().nlargest(6).index
palette = plt.cm.tab10.colors
self._scatter_map = {}
rest = df[~df["make"].isin(top)]
if not rest.empty:
sc = ax.scatter(rest["minmpg"], rest["maxmpg"],
s=25, c="lightgrey", alpha=.45, label="Other")
sc.set_picker(True); sc.set_pickradius(10)
self._scatter_map[sc] = rest.reset_index(drop=True)
for i, mk in enumerate(top):
sub = df[df["make"] == mk]
sc = ax.scatter(sub["minmpg"], sub["maxmpg"],
s=35, color=palette[i % 10], label=mk, alpha=.8)
sc.set_picker(True); sc.set_pickradius(10)
self._scatter_map[sc] = sub.reset_index(drop=True)
def _on_pick(event):
if len(event.ind) == 0:
return
row = self._scatter_map[event.artist].iloc[event.ind[0]]
label = shorten(f"{row['make']} {row.get('model','')}", width=40, placeholder="…")
if self._mpg_annot: self._mpg_annot.remove()
self._mpg_annot = ax.annotate(
label, (row["minmpg"], row["maxmpg"]),
xytext=(10, 10), textcoords="offset points",
bbox=dict(boxstyle="round", fc="white", alpha=0.9), color="black")
self.an_canvas.draw_idle()
self._mpg_pick_id = self.an_fig.canvas.mpl_connect("pick_event", _on_pick)
try:
best_hwy = df.loc[df["maxmpg"].idxmax()]
best_city = df.loc[df["minmpg"].idxmax()]
for r, t in [(best_hwy, "Best Hwy"), (best_city, "Best City")]:
ax.annotate(
f"{t}: {shorten(r['make']+' '+str(r.get('model','')),28, placeholder='…')}",
xy=(r["minmpg"], r["maxmpg"]),
xytext=(5, 5), textcoords="offset points",
fontsize=7, color="w", backgroundcolor="#00000080")
except (ValueError, KeyError): pass
ax.set_title("City MPG vs Highway MPG", color="w")
ax.set_xlabel("City MPG", color="w"); ax.set_ylabel("Highway MPG", color="w")
ax.tick_params(colors="w")
if len(top) > 0:
ax.legend(facecolor="#1e1e1e", framealpha=.3, fontsize=8, labelcolor="w", loc="upper left")
self.an_canvas.draw()
# ───────────── TABLE / SEARCH / EXPORT ─────────────────────
def _fill_tree(self):
self.tree.delete(*self.tree.get_children())
for _, row in self.filtered.head(500).iterrows():
vals = [f"{v:,.2f}" if isinstance(v, float)
else f"{int(v):,}" if isinstance(v, (int, np.integer)) else v
for v in row]
self.tree.insert("", "end", values=vals)
def _search_tree(self, *_):
term = self.search.get().lower()
self.tree.delete(*self.tree.get_children())
if not term: self._fill_tree(); return
mask = self.filtered.astype(str).apply(
lambda s: s.str.lower().str.comprises(term, na=False)).any(axis=1)
for _, row in self.filtered[mask].head(500).iterrows():
vals = [f"{v:,.2f}" if isinstance(v, float)
else f"{int(v):,}" if isinstance(v, (int, np.integer)) else v
for v in row]
self.tree.insert("", "end", values=vals)
def _export(self):
fn = tb.dialogs.filedialog.asksaveasfilename(
defaultextension=".csv", filetypes=[("CSV", "*.csv")])
if fn:
self.filtered.to_csv(fn, index=False)
tb.dialogs.Messagebox.show_info("Export complete", fn)
# ═══════════════════════════════════════════════════════════════
if __name__ == "__main__":
root = tb.Window(themename="darkly")
Dashboard(root)
root.mainloop()
High-Level Code Description and Technology Stack
This Python script creates a comprehensive and highly interactive graphical dashboard designed for the exploratory evaluation of a used automobile dataset. It’s built as a standalone desktop application using a mix of powerful libraries. Tkinter, via the ttkbootstrap wrapper, provides the fashionable, themed graphical user interface (GUI) components and window management. Data manipulation and aggregation are handled efficiently within the background by the pandas library. All data visualisations are generated by matplotlib and seamlessly embedded into the Tkinter window using its FigureCanvasTkAgg backend, allowing for complex, interactive charts inside the applying frame. The appliance is architected inside a single Dashboard class, encapsulating all its state and methods for a clean, organised structure.
Data Ingestion and Preprocessing
Upon startup, the applying performs a strong data loading and cleansing sequence. It reads a specified CSV file using pandas, immediately performing several preprocessing steps to make sure data quality and consistency.
- Header Normalisation: It iterates through all column names, converting them to lowercase and removing special characters. This prevents errors attributable to inconsistent naming, resembling “Price” vs. “price”.
- Column Aliasing: It uses a predefined dictionary to rename common alternative column names (e.g., “brand” or “manufacturer”) to a normal internal name (e.g., “make”). This adds flexibility, allowing the applying to work with different CSV formats without code changes.
- Intelligent ‘Yr’ Detection: If a “12 months” column isn’t explicitly found, the script intelligently scans other columns to seek out one containing numbers that fall inside a typical automotive 12 months range (1900–2035), routinely designating it because the ‘12 months’ column.
- Type Coercion: It systematically cleans columns expected to be numeric (like price and mileage) by removing non-numeric characters (e.g., ‘$’, ‘,’, ‘ mi’) and converting the outcomes to floating-point numbers, gracefully handling any conversion errors.
- Data Pruning: Finally, it removes any rows which can be missing essential data points (make and price), ensuring that each one data used for plotting and evaluation is valid.
User Interface and Interactive Filtering
The user interface is organised right into a principal notebook with three distinct tabs, providing a simple workflow for evaluation.
- A central feature is the dynamic filtering panel. This panel comprises widgets like a Combobox for automobile makes and Spinbox controls for price and 12 months ranges. These widgets are linked on to the applying’s core logic.
- State Management: When a user changes a filter, a central method, _apply_filters, is triggered. This function creates a brand new, temporary pandas DataFrame named self.filtered by applying the user’s selections to the master dataset. This self.filtered DataFrame then becomes the one source of truth for all visual components.
- Automatic UI Refresh: After the information is filtered, the _apply_filters method orchestrates a full refresh of the dashboard by calling all crucial update functions. This includes redrawing every plot on the “Overview” tab, updating the important thing performance indicator (KPI) cards, repopulating the information table, and crucially, redrawing the currently energetic chart on the “Evaluation” tab. This creates a highly responsive and intuitive user experience.
Visualisation and Evaluation Tabs
The core value of the applying lies in its visualisation capabilities, spread across two tabs:
1/ Overview Tab: This serves because the principal dashboard, featuring:
- KPI Cards: 4 outstanding cards at the highest display key metrics like “Total Cars” and “Average Price,” which update in real-time with the filters.
- 2×2 Chart Grid: A big, multi-panel figure displays 4 charts concurrently: a histogram for price distribution, a pie chart for drivetrain types, a horizontal bar chart for the highest 10 makes by average price, and a clickable scatter plot showing vehicle mileage versus price, coloured by 12 months. Clicking a degree on this scatter plot brings up an annotation showing the automobile’s make and model. This interactivity is achieved by connecting a Matplotlib pick_event to a handler function that attracts the annotation.
2/ Evaluation Tab: This tab is for more focused, single-plot evaluation. A row of buttons allows the user to pick one among several advanced visualisations:
- Correlation Heatmap: Shows the correlation between all numeric columns within the dataset.
- Price by Make Box Plot: Compares the worth distributions of the highest 15 most typical automobile makes, providing insight into price variance and outliers.
- Rankings Box Plot: Displays and compares the distributions of varied consumer rating categories (e.g., comfort, performance, reliability).
- MPG Scatter Plot: A totally interactive scatter plot for analysing city vs. highway MPG, with points coloured by make and a click-to-annotate feature just like the one on the overview tab.
The appliance cleverly remembers which evaluation plot was last viewed and routinely redraws it with latest data every time the worldwide filters are modified.
3/ Data Tab: For users who need to inspect the raw numbers, this tab displays the filtered data in a scrollable Treeview table. It also features a live search box that immediately filters the table’s contents because the user types.
Running the code
The code is run in the identical way as a daily Python program, so put it aside to a Python file, e.g tktest.py, and ensure you alter the file location to be wherever you downloaded the file from Kaggle. Run the code like this:
$ python tktest.py
Your screen should appear like this,

You possibly can switch between the Overview, Analytics and data TABS for various views on the information. Should you change the Make or Drivetrain from the drop-down options, the displayed data will reflect this immediately. Use the Apply Yr/Price Filter button to see changes to the information if you select different 12 months or price ranges.
The overview screen is the one you first see when the GUI displays. It consists of 4 principal charts and informational displays of statistics just underneath the filter fields.
The Evaluation TAB provides 4 additional views of the information. A correlation heat-map, a Price by make chart, an MPG chart showing how efficient the assorted make/models are and a rating chart over six different metrics. On each the Price by Make chart and the Mileage v price chart on the overview TAB, you may click on a person “dot” on the chart to see which automobile make and model it’s referring to. Here’s what the MPG chart looks like showing how efficient various makes are in comparing their City v Highway MPG figures.


Lastly, we have now a Data TAB. That is only a rows and columns tabular representation of the underlying data set. Like all of the displayed charts, this output changes as you filter the information.
To see it in motion, I first clicked on the Overview TAB and altered the input parameters to be,
Make: BMW
Drivetrain: All-wheel Drive
Price: 2300.0 to 449996.0
Yr: 2022
I then clicked on the information TAB and got this output.

Summary
This text serves as a comprehensive guide to using Tkinter, Python’s original built-in GUI library, for creating modern, data-driven desktop applications. It’s a durable, lightweight, and still-relevant tool, and paired with the ttkbootstrap library, is greater than capable of manufacturing modern-looking data displays and dashboards.
I started by covering the basic constructing blocks of any Tkinter application, resembling the basis window, widgets (buttons, labels), and geometry managers for layout.
I then moved on to a fully-featured analytics tool with a tabbed interface, dynamic filters that update all visuals in real-time, and clickable charts that provide a responsive and skilled user experience, making a powerful case for Tkinter’s capabilities beyond easy utilities.