自定义可移动点二维坐标轴控件
自定义可移动点二维坐标轴控件
目录
路由参数
X_YResultCollection为当前X轴对应Y轴值存储字典
public class ResultCollectionChangedEventArgs(RoutedEvent routedEvent, object source, IDictionary<double, double> resultCollection) : RoutedEventArgs(routedEvent, source)
{
public IDictionary<double, double> X_YResultCollection = resultCollection;
}
坐标轴控件定义
class CoordinateSystemControl : UserControl
{
#region 依赖属性
/// <summary>
/// 标注线颜色
/// </summary>
public Brush CalloutLineColor
{
get { return (Brush)GetValue(CalloutLineColorProperty); }
set { SetValue(CalloutLineColorProperty, value); }
}
/// <summary>
/// 坐标系颜色
/// </summary>
public Brush CoordinateSystemColor
{
get { return (Brush)GetValue(CoordinateSystemColorProperty); }
set { SetValue(CoordinateSystemColorProperty, value); }
}
/// <summary>
/// X轴名称
/// </summary>
public string X_AxisName
{
get { return (string)GetValue(X_AxisNameProperty); }
set { SetValue(X_AxisNameProperty, value); }
}
/// <summary>
/// Y轴名称
/// </summary>
public string Y_AxisName
{
get { return (string)GetValue(Y_AxisNameProperty); }
set { SetValue(Y_AxisNameProperty, value); }
}
/// <summary>
/// 选择点颜色
/// </summary>
public Brush SelectElementColor
{
get { return (Brush)GetValue(SelectElementColorProperty); }
set { SetValue(SelectElementColorProperty, value); }
}
/// <summary>
/// 选择点被选中图标
/// </summary>
public Brush SelectedElementColor
{
get { return (Brush)GetValue(SelectedElementColorProperty); }
set { SetValue(SelectedElementColorProperty, value); }
}
/// <summary>
/// Y轴箭头图标
/// </summary>
public FrameworkElement YAeeow
{
get { return (FrameworkElement)GetValue(YAeeowProperty); }
set { SetValue(YAeeowProperty, value); }
}
/// <summary>
/// X轴箭头图标
/// </summary>
public FrameworkElement XAeeow
{
get { return (FrameworkElement)GetValue(XAeeowProperty); }
set { SetValue(XAeeowProperty, value); }
}
/// <summary>
/// Y轴集合
/// </summary>
public IList<double> YCollection
{
get { return (IList<double>)GetValue(YCollectionProperty); }
set { SetValue(YCollectionProperty, value); }
}
/// <summary>
/// X轴集合
/// </summary>
public IList<double> XCollection
{
get { return (IList<double>)GetValue(XCollectionProperty); }
set { SetValue(XCollectionProperty, value); }
}
/// <summary>
/// X轴结点对应Y轴值
/// </summary>
public IDictionary<double, double> X_YResultCollection
{
get { return (IDictionary<double, double>)GetValue(X_YResultCollectionProperty); }
set { SetValue(X_YResultCollectionProperty, value); }
}
/// <summary>
/// 结果集合改变命令
/// </summary>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
#endregion
#region 路由事件
/// <summary>
/// 结果集合改变路由事件
/// </summary>
public static readonly RoutedEvent ResultCollectionChangedEvent = EventManager.RegisterRoutedEvent("ResultCollectionChanged", RoutingStrategy.Bubble, typeof(EventHandler<ResultCollectionChangedEventArgs>), typeof(CoordinateSystemControl));
public event RoutedEventHandler ResultCollectionChanged
{
add => AddHandler(ResultCollectionChangedEvent, value);
remove => RemoveHandler(ResultCollectionChangedEvent, value);
}
#endregion
#region 依赖属性注册
public static readonly DependencyProperty CalloutLineColorProperty =
DependencyProperty.Register("CalloutLineColor", typeof(Brush), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata());
public static readonly DependencyProperty CoordinateSystemColorProperty =
DependencyProperty.Register("CoordinateSystemColor", typeof(Brush), typeof(CoordinateSystemControl), new PropertyMetadata());
public static readonly DependencyProperty X_AxisNameProperty =
DependencyProperty.Register("X_AxisName", typeof(string), typeof(CoordinateSystemControl), new PropertyMetadata("X轴"));
public static readonly DependencyProperty Y_AxisNameProperty =
DependencyProperty.Register("Y_AxisName", typeof(string), typeof(CoordinateSystemControl), new PropertyMetadata("Y轴"));
public static readonly DependencyProperty SelectElementColorProperty =
DependencyProperty.Register("SelectElementColor", typeof(Brush), typeof(CoordinateSystemControl), new PropertyMetadata());
public static readonly DependencyProperty SelectedElementColorProperty =
DependencyProperty.Register("SelectedElementColor", typeof(Brush), typeof(CoordinateSystemControl), new PropertyMetadata());
public static readonly DependencyProperty YAeeowProperty =
DependencyProperty.Register("YAeeow", typeof(FrameworkElement), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata());
public static readonly DependencyProperty XAeeowProperty =
DependencyProperty.Register("XAeeow", typeof(FrameworkElement), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata());
public static readonly DependencyProperty YCollectionProperty =
DependencyProperty.Register("YCollection", typeof(IList<double>), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, YCollectionCallback));
public static readonly DependencyProperty XCollectionProperty =
DependencyProperty.Register("XCollection", typeof(IList<double>), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, XCollectionCallback));
public static readonly DependencyProperty X_YResultCollectionProperty =
DependencyProperty.Register("X_YResultCollection", typeof(IDictionary<double, double>), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, X_YResultCollectionChangedCallback));
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, CommandChangedCallback));
#endregion
#region 私有字段
private bool _isFirstLoaded = false;
//坐标系顶点留空距离
private readonly int _spacedPoints = 15;
private readonly Canvas _canvas;
//X、Y轴线
private Line _yLine;
private Line _xLine;
private Point _originPoint;
private Point _yEndpoint;
private Point _xEndpoint;
private int _ySingleDistance;
private int _xSingleDistance;
private double _proportionSize;
//选择点实时数据位置变形
private Transform _transform_textBox;
//选择点位置校准变形
private Transform _transform_chekEllipse;
//拖动选择点位置变形
private Transform _transform_clickMoveEllipse;
private QuadraticEase _easingFunction;
//选择点显示值TextBox映射
private readonly Dictionary<Ellipse, TextBox> _selectEllipse_TextBoxMap = [];
//选择点与连接线链表
private readonly LinkedList<Shape> _selectEllipse_ConnectionLines = [];
private readonly Dictionary<double, Ellipse> _xValue_EllipseMap = [];
#endregion
public CoordinateSystemControl()
{
_canvas = new Canvas() { Margin = new Thickness(30) };
_canvas.SizeChanged += Canvas_SizeChanged;
_canvas.Loaded += Canvas_Loaded;
this.AddChild(_canvas);
_transform_textBox = new TranslateTransform() { X = -18, Y = -35 };
_transform_chekEllipse = new TranslateTransform() { X = -7, Y = -5 };
_transform_clickMoveEllipse = new TransformGroup
{
Children = { new TranslateTransform() { X = -3, Y = -5 }, new ScaleTransform(1.5, 1.5) }
};
_easingFunction = new()
{
EasingMode = EasingMode.EaseOut// 设置缓动模式为EaseOut,即先快后慢
};
//初始化依赖属性
CalloutLineColor = Brushes.DarkBlue;
CoordinateSystemColor = Brushes.Black;
SelectElementColor = Brushes.BlueViolet;
SelectedElementColor = Brushes.MediumVioletRed;
}
#region 事件与回调
private void Canvas_Loaded(object sender, RoutedEventArgs e)
{
Initialize();
_isFirstLoaded = true;
}
private static void CommandChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is ICommand command)
{
var sender = d as CoordinateSystemControl;
command.CanExecuteChanged -= sender!.Command_CanExecuteChanged;
command.CanExecuteChanged += sender.Command_CanExecuteChanged;
}
}
private void Command_CanExecuteChanged(object? sender, EventArgs e)
{
IsEnabled = Command.CanExecute(null);
}
private static void YCollectionCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is ObservableCollection<double> oldValue)
{
oldValue.CollectionChanged -= YCollectionChangedCallback;
}
if (e.NewValue is ObservableCollection<double> newValue)
{
newValue.CollectionChanged += YCollectionChangedCallback;
}
void YCollectionChangedCallback(object? sender, NotifyCollectionChangedEventArgs e)
{
if (d is CoordinateSystemControl control)
{
if (control._isFirstLoaded)
{
control.Initialize();
}
}
}
}
private static void XCollectionCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is ObservableCollection<double> oldValue)
{
oldValue.CollectionChanged -= XCollectionChangedCallback;
}
if (e.NewValue is ObservableCollection<double> newValue)
{
newValue.CollectionChanged += XCollectionChangedCallback;
}
void XCollectionChangedCallback(object? sender, NotifyCollectionChangedEventArgs e)
{
if (d is CoordinateSystemControl control)
{
if (control._isFirstLoaded)
{
control.Initialize();
}
}
}
}
private static void X_YResultCollectionChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs de)
{
if (de.OldValue is ObservableDictionary<double, double> oldValue)
{
oldValue.CollectionChanged -= X_YResultCollectionChangedCallback;
}
if (de.NewValue is ObservableDictionary<double, double> newValue)
{
newValue.CollectionChanged += X_YResultCollectionChangedCallback;
}
void X_YResultCollectionChangedCallback(object? sender, NotifyCollectionChangedEventArgs e)
{
if (d is not CoordinateSystemControl control) return;
if (control.IsMouseCaptureWithin) return;
var oldValue = de.OldValue as ObservableDictionary<double, double>;
if (de.NewValue is not ObservableDictionary<double, double> newValue) return;
foreach (var item in newValue)
{
double x = item.Key;
var point = oldValue?.Where(n => n.Key == x).FirstOrDefault();
if (point is null || point.Value.Value != item.Value)
{
if (!control._xValue_EllipseMap.TryGetValue(x, out var ellipse)) continue;
double YUnit = control.CalculateYUnit(item.Value);
control.SetSelectEllipsePosition(ellipse, control.GetActualCanvasTop(YUnit));
}
}
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox)sender;
if (!textBox.IsKeyboardFocused) return;
if (!double.TryParse(textBox.Text, out double value) || !ValidateNumber(value))
{
textBox.Background = Brushes.OrangeRed;
return;
}
textBox.Background = Brushes.Transparent;
double result = CalculateYUnit(value);
var selectEllipse = _selectEllipse_TextBoxMap.Single(n => n.Value.GetHashCode() == textBox.GetHashCode()).Key;
var canvasTop = GetActualCanvasTop(result);
SetSelectEllipsePosition(selectEllipse, canvasTop);
StoragePosition(selectEllipse);
bool ValidateNumber(double value)
{
return value >= 0 && value <= YCollection.Last();
}
}
private void SelectEllipse_Control_MouseUp(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Released)
{
Mouse.Capture(null);
var element = (Ellipse)sender;
element.RenderTransform = _transform_chekEllipse;
element.Fill = SelectElementColor;
StoragePosition(element);
RaiseEventInvokeCommand();
}
e.Handled = true;
}
private void SelectEllipse_Control_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && Mouse.Captured == sender)
{
var ellipse = (Ellipse)sender;
var position = ValidataMouseYValue(e.GetPosition(_canvas).Y - 5);
SetSelectEllipsePosition(ellipse, position);
}
e.Handled = true;
}
private void SelectEllipse_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Mouse.Capture((IInputElement)sender);
var element = (Ellipse)sender;
element.RenderTransform = _transform_clickMoveEllipse;
element.Fill = SelectedElementColor;
}
e.Handled = true;
}
#endregion
void Initialize()
{
YCollection ??= [1, 2, 3, 4, 5, 6, 7, 8, 9];
XCollection ??= [1, 2, 3, 4, 5, 6, 7, 8, 9];
//清理缓存数据
_selectEllipse_TextBoxMap.Clear();
_selectEllipse_ConnectionLines.Clear();
_xValue_EllipseMap.Clear();
if (_canvas.Children.Count > 0) _canvas.Children.RemoveRange(0, _canvas.Children.Count);
_originPoint = new Point(0, _canvas.ActualHeight);
_yEndpoint = new Point(_originPoint.X, _spacedPoints + 7);
_xEndpoint = new Point(_canvas.ActualWidth - _spacedPoints - X_AxisName.Length * 7, _originPoint.Y);
_ySingleDistance = CalculateSingleDistance(Math.Abs(_originPoint.Y - _yEndpoint.Y), YCollection);
_xSingleDistance = CalculateSingleDistance(Math.Abs(_originPoint.X - _xEndpoint.X), XCollection);
_proportionSize = 8;
_yLine = new Line() { Stroke = CoordinateSystemColor, StrokeThickness = 2, X1 = _yEndpoint.X, Y1 = _yEndpoint.Y, X2 = _originPoint.X, Y2 = _originPoint.Y };
_xLine = new Line() { Stroke = CoordinateSystemColor, StrokeThickness = 2, X1 = _originPoint.X, Y1 = _originPoint.Y, X2 = _xEndpoint.X, Y2 = _xEndpoint.Y };
//给线条添加动画
_yLine.BeginAnimation(Line.Y1Property, new DoubleAnimation(_originPoint.Y, _yEndpoint.Y, TimeSpan.FromSeconds(1)));
_xLine.BeginAnimation(Line.X2Property, new DoubleAnimation(_originPoint.X, _xEndpoint.X, TimeSpan.FromSeconds(1)));
_canvas.Children.Add(_yLine);
_canvas.Children.Add(_xLine);
var y_AxisText = new TextBlock { Text = Y_AxisName };
var x_AxisText = new TextBlock { Text = X_AxisName };
_canvas.Children.Add(y_AxisText);
_canvas.Children.Add(x_AxisText);
Canvas.SetTop(y_AxisText, _yEndpoint.Y - _spacedPoints);
Canvas.SetLeft(y_AxisText, _yEndpoint.X + _spacedPoints);
Canvas.SetTop(x_AxisText, _xEndpoint.Y - _spacedPoints * 2);
Canvas.SetLeft(x_AxisText, _xEndpoint.X);
y_AxisText.BeginAnimation(Canvas.TopProperty, new DoubleAnimation(_originPoint.Y, _yEndpoint.Y - _spacedPoints, TimeSpan.FromSeconds(1))
{
EasingFunction = _easingFunction
});
x_AxisText.BeginAnimation(Canvas.LeftProperty, new DoubleAnimation(_originPoint.X, _xEndpoint.X + _spacedPoints, TimeSpan.FromSeconds(1))
{
EasingFunction = _easingFunction
});
//加入坐标轴图标
AddAeeow();
//添加标注线和标注值和选择点
AddCalloutLine();
}
private void AddAeeow()
{
YAeeow ??= new Path()
{
Data = new PathGeometry()
{
Figures = [new PathFigure(){
IsClosed = true,
StartPoint = new Point(0, 0),
Segments = [new LineSegment(new Point(-_proportionSize, _proportionSize/2), true),
new LineSegment(new Point(0, -_proportionSize), true),
new LineSegment(new Point(_proportionSize, _proportionSize/2), true)]
}]
},
Fill = CoordinateSystemColor
};
XAeeow ??= new Path()
{
Data = new PathGeometry()
{
Figures = [new PathFigure() {
IsClosed = true,
StartPoint = new Point(0, 0),
Segments = [new LineSegment(new Point(-_proportionSize/2, _proportionSize), true),
new LineSegment(new Point(_proportionSize, 0), true),
new LineSegment(new Point(-_proportionSize/2, -_proportionSize), true)]
}]
},
Fill = CoordinateSystemColor
};
_canvas.Children.Add(YAeeow);
_canvas.Children.Add(XAeeow);
Canvas.SetTop(this.YAeeow, _yEndpoint.Y);
Canvas.SetLeft(this.YAeeow, _yEndpoint.X);
Canvas.SetTop(this.XAeeow, _xEndpoint.Y);
Canvas.SetLeft(this.XAeeow, _xEndpoint.X);
XAeeow.BeginAnimation(Canvas.LeftProperty, new DoubleAnimation(_originPoint.X, _xEndpoint.X, TimeSpan.FromSeconds(1)));
YAeeow.BeginAnimation(Canvas.TopProperty, new DoubleAnimation(_originPoint.Y, _yEndpoint.Y, TimeSpan.FromSeconds(1)));
}
private void AddCalloutLine()
{
//添加Y轴标注线和标注值
AddYCalloutLines();
//添加X轴标注线和标注值和选择点
AddXCalloutLinesAndSelectEllipses();
}
private void AddYCalloutLines()
{
for (int i = 1; i < YCollection.Count + 1; i++)
{
var canvasTop = GetActualCanvasTop(i);
//添加标注线和标注值
var line = new Line() { Stroke = CalloutLineColor, StrokeThickness = 1, X1 = 0, Y1 = 0, X2 = _proportionSize, Y2 = 0 };
var text = new TextBlock() { Text = YCollection[i - 1].ToString(), RenderTransformOrigin = new(0.5, 0.5), RenderTransform = new TranslateTransform() { X = -16, Y = -13 } };
// 添加动画
var time = TimeSpan.FromSeconds(i * 0.15);
SetAnimation(text, time);
SetAnimation(line, time);
Canvas.SetLeft(line, _originPoint.X);
Canvas.SetTop(line, canvasTop);
Canvas.SetLeft(text, _originPoint.X);
Canvas.SetTop(text, canvasTop);
_canvas.Children.Add(line);
_canvas.Children.Add(text);
}
}
private void AddXCalloutLinesAndSelectEllipses()
{
for (int i = 1; i < XCollection.Count + 1; i++)
{
var currentValue = XCollection[i - 1];
var cancasLeft = GetActualCanvasLeft(i);
var line = new Line() { Stroke = CalloutLineColor, StrokeThickness = 1, X1 = 0, Y1 = 0, X2 = 0, Y2 = -_proportionSize };
var textBlock = new TextBlock() { Text = currentValue.ToString(), RenderTransformOrigin = new(0.5, 0.5), RenderTransform = new TranslateTransform() { X = -5, Y = 5 } };
Canvas.SetLeft(line, cancasLeft);
Canvas.SetTop(line, _originPoint.Y);
Canvas.SetLeft(textBlock, cancasLeft);
Canvas.SetTop(textBlock, _originPoint.Y);
_canvas.Children.Add(textBlock);
_canvas.Children.Add(line);
//添加选择点
var selectEllipse = new Ellipse
{
Width = 14,
Height = 14,
Fill = SelectElementColor,
RenderTransformOrigin = new Point(0.5, 0.5),
RenderTransform = _transform_chekEllipse,
};
selectEllipse.AddHandler(MouseDownEvent, new MouseButtonEventHandler(SelectEllipse_MouseDown));
selectEllipse.AddHandler(MouseMoveEvent, new MouseEventHandler(SelectEllipse_Control_MouseMove));
selectEllipse.AddHandler(MouseUpEvent, new MouseButtonEventHandler(SelectEllipse_Control_MouseUp));
X_YResultCollection ??= new ObservableDictionary<double, double>();
bool excist = X_YResultCollection.Any(n => n.Key == currentValue);
string text = excist ? X_YResultCollection!.Single(n => n.Key == currentValue).Value.ToString("F2") : string.Empty;
var textBox = new TextBox() { Text = text, Background = Brushes.Transparent, BorderThickness = new(0), RenderTransform = _transform_textBox };
textBox.TextChanged += TextBox_TextChanged;
//添加动画
TimeSpan time = TimeSpan.FromSeconds(i * 0.15);
SetAnimation(selectEllipse, time);
SetAnimation(textBox, time);
SetAnimation(line, time);
SetAnimation(textBlock, time);
_canvas.Children.Add(selectEllipse);
_canvas.Children.Add(textBox);
_selectEllipse_TextBoxMap.Add(selectEllipse, textBox);
_xValue_EllipseMap.Add(currentValue, selectEllipse);
AddSelectEllipse(selectEllipse, i, currentValue);
AddConnectionLine(selectEllipse, i == XCollection.Count);
}
}
private void AddConnectionLine(Ellipse selectEllipse, bool isLast = false)
{
if (_selectEllipse_ConnectionLines.Count == 0)
{
_selectEllipse_ConnectionLines.AddFirst(selectEllipse);
}
else
{
var lastConnectionLine = (Line)_selectEllipse_ConnectionLines.Last!.Value;
lastConnectionLine.X2 = Canvas.GetLeft(selectEllipse);
lastConnectionLine.Y2 = Canvas.GetTop(selectEllipse);
_selectEllipse_ConnectionLines.AddLast(selectEllipse);
}
if (isLast) return;
var ConnectionLine = new Line() { StrokeThickness = 1, X1 = Canvas.GetLeft(selectEllipse), Y1 = Canvas.GetTop(selectEllipse), X2 = 0, Y2 = 0, Stroke = Brushes.LightGray };
_selectEllipse_ConnectionLines.AddLast(ConnectionLine);
_canvas.Children.Add(ConnectionLine);
//将选择点置于最上层
Panel.SetZIndex(selectEllipse, 1);
}
private void SetAnimation(UIElement uIElement, TimeSpan durationTime)
{
uIElement.Opacity = 0;
var fadeInAnimation = new DoubleAnimation()
{
From = 0,
To = 1,
Duration = durationTime,
};
uIElement.BeginAnimation(OpacityProperty, fadeInAnimation);
}
private void ChangeConnectionPosition(Ellipse ellipse, double canvasTop)
{
var linkedNode = _selectEllipse_ConnectionLines.Find(ellipse);
if (linkedNode?.Previous != null)
{
var previousLine = (Line)linkedNode.Previous.ValueRef;
previousLine.Y2 = canvasTop;
}
if (linkedNode?.Next != null)
{
var nextLine = (Line)linkedNode.Next.ValueRef;
nextLine.Y1 = canvasTop;
}
}
private double ValidataMouseYValue(double yPosition)
{
double MaxYValue = _originPoint.Y - _ySingleDistance * YCollection.Count;
if (yPosition < MaxYValue)
{
yPosition = MaxYValue;
}
if (yPosition > _originPoint.Y)
{
yPosition = _originPoint.Y;
}
return yPosition;
}
private void StoragePosition(Ellipse ellipse)
{
var xUnit = Math.Abs(_originPoint.X - Canvas.GetLeft(ellipse)) / _xSingleDistance;
var xValue = XCollection[(int)xUnit - 1];
var yUnit = (_originPoint.Y - Canvas.GetTop(ellipse)) / _ySingleDistance;
double yValue = CalculateYValue(yUnit);
X_YResultCollection[xValue] = Math.Round(yValue, 2);
}
private double CalculateYValue(double yunit)
{
var yArray = YCollection.ToArray();
bool minFlag = yunit >= 1;
if (yunit >= yArray.Length) return yArray.Last();
int lastValueIndex = (int)yunit - 1;
double lastValue = minFlag ? yArray[lastValueIndex] : 0;
double nextValue = yArray[lastValueIndex + 1];
return lastValue + (yunit - lastValueIndex - 1) * (nextValue - lastValue);
}
private double CalculateYUnit(double yValue)
{
double lastValue = 0;
int i = -1;
foreach (var item in YCollection)
{
i++;
if (item < yValue)
{
lastValue = item;
continue;
}
return i + (yValue - lastValue) / (item - lastValue);
}
return 0;
}
private void AddSelectEllipse(Ellipse ellipse, double xUnit, double xValue)
{
if (X_YResultCollection.Where(n => n.Key == xValue).Any())
{
var yValue = X_YResultCollection.Single(n => n.Key == xValue).Value;
double yUnit = CalculateYUnit(yValue);
SetSelectEllipsePosition(ellipse, GetActualCanvasTop(yUnit), GetActualCanvasLeft(xUnit));
}
else
{
X_YResultCollection.Add(xValue, 1);
AddSelectEllipse(ellipse, xUnit, xValue);
}
}
private void SetSelectEllipsePosition(Ellipse ellipse, double top, double? left = null)
{
Canvas.SetTop(ellipse, top);
var textBox = _selectEllipse_TextBoxMap[ellipse];
Canvas.SetTop(textBox, top);
//改变对应textblock值
var yUnit = (_originPoint.Y - top) / _ySingleDistance;
double yValue = CalculateYValue(yUnit);
textBox.Text = yValue.ToString("F2");
//改变连接线位置
ChangeConnectionPosition(ellipse, top);
if (left.HasValue)
{
Canvas.SetLeft(ellipse, (double)left);
Canvas.SetLeft(textBox, (double)left);
}
}
private int CalculateSingleDistance(double totalDistance, IEnumerable<double> values)
{
var result = (totalDistance - _spacedPoints) / values.Count();
return result < 1 ? 1 : (int)result;
}
private void Canvas_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.PreviousSize.Width != 0 && e.PreviousSize.Height != 0)
{
Initialize();
}
e.Handled = true;
}
private double GetActualCanvasLeft(double unit) => _originPoint.X + _xSingleDistance * unit;
private double GetActualCanvasTop(double unit) => _originPoint.Y - _ySingleDistance * unit;
private void RaiseEventInvokeCommand()
{
ResultCollectionChangedEventArgs args = new(ResultCollectionChangedEvent, this, X_YResultCollection);
RaiseEvent(args);
if (Command?.CanExecute(null) ?? true)
{
Command?.Execute(X_YResultCollection);
}
}
public static double CalculateYValue(Dictionary<double, double> dic, double xValue)
{
double lastX = 0;
double lastY = 0;
foreach (var point in dic)
{
if (xValue > point.Key)
{
lastX = point.Key;
lastY = point.Value;
continue;
}
else if (xValue == point.Key)
{
return point.Value;
}
return (point.Value - lastY) / (point.Key - lastX) * (xValue - lastX) / (point.Key - lastX) + lastY;
}
return dic[dic.Max(n => n.Key)];
}
}
Demo
在拖动完选择点后会触发命令和抛出路由事件
- View
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="400" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:CoordinateSystemControl
Command="{Binding TestCommand}"
X_AxisName="X"
XCollection="{Binding XCollection}"
YCollection="{Binding YCollection}"
X_YResultCollection="{Binding Datas}"
Y_AxisName="Y" />
<Button
Grid.Row="1"
Width="40"
Height="20"
Command="{Binding ClickCommand}"
Content="click me" />
</Grid>
- ViewModel
public ObservableCollection<double> XCollection { get; set; } = [1, 3, 5, 7, 9];
public ObservableCollection<double> YCollection { get; set; } = [1, 3, 5, 7, 9];
public ObservableDictionary<double, double> Datas { get; set; } = [];
//构造函数
public ViewModel()
{
Datas.Add(1, 1);
Datas.Add(3, 1);
Datas.Add(5, 1);
Datas.Add(7, 1);
Datas.Add(9, 1);
}
[RelayCommand]
public async Task Test(IDictionary<double, double> data)
{
}
[RelayCommand]
private void Click()
{
Datas[1.0] = 5;
}
注意
ObservableDictionary可查看[https://www.cnblogs.com/yinyuessh/p/18205333]
希望能给你带来帮助 --来自.net菜鸡粉丝的祝愿