B4J Library [B4X] A class to draw on canvas many types of arrows at any angle.

Once again I found myself writing code to draw arrows on canvas.
@klaus has provided a basic technique that I also use: define the set of points on the arrow as a path and then draw the path.
https://www.b4x.com/android/forum/threads/draw-broken-line-arrow-head.43450/post-264259

I finally decided to write a more general class to do this job. It is a cross-platform design.
It is small, about 120 lines of code. It's compactness is achieved by the use of a point object type.
Points have x and y coordinates and a set of public utility functions (Add, Subtract, Distance, Midpoint, etc.)

Arrows can be drawn by three methods.
-draw. Specified by the two points, start and end
-draw2. The most general specification: a portion of a vector
-draw3. Specified as a start point plus length and angle

The style of the arrow is specified through a Map object, keys: Color, Filled, Tip width, and Tip height
There are 4 optional parameters: DashColor, Inside Text, Font, and FontColor
(if omitted: undashed, no text, default font(18), black)

Please use the class in any way you like and modify it to meet your needs.
Let me know if I missed an edge case.

Note: if you need the arrow as an image, you can get the bitmap from the canvas.

screenshot.png
 

Attachments

  • Arrows_V1.1.zip
    50.4 KB · Views: 52
Last edited:

William Lancee

Well-Known Member
Licensed User
Longtime User
The code to generate the above screenshot.

B4X:
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    'in Globals Private arrow As arrows
    
    'square drawing surface made to fit screen for demo, landscape / portrait
    Dim wh As Float = Min(Root.width, Root.height)
    Dim xoffset, yoffset As Float
    If wh = Root.height Then xoffset = Root.Width/2 - wh / 2 Else yoffset = Root.Height/2 - wh / 2
    Dim surface As B4XView = xui.CreatePanel("")
    Root.AddView(surface, xoffset, yoffset, wh, wh)
    
    Dim cv As B4XCanvas: cv.Initialize(surface)
    Dim rx As B4XRect: rx.Initialize(0, 0, wh, wh)
    cv.DrawRect(rx, xui.Color_Black, False, 1)
    
    arrow.Initialize(cv)
    Dim arrowSpec As Map
    
    'Arrow 1    specified by the two end points, style dashed
    arrowSpec = CreateMap("Color": xui.Color_Black, "Filled": False, "DashColor": xui.Color_White, "Thickness": 10, "TipWidth": 25, "TipHeight": 30)
    arrow.draw(rx.Right - 100, rx.Bottom - 100, rx.Left + 100, rx.top + 100, arrowSpec)

    'Arrow 2    specified as a portion of a vector
    arrowSpec = CreateMap("Color": xui.Color_Blue, "Filled": True, "Thickness": 10, "TipWidth": 25, "TipHeight": 30)
    arrow.draw2(rx.Left, rx.Bottom, rx.Right, rx.top, 20, 70, arrowSpec)

    'Arrows 3, 4, 5    specified as a start point plus length and angle - start point is a circle
    arrowSpec = CreateMap("Color": xui.Color_Red, "Filled": True, "Thickness": 5, "TipWidth": 12, "TipHeight": 15)
    arrow.draw3(rx.CenterX, rx.CenterY + 80, 200, 25, arrowSpec)
    arrow.draw3(rx.CenterX, rx.CenterY + 80, 200, -25, arrowSpec)
    arrow.draw3(rx.CenterX, rx.CenterY + 80, 200, 145, arrowSpec)
    cv.DrawCircle(rx.CenterX, rx.CenterY + 80, 10, xui.Color_White, True, 0)
    cv.DrawCircle(rx.CenterX, rx.CenterY + 80, 10, xui.Color_Black, False, 1)

    'Arrow 6    specified as a start point plus length and angle - will be outlined
    Dim lineLength As Float = surface.Width / 3
    arrowSpec = CreateMap("Color": xui.Color_RGB(220, 220, 255), "Filled": True, "Thickness": 10, "TipWidth": 25, "TipHeight": 30)
    arrow.draw3(rx.CenterX, rx.CenterY - 50, lineLength, -90, arrowSpec)
    arrowSpec = CreateMap("Color": xui.Color_Black, "Filled": False, "Thickness": 10, "TipWidth": 25, "TipHeight": 30)
    arrow.draw3(rx.CenterX, rx.CenterY - 50, lineLength, -90, arrowSpec)

    'Arrow 7, 8    specified as a start point plus length and angle - will have a caption, two similar arrows
    arrowSpec = CreateMap("Color": xui.Color_Black, "Filled": False, "Thickness": 20, "TipWidth": 30, "TipHeight": 40)
    arrowSpec.Put("Caption", "Go There")
    arrowSpec.Put("Font", xui.CreateDefaultBoldFont(16))
    arrowSpec.Put("FontColor", xui.Color_Blue)
    arrow.draw3(rx.Left + 100, rx.Bottom - 50, lineLength, 0, arrowSpec)
    arrow.draw3(rx.right - 100, rx.Bottom - 50, lineLength, -160, arrowSpec)
    
    'Arrow 9    specified as a start point plus length and angle - will be skinny and filled and dashed
    arrowSpec = CreateMap("Color": xui.Color_Black, "Filled": True, "DashColor": xui.Color_White, "Thickness": 2, "TipWidth": 7, "TipHeight": 10)
    arrow.draw3(rx.CenterX, rx.CenterY + lineLength / 1.5, lineLength / 2, 90, arrowSpec)

    'Arrow 10    specified as a start point plus length and angle - filled WITHOUT an arrow tip - looks simply like an angled line
    arrowSpec = CreateMap("Color": xui.Color_Black, "Filled": True, "Thickness": 2, "TipWidth": 0, "TipHeight": 0)
    arrow.draw3(rx.Left + lineLength / 5, rx.CenterX, lineLength / 2, -70, arrowSpec)
    
End Sub
 

walt61

Well-Known Member
Licensed User
Longtime User
Hi @William Lancee , suddenly my (your, that is) Arrows class threw a concurrency exception in the 'standardize' method (I hadn't used the class for a long time). I assume it was caused by the map and its keys collection being iterated through and added to at the same time. I fixed it this way:

B4X:
' ORIGINAL CODE
Private Sub standardize_original_code(specs As Map)
    For Each kw As String In specs.keys
        specs.Put(kw.toLowerCase, specs.Get(kw))    'both original and lower case are indexed
    Next
End Sub

' NEW CODE
Private Sub standardize(specs As Map)
    Dim lstKeys As List
    lstKeys.Initialize
    For Each kw As String In specs.keys
        lstKeys.Add(kw)
    Next
    For Each kw As String In lstKeys
        specs.Put(kw.toLowerCase, specs.Get(kw))    'both original and lower case are indexed
    Next
End Sub
 

William Lancee

Well-Known Member
Licensed User
Longtime User
@walt61
Great catch. It was quite a while ago and nobody complained. My incorrect code was a "last minute optimization", missing the fact that the iterator was being modified inside the loop. Your fix is appreciated.
 

Cableguy

Expert
Licensed User
Longtime User
Could this become a full blown SVG lib, giving us the ability to draw shapes using waypoints?
 

Cableguy

Expert
Licensed User
Longtime User
looks interesting indeed
 
Top