Problem drawing dotted path lines, with even dots along the path line

Problem drawing dotted path lines, with even dots along the path line.

I want to draw a path line that can be any shape (square, round, outline etc), and along this path line I want even dots to appear.

I also want to be able to resize the shape (as in the example attached)

This is very easy to do using “DrawPath” and defining points for the path and using a pen defined as dots.

The problem I have is I want the dots to be evenly placed along the path, on any shape or size I draw.

In the example I have created, I create a square with 4 points and draw a dotted path.

There are 2 buttons that allows me to resize the square (larger or smaller) this helps show the problem more clearly.

The path starts at the top left corner and finishes back at the top left corner, the dots are evenly placed along the path, except the final dots at the end of the path. (Sometimes the dots are even and other times they are not even).

The shapes I want to draw can be any size and any shape, but I have used a square in this example to show the problem I have.

Can anyone please help point me in the direction I need to go that allows me to draw the dots and give the impression that they are even all along the path, for all shapes and sizes.

In the pictures below if you change the size of the shape, one will have even looking dots, the other will not.

Good:
The picture shows a path line that has dots that look even.

Any help much appreciated

Setting up the project:

Create a new project and add a form, add a panel to the form that fills the bottom part of the form, add at the top of the form a label and 2 buttons.

Imports System.Drawing.Drawing2D
Imports System.Math
Public Class Form1
Public pathLinesStart As GraphicsPath = New GraphicsPath
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim TPoint(5) As PointF
Dim Npoint As PointF = Nothing
'For x1 As Integer = 0 To 4
'Npoint = New PointF(30, 30)
'TPoint(0) = Npoint
'Npoint = New PointF(300, 30)
'TPoint(1) = Npoint
'Npoint = New PointF(300, 300)
'TPoint(2) = Npoint
'Npoint = New PointF(30, 300)
'TPoint(3) = Npoint
'Npoint = New PointF(30, 30)
'TPoint(4) = Npoint
'Npoint = New PointF(30, 30)
'TPoint(5) = Npoint
Npoint = New PointF(126, 126)
TPoint(0) = Npoint
Npoint = New PointF(205, 126)
TPoint(1) = Npoint
Npoint = New PointF(205, 205)
TPoint(2) = Npoint
Npoint = New PointF(126, 205)
TPoint(3) = Npoint
Npoint = New PointF(126, 126)
TPoint(4) = Npoint
Npoint = New PointF(126, 126)
TPoint(5) = Npoint
'Next
pathLinesStart.AddLines(TPoint)
CalculateNewPoints(True, 0)
Me.Refresh()
End Sub
Private Sub Panel1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Panel1.Paint
Dim p As Pen = New Pen(Color.Black, 5)
p.LineJoin = LineJoin.Round
p.EndCap = LineCap.Round
p.StartCap = LineCap.Round
Dim g As Graphics = e.Graphics
p.DashStyle = DashStyle.Dot
p.DashCap = DashCap.Round
g.InterpolationMode = InterpolationMode.HighQualityBicubic
g.SmoothingMode = SmoothingMode.HighQuality
p.Color = Color.Red
g.DrawPath(p, pathLinesStart)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
' Increase change point size
CalculateNewPoints(True, 10)
Me.Refresh()
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
' Decrease change point size
CalculateNewPoints(False, 10)
Me.Refresh()
End Sub
Public Sub CalculateNewPoints(ByRef Increase As Boolean, ByVal PercentageChange As Double)
Dim RotatePerc As Single = 0
Dim pathLines As GraphicsPath = pathLinesStart
Dim xmin As Single = 0
Dim xmax As Single = 0
Dim ymin As Single = 0
Dim ymax As Single = 0
Dim xmin2 As Single = 0
Dim xmax2 As Single = 0
Dim ymin2 As Single = 0
Dim ymax2 As Single = 0
Dim CPoint As PointF = New Point(0, 0)
Dim CPoint2 As PointF = New Point(0, 0)
Dim OldPoint As PointF = New Point(0, 0)
xmin = pathLines.PathPoints(0).X
xmax = pathLines.PathPoints(0).X
ymin = pathLines.PathPoints(0).Y
ymax = pathLines.PathPoints(0).Y
For x1 As Integer = 0 To UBound(pathLines.PathPoints)
If pathLines.PathPoints(x1).X < xmin Then
xmin = pathLines.PathPoints(x1).X
End If
If pathLines.PathPoints(x1).X > xmax Then
xmax = pathLines.PathPoints(x1).X
End If
If pathLines.PathPoints(x1).Y < ymin Then
ymin = pathLines.PathPoints(x1).Y
End If
If pathLines.PathPoints(x1).Y > ymax Then
ymax = pathLines.PathPoints(x1).Y
End If
Next
Dim xwidth As Single = ((xmax - xmin))
Dim ywidth As Single = ((ymax - ymin))
' Find the centre point of the annotation
CPoint = New PointF(((xwidth / 2)) + xmin, ((ywidth / 2)) + ymin)
Dim aAngle As Single = 0
Dim nChngY As Single = 0
Dim nChngX As Single = 0
Dim nChng As Single = 0
Dim nPercChng As Double = (PercentageChange / 100)
Dim xPoint As Single = 0
Dim yPoint As Single = 0
Dim TPoint(UBound(pathLines.PathPoints)) As PointF
Dim Npoint As PointF = Nothing
Label1.Text = ""
For x1 As Integer = 0 To UBound(pathLines.PathPoints)
OldPoint = New PointF((pathLines.PathPoints(x1).X), (pathLines.PathPoints(x1).Y))
aAngle = CSng(Math.Atan2((OldPoint.Y - CPoint.Y), (OldPoint.X - CPoint.X)))
' get length of Hypotenuse
nChngX = CSng(Math.Pow((CPoint.X - OldPoint.X), 2))
nChngY = CSng(Math.Pow((CPoint.Y - OldPoint.Y), 2))
nChng = CSng((Math.Sqrt((nChngX + nChngY))))
' get percentage changed based on length of Hypotenuse
Dim nChngPc As Double = nChng * nPercChng
xPoint = CSng((Math.Cos(aAngle) * nChngPc))
yPoint = CSng(CSng((Math.Sin(aAngle) * nChngPc)))
If OldPoint.X > (CPoint.X) Then
' add
xPoint = Math.Abs(xPoint)
Else
' sub
xPoint = Math.Abs(xPoint)
xPoint = 0 - xPoint
End If
If OldPoint.Y > (CPoint.Y) Then
' add
yPoint = Math.Abs(yPoint)
Else
' sub
yPoint = Math.Abs(yPoint)
yPoint = 0 - yPoint
End If
Dim X1a As Single, Y1a As Single, X2a As Single, Y2a As Single
If Increase = True Then
'larger
X1a = ((OldPoint.X + (xPoint)))
Y1a = (OldPoint.Y + (yPoint))
Else
'smaller
X1a = ((OldPoint.X - (xPoint)))
Y1a = (OldPoint.Y - (yPoint))
End If
Dim r As Double = Math.Atan(Y1a / X1a)
' get length of Hypotenuse
nChngX = CSng(Math.Pow((CPoint.X - X1a), 2))
nChngY = CSng(Math.Pow((CPoint.Y - Y1a), 2))
nChng = CSng((Math.Sqrt((nChngX + nChngY))))
r = ((Math.PI / 180) * (nChng))
Dim ROT As Single = CSng(((Math.PI / 180) * RotatePerc))
r = nChng
X2a = CSng((r * Cos(ROT) * Cos(aAngle)) - (r * Sin(ROT) * Sin(aAngle)))
Y2a = CSng((r * Sin(ROT) * Cos(aAngle)) + (r * Cos(ROT) * Sin(aAngle)))
X2a = X2a + CPoint.X
Y2a = Y2a + CPoint.Y
Npoint = New PointF(CInt((X2a)), CInt((Y2a)))
TPoint(x1) = Npoint
Label1.Text = Label1.Text + "(" + Npoint.X.ToString + "," + Npoint.Y.ToString + ") - "
Next
pathLinesStart = New GraphicsPath
pathLinesStart.AddLines(TPoint)
End Sub
End Class

