B4J Tutorial [ABMaterial] Google Chatrs

Hi ,
there is a way to load a google chart engine into abmaterial?
i have used this in websocket event but with no success.
B4X:
ws.Eval( "$('head').append('<script type=text/javascript src=https://www.google.com/jsapi></script>');google.load('visualization', '1.0', {'packages':['corechart']});",Null)

can anyone help me?
regards Paolo
 

alwaysbusy

Expert
Licensed User
Longtime User
You can use the ABMCustomComponent.

As for now, just download the loader.js file and put it in the /js/custom folder: https://www.gstatic.com/charts/loader.js
I could add a Page.AddExtraJavaScriptUrl("https://www.gstatic.com/charts/loader.js").

Then in your page:
B4X:
page.AddExtraJavaScriptFile("custom/loader.js")  
  
Dim custChart As CustomGoogleChart
custChart.Initialize(page, "custChart")
page.Cell(2,1).AddComponent(custChart.ABMComp)

And here is the CustomGoogleChart class:
B4X:
'Class module
Sub Class_Globals
    Dim ABMComp As ABMCustomComponent  
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String)
    ABMComp.Initialize("ABMComp", Me, InternalPage, ID)  
End Sub

Sub ABMComp_Build(internalID As String) As String
    Return $"<div id="myPieChart"></div>"$  
End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
    Dim script As String = $"google.charts.load('current', {packages: ['corechart']});
    google.charts.setOnLoadCallback(drawChart);

    function drawChart() {
      // Define the chart to be drawn.
      var data = new google.visualization.DataTable();
      data.addColumn('string', 'Element');
      data.addColumn('number', 'Percentage');
      data.addRows([
        ['Nitrogen', 0.78],
        ['Oxygen', 0.21],
        ['Other', 0.01]
      ]);

      // Instantiate and draw the chart.
      var chart = new google.visualization.PieChart(document.getElementById('myPieChart'));
      chart.draw(data, null);
   }
"$
  
     InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
    ' flush not needed, it's done in the refresh method in the lib
End Sub

' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
  
End Sub

' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
  
End Sub

Resulting in something like this:
upload_2016-3-12_8-8-31.png
 
Last edited:

billyrudi

Active Member
Licensed User
Longtime User
Hi Alain,
tanks for your replay..
i add some line of code to show how it is possible to draw a pie chart dinamically and with a full screen.


My page:

B4X:
Sub Page_Ready()
    Log("ready!")
   
    custChart.Resize(0,0,wc.GetClientWidth.Value,wc.GetClientHeight.Value ,ws)
   
    Dim tit As String = "Energia Prodotta " & "124" & " kWh" & CRLF & "Accumulata in Batterie " & 122  & ""
    Dim Options As Map = CreateMap("width":wc.GetClientWidth.Value, _
                                         "height":wc.GetClientHeight.Value, "title": tit, _
                                     "pieHole":"0.4","is3D": "True", _
                                     "backgroundColor" : "#FFFFFF","fontSize": 15 , _
                                     "legend" : "none","legendFontSize":  "12" ,"colors": Array As Object("13D172", "#ffff00","#2C7D0C"))
    custChart.SetOptions(Options)
    Dim Data As List
    Data.Initialize()
    Data.Add(Array As Object("kWh","Prodotti"))
    Data.Add(Array As Object("Accumulata in Batterie " & 122  & " kWh",122))
    Data.Add(Array As Object("Immessa in Rete " & 1  & "  kWh",1))
    Data.Add(Array As Object("Autoconsumata da " & 1  & "  Moduli FV kWh", 1))
       
       
   
    custChart.SetData(Data)
    custChart.DrawChart(ws)
   
    page.RestoreNavigationBarPosition
End Sub


where wc is the famous cWebClientClass...

The CustomGoogleChart class:

B4X:
Sub Class_Globals
    Dim ABMComp As ABMCustomComponent 
    Private mChartType As String
    Private mOptions As Map
    Private mData As List

    Private mID As String
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String)
   
    ABMComp.Initialize("ABMComp", Me, InternalPage, ID)  
    mID = ID
End Sub

Sub ABMComp_Build(internalID As String) As String
    Return $"<div id="grafico"></div>"$  
End Sub


Sub SetData(Data As List)
    mData = Data
End Sub

Sub SetOptions(Options As Map)
    mOptions = Options
End Sub

Sub DrawChart(ws As WebSocket)

    Dim Script As String

    Script = " var chart = new google.visualization.PieChart(document.getElementById('grafico')); var data = google.visualization.arrayToDataTable(arguments[1]);" & CRLF & _
                             "chart.draw(data, arguments[2]);"

    ws.Eval(Script, Array As Object("#grafico", mData, mOptions))

