B4J Tutorial [ABMaterial] Alternative lightweight charts

The official charts library of ABMaterial is Chartist, but because of its many features it is also a rather slow rendering js/css library and not always that easy to implement. @Mashiane has created a nice ABMCustomComponent wrapper for the JQPlot plugin and there are many examples in the forum on how to implement Google Charts in ABM, like this post from @Harris.

So why yet another one? Well the main focus on this wrapper is being fast AND easy to implement. Of course this also means less tunable features but I have found that maybe for 95% of our needs having some basic stuff like a tooltip and being clickable is actually enough.

I came accross the Frappé charts javascript library. It has tooltips, some interactions and animations. And it is bloody fast! :)

I only had to make a couple of CSS changes to make it ABM compatible and the ABMCustomComponent wrapper was very easy to write.

Usage:

Create your chart variables in Class_globals:
B4X:
Dim FrappeChart1 As FrappeChart
Dim FrappeChart2 As FrappeChart
Dim FrappeChart3 As FrappeChart
Dim FrappeChart4 As FrappeChart
Dim FrappeChart5 As FrappeChart
Dim FrappeChart6 As FrappeChart
Dim FrappeChart7 As FrappeChart

In Page_Connect(), make your charts (you immidately can see the simplicity of the code):
B4X:
FrappeChart1.Initialize(page, "FrappeChart1", "bar", "My Awesome Chart", 250)
FrappeChart1.FrappeLabels.AddAll(Array As String("12am-3am", "3am-6am", "6am-9am", "9am-12pm","12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"))
FrappeChart1.AddDataSet("Some data", "light-blue", Array As Int(25, 40, 30, 35, 8, 52, 17, -4), Array As String())
FrappeChart1.AddDataSet("Another set", "violet", Array As Int(25, 50, -10, 15, 18, 32, 27, 14), Array As String())
FrappeChart1.AddDataSet("Yet Another", "blue", Array As Int(15, 20, -3, -15, 58, 12, -17, 37), Array As String())
page.Cell(1,1).AddComponent(FrappeChart1.ABMComp)
   
FrappeChart2.Initialize(page, "FrappeChart2", "line", "My Awesome Chart", 250)
FrappeChart2.FrappeLabels.AddAll(Array As String("12am-3am", "3am-6am", "6am-9am", "9am-12pm","12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"))
FrappeChart2.AddDataSet("Some data", "light-blue", Array As Int(25, 40, 30, 35, 8, 52, 17, -4), Array As String())
FrappeChart2.AddDataSet("Another set", "violet", Array As Int(25, 50, -10, 15, 18, 32, 27, 14), Array As String())
FrappeChart2.AddDataSet("Yet Another", "blue", Array As Int(15, 20, -3, -15, 58, 12, -17, 37), Array As String())
page.Cell(1,1).AddComponent(FrappeChart2.ABMComp)
   
FrappeChart3.Initialize(page, "FrappeChart3", "scatter", "My Awesome Chart", 250)
FrappeChart3.FrappeLabels.AddAll(Array As String("12am-3am", "3am-6am", "6am-9am", "9am-12pm","12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"))
FrappeChart3.AddDataSet("Some data", "light-blue", Array As Int(25, 40, 30, 35, 8, 52, 17, -4), Array As String())
FrappeChart3.AddDataSet("Another set", "violet", Array As Int(25, 50, -10, 15, 18, 32, 27, 14), Array As String())
FrappeChart3.AddDataSet("Yet Another", "blue", Array As Int(15, 20, -3, -15, 58, 12, -17, 37), Array As String())
page.Cell(1,1).AddComponent(FrappeChart3.ABMComp)
     
