import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch import matplotlib.patheffects as pe # ── colour palette ────────────────────────────────────────────────────────── C = { "admin": "#4A90D9", "user": "#2ECC71", "village": "#E74C3C", "announce": "#F39C12", "project": "#9B59B6", "division": "#1ABC9C", "discuss": "#E67E22", "other": "#95A5A6", "new": "#E74C3C", } GROUPS = { "Admin": (["AdminRole","Admin"], C["admin"]), "User": (["UserRole","User","TokenDeviceUser","UserLog"],C["user"]), "Village": (["Village","ColorTheme","BannerImage"], C["village"]), "Announcement": (["Announcement","AnnouncementMember", "AnnouncementFile"], C["announce"]), "Project": (["Project","ProjectMember","ProjectFile", "ProjectLink","ProjectTask","ProjectTaskFile", "ProjectTaskDetail"], C["project"]), "Division": (["Division","DivisionMember","DivisionProject", "DivisionProjectMember","DivisionProjectFile", "DivisionProjectLink", "DivisionProjectTask","DivisionProjectTaskFile", "DivisionProjectTaskDetail", "DivisionDisscussion","DivisionDisscussionComment", "DivisionDiscussionFile", "DivisionDocumentFolderFile","DivisionDocumentShare", "DivisionCalendar","DivisionCalendarReminder", "DivisionCalendarMember", "ContainerFileDivision"], C["division"]), "Discussion": (["Discussion","DiscussionMember", "DiscussionComment","DiscussionFile"], C["discuss"]), "Other": (["Group","Position","Notifications", "Subscribe","Setting","ContainerImage"], C["other"]), } NEW_MODELS = {"ProjectTaskFile", "DivisionProjectTaskFile"} # ── relations (src, dst, label) ───────────────────────────────────────────── RELATIONS = [ # Admin ("AdminRole","Admin","1-N"), # User ("UserRole","User","1-N"), ("Village","User","1-N"), ("Group","User","1-N"), ("Position","User","0..1-N"), # Village ("Village","Group","1-N"), ("Village","Announcement","1-N"), ("Village","Project","1-N"), ("Village","Division","1-N"), ("Village","Discussion","1-N"), ("Village","ColorTheme","1-N"), ("Village","BannerImage","1-N"), # Group ("Group","Position","1-N"), ("Group","Project","1-N"), ("Group","Division","1-N"), ("Group","AnnouncementMember","1-N"), ("Group","Discussion","1-N"), # Announcement ("Announcement","AnnouncementMember","1-N"), ("Announcement","AnnouncementFile","1-N"), ("Division","AnnouncementMember","1-N"), # Project ("Project","ProjectMember","1-N"), ("Project","ProjectFile","1-N"), ("Project","ProjectLink","1-N"), ("Project","ProjectTask","1-N"), ("ProjectTask","ProjectTaskDetail","1-N"), ("ProjectTask","ProjectTaskFile","1-N"), ("ProjectFile","ProjectTaskFile","1-N"), # Division ("Division","DivisionMember","1-N"), ("Division","DivisionProject","1-N"), ("DivisionProject","DivisionProjectMember","1-N"), ("DivisionProject","DivisionProjectFile","1-N"), ("DivisionProject","DivisionProjectLink","1-N"), ("DivisionProject","DivisionProjectTask","1-N"), ("DivisionProjectTask","DivisionProjectTaskDetail","1-N"), ("DivisionProjectTask","DivisionProjectTaskFile","1-N"), ("DivisionProjectFile","DivisionProjectTaskFile","1-N"), ("ContainerFileDivision","DivisionProjectFile","1-N"), ("Division","DivisionDisscussion","1-N"), ("DivisionDisscussion","DivisionDisscussionComment","1-N"), ("DivisionDisscussion","DivisionDiscussionFile","1-N"), ("Division","DivisionDocumentFolderFile","1-N"), ("DivisionDocumentFolderFile","DivisionDocumentShare","1-N"), ("Division","DivisionCalendar","1-N"), ("DivisionCalendar","DivisionCalendarReminder","1-N"), ("DivisionCalendar","DivisionCalendarMember","1-N"), # Discussion ("Discussion","DiscussionMember","1-N"), ("Discussion","DiscussionComment","1-N"), ("Discussion","DiscussionFile","1-N"), # Other ("User","Notifications","1-N"), ("User","Subscribe","1-1"), ("User","TokenDeviceUser","1-N"), ("User","UserLog","1-N"), ] # ── layout: group boxes ────────────────────────────────────────────────────── # (x, y, w, h) in data coordinates (canvas = 0..100 x 0..100) LAYOUT = { "Admin": ( 1, 88, 18, 10), "User": ( 1, 68, 22, 18), "Village": (26, 88, 22, 10), "Other": (51, 88, 22, 10), "Announcement": (76, 80, 22, 18), "Project": ( 1, 2, 38, 48), "Division": (41, 2, 38, 64), "Discussion": (81, 2, 17, 30), } def group_center(gname): x,y,w,h = LAYOUT[gname] return x+w/2, y+h/2 def model_pos(model): for gname,(models,_) in GROUPS.items(): if model in models: x,y,w,h = LAYOUT[gname] idx = models.index(model) n = len(models) cols = max(1, min(3, n)) rows = (n + cols - 1) // cols col = idx % cols row = idx // cols mx = x + 1.5 + col * (w-2) / cols my = y + h - 2.5 - row * (h-1.5) / rows return mx, my return 50, 50 # ── draw ───────────────────────────────────────────────────────────────────── fig, ax = plt.subplots(figsize=(28, 22)) ax.set_xlim(0, 100) ax.set_ylim(0, 100) ax.axis("off") fig.patch.set_facecolor("#F0F4F8") ax.set_facecolor("#F0F4F8") ax.set_title("ERD – Sistem Desa Mandiri", fontsize=20, fontweight="bold", color="#2C3E50", pad=14) # group boxes for gname, (models, color) in GROUPS.items(): x,y,w,h = LAYOUT[gname] rect = FancyBboxPatch((x,y), w, h, boxstyle="round,pad=0.3", linewidth=2, edgecolor=color, facecolor=color+"22") ax.add_patch(rect) ax.text(x+w/2, y+h-0.6, gname, ha="center", va="top", fontsize=9, fontweight="bold", color=color) # model nodes for gname, (models, color) in GROUPS.items(): for m in models: mx, my = model_pos(m) is_new = m in NEW_MODELS fc = "#FFECEC" if is_new else "white" ec = C["new"] if is_new else color lw = 2.5 if is_new else 1.5 node = FancyBboxPatch((mx-3.2, my-0.85), 6.4, 1.7, boxstyle="round,pad=0.2", linewidth=lw, edgecolor=ec, facecolor=fc) ax.add_patch(node) fw = "bold" if is_new else "normal" ax.text(mx, my, m, ha="center", va="center", fontsize=6.2, color="#2C3E50", fontweight=fw) # relations drawn = set() for src, dst, lbl in RELATIONS: key = tuple(sorted([src,dst])) sx, sy = model_pos(src) dx, dy = model_pos(dst) color = "#BDC3C7" is_new_rel = src in NEW_MODELS or dst in NEW_MODELS if is_new_rel: color = C["new"] ax.annotate("", xy=(dx,dy), xytext=(sx,sy), arrowprops=dict(arrowstyle="-|>", color=color, lw=1.5 if is_new_rel else 0.8, connectionstyle="arc3,rad=0.05")) if key not in drawn: mx2, my2 = (sx+dx)/2, (sy+dy)/2 ax.text(mx2, my2+0.4, lbl, ha="center", va="bottom", fontsize=4.5, color=color, alpha=0.85) drawn.add(key) # legend leg_items = [ mpatches.Patch(facecolor="#FFECEC", edgecolor=C["new"], linewidth=2, label="Model Baru"), mpatches.Patch(facecolor="white", edgecolor="#BDC3C7", label="Model Lama"), ] ax.legend(handles=leg_items, loc="lower right", fontsize=9, framealpha=0.9, edgecolor="#BDC3C7") out = "/Users/wibu04/Documents/Projects/sistem-desa-mandiri/erd.png" plt.savefig(out, dpi=150, bbox_inches="tight", facecolor=fig.get_facecolor()) plt.close() print("Saved:", out)