End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
   
                                        
    Dim script As String = $"google.charts.load('current', {packages: ['corechart']});
      // Instantiate and draw the chart.
      var chart = new google.visualization.PieChart(document.getElementById('grafico'));
"$
  
     InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
    ' flush not needed, it's done in the refresh method in the lib
End Sub


Sub Resize(Left As String, Top As String, Width As String, Height As String, mWS As WebSocket)

    Dim CSS As String

    CSS = "$(arguments[0]).css({"
   
    If Left   <> -1 Then CSS = CSS & "'left':'" & Left & "px',"
    If Top    <> -1 Then CSS = CSS & "'top':'" & Top & "px',"
    If Width  <> -1 Then CSS = CSS & "'width':'" & Width & "px',"
    If Height <> -1 Then CSS = CSS & "'height':'" & Height & "px',"
   
    If CSS.EndsWith(",") Then
        CSS = CSS.SubString2(0, CSS.Length - 1)
        CSS = CSS & "});"
        mWS.Eval(CSS, Array As Object("#grafico"))
    End If
   
End Sub


' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
  
End Sub

' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
  
End Sub
 

alwaysbusy

Expert
Licensed User
Longtime User
Here is another example of a Google Chart (both the material and classic version of a line chart):

Add a new normal class e.g. CustomGoogleChart2

B4X:
'Class module
Sub Class_Globals
    Dim ABM As ABMaterial 'ignore
    Dim ABMComp As ABMCustomComponent 
    Dim Title As String
    Dim Subtitle As String 'only used for a google material chart
    Dim Columns As List
    Dim Rows As List
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String)
    ABMComp.Initialize("ABMComp", Me, InternalPage, ID)
    ABMComp.Visibility = ABM.VISIBILITY_HIDE_ALL ' hide the chart while loading
    ' the automatic events will be raised on the page e.g. if the id = "mycomp" then the event will be mycomp_Click(params as Map)
    ' future: we'll see if some other mechanisme is needed for non automatic events
    Columns.Initialize
    Rows.Initialize
End Sub

public Sub AddColumn(varType As String, text As String)
    Columns.add($"data.addColumn('${varType}', '${text}');"$)
End Sub

public Sub AddRow(Day As Int, Guardians As Double, Avengers As Double, Transformers As Double)
    If Rows.Size > 0 Then ' with comma
        Rows.Add($",[${Day}, ${Guardians}, ${Avengers}, ${Transformers}]"$)
    Else ' first one, does not need a comma
        Rows.Add($"[${Day}, ${Guardians}, ${Avengers}, ${Transformers}]"$)
    End If
End Sub

private Sub GetColumns() As String
    Dim s As String
    For i = 0 To Columns.Size-1
        s = s & Columns.Get(i)
    Next
    Return s
End Sub

private Sub GetRows() As String
    Dim s As String
    For i = 0 To Rows.Size-1
        s = s & Rows.Get(i)
    Next
    Return s
End Sub

' runs when an object is created for the first time. Expects a valid html string
Sub ABMComp_Build(internalID As String) As String
    Return $"<div id="${internalID}"></div>"$ 
End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
    ' this is for a classic google chart
    Dim script As String = $"google.charts.load('current', {'packages':['corechart']});
    google.charts.setOnLoadCallback(drawChart);
 
    function drawChart() {
         // Define the chart to be drawn.
         var data = new google.visualization.DataTable();
         ${GetColumns}
      
          data.addRows([${GetRows}]);
   
          var options = {
            title: '${Title}',      
            width: '100%',
            height: 500,
            vAxis: {title: "Visitors", minValue:30, maxValue:50},
              hAxis: {title: "Day"},         
            explorer: {
                actions: ['dragToZoom', 'rightClickToReset'],
                axis: 'horizontal',
                keepInBounds: true,
                maxZoomIn: 100.0
            },         
              legend: { position: 'bottom' }
          };

          // Instantiate and draw the chart.
          var chart = new google.visualization.LineChart(document.getElementById('${internalID}'));
          chart.draw(data, options);   
   
          // added this because on firefox google charts can give errors
          google.visualization.events.addListener(chart, 'error', function (err) {
             google.visualization.errors.removeError(err.id);
          });
   
          // added this so the chart becomes visible
          document.getElementById('${internalID}').className=""; 
          // I force a second redraw by calling chart.draw(data, options); again (forces the width to fit)    
          chart.draw(data, options);
     }
  
     // make it resposive on resize
     $(window).resize(function(){
          drawChart();
     });
     "$
 
    ' this is for a google material chart
