|
|
|
@ -262,6 +262,8 @@ class TrendWidgets(QWidget):
|
|
|
|
|
self.crosshair_visible = False
|
|
|
|
|
self.crosshair_point = None # 交叉点圆点
|
|
|
|
|
self.crosshair_text = None # 坐标文本标签
|
|
|
|
|
self._snapped_var = None # 当前吸附的变量名
|
|
|
|
|
self.snap_enabled = True # 是否启用智能吸附功能
|
|
|
|
|
|
|
|
|
|
# 连接鼠标事件
|
|
|
|
|
self.canvas.mpl_connect('motion_notify_event', self.onMouseMove)
|
|
|
|
@ -877,10 +879,17 @@ class TrendWidgets(QWidget):
|
|
|
|
|
if not (xlim[0] <= x <= xlim[1] and ylim[0] <= y <= ylim[1]):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 可选:吸附到最近的数据点
|
|
|
|
|
snap_x, snap_y = self.snapToNearestDataPoint(x, y)
|
|
|
|
|
if snap_x is not None and snap_y is not None:
|
|
|
|
|
x, y = snap_x, snap_y
|
|
|
|
|
# 智能吸附到最近的数据点(如果启用)
|
|
|
|
|
if self.snap_enabled:
|
|
|
|
|
snap_x, snap_y, snap_var_name = self.snapToNearestDataPoint(x, y)
|
|
|
|
|
if snap_x is not None and snap_y is not None:
|
|
|
|
|
x, y = snap_x, snap_y
|
|
|
|
|
# 存储吸附信息,用于显示
|
|
|
|
|
self._snapped_var = snap_var_name
|
|
|
|
|
else:
|
|
|
|
|
self._snapped_var = None
|
|
|
|
|
else:
|
|
|
|
|
self._snapped_var = None
|
|
|
|
|
|
|
|
|
|
# 更新垂直线位置
|
|
|
|
|
self.crosshair_v.set_data([x, x], [ylim[0], ylim[1]])
|
|
|
|
@ -890,8 +899,21 @@ class TrendWidgets(QWidget):
|
|
|
|
|
self.crosshair_h.set_data([xlim[0], xlim[1]], [y, y])
|
|
|
|
|
self.crosshair_h.set_visible(True)
|
|
|
|
|
|
|
|
|
|
# 更新交叉点圆点位置
|
|
|
|
|
# 更新交叉点圆点位置和样式
|
|
|
|
|
self.crosshair_point.set_offsets([[x, y]])
|
|
|
|
|
|
|
|
|
|
# 根据是否吸附到数据点改变圆点颜色
|
|
|
|
|
if hasattr(self, '_snapped_var') and self._snapped_var:
|
|
|
|
|
# 吸附状态:使用绿色表示精确定位
|
|
|
|
|
self.crosshair_point.set_color('#00AA00')
|
|
|
|
|
self.crosshair_point.set_edgecolors('white')
|
|
|
|
|
self.crosshair_point.set_sizes([64]) # 稍大一些
|
|
|
|
|
else:
|
|
|
|
|
# 普通状态:使用红色
|
|
|
|
|
self.crosshair_point.set_color('#FF4444')
|
|
|
|
|
self.crosshair_point.set_edgecolors('white')
|
|
|
|
|
self.crosshair_point.set_sizes([36]) # 正常大小
|
|
|
|
|
|
|
|
|
|
self.crosshair_point.set_visible(True)
|
|
|
|
|
|
|
|
|
|
# 更新坐标文本
|
|
|
|
@ -912,40 +934,70 @@ class TrendWidgets(QWidget):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def snapToNearestDataPoint(self, x, y):
|
|
|
|
|
"""吸附到最近的数据点(可选功能)"""
|
|
|
|
|
if not self.selectedVars:
|
|
|
|
|
return None, None
|
|
|
|
|
"""智能吸附到最近的数据点 - 多变量增强版"""
|
|
|
|
|
if not self.selectedVars or not self.multiVarData:
|
|
|
|
|
return None, None, None
|
|
|
|
|
|
|
|
|
|
min_distance = float('inf')
|
|
|
|
|
snap_x, snap_y = None, None
|
|
|
|
|
snap_var_name = None
|
|
|
|
|
|
|
|
|
|
# 获取当前坐标轴范围,用于归一化距离计算
|
|
|
|
|
xlim = self.axMain.get_xlim()
|
|
|
|
|
ylim = self.axMain.get_ylim()
|
|
|
|
|
x_range = xlim[1] - xlim[0]
|
|
|
|
|
y_range = ylim[1] - ylim[0]
|
|
|
|
|
|
|
|
|
|
# 遍历所有选中的变量,找到最近的数据点
|
|
|
|
|
for varName in self.selectedVars:
|
|
|
|
|
if varName in self.multiVarData:
|
|
|
|
|
data = self.multiVarData[varName]
|
|
|
|
|
if data['x'] and data['y']:
|
|
|
|
|
# 找到最接近的X坐标点
|
|
|
|
|
closest_idx = self.findClosestPoint(data['x'], x)
|
|
|
|
|
if closest_idx is not None and closest_idx < len(data['y']):
|
|
|
|
|
data_x = data['x'][closest_idx]
|
|
|
|
|
data_y = data['y'][closest_idx]
|
|
|
|
|
|
|
|
|
|
# 将datetime转换为matplotlib数值
|
|
|
|
|
if isinstance(data_x, datetime.datetime):
|
|
|
|
|
data_x_num = mdates.date2num(data_x)
|
|
|
|
|
else:
|
|
|
|
|
data_x_num = data_x
|
|
|
|
|
|
|
|
|
|
# 计算距离(在屏幕坐标系中)
|
|
|
|
|
distance = abs(data_x_num - x)
|
|
|
|
|
|
|
|
|
|
# 只有距离足够近才吸附(避免过度吸附)
|
|
|
|
|
if distance < min_distance and distance < 0.01: # 可调整吸附阈值
|
|
|
|
|
min_distance = distance
|
|
|
|
|
snap_x = data_x_num
|
|
|
|
|
snap_y = data_y
|
|
|
|
|
|
|
|
|
|
return snap_x, snap_y
|
|
|
|
|
# 遍历该变量的所有数据点
|
|
|
|
|
for i, (data_x, data_y) in enumerate(zip(data['x'], data['y'])):
|
|
|
|
|
try:
|
|
|
|
|
# 将datetime转换为matplotlib数值
|
|
|
|
|
if isinstance(data_x, datetime.datetime):
|
|
|
|
|
data_x_num = mdates.date2num(data_x)
|
|
|
|
|
else:
|
|
|
|
|
data_x_num = data_x
|
|
|
|
|
|
|
|
|
|
# 计算归一化的欧几里得距离
|
|
|
|
|
# 归一化是为了让X轴(时间)和Y轴(数值)的距离具有可比性
|
|
|
|
|
dx_norm = (data_x_num - x) / x_range if x_range != 0 else 0
|
|
|
|
|
dy_norm = (data_y - y) / y_range if y_range != 0 else 0
|
|
|
|
|
distance = (dx_norm ** 2 + dy_norm ** 2) ** 0.5
|
|
|
|
|
|
|
|
|
|
# 使用可配置的吸附阈值
|
|
|
|
|
snap_threshold = getattr(self, 'snap_threshold', 0.05) # 默认5%的屏幕距离
|
|
|
|
|
|
|
|
|
|
if distance < min_distance and distance < snap_threshold:
|
|
|
|
|
min_distance = distance
|
|
|
|
|
snap_x = data_x_num
|
|
|
|
|
snap_y = data_y
|
|
|
|
|
snap_var_name = varName
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
# 跳过有问题的数据点
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
return snap_x, snap_y, snap_var_name
|
|
|
|
|
|
|
|
|
|
def toggleSnapToDataPoints(self, enabled=None):
|
|
|
|
|
"""切换智能吸附功能"""
|
|
|
|
|
if enabled is None:
|
|
|
|
|
self.snap_enabled = not self.snap_enabled
|
|
|
|
|
else:
|
|
|
|
|
self.snap_enabled = enabled
|
|
|
|
|
|
|
|
|
|
print(f"智能吸附功能: {'启用' if self.snap_enabled else '禁用'}")
|
|
|
|
|
return self.snap_enabled
|
|
|
|
|
|
|
|
|
|
def setSnapThreshold(self, threshold):
|
|
|
|
|
"""设置吸附阈值(0.01-0.2之间)"""
|
|
|
|
|
self.snap_threshold = max(0.01, min(0.2, threshold))
|
|
|
|
|
print(f"吸附阈值设置为: {self.snap_threshold:.3f}")
|
|
|
|
|
return self.snap_threshold
|
|
|
|
|
|
|
|
|
|
def _delayed_crosshair_draw(self):
|
|
|
|
|
"""延迟绘制十字标线,避免频繁更新"""
|
|
|
|
@ -958,15 +1010,17 @@ class TrendWidgets(QWidget):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def formatCoordinateText(self, x, y):
|
|
|
|
|
"""格式化坐标文本显示"""
|
|
|
|
|
"""格式化坐标文本显示 - 增强版,显示吸附信息"""
|
|
|
|
|
try:
|
|
|
|
|
# 格式化时间(X轴)
|
|
|
|
|
if isinstance(x, (int, float)):
|
|
|
|
|
# 将matplotlib数值转换为datetime
|
|
|
|
|
time_obj = mdates.num2date(x)
|
|
|
|
|
time_str = time_obj.strftime('%H:%M:%S')
|
|
|
|
|
date_str = time_obj.strftime('%m-%d')
|
|
|
|
|
else:
|
|
|
|
|
time_str = str(x)
|
|
|
|
|
date_str = ""
|
|
|
|
|
|
|
|
|
|
# 格式化数值(Y轴)
|
|
|
|
|
if isinstance(y, (int, float)):
|
|
|
|
@ -979,7 +1033,17 @@ class TrendWidgets(QWidget):
|
|
|
|
|
else:
|
|
|
|
|
value_str = str(y)
|
|
|
|
|
|
|
|
|
|
return f"时间: {time_str}\n数值: {value_str}"
|
|
|
|
|
# 构建基础文本
|
|
|
|
|
if date_str:
|
|
|
|
|
coord_text = f"时间: {date_str} {time_str}\n数值: {value_str}"
|
|
|
|
|
else:
|
|
|
|
|
coord_text = f"时间: {time_str}\n数值: {value_str}"
|
|
|
|
|
|
|
|
|
|
# 如果吸附到了数据点,显示变量信息
|
|
|
|
|
if hasattr(self, '_snapped_var') and self._snapped_var:
|
|
|
|
|
coord_text += f"\n📍 {self._snapped_var}"
|
|
|
|
|
|
|
|
|
|
return coord_text
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return f"X: {x:.3f}\nY: {y:.3f}"
|
|
|
|
|