Python ve Tkinter ile Modern Dashboard Oluşturma: Veri Analizi için Masaüstü Uygulama Rehberi
0

Streamlit, Dash ve Gradio gibi modern, web tabanlı framework’ler veri odaklı ve makine öğrenmesi uygulamalarının masaüstü sunumu için öne çıkarken, “Tkinter kütüphanesi kullanmaya değer mi?” sorusunu soruyoruz.

Bu soruya cevabım kesinlikle Evet! Bu makalede Tkinter’ın yerel masaüstü GUI ve veri dashboard uygulamaları oluşturmak için hala güçlü, hafif ve son derece alakalı bir araç olduğunu göstermeyi umuyorum.

Dahili araçlar, basit yardımcı programlar veya eğitim yazılımları oluşturması gereken geliştiriciler için Tkinter ideal seçim olabilir. Karmaşık web sunucuları, JavaScript bilgisi veya ağır bağımlılıklar gerektirmez. Sade Python’dur. Ve ilerleyen bölümlerde göstereceğim gibi, oldukça karmaşık, modern görünümlü dashboardlar üretebilirsiniz.

Bu makalenin geri kalanında, Tkinter’ın temel ilkelerinden dinamik, veri odaklı bir dashboard’un pratik yapımına kadar bir yolculuğa çıkacağız ve bu “eski” GUI kütüphanesinin hala modern hilelere sahip olduğunu kanıtlayacağız.

Tkinter Nedir ve Neden Hala Önemsemelisiniz?

Tkinter, Python için standart, yerleşik Grafik Kullanıcı Arayüzü (GUI) araç setidir. İsim “Tk Interface”in kelime oyunudur. 1990’ların başlarından beri var olan sağlam ve platformlar arası GUI araç seti olan Tcl/Tk’nin sarmalayıcısıdır.

En önemli avantajı Python standart kütüphanesine dahil olmasıdır. Bu, Python yüklüyse Tkinter’a sahip olduğunuz anlamına gelir. Çalıştırılacak pip install komutları, çözülecek sanal ortam bağımlılık çakışmaları yoktur. Windows, macOS ve Linux’ta kutudan çıktığı gibi çalışır.

Peki neden gösterişli web framework’lerinin çağında Tkinter’ı seçmelisiniz?

Tkinter’ın Avantajları

  • Basitlik ve Hız: Küçük ve orta ölçekli uygulamalar için Tkinter ile geliştirmek hızlıdır. Sadece birkaç satır kodla etkileşimli öğeler içeren işlevsel bir pencere oluşturabilirsiniz.
  • Hafiflik: Tkinter uygulamaları küçük ayak izine sahiptir. Tarayıcı veya web sunucusu gerektirmezler, bu da bunları herhangi bir makinede verimli çalışması gereken basit yardımcı programlar için ideal hale getirir.
  • Yerel Görünüm ve His (belirli ölçüde): Klasik Tkinter’ın eski görünümü ünlüyken, ttk temalı widget seti, ana bilgisayar işletim sistemiyle daha iyi eşleşen daha modern, yerel görünümlü kontrollere erişim sağlar.
  • Öğrenmek İçin Mükemmel: Tkinter, olay odaklı programlamanın temel kavramlarını öğretir – tüm GUI geliştirmesinin çekirdeği. Tkinter’da widget’ları, düzenleri ve kullanıcı olaylarını yönetmeyi anlamak, diğer GUI framework’lerini öğrenmek için sağlam bir temel sağlar.

Tabii ki dezavantajları da var. Karmaşık, estetik açıdan zorlu uygulamalar oluşturmak zor olabilir ve tasarım felsefesi Streamlit veya Gradio’nun bildirimsel stiline kıyasla daha ayrıntılı hissedilebilir. Ancak amaçlanan kullanımı – işlevsel, bağımsız masaüstü uygulamaları oluşturmak – için mükemmeldir.

Zamanla, Tkinter GUI’larını daha modern görünümlü hale getiren ek kütüphaneler yazıldı. Bunlardan kullanacağımız biri ttkbootstrap adındadır. Bu, Tkinter’ın üstüne inşa edilmiş, ekstra widget’lar ekleyen ve GUI’larınıza Bootstrap ilhamlı görünüm kazandırabilen bir kütüphanedir.

