September 28, 2026

Streamlit: Membuat Dashboard Data.

Membangun dashboard data dengan Streamlit: model eksekusi, widget st.*, layout, caching st.cache_data, session state, multipage app dengan st.navigation, dan deploy.

Streamlit mengubah skrip Python menjadi web app interaktif tanpa menulis HTML, CSS, atau JavaScript. Dashboard skripsi saya — Price Prediction System — dibangun dengan Streamlit: halaman fetch data, analitik harian, agregasi bulanan, forecast, dan perbandingan antar kota. Contoh di bawah mengikuti API Streamlit terbaru (seri 1.6x).

pip install streamlit plotly pandas
streamlit run app.py        # buka http://localhost:8501

1. Model Eksekusi: Skrip Dijalankan Ulang

Hal terpenting yang harus dipahami: setiap interaksi (klik, ketik, geser slider) menjalankan ulang seluruh skrip dari atas ke bawah. Nilai widget dikembalikan sebagai variabel biasa.

import streamlit as st
import pandas as pd
import numpy as np

st.title("Harga Beras Harian")

hari = st.slider("Jumlah hari", 7, 90, 30)          # rerun saat digeser
idx = pd.date_range(end=pd.Timestamp.today().normalize(), periods=hari, freq="D")
harga = 13000 + np.random.default_rng(1).normal(0, 60, hari).cumsum()

df = pd.DataFrame({"harga": harga.round()}, index=idx)
st.line_chart(df)
st.metric("Harga terakhir", f"Rp {df['harga'].iloc[-1]:,.0f}".replace(",", "."),
          delta=f"{df['harga'].iloc[-1] - df['harga'].iloc[-2]:+.0f}")

Konsekuensinya: kerja mahal (query DB, fetch API, training model) harus di-cache, dan state yang perlu bertahan antar-rerun disimpan di st.session_state.


2. Widget yang Sering Dipakai

WidgetMengembalikanContoh pakai
st.selectboxsatu nilaipilih kota
st.multiselectlistbandingkan beberapa kota
st.date_inputdate atau tuple rentangperiode analisis
st.sliderangka/rentangproporsi data training 60–90%
st.radio / st.segmented_controlsatu nilaimetode Auto / SES / DES
st.number_inputangkanilai α
st.buttonTrue di rerun saat diklikjalankan fetch
st.toggle / st.checkboxbooltampilkan tabel mentah
st.file_uploaderfile-like / Noneupload CSV

Output yang paling sering: st.dataframe, st.metric, st.plotly_chart, st.line_chart, dan status st.success/st.warning/st.error.

metode = st.radio("Metode", ["Auto", "SES", "DES"], horizontal=True)
train = st.slider("Data training (%)", 60, 90, 80, step=5)
alpha = st.number_input("Alpha (α)", 0.05, 0.95, 0.5, step=0.05,
                        disabled=metode == "Auto")

if st.button("Jalankan forecast", type="primary"):
    st.write(f"Menjalankan {metode} dengan {train}% data training, α={alpha}")

Untuk banyak input yang baru diproses bersamaan, bungkus dalam st.form agar tidak rerun di setiap ketikan:

with st.form("filter"):
    kota = st.multiselect("Kota", ["Kab. Malang", "Kota Surabaya", "Kab. Jember"])
    rentang = st.date_input("Rentang", value=(pd.Timestamp("2025-01-01"), pd.Timestamp("2025-06-30")))
    submitted = st.form_submit_button("Terapkan")

3. Layout

st.set_page_config(page_title="Analitik Harian", page_icon="📊", layout="wide")  # taruh di awal

with st.sidebar:
    st.header("Filter")
    kota = st.selectbox("Kota", ["Kab. Malang", "Kota Surabaya"])

c1, c2, c3, c4 = st.columns(4)
c1.metric("Rata-rata", "Rp 13.240")
c2.metric("Tertinggi", "Rp 13.900")
c3.metric("Terendah", "Rp 12.980")
c4.metric("Median", "Rp 13.210")

tab1, tab2 = st.tabs(["🚨 Lonjakan Harga", "📉 Perubahan Harian"])
with tab1:
    st.write("Tabel lonjakan ≥ 5%")
with tab2:
    st.write("Tabel perubahan harian")

with st.expander("Lihat data mentah"):
    st.dataframe(df, width="stretch")

with st.container(border=True):
    st.caption("Sumber: data harga pangan resmi Pemprov Jawa Timur")

use_container_width=True sudah deprecated di st.dataframe, st.plotly_chart, dan kawan-kawan. Gantinya width="stretch" (default) atau width="content". Kode lama di skripsi saya masih memakai bentuk lama — jalan, tetapi memunculkan peringatan.


4. Caching

Dua decorator, dua kegunaan:

DecoratorUntukNilai yang dikembalikan
@st.cache_dataHasil komputasi/data: DataFrame, list, dict, hasil querysalinan baru tiap pemanggilan (aman dimodifikasi)
@st.cache_resourceObjek global yang di-share: engine DB, model ML, client APIobjek yang sama untuk semua user & sesi
import os
from sqlalchemy import create_engine, text

@st.cache_resource
def get_engine():
    return create_engine(os.environ["DATABASE_URL"], pool_pre_ping=True)

