B4J Tutorial [BANanoAPI] - Scripting the DOM: The HTML5 Canvas Story (Advanced Users)

Ola

Download

BANanoCanvas - Find the replica there for use in all BANano Projects

DISCLAIMER: This is purely for fun and learning purposes.

Another pleasure, BANanoAPI. Why the suffix API? Well, the functionality of the library used here is based on the BANanoObject. In December 2018, a thread was posted about BANanoObject - talks with javascript. and this enabled a lot of possibilities for us to maneuver our way around the javascript framework used for Web Development.

So what is this all about?

This is about DOM, BOM, CSSOM scripting using the BANanoObject.

As you will find, the source code of the library has a lot of .GetField, .SetField, and .RunMethod. Thus the helper classes created are on top of the BANanoObject and in some instances the BANanoElement.

Wait, why are you having direct javascript in the B4X IDE?

Whilst you will notice some similarities within the code examples here with some javascript code, that was just done for simplicity and speed. I just wanted to copy and paste source code and run it and just wrapped a few things to achieve that for my purposes. Yes, its a personal project, but then again, why not share it.

Remember, with BANano, we have #if javascript and .CallJavaScriptMethod, so we could have just used that syntax instead, but then again, I am adventurous, so don't be alarmed if most of the cases you don't see a lot of the BANano core code in the examples, underneath it is all its just the BANanoObject and some shortcuts.

Why re-invent the wheel?

Some things were done for fun, learn and explore what will happen.

Do you have to write code like I have done here mostly?

Absolutely NOT. For HTML elements, its rather and ALWAYS better and recommended to use the Abstract Designer. There is an independent custom view in the library that you can use outside of this API. To regress a little, on the issue of the custom view...

I tested that with some Application Modelling Language that I was curious about. A pure MVC javascript framework. The image below was the eye candy for that experiment. AML is used for prototyping apps, its old tech. It is when I was exploring that framework that this BANanoAPI was birthed.

1601159902877.png


Anyway...

Beginning the HTLM5 Canvas is no easy feat and yes there are libraries out there added on top of it that are better. The purpose though here is learning it and how it works. The best way to do that is to learn some javascript that is specific to the canvas. You get the javascript specific to the canvas, convert it to the BANano language and code your way to canvas based dev or rather use #if javascript with .CallJavaScript.

My approach was rather simple, use the existing language but make it work with BANano. This helps me learn the underlying language and at the same time also learn how to manipulate it to suit my needs. So I created some helper classes for the purpose. The helper classes are based on the BANanoObject and will work around that object ONLY.

To use the canvas, you need to create the canvas element for your page body. The example below, creates a canvas, gets the 2d context and then adds this to an existing table at RC position provided.

B4X:
Sub Skeleton(doc As JSDocument, tb As JSTable, cid As String, rowPos As Int, cellPos As Int) As JSCanvas
    'create the canvas
    Dim mycanvas As JSElement = doc.createElement("CANVAS")
    mycanvas.id = cid
    mycanvas.width = 300
    mycanvas.height = 150
    mycanvas.style.border = "1px solid #d3d3d3"
    mycanvas.innerHTML = "Your browser does Not support the HTML5 canvas tag"
    'add canvas to table
    tb.row(rowPos).cell(cellPos).empty
    tb.row(rowPos).cell(cellPos).appendChild(mycanvas)
    'creare the context
    Dim ctx As JSCanvas
    ctx.Initialize(mycanvas, "2d")
    Return ctx
End Sub

Like I said, we are using BANanoObject for this library, so we call the DOM API directly. Looking at .CreateElement, we will note that its defined like this.

B4X:
'createElement
Sub createElement(arguements As String) As JSElement
    Dim bo As BANanoObject = d.RunMethod("createElement", Array(arguements))
    Dim jse As JSElement = ToJSElement(bo)
    Return jse
End Sub

Writing this the BANano Core way would be.

B4X:
Sub CreateElement(arguments as string) as BANanoObject
      dim bo As BANanoElement
       bo.Initialize(arguments)
       return bo.ToObject
End Sub

JSElement = BANanoObject (with a name)

Whilst this thread is about DOM/BOM/CSSOM scripting

1. Creating User Interfaces by scripting the DOM is not recommended - use the abstract designer where absolutely necessary.
2. Creating Cascading Style Sheets by scripting the BOM/CSSOM is not recommended - use .css and inline styles