FrappeChart4.Initialize(page, "FrappeChart4", "pie", "My Awesome Chart", 250)
FrappeChart4.FrappeLabels.AddAll(Array As String("12am-3am", "3am-6am", "6am-9am", "9am-12pm","12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"))
FrappeChart4.AddDataSet("Some data", "light-blue", Array As Int(25, 40, 30, 35, 8, 52, 17, -4), Array As String())
FrappeChart4.AddDataSet("Another set", "violet", Array As Int(25, 50, -10, 15, 18, 32, 27, 14), Array As String())
FrappeChart4.AddDataSet("Yet Another", "blue", Array As Int(15, 20, -3, -15, 58, 12, -17, 37), Array As String())
page.Cell(1,1).AddComponent(FrappeChart4.ABMComp)
   
FrappeChart5.Initialize(page, "FrappeChart5", "percentage", "My Awesome Chart", 250)
FrappeChart5.FrappeLabels.AddAll(Array As String("12am-3am", "3am-6am", "6am-9am", "9am-12pm","12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"))
FrappeChart5.AddDataSet("Some data", "light-blue", Array As Int(25, 40, 30, 35, 8, 52, 17, -4), Array As String())
FrappeChart5.AddDataSet("Another set", "violet", Array As Int(25, 50, -10, 15, 18, 32, 27, 14), Array As String())
FrappeChart5.AddDataSet("Yet Another", "blue", Array As Int(15, 20, -3, -15, 58, 12, -17, 37), Array As String())
page.Cell(1,1).AddComponent(FrappeChart5.ABMComp)
   
FrappeChart6.Initialize(page, "FrappeChart6", "bar", "My Awesome Chart", 250)
FrappeChart6.FrappeLabels.AddAll(Array As String("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"))
FrappeChart6.AddDataSet("Some data", "purple", Array As Int(25, 40, 30, 35, 8, 52, 17), Array As String())
FrappeChart6.AddDataSet("Another set", "orange", Array As Int(25, 50, -10, 15, 18, 32, 27), Array As String())
FrappeChart6.FrappeShowSums = True
FrappeChart6.FrappeShowAverages = True
page.Cell(1,1).AddComponent(FrappeChart6.ABMComp)
   
FrappeChart7.Initialize(page, "FrappeChart7", "bar", "My Awesome Chart", 300)
FrappeChart7.FrappeLabels.AddAll(Array As String("Ganymede", "Callisto", "Io", "Europa"))
FrappeChart7.AddDataSet("Distances", "grey", Array As Int(1070.412, 1882.709, 421.700, 671.034), Array As String("1.070.412km", "1.882.709km", "421.700km", "671.034km"))
FrappeChart7.FrappeIsNavigable = True
FrappeChart7.FrappeRaiseEventOnClick = True
page.Cell(2,1).AddComponent(FrappeChart7.ABMComp)

An example of the Click event on Chart7:
B4X:
Sub FrappeChart7_Clicked(index As Int)
   Dim img As ABMImage = page.Component("img")
   Dim lbl As ABMLabel = page.Component("lbl")
   Select Case index
     Case 0
       img.Source = "../images/ganymede.jpg"
       lbl.Text = "{B}Ganymede{/B}{BR}{BR}Semi-major-axis: 1070412 km{BR}{BR}Mass: 14819000 x 10^16 kg{BR}Diameter: 5262.4 km"
     Case 1
       img.Source = "../images/callisto.jpg"
       lbl.Text = "{B}Callisto{/B}{BR}{BR}Semi-major-axis: 1882709 km{BR}{BR}Mass: 10759000 x 10^16 kg{BR}Diameter: 4820.6 km"
     Case 2
       img.Source = "../images/io.jpg"
       lbl.Text = "{B}Io{/B}{BR}{BR}Semi-major-axis: 421700 km{BR}{BR}Mass: 8931900 x 10^16 kg{BR}Diameter: 3637.4 km"
     Case 3
       img.Source = "../images/europa.jpg"
       lbl.Text = "{B}Europa{/B}{BR}{BR}Semi-major-axis: 671034 km{BR}{BR}Mass: 4800000 x 10^16 kg{BR}Diameter: 3121.6 km"
   End Select
   img.Refresh
   lbl.Refresh
