import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import numpy as np plt.rcParams['font.family'] = 'sans-serif' for f in ['WenQuanYi Micro Hei', 'Noto Sans CJK SC', 'SimHei', 'DejaVu Sans']: try: plt.rcParams['font.sans-serif'] = [f] break except: continue plt.rcParams['axes.unicode_minus'] = False # Data from query labels = ['0分钟\n(无数据)', '5-10', '10-15', '15-20', '20-25', '25-30', '30-35', '35-40', '40-50', '50-60', '60-90', '90+'] counts = [14990, 8945, 33817, 21566, 11563, 5411, 2412, 1210, 957, 385, 313, 153] pcts = [14.7, 8.8, 33.2, 21.2, 11.4, 5.3, 2.4, 1.2, 0.9, 0.4, 0.3, 0.2] fig, ax = plt.subplots(figsize=(16, 7)) x = np.arange(len(labels)) colors = ['#E74C3C' if i == 0 else plt.cm.Blues(0.3 + 0.7*i/len(labels)) for i in range(len(labels))] bars = ax.bar(x, counts, color=colors, edgecolor='white', linewidth=0.5) # Labels for bar, v, p in zip(bars, counts, pcts): if v > 200: ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + max(counts)*0.015, f'{v:,}\n({p}%)', ha='center', fontsize=9, color='#333', fontweight='bold') ax.set_xticks(x) ax.set_xticklabels(labels, fontsize=10) ax.set_ylabel('完成次数', fontsize=13) ax.set_title('最近3个月(2026.03-05)完成一个课时学习时长分布', fontsize=16, fontweight='bold') ax.grid(axis='y', alpha=0.2, linestyle='--') ax.set_ylim(0, max(counts) * 1.22) # Summary box stats_text = ( f'总完成: 101,724次\n' f'平均: 14.8分钟\n' f'中位数: 13.8分钟\n' f'排除空数据平均: 17.4分钟\n' f'无时长数据: 14,990次(14.7%)' ) ax.text(0.98, 0.95, stats_text, transform=ax.transAxes, fontsize=11, verticalalignment='top', horizontalalignment='right', bbox=dict(boxstyle='round', facecolor='#FFFDE7', alpha=0.9, edgecolor='#CCC'), fontfamily='monospace') # Key insight annotation ax.annotate('峰值: 10-15分钟\n占33.2%', xy=(2, 33817), xytext=(5, 28000), arrowprops=dict(arrowstyle='->', color='#333', lw=1.5), fontsize=11, color='#333', fontweight='bold', bbox=dict(boxstyle='round,pad=0.3', facecolor='#FFF2CC', alpha=0.8)) # Distribution curve from scipy import interpolate nonzero_x = x[1:] # skip 0-min nonzero_counts = np.array(counts[1:], dtype=float) smooth_x = np.linspace(nonzero_x[0], nonzero_x[-1], 200) try: tck = interpolate.splrep(nonzero_x, nonzero_counts, s=5000) smooth_y = interpolate.splev(smooth_x, tck) ax.plot(smooth_x, smooth_y, '-', color='#C0392B', linewidth=2, alpha=0.6, label='平滑趋势') ax.legend(fontsize=10) except: pass fig.text(0.5, 0.01, '统计口径: 最近3个月 play_status=1 的课时完成记录, 时长=单次完成所有组件 interval_time 之和/60000(分钟)', ha='center', fontsize=9, color='#888') plt.tight_layout(rect=[0, 0.04, 1, 0.95]) plt.savefig('/root/.openclaw/workspace/output/single_duration_3m.png', dpi=150, bbox_inches='tight') print('Saved.')