using log4net.Repository.Hierarchy; using Plugin.TestResult_WPF.Model; using ScottPlot; using ScottPlot.Plottables; using ScottPlot.TickGenerators; using System; using System.Collections.Generic; using System.Data.SqlTypes; using System.Linq; using System.Text; using System.Timers; using System.Windows.Controls; using System.Windows.Documents; using System.Xml.Linq; namespace Plugin.TestResult_WPF { /// /// Chart.xaml 的交互逻辑 /// /// /// Chart.xaml 的交互逻辑 /// public partial class Chart : UserControl { private readonly int maxSize; private readonly Queue xData; private readonly Queue yData; private readonly Dictionary> xValues; private readonly Dictionary> yValues; private readonly Dictionary maxValue = new Dictionary(); private readonly Dictionary minValue = new Dictionary(); private readonly string title; private readonly bool multi; private readonly int refreshInterval; private double maxYValue = -1; private double minYValue = double.MaxValue; private float fontSize = -1; private Timer timer; private LinePlot[] childrenPlot; private readonly Dictionary loggers = new Dictionary(); private DataLogger InitLogger() { DataLogger logger = chart.Plot.Add.DataLogger(); //logger.ViewSlide(maxSize * 5); logger.ViewFull(); //logger.Add(new Coordinates(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), 0)); return logger; } public Chart(string title, bool multi, float titleSize) { BaseConfig config = new BaseConfig(); this.title = title; this.maxSize = config.MaxSize; this.multi = multi; this.refreshInterval = config.RefreshInterval; this.fontSize = titleSize; InitializeComponent(); if (this.multi) { xValues = new Dictionary>(); yValues = new Dictionary>(); //chart.Plot.ShowLegend(Alignment.UpperLeft, Orientation.Vertical); var legend = chart.Plot.ShowLegend(Edge.Top); legend.Alignment = Alignment.UpperLeft; legend.Padding = new PixelPadding(0); } else { xData = new Queue(maxSize); yData = new Queue(maxSize); } chart.Plot.Axes.Left.TickLabelStyle.FontSize = 12; chart.Plot.Axes.Left.Label.FontSize = 15; chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = 12; chart.Plot.Axes.Bottom.TickLabelStyle.IsVisible = false; chart.Plot.Axes.Bottom.MajorTickStyle.Color = Colors.Transparent; chart.Plot.Axes.Bottom.MinorTickStyle.Color = Colors.Transparent; /// 禁用鼠标 chart.UserInputProcessor.Disable(); //chart.Plot.Layout.Frameless(); //Streamer1 = chart.Plot.Add.DataStreamer(maxSize); //Streamer1.ViewScrollLeft(); //Streamer1.Color = Colors.Green; //chart.Plot.Add.Scatter(xData.ToArray(), yData.ToArray()); //string CustomFormatter(DateTime dt) //{ // return dt.ToString("HH:mm:ss.fff"); //} //var axis = chart.Plot.Axes.DateTimeTicksBottom(); //var tickGen = (DateTimeAutomatic)axis.TickGenerator; //tickGen.LabelFormatter = CustomFormatter; //chart.Plot.Axes.Bottom.TickGenerator = new DateTimeAutomatic(); //chart.Plot.Axes.DateTimeTicksBottom(); //chart.Plot.RenderManager.RenderStarting += (s, e) => //{ // try // { // Tick[] ticks = chart.Plot.Axes.Bottom.TickGenerator.Ticks; // for (int i = 0; i < ticks.Length; i++) // { // long ms = ticks[i].Position < 0 ? 0 : (long)ticks[i].Position; // DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)).AddMilliseconds(ms); // string label = $"{dt:hh:mm:ss.fff}"; // ticks[i] = new Tick(ticks[i].Position, label); // } // } // catch (Exception ex) // { // } //}; string CustomFormatter(double position) { if (position > 0) return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)).AddMilliseconds(position).ToString("HH:mm:ss"); return ""; } EvenlySpacedMinorTickGenerator minorTickGen = new EvenlySpacedMinorTickGenerator(10); NumericAutomatic myTickGenerator = new NumericAutomatic() { LabelFormatter = CustomFormatter, MinorTickGenerator = minorTickGen }; chart.Plot.Axes.Bottom.TickGenerator = myTickGenerator; if (!string.IsNullOrEmpty(this.title)) { chart.Plot.Axes.Left.Label.Text = this.title; chart.Plot.Axes.Left.Label.Bold = false; chart.Plot.Font.Automatic(); //RefreshData(); } /// 设置刷新定时器 //refreshTimer = new System.Timers.Timer() { Interval = this.refreshInterval, AutoReset = true }; //refreshTimer.Elapsed += RefreshTimer_Elapsed; //chart.Plot.DataBackground.Color = new Color(0, 0, 0); //chart.Plot.Grid.MajorLineColor = Colors.Green.WithOpacity(.6); //chart.Plot.Grid.MajorLineWidth = 1; //chart.Plot.Grid.MinorLineColor = Colors.Gray.WithOpacity(.3); //chart.Plot.Grid.MinorLineWidth = 1; //chart.Plot.Axes.Bottom.TickLabelStyle.IsVisible = false; //chart.Plot.Axes.Bottom.MajorTickStyle.Length = 0; //chart.Plot.Axes.Left.TickLabelStyle.IsVisible = false; //chart.Plot.Axes.Left.MajorTickStyle.Length = 0; //chart.Plot.Axes.Bottom.FrameLineStyle.Width = 0; //chart.Plot.Axes.Left.FrameLineStyle.Width = 0; //chart.Plot.Axes.Top.FrameLineStyle.Width = 0; //chart.Plot.Axes.Right.FrameLineStyle.Width = 0; timer = new Timer(); timer.Interval = this.refreshInterval; timer.Elapsed += Timer_Elapsed; } public Chart(string title, string[] serials, float titleSize) { BaseConfig config = new BaseConfig(); this.title = title; this.multi = true; this.maxSize = config.MaxSize; this.refreshInterval = config.RefreshInterval; fontSize = titleSize; childrenPlot = new LinePlot[serials.Length]; InitializeComponent(); xValues = new Dictionary>(); yValues = new Dictionary>(); //chart.Plot.ShowLegend(Alignment.UpperLeft, Orientation.Vertical); var panel = chart.Plot.ShowLegend(Edge.Top); panel.Alignment = Alignment.UpperLeft; panel.Padding = new PixelPadding(0); chart.Plot.Axes.Left.TickLabelStyle.FontSize = titleSize - 3; chart.Plot.Axes.Left.Label.FontSize = titleSize; chart.Plot.Axes.Right.TickLabelStyle.FontSize = titleSize - 3; chart.Plot.Axes.Right.Label.FontSize = titleSize; chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = titleSize - 3; chart.Plot.Axes.Bottom.TickLabelStyle.IsVisible = false; chart.Plot.Axes.Bottom.MajorTickStyle.Color = Colors.Transparent; chart.Plot.Axes.Bottom.MinorTickStyle.Color = Colors.Transparent; /// 禁用鼠标 chart.UserInputProcessor.Disable(); string CustomFormatter(double position) { if (position > 0) return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)).AddMilliseconds(position).ToString("HH:mm:ss"); return ""; } chart.Plot.HideLegend(); EvenlySpacedMinorTickGenerator minorTickGen = new EvenlySpacedMinorTickGenerator(10); NumericAutomatic myTickGenerator = new NumericAutomatic() { LabelFormatter = CustomFormatter, MinorTickGenerator = minorTickGen }; chart.Plot.Axes.Bottom.TickGenerator = myTickGenerator; if (!string.IsNullOrEmpty(serials[0])) { var newLine = chart.Plot.Add.Line(Coordinates.NaN, Coordinates.NaN); newLine.Axes.YAxis = chart.Plot.Axes.Left; newLine.Axes.XAxis = chart.Plot.Axes.Bottom; childrenPlot[0] = newLine; chart.Plot.Axes.Left.Label.Text = serials[0]; chart.Plot.Axes.Left.Label.Bold = false; chart.Plot.Font.Automatic(); } if (!string.IsNullOrEmpty(serials[1])) { var newLine = chart.Plot.Add.Line(Coordinates.NaN, Coordinates.NaN); newLine.Axes.YAxis = chart.Plot.Axes.Right; newLine.Axes.XAxis = chart.Plot.Axes.Bottom; childrenPlot[1] = newLine; chart.Plot.Axes.Right.Label.Text = serials[1]; chart.Plot.Axes.Right.Label.Bold = false; chart.Plot.Font.Automatic(); } timer = new Timer(); timer.Interval = this.refreshInterval; timer.Elapsed += Timer_Elapsed; } private void Timer_Elapsed(object sender, ElapsedEventArgs e) { if (maxYValue > 0) { RefreshData(); } } private DataLogger GetLogger(string name, out bool isNew, string legend = null) { isNew = false; if (!loggers.ContainsKey(name)) { var logger = InitLogger(); if (legend != null) logger.LegendText = name; loggers.Add(name, logger); isNew = true; } return loggers[name]; } public void RefreshData() { bool flag = timer.Enabled; try { timer.Enabled = false; foreach (var logger in loggers.Values) { logger.ViewFull(); } /// 限制y轴 if (maxYValue > -1) { chart.Plot.Axes.SetLimitsY(minYValue > 5 ? minYValue - 5 : 0, maxYValue + 5); } else { chart.Plot.Axes.SetLimitsY(0, 100); } chart.Refresh(); } finally { timer.Enabled = flag; } } public void Clear() { this.maxYValue = -1; this.minYValue = double.MaxValue; foreach (var name in loggers.Keys) { loggers[name].Clear(); xValues?[name]?.Clear(); yValues?[name]?.Clear(); } xData?.Clear(); yData?.Clear(); chart.Refresh(); } public List GetSingleData() { bool flag = timer.Enabled; try { timer.Enabled = false; return yData?.ToList(); } finally { timer.Enabled = flag; } } private void InsertData(Queue xAxis, Queue yAxis, long x, double y) { if (xAxis.Count >= maxSize) { xAxis.Dequeue(); yAxis.Dequeue(); } xAxis.Enqueue(x); yAxis.Enqueue(y); } public void AddData(double y) { if (this.multi) { return; } var commonName = "Single"; this.maxYValue = y > maxYValue ? y : maxYValue; this.minYValue = y < minYValue ? y : minYValue; var nowTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); DataLogger singleLogger = GetLogger(commonName, out _); singleLogger.Color = Colors.Red; InsertData(xData, yData, nowTime, y); singleLogger.Add(new Coordinates(nowTime, y)); //Streamer1.Add(new Coordinates(DateTimeOffset.Now.ToUnixTimeSeconds(), y)); chart.Plot.GetPlottables() .ToList() .ForEach(m => m.X -= 1); chart.Plot.GetPlottables() .Where(m => m.X < 0) .ToList() .ForEach(m => chart.Plot.Remove(m)); } public void AddData(string name, double y) { if (!this.multi) { return; } this.maxYValue = y > maxYValue ? y : maxYValue; this.minYValue = y < minYValue ? y : minYValue; DataLogger logger = null; if (loggers.ContainsKey(name)) { logger = loggers[name]; } else { logger = InitLogger(); logger.LegendText = name; loggers.Add(name, logger); var xAxis = new Queue(maxSize); var yAxis = new Queue(maxSize); xValues.Add(name, xAxis); yValues.Add(name, yAxis); } var x = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); logger.Add(new Coordinates(x, y)); InsertData(xValues[name], yValues[name], x, y); //Streamer1.Add(new Coordinates(DateTimeOffset.Now.ToUnixTimeSeconds(), y)); chart.Plot.GetPlottables() .ToList() .ForEach(m => m.X -= 1); chart.Plot.GetPlottables() .Where(m => m.X < 0) .ToList() .ForEach(m => chart.Plot.Remove(m)); } public void AddData(int serials, string name, double y) { if (!this.multi) { return; } var plot = childrenPlot[serials]; DataLogger logger = GetLogger(name, out bool isNew, name); this.maxYValue = y > maxYValue ? y : maxYValue; this.minYValue = y < minYValue ? y : minYValue; logger.Axes.YAxis = plot.Axes.YAxis; logger.Axes.XAxis = plot.Axes.XAxis; if (name == "水箱温度") { logger.Color = Colors.Red; } else if (name == "回水温度") { logger.Color = Colors.Blue; } else { logger.Color = Colors.Green; } if (isNew) { var xAxis = new Queue(maxSize); var yAxis = new Queue(maxSize); xValues.Add(name, xAxis); yValues.Add(name, yAxis); } var x = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); logger.Add(new Coordinates(x, y)); InsertData(xValues[name], yValues[name], x, y); //Streamer1.Add(new Coordinates(DateTimeOffset.Now.ToUnixTimeSeconds(), y)); chart.Plot.GetPlottables() .ToList() .ForEach(m => m.X -= 1); chart.Plot.GetPlottables() .Where(m => m.X < 0) .ToList() .ForEach(m => chart.Plot.Remove(m)); } private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e) { //refreshTimer.Start(); timer?.Start(); } private void UserControl_Unloaded(object sender, System.Windows.RoutedEventArgs e) { timer?.Stop(); } private void UserControl_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e) { if (this.multi) return; if (fontSize < 0) { chart.Plot.Axes.Left.Label.FontSize = (float)FontSizeConverter.CountFontSize(15, ActualHeight, 92); chart.Plot.Axes.Left.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(10, ActualHeight, 92); chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(10, ActualHeight, 92); } else { chart.Plot.Axes.Left.Label.FontSize = fontSize - 3; chart.Plot.Axes.Left.TickLabelStyle.FontSize = fontSize - 3; chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = fontSize; } } } }