End Sub

This is the result:

Frappe1.png


Frappe2a.png


Frappe3a.png


Attached are the FrappeChart.bas and the frappe-charts.min.iife.js files. Copy the .js file to the /www/js/custom/ folder and import the .bas file.
 

Attachments

  • FrappeChart.zip
    17.2 KB · Views: 532

Anser

Well-Known Member
Licensed User
Longtime User
In the Template project, inside the folder /www/js/custom there is already a file with the name frappe-charts.min.iife.403.js So I believe that I don not have to copy the frappe-charts.min.iife.js file that is available inside the zip folder in the above post

I just have to include the FrappeChart.bas in my project. Right ?

May be because this thread/post was created before the release of ABMaterial ver 4.03

I could net get it running. On the page instead of the graph it is just showing a text
var _tabservice-frappechart2;

I followed what has been described in the first post of this thread to create a Frappe Chart

Solved : The following line should be included in the BuildPage()
page.AddExtraJavaScriptFile("../../js/custom/frappe-charts.min.iife.403.js")
 
Last edited:

Mashiane

Expert
Licensed User
Longtime User
So I'm exploring... plotting chart from a db 1 series at a time...
FrappeBE.png


B4X:
Dim fcbudgetexpenditure As FrappeChart
    fcbudgetexpenditure.Initialize(page, "fcbudgetexpenditure", "bar", "Total Budget vs Total Expenditure (Millions)", 500)
    fcbudgetexpenditure.frappexaxismode = "span"
    fcbudgetexpenditure.frappeyaxismode = "span"
    fcbudgetexpenditure.frappeisseries = True
    fcbudgetexpenditure.frapperaiseeventonclick = False
    fcbudgetexpenditure.frappeisnavigable = True
    fcbudgetexpenditure.frappeshowsums = False
    fcbudgetexpenditure.frappeshowaverages = False
    fcbudgetexpenditure.frappelineshowdots = True
    fcbudgetexpenditure.frappelineheatline = False
    fcbudgetexpenditure.frappelineregionfill = False
    fcbudgetexpenditure.valuesoverpoints = True
    Dim jSQL As SQL = ABMShared.SQLGet
    Dim lbls As List: lbls.Initialize
    lbls.add("Budget")
    fcbudgetexpenditure.FrappeLabels.AddAll(lbls)
    Dim recs As List = ABMShared.Execute2List(jSQL,"select Round(SUM(budget)/1000000,0) As a from projects",Null,0)
    fcbudgetexpenditure.AddDataSet("Budget", "blue", recs, Array As String())
    Dim recs As List = ABMShared.Execute2List(jSQL,"select Round(SUM(expenditure)/1000000,0) As b from projects",Null,0)
    fcbudgetexpenditure.AddDataSet("Expenditure", "red", recs, Array As String())
    ABMShared.SQLClose(jSQL)
    page.Cell(2,1).AddComponent(fcbudgetexpenditure.ABMComp)

One thing though, I have to call 1 query against the db, extracting both a & b using executemaps, parse that map and return each single dataset, then add to the chart.

Lesson: Only add 1 label for grouping datasets
 
Last edited:

Mashiane

Expert
Licensed User
Longtime User
Drawing datasets from different queries: continued..

So here I wanted a bar chart to show total projects vs completed projects, using same approach above, I tweaked my queries.. one counts all the projects and one counts only projects with actual progress = 100%.

