2

I'm looking for some tips/advice on how calculate A, B and C in the following WPF and/or SVG graphic. This is related to the graphic of another question I just asked, but instead of using strokes with LineJoin and Miter, I'd like to just create a Path with LineTo and ArcTo instead. The miter in SVG and WPF is slightly different and it does not get appended to the coordinate space, but rather lays outside of it (e.g. if a bounding box was 100x100, the miter would appear in the negative space and the rounded ends would appear in the 100+ space).

The graphic I'm trying to figure out is: arrow

I apologize in advance of knowing almost nothing of trigonometry. I did try looking up cos, sin and tan, but couldn't brain much of it. I looked all over SO, and couldn't find something straightforward enough dealing with what I'm looking for help with.

Some things:

  • I'm using inches as the values below.
  • I will always know the bounding rectangle (in this case, 1.68 inches x 1.68 inches)
  • I will always know the "stroke thickness" (the two slopes of the arrow). In this case, 0.28 inches.
  • The two circles (actually, half circles) will always be flush against the bottom left and the bottom right corners. They will always be of a known size (in this case, diameter of 0.28 inches). The plan is to ArcTo from A to B (or vice versa if that's better).
  • The MoveTo is always the top center of the bounding rectangle (in this case, 0.84,0).

I really appreciate any advice on this. Thanks in advance. (Lest I be dreadfully accused of "homework", know that I got my GED in 1987...)

Todd Main
  • 28,951
  • 11
  • 82
  • 146

1 Answers1

1

Approximate results, better calculate it in your program for higher precision.

A = 0.0178, 1.4717
B = 0.2622, 1.6083
C = 0.84,   0.5739

Close up of Circle

Crude close-up of the bottom left circle

This calculation probably contains some unnecessary steps, but here is how you can calculate A and B with basic trigonometry. Since the arrow is symmetrical I only calculated the left half, assuming that the origin (0,0) is top left.

r = 0.14 (0.28/2)
w,h = 1.64

The center of the circle (D) is at r, h-r. The tip of the Arrow (T) is w/2,0. Since the left tangent (A to T) has to form a right angle with A to D, we can determine the angle a1 (between D and T) the following way:

a1 = acos(r / H)

H (Hypotenuse) in this case is the distance from D to T, in this case

H = |D - T|
H = sqrt(0,7² + 1,54²)
H = 1,6916...

So a1 is 85.25273°. Together with the angle from horizontal to DT (a2):

a2 = atan(DTy / DTx)
a2 = atan((1.54 - 0.0) / (0.84 - 0.14))
a2 = atan(1.54 / 0.7)
a2 = 65.56°

So the total angle between our horizontal axis (X) and DA is

a = a1+a2
a = 150.81273°

Now we know the distance (r) and the angle (a) from D to A and B.

dx = cos(a) * r
dy = sin(a) * r

dx = 0.1222
dy = 0.0683

All you have to do now is add and subtract that from the center (D) and you have A and B

A = 0.0178, 1.4717
B = 0.2622, 1.6083

Determining C

Now on to C. Since we want AT and BC to be parallel, we can use the angle a we already calculated to determine BC. Here x is the delta X between B and C, y the delta Y, b the angle of BC relative to the x-axis.

b = a - 90°
b = 60.81273°

We already know the x position of C (Cx) because we want it to be in the center.

Cx = w/2
Cx = 0.84
x = Cx - Bx
x = 0.84 - 0.2622
x = 0.5778

Now that we have another right-angled triangle, we can determine y.

tan(b) = y/x
y = tan(b) * x
y = 1.79022 * 0.5778
y = 1.0344

Cy = By - y
Cy = 1.6083 - 1.0344
Cy = 0.5739

Put all that together and you get

C = 0.84, 0.5739

The complete path for the left half would look like this (just a lot more accurate):

<SVG Width="84px" height="168px">
    <g transform="scale(100)">
        <Path d="M 0,0 L 0.84,0 L 0.84,1.68 L 0,1.68 Z" fill="#f6be98"/>
        <Path d="M 0.84,0 L 0.0178 1.4717 A 0.14,0.14 0 0 0 0.2622 1.6083 L 0.84 0.5739 Z" fill="#4472c4" />
    </g>
</SVG>

This edit below is by Todd Main, original poster. The following is an implementation of Manfred Radlwimmer's fantastic code. It is in VB.NET and uses XML Literals. It is devoid of any semicolons. If SO allow commenting code in VB in this day and age, it would have had comments. It creates the picture in the original post and is appended to this answer for others to see and use as needed.

    Private Sub Main()
        Dim diameter = 20
        Dim bb As New Size(120, 120)
        Dim pathData = RenderedArrowheadPath(diameter, bb)
        Dim svg As String = RenderedSVG(bb, pathData).ToString
        Console.WriteLine(svg)
        Console.ReadLine()
    End Sub
    Private Function RenderedSVG(bb As Size, pathData As String) As XElement
        Return <SVG width=<%= bb.Width.ToString & "px" %> height=<%= bb.Height.ToString & "px" %>>
                   <g>
                       <Path d=<%= $"M0,0 L{bb.Width},0 L{bb.Width},{bb.Height} L0,{bb.Height} Z" %> fill="#f6be98"/>
                       <Path d=<%= pathData %> fill="#4472c4"/>
                   </g>
               </SVG>
    End Function
    Private Function RenderedArrowheadPath(diameter As Double, bb As Size) As String

        Dim radius = diameter / 2
        Dim Distance = New Point(radius, bb.Height - radius)
        Dim Tip = bb.Width / 2
        Dim DT = New Point(bb.Height - radius, (bb.Width / 2) - radius)
        Dim Hypotenuse = Math.Sqrt(DT.X ^ 2 + DT.Y ^ 2)
        Dim angle1 = Math.Acos(radius / Hypotenuse)
        Dim angle2 = Math.Atan(DT.X / DT.Y)
        Dim angle = angle1 + angle2
        Dim dx = Math.Cos(angle) * radius
        Dim dy = Math.Sin(angle) * radius

        Dim PointA = New Point(Distance.X + dx, Distance.Y - dy)
        Dim PointB = New Point(Distance.X - dx, Distance.Y + dy)


        Dim b = angle - (90 / (180 / Math.PI)) 
        Dim Cx = bb.Width / 2
        Dim Bx = PointB.X
        Dim X = Cx - Bx
        Dim Y = Math.Tan(b) * X
        Dim Cy = PointB.Y - Y
        Dim PointC = New Point(Cx, Cy)


        Dim PointBInv = New Point(bb.Width - (Distance.X - dx), Distance.Y + dy)
        Dim PointAInv = New Point(bb.Width - (Distance.X + dx), Distance.Y - dy)

        Return $"M{Tip},0 L{PointA.X},{PointA.Y} A{radius},{radius} 0 0 0 {PointB.X},{PointB.Y} L{PointC.X},{PointC.Y} L{PointBInv.X},{PointBInv.Y} A{radius},{radius} 0 0 0 {PointAInv.X},{PointAInv.Y}  Z"
    End Function

This produces:

<SVG width="120px" height="120px">
  <g>
    <Path d="M0,0 L120,0 L120,120 L0,120 Z" fill="#f6be98" />
    <Path d="M60,0 L1.27003148173183,105.122741582605 A10,10 0 0 0 18.7299685182682,114.877258417395 L60,41.0066440783012 L101.270031481732,114.877258417395 A10,10 0 0 0 118.729968518268,105.122741582605  Z" fill="#4472c4" />
  </g>
</SVG>
Todd Main
  • 28,951
  • 11
  • 82
  • 146
Manfred Radlwimmer
  • 13,257
  • 13
  • 53
  • 62
  • Thanks. Where are the values of 0,7² + 1,54² coming from that are used in `H` and `a2`? Also, any thoughts on how to calculate `C`? – Todd Main Sep 26 '17 at 08:33
  • `H` is the distance between the center of the circle and the tip of the arrow. 0.7 is the difference on the x-axis and 1.54 is the distance on the y axis. I'll have a look at C soon. – Manfred Radlwimmer Sep 26 '17 at 08:37
  • Wow @ManfredRadlwimmer, this is amazing and incredibly helpful. Your graphic is very helpful. I can't wait to see the `C` when you have a chance. I'll wrap it all up in .Net code (VB.NET, just for the C# guys) once `C` is done to show how to implement in WPF and append it to your answer. – Todd Main Sep 26 '17 at 09:09