Files
JH/exports/create_erd_dark_image.py

136 lines
6.0 KiB
Python

from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
OUT = Path('/home/hyein/jh-mh/장헌산업/exports')
PNG = OUT / 'work_data_erd_dark.png'
W, H = 1400, 1900
img = Image.new('RGB', (W, H), '#17191a')
d = ImageDraw.Draw(img)
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 17)
font_b = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 18)
font_t = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 24)
font_s = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 13)
BG = '#17191a'
CARD = '#1e2224'
CARD2 = '#202426'
LINE = '#4b5256'
LINE2 = '#697176'
TEXT = '#e6ecef'
MUTED = '#9aa4aa'
PK = '#f4d06f'
FK = '#8cc7ff'
ACCENT = '#d0d6d9'
entities = {
'member': {
'xy': (150, 360), 'w': 250,
'fields': ['PK MemberNo', 'korName', 'teamName', 'rankName', 'groupCode', 'rankCode', 'isRetired']
},
'dallyproject': {
'xy': (185, 720), 'w': 290,
'fields': ['PK id', 'FK MemberNo', 'WorkDate', 'EntryPCode', 'EntryTime', 'LeaveTime', 'OverTime', 'TotalHours', 'RegularHours', 'OvertimeHours']
},
'work_calendar_day': {
'xy': (70, 1190), 'w': 315,
'fields': ['PK memberNo + workDate', 'korName', 'teamName', 'rankName', 'sqlHours', 'sqlProjectCodes', 'siteCount', 'siteProjectCodes', 'siteWorkTexts', 'hasSql', 'hasSite']
},
'work_calendar_detail': {
'xy': (515, 920), 'w': 310,
'fields': ['PK id', 'source: sql / site', 'FK memberNo', 'workDate', 'projectCode', 'projectName', 'workText', 'jobType', 'hours', 'regularHours', 'overtimeHours', 'personCount']
},
'project_alias': {
'xy': (655, 70), 'w': 315,
'fields': ['PK projectCode', 'shortName', 'bridge/project name']
},
'site_worksheet_record': {
'xy': (680, 430), 'w': 340,
'fields': ['PK projectCode + workDate', 'PK memberNo + korName', 'FK memberNo', 'jobType', 'workText', 'note', 'personCount']
},
'site_worksheet_worker_cache': {
'xy': (960, 170), 'w': 360,
'fields': ['PK projectCode + workDate', 'PK korName + jobType', 'workText', 'note', 'personCount', 'syncedAt']
},
'site_worksheet_day_sync': {
'xy': (930, 760), 'w': 310,
'fields': ['PK projectCode + workDate', 'syncedAt']
},
'site_worksheet_menu_sync': {
'xy': (860, 1090), 'w': 335,
'fields': ['PK projectCode + workDate', 'PK selMenu', '2 = normal worksheet', '3 = tension/temp works', 'syncedAt']
},
}
# compute heights
for e in entities.values():
e['h'] = 52 + len(e['fields']) * 26 + 18
def rounded_box(x, y, w, h, name, fields):
d.rounded_rectangle((x, y, x+w, y+h), radius=6, fill=CARD, outline='#3c4246', width=2)
d.rectangle((x, y, x+w, y+42), fill=CARD2)
d.line((x, y+42, x+w, y+42), fill='#3c4246', width=1)
d.text((x+16, y+12), name, font=font_b, fill=TEXT)
yy = y + 58
for f in fields:
color = TEXT
if f.startswith('PK'):
color = PK
elif f.startswith('FK'):
color = FK
d.text((x+18, yy), f, font=font_s, fill=color)
yy += 26
def pt(name, side):
e = entities[name]
x, y = e['xy']; w = e['w']; h = e['h']
if side == 'l': return (x, y+h//2)
if side == 'r': return (x+w, y+h//2)
if side == 't': return (x+w//2, y)
if side == 'b': return (x+w//2, y+h)
def poly(points, label=None, label_pos=None):
d.line(points, fill=LINE, width=2, joint='curve')
for x, y in (points[0], points[-1]):
d.ellipse((x-4, y-4, x+4, y+4), fill=ACCENT)
if label:
x, y = label_pos or points[len(points)//2]
tw = d.textlength(label, font=font_s)
d.rounded_rectangle((x-tw/2-7, y-12, x+tw/2+7, y+12), radius=4, fill=BG, outline='#363b3f')
d.text((x-tw/2, y-9), label, font=font_s, fill=MUTED)
# title
d.text((70, 35), 'Work Data ERD', font=font_t, fill=TEXT)
d.text((70, 68), 'SQL work records + ERP site worksheet integration', font=font_s, fill=MUTED)
# relation lines behind cards
poly([pt('member','b'), (275, 665), pt('dallyproject','t')], 'MemberNo', (300, 650))
poly([pt('dallyproject','b'), (330, 1085), (520, 1085), pt('work_calendar_detail','l')], 'source=sql', (450, 1072))
poly([pt('work_calendar_detail','l'), (425, 1100), (425, 1350), pt('work_calendar_day','r')], 'daily summary', (426, 1280))
poly([pt('member','l'), (55, 460), (55, 1370), pt('work_calendar_day','l')], 'MemberNo', (56, 820))
poly([pt('member','r'), (520, 500), (520, 560), pt('site_worksheet_record','l')], 'MemberNo', (520, 538))
poly([pt('project_alias','b'), (815, 330), pt('site_worksheet_record','t')], 'projectCode', (810, 340))
poly([pt('project_alias','r'), (1070, 170), pt('site_worksheet_worker_cache','l')], 'projectCode', (1050, 145))
poly([pt('site_worksheet_worker_cache','b'), (1050, 405), (1000, 405), pt('site_worksheet_record','r')], 'match name/date/project', (1010, 410))
poly([pt('site_worksheet_record','b'), (740, 880), pt('work_calendar_detail','t')], 'source=site', (715, 850))
poly([pt('site_worksheet_day_sync','t'), (1005, 650), (1080, 400), pt('site_worksheet_worker_cache','b')], 'project+date fetched', (1040, 645))
poly([pt('site_worksheet_menu_sync','t'), (935, 1030), (1085, 400), pt('site_worksheet_worker_cache','b')], 'menu 2/3 fetched', (950, 1035))
poly([pt('project_alias','l'), (560, 185), (560, 825), pt('dallyproject','r')], 'EntryPCode', (560, 420))
poly([pt('project_alias','b'), (750, 780), pt('work_calendar_detail','r')], 'projectName', (755, 760))
# draw cards
for name, e in entities.items():
x, y = e['xy']
rounded_box(x, y, e['w'], e['h'], name, e['fields'])
# subtle legend
d.rounded_rectangle((70, 1740, 1270, 1845), radius=8, fill='#1c2022', outline='#343a3e')
d.text((90, 1765), 'Flow', font=font_b, fill=TEXT)
d.text((90, 1795), 'dallyproject -> work_calendar_detail -> work_calendar_day', font=font_s, fill=MUTED)
d.text((90, 1820), 'site_worksheet_worker_cache -> site_worksheet_record -> work_calendar_detail/day', font=font_s, fill=MUTED)
img.save(PNG)
print(PNG)