B4X:
fccompletedprojects.Initialize(page, "fccompletedprojects", "bar", "Total Projects vs Completed Projects", 500)
    fccompletedprojects.frappexaxismode = "span"
    fccompletedprojects.frappeyaxismode = "span"
    fccompletedprojects.frappeisseries = True
    fccompletedprojects.frapperaiseeventonclick = False
    fccompletedprojects.frappeisnavigable = True
    fccompletedprojects.frappeshowsums = False
    fccompletedprojects.frappeshowaverages = False
    fccompletedprojects.frappelineshowdots = True
    fccompletedprojects.frappelineheatline = False
    fccompletedprojects.frappelineregionfill = False
    fccompletedprojects.valuesoverpoints = True
    Dim jSQL As SQL = ABMShared.SQLGet
    Dim lbls As List: lbls.Initialize
    lbls.add("Total Projects vs Completed Projects")
    fccompletedprojects.FrappeLabels.AddAll(lbls)
    Dim recsa As List = ABMShared.Execute2List(jSQL,"select Round(COUNT(id),0) As a from projects",Null,0)
    fccompletedprojects.AddDataSet("Total", "blue", recsa, Array As String())
    Dim recsb As List = ABMShared.Execute2List(jSQL,"select count(id) as b from projects where actualprogress = 100",Null,0)
    fccompletedprojects.AddDataSet("Completed", "red", recsb, Array As String())
    ABMShared.SQLClose(jSQL)

TotalvsComplete.png
 

Mashiane

Expert
Licensed User
Longtime User
Loading both x and y axis from a DB

FrappeDB.png


I just need to figure out how to set the x axis to work with angles here, but the demo here is about reading both the x axis and y axis values from the db and then plotting this on the frappe chart...

1. We need to pass lists for labels, and each of the datasets.
2. We run an execute maps to return 'a' and 'b' i.e. xy data from our recordsets using executemaps
3. We use the x i.e. clientnames as labels to plot these for the x axis
4. We use the a
B4X:
fcprojectsbyclient.Initialize(page, "fcprojectsbyclient", "bar", "Projects per Client", 500)
    fcprojectsbyclient.frappexaxismode = "span"
    fcprojectsbyclient.frappeyaxismode = "span"
    fcprojectsbyclient.frappeisseries = True
    fcprojectsbyclient.frapperaiseeventonclick = False
    fcprojectsbyclient.frappeisnavigable = True
    fcprojectsbyclient.frappeshowsums = False
    fcprojectsbyclient.frappeshowaverages = False
    fcprojectsbyclient.frappelineshowdots = True
    fcprojectsbyclient.frappelineheatline = False
    fcprojectsbyclient.frappelineregionfill = False
    fcprojectsbyclient.valuesoverpoints = True
    Dim jSQL As SQL = ABMShared.SQLGet
    Dim recs As List = ABMShared.SQLExecuteMaps(jSQL, "select count(projects.id) as a,clients.clientname as x from projects inner join clients on clients.id = projects.clientid group by clients.clientname", Null)
    'extract the x axis values
    Dim xaxis As List = ABMShared.MapProperty2List(recs,"x")
    fcprojectsbyclient.FrappeLabels.AddAll(xaxis)
    'Draw each series
    Dim recsa As List = ABMShared.MapProperty2List(recs,"a")
    fcprojectsbyclient.AddDataSet("Projects", "blue", recsa, Array As String())
    ABMShared.SQLClose(jSQL)
    page.Cell(4,1).AddComponent(fcprojectsbyclient.ABMComp)

There is code here to loop through each of the maps from ExecuteMaps and extract the provided property, in this case x and then a and return these are lists that are passed to the chart components for both the x axis and y axis values.

B4X:
'extract map properties to a list
Sub MapProperty2List(om As List,prop As String) As List
    Dim lst As List: lst.initialize
    Dim mtot As Int = om.Size - 1
    Dim mcnt As Int = 0
    For mcnt = 0 To mtot
        Dim omm As Map = om.Get(mcnt)
        Dim strvalue As String = GetDefault(omm,prop,"")
        lst.Add(strvalue)
    Next
    Return lst
End Sub
Sub GetDefault(m As Map, prop As String, def As String) As String
    prop = prop.tolowercase
    Return m.getdefault(prop,def)
End Sub
 
Top