using HMI.Base; using LiveChartsCore; using LiveChartsCore.Defaults; using LiveChartsCore.Drawing; using LiveChartsCore.Drawing.Layouts; using LiveChartsCore.Kernel.Sketches; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Drawing; using LiveChartsCore.SkiaSharpView.Drawing.Geometries; using LiveChartsCore.SkiaSharpView.Drawing.Layouts; using LiveChartsCore.SkiaSharpView.Painting; using LiveChartsCore.SkiaSharpView.SKCharts; using SkiaSharp; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace Plugin.TestResult_WPF { public class SeriesConfig { public static readonly float FontSize = 12; /// /// 数据名 /// public string Name { get; set; } public string YAxesName { get; set; } public int YAxesIndex { get; set; } } public class CustomLegend : SKDefaultLegend { protected override Layout GetLayout(LiveChartsCore.Chart chart) { var stackLayout = new StackLayout { Orientation = ContainerOrientation.Horizontal, Padding = new Padding(0, 10, 0, 0), HorizontalAlignment = Align.Start, VerticalAlignment = Align.Middle, }; foreach (var series in chart.Series.Where(x => x.IsVisibleAtLegend)) { stackLayout.Children.Add(new LegendItem(series)); } return stackLayout; } } public class LegendItem : StackLayout { public LegendItem(ISeries series) { Orientation = ContainerOrientation.Horizontal; Padding = new Padding(0); VerticalAlignment = Align.Start; HorizontalAlignment = Align.Middle; Opacity = series.IsVisible ? 1 : 0.5f; var miniature = (IDrawnElement)series.GetMiniatureGeometry(null); Children = new List> { miniature, new LabelGeometry { Text = series.Name, TextSize = SeriesConfig.FontSize, Paint = new SolidColorPaint(SKColors.Black){ SKTypeface = SKFontManager.Default.MatchCharacter('汉') }, Padding = new Padding(5,0,10,0), VerticalAlign = Align.Middle, HorizontalAlign = Align.Start }}; } } public partial class LiveChart : NotifyControl { private static SKColor[] colors = new SKColor[] { //new SKColor(229, 84, 62), //new SKColor(72, 143, 239), //new SKColor(147, 196, 83), new SKColor(255, 0, 0), new SKColor(0, 0, 255), new SKColor(0, 255, 0), new SKColor(76, 184, 210), new SKColor(246, 198, 50), }; private DateTime startTime; private string configText; private bool showLegend; private bool enableResize; private SolidColorPaint tooltipTextPaint = new SolidColorPaint(SKColors.Black) { SKTypeface = SKFontManager.Default.MatchCharacter('汉') }; public SolidColorPaint TooltipTextPaint { get => tooltipTextPaint; set { Set(ref tooltipTextPaint, value); } } private double tooltipTextSize = SeriesConfig.FontSize; public double TooltipTextSize { get => tooltipTextSize; set { Set(ref tooltipTextSize, value); } } private Dictionary configCache = new Dictionary(); private Dictionary seriesCache = new Dictionary(); private Dictionary> valueCache = new Dictionary>(); private Dictionary minValueCache = new Dictionary(); private Dictionary maxValueCache = new Dictionary(); private ObservableCollection series = new ObservableCollection(); public ObservableCollection Series { get => series; set { Set(ref series, value); } } public ObservableCollection YAxes { get; set; } = new ObservableCollection(); private Axis[] xAxes; public Axis[] XAxes { get => xAxes; set { Set(ref xAxes, value); } } public DrawMarginFrame DrawMarginFrame => new DrawMarginFrame() { Fill = new SolidColorPaint(new SKColor(223, 223, 223)), Stroke = new SolidColorPaint(new SKColor(180, 180, 180), 1) }; public object Sync { get; } = new object(); private int CreateYAxis(string name) { for (int i = 0; i < YAxes.Count; i++) { if (YAxes[i].Name == name) { return i; } } if (YAxes.Count == 0) { YAxes.Add(new Axis { Name = name, MinStep = 1, TextSize = SeriesConfig.FontSize, NameTextSize = SeriesConfig.FontSize, NamePaint = new SolidColorPaint(new SKColor(0, 0, 0)) { SKTypeface = SKFontManager.Default.MatchCharacter('汉') }, Padding = new Padding(0, 0, 5, 0), LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)), SeparatorsPaint = new SolidColorPaint { Color = SKColors.Black.WithAlpha(30), StrokeThickness = 0.5f }, TicksPaint = new SolidColorPaint { Color = SKColor.Empty, StrokeThickness = 0 }, }); } else { YAxes.Add(new Axis { Name = name, Position = LiveChartsCore.Measure.AxisPosition.End, MinStep = 1, TextSize = SeriesConfig.FontSize, NameTextSize = SeriesConfig.FontSize, NamePaint = new SolidColorPaint(new SKColor(0, 0, 0)) { SKTypeface = SKFontManager.Default.MatchCharacter('汉') }, Padding = new Padding(5, 0, 0, 0), LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)), TicksPaint = new SolidColorPaint { Color = SKColor.Empty, StrokeThickness = 0 }, ShowSeparatorLines = false, }); } return YAxes.Count - 1; } public void RefreshConfig(string configStr) { configText = configStr; configCache.Clear(); seriesCache.Clear(); valueCache.Clear(); minValueCache.Clear(); maxValueCache.Clear(); Series.Clear(); YAxes.Clear(); startTime = DateTime.MinValue; try { string[] arr = configStr.Split(';'); for (int i = 0; i < arr.Length; i++) { var config = arr[i]; string[] datas = config.Split(','); var sConfig = new SeriesConfig(); if (datas.Length >= 2) { sConfig.YAxesName = datas[1]; } else { sConfig.YAxesName = datas[0]; } sConfig.Name = datas[0]; sConfig.YAxesIndex = CreateYAxis(sConfig.YAxesName); if (!string.IsNullOrEmpty(sConfig.Name)) { configCache[sConfig.Name] = sConfig; var values = new List(); valueCache[sConfig.Name] = values; var ls = new LineSeries { Name = sConfig.Name, Values = values, ScalesYAt = sConfig.YAxesIndex, Fill = null, GeometryFill = null, GeometryStroke = null, LineSmoothness = 0, Stroke = new SolidColorPaint(GetColor(i)) { StrokeThickness = 0.8f }, }; seriesCache[sConfig.Name] = ls; Series.Add(ls); } } if (showLegend) { chart.LegendPosition = LiveChartsCore.Measure.LegendPosition.Top; chart.Legend = new CustomLegend(); } chart.TooltipTextPaint = new SolidColorPaint(SKColors.Black) { SKTypeface = SKFontManager.Default.MatchCharacter('汉') }; } catch (Exception ex) { } } public LiveChart(string configStr, bool showLegend = true, bool enableResize = false) { DataContext = this; this.showLegend = showLegend; this.enableResize = enableResize; var xAxis = new DateTimeAxis(TimeSpan.FromSeconds(10), Formatter) { Padding = new Padding(0), TextSize = SeriesConfig.FontSize, NameTextSize = SeriesConfig.FontSize, CustomSeparators = GetSeparators(), AnimationsSpeed = TimeSpan.FromMilliseconds(0), SeparatorsPaint = new SolidColorPaint(SKColors.Black.WithAlpha(30)), }; XAxes = new Axis[] { xAxis }; InitializeComponent(); RefreshConfig(configStr); } private double[] GetSeparators() { if (valueCache.Count <= 0) { return new double[] { DateTime.Now.Ticks }; } if (valueCache.Values.First().Count <= 5) { //return new double[] { now.Ticks }; return valueCache.Values.First().Select(e => (double)e.DateTime.Ticks).ToArray(); } var now = valueCache.Values.First().Last().DateTime; var step = (now - startTime).TotalSeconds / 6; return new double[] { now.AddSeconds(-5*step).Ticks, now.AddSeconds(-4*step).Ticks, now.AddSeconds(-3*step).Ticks, now.AddSeconds(-2*step).Ticks, now.AddSeconds(-step).Ticks, }; } private static string Formatter(DateTime date) { //var secsAgo = (DateTime.Now - date).TotalSeconds; //return secsAgo < 1 // ? "now" // : $"{secsAgo:N0}s ago"; return date.ToString("HH:mm:ss"); } private void RefreshChart() { XAxes[0].CustomSeparators = GetSeparators(); } private double GetMaxByName(string name) { return maxValueCache.ContainsKey(name) ? maxValueCache[name] : double.MinValue; } private double GetMinByName(string name) { return minValueCache.ContainsKey(name) ? minValueCache[name] : double.MaxValue; } //public void AddData(string name, double value) //{ // var now = DateTime.Now; // if (valueCache.ContainsKey(name)) // { // var config = configCache[name]; // valueCache[name].Add(new DateTimePoint(now, value)); // minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value); // maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value); // YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1; // YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1; // RefreshChart(); // } // if (startTime == DateTime.MinValue) // { // startTime = now; // } //} private SKColor GetColor(int index) { if (index >= colors.Length) { return colors[index % colors.Length]; } return colors[index]; } private void AddNewSeries(string name, string YName) { var config = new SeriesConfig { Name = name, YAxesName = YName, YAxesIndex = CreateYAxis(YName) }; configCache[name] = config; var values = new List(); valueCache[name] = values; var ls = new LineSeries { Name = name, Values = values, ScalesYAt = config.YAxesIndex, Fill = null, GeometryFill = null, GeometryStroke = null, LineSmoothness = 0, Stroke = new SolidColorPaint(GetColor(Series.Count)) { StrokeThickness = 0.8f }, }; seriesCache[Name] = ls; Series.Add(ls); } public void AddDynamicData(string name, string YName, DateTime now, double value) { if (!valueCache.ContainsKey(name)) { AddNewSeries(name, YName); } var config = configCache[name]; valueCache[name].Add(new DateTimePoint(now, value)); minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value); maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value); YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1; YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1; RefreshChart(); if (startTime == DateTime.MinValue) { startTime = now; } } public void AddData(string name, DateTime now, double value) { if (valueCache.ContainsKey(name)) { var config = configCache[name]; valueCache[name].Add(new DateTimePoint(now, value)); minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value); maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value); YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1; YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1; RefreshChart(); } if (startTime == DateTime.MinValue) { startTime = now; } } public void Clear() { RefreshConfig(configText); } public bool CanHandle(string name) { return configCache.ContainsKey(name); } public List GetSingleData(string name) { if (valueCache.ContainsKey(name)) { return valueCache[name].Select(d => d.Value.Value).ToList(); } return null; } 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 (enableResize) { var size = FontSizeConverter.CountFontSize(12, ActualHeight, 92); foreach (var y in YAxes) { y.TextSize = size; y.NameTextSize = size; } foreach (var x in XAxes) { x.TextSize = size; x.NameTextSize = size; } TooltipTextSize = size; //TooltipTextPaint = new SolidColorPaint(SKColors.Black) //{ // SKTypeface = SKFontManager.Default.MatchCharacter('汉') //}; } } } }