using FLY.OBJComponents.Client; using FLY.OBJComponents.IService; using FLY.Weight2.Client; using FLY.Weight2.IService; using FLY.Weight2.Server.Model; using LiveCharts; using LiveCharts.Configurations; using LiveCharts.Wpf; using MultiLayout; using MultiLayout.UiModule; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Navigation; using System.Windows.Threading; using Unity; namespace FLY.Weight2.UI.Client.UiModule { /// <summary> /// FlowGraph.xaml 的交互逻辑 /// </summary> public partial class FlowGraph : UserControl { IUnityContainer container; IWeightSystemService weightSystemService; FlowGraphVm viewModel; public FlowGraph() { InitializeComponent(); } [InjectionMethod] public void Init( IUnityContainer container, IWeightSystemService weightSystemService, IBulkDbFlowService bulkDb) { this.container = container; this.weightSystemService = weightSystemService; viewModel = new FlowGraphVm(); viewModel.Init(weightSystemService, bulkDb); DataContext = viewModel; } private void btnInfoClick(object sender, RoutedEventArgs e) { WdGraphSet w = new WdGraphSet(); w.Init(viewModel); w.Owner = FLY.ControlLibrary.COMMON.GetWindow(this); w.ShowDialog(); } private void btnTableClick(object sender, RoutedEventArgs e) { PgFlowTable p = new PgFlowTable(); container.BuildUp(p); MultiLayout.FlyLayoutManager.NavigationService.Navigate(p); } private void btnSelectClick(object sender, RoutedEventArgs e) { viewModel.SelectABC(((Button)sender).Tag as FlowGraphVm_Item); } private void btnSelectAllClick(object sender, RoutedEventArgs e) { viewModel.SelectAll(); } } public class FlowGraphVm : INotifyPropertyChanged { const int MARKNO_UPDATE_Y = 0; /// <summary> /// 画面只显示200个数据, 就算设置画面设置显示1000个数,也会整合为200个数据 /// </summary> const int Len = 200; /// <summary> /// 自动按保持最新值,总时间 /// </summary> const int AutoKeepTotalSec = 60; public event PropertyChangedEventHandler PropertyChanged; #region 图表控制 /// <summary> /// 曲线值转换器 /// </summary> public CartesianMapper<Lc_Flow> Mapper { get; set; } /// <summary> /// 曲线颜色 /// </summary> public Brush Stroke { get; set; } /// <summary> /// X轴格式 /// </summary> public Func<double, string> XFormatter { get; set; } /// <summary> /// y轴格式 /// </summary> public Func<double, string> YFormatter { get; set; } /// <summary> /// Y轴中值 /// </summary> public double YTarget { get; set; } /// <summary> /// 最大值 /// </summary> public double YMax { get; set; } = double.NaN; /// <summary> /// 最小值 /// </summary> public double YMin { get; set; } = double.NaN; public double XMax { get; set; } = double.NaN; #endregion #region property public ChartValues<Lc_Flow> Values { get; private set; } = new ChartValues<Lc_Flow>(); /// <summary> /// 开始时间 /// </summary> public DateTime BeginTime { get; set; } /// <summary> /// 结束时间 /// </summary> public DateTime EndTime { get; set; } /// <summary> /// Values.Last().Id /// </summary> public long Id { get; private set; } = -1; public ObservableCollection<FlowGraphVm_Item> Items { get; } = new ObservableCollection<FlowGraphVm_Item>(); /// <summary> /// 总流量 /// </summary> public double TotalFlow { get; set; } #endregion #region 查询模式 /// <summary> /// 只要 Number被改变,且不为0, AutoKeepNewestTimeRemaining就会被设为10s. /// 当 AutoKeepNewestTimeRemaining = 0, Number就会被设置为0 /// </summary> public int AutoKeepNewestTimeRemaining { get; private set; } /// <summary> /// 当前就是最新的视图 /// </summary> public bool IsNewest { get; private set; } /// <summary> /// 加载中 /// </summary> public bool IsLoading { get; private set; } #endregion /// <summary> /// 当前现在不是最新,而且没有移动动作,20秒自动按 Newest() /// </summary> DispatcherTimer timer; IWeightSystemService weightSystemService; FlowGraphParams graphparam; IBulkDbFlowService bulkDb; public FlowGraphVm() { #region 与数据无关界面参数 XFormatter = (x) => { int index = (int)x; if (index < Values.Count() && index >= 0) return Values[index].Time.ToString("HH:mm"); else return "??:??"; }; YFormatter = (y) => { return $"{y,6:F1}"; }; #endregion } public void Init( IWeightSystemService weightSystemService, IBulkDbFlowService bulkDb ) { graphparam = FlowGraphParams.Current; this.weightSystemService = weightSystemService; this.bulkDb = bulkDb; Values.Clear(); Id = -1; IsNewest = true; ToNewest(); //根据 weightSystemClient.Item创建Series BindingSeries(); SelectAll(); UpdateY(); UpdateX(); if (this.weightSystemService is WeightSystemServiceClient) { (this.weightSystemService as WeightSystemServiceClient).ResetItemsEvent += BindingSeries; } bulkDb.PropertyChanged += BulkDb_PropertyChanged; this.PropertyChanged += FlowGraphVm_PropertyChanged; graphparam.PropertyChanged += Graphparam_PropertyChanged; timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromSeconds(1); timer.Tick += (s, e) => { if (IsNewest) return; if (AutoKeepNewestTimeRemaining > 0) AutoKeepNewestTimeRemaining--; if (AutoKeepNewestTimeRemaining <= 0) { ToNewest(); } }; this.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(IsNewest)) { timer.IsEnabled = !IsNewest; } }; } private void BulkDb_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(bulkDb.LastId)) { Update4LastIdChanged(); } else if (e.PropertyName == nameof(FObjBase.FObjServiceClient.IsConnected)) { if ((bulkDb as FObjBase.FObjServiceClient).IsConnected) { Update4LastIdChanged(); } else { IsLoading = false; } } } private void Graphparam_PropertyChanged(object sender, PropertyChangedEventArgs e) { if ((e.PropertyName == nameof(graphparam.IsYRangeAuto)) || (e.PropertyName == nameof(graphparam.IsYRangePercent)) || (e.PropertyName == nameof(graphparam.YRangePercent)) || (e.PropertyName == nameof(graphparam.YRange))) { UpdateY(); } else if (e.PropertyName == nameof(graphparam.Interval)) { ToNewest(); } } private void FlowGraphVm_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(YTarget)) UpdateY(); } void BindingSeries() { if (Items.Count != weightSystemService.Items.Count) { Items.Clear(); for (int i = 0; i < weightSystemService.Items.Count; i++) { FlowGraphVm_Item item = new FlowGraphVm_Item { Title = weightSystemService.Items[i].Number, Fill = weightSystemService.Items[i].Color, Visibility = Visibility.Hidden, Index = i }; Items.Add(item); } UpdateYTarget(); } } void UpdateY() { if (graphparam.IsYRangeAuto) { YMax = double.NaN; YMin = double.NaN; } else if (graphparam.IsYRangePercent) { double range = YTarget * graphparam.YRangePercent; if (range <= 0.1) { YMax = double.NaN; YMin = double.NaN; } else { YMax = YTarget + range; YMin = YTarget - range; } } else { double range = graphparam.YRange; if (range <= 0.1) { YMax = double.NaN; YMin = double.NaN; } else { YMax = YTarget + range; YMin = YTarget - range; } } } void UpdateX() { XMax = Len - 1; } void UpdateYTarget() { FObjBase.PollModule.Current.Poll_JustOnce(() => { if (Values.Count() == 0) return; Lc_Flow flows = Values.Last(); for (int i = 0; i < Items.Count() && i < flows.Items.Count(); i++) { Items[i].Value = flows.Items[i].Flow; } TotalFlow = flows.Total; double target = TotalFlow; try { FlowGraphVm_Item it = Items.First((i) => i.Visibility == Visibility.Visible); if (it != null) target = it.Value; } catch { } YTarget = Math.Round(target); }, this, MARKNO_UPDATE_Y); } void Update4LastIdChanged() { if (!IsConnected) return; if (IsLoading) return; if (!IsNewest) return; int cnt; if (Id == -1) { cnt = Len; Values.Clear(); } else { cnt = (int)(bulkDb.LastId - Id); cnt /= graphparam.Interval; if (cnt > Len) cnt = Len; } if (cnt == 0) return; IsLoading = true; bulkDb.GetTrend( new Pack_GetTrendRequest() { Id = 0, Interval = graphparam.Interval, Count = cnt }, GetTrendReponse, this); } void GetTrendReponse(object asyncContext, object retData) { IsLoading = false; var reponse = retData as Pack_GetTrendReponse<Lc_Flow>; //向Values 后面添加数据 if (reponse.Values != null && reponse.Values.Count > 0) { //从尾向前排的,需要反转!!!! reponse.Values.Reverse(); if (Values.Count() > 0) { if (reponse.Values.First().Time <= Values.Last().Time) { //不能接上 Values.Clear(); } } Values.AddRange(reponse.Values); //限制Values长度 int remove_cnt = Values.Count() - Len; for (int i = 0; i < remove_cnt; i++) Values.RemoveAt(0); Id = reponse.Values.Last().ID; BeginTime = Values.First().Time; EndTime = Values.Last().Time; long newestId = graphparam.Interval * (bulkDb.LastId / graphparam.Interval); if (Id == newestId) IsNewest = true; else IsNewest = false; UpdateYTarget(); } else { long newestId = graphparam.Interval * (bulkDb.LastId / graphparam.Interval); if (Id == newestId) IsNewest = true; else IsNewest = false; } } public void Search(long lastId) { if (lastId <= 0) return; if (!IsConnected) return; if (IsLoading) return; AutoKeepNewestTimeRemaining = AutoKeepTotalSec; IsNewest = false; IsLoading = true; //TODO,计算,是否有重叠的数据 bulkDb.GetTrend( new Pack_GetTrendRequest() { Id = lastId, Interval = graphparam.Interval, Count = Len }, GetTrendReponse, this); } bool IsConnected { get { return (bulkDb as FObjBase.FObjServiceClient).IsConnected; } } public void Seatch(DateTime lastTime) { if (!IsConnected) return; if (IsLoading) return; AutoKeepNewestTimeRemaining = AutoKeepTotalSec; IsNewest = false; IsLoading = true; //TODO,计算,是否有重叠的数据 bulkDb.GetTrend( new Pack_GetTrendRequest() { Interval = graphparam.Interval, Count = Len, IsSearchByTime = true, Time = lastTime }, GetTrendReponse, this); } public void PreView() { Search(Id - Len * graphparam.Interval); } public void NextView() { Search(Id + Len * graphparam.Interval); } public void ToNewest() { if (!IsConnected) return; if (IsLoading) return; IsNewest = true; IsLoading = true; bulkDb.GetTrend( new Pack_GetTrendRequest() { Id = 0, Interval = graphparam.Interval, Count = Len }, GetTrendReponse, this); } class GetMapperOfFlyData_Flow { int Index = 0; public GetMapperOfFlyData_Flow(int index) { Index = index; } public CartesianMapper<Lc_Flow> GetCartesianMapper() { return Mappers.Xy<Lc_Flow>() .X((value, index) => { return index; }) .Y(value => { if (Index < value.Items.Count()) return value.Items[Index].Flow; else return double.NaN; }); } public static CartesianMapper<Lc_Flow> ConfigurationAll = Mappers.Xy<Lc_Flow>() .X((value, index) => { return index; }) .Y(value => { return value.Total; }); } public void SelectAll() { foreach (FlowGraphVm_Item i in Items) { i.Visibility = Visibility.Hidden; } Stroke = new SolidColorBrush(Colors.Black); Mapper = GetMapperOfFlyData_Flow.ConfigurationAll; UpdateYTarget(); } public void SelectABC(FlowGraphVm_Item item) { foreach (FlowGraphVm_Item i in Items) { if (i != item) { i.Visibility = Visibility.Hidden; } else { i.Visibility = Visibility.Visible; } } Stroke = item.Fill; Mapper = new GetMapperOfFlyData_Flow(item.Index).GetCartesianMapper(); UpdateYTarget(); } } public class FlowGraphVmUt : INotifyPropertyChanged { #region 图表控制 /// <summary> /// 曲线值转换器 /// </summary> public CartesianMapper<Lc_Flow> Mapper { get; set; } /// <summary> /// 曲线颜色 /// </summary> public Brush Stroke { get; set; } /// <summary> /// X轴格式 /// </summary> public Func<double, string> XFormatter { get; set; } /// <summary> /// y轴格式 /// </summary> public Func<double, string> YFormatter { get; set; } /// <summary> /// Y轴中值 /// </summary> public double YTarget { get; set; } /// <summary> /// 最大值 /// </summary> public double YMax { get; set; } = double.NaN; /// <summary> /// 最小值 /// </summary> public double YMin { get; set; } = double.NaN; public double XMax { get; set; } = double.NaN; #endregion #region property public ChartValues<Lc_Flow> Values { get; private set; } = new ChartValues<Lc_Flow>(); /// <summary> /// 开始时间 /// </summary> public DateTime BeginTime { get; set; } /// <summary> /// 结束时间 /// </summary> public DateTime EndTime { get; set; } /// <summary> /// Values.Last().Id /// </summary> public long Id { get; private set; } = -1; public List<FlowGraphVm_Item> Items { get; } = new List<FlowGraphVm_Item>(); /// <summary> /// 总流量 /// </summary> public double TotalFlow { get; set; } #endregion #region 查询模式 /// <summary> /// 只要 Number被改变,且不为0, AutoKeepNewestTimeRemaining就会被设为10s. /// 当 AutoKeepNewestTimeRemaining = 0, Number就会被设置为0 /// </summary> public int AutoKeepNewestTimeRemaining { get; private set; } = 10; /// <summary> /// 当前就是最新的视图 /// </summary> public bool IsNewest { get; private set; } /// <summary> /// 加载中 /// </summary> public bool IsLoading { get; private set; } #endregion public FlowGraphVmUt() { #region 与数据无关界面参数 XFormatter = (x) => { int index = (int)x; if (index < Values.Count() && index >= 0) return Values[index].Time.ToString("HH:mm"); else return "??:??"; }; YFormatter = (y) => { return $"{y,6:F1}"; }; Mapper = Mappers.Xy<Lc_Flow>() .X((value, index) => { return index; }) .Y(value => { return value.Total; }); #endregion Items = new List<FlowGraphVm_Item>(); Items.Add(new FlowGraphVm_Item() { Title = "A", Value = 103.1, Visibility = Visibility.Hidden }); Items.Add(new FlowGraphVm_Item() { Title = "B", Value = 10, Visibility = Visibility.Visible }); Items.Add(new FlowGraphVm_Item() { Title = "C", Value = 23, Visibility = Visibility.Hidden }); Items.Add(new FlowGraphVm_Item() { Title = "D", Value = 46.7, Visibility = Visibility.Hidden }); Items.Add(new FlowGraphVm_Item() { Title = "E", Value = 80, Visibility = Visibility.Hidden }); for (int i = 0; i < Items.Count(); i++) { FlowGraphVm_Item s = Items[i]; s.Fill = WeighterColorDB.GetSelf(i); } TotalFlow = Items.Sum(i => i.Value); double yRangePercent = 0.3; XMax = 200; YTarget = TotalFlow; YMax = YTarget + YTarget * yRangePercent; YMax = YTarget - YTarget * yRangePercent; DateTime startTime = DateTime.Now; Random random = new Random(); for (int j = 0; j < 200; j++) { Lc_Flow lc_Flow = new Lc_Flow(); lc_Flow.ID = j; lc_Flow.Time = startTime + TimeSpan.FromSeconds(j * 10); lc_Flow.Items = new Lc_FlowItem[Items.Count()]; for (int i = 0; i < Items.Count(); i++) { lc_Flow.Items[i] = new Lc_FlowItem() { Flow = Items[i].Value * (1 + (random.NextDouble() - 0.5) * 0.10), }; } lc_Flow.Total = lc_Flow.Items.Sum(item => item.Flow); Values.Add(lc_Flow); } BeginTime = Values.First().Time; EndTime = Values.Last().Time; Id = Values.Last().ID; IsNewest = true; } public event PropertyChangedEventHandler PropertyChanged; } public class FlowGraphVm_Item : INotifyPropertyChanged { public string Title { get; set; } /// <summary> /// 颜色 /// </summary> public Brush Fill { get; set; } /// <summary> /// 显示 /// </summary> public Visibility Visibility { get; set; } /// <summary> /// 值 /// </summary> public double Value { get; set; } public int Index { get; set; } public event PropertyChangedEventHandler PropertyChanged; } public class FlowGraphParams : INotifyPropertyChanged { static FlowGraphParams current = null; public static FlowGraphParams Current { get { if (current == null) { current = new FlowGraphParams(); current.Load(); } return current; } } public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// 数据间隔 /// </summary> public int Interval { get; set; } = 6; /// <summary> /// Y轴 自动设置 MaxValue, MinValue, /// </summary> public bool IsYRangeAuto { get; set; } = true; /// <summary> /// /Y轴以% 范围显示 /// </summary> public bool IsYRangePercent { get; set; } = true; /// <summary> /// Y轴显示范围 % /// MaxValue = YTarget*(1+YRangePercent) /// MinValue = YTarget*(1-YRangePercent) /// </summary> public double YRangePercent { get; set; } = 0.15; /// <summary> /// Y轴显示范围, 中间值一定是 YTarget /// MaxValue = YTarget+YRange /// MinValue = YTarget-YRange /// </summary> public int YRange { get; set; } = 20; string FilePath; public FlowGraphParams() { string path = System.IO.Path.Combine(FlyLayout.BasePath, "flowgraph.json"); FilePath = path; } /// <summary> /// 保存 /// </summary> /// <returns></returns> public bool Save() { try { string json = Newtonsoft.Json.JsonConvert.SerializeObject(this); File.WriteAllText(FilePath, json); } catch { return false; } return true; } /// <summary> /// 读取 /// </summary> /// <returns></returns> bool Load() { try { string json = File.ReadAllText(FilePath); Newtonsoft.Json.JsonConvert.PopulateObject(json, this); return true; } catch { return false; } } } public class UiModule2_FlowGraph : MultiLayout.UiModule.IUiModule2 { /// <summary> /// 控件标题 /// 它的值取决于culture /// </summary> public string Title=> "单组称重.流量图"; public ComponentType Type => ComponentType.Graph; public bool IsUnique => false; /// <summary> /// 控件 /// 创建时,需要给它唯一ID,让加载自己的数据 /// </summary> /// <param name="id"></param> /// <returns></returns> public FrameworkElement GetComponent(int id, IUnityContainer container) { FlowGraph graph = new FlowGraph(); container.BuildUp(graph); return graph; } /// <summary> /// 控件缩略图,用于编辑界面时,大致看看 /// 创建时,需要给它唯一ID,让加载自己的数据 /// </summary> /// <param name="id"></param> /// <returns></returns> public FrameworkElement GetThumbnail() { return new System.Windows.Controls.Grid(); } /// <summary> /// 给出全部控件ID, 控件自行删除没有的参数 /// </summary> /// <param name="IDs"></param> public void MatchParam(int[] IDs) { } } }