Files
CapMachine/CapMachine.Wpf/Views/RealTimeChartView.xaml.cs
2024-07-12 10:39:55 +08:00

627 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Arction.Wpf.Charting;
using Arction.Wpf.Charting.Annotations;
using Arction.Wpf.Charting.Axes;
using Arction.Wpf.Charting.SeriesXY;
using Arction.Wpf.Charting.Views.ViewXY;
using CapMachine.Model;
using CapMachine.Wpf.Models;
using CapMachine.Wpf.PrismEvent;
using CapMachine.Wpf.Services;
using CapMachine.Wpf.ViewModels;
using ImTools;
using NLog;
using Prism.Events;
using System.Text;
using System.Timers;
using System.Windows.Controls;
using System.Windows.Media;
namespace CapMachine.Wpf.Views
{
/// <summary>
/// RealTimeChartView.xaml 的交互逻辑
/// </summary>
public partial class RealTimeChartView : UserControl
{
/// <summary>
/// 定时器
/// </summary>
static System.Timers.Timer CurTimer { get; set; }
private IEventAggregator _EventAggregator { get; set; }
public MachineRtDataService MachineRtDataService { get; }
public IFreeSql FreeSql { get; }
/// <summary>
/// 曲线配置
/// </summary>
public ChartConfig ChartConfigInfo { get; set; } = new ChartConfig();
/// <summary>
/// 实例化函数
/// </summary>
/// <param name="eventAggregator"></param>
/// <param name="machineRtDataService"></param>
public RealTimeChartView(IEventAggregator eventAggregator, MachineRtDataService machineRtDataService, IFreeSql freeSql)
{
InitializeComponent();
LoadLighingChart();
LvChartRtValue.ItemsSource = ListChartRtSeries;
//事件服务
_EventAggregator = eventAggregator;
MachineRtDataService = machineRtDataService;
FreeSql = freeSql;
_EventAggregator.GetEvent<ChartRtEvent>().Subscribe(GetChartRtEvent);
_EventAggregator.GetEvent<ChartRtActionEvent>().Subscribe(ReLoadData);
}
/// <summary>
/// 重新加载数据
/// 更新曲线和Y轴设置
/// </summary>
/// <param name="type"></param>
/// <exception cref="NotImplementedException"></exception>
private void ReLoadData(ChartActionType type)
{
//
switch (type)
{
case ChartActionType.ReLoadChart:
var ChartSelectedData = FreeSql.Select<ConfigChartSelect>().Include(a => a.ConfigChartYAxis).ToList();
LoadChartConfigSelect(ChartSelectedData);
break;
default:
break;
}
}
/// <summary>
/// 获取曲线的实时值
/// 数据是发布过来
/// </summary>
/// <param name="value"></param>
/// <exception cref="NotImplementedException"></exception>
private void GetChartRtEvent(List<ChartRtValue> RtData)
{
ListChartRtValue = RtData;
//UpdateLightningChartData();
App.Current.Dispatcher.Invoke(() =>
{
foreach (var item in ListChartRtValue)
{
var Data = ListChartRtSeries.Find(a => a.Name == item.Name);
if (Data != null)
{
Data.AddValue(item.Value);
Data.Value = item.Value;
}
}
});
}
/// <summary>
/// 时间序列
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CurTimer_Elapsed(object? sender, ElapsedEventArgs e)
{
}
#region
#endregion
#region 线
/// <summary>
/// 当前曲线配置信息
/// </summary>
public List<ConfigChartSelect> CurListConfigChartSelect { get; set; }
/// <summary>
/// 加载曲线配置信息
/// </summary>
private void LoadChartConfigSelect(List<ConfigChartSelect> configChartSelects)
{
//从配置入手对比目前的状态,针对配置新增
foreach (var itemConfig in configChartSelects)
{
//曲线增加时
var data = ListChartRtSeries.FindFirst(a => a.Name == itemConfig.Name);
if (data == null)
{
ListChartRtSeries.Add(new ChartRtSeries(lightningChart1, ChartConfigInfo, ListChartRtSeries, itemConfig));
}
else
{
//修改
}
}
////从目前的状态对比配置,针对删除项目
//foreach (var itemSeries in ListChartRtSeries)
//{
// var data = configChartSelects.FindFirst(a => a.Name == itemSeries.Name);
// if (data == null)
// {
// //查找删除的系列的Y轴是否还有其他的配置使用
// var configChartYAxis = configChartSelects.Find(a => a.ConfigChartYAxis.Name == itemSeries.ChartAxisY.Name);
// if (configChartYAxis == null)
// {
// //Y轴没有使用则删除
// itemSeries.DeleteAxisY();
// }
// //未找到,则删除
// ListChartRtSeries.Remove(itemSeries);
// }
// else
// {
// //存在就不需要操作
// }
//}
//从目前的状态对比配置,针对删除项目
for (int i = 0; i < ListChartRtSeries.Count; i++)
{
var data = configChartSelects.FindFirst(a => a.Name == ListChartRtSeries[i].Name);
if (data == null)
{
//查找删除的系列的Y轴是否还有其他的配置使用-先删除Y轴
var configChartYAxis = configChartSelects.Find(a => a.ConfigChartYAxis.Name == ListChartRtSeries[i].ChartAxisY.Name);
if (configChartYAxis == null)
{
//Y轴没有使用则删除
ListChartRtSeries[i].DeleteAxisY();
}
//未找到,则删除 曲线实例删除
ListChartRtSeries[i].DeleteSeries();
}
else
{
//存在就不需要操作
}
}
}
/// <summary>
/// 实时曲线数据集合
/// </summary>
public List<ChartRtValue> ListChartRtValue { get; set; }
/// <summary>
/// 曲线数据系列集合
/// </summary>
public List<ChartRtSeries> ListChartRtSeries { get; set; } = new List<ChartRtSeries>();
/// <summary>
/// Chart图表X轴的跨度-秒
/// </summary>
private int ChartXDateTimeRange = 3600;
/// <summary>
/// X轴滚动
/// </summary>
private bool ScrollEnable = true;
/// <summary>
/// 更新图表信息
/// </summary>
private void UpdateLightningChartData()
{
//timeStamp = timeStamp.AddSeconds(-60);
foreach (var item in lightningChart1.ViewXY.PointLineSeries)
{
//Disable updates, to prevent several extra refreshes
lightningChart1.BeginUpdate();
//Array for 1 point
SeriesPoint[] points = new SeriesPoint[1];
//Convert 'Now' to X value
var _previousX = lightningChart1.ViewXY.XAxes[0].DateTimeToAxisValue(DateTime.Now);
points[0].X = _previousX;
points[0].Y = GetChartRtValue(item.Title.Text);
//Add the new point into end of first PointLineSeries
item.AddPoints(points, false);
item.DeletePointsBeforeX(_previousX - ChartXDateTimeRange);
//item.DeletePointsBeforeX(_previousX - 100);
if (ScrollEnable)
{
lightningChart1.ViewXY.XAxes[0].ScrollPosition = _previousX;
}
//Allow updates again, and update
lightningChart1.EndUpdate();
}
}
/// <summary>
/// 根据名称获取曲线所需的数据
/// </summary>
private double GetChartRtValue(string Name)
{
try
{
var Data = ListChartRtValue.Find(a => a.Name == Name);
if (Data != null)
{
return Data.Value;
}
return 0;
}
catch (Exception ex)
{
//logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
return 0;
}
finally
{
}
}
/// <summary>
/// 曲线个数
/// </summary>
private int MainRealTimeChartSeriesCount = 0;
/// <summary>
/// LightingChart图标的初始化
/// </summary>
private void LoadLighingChart()
{
//Disable rendering, strongly recommended before updating chart properties
lightningChart1.BeginUpdate();
//Reduce memory usage and increase performance. Destroys out-scrolled data.
lightningChart1.ViewXY.DropOldSeriesData = false;
//多个Y曲线摆放形式
lightningChart1.ViewXY.AxisLayout.YAxesLayout = YAxesLayout.Layered;
//网格带
lightningChart1.ViewXY.AxisLayout.AxisGridStrips = XYAxisGridStrips.X;
//y轴的在左边还是右边
lightningChart1.ViewXY.AxisLayout.YAxisAutoPlacement = YAxisAutoPlacement.AllLeft;
//lightningChart1.Parent = this.panel1;
//lightningChart1.Name = "Temperature measurement _chart";
lightningChart1.Title.Text = "实时曲线";
//lightningChart1.ViewXY.
//lightningChart1.Dock = DockStyle.Fill;
//清除Y轴系列
//lightningChart1.ViewXY.YAxes.Clear();
var InitAxisY = new AxisY(lightningChart1.ViewXY);
InitAxisY.Visible = false;
InitAxisY.ZoomingEnabled = false;
lightningChart1.ViewXY.YAxes.Add(InitAxisY);
//Configure x-axis
AxisX xAxis = lightningChart1.ViewXY.XAxes[0];
xAxis.ValueType = AxisValueType.DateTime;
xAxis.Title.Text = "时间";
xAxis.AutoFormatLabels = true;
xAxis.LabelsTimeFormat = "MM/dd \n HH:mm.ss";
//xAxis.LabelsTimeFormat = "dd/MM/yyyy\nHH:mm.ss";
xAxis.LabelsAngle = 45;
//xAxis.SteppingInterval = 0.1;
//xAxis.ScrollingGap = 15;
//xAxis.AllowScaling = true;
xAxis.ScrollMode = XAxisScrollMode.Scrolling;
xAxis.ZoomingEnabled = true;
//Convert DateTime values to axis values
DateTime now = DateTime.Now;
double minX = xAxis.DateTimeToAxisValue(now);
double maxX = xAxis.DateTimeToAxisValue(now.AddMinutes(60));
xAxis.SetRange(minX, maxX);
//DateTime now = DateTime.Now;
//xAxis.SetDateTimeRange(now,now.AddMinutes(60));
//光标数据
//Add an annotation to show the cursor values
//AnnotationXY cursorValueDisplay = new AnnotationXY();
AnnotationXY cursorValueDisplay = new AnnotationXY(lightningChart1.ViewXY, lightningChart1.ViewXY.XAxes[0], lightningChart1.ViewXY.YAxes[0]);
cursorValueDisplay.Style = AnnotationStyle.RoundedCallout;
cursorValueDisplay.LocationCoordinateSystem = CoordinateSystem.RelativeCoordinatesToTarget;
cursorValueDisplay.LocationRelativeOffset.X = 130;
cursorValueDisplay.LocationRelativeOffset.Y = -100;
cursorValueDisplay.Sizing = AnnotationXYSizing.Automatic;
//cursorValueDisplay.TextStyle.Font = new WpfFont("Lucida console", 10f, FontStyle.Regular);
cursorValueDisplay.TextStyle.Color = System.Windows.Media.Color.FromRgb(0, 0, 0);
cursorValueDisplay.Text = "";
cursorValueDisplay.Fill.Color = System.Windows.Media.Color.FromRgb(255, 255, 255);
cursorValueDisplay.Fill.GradientColor = System.Windows.Media.Color.FromRgb(120, 120, 120);
cursorValueDisplay.BorderVisible = false;
cursorValueDisplay.AllowTargetMove = false;
cursorValueDisplay.Visible = false;
lightningChart1.ViewXY.Annotations.Add(cursorValueDisplay);
//Add cursor
LineSeriesCursor cursor = new LineSeriesCursor(lightningChart1.ViewXY, lightningChart1.ViewXY.XAxes[0]);
lightningChart1.ViewXY.LineSeriesCursors.Add(cursor);
cursor.PositionChanged += cursor_PositionChanged;
cursor.ValueAtXAxis = lightningChart1.ViewXY.XAxes[0].DateTimeToAxisValue(DateTime.Now.AddMinutes(30));
cursor.LineStyle.Color = System.Windows.Media.Color.FromRgb(200, 200, 200);
cursor.SnapToPoints = false;
cursor.TrackPoint.Color1 = System.Windows.Media.Color.FromRgb(255, 255, 255);
cursor.Style = CursorStyle.PointTracking;
lightningChart1.Loaded += LightningChart1_Loaded;
lightningChart1.SizeChanged += LightningChart1_SizeChanged;
//lightningChart1.Resize += new EventHandler(_chart_Resize);
////清除Y轴系列
//lightningChart1.ViewXY.YAxes.Clear();
//Don't show legendbox
lightningChart1.ViewXY.LegendBoxes[0].Visible = true;
lightningChart1.ViewXY.LegendBoxes[0].Position = LegendBoxPositionXY.TopCenter;
//Allow chart rendering
lightningChart1.EndUpdate();
}
private void LightningChart1_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
{
UpdateCursorResult();
}
private void LightningChart1_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
UpdateCursorResult();
}
private void cursor_PositionChanged(object sender, PositionChangedEventArgs e)
{
e.CancelRendering = true;
UpdateCursorResult();
}
/// <summary>
/// Update cursor texts.
/// </summary>
private void UpdateCursorResult()
{
bool showNextToCursor = true;
//Disable rendering, strongly recommended before updating chart properties
lightningChart1.BeginUpdate();
//Get cursor
LineSeriesCursor cursor = lightningChart1.ViewXY.LineSeriesCursors[0];
//Get annotation
AnnotationXY cursorValueDisplay = lightningChart1.ViewXY.Annotations[0];
//Set annotation target. The location is relative to target.
//Use graph bottom as target Y value.
float targetYCoord = (float)lightningChart1.ViewXY.GetMarginsRect().Bottom;
double y;
lightningChart1.ViewXY.YAxes[0].CoordToValue(targetYCoord, out y);
cursorValueDisplay.TargetAxisValues.X = cursor.ValueAtXAxis;
cursorValueDisplay.TargetAxisValues.Y = y;
double seriesYValue = 0;
StringBuilder sb = new StringBuilder();
int seriesIndex = 1;
string channelStringFormat = "{0}: {1}";
bool labelVisible = false;
bool accurateValue = false;
string value = "";
foreach (PointLineSeries pointLineSeries in lightningChart1.ViewXY.PointLineSeries)
{
//show series titles and cursor values in them, on the right side of the chart,
//if cursor values are not shown next to the cursor in an annotation
pointLineSeries.Title.Visible = !showNextToCursor;
bool resolved = false;
value = "";
if (accurateValue)
resolved = SolveValueAccurate(pointLineSeries, cursor.ValueAtXAxis, out seriesYValue);
else
resolved = SolveValueCoarse(pointLineSeries, cursor.ValueAtXAxis, out seriesYValue);
AxisY axisY = lightningChart1.ViewXY.YAxes[pointLineSeries.AssignYAxisIndex];
if (resolved)
{
labelVisible = true;
//value = string.Format(channelStringFormat, seriesIndex, seriesYValue.ToString("0.000"), axisY.Units.Text);
value = string.Format(channelStringFormat, pointLineSeries.Title.Text, seriesYValue.ToString("0.000"));
}
else
{
//value = string.Format(channelStringFormat, seriesIndex, "---", axisY.Units.Text);
value = string.Format(channelStringFormat, pointLineSeries.Title.Text, "---");
}
sb.AppendLine(value);
//这个会改变曲线标题的名称,影响曲线的定位、删除等操作
//pointLineSeries.Title.Text = value;
seriesIndex++;
}
//sb.AppendLine("");
//sb.AppendLine("时间: " + lightningChart1.ViewXY.XAxes[0].TimeString(cursor.ValueAtXAxis, "HH:mm:ss.ffff"));
////Set text
cursorValueDisplay.Text = sb.ToString();
////Show the label only if it selected to be shown
cursorValueDisplay.Visible = labelVisible && showNextToCursor;
//Allow chart rendering
lightningChart1.EndUpdate();
}
/// <summary>
/// Solve value from series data points array. Accurate method, but slower than SolveValueCoarse
/// </summary>
/// <param name="series">Series</param>
/// <param name="xValue">X value</param>
/// <param name="yValue">Output Y value</param>
/// <returns>Success status</returns>
private bool SolveValueAccurate(PointLineSeries series, double xValue, out double yValue)
{
AxisY axisY = lightningChart1.ViewXY.YAxes[series.AssignYAxisIndex];
yValue = 0;
LineSeriesValueSolveResult result = series.SolveYValueAtXValue(xValue);
if (result.SolveStatus == LineSeriesSolveStatus.OK)
{
//PointLineSeries may have two or more points at same X value. If so, center it between min and max
yValue = (result.YMax + result.YMin) / 2.0;
return true;
}
else
{
return false;
}
}
/// <summary>
/// Solve value from screen coordinates. Faster method, but not less accurateValue than SolveValueAccurate
/// </summary>
/// <param name="series">Series</param>
/// <param name="xValue">X value</param>
/// <param name="yValue">Output Y value</param>
/// <returns>Success status</returns>
private bool SolveValueCoarse(PointLineSeries series, double xValue, out double yValue)
{
AxisY axisY = lightningChart1.ViewXY.YAxes[series.AssignYAxisIndex];
float coordX = lightningChart1.ViewXY.XAxes[0].ValueToCoord(xValue);
float coordY;
yValue = 0;
LineSeriesCoordinateSolveResult result = series.SolveYCoordAtXCoord(coordX);
if (result.SolveStatus == LineSeriesSolveStatus.OK)
{
coordY = (result.CoordBottom + result.CoordTop) / 2f;
if (axisY.CoordToValue((int)Math.Round(coordY), out yValue) == false)
{
return false;
}
}
else
{
return false;
}
return true;
}
/// <summary>
/// 增加Y轴
/// </summary>
/// <param name="Name"></param>
/// <param name="MaxVal"></param>
/// <param name="MinVal"></param>
private void AddYAxis(string Name, double MinVal, double MaxVal)
{
lightningChart1.BeginUpdate();
var CreateAxisY = new AxisY();
CreateAxisY.Title.Text = Name;
CreateAxisY.Title.AllowDragging = false;
CreateAxisY.SetRange(MinVal, MaxVal);
CreateAxisY.MajorGrid.Visible = false;
//取消滚轮缩放
CreateAxisY.ZoomingEnabled = false;
lightningChart1.ViewXY.YAxes.Add(CreateAxisY);
//Configure and add series to the chart
PointLineSeries CreateSeries = new PointLineSeries(lightningChart1.ViewXY, lightningChart1.ViewXY.XAxes[0], CreateAxisY);
CreateSeries.Title.Text = Name;
CreateSeries.LineStyle.Color = ChartConfigInfo.ListColor[lightningChart1.ViewXY.YAxes.Count + 1];
CreateSeries.AllowUserInteraction = false;
lightningChart1.ViewXY.PointLineSeries.Add(CreateSeries);
lightningChart1.EndUpdate();
MainRealTimeChartSeriesCount++;
}
private void DeleteYAxis(string Name)
{
lightningChart1.BeginUpdate();
for (int i = 0; i < lightningChart1.ViewXY.YAxes.Count; i++)
{
if (lightningChart1.ViewXY.YAxes[i].Title.Text == Name)
{
lightningChart1.ViewXY.YAxes.RemoveAt(i);
break;
}
}
for (int i = 0; i < lightningChart1.ViewXY.PointLineSeries.Count; i++)
{
if (lightningChart1.ViewXY.PointLineSeries[i].Title.Text == Name)
{
lightningChart1.ViewXY.PointLineSeries.RemoveAt(i);
break;
}
}
lightningChart1.EndUpdate();
if (MainRealTimeChartSeriesCount <= 0)
{
MainRealTimeChartSeriesCount = 0;
}
else
{
MainRealTimeChartSeriesCount--;
}
}
#endregion
}
}