Things to remember

B4X:
JSWindow (BANanoObject) = BANAnoWindow
JSDocument (BANanoObject) = BANano.Window.GetField("document") / BANanoWindow.GetField("document")
body (BANanoObject) = BANano.Window.GetField("document").GetField("body")
JSElement (BANAnoObject) = BANanoElement.Initialize(?).ToObject

One of the nice examples of this exercise is this pie chart.

Day19.jpg
 
Last edited:

Mashiane

Expert
Licensed User
Longtime User
On the demo source code, on the Main module, on BANano_Ready Sub, UNCOMMENT ONLY - pgCanvasDay18.Init

Day19.jpg


B4X:
Dim ctx1 As JSCanvas = canvdays.Skeleton(document, tb1, "ctx1", 1, 1)
    ctx1.Canvas.width = 600
    ctx1.Canvas.height = 600
    Dim legend As JSElement = document.createElement("DIV")
    tb1.row(1).cell(1).appendChild(legend)
    
    Dim myVinyls As Map = CreateMap("Classical music": 10, "Alternative rock": 14, "Pop": 2, "Jazz": 12)
    Dim colors As List
    colors.Initialize 
    colors.AddAll(Array("#fde23e", "#f16e23", "#57d9ff","#937e88"))
    Dim options As Map = CreateMap("data":myVinyls, "colors":colors)
    'options.put("doughnutHoleSize", 0.5)
    options.Put("legend", legend)
    '
    'calculate total value
    Dim total_value As Int = 0
    Dim color_index As Int = 0
    Dim idata As Map = options.Get("data")
    Dim icolors As List = options.get("colors") 
    '
    For Each k As String In idata.keys
        Dim v As Int = idata.get(k)
        total_value = total_value + v
    Next
    '
    Dim m As JSMath
    m.initialize
    
    Dim start_angle As Int = 0
    Dim color_index As Int = -1
    For Each categ As String In idata.keys
        Dim val As String = idata.get(categ) 
        val = BANano.parseint(val)
        '
        Dim slice_angle As Double = (2 * cPI * val) / total_value
        '
        Dim minof As Int = m.min1(Array(ctx1.canvas.width / 2, ctx1.canvas.height / 2))
        '
        color_index = color_index + 1
        Dim scolor As String = icolors.get(color_index)
         '
        ctx1.drawPieSlice(ctx1.canvas.width / 2, ctx1.canvas.height / 2, minof, start_angle, start_angle + slice_angle, scolor)
        
        start_angle = start_angle + slice_angle
    Next
    
    'drawing a white circle over the chart
    'To create the doughnut chart
    Dim doughnutHoleSize As Double = options.getdefault("doughnutHoleSize",0)
    If doughnutHoleSize > 0 Then
        ctx1.drawPieSlice(ctx1.canvas.width / 2, ctx1.canvas.height / 2, doughnutHoleSize * m.min1(Array(ctx1.canvas.width / 2, ctx1.canvas.height / 2)) , 0, 2 * cPI, "#ff0000")
    End If
    '
    start_angle = 0
    For Each categ As String In idata.keys
        Dim val As String = idata.get(categ) 
        val = BANano.parseInt(val)
        slice_angle = 2 * cPI * val / total_value
        Dim pieRadius As Double = m.min1(Array(ctx1.canvas.width/2,ctx1.canvas.height/2))
        Dim labelX As Int = ctx1.canvas.width/2 + (pieRadius / 2) * Cos(start_angle + slice_angle/2)
        Dim labelY As Int = ctx1.canvas.height/2 + (pieRadius / 2) * Sin(start_angle + slice_angle/2)
 
        If (doughnutHoleSize) > 0 Then
            Dim offset As Int = (pieRadius * doughnutHoleSize ) / 2
            labelX = ctx1.canvas.width/2 + (offset + pieRadius / 2) * Cos(start_angle + slice_angle/2)
            labelY = ctx1.canvas.height/2 + (offset + pieRadius / 2) * Sin(start_angle + slice_angle/2)
        End If
     
        Dim labelText As Int = Round(100 * val / total_value)
        ctx1.fillStyle = "white"
        ctx1.font = "bold 20px Arial"
        ctx1.fillText1(labelText & "%", labelX, labelY)
        start_angle = start_angle + slice_angle
    Next
    '
    If options.ContainsKey("legend") Then
        color_index = -1
        Dim legendHTML As StringBuilder
        legendHTML.initialize
        For Each categ As String In idata.keys
            color_index = color_index + 1
            legendHTML.Append("<div><span style='display:inline-block;width:20px;background-color:")
            legendHTML.append(icolors.get(color_index) & ";'>&nbsp;</span> " & categ & "</div>")
        Next    
        Dim lgnd As JSElement = options.get("legend")
        lgnd.innerHTML = legendHTML
    End If

