""" matplotlib 기반 차트 위젯. stats_view에서 주간/월간 추세를 시각화. matplotlib 미설치 시 ImportError 안내 라벨로 fallback. """ from typing import List, Tuple from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel from PyQt5.QtCore import Qt try: from matplotlib.figure import Figure from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas import matplotlib matplotlib.rcParams['font.family'] = ['Malgun Gothic', 'Apple SD Gothic Neo', 'sans-serif'] matplotlib.rcParams['axes.unicode_minus'] = False _MPL = True except ImportError: _MPL = False class _Fallback(QWidget): """matplotlib 미설치 시 안내.""" def __init__(self, message: str): super().__init__() layout = QVBoxLayout() label = QLabel(message) label.setAlignment(Qt.AlignCenter) label.setWordWrap(True) label.setStyleSheet("color: #888; padding: 20px;") layout.addWidget(label) self.setLayout(layout) def make_chart_widget(parent=None) -> QWidget: """차트가 그려질 빈 캔버스 위젯. matplotlib 없으면 fallback.""" if not _MPL: return _Fallback("차트 표시에는 matplotlib가 필요합니다.\npip install matplotlib") widget = QWidget(parent) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) fig = Figure(figsize=(5, 3), dpi=100, tight_layout=True) canvas = FigureCanvas(fig) layout.addWidget(canvas) widget.setLayout(layout) widget._figure = fig widget._canvas = canvas return widget def draw_daily_hours(widget: QWidget, records: List[dict]) -> None: """일별 근무시간 막대 그래프. Args: widget: make_chart_widget()로 만든 위젯 records: [{date, total_hours, overtime_minutes}, ...] """ if not getattr(widget, '_figure', None): return fig = widget._figure fig.clear() if not records: ax = fig.add_subplot(111) ax.text(0.5, 0.5, '기록 없음', ha='center', va='center', transform=ax.transAxes) widget._canvas.draw() return dates = [r['date'][5:] for r in records] # MM-DD만 hours = [r.get('total_hours', 0) or 0 for r in records] overtimes = [(r.get('overtime_minutes', 0) or 0) / 60 for r in records] base = [max(h - o, 0) for h, o in zip(hours, overtimes)] ax = fig.add_subplot(111) ax.bar(dates, base, label='정상', color='#4a90e2') ax.bar(dates, overtimes, bottom=base, label='연장', color='#ff6b6b') ax.set_ylabel('시간') ax.legend(loc='upper left', fontsize=8) ax.tick_params(axis='x', labelrotation=45, labelsize=8) ax.grid(axis='y', alpha=0.3) widget._canvas.draw() def draw_weekday_avg(widget: QWidget, records: List[dict]) -> None: """요일별 평균 근무시간 막대 그래프.""" if not getattr(widget, '_figure', None): return fig = widget._figure fig.clear() from datetime import datetime as _dt weekday_totals = [0.0] * 7 weekday_counts = [0] * 7 for r in records: try: d = _dt.strptime(r['date'], '%Y-%m-%d') except (ValueError, TypeError): continue weekday_totals[d.weekday()] += r.get('total_hours', 0) or 0 weekday_counts[d.weekday()] += 1 avg = [(t / c) if c else 0 for t, c in zip(weekday_totals, weekday_counts)] labels = ['월', '화', '수', '목', '금', '토', '일'] ax = fig.add_subplot(111) colors = ['#4a90e2'] * 5 + ['#ff6b6b'] * 2 # 주말 강조 ax.bar(labels, avg, color=colors) ax.set_ylabel('평균 시간') ax.set_title('요일별 평균 근무시간') ax.grid(axis='y', alpha=0.3) widget._canvas.draw()