import customtkinter as ctk
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class Menu31App(ctk.CTk):
def __init__(self):
super().__init__()
# Konfigurasi Window Utama
self.title("Simulator Anatomi Kriteria KHGT 5-8 (Modul 31)")
self.geometry("1100x700")
self.minsize(900, 600)
# Set Tema Aplikasi
ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")
self.setup_ui()
self.update_kriteria_batas_plot() # Panggil plot pertama kali saat aplikasi dibuka
def setup_ui(self):
# Konfigurasi Grid Layout (Sidebar Kiri & Main Frame Kanan)
self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
# ==========================================
# 1. SIDEBAR (PANEL KIRI UNTUK SLIDER INPUT)
# ==========================================
self.sidebar = ctk.CTkFrame(self, width=320, corner_radius=0)
self.sidebar.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
ctk.CTkLabel(self.sidebar, text="SIMULATOR KHGT", font=("Segoe UI", 18, "bold"), text_color="#00E5FF").pack(pady=(20, 10))
# Container untuk Kontrol Manual
frame_kb_opt = ctk.CTkFrame(self.sidebar, fg_color="#212121", corner_radius=8)
frame_kb_opt.pack(fill="x", padx=15, pady=10)
ctk.CTkLabel(frame_kb_opt, text="PARAMETER (MANUAL)", font=("Segoe UI", 12, "bold")).pack(anchor="w", padx=15, pady=(15, 5))
# --- Slider Elongasi ---
ctk.CTkLabel(frame_kb_opt, text="Elongasi / Jarak Sudut (°):", font=("Segoe UI", 12)).pack(anchor="w", padx=15, pady=(10, 0))
self.var_kb_elongasi = ctk.DoubleVar(value=8.5)
self.slider_elongasi = ctk.CTkSlider(frame_kb_opt, from_=0.0, to=15.0, variable=self.var_kb_elongasi, command=self.on_slider_change)
self.slider_elongasi.pack(fill="x", padx=15, pady=(5, 5))
self.lbl_kb_elongasi = ctk.CTkLabel(frame_kb_opt, text="8.50°", font=("Consolas", 14, "bold"), text_color="#FFD54F")
self.lbl_kb_elongasi.pack(pady=(0, 10))
# --- Slider Ketinggian ---
ctk.CTkLabel(frame_kb_opt, text="Ketinggian / Altitude (°):", font=("Segoe UI", 12)).pack(anchor="w", padx=15, pady=(10, 0))
self.var_kb_ketinggian = ctk.DoubleVar(value=6.0)
self.slider_ketinggian = ctk.CTkSlider(frame_kb_opt, from_=-5.0, to=15.0, variable=self.var_kb_ketinggian, command=self.on_slider_change)
self.slider_ketinggian.pack(fill="x", padx=15, pady=(5, 5))
self.lbl_kb_ketinggian = ctk.CTkLabel(frame_kb_opt, text="6.00°", font=("Consolas", 14, "bold"), text_color="#00E676")
self.lbl_kb_ketinggian.pack(pady=(0, 20))
ctk.CTkLabel(self.sidebar, text="Geser slider di atas untuk melihat\nperubahan posisi hilal dan statusnya\nsecara real-time pada grafik.",
font=("Segoe UI", 11, "italic"), text_color="#9E9E9E").pack(pady=20)
# ==========================================
# 2. MAIN FRAME (PANEL KANAN UNTUK MATPLOTLIB)
# ==========================================
self.main_frame = ctk.CTkFrame(self, fg_color="transparent")
self.main_frame.grid(row=0, column=1, sticky="nsew", padx=10, pady=10)
# Setup Figure Matplotlib (Theme Dark Mode)
self.fig_kb, self.ax_kb = plt.subplots(figsize=(8, 5.5), facecolor='#101010')
self.ax_kb.set_facecolor('#101010')
elongation_max = 15
altitude_max = 15
altitude_min = -5
# --- Pembuatan Zona Warna Parameter ---
self.ax_kb.fill_between([0, 7], altitude_min, altitude_max, color='#FF5252', alpha=0.3, label='Batas Danjon (< 7°)')
self.ax_kb.fill_between([7, elongation_max], altitude_min, 5, color='#FFB74D', alpha=0.3, label='Bias Senja (< 5°)')
self.ax_kb.fill_between([7, 8], 5, altitude_max, color='#FFF176', alpha=0.3, label='Margin Keamanan (7°-8°)')
self.ax_kb.fill_between([8, elongation_max], 5, altitude_max, color='#69F0AE', alpha=0.3, label='Kriteria KHGT Terpenuhi')
# --- Garis-garis batas ---
self.ax_kb.axhline(y=0, color='gray', linestyle='-', linewidth=1.5) # Garis Nol (Horizon)
self.ax_kb.axvline(x=8, color='#00E676', linestyle='--', linewidth=2)
self.ax_kb.axhline(y=5, color='#00E676', linestyle='--', linewidth=2)
self.ax_kb.axvline(x=7, color='#FF1744', linestyle=':', linewidth=2)
# --- Teks Info Zona di dalam Grafik ---
self.ax_kb.text(3.5, 6.5, 'MUSTAHIL\n(Secara Fisis)', ha='center', va='center', fontsize=12, fontweight='bold', color='#FF5252')
self.ax_kb.text(11.5, 2.5, 'TIDAK TERLIHAT\n(Tertutup Cahaya Senja)', ha='center', va='center', fontsize=10, fontweight='bold', color='#FFB74D')
self.ax_kb.text(7.5, 10, 'RAWAN', ha='center', va='center', fontsize=10, fontweight='bold', color='#FFF176', rotation=90)
self.ax_kb.text(11.5, 10, 'RUKYAT BIL \'ILMI', ha='center', va='center', fontsize=10, fontweight='bold', color='#00E676')
# --- Inisialisasi Titik Bintang (Hilal) ---
self.titik_hilal_kb, = self.ax_kb.plot([8.5], [6.0], 'w*', markersize=18, zorder=5, markeredgecolor='black', label='Titik Simulasi Real-time')
# --- Label Mengambang di dekat bintang ---
self.teks_info_bintang = self.ax_kb.text(8.5, 6.6, 'Eln: 8.50° | Alt: 6.00°',
color='white', fontsize=11, fontweight='bold',
ha='center', va='bottom', zorder=6,
bbox=dict(boxstyle="round,pad=0.4", fc="#1E1E1E", ec="#00E5FF", alpha=0.9))
# --- Konfigurasi Sumbu (Axis) ---
self.ax_kb.set_xlim(0, elongation_max)
self.ax_kb.set_ylim(altitude_min, altitude_max)
self.ax_kb.set_xlabel('Elongasi / Jarak Sudut Bulan-Matahari (Derajat)', fontsize=12, color='white')
self.ax_kb.set_ylabel('Ketinggian Hilal / Altitude (Derajat)', fontsize=12, color='white')
self.ax_kb.set_title('Anatomi Kriteria KHGT 5-8', fontsize=14, fontweight='bold', pad=15, color='white')
self.ax_kb.grid(True, linestyle='--', alpha=0.2, color='white')
self.ax_kb.tick_params(colors='white')
# --- Legend ---
legend = self.ax_kb.legend(loc='upper left', fontsize='9', facecolor='#1E1E1E', edgecolor='#333333')
for text in legend.get_texts():
text.set_color("white")
self.fig_kb.tight_layout()
# --- Integrasi Grafik Matplotlib ke Tkinter Frame ---
self.canvas_kb = FigureCanvasTkAgg(self.fig_kb, master=self.main_frame)
self.canvas_kb.get_tk_widget().pack(fill="both", expand=True, padx=5, pady=(0, 10))
# --- Status Box Teks Kesimpulan di bawah grafik ---
status_frame = ctk.CTkFrame(self.main_frame, fg_color="#1A1A1A", corner_radius=8, border_width=1, border_color="#333333")
status_frame.pack(fill="x", padx=5, pady=5)
self.kb_status_label = ctk.CTkLabel(status_frame, text="STATUS: -", font=("Consolas", 16, "bold"))
self.kb_status_label.pack(pady=(15, 5))
self.kb_alasan_label = ctk.CTkLabel(status_frame, text="ALASAN: -", font=("Segoe UI", 13), justify="center", wraplength=700)
self.kb_alasan_label.pack(pady=(0, 15), padx=20)
# ==========================================
# 3. FUNGSI LOGIKA (ENGINE INTERAKTIF)
# ==========================================
def on_slider_change(self, value):
"""Fungsi jembatan saat slider digeser"""
self.lbl_kb_elongasi.configure(text=f"{self.var_kb_elongasi.get():.2f}°")
self.lbl_kb_ketinggian.configure(text=f"{self.var_kb_ketinggian.get():.2f}°")
self.update_kriteria_batas_plot()
def update_kriteria_batas_plot(self):
"""Memperbarui grafik dan penjelasan teks secara real-time"""
if not hasattr(self, 'ax_kb'): return
x = self.var_kb_elongasi.get()
y = self.var_kb_ketinggian.get()
# Update koordinat bintang di grafik
self.titik_hilal_kb.set_xdata([x])
self.titik_hilal_kb.set_ydata([y])
# Logika pintar agar teks melayang tidak terpotong jika menyentuh atap/bawah layar
offset_y = -1.2 if y > 13 else 0.6
self.teks_info_bintang.set_position((x, y + offset_y))
self.teks_info_bintang.set_text(f"Eln: {x:.2f}° | Alt: {y:.2f}°")
# --------------------------------------------------------
# EVALUASI FISIS DAN OPTIS PENENTUAN STATUS
# --------------------------------------------------------
if x < 7:
status_msg = "STATUS: MUSTAHIL (Berada di Bawah Batas Danjon - Fisis)"
color_msg = '#FF5252'
penjelasan_msg = ("ALASAN: Sabit bulan secara fisis belum terbentuk. Pada sudut elongasi di bawah 7 derajat,\n"
"bayangan pegunungan di permukaan bulan memutus pantulan cahaya matahari (Limit Danjon).\n"
"Pengamatan hilal pada area ini mustahil dilakukan secara optik maupun fisis.")
elif x >= 7 and y < 5:
status_msg = "STATUS: TIDAK TERLIHAT (Kalah Terang oleh Bias Senja - Optis)"
color_msg = '#FFB74D'
penjelasan_msg = ("ALASAN: Sabit mungkin sudah terbentuk secara fisis, namun ketinggian di bawah 5 derajat\n"
"membuat hilal kalah terang oleh cahaya senja (Twilight Glare) di atmosfer bumi.\n"
"Kontras cahaya sabit tidak mencukupi untuk mengalahkan bias cahaya ufuk barat.")
elif 7 <= x < 8 and y >= 5:
status_msg = "STATUS: RAWAN (Masuk dalam Margin Keamanan Observasi)"
color_msg = '#FFF176'
penjelasan_msg = ("ALASAN: Berada di zona transisi. Meskipun ketinggian di atas ufuk sudah cukup,\n"
"elongasi di bawah 8 derajat dianggap belum aman dari ketidakteraturan permukaan bulan.\n"
"Kriteria KHGT menetapkan 8 derajat sebagai batas margin keamanan (Safety Margin).")
else:
status_msg = "STATUS: RUKYAT BIL 'ILMI (Hilal Sah & Awal Bulan Serentak!)"
color_msg = '#00E676'
penjelasan_msg = ("ALASAN: Memenuhi kriteria global Istanbul 2016. Pada posisi ini, hilal memiliki elongasi\n"
"yang cukup untuk membentuk sabit utuh dan ketinggian yang cukup untuk mengalahkan bias\n"
"cahaya senja. Secara saintifik terbukti wujud (Rukyat bil 'Ilmi).")
# Perbarui teks di panel bawah
self.kb_status_label.configure(text=status_msg, text_color=color_msg)
self.kb_alasan_label.configure(text=penjelasan_msg)
# Render ulang kanvas
self.canvas_kb.draw_idle()
if __name__ == "__main__":
app = Menu31App()
app.mainloop()