Aplikasi kecil dengan Dash

Halaman dasbor murni Python dengan callback dari masukan hingga grafik.

Daftar isi

Dash menyusun grafik menjadi halaman web yang ditulis sepenuhnya dalam Python, tanpa satu baris HTML maupun JavaScript yang perlu ditulis secara manual. Topik ini membangun aplikasi kecil yang menampilkan grafik batang rata-rata nilai per mata pelajaran, lengkap dengan dropdown untuk memilih kelas yang grafiknya diperbarui secara otomatis ketika pilihannya berubah. Struktur aplikasinya diperiksa melalui skrip inspeksi, sedangkan cara menjalankannya pada komputer sendiri dijelaskan di bagian akhir.

Layout berupa komponen#

Layout Dash adalah pohon komponen, yaitu objek Python yang dirender menjadi elemen halaman web. Komponen html.Div dan html.H2 mewakili elemen HTML biasa, sedangkan komponen dcc.Graph menampung figur plotly dan komponen dcc.Dropdown menampung menu pilihan yang nilainya dapat dibaca maupun diubah oleh pengguna. Setiap komponen yang menjadi sumber atau sasaran interaksi diberi id, karena id itulah yang menghubungkan komponen dengan callback. Pada aplikasi topik ini, layout terdiri atas satu html.Div berisi judul, satu dcc.Dropdown dengan id pilih-kelas dan pilihan 7A serta 7B, serta satu dcc.Graph dengan id grafik-rata. Pohon itu dideklarasikan satu kali pada app.layout, dan Dash menyajikannya sebagai halaman web.

Komponen dibagi menjadi dua golongan menurut perannya. Komponen HTML hanya menyusun tampilan dan tidak membawa nilai interaktif, sedangkan komponen dari modul dcc, singkatan dari dash core components, menyimpan nilai yang berubah, misalnya pilihan dropdown dan figur grafik. Keputusan menempatkan id pada golongan kedua itulah yang menjadi fondasi callback pada bagian berikutnya.

Callback dari masukan ke keluaran#

Callback adalah fungsi Python yang dipanggil otomatis ketika nilai komponen tertentu berubah, dan penghubungnya adalah dekorator @app.callback. Dekorator tersebut menerima Output yang menyatakan komponen serta properti yang diperbarui, misalnya "grafik-rata" dengan properti figure, beserta Input yang menyatakan komponen pemantiknya, misalnya "pilih-kelas" dengan properti value. Ketika pengguna mengubah dropdown, Dash mengirim nilai terbaru ke server, menjalankan fungsi callback tersebut, dan mengganti figur pada halaman dengan nilai yang dikembalikan. Fungsi update_chart(kelas) pada aplikasi ini memfilter baris dataset sesuai kelas terpilih, menghitung rata-rata tiga kolom nilai, dan mengembalikan figur batang dari px.bar.

Callback pada aplikasi ini juga memperlihatkan sisi yang perlu diwaspadai, yaitu saringan data yang bekerja tanpa memberi tahu pembaca. Dropdown menghapus seluruh baris kelas lain sebelum rata-rata dihitung, dan grafik yang muncul tidak menyertakan penanda apa pun tentang saringan itu, selain judul kecil yang ditetapkan di dalam callback. Pada aplikasi dengan banyak filter, pembaca mudah mengira grafik menyajikan seluruh dataset padahal saringan defaultnya menyembunyikan sebagian besar barisnya. Karena itu, judul setiap figur menyebut saringan yang aktif, dan jumlah baris yang digunakan layak ditampilkan ketika saringannya banyak.

Berkas aplikasi#

Berkas app.py di bawah memuat seluruh aplikasi, dan dataset nilai_ujian.csv diletakkan di folder yang sama dengannya. Blok ini tidak dieksekusi di sandbox karena eksekusi langsungnya adalah server yang menunggu tanpa akhir, sehingga logikanya diperiksa melalui skrip inspeksi pada bagian berikutnya.

python app.py
# tidak-dijalankan (server menunggu selamanya, jalankan dengan: python app.py)
import pandas as pd
import plotly.express as px
from dash import Dash, Input, Output, dcc, html

# dataset kanonik jalur, disalin ke folder topik
tabel = pd.read_csv("nilai_ujian.csv")
kolom_nilai = ["nilai_matematika", "nilai_fisika", "nilai_literasi"]
label_kolom = {
    "nilai_matematika": "Matematika",
    "nilai_fisika": "Fisika",
    "nilai_literasi": "Literasi",
}

app = Dash(__name__)
app.layout = html.Div(
    [
        html.H2("Rata-rata nilai per mata pelajaran"),
        dcc.Dropdown(
            id="pilih-kelas",
            options=[{"label": k, "value": k} for k in sorted(tabel["kelas"].unique())],
            value="7A",
            clearable=False,
        ),
        dcc.Graph(id="grafik-rata"),
    ],
    style={"maxWidth": "640px", "margin": "0 auto"},
)