Attached 2 images: Bad dots & Good dots

Bad:
The picture shows a path line that has dots that clash at the start and end of the path line – NOT GOOD

Another way --
From this call:
"pathLinesStart.AddLines(TPoint)"
..it looks like you are already creating an array of lines from points
(although the AddLines code isn't shown)

If you use just an array of lines instead of a path then you can
space the endpoints of the lines to prevent dot collisions.

surfR2911, AddLines is a method of the GraphicsPath object, so I'm not sure what code isn't shown. The TPoint array is set up to defined the multiple lines in a figure and passed to the AddLines method. There isn't any additional code involved in that.

coolpjmartin,
If you are going to need arbitrarily spaced dots to aline to a given figure, then you are going to have to calculated the perimeter distance, divide by the desired spacing, then create a custom "Dot Pattern" ("pen.DashPattern =" method) to fill that perimeter evenly so that the end points align.
Good luck, let us know how it turns out.

{edit:}
If you could ensure that the length of a line is a multiple of your pen width, then the dot pattern should stay align as the default spacing is going to be a dot (dash actually) of one penWidth in length, followed by a space one penWidth in length.

But, as an example of one way to change the spacing (in this case increasing the space between dots to align to the corners of your example square. You could also decrease the space, which would probably look better for smaller figures) to align dots to all four corners of your square, using the example code you posted, here is a modified Panel1_Paint to handle this case.

Code:

Private Sub Panel1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Panel1.Paint
Dim p As Pen = New Pen(Color.Black, 5)
p.LineJoin = LineJoin.Round
p.EndCap = LineCap.Round
p.StartCap = LineCap.Round
Dim g As Graphics = e.Graphics
' p.DashStyle = DashStyle.Dot
p.DashCap = DashCap.Round
'We want to increase the space between dots so the dots will align to the corners of the square
'
Dim LenOfOneSide As Single = pathLinesStart.PathPoints(1).X - pathLinesStart.PathPoints(0).X 'length of one side of square
'numOfSpace is the number of spaces between the dots before the last dot drawn on a line.
Dim numOfSpace As Single = Int(LenOfOneSide / 10) '10 = 2*penWidth, which is our standard dot spacing (equal size dot and space between dots)
Dim extraSpace As Single = (LenOfOneSide / 10) - numOfSpace 'should be a number from 0 to 1, indicating a percentage of extra dot space
Dim spreadSpace As Single = (extraSpace / numOfSpace) * 2.0F 'Spread the extra space across the number of spaces (but don't increase dot size so *2)
spreadSpace = spreadSpace + 1 'the default space is 1 unit, so we're adding the extra spread space to that 1 unit
Dim dashVals() As Single = {1.0F, spreadSpace} 'the dot is left at 1.0, extra padding is in the spaces to align dots
p.DashPattern = dashVals 'Set the DashPattern to our customized pattern
g.InterpolationMode = InterpolationMode.HighQualityBicubic
g.SmoothingMode = SmoothingMode.HighQuality
p.Color = Color.Red
g.DrawPath(p, pathLinesStart)
g.DrawPath(Pens.Black, pathLinesStart)
End Sub

Of course, depending on what you are doing, it looks like it would be a lot less work to not have to recalculated and modified the points in your paths to manually scale them up or rotate them, of reposition them.
You can just use transforms to scale, rotate, position your coordinate system before you draw the path, leaving the path data itself unmodified, so you don't get creeping distortions in your path due to repeated modification of the data itself.
If you need to keep the pen size the same when you scale using transforms, you can define a new pen using a width the inverse of your scale so that it always draws the same width regardless of scale.

__________________
There Is An Island Of Opportunity In The Middle of Every Difficulty.
Miss That, Though, And You're Pretty Much Doomed.

The ASP.NET 2.0 Anthology
101 Essential Tips, Tricks & Hacks - Free 156 Page Preview. Learn the most practical features and best approaches for ASP.NET. subscribe