Tkinter Uygulamasının Temel Kavramları

Her Tkinter uygulaması birkaç temel sütun üzerine inşa edilmiştir. Anlamlı bir şey oluşturmadan önce bu kavramları kavramak şarttır.

1. Ana Pencere (Root Window)

Ana pencere, tüm uygulamanızın ana konteyneridir. Başlık çubuğu, küçült, büyüt ve kapat düğmeleri olan üst düzey penceredir. Tek satır kodla oluşturursunuz:

import tkinter as tk

root = tk.Tk()
root.title("İlk Tkinter Uygulamam")
root.mainloop()

Bu kod şunu üretir:

Basit Tkinter Penceresi

Bakması en heyecan verici şey değil ama bir başlangıç. Uygulamanızdaki diğer her şey – düğmeler, etiketler, giriş alanları vb. – bu ana pencerenin içinde yaşayacak.

2. Widget’lar

Widget’lar GUI’nizin yapı taşlarıdır. Kullanıcının gördüğü ve etkileşimde bulunduğu öğelerdir. En yaygın widget’lardan bazıları:

  • Label: Statik metin veya resim görüntüler
  • Button: Bir fonksiyonu tetikleyebilen tıklanabilir düğme
  • Entry: Tek satırlık metin giriş alanı
  • Text: Çok satırlı metin giriş ve görüntüleme alanı
  • Frame: Diğer widget’ları gruplamak için kullanılan görünmez dikdörtgen konteyner. Karmaşık düzenleri organize etmek için çok önemli
  • Canvas: Şekil çizmek, grafik oluşturmak veya resim görüntülemek için çok yönlü widget
  • Checkbutton ve Radiobutton: Boolean veya çoklu seçim seçimleri için

3. Geometri Yöneticileri

Widget’larınızı oluşturduktan sonra, Tkinter’a bunları pencerenin neresine koyacağını söylemeniz gerekir. Bu, geometri yöneticilerinin işidir. Aynı ebeveyn konteyner içinde (kök veya Frame gibi) farklı yöneticileri karıştıramazsınız.

  • pack(): En basit yönetici. Widget’ları pencereye dikey veya yatay olarak “paketler”. Basit düzenler için hızlıdır ama kesin kontrol sunar.
  • place(): En kesin yönetici. Widget’ın tam piksel koordinatlarını (x, y) ve boyutunu (genişlik, yükseklik) belirtmenizi sağlar. Genellikle kaçınılmalıdır çünkü uygulamanızı katı ve pencere boyutlandırmaya duyarlı olmayacak hale getirir.
  • grid(): En güçlü ve esnek yönetici, dashboard’umuz için kullanacağımız. Widget’ları satır ve sütunların tablo benzeri yapısında organize eder, hizalanmış, yapılandırılmış düzenler oluşturmak için mükemmel.

4. Ana Döngü (Main Loop)

root.mainloop() satırı herhangi bir Tkinter uygulamasının son ve en kritik parçasıdır. Bu yöntem olay döngüsünü başlatır. Uygulama bekleme durumuna geçer, fare tıklamaları, tuş basmaları veya pencere boyutlandırma gibi kullanıcı eylemlerini dinler. Bir olay gerçekleştiğinde, Tkinter onu işler (örn. düğme tıklamasına bağlı bir fonksiyonu çağırır) ve sonra döngüye geri döner. Uygulama yalnızca bu döngü sonlandırıldığında, genellikle pencereyi kapatarak kapanacaktır.

Geliştirme Ortamı Kurulumu

Kodlamaya başlamadan önce, geliştirme ortamı kuralım. Ortam kurulumu için conda’yı değiştirerek yavaş yavaş UV komut satırı aracına geçiyorum ve burada onu kullanacağız.

# projeyi başlat
uv init tktest
cd tktest

# yeni venv oluştur
uv venv tktest

# ona geç
tktest\Scripts\activate

# Gerekli harici kütüphaneleri yükle
(tktest) uv pip install matplotlib ttkbootstrap pandas

Örnek 1: Basit “Merhaba, Tkinter!” Uygulaması