@st.cache_data(ttl="10m", show_spinner="Memuat data harga...")
def load_harian(kode_kota: str, komoditas_id: int, start, end) -> pd.DataFrame:
    q = text("""SELECT tanggal, harga FROM history_data_komoditas
                WHERE kode_kota = :k AND komoditas_id = :c
                  AND tanggal BETWEEN :s AND :e ORDER BY tanggal""")
    with get_engine().connect() as conn:
        df = pd.read_sql(q, conn, params={"k": kode_kota, "c": komoditas_id, "s": start, "e": end})
    return df.assign(tanggal=pd.to_datetime(df["tanggal"])).set_index("tanggal")

@st.cache_data(ttl=120)
def daftar_kota() -> list[str]:
    with get_engine().connect() as conn:
        return pd.read_sql(text("SELECT DISTINCT kode_kota FROM history_data_komoditas"), conn)["kode_kota"].tolist()

Aturan cache key: argumen fungsi di-hash. Argumen yang tidak bisa di-hash (mis. koneksi) beri awalan underscore — def load(_conn, kota) — agar dilewati. Setelah fetch data baru, buang cache dengan load_harian.clear() atau st.cache_data.clear().


5. Session State

st.session_state adalah dict per tab browser yang bertahan antar-rerun.

if "hasil_forecast" not in st.session_state:
    st.session_state.hasil_forecast = None

if st.button("Hitung"):
    st.session_state.hasil_forecast = {"metode": "DES", "mape": 1.86}

if st.session_state.hasil_forecast:
    st.json(st.session_state.hasil_forecast)

# widget dengan key otomatis tersimpan di session_state
st.selectbox("Kota", ["Kab. Malang", "Kota Surabaya"], key="kota_aktif")
st.write(st.session_state.kota_aktif)

6. Multipage App

Cara lama (masih didukung): folder pages/ di samping entrypoint. Nama file menentukan urutan dan label — begitulah skripsi saya disusun:

app/
├── Home.py
└── pages/
    ├── 0_Fetch_Data.py
    ├── 1_Analitik_Harian.py
    ├── 2_Agregasi_Bulanan.py
    ├── 3_Forecast.py
    └── 4_Compare_Kota.py

Cara yang disarankan sekarang: st.Page + st.navigation, yang memberi kontrol penuh atas judul, ikon, grup, dan posisi menu.

# streamlit_app.py (entrypoint)
import streamlit as st

st.set_page_config(page_title="Dashboard Harga", layout="wide")

pages = {
    "Data": [
        st.Page("views/home.py", title="Beranda", icon="🏠", default=True),
        st.Page("views/fetch.py", title="Fetch Data", icon="🔄"),
    ],
    "Analisis": [
        st.Page("views/harian.py", title="Analitik Harian", icon="📊"),
        st.Page("views/bulanan.py", title="Agregasi Bulanan", icon="📅"),
        st.Page("views/forecast.py", title="Forecast", icon="📈"),
    ],
}

pg = st.navigation(pages)          # position="sidebar" | "top" | "hidden"
pg.run()

Begitu st.navigation dipanggil, folder pages/ diabaikan, jadi pilih salah satu. Pindah halaman dari kode: st.switch_page("views/forecast.py"); tautan: st.page_link(...).


7. Konfigurasi, Secrets & Deploy

# .streamlit/config.toml
[theme]
primaryColor = "#0f766e"
base = "light"

[server]
headless = true
# .streamlit/secrets.toml — JANGAN di-commit
[connections.sql]
dialect = "mysql"
driver = "pymysql"
host = "db.example.internal"
port = 3306
database = "harga_komoditas"
username = "app_readonly"
password = "isi-di-server"

Dengan secrets di atas, conn = st.connection("sql") lalu conn.query("SELECT ...", ttl=600) sudah sekaligus melakukan caching.

Opsi deploy:

OpsiCocok untukCatatan
Streamlit Community Clouddemo publik dari repo GitHubsecrets diisi lewat dashboard; DB harus bisa diakses dari internet
VPS + systemd/DockerDB lokal/internalstreamlit run app.py --server.port 8501 --server.address 0.0.0.0 di belakang reverse proxy
Containerreproduciblelihat Docker Compose untuk Dev Environment

Streamlit memakai WebSocket, jadi reverse proxy (Nginx, Cloudflare Tunnel) harus meneruskan header Upgrade. Untuk chart yang lebih kustom di luar Python, bandingkan dengan pendekatan di Visualisasi Data dengan ECharts di React .


Kesalahan Umum

KesalahanAkibatPerbaikan
Query DB tanpa cacheSetiap klik = query ulang, dashboard lambat@st.cache_data(ttl=...)
Engine/model di st.cache_dataObjek di-pickle/disalin, bisa errorPakai @st.cache_resource
Memodifikasi objek dari cache_resourcePerubahan bocor ke semua userSalin dulu atau pakai cache_data
Mengandalkan st.button sebagai stateNilai True hanya satu rerun, hasil hilangSimpan hasil ke st.session_state
Dua widget identik tanpa keyDuplicateElementId errorBeri key unik
Layout wide diset di tiap halaman secara berbedaTampilan melompat saat pindah halamanSet st.set_page_config di entrypoint; panggilan berikutnya hanya menimpa parameter yang disebut
Mencampur pages/ dan st.navigationHalaman di pages/ hilangPilih satu pendekatan
Commit secrets.tomlKredensial bocor.gitignore; isi lewat secrets di server

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.

Comments