动态图表在办公自动化中常用于跟踪持续变化的数据,如工单增长、性能指标。核心原理是在同一个画布上循环刷新数据,而非重新生成图片。本文基于Python的Matplotlib和FuncAnimation,实现从随机模拟到真实CSV/Excel数据源的动态折线图,并加入移动平均和阈值告警,让图表具备监控能力。
- import random
- import time
- from collections import deque
- import matplotlib.pyplot as plt
- from matplotlib.animation import FuncAnimation
- plt.rcParams["font.sans-serif"] = ["SimHei"]
- plt.rcParams["axes.unicode_minus"] = False
- MAX_POINTS = 60
- x_data = deque(maxlen=MAX_POINTS)
- y_data = deque(maxlen=MAX_POINTS)
- start_time = time.time()
- fig, ax = plt.subplots(figsize=(10, 5))
- (line,) = ax.plot([], [], marker="o", linewidth=2)
- ax.set_title("动态折线图:实时数据更新")
- ax.set_xlabel("时间(秒)")
- ax.set_ylabel("指标值")
- ax.grid(True, linestyle="--", alpha=0.35)
- def get_latest_value():
- return random.randint(50, 150)
- def update(frame):
- current_time = int(time.time() - start_time)
- current_value = get_latest_value()
- x_data.append(current_time)
- y_data.append(current_value)
- line.set_data(list(x_data), list(y_data))
- ax.set_xlim(max(0, current_time - MAX_POINTS), current_time + 1)
- ax.set_ylim(min(y_data) - 5, max(y_data) + 5)
- return (line,)
- ani = FuncAnimation(fig, update, interval=1000, blit=True)
- plt.tight_layout()
- plt.show()
复制代码
以上代码演示了最小可用版:用deque限制缓存长度(60个点),避免数据无限增长导致卡顿。实际使用时只需替换get_latest_value()为读取文件、API或系统指标的代码。
接下来加入移动平均和阈值告警,使图表具备业务判断能力。
- import random, time
- from collections import deque
- import matplotlib.pyplot as plt
- from matplotlib.animation import FuncAnimation
- plt.rcParams["font.sans-serif"] = ["SimHei"]
- plt.rcParams["axes.unicode_minus"] = False
- MAX_POINTS = 60
- WINDOW = 8
- THRESHOLD = 140
- x_data = deque(maxlen=MAX_POINTS)
- y_data = deque(maxlen=MAX_POINTS)
- ma_data = deque(maxlen=MAX_POINTS)
- start_time = time.time()
- fig, ax = plt.subplots(figsize=(10,5))
- (line,) = ax.plot([],[], marker="o", linewidth=2, label="实时值")
- (ma_line,) = ax.plot([],[], linestyle="--", linewidth=2, label="移动平均")
- (th_line,) = ax.plot([],[], linestyle=":", linewidth=2, label="阈值线")
- ax.set_title("动态图表:实时值 + 移动平均 + 阈值线")
- ax.set_xlabel("时间(秒)")
- ax.set_ylabel("指标值")
- ax.grid(True, linestyle="--", alpha=0.35)
- ax.legend(loc="upper left")
- def get_latest_value():
- base = random.randint(80,130)
- if random.random() < 0.08:
- base += random.randint(10,40)
- return base
- def moving_average(values, window):
- values = list(values)
- if len(values) < window:
- return sum(values)/len(values)
- return sum(values[-window:])/window
- def update(frame):
- current_time = int(time.time() - start_time)
- current_value = get_latest_value()
- x_data.append(current_time)
- y_data.append(current_value)
- ma_data.append(moving_average(y_data, WINDOW))
- line.set_data(list(x_data), list(y_data))
- ma_line.set_data(list(x_data), list(ma_data))
- if x_data:
- th_line.set_data([x_data[0], x_data[-1]], [THRESHOLD, THRESHOLD])
- ax.set_xlim(max(0, current_time - MAX_POINTS), current_time + 1)
- ax.set_ylim(min(min(y_data), THRESHOLD)-10, max(max(y_data), THRESHOLD)+10)
- if current_value >= THRESHOLD:
- ax.set_title(f"触发阈值告警:当前值={current_value}")
- else:
- ax.set_title("动态图表:实时值 + 移动平均 + 阈值线")
- return (line, ma_line, th_line)
- ani = FuncAnimation(fig, update, interval=1000, blit=True)
- plt.tight_layout()
- plt.show()
复制代码
该版本中,移动平均线使趋势更平滑,阈值线(140)触发时标题自动显示告警。阈值需根据业务数据合理设定,例如CPU使用率、服务响应时间等。
在真实办公场景中,数据往往来自Excel或CSV文件。以下代码演示轮询读取CSV并刷新图表,适合轻量监控部署:
- import time
- from collections import deque
- from pathlib import Path
- import pandas as pd
- import matplotlib.pyplot as plt
- from matplotlib.animation import FuncAnimation
- plt.rcParams["font.sans-serif"] = ["SimHei"]
- plt.rcParams["axes.unicode_minus"] = False
- CSV_PATH = Path("metrics.csv")
- MAX_POINTS = 120
- x_data = deque(maxlen=MAX_POINTS)
- y_data = deque(maxlen=MAX_POINTS)
- fig, ax = plt.subplots(figsize=(10,5))
- (line,) = ax.plot([],[], marker="o", linewidth=2)
- ax.set_title("动态更新:轮询读取CSV最新数据")
- ax.set_xlabel("时间")
- ax.set_ylabel("指标值")
- ax.grid(True, linestyle="--", alpha=0.35)
- def read_latest_from_csv():
- if not CSV_PATH.exists():
- return None, None
- df = pd.read_csv(CSV_PATH)
- if df.empty:
- return None, None
- last = df.iloc[-1]
- return str(last["timestamp"]), float(last["value"])
- def update(frame):
- timestamp, value = read_latest_from_csv()
- if timestamp is None:
- return (line,)
- # 避免重复追加同一时间戳
- if len(x_data) > 0 and x_data[-1] == timestamp:
- return (line,)
- x_data.append(timestamp)
- y_data.append(value)
- line.set_data(list(range(len(x_data))), list(y_data))
- ax.set_xlim(0, max(10, len(x_data)))
- ax.set_ylim(min(y_data)-5, max(y_data)+5)
- # 设置时间标签并旋转
- ax.set_xticks(range(len(x_data)))
- ax.set_xticklabels(list(x_data), rotation=30, ha="right")
- return (line,)
- ani = FuncAnimation(fig, update, interval=2000, blit=True)
- plt.tight_layout()
- plt.show()
复制代码
注意:CSV文件被其他程序写入时可能出现读取冲突。建议在脚本中加入异常捕获,判断重复时间戳和空数据。正式环境可优先使用CSV作为中间文件,避免直接读写xlsx导致的占用风险。
动态图表的交付方式有三种:本地窗口展示(调试/演示)、导出GIF(博客/复盘)、Web看板(团队共享)。导出GIF示例:- pip install pillow
- ani.save("动态图表.gif", writer="pillow", fps=2)
复制代码
需要验证的要点:数据源更新后图表追加新点;无更新时不重复;y轴范围覆盖最新值;移动平均跟随变化;阈值告警正确触发。常见问题包括中文乱码(配置SimHei字体)、动画越来越卡(用deque限制点数)、CSV不刷新(检查文件是否写入新行)、blit=True异常(可改为False调试)。
总之,动态图表的核心链路为:数据源 → 刷新机制 → 图表对象 → 分析规则 → 交付方式。建议学习顺序:先用随机数跑通FuncAnimation,再加入移动平均和阈值,然后替换成真实CSV/Excel,最后考虑导出GIF或制作Web看板。 |