You could try to refer the following code to your project.
MainWindow.xaml:
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="1" Margin="10,10,10,10">
<StackPanel>
<TextBlock Text="StartPoint" />
<Grid Margin="10,1,10,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="60" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">x:</TextBlock>
<TextBox Grid.Column="1" Text="{Binding FirstPointX}" />
</Grid>
<Grid Margin="10,1,10,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="60" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Y:</TextBlock>
<TextBox Grid.Column="1" Text="{Binding FirstPointY}" />
</Grid>
</StackPanel>
</Grid>
<Grid Grid.Row="2" Margin="10,10,10,10">
<StackPanel>
<TextBlock Text="EndPoint" />
<Grid Margin="10,1,10,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="60" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">x:</TextBlock>
<TextBox Grid.Column="1" Text="{Binding SecondPointX}" />
</Grid>
<Grid Margin="10,1,10,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="60" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Y:</TextBlock>
<TextBox Grid.Column="1" Text="{Binding SecondPointY}" />
</Grid>
</StackPanel>
</Grid>
<Grid Grid.Row="3" Margin="10,10,10,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="60" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">frequency</TextBlock>
<TextBox Grid.Column="1" Text="{Binding WaveLength}" />
</Grid>
</Grid>
<Grid Grid.Row="4" Margin="10,10,10,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="60" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">amplitude</TextBlock>
<TextBox Grid.Column="1" Text="{Binding WaveHeight}" />
</Grid>
</Grid>
<Grid Grid.Row="5" Margin="10,10,10,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="60" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">increase</TextBlock>
<TextBox Grid.Column="1" Text="{Binding CurveSquaring}" />
</Grid>
</Grid>
<Grid Grid.Row="6" Margin="10,10,10,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="60" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">line width</TextBlock>
<TextBox Grid.Column="1" Text="{Binding BorderThickness}" />
</Grid>
</Grid>
<Button x:Name="draw" Grid.Row="7" Margin="10,10,10,10" Content="DrawWavyLine" Click="DrawWaveLine_OnClick" />
<Button x:Name="remove" Grid.Row="8" Margin="10,10,10,10" Content="RemoveWavyLine" Click="CleanGrid_OnClick" />
</Grid>
<Grid Grid.Column="1" x:Name="WaveLinePanel">
</Grid>
</Grid>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Media;
namespace WavyLineDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel = (ViewModel)DataContext;
}
public ViewModel ViewModel { get; set; }
private void DrawWaveLine_OnClick(object sender, RoutedEventArgs e)
{
var waveLine = ViewModel.Draw();
WaveLinePanel.Children.Add(waveLine);
}
private void CleanGrid_OnClick(object sender, RoutedEventArgs e)
{
WaveLinePanel.Children.Clear();
}
}
public class WaveLine : UIElement
{
public WaveLine()
{
_waveLineList = new VisualCollection(this);
}
public double BorderThickness { get; set; } = 1.0;
public double WaveLength { get; set; } = 4;
public double WaveHeight { get; set; } = 5;
public double CurveSquaring { get; set; } = 0.57;
protected override int VisualChildrenCount => _waveLineList.Count;
public void DrawWaveLine(Point startPoint, Point endPoint)
{
var p1 = startPoint;
var p2 = endPoint;
var distance = (p1 - p2).Length;
var angle = CalculateAngle(p1, p2);
var waveLength = WaveLength;
var waveHeight = WaveHeight;
var howManyWaves = distance / waveLength;
var waveInterval = distance / howManyWaves;
var maxBcpLength =
Math.Sqrt(waveInterval / 4.0 * (waveInterval / 4.0) + waveHeight / 2.0 * (waveHeight / 2.0));
var curveSquaring = CurveSquaring;
var bcpLength = maxBcpLength * curveSquaring;
var bcpInclination = CalculateAngle(new Point(0, 0), new Point(waveInterval / 4.0, waveHeight / 2.0));
var wigglePoints = new List<(Point bcpOut, Point bcpIn, Point anchor)>();
var prevFlexPt = p1;
var polarity = 1;
for (var waveIndex = 0; waveIndex < howManyWaves * 2; waveIndex++)
{
var bcpOutAngle = angle + bcpInclination * polarity;
var bcpOut = new Point(prevFlexPt.X + Math.Cos(bcpOutAngle) * bcpLength,
prevFlexPt.Y + Math.Sin(bcpOutAngle) * bcpLength);
var flexPt = new Point(prevFlexPt.X + Math.Cos(angle) * waveInterval / 2.0,
prevFlexPt.Y + Math.Sin(angle) * waveInterval / 2.0);
var bcpInAngle = angle + (Math.PI - bcpInclination) * polarity;
var bcpIn = new Point(flexPt.X + Math.Cos(bcpInAngle) * bcpLength,
flexPt.Y + Math.Sin(bcpInAngle) * bcpLength);
wigglePoints.Add((bcpOut, bcpIn, flexPt));
polarity *= -1;
prevFlexPt = flexPt;
}
var streamGeometry = new StreamGeometry();
using (var streamGeometryContext = streamGeometry.Open())
{
streamGeometryContext.BeginFigure(wigglePoints[0].anchor, true, false);
for (var i = 1; i < wigglePoints.Count; i += 1)
{
var (bcpOut, bcpIn, anchor) = wigglePoints[i];
streamGeometryContext.BezierTo(bcpOut, bcpIn, anchor, true, false);
}
}
var visual = new DrawingVisual();
var fillBrush = Brushes.Transparent;
var lineBrush = Brushes.DarkSeaGreen;
var borderThickness = BorderThickness;
var strokePen = new Pen(lineBrush, borderThickness);
using (var context = visual.RenderOpen())
{
context.DrawGeometry(fillBrush, strokePen, streamGeometry);
}
_waveLineList.Add(visual);
}
private readonly VisualCollection _waveLineList;
private static double CalculateAngle(Point p1, Point p2)
{
return Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
}
protected override Visual GetVisualChild(int index)
{
return _waveLineList[index];
}
}
public class ViewModel : INotifyPropertyChanged
{
public string WaveLength
{
get => _waveLength;
set
{
if (value == _waveLength) return;
_waveLength = value;
OnPropertyChanged();
}
}
public string WaveHeight
{
get => _waveHeight;
set
{
if (value == _waveHeight) return;
_waveHeight = value;
OnPropertyChanged();
}
}
public string CurveSquaring
{
get => _curveSquaring;
set
{
if (value == _curveSquaring) return;
_curveSquaring = value;
OnPropertyChanged();
}
}
public string BorderThickness
{
get => _borderThickness;
set
{
if (value == _borderThickness) return;
_borderThickness = value;
OnPropertyChanged();
}
}
public string FirstPointY
{
get => _firstPointY;
set
{
if (value == _firstPointY) return;
_firstPointY = value;
OnPropertyChanged();
}
}
public string SecondPointX
{
get => _secondPointX;
set
{
if (value == _secondPointX) return;
_secondPointX = value;
OnPropertyChanged();
}
}
public string SecondPointY
{
set
{
if (value == _secondPointY) return;
_secondPointY = value;
OnPropertyChanged();
}
get => _secondPointY;
}
public string FirstPointX
{
set
{
if (value == _firstPointX) return;
_firstPointX = value;
OnPropertyChanged();
}
get => _firstPointX;
}
public WaveLine Draw()
{
double.TryParse(FirstPointX, out var firstPointX);
double.TryParse(FirstPointY, out var firstPointY);
double.TryParse(SecondPointX, out var secondPointX);
double.TryParse(SecondPointY, out var secondPointY);
double.TryParse(BorderThickness, out var borderThickness);
double.TryParse(CurveSquaring, out var curveSquaring);
double.TryParse(WaveHeight, out var waveHeight);
double.TryParse(WaveLength, out var waveLength);
var waveLine = new WaveLine
{
BorderThickness = borderThickness,
CurveSquaring = curveSquaring,
WaveHeight = waveHeight,
WaveLength = waveLength
};
waveLine.DrawWaveLine(new Point(firstPointX, firstPointY), new Point(secondPointX, secondPointY));
return waveLine;
}
private string _borderThickness = 1.ToString();
private string _curveSquaring = 0.57.ToString();
private string _firstPointX = 50.ToString();
private string _firstPointY = 290.ToString();
private string _secondPointX = 760.ToString();
private string _secondPointY = 226.ToString();
private string _waveHeight = 90.ToString();
private string _waveLength = 100.ToString();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
The result:

If the response is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our [documentation][5] to enable e-mail notifications if you want to receive the related email notification for this thread.
[5]: https://learn.microsoft.com/en-us/answers/articles/67444/email-notifications.html