@app.callback(Output("grafik-rata", "figure"), Input("pilih-kelas", "value"))
def update_chart(kelas):
    # filter baris sesuai kelas terpilih, lalu hitung rata-rata tiap kolom nilai
    pilihan = tabel[tabel["kelas"] == kelas]
    rata = (
        pilihan[kolom_nilai]
        .mean()
        .round(2)
        .rename_axis("mata_pelajaran")
        .reset_index(name="rata_rata")
    )
    rata["mata_pelajaran"] = rata["mata_pelajaran"].map(label_kolom)
    fig = px.bar(rata, x="mata_pelajaran", y="rata_rata")
    fig.update_layout(yaxis_range=[0, 100], title=f"Kelas {kelas}")
    return fig


if __name__ == "__main__":
    app.run(debug=True)

Inspeksi struktur aplikasi#

Struktur aplikasi dapat diperiksa tanpa menjalankan server. Skrip berikut mengimpor app dari app.py, mengumpulkan id komponen pada pohon layout secara rekursif, lalu membaca peta callback yang disimpan Dash pada app.callback_map. Jalankan skripnya dari folder topiknya.

python inspeksi_app.py
import sys

from dash.development.base_component import Component

# folder berjalan, yaitu folder berisi app.py, ditambahkan ke jalur impor
# agar skrip ini dapat dijalankan dari folder topiknya
sys.path.insert(0, ".")

from app import app


def daftar_id(node):
    """Kumpulkan id komponen pada pohon layout secara rekursif."""
    hasil = []
    if isinstance(node, Component):
        id_komponen = getattr(node, "id", None)
        if id_komponen:
            hasil.append(id_komponen)
        anak = getattr(node, "children", None)
        if isinstance(anak, Component):
            hasil.extend(daftar_id(anak))
        elif isinstance(anak, list):
            for bagian in anak:
                hasil.extend(daftar_id(bagian))
    return hasil


print("id komponen:", daftar_id(app.layout))
for keluaran, isi in app.callback_map.items():
    masukan = isi["inputs"][0]
    sumber = f"{masukan['id']}.{masukan['property']}"
    print("callback:", keluaran, "dipicu oleh", sumber)
text Keluaran
id komponen: ['pilih-kelas', 'grafik-rata']
callback: grafik-rata.figure dipicu oleh pilih-kelas.value

Keluaran menunjukkan dua komponen pada layout dan satu callback yang menghubungkan properti value dropdown dengan properti figure grafik. Peta ini persis seperti deklarasi dekoratornya, sehingga kesalahan penulisan id pada Output atau Input langsung terlihat pada peta tersebut tanpa perlu membuka halaman. Aplikasi yang lebih besar mengikuti pola yang sama, hanya dengan lebih banyak baris pada kedua daftarnya.

Menjalankan aplikasi#

Aplikasi dijalankan dari folder yang berisi app.py dan datasetnya. Servernya menunggu tanpa akhir, sehingga blok perintah di bawah tidak dieksekusi di sandbox.

bash
# tidak-dijalankan: server menunggu selamanya
# jalankan dari folder yang berisi app.py dan nilai_ujian.csv
python app.py

Setelah perintah dijalankan, terminal menampilkan alamat lokal, umumnya http://127.0.0.1:8050, dan halaman aplikasi terbuka di browser pada alamat itu. Mengubah pilihan dropdown memanggil callback, dan grafiknya berganti sesuai kelas terpilih, yaitu Matematika 81.42, Fisika 79.17, dan Literasi 79.33 untuk kelas 7A, serta 77.58, 75.58, dan 74.75 untuk kelas 7B. Menekan Ctrl+C di terminal menghentikan servernya.

Kesalahan umum#

  • Menyebut id yang salah pada Output atau Input. Callback mencari komponen berdasarkan id, sehingga id yang keliru membuat callback tidak pernah dipanggil dan Dash menampilkan pesan error yang menyebut id yang tidak ditemukan. Peta pada skrip inspeksi menjadi alat pemeriksaan cepat untuk kasus ini.
  • Menghitung ulang sesuatu yang tidak bergantung masukan di dalam callback. Bagian perhitungan yang sama untuk semua pilihan, misalnya membaca dataset, berada di luar callback dan dihitung satu kali. Callback yang ramping hanya mengerjakan bagian yang benar-benar berubah mengikuti masukannya.
  • Menjalankan server baru ketika server lama masih hidup. Aplikasi berikutnya gagal berjalan karena alamatnya masih digunakan instance sebelumnya, dan terminal menampilkan pesan bahwa alamat itu sedang terpakai. Server lama dihentikan dengan Ctrl+C sebelum perintahnya dijalankan ulang.

Ringkasan#

  • Layout Dash adalah pohon komponen Python seperti dcc.Dropdown dan dcc.Graph, dengan id sebagai penghubung antarkomponen.
  • Dekorator @app.callback menghubungkan Input ke Output, yaitu nilai dropdown kelas ke figur grafik, dan Dash menjalankan fungsinya di server setiap kali nilai berubah.
  • Callback yang menyaring dataset perlu menyatakan saringannya pada tampilan, karena grafik yang dihasilkan tidak menunjukkan data yang dikecualikan.

Referensi#