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 { /// /// RealTimeChartView.xaml 的交互逻辑 /// public partial class RealTimeChartView : UserControl { /// /// 定时器 /// static System.Timers.Timer CurTimer { get; set; } private IEventAggregator _EventAggregator { get; set; } public MachineRtDataService MachineRtDataService { get; } public IFreeSql FreeSql { get; } /// /// 曲线配置 /// public ChartConfig ChartConfigInfo { get; set; } = new ChartConfig(); /// /// 实例化函数 /// /// /// public RealTimeChartView(IEventAggregator eventAggregator, MachineRtDataService machineRtDataService, IFreeSql freeSql) { InitializeComponent(); LoadLighingChart(); LvChartRtValue.ItemsSource = ListChartRtSeries; //事件服务 _EventAggregator = eventAggregator; MachineRtDataService = machineRtDataService; FreeSql = freeSql; _EventAggregator.GetEvent().Subscribe(GetChartRtEvent); _EventAggregator.GetEvent().Subscribe(ReLoadData); } /// /// 重新加载数据 /// 更新曲线和Y轴设置 /// /// /// private void ReLoadData(ChartActionType type) { // switch (type) { case ChartActionType.ReLoadChart: var ChartSelectedData = FreeSql.Select().Include(a => a.ConfigChartYAxis).ToList(); LoadChartConfigSelect(ChartSelectedData); break; default: break; } } /// /// 获取曲线的实时值 /// 数据是发布过来 /// /// /// private void GetChartRtEvent(List 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; } } }); } /// /// 时间序列 /// /// /// private void CurTimer_Elapsed(object? sender, ElapsedEventArgs e) { } #region 开始测试 #endregion #region 曲线配置 /// /// 当前曲线配置信息 /// public List CurListConfigChartSelect { get; set; } /// /// 加载曲线配置信息 /// private void LoadChartConfigSelect(List 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 { //存在就不需要操作 } } } /// /// 实时曲线数据集合 /// public List ListChartRtValue { get; set; } /// /// 曲线数据系列集合 /// public List ListChartRtSeries { get; set; } = new List(); /// /// Chart图表X轴的跨度-秒 /// private int ChartXDateTimeRange = 3600; /// /// X轴滚动 /// private bool ScrollEnable = true; /// /// 更新图表信息 /// 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(); } } /// /// 根据名称获取曲线所需的数据 /// 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 { } } /// /// 曲线个数 /// private int MainRealTimeChartSeriesCount = 0; /// /// LightingChart图标的初始化 /// 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(); } /// /// Update cursor texts. /// 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(); } /// /// Solve value from series data points array. Accurate method, but slower than SolveValueCoarse /// /// Series /// X value /// Output Y value /// Success status 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; } } /// /// Solve value from screen coordinates. Faster method, but not less accurateValue than SolveValueAccurate /// /// Series /// X value /// Output Y value /// Success status 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; } /// /// 增加Y轴 /// /// /// /// 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 } }