'    Dim script As String = $"google.charts.load('current', {'packages':['line']});
'    google.charts.setOnLoadCallback(drawChart);
' 
'    function drawChart() {
'         // Define the chart to be drawn.
'         var data = new google.visualization.DataTable();
'         ${GetColumns}
'      
'          data.addRows([${GetRows}]);
'   
'          var options = {
'            chart: {
'                  title: '${Title}',
'                  subtitle: '${Subtitle}'
'            },
'            width: '100%',
'            height: 500
'          };
'
'          // Instantiate and draw the chart.
'          var chart = new google.charts.Line(document.getElementById('${internalID}'));
'          chart.draw(data, options);   
'   
'          // added this because on firefox google charts can give errors
'          google.visualization.events.addListener(chart, 'error', function (err) {
'             google.visualization.errors.removeError(err.id);
'          });
'   
'          // added this so the chart becomes visible
'          document.getElementById('${internalID}').className=""; 
'          // I force a second redraw by calling chart.draw(data, options); again (forces the width to fit)    
'          chart.draw(data, options);
'     }
'  
'     // make it resposive on resize
'     $(window).resize(function(){
'          drawChart();
'     });
'     "$
 
     InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
    ' flush not needed, it's done in the refresh method in the lib
End Sub

' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)

    'Dim script As String = $""$ 
    'InternalPage.ws.Eval(script, Null) 
End Sub

' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
 
End Sub

Usage in your page:

In BuildPage(): You have to download this js file from the google site:
https://www.gstatic.com/charts/loader.js

and place it in the www/js/custom/ folder.

B4X:
page.AddExtraJavaScriptFile("custom/loader.js")

In ConnectPage():

B4X:
Dim myLineChart As CustomGoogleChart2
    myLineChart.Initialize(page, "myLinechart")
    myLineChart.Title = "Box Office Earnings in First Two Weeks of Opening"
    myLineChart.Subtitle = "in millions of dollars (USD)"
    myLineChart.AddColumn("number", "Day")
    myLineChart.AddColumn("number", "Guardians of the Galaxy")
    myLineChart.AddColumn("number", "The Avengers")
    myLineChart.AddColumn("number", "Transformers: Age of Extinction")
 
    For i = 1 To 100
        myLineChart.AddRow(i, Rnd(30,50), Rnd(30,50), Rnd(30,50))
    Next
    page.Cell(7,1).AddComponent(myLineChart.ABMComp)
 

Harris

Expert
Licensed User
Longtime User
First attempt is looking good using Google Charts. Very fast (much faster than jCharts and Chartist).
Now to learn all the options.


The only problem is formatting a Long (datetime) value to a string which represents the values current time for the (x) haxis.

I have always had this problem with JS since most don't acknowledge a long value of datetime and parse it into it's constituents (date and time values).
I must be missing something very important here...


gcharts.jpg


So how do I format the x (hvalue) into a string that represents the time of day from the long value passed (instead of of the long value shown here).


BTW, Google Material Charts is very slow.... (as I have experimented). At present, I don't see much benefit.

Thanks
 

Harris

Expert
Licensed User
Longtime User
Format time on hAxis
B4X:
Sub PutChartdata

    myLineChart.Rows.Clear
    myLineChart.Columns.Clear
   
    myLineChart.Title = "ECM PID Values - (for each second)"
    myLineChart.Subtitle = "by the second"
    myLineChart.AddColumn("timeofday", "Time of Day") 'use timeofday data type
    myLineChart.AddColumn("number", "Speed")
    myLineChart.AddColumn("number", "RPM")
    myLineChart.AddColumn("number", "Throttle")
   
    For i = 0 To splist.Size-1
        Dim dt As Long
        dt = rngdate.Get(i)
        Dim hour, mins, sec  As Int
        hour = DateTime.GetHour(dt)
        mins = DateTime.GetMinute(dt)
        sec  = DateTime.GetSecond(dt)
        ' pass array of hours, minutes and seconds
        myLineChart.AddRow(Array As Int(hour,mins,sec) , splist.Get(i), rpmlist.Get(i)/100, thlist.Get(i))
    Next
   
    page.Cell(2,2).Refresh 
    ' calls ABMComp_Refresh(InternalPage As ABMPage, internalID As String) - put script here to paint the chart!
   
    Log("fininshed call to refresh")

    page.Resume   ' hide progress wheel
   
End Sub

' modified AddRow to accept array of time values

public Sub AddRow(Day() As Int , Guardians As Double, Avengers As Double, Transformers As Double)
    If Rows.Size > 0 Then ' with comma
        Rows.Add($",[ [${Day(0)},${Day(1)},${Day(2)}], ${Guardians}, ${Avengers}, ${Transformers} ]"$)
    Else ' first one, does not need a comma
        Rows.Add($"[ [${Day(0)},${Day(1)},${Day(2)}], ${Guardians}, ${Avengers}, ${Transformers}]"$)
    End If
End Sub
 
Top