Monday, October 3, 2022

Dot Product

I was watching a YouTube article about Quantum Spin and, during the introduction, the author mentioned that a vector dot product was a measure of how similar two vectors are. I had never thought of it that way before so I thought it might be fun to visualize it. Here is the result.

Start a Visual Studio 2022 C# Framework WPF project and call it DotProduct.

We will create an 800 x 800 canvas with axes drawn through the middle. We will add two lines that represent vectors and display their dot product in the middle of the canvas. We will then allow the user to grab the end of either of the vectors and move it around, updating the dot product as they do so.

We will start with the code behind which will need a class called cVector.

Is Tracking is true if the end of the line representing the vector is following the mouse. The other end is always anchored at the origin (which is point 400, 400 on the canvas).

The X and Y coordinates hold the location of the movable end of the line in canvas coordinates.

Color is the color of the line and there is a constructor that can easily initialize a new vector. We need to UI to update when X or Y are changed so the class must implement INotifyPropertyChanged.

    public class cVector : INotifyPropertyChanged
    {
        private bool _IsTracking = false;
        public bool IsTracking
        {
            get { return _IsTracking; }
            set { SetProperty(ref _IsTracking, value); }
        }
 
        private decimal _X;
        public decimal X
        {
            get { return _X; }
            set { SetProperty(ref _X, value); }
        }
 
        private decimal _Y;
        public decimal Y
        {
            get { return _Y; }
            set { SetProperty(ref _Y, value); }
        }
 
        private SolidColorBrush _Color;
        public SolidColorBrush Color
        {
            get { return _Color; }
            set { SetProperty(ref _Color, value); }
        }
 
        public cVector(decimal X, decimal Y, Color C)
        {
            IsTracking = false;
            this.X = X;
            this.Y = Y;
            this.Color = new SolidColorBrush(C);
        }
 
        public event PropertyChangedEventHandler PropertyChanged;
        public void SetProperty<T>(ref T storage, T value, [CallerMemberName] string name = "")
        {
            if (!Object.Equals(storage, value))
            {
                storage = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
 
The MainWindow class itself needs a constructor and properties to hold the list of vectors (there will only be two) and the Dot Product. We need the UI to change when we change DotProduct so the class has to implement INotifyPropertyChanged.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private List<cVector> _Vectors = new List<cVector> { new cVector(200,200, Colors.Red), new cVector(600,200, Colors.Blue)};
        public List<cVector> Vectors
        {
            get { return _Vectors; }
            set { SetProperty(ref _Vectors, value); }
        }
 
        private int _DotProduct;
        public int DotProduct
        {
            get { return _DotProduct; }
            set { SetProperty(ref _DotProduct, value); }
        }
 
        public MainWindow()
        {
            InitializeComponent();
            UpdateDotProduct(Vectors[0], Vectors[1]);
        }
 
        public void UpdateDotProduct(cVector v1, cVector v2)
        {
            DotProduct = (int)((v1.X - 400) * (v2.X - 400) + (v1.Y - 400) * (v2.Y - 400));
        }
    }
 
Before we finish the code behind we can write the XAML. It's a canvas, two lines for the axes, two lines for the vectors, and a textblock for the Dot Product. We add some styling to highlight the vector as it is tracking the mouse.

<Window x:Class="DotProduct.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DotProduct"
        mc:Ignorable="d" ResizeMode="NoResize"
        Title="Dot Product" Height="800" Width="800"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Background="Black">
    <Canvas Background="Transparent">
        <Line X1="400" X2="400" Y1="0" Y2="800" Stroke="LightGray"/>
        <Line X1="0" X2="800" Y1="400" Y2="400" Stroke="LightGray"/>
        <Line X1="400" Y1="400" X2="{Binding Vectors[0].X}" Y2="{Binding Vectors[0].Y}" Stroke="{Binding Vectors[0].Color}">
            <Line.Style>
                <Style TargetType="Line">
                    <Setter Property="StrokeThickness" Value="1"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Vectors[0].IsTracking}" Value="True">
                            <Setter Property="StrokeThickness" Value="2"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Line.Style>
        </Line>
        <Line X1="400" Y1="400" X2="{Binding Vectors[1].X}" Y2="{Binding Vectors[1].Y}" Stroke="{Binding Vectors[1].Color}">
            <Line.Style>
                <Style TargetType="Line">
                    <Setter Property="StrokeThickness" Value="1"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Vectors[1].IsTracking}" Value="True">
                            <Setter Property="StrokeThickness" Value="2"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Line.Style>
        </Line>
        <TextBlock Canvas.Top="390" Canvas.Left="350" Height="20" Background="Black" Foreground="White" Padding="2"
                   Text="{Binding DotProduct, StringFormat=Dot Product {0}}"/>
    </Canvas>
</Window>
 

If we run the application now we can see these controls, but there is no interaction. We will need to add event handlers for MouseDown, MouseUp, and MouseMove.

Add these event handler definitions to the Canvas tag so that it looks like this.

<Canvas MouseDown="Canvas_MouseDown" MouseUp="Canvas_MouseUp" MouseMove="Canvas_MouseMove" Background="Transparent">

We want to grab the end of a vector when the user clicks near it. As the user moves the mouse the end of the vector will move with it. To do this well, we need to know how far from the mouse the end of the vector needs to be. Add two private members to the MainWindow class to hold the vertical and horizontal offset from the mouse pointer to the end of the vector.

        private decimal OffsetX;
        private decimal OffsetY;

Then add the event handlers to the MainWindow class.

MouseDown iterates through the list of vectors and finds the first one whose end is close to the mouse (if any). The vector is flagged as tracking and MouseMove events will update it.

MouseUp sets all the vectors as not tracking.

MouseMove finds each vector that is tracking (should be zero or one) and moves the end of the vector relative to the current mouse position, recalculating Dot Product at the same time.

        private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Point p = Mouse.GetPosition((Canvas)sender);
            foreach (cVector v in Vectors)
            {
                OffsetX = (decimal)p.X - v.X;
                OffsetY = (decimal)p.Y - v.Y;
                if (Math.Abs(OffsetX) < 10 && Math.Abs(OffsetY) < 10)
                {
                    v.IsTracking = true;
                    return;
                }
            }
        }
 
        private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
        {
            Vectors.ForEach(v => v.IsTracking = false);
        }
 
        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            Point p = Mouse.GetPosition((Canvas)sender);
            foreach (cVector v in Vectors)
            {
                if (v.IsTracking)
                {
                    v.X = (decimal)p.X - OffsetX;
                    v.Y = (decimal)p.Y - OffsetY;
                    UpdateDotProduct(Vectors[0], Vectors[1]);
                }
            }
        }
 
If we run the application now we can grab the end of a vector and move it, which causes the Dot Product to be recalculated.

Two similar vectors have a high Dot product

Dissimilar vectors have a low Dot Product 

You might wonder why I set the background of the canvas to Transparent. Canvases have a default background of null. Controls with a background of null cannot receive mouse click events. Strange but true.

No comments:

Post a Comment