Bu kavramları uygulamaya koyalım. Etiket ve düğme içeren bir pencere oluşturacağız. Düğmeye tıklandığında, etiketin metni değişecek.

import tkinter as tk

# 1. Ana pencereyi oluştur
root = tk.Tk()
root.title("Basit Etkileşimli Uygulama")
root.geometry("300x150")  # Pencere boyutunu ayarla: genişlik x yükseklik

# Bu fonksiyon düğmeye tıklandığında çağrılacak
def dugme_tikla():
    # Etiket widget'ının metnini güncelle
    etiket.config(text="Merhaba, Tkinter!")

# 2. Widget'ları oluştur
etiket = tk.Label(root, text="Aşağıdaki düğmeye tıklayın.")
dugme = tk.Button(root, text="Beni Tıkla!", command=dugme_tikla)

# 3. Widget'ları yerleştirmek için geometri yöneticisi kullan
# Bu basit düzen için pack() kullanıyoruz
etiket.pack(pady=20)  # pady dikey boşluk ekler
dugme.pack()

# 4. Ana olay döngüsünü başlat
root.mainloop()

Şöyle görünmeli, sağdaki resim düğmeye tıkladığınızda aldığınız:

Etkileşimli Tkinter Uygulaması

Buraya kadar oldukça basit; ancak Tkinter ile modern, görsel açıdan çekici GUI’lar ve dashboardlar oluşturabilirsiniz. Bunu göstermek için Tkinter’ın neler yapabileceğini sergileyen daha kapsamlı ve karmaşık bir uygulama oluşturacağız.

Örnek 2 – Modern Veri Dashboard’u

Bu örnek için Kaggle’dan CarsForSale adlı veri setini kullanarak bir veri dashboard’u oluşturacağız. Bu, CC0:Kamu Malı lisansıyla gelir, yani çoğu amaç için serbestçe kullanılabilir.

2001–2022 dönemini kapsayan yaklaşık 40 farklı üreticiden yaklaşık 9300 farklı araba modelinin satış ve performans detaylarını içeren ABD merkezli bir veri setidir. Aşağıdaki bağlantıdan alabilirsiniz:

https://www.kaggle.com/datasets/chancev/carsforsale/data

Veri setini indirin ve yerel sisteminizde bir CSV dosyasına kaydedin.

Veri Seti Önizlemesi

Bu örnek ilkinden çok daha karmaşık olacak, ama size Tkinter ile tam olarak ne yapılabileceği konusunda iyi bir fikir vermek istedim. Kodu sunacağım ve ürettiği GUI’yi incelemeden önce genel işlevselliğini açıklayacağım.

