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;
|
/// <summary>
|
/// 数据名
|
/// </summary>
|
public string Name { get; set; }
|
public string YAxesName { get; set; }
|
public int YAxesIndex { get; set; }
|
}
|
|
public class CustomLegend : SKDefaultLegend
|
{
|
|
protected override Layout<SkiaSharpDrawingContext> 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<SkiaSharpDrawingContext>)series.GetMiniatureGeometry(null);
|
Children = new List<IDrawnElement<SkiaSharpDrawingContext>> {
|
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<string, SeriesConfig> configCache = new Dictionary<string, SeriesConfig>();
|
|
private Dictionary<string, ISeries> seriesCache = new Dictionary<string, ISeries>();
|
private Dictionary<string, List<DateTimePoint>> valueCache = new Dictionary<string, List<DateTimePoint>>();
|
|
private Dictionary<string, double> minValueCache = new Dictionary<string, double>();
|
private Dictionary<string, double> maxValueCache = new Dictionary<string, double>();
|
|
private ObservableCollection<ISeries> series = new ObservableCollection<ISeries>();
|
public ObservableCollection<ISeries> Series
|
{
|
get => series;
|
set
|
{
|
|
Set(ref series, value);
|
}
|
}
|
|
public ObservableCollection<ICartesianAxis> YAxes { get; set; } = new ObservableCollection<ICartesianAxis>();
|
|
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<DateTimePoint>();
|
valueCache[sConfig.Name] = values;
|
|
var ls = new LineSeries<DateTimePoint>
|
{
|
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<DateTimePoint>();
|
valueCache[name] = values;
|
|
var ls = new LineSeries<DateTimePoint>
|
{
|
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<double> 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('汉')
|
//};
|
}
|
}
|
}
|
}
|