using Newtonsoft.Json.Linq;
using Sunny.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
// using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace HHVideoPlayer.Model.ChartModel
{
public class ChartLib
{
public static void loadXY(LineChart chartdb, string path)
{
List<string> Records = File.ReadLines(path).ToList();
foreach (var item in Records)
{
if (item.Contains(","))
{
DateTime x = DateTime.ParseExact(item.Split(",").First(), "HH:mm:ss.ffff", CultureInfo.InvariantCulture, DateTimeStyles.None);
chartdb.xDB.Add(x);
chartdb.yDB.Add(Convert.ToDouble(item.Split(",").Last()));
}
}
chartdb.startTime = chartdb.xDB[0];
chartdb.endTime = chartdb.xDB[chartdb.xDB.Count - 1];
//Debug.WriteLine($"{chartdb.xDB[0]} --{chartdb.xDB[chartdb.xDB.Count-1]}");
return;
}
}
public class ChartConfig
{
public string ChartName { get; set; } = "DefaultChart";
public string ChartTitle { get; set; } = "数据图表";
public Color SeriesColor { get; set; } = Color.Blue;
public bool ScrollBarEnable { get; set; } = true;
public int ScrollBarSize { get; set; } = 14;
public double ViewSize { get; set; } = 60; // 默认显示60秒
}
public class LineChart
{
public Chart Chart_ { get; }
public String ShohType = "OverView"; // Follow or OverView
public int _index = 0;
public List<DateTime> xDB = new List<DateTime>();
public List<Double> yDB = new List<Double>();
public DateTime startTime;
public DateTime endTime;
public System.Windows.Forms.Timer PlayTimer { get; }
private bool _isPlaying = false;
// 添加鼠标交互状态变量
private Point _dragStartPoint;
private bool _isDragging;
private double _startPosition;
private double _startSize;
// 1. 添加图表状态追踪变量
private bool _isViewValid = false;
private bool _isDataLoaded = false;
// 添加游标线和文本注解
private VerticalLineAnnotation _cursorLine;
private TextAnnotation _cursorText;
private bool _isCursorMoving = false;
private Point _cursorDragStart;
private double _cursorOriginalX;
public int PointIndex { get { return _index; } }
public string PointXValue
{
get{
if (_index <= xDB.Count - 1)return xDB[_index].ToString("HH:mm:ss.ff");
else return "";}
}
public string PointYValue { get {
if (_index <= yDB.Count - 1) return Math.Round(yDB[_index], 2).ToString();
else return "";
} }
public LineChart(Chart chart, string showtype = "Follow") {
Chart_ = chart;
_index = 0;
ShohType = showtype;
InitChart();
PlayTimer = new System.Windows.Forms.Timer { Interval = 1 }; // 20 FPS (50ms)
//Initialize();
InitializeCursorLine();
BindFunc();
}
private void BindFunc()
{
PlayTimer.Tick += (s, e) => PlayNextPoint();
// 绑定事件
// 启用鼠标事件
Chart_.MouseDown += Chart_MouseDown;
Chart_.MouseMove += Chart_MouseMove;
Chart_.MouseUp += Chart_MouseUp;
Chart_.MouseWheel += Chart_MouseWheel;
Chart_.MouseLeave += Chart_MouseLeave;
//Chart_.AxisViewChanged += OnAxisViewChanged;
//Chart_.AxisScrollBarClicked += OnScrollBarClicked;
//_cursorLine.PositionChanging += CursorLine_PositionChanging;
//_cursorLine.PositionChanged += CursorLine_PositionChanged;
}
public void InitChart()
{
CreateChart();
// 添加双缓冲支持(防止闪烁和空白)
typeof(Chart).InvokeMember("DoubleBuffered",
System.Reflection.BindingFlags.SetProperty |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance,
null, Chart_, new object[] { true });
// 添加安全刷新标记
_isViewValid = false;
_isDataLoaded = false;
// X轴滚动到最新位置
Chart_.ChartAreas[0].AxisX.ScaleView.Scroll(ScrollType.Last);
// timer.Interval = 10;
AddSeries("line", Color.Red);
InitializeCursorLine(); // 确保游标线被初始化
//Debug.WriteLine("InitChart");
}
private void InitializeCursorLine()
{
// 创建垂直游标线
_cursorLine = new VerticalLineAnnotation
{
Name = "CursorLine",
AxisX = Chart_.ChartAreas[0].AxisX,
AxisY = Chart_.ChartAreas[0].AxisY,
LineColor = Color.Red,
LineWidth = 2,
IsInfinitive = true, // 贯穿整个图表
AllowMoving = true,
ClipToChartArea = Chart_.ChartAreas[0].Name,
AnchorDataPoint = null
};
// 创建坐标文本显示
_cursorText = new TextAnnotation
{
Name = "CursorText",
AxisX = Chart_.ChartAreas[0].AxisX,
AxisY = Chart_.ChartAreas[0].AxisY,
ForeColor = Color.Red,
Font = new Font("Arial", 10, FontStyle.Bold),
AnchorAlignment = ContentAlignment.TopLeft,
AnchorOffsetX = 5,
AnchorOffsetY = 5,
Text = "Time: N/A\nValue: N/A",
Visible = false
};
// 添加游标线和文本到图表
Chart_.Annotations.Add(_cursorLine);
Chart_.Annotations.Add(_cursorText);
// 设置初始位置(如果数据已加载)
if (xDB != null && xDB.Count > 0)
{
_cursorLine.X = xDB[0].ToOADate();
UpdateCursorPosition(_cursorLine.X);
}
}
private void AddSeries(string seriersName, Color serierscolor)
{
Chart_.Series.Clear();
Series series = new Series(seriersName);
//图表类型 设置为样条图曲线
//series.ChartType = SeriesChartType.Line;
//FastLine
series.ChartType = SeriesChartType.FastLine;
series.IsXValueIndexed = true; // 自动设置索引
series.XValueType = ChartValueType.Time; // X轴为时间类型
// 数据点标记设置
series.MarkerStyle = MarkerStyle.Circle; // 圆形标记
series.MarkerColor = Color.Black; // 标记颜色
//设置点的大小
series.MarkerSize = 5;
//设置曲线的颜色
series.Color = serierscolor;
//设置曲线宽度
series.BorderWidth = 2; // 线宽
series.CustomProperties = "PointWidth=2"; // 点宽
series.IsValueShownAsLabel = true; // 显示数值标签
Chart_.Series.Add(series); // 添加到图表
//Debug.WriteLine("AddSerie");
}
private void CreateChart()
{
Chart_.ChartAreas.Clear();
//chart = new Chart();
//this.panel1.Controls.Add(chart);
// Chart_.Dock = DockStyle.Fill; // 填充到整个面板
Chart_.Visible = true;
ChartArea chartArea = new ChartArea()
{
CursorX = { IsUserEnabled = true , IsUserSelectionEnabled = true ,SelectionColor = Color.SkyBlue, IntervalType = DateTimeIntervalType.Auto
},
CursorY = { IsUserEnabled = true , IsUserSelectionEnabled = true ,SelectionColor = Color.SkyBlue, AutoScroll = true,
},
AxisX = {
ScaleView = {Zoomable=false ,MinSizeType = DateTimeIntervalType.Seconds},
LabelStyle = { Format = "HH:mm:ss.ff", Angle = -90 ,IsEndLabelVisible = true},
ScrollBar = { ButtonStyle = ScrollBarButtonStyles.All },
// 设置主网格线(透明度50%)Interval=0 自动
MajorGrid = { Enabled = true, LineColor = Color.FromArgb(50, Color.LightGray) ,Interval=0},
// 设置次网格线(更浅的透明度30%)
MinorGrid = {Enabled = true, LineColor = Color.FromArgb(30, Color.LightGray), LineDashStyle=ChartDashStyle.Dash },
Title = @"Time",IsLabelAutoFit = true, LabelAutoFitMinFontSize = 5, Interval = 10,
IntervalAutoMode = IntervalAutoMode.FixedCount, IntervalType = DateTimeIntervalType.NotSet,
TextOrientation = TextOrientation.Auto,
LineWidth = 2,LineColor = Color.Black, Enabled = AxisEnabled.True,Crossing = 0,
//IsAuto
},
AxisY ={
ScaleView = {Zoomable=false },
// 设置主网格线(透明度50%)
MajorGrid = { Enabled = true , LineColor = Color.FromArgb(50, Color.LightGray), Interval=0},
// 设置次网格线(更浅的透明度30%)
MinorGrid = {Enabled = true, LineColor = Color.FromArgb(30, Color.LightGray), LineDashStyle=ChartDashStyle.Dash },
Title = @"Value", LineWidth = 2, LineColor = Color.Black, Enabled = AxisEnabled.True
},
Position = {
Height = 85,Width = 95, X = 0,Y = 20
},
BackSecondaryColor = Color.White, //渐变背景色
BackGradientStyle = GradientStyle.TopBottom, //渐变方式
BackHatchStyle = ChartHatchStyle.None, //背景阴影
BorderDashStyle = ChartDashStyle.NotSet, //边框线样式
BorderWidth = 1, //边框宽度
BorderColor = Color.Black,
};
Chart_.ChartAreas.Add(chartArea);
Chart_.BackGradientStyle = GradientStyle.TopBottom;
//图表的边框颜色、
Chart_.BorderlineColor = Color.FromArgb(26, 59, 105);
//图表的边框线条样式
Chart_.BorderlineDashStyle = ChartDashStyle.Solid;
//图表边框线条的宽度
Chart_.BorderlineWidth = 2;
//图表边框的皮肤
Chart_.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
Chart_.Paint += (s, e) => ValidateChartState();
//Debug.WriteLine("CreateChart");
}
// 4. 添加图表状态验证方法
private void ValidateChartState()
{
if (!_isViewValid) // || !_isDataLoaded
{
//Debug.WriteLine("检测到无效视图状态,重新初始化图表...");
this.RefreshChart();
}
}
// 6. 增强RefreshChart方法
public void RefreshChart()
{
if (!_isDataLoaded) return;
try
{
Chart_.SuspendLayout();
var series = Chart_.Series[0];
var points = series.Points;
// 保存当前视图状态
var area = Chart_.ChartAreas[0];
double viewMin = area.AxisX.ScaleView.ViewMinimum;
double viewMax = area.AxisX.ScaleView.ViewMaximum;
bool isZoomed = !double.IsNaN(viewMin) && !double.IsNaN(viewMax);
// 清除并重新添加数据
series.Points.Clear();
foreach (var (x, y) in xDB.Zip(yDB, (dt, val) => (dt, val)))
{
series.Points.AddXY(x, y);
}
// 恢复视图状态
if (isZoomed)
{
area.AxisX.ScaleView.Zoom(viewMin, viewMax);
}
else
{
area.AxisX.ScaleView.ZoomReset();
}
_isViewValid = true;
}
finally
{
Chart_.ResumeLayout();
Chart_.Refresh();
}
}
public void PlayNextPoint(int pointsep = 10)
{
// 前置检查
if (Chart_.Series.Count <= 0 || xDB.Count != yDB.Count || _index >= xDB.Count)
return;
// 如果游标线未被拖动,则更新游标位置
if (!_isCursorMoving && ShohType == "Follow")
{
if (_index < xDB.Count)
{
double xValue = xDB[_index].ToOADate();
_cursorLine.X = xValue;
UpdateCursorPosition(xValue);
}
}
// 获取当前系列
Series series = Chart_.Series[0];
// 添加当前点(按间隔筛选)
if (_index % pointsep == 0)
{
DateTime nowTime = xDB[_index];
double value = yDB[_index];
series.Points.AddXY(nowTime, value);
_isViewValid = true;
// 视图滚动处理
UpdateViewForCurrentMode(pointsep = 10);
}
// 移动到下一个点
_index++;
// 重置检查(放在最后确保完成当前点处理)
if (_index >= xDB.Count)
{
_index = 0;
}
// Debug.WriteLine("CreateChart");
}
private void UpdateViewForCurrentMode(int pointsep =10)
{
var area = Chart_.ChartAreas[0];
var axisX = area.AxisX;
var series = Chart_.Series[0];
if (ShohType == "OverView")
{
//// 固定左侧为最早时间点
//DateTime minTime = xDB[0];
//axisX.Minimum = minTime.ToOADate();
//// 扩展右侧到最新时间点
//DateTime maxTime = xDB[_index];
//axisX.Maximum = maxTime.ToOADate();
//// 强制缩放以显示完整范围
//axisX.ScaleView.Zoom(axisX.Minimum, axisX.Maximum);
const int viewSize = 250;
int viewPoinr = _index / pointsep;
Chart_.ChartAreas[0].AxisX.ScaleView.Position = 1;
if (viewPoinr > viewSize)
{
double max = Chart_.ChartAreas[0].AxisX.Maximum;
max = (viewPoinr / viewSize + 1) * viewSize;
Chart_.ChartAreas[0].AxisX.Interval = max / viewSize;
Chart_.ChartAreas[0].AxisX.ScaleView.Size = viewPoinr * 1.1;
}
else
{
Chart_.ChartAreas[0].AxisX.ScaleView.Size = viewSize;
}
//axisX.ScaleView.Position = 0;
//axisX.IntervalAutoMode = IntervalAutoMode.VariableCount;
//if (series.Points.Count > 10)
//{
// axisX.Interval = series.Points.Count / 10.0;
//}
}
else if (ShohType == "Follow")
{
const int viewSize = 50;
axisX.Interval = 1;
axisX.ScaleView.Size = viewSize;
// 计算滚动位置(确保显示最新点)
if (series.Points.Count > viewSize)
{
axisX.ScaleView.Position = series.Points.Count - viewSize;
}
else
{
axisX.ScaleView.Position = 0;
}
}
}
public void ResetPlayback()
{
_index = 0;
if (Chart_.Series.Count > 0)
{
Chart_.Series[0].Points.Clear();
}
// 修复:重置视图范围
AdjustView();
// 重置游标线到起始位置
if (xDB.Count > 0)
{
_cursorLine.X = xDB[0].ToOADate();
UpdateCursorPosition(_cursorLine.X);
}
//Debug.WriteLine("ResetPlayback");
}
private void AdjustView0()
{
if (xDB.Count < 2) return;
var area = Chart_.ChartAreas[0];
DateTime minTime = xDB[0];
DateTime maxTime = xDB[xDB.Count -1];
area.AxisX.Minimum = startTime.ToOADate();
area.AxisX.Maximum = endTime.ToOADate(); // 使用索引器获取最后一个元素
area.AxisX.ScaleView.ZoomReset();
}
public void TogglePlay()
{
// 修复1:如果已经播放完毕,重置索引
if (_index >= xDB.Count)
{
ResetPlayback();
}
_isPlaying = !_isPlaying;
PlayTimer.Enabled = _isPlaying;
// 调试输出
Debug.WriteLine($"TogglePlay 播放状态: {_isPlaying}, 当前索引: {_index}, 数据总数: {xDB.Count}");
}
// 鼠标按下事件:开始拖动
private void Chart_MouseDown(object sender, MouseEventArgs e)
{
_isViewValid = true; // 标记视图有效
if (e.Button == MouseButtons.Left)
{
var chartArea = Chart_.ChartAreas[0];
_dragStartPoint = e.Location;
_startPosition = chartArea.AxisX.ScaleView.Position;
_startSize = chartArea.AxisX.ScaleView.Size;
_isDragging = true;
Chart_.Cursor = Cursors.SizeWE; // 设置水平拖动光标
}
//Debug.WriteLine($"Chart_MouseDown");
}
// 鼠标移动事件:处理拖动
private void Chart_MouseMove(object sender, MouseEventArgs e)
{
// 处理游标线移动
HandleCursorMovement(e);
// 仅当游标线未被拖动时处理
if (!_isCursorMoving && e.Button == MouseButtons.None)
{
// 获取鼠标位置的X坐标
var chartArea1 = Chart_.ChartAreas[0];
double xValue = chartArea1.AxisX.PixelPositionToValue(e.X);
// 更新游标线位置
_cursorLine.X = xValue;
UpdateCursorPosition(xValue);
}
_isViewValid = true; // 标记视图有效
if (!_isDragging) return;
var chartArea = Chart_.ChartAreas[0];
double positionChange = (e.X - _dragStartPoint.X) * chartArea.AxisX.ScaleView.Size / Chart_.Width;
double newPosition = _startPosition - positionChange;
// 保护边界
if (newPosition < chartArea.AxisX.Minimum)
newPosition = chartArea.AxisX.Minimum;
double maxPosition = chartArea.AxisX.Maximum - chartArea.AxisX.ScaleView.Size;
if (newPosition > maxPosition)
newPosition = maxPosition;
chartArea.AxisX.ScaleView.Position = newPosition;
//Debug.WriteLine($"Chart_MouseMove");
}
// 鼠标释放事件:结束拖动
private void Chart_MouseUp(object sender, MouseEventArgs e)
{
_isViewValid = true; // 标记视图有效
if (e.Button == MouseButtons.Left && _isDragging)
{
_isDragging = false;
Chart_.Cursor = Cursors.Default;
}
// 结束游标线拖动
if (_isCursorMoving && e.Button == MouseButtons.Left)
{
_isCursorMoving = false;
Chart_.Cursor = Cursors.Default;
}
//Debug.WriteLine($"Chart_MouseUpe");
}
// 鼠标离开时
private void Chart_MouseLeave(object sender, EventArgs e)
{
// 鼠标离开时隐藏游标线文本
if (!_isCursorMoving)
{
_cursorText.Visible = false;
}
}
// 鼠标滚轮事件:缩放功能
private void Chart_MouseWheel2(object sender, MouseEventArgs e)
{
_isViewValid = true; // 标记视图有效
var chartArea = Chart_.ChartAreas[0];
// 确保轴范围已初始化
if (double.IsNaN(chartArea.AxisX.Minimum) ||
double.IsNaN(chartArea.AxisX.Maximum) ||
xDB.Count == 0)
{
return; // 避免在未初始化状态下操作
}
try
{
// 获取当前视图范围(OLE自动化日期格式)
double currentPosition = chartArea.AxisX.ScaleView.Position;
double currentSize = chartArea.AxisX.ScaleView.Size;
// 计算鼠标在X轴上的比例位置
double mousePositionRatio = (double)e.X / Chart_.Width;
// 将鼠标位置转换为轴上的实际值
double axisPosition = chartArea.AxisX.Minimum +
mousePositionRatio * (chartArea.AxisX.Maximum - chartArea.AxisX.Minimum);
// 缩放因子
double zoomFactor = e.Delta > 0 ? 0.9 : 1.1;
// 计算新的大小(确保在合理范围内)
double newSize = Math.Max(0.001, Math.Min(
chartArea.AxisX.Maximum - chartArea.AxisX.Minimum,
currentSize * zoomFactor
));
// 计算新位置(确保在数据范围内)
double newPosition = Math.Max(
chartArea.AxisX.Minimum,
Math.Min(
chartArea.AxisX.Maximum - newSize,
axisPosition - (mousePositionRatio * newSize)
)
);
// 应用新的视图(使用安全的日期范围)
chartArea.AxisX.ScaleView.Zoom(
newPosition,
newPosition + newSize
);
}
catch (ArgumentException ex)
{
Debug.WriteLine($"缩放错误: {ex.Message}");
// 恢复安全视图
ResetViewToSafeRange();
}
//Debug.WriteLine($"Chart_MouseWheel2");
}
private void Chart_MouseWheel(object sender, MouseEventArgs e)
{
_isViewValid = true; // 标记视图有效
var chartArea = Chart_.ChartAreas[0];
// 0. 初始化检查:确保有数据和有效的轴范围
if (xDB.Count == 0) return;
// 1. 确保轴范围已初始化(处理未初始化情况)
if (double.IsNaN(chartArea.AxisX.Minimum) ||
double.IsNaN(chartArea.AxisX.Maximum))
{
// 初始化范围为完整数据集
chartArea.AxisX.Minimum = xDB[0].ToOADate();
chartArea.AxisX.Maximum = xDB[xDB.Count-1].ToOADate(); // C# 8.0索引语法
}
// 2. 可靠获取当前视图大小(三种备选方案)
double currentSize;
// 方案A:直接获取ScaleView.Size
if (!double.IsNaN(chartArea.AxisX.ScaleView.Size) &&
chartArea.AxisX.ScaleView.Size > 0)
{
currentSize = chartArea.AxisX.ScaleView.Size;
}
// 方案B:计算视图大小(适用于ScaleView.Size为NaN的情况)
else if (!double.IsNaN(chartArea.AxisX.ScaleView.ViewMinimum) &&
!double.IsNaN(chartArea.AxisX.ScaleView.ViewMaximum))
{
currentSize = chartArea.AxisX.ScaleView.ViewMaximum -
chartArea.AxisX.ScaleView.ViewMinimum;
}
// 方案C:使用完整数据范围(最后备选)
else
{
currentSize = chartArea.AxisX.Maximum - chartArea.AxisX.Minimum;
}
// 3. 计算鼠标位置对应的实际值
double mousePositionRatio = (double)e.X / Chart_.Width;
double axisMousePosition = chartArea.AxisX.Minimum +
mousePositionRatio * (chartArea.AxisX.Maximum - chartArea.AxisX.Minimum);
// 4. 计算缩放因子
double zoomFactor = e.Delta > 0 ? 0.9 : 1.1; // 滚轮向上=放大(0.9),向下=缩小(1.1)
// 5. 计算新视图大小(添加边界保护)
double minAllowedSize = (chartArea.AxisX.Maximum - chartArea.AxisX.Minimum) * 0.001; // 最小显示0.1%范围
double newSize = Math.Max(minAllowedSize, currentSize * zoomFactor);
newSize = Math.Min(newSize, chartArea.AxisX.Maximum - chartArea.AxisX.Minimum);
// 6. 计算新视图位置(保持鼠标点位置不变)
double newPosition = axisMousePosition - (mousePositionRatio * newSize);
// 7. 应用边界约束
newPosition = Math.Max(chartArea.AxisX.Minimum, newPosition);
newPosition = Math.Min(chartArea.AxisX.Maximum - newSize, newPosition);
// 8. 应用缩放
try
{
chartArea.AxisX.ScaleView.Zoom(newPosition, newPosition + newSize);
}
catch (ArgumentException ex)
{
//Debug.WriteLine($"缩放错误: {ex.Message}");
// 恢复安全范围
ResetViewToSafeRange();
}
}
// 添加安全范围重置方法
private void ResetViewToSafeRange()
{
if (xDB.Count == 0) return;
var area = Chart_.ChartAreas[0];
DateTime minDate = xDB.Min();
DateTime maxDate = xDB.Max();
area.AxisX.Minimum = minDate.ToOADate();
area.AxisX.Maximum = maxDate.ToOADate();
area.AxisX.ScaleView.ZoomReset();
//Debug.WriteLine($"ResetViewToSafeRange");
}
// 修改 AdjustView 方法(确保初始化范围)
private void AdjustView(bool fullView = false)
{
if (xDB.Count < 2) return;
var area = Chart_.ChartAreas[0];
// 3. 设置轴范围(使用OLE日期值)
double minDate = xDB[0].ToOADate();
double maxDate = xDB[xDB.Count - 1].ToOADate(); // 使用最后一个元素
// 4. 设置前清除缩放状态(关键步骤)
//area.AxisX.ScaleView.ZoomReset();
// 5. 设置最小最大值
area.AxisX.Minimum = minDate;
area.AxisX.Maximum = maxDate;
// 7. 强制刷新
Chart_.Refresh();
// 如果是完整视图模式,重置缩放
if (fullView)
{
area.AxisX.ScaleView.ZoomReset();
ShohType = "OverView";
}
//Debug.WriteLine($"AdjustView");
}
// 缩放时动态调整网格线 暂时不用, 用了有影响
private void OnAxisViewChanged(object sender, ViewEventArgs e)
{
// 当视图改变时确保范围安全
//var axis = e.Axis;
//if (axis.AxisName == AxisName.X)
//{
// SetSafeXRange(
// DateTime.FromOADate(e.NewPositionStart),
// DateTime.FromOADate(e.NewPositionEnd)
// );
//}
if (e.Axis.AxisName == AxisName.X || e.Axis.AxisName == AxisName.Y)
{
UpdateGridDensity();
}
}
// 根据缩放级别动态更新网格密度 暂时不用, 用了有影响
private void UpdateGridDensity()
{
var area = Chart_.ChartAreas[0];
var axisX = area.AxisX;
var axisY = area.AxisY;
// 计算X轴网格密度(基于可见范围)
double xRange = axisX.ScaleView.ViewMaximum - axisX.ScaleView.ViewMinimum;
double xInterval = CalculateOptimalInterval(xRange);
// 计算Y轴网格密度
double yRange = axisY.ScaleView.ViewMaximum - axisY.ScaleView.ViewMinimum;
double yInterval = CalculateOptimalInterval(yRange);
// 应用网格间隔
axisX.MajorGrid.Interval = xInterval;
axisX.MinorGrid.Interval = xInterval / 5; // 次要网格更密集
axisY.MajorGrid.Interval = yInterval;
axisY.MinorGrid.Interval = yInterval / 5;
// 更新标签间隔(防止标签重叠)
axisX.LabelStyle.Interval = xInterval * 2;
axisY.LabelStyle.Interval = yInterval * 2;
//Debug.WriteLine($"UpdateGridDensity");
}
// 计算最优网格间隔(基于对数缩放) 暂时不用, 用了有影响
private double CalculateOptimalInterval1(double range)
{
if (range <= 0) return 1;
// 计算数量级(10的幂)
int magnitude = (int)Math.Floor(Math.Log10(range));
double baseInterval = Math.Pow(10, magnitude);
// 确定最佳间隔(1, 2, 5序列)
if (range / baseInterval < 2)
return baseInterval / 5;
else if (range / baseInterval < 5)
return baseInterval / 2;
else if (range / baseInterval < 10)
return baseInterval;
else if (range / baseInterval < 20)
return baseInterval * 2;
else
return baseInterval * 5;
}
// 批量加载数据点 (替代原来的逐点添加)
public void LoadAllData()
{
try
{
_isDataLoaded = true;
_isViewValid = true;
this._index = 0;
Chart_.SuspendLayout();
Chart_.Series[0].Points.SuspendUpdates();
InitChart();
for (int i = 0; i < xDB.Count; i++)
{
// 批量添加点(每100点刷新一次)
if (i % 100 == 0 || i == xDB.Count - 1)
{
Chart_.Series[0].Points.AddXY(xDB[i], yDB[i]);
//Chart_.Refresh();
//Application.DoEvents(); // 允许UI处理事件
}
}
//this.RefreshChart();
_isDataLoaded = true;
_isViewValid = true;
}
finally
{
Chart_.Series[0].Points.ResumeUpdates();
Chart_.ResumeLayout();
Chart_.Refresh();
}
if (xDB.Count > 0)
{
_cursorLine.X = xDB[0].ToOADate();
UpdateCursorPosition(_cursorLine.X);
}
return;
}
// 手动实现游标线拖拽功能
private void HandleCursorMovement(MouseEventArgs e)
{
if (_cursorLine == null) return;
var chartArea = Chart_.ChartAreas[0];
double cursorX = _cursorLine.X;
double cursorY = chartArea.AxisY.PositionToValue(e.Y);
// 计算游标线在屏幕上的位置
int cursorScreenX = (int)chartArea.AxisX.ValueToPixelPosition(cursorX);
// 检查是否点击在游标线上(±5像素范围内)
if (Math.Abs(e.X - cursorScreenX) <= 5)
{
// 开始拖动
if (e.Button == MouseButtons.Left)
{
_isCursorMoving = true;
_cursorDragStart = e.Location;
_cursorOriginalX = cursorX;
Chart_.Cursor = Cursors.SizeWE;
}
}
// 拖动中
if (_isCursorMoving && e.Button == MouseButtons.Left)
{
double deltaX = chartArea.AxisX.PixelPositionToValue(e.X) -
chartArea.AxisX.PixelPositionToValue(_cursorDragStart.X);
double newX = _cursorOriginalX + deltaX;
// 确保游标线在数据范围内
double minX = chartArea.AxisX.Minimum;
double maxX = chartArea.AxisX.Maximum;
if (newX < minX) newX = minX;
if (newX > maxX) newX = maxX;
// 更新游标线位置
_cursorLine.X = newX;
UpdateCursorPosition(newX);
// 重绘图表
Chart_.Refresh();
}
}
private void CursorLine_PositionChanging(object sender, AnnotationPositionChangingEventArgs e)
{
_isCursorMoving = true;
UpdateCursorPosition(e.NewLocationX);
}
private void CursorLine_PositionChanged(object sender, EventArgs e)
{
_isCursorMoving = false;
}
private void UpdateCursorPosition(double xPosition)
{
if (xDB == null || yDB == null || xDB.Count == 0)
{
if (_cursorText != null) _cursorText.Visible = false;
return;
}
// 将X位置转换为DateTime
DateTime cursorTime = DateTime.FromOADate(xPosition);
// 查找最接近的数据点
int nearestIndex = FindNearestPointIndex(cursorTime);
if (nearestIndex < 0 || nearestIndex >= xDB.Count) return;
// 更新文本显示
string timeStr = xDB[nearestIndex].ToString("HH:mm:ss.fff");
double value = yDB[nearestIndex];
_cursorText.Text = $"Time: {timeStr}\nValue: {Math.Round(value, 2)}";
// 设置文本位置(在游标线右侧)
_cursorText.X = xPosition;
// 根据Y轴范围确定文本位置
var chartArea = Chart_.ChartAreas[0];
double yRange = chartArea.AxisY.Maximum - chartArea.AxisY.Minimum;
_cursorText.Y = chartArea.AxisY.Maximum - yRange * 0.05;
_cursorText.Visible = true;
}
private int FindNearestPointIndex(DateTime targetTime)
{
if (xDB == null || xDB.Count == 0) return -1;
// 二分查找最近的点
int low = 0;
int high = xDB.Count - 1;
while (low <= high)
{
int mid = (low + high) / 2;
int compare = targetTime.CompareTo(xDB[mid]);
if (compare == 0) return mid;
if (compare < 0) high = mid - 1;
else low = mid + 1;
}
// 确定最近的点
if (high < 0) return 0;
if (low >= xDB.Count) return xDB.Count - 1;
double diffLow = Math.Abs((targetTime - xDB[low]).TotalMilliseconds);
double diffHigh = Math.Abs((targetTime - xDB[high]).TotalMilliseconds);
return diffLow < diffHigh ? low : high;
}
private int FindNearestPointIndex2(DateTime targetTime)
{
if (xDB == null || xDB.Count == 0) return -1;
// 使用线性查找(对于大数据量,可以切换为二分查找)
int nearestIndex = 0;
double minDiff = double.MaxValue;
for (int i = 0; i < xDB.Count; i++)
{
double diff = Math.Abs((targetTime - xDB[i]).TotalMilliseconds);
if (diff < minDiff)
{
minDiff = diff;
nearestIndex = i;
}
}
return nearestIndex;
}
// 添加方法以编程方式设置游标位置
public void SetCursorPosition(DateTime time)
{
if (xDB == null || xDB.Count == 0) return;
_cursorLine.X = time.ToOADate();
UpdateCursorPosition(_cursorLine.X);
Chart_.Refresh();
}
// 添加方法获取当前游标位置
public DateTime GetCursorTime()
{
return DateTime.FromOADate(_cursorLine.X);
}
public double GetCursorValue()
{
int index = FindNearestPointIndex(GetCursorTime());
if (index < 0 || index >= yDB.Count) return double.NaN;
return yDB[index];
}
// 修改现有的AdjustView方法
// 修复视图调整方法 备用
private void AdjustView2(bool fullView = false)
{
if (xDB.Count == 0) return;
var area = Chart_.ChartAreas[0];
double minX = xDB[0].ToOADate();
double maxX = xDB.Last().ToOADate();
// 设置X轴范围
area.AxisX.Minimum = minX;
area.AxisX.Maximum = maxX;
if (fullView)
{
area.AxisX.ScaleView.ZoomReset();
ShohType = "OverView";
}
else if (ShohType == "Follow")
{
double viewSize = area.AxisX.ScaleView.Size;
if (double.IsNaN(viewSize) || viewSize <= 0)
viewSize = (maxX - minX) * 0.1; // 默认显示10%范围
double newPosition = Math.Max(minX, maxX - viewSize);
area.AxisX.ScaleView.Position = newPosition;
}
// 自动调整Y轴范围
double minY = yDB.Min();
double maxY = yDB.Max();
double margin = (maxY - minY) * 0.1; // 10%边距
area.AxisY.Minimum = minY - margin;
area.AxisY.Maximum = maxY + margin;
}
// 修复网格密度计算方法 用了有影响
private double CalculateOptimalInterval(double range)
{
if (range <= 0) return 1;
double logVal = Math.Log10(range);
int magnitude = (int)Math.Floor(logVal);
double fraction = logVal - magnitude;
double[] intervals = { 1, 2, 5, 10 };
double baseInterval = Math.Pow(10, magnitude);
// 选择最合适的间隔
if (fraction < 0.3) return baseInterval * intervals[0];
if (fraction < 0.6) return baseInterval * intervals[1];
return baseInterval * intervals[2];
}
// 8. 添加安全缩放重置方法 暂时无用
public void SafeResetView()
{
var area = Chart_.ChartAreas[0];
// 禁用自动范围
//area.AxisX.IsAutoMaximum = false;
//area.AxisX.IsAutoMinimum = false;
// 重置缩放视图
try
{
area.AxisX.ScaleView.ZoomReset();
}
catch
{
// 故障恢复
if (xDB.Count > 0)
{
double min = xDB[0].ToOADate();
double max = xDB[xDB.Count - 1].ToOADate();
// 设置最小最大值
area.AxisX.Minimum = min;
area.AxisX.Maximum = max;
// 强制设置视图
area.AxisX.ScaleView.Zoom(min, max);
}
}
_isViewValid = true;
}
}
}
- $exception {"“AnnotationCollection”中已存在名为“CursorLine”的图表元素。"} System.ArgumentException