###############################################################################
# İKİNCİ EL ARAÇ PAZARI DASHBOARD'U
###############################################################################
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:\Users\thoma\temp\carsforsale.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.colors
        self.current_analysis_plot_func = None
        self._load_data()
        self._build_gui()
        self._apply_filters()

    # ─────────── spin-box style (beyaz oklar) ────────────────
    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

    # ───────────────────── VERI YÜKLEME ───────────────────────────
    def _load_data(self):
        csv = Path(CSV_PATH)
        if not csv.exists():
            tb.dialogs.Messagebox.show_error("CSV bulunamadı", str(csv))
            sys.exit()
        
        df = pd.read_csv(csv, encoding="utf-8-sig", skipinitialspace=True)
        
        # Sütun adlarını normalize et
        df.columns = [
            COLUMN_ALIASES.get(
                re.sub(r"[^0-9a-z]", "", c.lower().replace("\ufeff", "")),
                c.lower()
            )
            for c in df.columns
        ]
        
        # Yıl sütunu akıllı algılama
        if "year" 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: "year"}, inplace=True)
                    break
        
        # Sayısal sütunları temizle
        for col in ("price", "minmpg", "maxmpg", "year", "mileage", "consumerrating"):
            if col in df.columns:
                df[col] = pd.to_numeric(
                    df[col].astype(str).str.replace(r"[^\d.]", "", regex=True),
                    errors="coerce"
                )
        
        # Gerekli sütunları kontrol et
        if any(c not in df.columns for c in REQUIRED):
            tb.dialogs.Messagebox.show_error(
                "Hatalı CSV", "Gerekli sütunlar eksik.")
            sys.exit()
        
        self.df = df.dropna(subset=["make", "price"])

    # ───────────────────── GUI OLUŞTURMA ───────────────────────────
    def _build_gui(self):
        # Başlık
        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="🚗 İKİNCİ EL ARAÇ DASHBOARD'U",
                font=("Segoe UI", 16, "bold"), anchor="w")\
            .pack(fill="both", padx=8, pady=4)
        
        # Notebook (sekmeler)
        self.nb = tb.Notebook(self.root)
        self.nb.pack(fill="both", expand=True)
        
        self._overview_tab()
        self._analysis_tab()
        self._data_tab()

    # ───────────────── GENEL BAKIŞ SEKMESİ ─────────────────────────
    def _overview_tab(self):
        tab = tb.Frame(self.nb)
        self.nb.add(tab, text="Genel Bakış")
        
        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="Filtreler", padding=6)
        f.pack(fill="x", padx=8, pady=6)
        
        # Marka filtresi
        tk.Label(f, text="Marka").grid(row=0, column=0, sticky="w", padx=4)
        self.make = tk.StringVar(value="Tümü")
        tb.Combobox(f, textvariable=self.make, state="readonly", width=14,
                   values=["Tümü"] + sorted(self.df["make"].unique()),
                   bootstyle="dark")\
            .grid(row=0, column=1)
        self.make.trace_add("write", self._apply_filters)
        
        # Çekiş sistemi filtresi (varsa)
        if "drivetrain" in self.df.columns:
            tk.Label(f, text="Çekiş").grid(row=0, column=2, padx=(20, 4))
            self.drive = tk.StringVar(value="Tümü")
            tb.Combobox(f, textvariable=self.drive, state="readonly", width=14,
                       values=["Tümü"] + sorted(self.df["drivetrain"].dropna().unique()),
                       bootstyle="dark")\
                .grid(row=0, column=3)
            self.drive.trace_add("write", self._apply_filters)
        
        # Fiyat aralığı
        pr_min, pr_max = self.df["price"].min(), self.df["price"].max()
        tk.Label(f, text="Fiyat $").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)
        
        # Yıl aralığı (varsa)
        if "year" in self.df.columns:
            yr_min, yr_max = int(self.df["year"].min()), int(self.df["year"].max())
            tk.Label(f, text="Yıl").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)
        
        # Filtre uygula düğmesi
        tb.Button(f, text="Yıl/Fiyat Filtrelerini Uygula",
                 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 ("Toplam Araç", "Ortalama Fiyat", "Ortalama Kilometre", "Ort Puan"):
            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, "bold"),
                          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="both", 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="both", expand=True)

    # ───────────────── ANALİZ SEKMESİ ──────────────────────────
    def _analysis_tab(self):
        tab = tb.Frame(self.nb)
        self.nb.add(tab, text="Analiz")
        
        # Kontrol düğmeleri
        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 (("Korelasyon", self._corr),
                       ("Markaya Göre Fiyat", self._price_make),
                       ("Yakıt Tüketimi", self._mpg),
                       ("Puanlamalar", self._ratings)):
            tb.Button(ctl, text=txt, command=lambda f=fn: set_and_run_analysis(f),
                     bootstyle="info-outline").pack(side="left", padx=4)
        
        # Analiz figürü
        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)

    # ───────────────── VERİ SEKMESİ ────────────────────────────────
    def _data_tab(self):
        tab = tb.Frame(self.nb)
        self.nb.add(tab, text="Veri")
        
        # Arama kutusu
        top = tb.Frame(tab)
        top.pack(fill="x", padx=8, pady=6)
        
        tk.Label(top, text="Ara").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)
        
        # Treeview (tablo)
        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")
        
        # Scrollbar'lar
        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="both", expand=True)
        ysb.pack(side="right", fill="y")
        xsb.pack(side="bottom", fill="x")

    # ───────────────── FİLTRE & İSTATİSTİKLER ──────────────────────────
    def _apply_filters(self, *_):
        df = self.df.copy()
        
        # Marka filtresi
        if self.make.get() != "Tümü":
            df = df[df["make"] == self.make.get()]
        
        # Çekiş filtresi
        if hasattr(self, "drive") and self.drive.get() != "Tümü":
            df = df[df["drivetrain"] == self.drive.get()]
        
        # Fiyat filtresi
        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)]
        
        # Yıl filtresi
        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()
        
        # Mevcut analiz grafiğini güncelle
        if self.current_analysis_plot_func:
            self.current_analysis_plot_func()

    def _update_cards(self):
        d = self.filtered
        self.cards["Toplam Araç"].configure(text=f"{len(d):,}")
        self.cards["Ortalama Fiyat"].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["Ortalama Kilometre"].configure(
            text=f"{m:,.0f} km" if not np.isnan(m) else "-")
        
        r = d["consumerrating"].mean() if "consumerrating" in d.columns else np.nan
        self.cards["Ort Puan"].configure(
            text=f"{r:.2f}" if not np.isnan(r) else "-")

    # ───────────────── GENEL BAKIŞ GRAFİKLERİ (tıklanabilir) ──────────────
    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, "Veri yok", ha="center", va="center", 
                   color="white", fontsize=16)
            self.ov_canvas.draw()
            return
        
        # 2x2 grid düzeni
        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])
        
        # Fiyat histogramı
        ax_hist.hist(df["price"], bins=30, color=self.clr.info)
        ax_hist.set_title("Fiyat Dağılımı", color="w")
        ax_hist.set_xlabel("Fiyat ($)", color="w")
        ax_hist.set_ylabel("Araç Sayısı", color="w")
        ax_hist.tick_params(colors="w")
        
        # Kilometre vs Fiyat scatter plot
        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(colors="w")
            cb.set_label("Yıl", 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("Kilometre vs Fiyat", color="w")
        ax_scatter.set_xlabel("Kilometre", color="w")
        ax_scatter.set_ylabel("Fiyat ($)", color="w")
        ax_scatter.tick_params(colors="w")
        
        # Çekiş sistemi pasta grafiği
        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("Çekiş Sistemine Göre Araçlar", color="w")
        
        # En pahalı 10 marka
        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("Ortalama Fiyata Göre İlk 10 Marka", color="w")
                ax_bar.set_xlabel("Ortalama Fiyat ($)", color="w")
                ax_bar.set_ylabel("Marka", color="w")
                ax_bar.tick_params(colors="w")
        
        self.ov_canvas.draw()

    # ───────────────── ANALİZ GRAFİKLERİ ──────────────────────────
    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, "Yeterli Sayısal Veri Yok", 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("Korelasyon", color="w")
        ax.set_title("Özellik Korelasyon Haritası", 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, "Bu Filtre için Veri Yok", 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, "Gösterilecek Marka Yok", 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]
        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("Markaya Göre Fiyat Dağılımı", color="w")
        ax.set_xlabel("Fiyat ($)", color="w")
        ax.set_ylabel("Marka", 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, "CSV'de Puan Verisi Yok", 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, "Bu Filtre için Puan Verisi Yok", 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("Puan Dağılımı", color="w")
        ax.set_ylabel("Puan (5 üzerinden)", color="w")
        ax.set_xlabel("Puan Türü", 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,"CSV'de Yakıt Tüketimi Verisi Yok",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,"Bu Filtre için Yakıt Tüketimi Verisi Yok",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 = {}
        
        # Diğer markalar
        rest = df[~df["make"].isin(top)]
        if not rest.empty:
            sc = ax.scatter(rest["minmpg"], rest["maxmpg"],
                           s=25, c="lightgrey", alpha=.45, label="Diğer")
            sc.set_picker(True)
            sc.set_pickradius(10)
            self._scatter_map[sc] = rest.reset_index(drop=True)
        
        # En çok olan 6 marka
        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)
        
        # En iyi yakıt tüketimi değerlerini işaretle
        try:
            best_hwy = df.loc[df["maxmpg"].idxmax()]
            best_city = df.loc[df["minmpg"].idxmax()]
            
            for r, t in [(best_hwy, "En İyi Otoyol"), (best_city, "En İyi Şehir")]:
                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("Şehir İçi vs Otoyol Yakıt Tüketimi", color="w")
        ax.set_xlabel("Şehir İçi MPG", color="w")
        ax.set_ylabel("Otoyol 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()

    # ───────────── TABLO / ARAMA / 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.contains(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("Dışa aktarma tamamlandı", fn)

# ═══════════════════════════════════════════════════════════════
if __name__ == "__main__":
    root = tb.Window(themename="darkly")
    Dashboard(root)
    root.mainloop()

Üst Düzey Kod Açıklaması ve Teknoloji Yığını

Bu Python betiği, ikinci el araç veri setinin keşifsel analizi için tasarlanmış kapsamlı ve son derece etkileşimli bir grafik dashboard’u oluşturur. Güçlü kütüphanelerin kombinasyonunu kullanarak bağımsız bir masaüstü uygulaması olarak inşa edilmiştir.

Tkinter, ttkbootstrap sarmalayıcısı aracılığıyla modern, temalı grafik kullanıcı arayüzü (GUI) bileşenleri ve pencere yönetimi sağlar. Veri manipülasyonu ve toplama, arka planda pandas kütüphanesi tarafından verimli bir şekilde gerçekleştirilir. Tüm veri görselleştirmeleri matplotlib tarafından oluşturulur ve FigureCanvasTkAgg backend’i kullanılarak Tkinter penceresine sorunsuz bir şekilde entegre edilir, uygulama çerçevesi içinde karmaşık, etkileşimli grafiklere olanak tanır.

Uygulama, tüm durumu ve metodlarını temiz, organize bir yapı için kapsülleyen tek bir Dashboard sınıfı içinde mimarisi oluşturulmuştur.

Veri Alımı ve Ön İşleme

Başlatıldığında, uygulama sağlam bir veri yükleme ve temizleme sırası gerçekleştirir. Pandas kullanarak belirli bir CSV dosyasını okur ve veri kalitesi ve tutarlılığını sağlamak için hemen birkaç ön işleme adımı gerçekleştirir.

Veri İşleme Özellikleri

  • Başlık Normalizasyonu: Tüm sütun adlarında yineler, bunları küçük harfe dönüştürür ve özel karakterleri kaldırır.
  • Sütun Takma Adları: Yaygın alternatif sütun adlarını standart dahili adlara yeniden adlandırır.
  • Akıllı ‘Yıl’ Algılama: “year” sütunu açıkça bulunamazsa, otomotiv yılı aralığında (1900–2035) sayılar içeren diğer sütunları akıllıca tarar.
  • Tür Zorlaması: Sayısal olması beklenen sütunları sistematik olarak temizler.
  • Veri Budama: Temel veri noktaları eksik olan satırları kaldırır.

Kullanıcı Arayüzü ve Etkileşimli Filtreleme

Kullanıcı arayüzü, analiz için basit bir iş akışı sağlayan üç ayrı sekmeli ana notebook içinde organize edilmiştir.

Temel Özellikler

  • Dinamik Filtreleme Paneli: Araba markaları için Combobox ve fiyat/yıl aralıkları için Spinbox kontrolleri
  • Durum Yönetimi: Kullanıcı bir filtreyi değiştirdiğinde, merkezi _apply_filters yöntemi tetiklenir
  • Otomatik UI Yenileme: Veriler filtrelendikten sonra, tüm görsel bileşenlerin tam yenilenmesini düzenler

Görselleştirme ve Analiz Sekmeleri

Uygulamanın temel değeri, iki sekmeye yayılan görselleştirme yeteneklerinde yatar:

1. Genel Bakış Sekmesi

Ana dashboard olarak hizmet eder ve şunları içerir:

  • KPI Kartları: Üstte “Toplam Araç” ve “Ortalama Fiyat” gibi temel metrikleri gösteren dört öne çıkan kart
  • 2×2 Grafik Izgarası: Dört grafiği aynı anda görüntüleyen büyük, çok panelli figür:
    • Fiyat dağılımı histogramı
    • Çekiş türleri pasta grafiği
    • Ortalama fiyata göre ilk 10 marka yatay çubuk grafiği
    • Yıla göre renklendirilen, tıklanabilir kilometre vs fiyat saçılım grafiği

2. Analiz Sekmesi

Daha odaklanmış, tek grafik analizi için. Bir düğme satırı, kullanıcının birkaç gelişmiş görselleştirmeden birini seçmesine olanak tanır:

  • Korelasyon Isı Haritası: Veri setindeki tüm sayısal sütunlar arasındaki korelasyonu gösterir
  • Markaya Göre Fiyat Kutu Grafiği: En yaygın 15 araba markasının fiyat dağılımlarını karşılaştırır
  • Puanlar Kutu Grafiği: Çeşitli tüketici puanlama kategorilerinin dağılımlarını görüntüler ve karşılaştırır
  • MPG Saçılım Grafiği: Şehir vs otoyol yakıt tüketimininin tam etkileşimli analizi

3. Veri Sekmesi

Ham sayıları incelemek isteyen kullanıcılar için, bu sekme filtrelenen verileri kaydırılabilir Treeview tablosunda görüntüler. Ayrıca kullanıcı yazdıkça tablonun içeriğini anında filtreleyen canlı arama kutusu içerir.

Kodu Çalıştırma

Kod, normal bir Python programı gibi çalıştırılır. Python dosyasına kaydedin (örn. tktest.py) ve dosya konumunu Kaggle’dan indirdiğiniz yere değiştirdiğinizden emin olun:

$ python tktest.py

Ekranınız şöyle görünmeli:

Dashboard Genel Görünümü

Veriler üzerinde farklı görünümler için Genel Bakış, Analiz ve Veri sekmeler arasında geçiş yapabilirsiniz. Açılır seçeneklerden Marka veya Çekiş sistemini değiştirirseniz, görüntülenen veriler bunu hemen yansıtacaktır.

Dashboard Özelliklerinin Detaylı İncelemesi

Genel Bakış Ekranı

İlk açıldığında göreceğiniz ekrandır. Filtre alanlarının hemen altında dört ana grafik ve istatistik bilgi göstergelerinden oluşur.

Analiz Sekmesi

Verinin dört ek görünümünü sağlar:

  • Korelasyon ısı haritası
  • Markaya göre fiyat grafiği
  • Çeşitli marka/modellerin ne kadar verimli olduğunu gösteren MPG grafiği
  • Altı farklı metrik üzerinden puanlama grafiği

Hem “Markaya Göre Fiyat” grafiğinde hem de genel bakış sekmesindeki “Kilometre vs fiyat” grafiğinde, hangi araba markası ve modelini ifade ettiğini görmek için grafikteki bireysel “noktaya” tıklayabilirsiniz.

MPG Analiz Grafiği

Veri Sekmesi

Bu, altta yatan veri setinin satırlar ve sütunlar tablo temsilcisidir. Gösterilen tüm grafikler gibi, bu çıktı da verileri filtrelediğinizde değişir.

Örnek kullanım: BMW markası, All-wheel Drive çekiş sistemi, 2022 yılı filtrelerini uyguladıktan sonra veri sekmesi şu çıktıyı verir:

Filtrelenmiş Veri Görünümü

Sonuç

Bu makale, Python’un orijinal yerleşik GUI kütüphanesi olan Tkinter’ı modern, veri odaklı masaüstü uygulamaları oluşturmak için kullanma konusunda kapsamlı bir rehber olarak hizmet etmektedir. Dayanıklı, hafif ve hala alakalı bir araçtır ve ttkbootstrap kütüphanesi ile eşleştirildiğinde modern görünümlü veri göstergeleri ve dashboard’lar üretebilme yetisine fazlasıyla sahiptir.

Herhangi bir Tkinter uygulamasının temel yapı taşlarını – ana pencere, widget’lar (düğmeler, etiketler) ve düzen için geometri yöneticilerini – kapsayarak başladım.

Daha sonra sekmeli arayüz, tüm görselleri gerçek zamanlı olarak güncelleyen dinamik filtreler ve duyarlı ve profesyonel kullanıcı deneyimi sağlayan tıklanabilir grafikler içeren tam özellikli bir analiz aracına geçtim. Bu, Tkinter’ın basit yardımcı programların ötesindeki yetenekleri için güçlü bir durum oluşturuyor.

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir