using MECF.Framework.Common.DataCenter; using MECF.Framework.UI.Client.ClientBase; using SciChart.Charting.Common.Helpers; using SciChart.Charting.ViewportManagers; using SciChart.Charting.Visuals.Annotations; using SciChart.Charting.Visuals.Axes; using SciChart.Charting.Visuals.RenderableSeries; using SciChart.Data.Model; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Data; using System.Windows; using Cali = Caliburn.Micro; using MECF.Framework.UI.Client.CenterViews.DataLogs.ProcessHistory; using System.Reflection; using SciChart.Charting.Model.ChartSeries; using System.IO; using Aitex.Core.Util; using Aitex.Core.RT.Log; namespace SicUI.Models.PMs.Charting { public class PMChartingV2ViewModel : SicModuleUIViewModelBase, ISupportMultipleSystem { private class QueryIndexer { public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public string Module { get; set; } public string ColumneName { get; set; } public string ProcessGuid { get; set; } public List DateList { get; set; } public long currentTimeTicks { get; set; } } /// /// ViewModel绑定的View对象 /// private PMChartingV2View view; /// /// 初始化界面 /// public PMChartingV2ViewModel() { DisplayName = "Process History"; var now = DateTime.Now; this.StartDateTime = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, 0); this.EndDateTime = new DateTime(now.Year, now.Month, now.Day, 23, 59, 59, 999); SourcePM = new ObservableCollection(new[] { "PM1", "PM2" }); //sci按钮操作 DataDetailVisbleCommand = new ActionCommand(SetDataDetailVisble); DefaultZoomCommand = new ActionCommand(Zoom); ShowLegendCommand = new ActionCommand(SetLegendAvalible); //获取所有Device GetPMNode(); SelectedData = new ObservableCollection(); SelectedRecipes = new ObservableCollection(); Recipes = new ObservableCollection(); //x y always指zoom功能失效,总是自动调整大小 //ChartAutoRange = AutoRange.Always; VisibleRangeX = new DoubleRange(-1000, 10000); VisibleRangeY = new DoubleRange(-10, 500); VisibleRangeXLimit = new DoubleRange(-1000, 10000); VisibleRangeYLimit = new DoubleRange(-10, 500); _thread = new PeriodicJob(300, MonitorHistoryData, "History", true); //_threadReal = new PeriodicJob(TrendInterval, MonitorRealTimeData, "RealTime", true); } protected override void OnViewLoaded(object _view) { //获取绑定的View(wpf没有默认的日期时间控件,Winform日期控件无法通过绑定属性获取) base.OnViewLoaded(_view); this.view = (PMChartingV2View)_view; this.view.wfTimeFrom.Value = this.StartDateTime; this.view.wfTimeTo.Value = this.EndDateTime; } private PeriodicJob _thread; //private PeriodicJob _threadReal; //DeviceTimer dt = new DeviceTimer(); //设置允许同时显示的最多曲线数 private const int MAX_PARAMETERS = 20; /// /// sci图表的视图管理类 /// private readonly IViewportManager _viewportManager = new DefaultViewportManager(); public IViewportManager ViewportManager { get { return _viewportManager; } } #region Properties /// /// 搜索条件:Recipe开始时间 /// public DateTime StartDateTime { get; set; } /// /// 搜索条件:Recipe结束时间 /// public DateTime EndDateTime { get; set; } /// /// 搜索条件:PM腔 /// public string SelectedValuePM { get; set; } /// /// 搜索条件:PM腔 /// public ObservableCollection SourcePM { get; set; } /// /// 搜索条件:Recipe /// public string RecipeName { get; set; } /// /// 查询曲线用点位组,每次有曲线变动时入队,生成曲线后出队 /// ConcurrentBag _lstTokenTimeData = new ConcurrentBag(); public ObservableCollection QueryDataDetail { get; set; } /// /// 用户选中的曲线组,前台treeview和chart调用 /// public ObservableCollection SelectedData { get; set; } /// /// 根据条件搜索的recipe /// public ObservableCollection Recipes { get; set; } /// /// 用户选中查阅的recipe /// public ObservableCollection SelectedRecipes { get; set; } private List Nodes = new List(); /// /// 获取设备需要监控的Device点 /// private ObservableCollection _configNodes = new ObservableCollection(); public ObservableCollection ConfigNodes { get { return _configNodes; } set { _configNodes = value; NotifyOfPropertyChange("ConfigNodes"); } } private double _maxYValue = 0, _minYValue = 0; private IRange _visibleRangeXLimit; public IRange VisibleRangeXLimit { get { return _visibleRangeXLimit;} set { _visibleRangeXLimit = value; NotifyOfPropertyChange(nameof(VisibleRangeXLimit)); } } private IRange _visibleRangeYLimit; public IRange VisibleRangeYLimit { get { return _visibleRangeYLimit; } set { _visibleRangeYLimit = value; NotifyOfPropertyChange(nameof(VisibleRangeYLimit)); } } private IRange _visibleRangeX; public IRange VisibleRangeX { get { return _visibleRangeX; } set { _visibleRangeX = value; NotifyOfPropertyChange(nameof(VisibleRangeX)); } } private IRange _visibleRangeY; public IRange VisibleRangeY { get { return _visibleRangeY; } set { _visibleRangeY = value; NotifyOfPropertyChange(nameof(VisibleRangeY)); } } private AutoRange _chartAutoRange = AutoRange.Never; public AutoRange ChartAutoRange { get { return _chartAutoRange; } set { _chartAutoRange = value; NotifyOfPropertyChange(nameof(ChartAutoRange)); } } private Visibility _dataDetailVisbility = Visibility.Collapsed; public Visibility DataDetailVisbility { get { return _dataDetailVisbility; } set { _dataDetailVisbility = value; NotifyOfPropertyChange(nameof(DataDetailVisbility)); } } private bool _showLegendInfo; public bool ShowLegendInfo { get { return _showLegendInfo; } set { _showLegendInfo = value; NotifyOfPropertyChange(nameof(ShowLegendInfo)); } } private Queue colorQueue = new Queue(new Color[]{Color.Red,Color.Orange,Color.Green,Color.Blue,Color.Purple,Color.Aqua,Color.Bisque,Color.Brown,Color.BurlyWood,Color.CadetBlue, Color.CornflowerBlue,Color.DarkBlue,Color.DarkCyan,Color.DarkGray,Color.DarkGreen,Color.DarkKhaki,Color.DarkMagenta,Color.DarkOliveGreen, Color.DarkOrange, Color.DarkSeaGreen,Color.DarkSlateBlue,Color.DarkSlateGray,Color.DarkViolet,Color.DeepPink,Color.DeepSkyBlue,Color.DimGray, Color.DodgerBlue,Color.ForestGreen, Color.Gold, Color.Gray,Color.GreenYellow,Color.HotPink,Color.Indigo,Color.Khaki, Color.LimeGreen,Color.MediumOrchid,Color.MediumPurple,Color.MediumSeaGreen,Color.MediumSlateBlue,Color.MediumSpringGreen, Color.MediumTurquoise,Color.Moccasin,Color.NavajoWhite,Color.Olive,Color.OliveDrab,Color.OrangeRed,Color.Orchid,Color.PaleGoldenrod,Color.PaleGreen, Color.PeachPuff,Color.Peru,Color.Plum,Color.PowderBlue,Color.RosyBrown,Color.RoyalBlue,Color.SaddleBrown,Color.Salmon,Color.SeaGreen, Color.Sienna, Color.SkyBlue,Color.SlateBlue,Color.SlateGray,Color.SpringGreen,Color.Teal,Color.Aquamarine,Color.Tomato,Color.Turquoise,Color.Violet,Color.Wheat, Color.YellowGreen}); #endregion Properties #region 界面按钮 /// /// 重新搜索Recipe /// public void SearchRecipe() { this.StartDateTime = this.view.wfTimeFrom.Value; this.EndDateTime = this.view.wfTimeTo.Value; if (StartDateTime > EndDateTime) { MessageBox.Show("Time range invalid, start time should be early than end time"); return; } Recipes.Clear(); RemoveAllLine(); try { string sql = $"SELECT * FROM \"process_data\" where \"process_data\".\"process_begin_time\" >='{StartDateTime:yyyyMMdd HHmmss}' and \"process_data\".\"process_end_time\" <='{EndDateTime:yyyyMMdd HHmmss}'"; if (!string.IsNullOrEmpty(SelectedValuePM)) { string[] pms = SelectedValuePM.Split(','); if (pms.Length > 0) { sql += " and (FALSE "; foreach (var pm in pms) { sql += $" OR \"process_data\".\"process_in\"='{pm}' "; } sql += " ) "; } } if (!string.IsNullOrEmpty(RecipeName)) { sql += string.Format(" and lower( \"process_data\".\"recipe_name\") like '%{0}%'", RecipeName.ToLower()); } sql += " order by \"process_data\".\"process_begin_time\" ASC;"; DataTable dbData = QueryDataClient.Instance.Service.QueryData(sql); Application.Current.Dispatcher.BeginInvoke(new Action(() => { if (dbData == null || dbData.Rows.Count == 0) return; double maxXValue = 0; for (int i = 0; i < dbData.Rows.Count; i++) { RecipeItemSic item = new RecipeItemSic(); item.Recipe = dbData.Rows[i]["recipe_name"].ToString(); item.Chamber = dbData.Rows[i]["process_in"].ToString(); item.Status = dbData.Rows[i]["process_status"].ToString(); //item.ProcessGuid = dbData.Rows[i]["guid"].ToString(); item.ProcessGuid = "Recipe"+i.ToString().PadLeft(3,'0'); if (!dbData.Rows[i]["process_begin_time"].Equals(DBNull.Value)) item.StartTime = ((DateTime)dbData.Rows[i]["process_begin_time"]).ToString("yyyy/MM/dd HH:mm:ss.fff"); if (!dbData.Rows[i]["process_end_time"].Equals(DBNull.Value)) item.EndTime = ((DateTime)dbData.Rows[i]["process_end_time"]).ToString("yyyy/MM/dd HH:mm:ss.fff"); long curTickCount = (DateTime.Parse(item.EndTime).Ticks - DateTime.Parse(item.StartTime).Ticks) / 10000000; if (maxXValue < curTickCount) { maxXValue = curTickCount; } //正确设置范围,否则曲线不显示 VisibleRangeX = new DoubleRange(-0.2*maxXValue, maxXValue * 1.2); VisibleRangeXLimit = new DoubleRange(-0.2 * maxXValue, maxXValue * 1.2);//X轴拉伸1.2倍 Recipes.Add(item); } })); } catch (Exception e) { LOG.Write(e); } } /// /// 选择Recipe /// public void CheckRecipe(RecipeItemSic recipe) { if (recipe.Selected) //选中配方 { SelectedRecipes.Add(recipe); //刷新曲线 //GetXRange(); foreach (PMNodeInfo node in ConfigNodes) { if (node.Selected) { ParameterCheck(node); } } } else //取消配方 { RecipeItemSic item = SelectedRecipes.FirstOrDefault(t => t.Recipe == recipe.Recipe); if (item != null) SelectedRecipes.Remove(item); RemoveRecipeRelated(recipe); //移除配方相关曲线 } } /// /// 移除配方相关曲线 /// /// private void RemoveRecipeRelated(RecipeItemSic recipe) { lock (_lockSelection) //递减法移除列表内项,不会删错 { for (int i = SelectedData.Count - 1; i >= 0; i--) { if ((SelectedData[i] as ChartDataLineXV2).ProcessGuid.Contains(recipe.ProcessGuid)) { SelectedData.RemoveAt(i); } } } } /// /// 清除所有选择的节点 /// public void RemoveAllLine() { this.SelectedData.Clear(); foreach (PMNodeInfo node in ConfigNodes) { node.Selected = false; } } #endregion 界面按钮 private void SelectedDataChanged() { foreach (var item in SelectedData) { if (item.Stroke.Equals(System.Windows.Media.Color.FromArgb(255, 0, 0, 255))) { Color drawingColor = colorQueue.Peek(); item.Stroke = System.Windows.Media.Color.FromRgb(drawingColor.R, drawingColor.G, drawingColor.B); colorQueue.Enqueue(colorQueue.Dequeue()); } } } /// /// 加载PM的Device名称 /// private void GetPMNode() { Nodes.Clear(); ConfigNodes.Clear(); List dataList = (List)QueryDataClient.Instance.Service.GetConfig("System.NumericDataList"); dataList.Sort(); List lstNode = new List(); foreach (string dataName in dataList) { string[] nodeName = dataName.Split('.'); if (nodeName.Length > 1 && nodeName[0].IndexOf("PM") == 0) { string nodeStr = dataName.Substring(dataName.IndexOf('.') + 1); if (!lstNode.Contains(nodeStr)) //不显示重复项 { lstNode.Add(nodeStr); Nodes.Add(new PMNodeInfo() { NodeStr = nodeStr }); ConfigNodes.Add(new PMNodeInfo() { NodeStr = nodeStr }); } } } } public void Find(string keyword) { ConfigNodes.Clear(); //遍历所有node,隐藏不符合条件项 foreach (var node in Nodes) { node.ApplyCriteria(keyword); if (node.IsMatch) { ConfigNodes.Add(node); } } } private void SetDataDetailVisble() { if (DataDetailVisbility == Visibility.Collapsed) { DataDetailVisbility = Visibility.Visible; } else { DataDetailVisbility = Visibility.Collapsed; } } #region 获取HistoryData private object _lockSelection = new object(); protected bool MonitorHistoryData() { try { if (_lstTokenTimeData.Count > 0) { lock (_lockSelection) { foreach (var item in _lstTokenTimeData) { DateTime timeFrom = item.StartTime; DateTime timeTo = item.EndTime; GetHistoryData(item.DateList, timeFrom, timeTo, item.Module, item.ColumneName, item.ProcessGuid); _lstTokenTimeData.TryTake(out _); } } } } catch (Exception ex) { LOG.Error(ex.Message); } return true; } private void GetHistoryData(List lstDayStr, DateTime from, DateTime to, string module, string colName, string processGuid) { Dictionary> historyData = new Dictionary>(); List lstDataDetail = new List(); //db获取对应字段数据 foreach (string cDate in lstDayStr) { string sql = String.Format("select time AS InternalTimeStamp,\"{0}\"", colName); sql += string.Format(" from \"{0}\" where time > {1} and time <= {2} order by time asc", cDate + "." + module, from.Ticks, to.Ticks); DataTable dataTable = QueryDataClient.Instance.Service.QueryData(sql); if (dataTable == null || dataTable.Rows.Count == 0 || dataTable.Columns.Count < 2) { return; } for (int i = 0; i < dataTable.Rows.Count; i++) { DataDetail data = new DataDetail(); long ticks = (long)dataTable.Rows[i][0] - from.Ticks; data.xValue = ticks / 10000000; if (dataTable.Rows[i][1] is DBNull || dataTable.Rows[i][1] is null) { data.yValue = 0; } else if (dataTable.Rows[i][1] is bool) { data.yValue = (bool)dataTable.Rows[i][1] ? 1 : 0; } else { data.yValue = float.Parse(dataTable.Rows[i][1].ToString()); } if (data.yValue > _maxYValue) { _maxYValue = data.yValue; } if (data.yValue < _minYValue) { _minYValue = data.yValue; } lstDataDetail.Add(data); } } //正确设置范围,否则曲线不显示 VisibleRangeY = new DoubleRange(_minYValue - _maxYValue * 0.5, _maxYValue * 2.1); VisibleRangeYLimit = new DoubleRange(_minYValue - _maxYValue * 0.5, _maxYValue * 2.1);//Y轴拉伸1.2倍 //所有数据内存 historyData.Add(processGuid, lstDataDetail); //数据绑定到曲线中 Application.Current.Dispatcher.BeginInvoke(new Action(() => { try { foreach (var item in SelectedData) { var seriesItem = item as ChartDataLineXV2; if (seriesItem == null) continue; foreach (var data in historyData) { if (data.Key != seriesItem.ProcessGuid) continue; seriesItem.Capacity += data.Value.Count; foreach (var detailDataItem in data.Value) { seriesItem.Append(detailDataItem.xValue, detailDataItem.yValue); } } } } catch (Exception ex) { LOG.Write(ex); } })); } #endregion /// /// 更新Data变化 /// /// /// public bool ParameterCheck(PMNodeInfo node) { if (SelectedRecipes.Count == 0) { return false; } if (node.Selected) { foreach (RecipeItemSic itemSic in SelectedRecipes) { string nodeStr = itemSic.Chamber + "." + node.NodeStr; //字段名称 string nodeStr_1 = itemSic.LotID; //显示名称 string nodeWithGuid = node.NodeStr + "_" + itemSic.ProcessGuid; //字段名+ Guid (唯一ID) DateTime dtStartTime = DateTime.Parse(itemSic.StartTime); DateTime dtEndTime = DateTime.Parse(itemSic.EndTime); lock (_lockSelection) { bool isExist = SelectedData.FirstOrDefault(x => (x as ChartDataLineXV2).ProcessGuid == nodeWithGuid) != null; if (!isExist) { if (SelectedData.Count < MAX_PARAMETERS) { var line = new ChartDataLineXV2(nodeWithGuid); line.Time=itemSic.StartTime+"-"+itemSic.EndTime; line.RecipeName = new DirectoryInfo(itemSic.Recipe).Name; if (itemSic.Chamber.StartsWith("PM1")) line.Module = "PM1"; else if (itemSic.Chamber.StartsWith("PM2")) line.Module = "PM2"; else line.Module = "System"; line.ProcessGuid = nodeWithGuid; line.RecName = nodeStr_1; line.Tag = node; SelectedData.Add(line); SelectedDataChanged(); QueryIndexer indexer = _lstTokenTimeData.FirstOrDefault(x => x.ProcessGuid == line.ProcessGuid); if (indexer == null) { indexer = new QueryIndexer() { Module = line.Module, StartTime = dtStartTime, EndTime = dtEndTime, ProcessGuid = line.ProcessGuid, ColumneName = nodeStr, DateList = GetDateList(dtStartTime, dtEndTime) }; _lstTokenTimeData.Add(indexer); } } else { return false; } } } } } else { lock (_lockSelection) { for (int i = SelectedData.Count - 1; i >= 0; i--) { if ((SelectedData[i] as ChartDataLineXV2).DataName.Contains(node.NodeStr)) { SelectedData.RemoveAt(i); } } } } return true; } public void SelectColor(ChartDataLineXV2 cp) { if (cp == null) return; var dlg = new System.Windows.Forms.ColorDialog(); if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { cp.Stroke = new System.Windows.Media.Color() { A = dlg.Color.A, B = dlg.Color.B, G = dlg.Color.G, R = dlg.Color.R }; } } /// /// 获取开始时间和结束时间之间的所有日期 /// /// /// /// private List GetDateList(DateTime timeStart, DateTime timeEnd) { List lstTime = new List(); string dtEndStr = timeEnd.ToString("yyyyMMdd"); int timeSpan = (int)(timeEnd - timeStart).TotalDays + 1; for (int i = 0; i <= timeSpan; i++) { string dt = timeStart.AddDays(i).ToString("yyyyMMdd"); lstTime.Add(dt); if (dt == dtEndStr) { break; } } return lstTime; } /// /// 获取所有的列名 /// private List GetColumnList(string modleName) { List _seletedItemName = new List(); foreach (PMNodeInfo node in ConfigNodes) { if (node.Selected) { _seletedItemName.Add("\"" + modleName + "." + node.NodeStr + "\""); } } return _seletedItemName; } public ActionCommand DefaultZoomCommand { get; private set; } public ActionCommand ShowLegendCommand { get; private set; } public ActionCommand DataDetailVisbleCommand { get; private set; } public ActionCommand ShowAlarmCommand { get; private set; } private void SetLegendAvalible() { ShowLegendInfo = !ShowLegendInfo; } private void Zoom() { ViewportManager.AnimateZoomExtents(TimeSpan.FromMilliseconds(500)); } } }