2025-10-10 15:58:01 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using System.Windows.Controls;
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
|
|
|
|
|
|
namespace WpfApp.src.components
|
|
|
|
|
|
{
|
2025-10-11 09:21:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 刻度线数据结构
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class ScaleLine
|
|
|
|
|
|
{
|
|
|
|
|
|
public double Y1 { get; set; }
|
|
|
|
|
|
public double Y2 { get; set; }
|
|
|
|
|
|
public double X1 { get; set; }
|
|
|
|
|
|
public double X2 { get; set; }
|
|
|
|
|
|
public double StrokeThickness { get; set; }
|
|
|
|
|
|
public string Label { get; set; }
|
|
|
|
|
|
public double LabelX { get; set; }
|
|
|
|
|
|
public double LabelY { get; set; }
|
|
|
|
|
|
public bool IsMainScale { get; set; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 15:58:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// SensorChart.xaml 的交互逻辑
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public partial class SensorChart : UserControl, INotifyPropertyChanged
|
|
|
|
|
|
{
|
|
|
|
|
|
public SensorChart()
|
|
|
|
|
|
{
|
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
|
DataContext = this;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置默认值
|
|
|
|
|
|
SensorName = "传感器";
|
|
|
|
|
|
HeaderBackground = new SolidColorBrush(Color.FromRgb(230, 243, 255)); // #E6F3FF
|
|
|
|
|
|
Value = 0;
|
|
|
|
|
|
RedLineValues = new List<double>();
|
2025-10-11 09:21:00 +08:00
|
|
|
|
Level = SensorLevel.Medium; // 默认中等
|
|
|
|
|
|
|
|
|
|
|
|
// 生成初始刻度
|
|
|
|
|
|
GenerateScaleLines();
|
2025-10-10 15:58:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void OnPropertyChanged(string propertyName)
|
|
|
|
|
|
{
|
|
|
|
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region 依赖属性
|
|
|
|
|
|
|
|
|
|
|
|
// 传感器名称
|
|
|
|
|
|
public static readonly DependencyProperty SensorNameProperty =
|
|
|
|
|
|
DependencyProperty.Register("SensorName", typeof(string), typeof(SensorChart),
|
|
|
|
|
|
new PropertyMetadata("传感器", OnSensorNameChanged));
|
|
|
|
|
|
|
|
|
|
|
|
public string SensorName
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return (string)GetValue(SensorNameProperty); }
|
|
|
|
|
|
set { SetValue(SensorNameProperty, value); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnSensorNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var control = d as SensorChart;
|
|
|
|
|
|
control?.OnPropertyChanged(nameof(SensorName));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标题背景色
|
|
|
|
|
|
public static readonly DependencyProperty HeaderBackgroundProperty =
|
|
|
|
|
|
DependencyProperty.Register("HeaderBackground", typeof(Brush), typeof(SensorChart),
|
|
|
|
|
|
new PropertyMetadata(new SolidColorBrush(Color.FromRgb(230, 243, 255)), OnHeaderBackgroundChanged));
|
|
|
|
|
|
|
|
|
|
|
|
public Brush HeaderBackground
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return (Brush)GetValue(HeaderBackgroundProperty); }
|
|
|
|
|
|
set { SetValue(HeaderBackgroundProperty, value); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnHeaderBackgroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var control = d as SensorChart;
|
|
|
|
|
|
control?.OnPropertyChanged(nameof(HeaderBackground));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 数值
|
|
|
|
|
|
public static readonly DependencyProperty ValueProperty =
|
|
|
|
|
|
DependencyProperty.Register("Value", typeof(double), typeof(SensorChart),
|
|
|
|
|
|
new PropertyMetadata(0.0, OnValueChanged));
|
|
|
|
|
|
|
|
|
|
|
|
public double Value
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return (double)GetValue(ValueProperty); }
|
|
|
|
|
|
set { SetValue(ValueProperty, value); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var control = d as SensorChart;
|
|
|
|
|
|
control?.UpdateBarPosition();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 红线数值列表
|
|
|
|
|
|
public static readonly DependencyProperty RedLineValuesProperty =
|
|
|
|
|
|
DependencyProperty.Register("RedLineValues", typeof(List<double>), typeof(SensorChart),
|
|
|
|
|
|
new PropertyMetadata(new List<double>(), OnRedLineValuesChanged));
|
|
|
|
|
|
|
|
|
|
|
|
public List<double> RedLineValues
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return (List<double>)GetValue(RedLineValuesProperty); }
|
|
|
|
|
|
set { SetValue(RedLineValuesProperty, value); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnRedLineValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var control = d as SensorChart;
|
|
|
|
|
|
control?.UpdateRedLinePositions();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-11 09:21:00 +08:00
|
|
|
|
// 传感器等级
|
|
|
|
|
|
public static readonly DependencyProperty LevelProperty =
|
|
|
|
|
|
DependencyProperty.Register("Level", typeof(SensorLevel), typeof(SensorChart),
|
|
|
|
|
|
new PropertyMetadata(SensorLevel.Medium, OnLevelChanged));
|
|
|
|
|
|
|
|
|
|
|
|
public SensorLevel Level
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return (SensorLevel)GetValue(LevelProperty); }
|
|
|
|
|
|
set { SetValue(LevelProperty, value); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnLevelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var control = d as SensorChart;
|
|
|
|
|
|
control?.OnLevelChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 15:58:01 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 计算属性
|
|
|
|
|
|
|
|
|
|
|
|
private double _barTop;
|
|
|
|
|
|
public double BarTop
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _barTop; }
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
_barTop = value;
|
|
|
|
|
|
OnPropertyChanged(nameof(BarTop));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private double _barHeight;
|
|
|
|
|
|
public double BarHeight
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _barHeight; }
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
_barHeight = value;
|
|
|
|
|
|
OnPropertyChanged(nameof(BarHeight));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<double> _redLinePositions = new List<double>();
|
|
|
|
|
|
public List<double> RedLinePositions
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _redLinePositions; }
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
_redLinePositions = value;
|
|
|
|
|
|
OnPropertyChanged(nameof(RedLinePositions));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-11 09:21:00 +08:00
|
|
|
|
private List<ScaleLine> _scaleLines = new List<ScaleLine>();
|
|
|
|
|
|
public List<ScaleLine> ScaleLines
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _scaleLines; }
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
_scaleLines = value;
|
|
|
|
|
|
OnPropertyChanged(nameof(ScaleLines));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 当前等级的最小值
|
|
|
|
|
|
public double MinValue
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (Level)
|
|
|
|
|
|
{
|
|
|
|
|
|
case SensorLevel.Low: return -20;
|
|
|
|
|
|
case SensorLevel.Medium: return -40;
|
|
|
|
|
|
case SensorLevel.High: return -60;
|
|
|
|
|
|
default: return -40;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 当前等级的最大值
|
|
|
|
|
|
public double MaxValue
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (Level)
|
|
|
|
|
|
{
|
|
|
|
|
|
case SensorLevel.Low: return 20;
|
|
|
|
|
|
case SensorLevel.Medium: return 40;
|
|
|
|
|
|
case SensorLevel.High: return 60;
|
|
|
|
|
|
default: return 40;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 15:58:01 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 私有方法
|
|
|
|
|
|
|
2025-10-11 09:21:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 等级改变时的处理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void OnLevelChanged()
|
|
|
|
|
|
{
|
|
|
|
|
|
OnPropertyChanged(nameof(Level));
|
|
|
|
|
|
OnPropertyChanged(nameof(MinValue));
|
|
|
|
|
|
OnPropertyChanged(nameof(MaxValue));
|
|
|
|
|
|
GenerateScaleLines();
|
|
|
|
|
|
UpdateBarPosition();
|
|
|
|
|
|
UpdateRedLinePositions();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成刻度线
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void GenerateScaleLines()
|
|
|
|
|
|
{
|
|
|
|
|
|
var lines = new List<ScaleLine>();
|
|
|
|
|
|
double minVal = MinValue;
|
|
|
|
|
|
double maxVal = MaxValue;
|
|
|
|
|
|
|
|
|
|
|
|
// 根据等级确定刻度间隔
|
|
|
|
|
|
double mainInterval = (maxVal - minVal) / 8; // 8个主刻度间隔
|
|
|
|
|
|
double subInterval = mainInterval / 4; // 每个主刻度间隔4个小刻度
|
|
|
|
|
|
|
|
|
|
|
|
// 生成主刻度线
|
|
|
|
|
|
for (int i = 0; i <= 8; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
double value = maxVal - i * mainInterval;
|
|
|
|
|
|
double y = ValueToY(value);
|
|
|
|
|
|
|
|
|
|
|
|
lines.Add(new ScaleLine
|
|
|
|
|
|
{
|
|
|
|
|
|
X1 = 40,
|
|
|
|
|
|
Y1 = y,
|
|
|
|
|
|
X2 = 130,
|
|
|
|
|
|
Y2 = y,
|
|
|
|
|
|
StrokeThickness = 1,
|
|
|
|
|
|
Label = value.ToString("0"),
|
|
|
|
|
|
LabelX = 135,
|
|
|
|
|
|
LabelY = y - 5,
|
|
|
|
|
|
IsMainScale = true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 生成小刻度线(除了最后一个主刻度)
|
|
|
|
|
|
if (i < 8)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int j = 1; j < 4; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
double subValue = value - j * subInterval;
|
|
|
|
|
|
double subY = ValueToY(subValue);
|
|
|
|
|
|
|
|
|
|
|
|
lines.Add(new ScaleLine
|
|
|
|
|
|
{
|
|
|
|
|
|
X1 = 40,
|
|
|
|
|
|
Y1 = subY,
|
|
|
|
|
|
X2 = 125,
|
|
|
|
|
|
Y2 = subY,
|
|
|
|
|
|
StrokeThickness = 0.5,
|
|
|
|
|
|
Label = "",
|
|
|
|
|
|
LabelX = 0,
|
|
|
|
|
|
LabelY = 0,
|
|
|
|
|
|
IsMainScale = false
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ScaleLines = lines;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 15:58:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将数值转换为Y坐标位置
|
2025-10-11 09:21:00 +08:00
|
|
|
|
/// 根据当前等级动态计算刻度范围
|
2025-10-10 15:58:01 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="value">数值</param>
|
|
|
|
|
|
/// <returns>Y坐标</returns>
|
|
|
|
|
|
private double ValueToY(double value)
|
|
|
|
|
|
{
|
2025-10-11 09:21:00 +08:00
|
|
|
|
double minVal = MinValue;
|
|
|
|
|
|
double maxVal = MaxValue;
|
|
|
|
|
|
|
2025-10-10 15:58:01 +08:00
|
|
|
|
// 限制数值范围
|
2025-10-11 09:21:00 +08:00
|
|
|
|
value = Math.Max(minVal, Math.Min(maxVal, value));
|
2025-10-10 15:58:01 +08:00
|
|
|
|
|
2025-10-11 09:21:00 +08:00
|
|
|
|
// 线性映射:maxVal对应Y=20,minVal对应Y=420
|
|
|
|
|
|
double range = maxVal - minVal;
|
|
|
|
|
|
return 220 - ((value - minVal) / range - 0.5) * 400;
|
2025-10-10 15:58:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新柱状图位置
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void UpdateBarPosition()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Value >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 正值:从0线向上
|
|
|
|
|
|
BarTop = ValueToY(Value);
|
|
|
|
|
|
BarHeight = ValueToY(0) - ValueToY(Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 负值:从0线向下
|
|
|
|
|
|
BarTop = ValueToY(0);
|
|
|
|
|
|
BarHeight = ValueToY(Value) - ValueToY(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新红线位置
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void UpdateRedLinePositions()
|
|
|
|
|
|
{
|
|
|
|
|
|
var positions = new List<double>();
|
|
|
|
|
|
if (RedLineValues != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var value in RedLineValues)
|
|
|
|
|
|
{
|
|
|
|
|
|
positions.Add(ValueToY(value));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
RedLinePositions = positions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 公共方法
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置传感器数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="name">传感器名称</param>
|
|
|
|
|
|
/// <param name="value">数值</param>
|
|
|
|
|
|
/// <param name="redLines">红线数值列表</param>
|
|
|
|
|
|
/// <param name="headerColor">标题背景色</param>
|
2025-10-11 09:21:00 +08:00
|
|
|
|
/// <param name="level">传感器等级</param>
|
|
|
|
|
|
public void SetSensorData(string name, double value, List<double> redLines = null, Color? headerColor = null, SensorLevel? level = null)
|
2025-10-10 15:58:01 +08:00
|
|
|
|
{
|
|
|
|
|
|
SensorName = name;
|
|
|
|
|
|
Value = value;
|
|
|
|
|
|
|
|
|
|
|
|
if (redLines != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
RedLineValues = new List<double>(redLines);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (headerColor.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
HeaderBackground = new SolidColorBrush(headerColor.Value);
|
|
|
|
|
|
}
|
2025-10-11 09:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
if (level.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
Level = level.Value;
|
|
|
|
|
|
}
|
2025-10-10 15:58:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 添加红线
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="value">红线数值</param>
|
|
|
|
|
|
public void AddRedLine(double value)
|
|
|
|
|
|
{
|
|
|
|
|
|
var lines = new List<double>(RedLineValues) { value };
|
|
|
|
|
|
RedLineValues = lines;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 清除所有红线
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void ClearRedLines()
|
|
|
|
|
|
{
|
|
|
|
|
|
RedLineValues = new List<double>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|