Enjoy!
 

Mashiane

Expert
Licensed User
Longtime User
On the demo source code, on the Main module, on BANano_Ready Sub, UNCOMMENT ONLY - pgCanvasDay19.Init

Day20.jpg


B4X:
'Turn transparency on
    ctx.globalAlpha = 0.2
    ctx.fillStyle = "blue"
    ctx.fillRect1(50, 50, 75, 50)
    ctx.fillStyle = "green"
    ctx.fillRect1(80, 80, 75, 50)

The other example...

B4X:
ctx1.fillStyle = "red"
    ctx1.fillRect1(20, 20, 75, 50)
    ctx1.fillStyle = "blue"
    ctx1.globalCompositeOperation = "source-over"
    ctx1.fillRect1(50, 50, 75, 50)
    ctx1.fillStyle = "red"
    ctx1.fillRect1(150, 20, 75, 50)
    ctx1.fillStyle = "blue"
    ctx1.globalCompositeOperation = "destination-over"
    ctx1.fillRect1(180, 50, 75, 50)
 

Mashiane

Expert
Licensed User
Longtime User
On the demo source code, on the Main module, on BANano_Ready Sub, UNCOMMENT ONLY - pgCanvasDay20.Init

We want to style the canvases that we will draw, we use #if css

B4X:
#if css
canvas {
  border: 1px solid #d3d3d3;
  margin-right: 10px;
  margin-bottom: 20px;
}
#End If

Day21.jpg


We loop through each composition using a list..

B4X:
Dim gco As List
    gco.Initialize 
    gco.add("source-atop")
    gco.add("source-in")
    gco.add("source-out")
    gco.add("source-over")
    gco.add("destination-atop")
    gco.add("destination-in")
    gco.add("destination-out")
    gco.add("destination-over")
    gco.add("lighter")
    gco.add("copy")
    gco.add("xor")

    Dim n As Int
    Dim g As Int = gco.Size - 1
    For n = 0 To g
        Dim p As JSElement = document.createElement("P")
        p.id = $"p_${n}"$
        p.innerHTML = gco.Get(n) & ":<br>"
        p.style.float1 = "left"
        
        Dim c As JSElement = document.createElement("canvas")
        c.width = 120
        c.height = 100
        'add canvas to paragraph
        p.appendChild(c)
        Dim ctx As JSCanvas
        ctx.Initialize(c, "2d")
        ctx.fillStyle = "blue"
        ctx.fillRect1(10, 10, 50, 50)
        ctx.globalCompositeOperation = gco.Get(n)
        ctx.beginPath1
        ctx.fillStyle = "red"
        ctx.arc1(50, 50, 30, 0, 2 * cPI)
        ctx.fill1
        '
        body.appendChild(p)
        body.appendChild(document.createElement("DIV"))
    Next

This covers the basics of managing the canvas.
 

Mashiane

Expert
Licensed User
Longtime User
Let's create a clock, based on the canvas...

CanvasClock.gif


Based on everything we have learned and some, its time to create a clock. We will use .setInterval for this to fire every 1 second.

B4X:
'creare the context for drawing
    ctx.Initialize(canvas, "2d")
    'the radius is based on the height of the canvas
    radius = canvas.height / 2
    'remap and center the
    ctx.translate1(radius, radius)
    'clock should be inside radius
    radius = radius * 0.90
    window.setInterval(Me, "drawclock", 1000)

The code we will run will do 3 things..

B4X:
Sub drawClock()
    drawFace(ctx, radius)
    drawNumbers(ctx, radius)
    drawTime(ctx, radius)
End Sub

See the pgClock code module on the download.

Ta!
 
Top