Fetching Info of All Users and their connected devices for the SUSI.AI Admin Panel

Fetching the data of all users is required for displaying the list of users on the SUSI.AI Admin panel. It was also required to fetch the information of connected devices of the user along with the other user data. The right to fetch the data of all users should only be permitted to user roles “OPERATOR” and above. This blog post explains how the data of connected devices of all users is fetched, which can then be used in the Admin panel.

How is user data stored on the server?

All the personal accounting information of any user is stored in the user’s accounting object. This is stored in the “accounting.json” file. The structure of this file is as follows:

{
  "email:akjn11@gmail.com": {
    "devices": {
      "8C-39-45-23-D8-95": {
        "name": "Device 2",
        "room": "Room 2",
        "geolocation": {
          "latitude": "54.34567",
          "longitude": "64.34567"
        }
      }
    },
    "lastLoginIP": "127.0.0.1"
  },
  "email:akjn22@gmail.com": {
    "devices": {
      "1C-29-46-24-D3-55": {
        "name": "Device 2",
        "room": "Room 2",
        "geolocation": {
          "latitude": "54.34567",
          "longitude": "64.34567"
        }
      }
    },
    "lastLoginIP": "127.0.0.1"
  }
}

 

As can be seen from the above sample content of the “accounting.json” file, we need to fetch this data so that it can then be used to display the list of users along with their connected devices on the Admin panel.

Implementing API to fetch user data and their connected devices

The endpoint of the servlet is “/aaa/getUsers.json” and the minimum user role for this servlet is “OPERATOR”. This is implemented as follows:

   @Override
    public String getAPIPath() {
        return "/aaa/getUsers.json";
    }

    @Override
    public UserRole getMinimalUserRole() {
        return UserRole.OPERATOR;
    }

 

Let us go over the main method serviceImpl() of the servlet:

  • We need to traverse through the user data of all authorized users. This is done by getting the data using DAO.getAuthorizedClients() and storing them in a Collection. Then we extract all the keys from this collection, which is then used to traverse into the Collection and fetch the user data. The implementation is as follows:

    Collection<ClientIdentity> authorized = DAO.getAuthorizedClients();
    List<String> keysList = new ArrayList<String>();
    authorized.forEach(client -> keysList.add(client.toString()));

    for (Client client : authorized) {
        // code           
    }

 

  • Then we traverse through each client and generate a client identity to get the user role of the client. This is done using the DAO.getAuthorization() method. The user role of the client is also put in the final object which we want to return. This is implemented as follows:

    JSONObject json = client.toJSON();
    ClientIdentity identity = new 
    ClientIdentity(ClientIdentity.Type.email, client.getName());
    Authorization authorization = DAO.getAuthorization(identity);
    UserRole userRole = authorization.getUserRole();
    json.put("userRole", userRole.toString().toLowerCase());

 

  • Then the client credentials are generated and it is checked whether the user is verified or not. If the user is verified, then in the final object, “confirmed” is set to true, else it is set to false.

    ClientCredential clientCredential = new ClientCredential (ClientCredential.Type.passwd_login, identity.getName());
    Authentication authentication = DAO.getAuthentication(clientCredential);

    json.put("confirmed", authentication.getBoolean("activated", false));

 

  • Then we fetch the accounting object of the user using DAO.getAccounting(), and extract all the user data and put them in separate key value pairs in the final object which we want to return. As the information of all connected devices of a user is also stored in the user’s accounting object, that info is also extracted the same way and put into the final object.

    Accounting accounting = DAO.getAccounting(authorization.getIdentity());
    if (accounting.getJSON().has("lastLoginIP")) {
        json.put("lastLoginIP", accounting.getJSON().getString("lastLoginIP"));
    } else {
        json.put("lastLoginIP", "");
    }

    if(accounting.getJSON().has("signupTime")) {
        json.put("signupTime", accounting.getJSON().getString("signupTime"));
    } else {
        json.put("signupTime", "");
    }

    if(accounting.getJSON().has("lastLoginTime")) {
        json.put("lastLoginTime", accounting.getJSON().getString("lastLoginTime"));
    } else {
        json.put("lastLoginTime", "");
    }

    if(accounting.getJSON().has("devices")) {
        json.put("devices", accounting.getJSON().getJSONObject("devices"));
    } else {
        json.put("devices", "");
    }
    accounting.commit();

 

This is how the data of all users is fetched by any Admin or higher user role, and is then used to display the user list on the Admin panel.

Resources

Continue ReadingFetching Info of All Users and their connected devices for the SUSI.AI Admin Panel

Ticket Quantity Spinner in Open Event Android

Spinners are basically drop down menu which provides an easy way to select an item from a set of items. Spinner is used in Open Event Android to allow user select quantity of tickets as shown in the image above. This blog post will guide you on how its implemented in Open Event Android.

Add Spinner to the XML file

<Spinner
           android:id=“@+id/orderRange”
           android:layout_width=“30dp”
           android:layout_height=“30dp”
           android:spinnerMode=“dialog”
           android:textSize=“@dimen/text_size_small” />

The above code spinnet will create a spinner type view with 30dp height and width, spinnerMode can be dialog or dropDown following are example spinner for both of the modes left being dialog type and right dropDown type.

                

(Image Source: Stack Overflow)

Set Up Adapter and Populate Data on Spinner

To show the list of acceptable Quantities for a Ticket, create an ArrayList of String and add all values from ticket.minOrder to ticket.maxOrder along with a zero option. Since ArrayList is of String and the values are integer they need to be converted to String using Integer.toString(i) method. Following code snippet will give you more understanding about it.

val spinnerList = ArrayList<String>()
           spinnerList.add(“0”)
           for (i in ticket.minOrder..ticket.maxOrder) {
               spinnerList.add(Integer.toString(i))
           }

We will also need to set up an onItemSelectedListener to listen for Item Selections and override onItemSelected and onNothingSelected functions in it. Whenever an item is selected onItemSelected is called with arguments such as view, position of the selected item, id etc, these can be used to find the selected Quantity from the ArrayList of acceptable Quantities.      

    itemView.orderRange.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
               override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
                   itemView.order.text = spinnerList[pos]
                   selectedListener?.onSelected(ticket.id, spinnerList[pos].toInt())
               }

               override fun onNothingSelected(parent: AdapterView<*>) {
               }
           }

Since Spinner is an Adapter Based view we need to create a SpinnerAdapter and attach it with the Spinner. If you want to create custom Spinners you would have to create a custom adapter for it along with a layout for its row elements or else one can use library-provided layout. Given below is the implementation for library-provided spinner row type. There are different options available for row type we are using R.layout.select_dialog_singlechoice because we need single selectable Quantity spinner along with Radio button for every ticket.

     itemView.orderRange.adapter = ArrayAdapter(itemView.context, R.layout.select_dialog_singlechoice, spinnerList)

Resources

Continue ReadingTicket Quantity Spinner in Open Event Android

Deploying Susper On Heroku

In Susper, currently we have two branches, master and development branch. The master branch is deployed on susper.com and we needed a deployment of development branch. For this we are using heroku and in this blog I will describe how we have deployed Susper’s development branch on heroku at http://susper-dev.herokuapp.com

Here are the steps:

1.Get a free heroku account:

To deploy our apps on heroku we must have a heroku account which can be created for free on https://www.heroku.com.

2.Create a new app on heroku:

After creating an account we need to create an app with a name and a region, for Susper we have used name susper-dev and region as United States.

3.Link the app to Susper’s development branch:

After successfully creating the app we need to link our app to Susper’s development branch and in Deploy section and then we have to enable automatic deployment from development branch after CI passes.

  1. Setup the Susper Project:

Now we have deployed our and we need to configure it so that it can successfully start on heroku. Following are the steps to configure Susper.

a) Add node and npm engine in package.json:

Now we need to tell heroku which npm and node version to use while running our app,this can be done by defining an engine field in package.json and adding npm and node versions as values as shown here:

"engines": {
   "node": "8.2.1",
   "npm": "6.0.1"
 }

 

b) Adding a postinstall field under scripts field in package.json:

Now we need to tell heroku the command that we will be using to build our app. Since on susper.com we are using ng build –prod –build-optimizer that builds our app for production by optimizing the build artifacts. Therefore on heroku also we will be using the same command:

"postinstall": "ng build --prod --build-optimizer"

 

c) Defining dependencies and typescript version for our project:This can be done by defining the @angular/cli ,@angular/compiler-cli and typescript and their version under dependencies field in package.json. In Susper we are using the following dependency versions.

"@angular/cli": "^1.1.0",
"@angular/compiler": "4.1.3",
"typescript": "~2.3.4"

 

d) Creating a javascript file to install express server and run our app: Locally when we run ng serve angular cli automatically creates a express server where our app is deployed locally but on heroku we will be required to start our express server which will be used by our app using a javascript file. Here is the code for starting the server.

//Install the server
const express = require('express');
const path = require('path');
const app = express();
// Serving the static file from dist folder
app.use(express.static(__dirname + '/dist'));
app.get('/*', function(request,response) {
response.sendFile(path.join(__dirname+'/dist/index.html'));
});
// Starting the app and listening on default heroku port.
app.listen(process.env.PORT || 8080);

 

Now we need to run this file, for this we will change start command in package.json file under script to start this file.

"start": "node server.js"

 

Now everytime a new commit is made to development branch our app will be automatically deployed on heroku at https://susper-dev.herokuapp.com

Resources

 

 

Continue ReadingDeploying Susper On Heroku

Mapping Events to Load from Database

Mapping Events to Load from Database

In Open Event Android whenever App is started events are fetched for the location given by the user, since we have locally added isFavorite extra field for every event it is necessary to be updated for all the events returned in the API response before inserting it into our database otherwise all the favorite related information would be lost. This blog post will guide you on how its done in Open Event Android.

The sequence of steps followed

  • Take the IDs of events being saved into the database
  • Use DAO method which does “SELECT id from Event where favorite = 1 AND id in :eventIds and pass API returned eventIds to this function
  • Set the old favorited on new event objects
  • Save them in database

Let’s see how all of these steps are performed in greater details. Whenever user gives in a location following function is called

fun getEventsByLocation(locationName: String): Single<List<Event>> {
       return eventApi.searchEvents(“name”, locationName).flatMap { apiList ->
           val eventIds = apiList.map { it.id }.toList()
           eventDao.getFavoriteEventWithinIds(eventIds).flatMap { favIds ->
               updateFavorites(apiList, favIds)
           }
       }
   }

Here we are extracting all the Ids of events returned in the API response and then calling getFavoriteEventWithinIds on it. The latter takes the list of eventIds and return the Ids of events which are favorite out of them. This is then passed to the function updateFavorite along with the API returned Events. Following is the implementation of updateFavorite method.

fun updateFavorites(apiEvents: List<Event>, favEventIds: List<Long>): Single<List<Event>> {
       apiEvents.map { if (favEventIds.contains(it.id)) it.favorite = true }
       eventDao.insertEvents(apiEvents)
       val eventIds = apiEvents.map { it.id }.toList()
       return eventDao.getEventWithIds(eventIds)
   }

updateFavorite checks for all events in the list of events whether if its Id is present in the favorite event ids it sets favorite field of that event true. After the favorites for the list of events are updated they are inserted into the database using DAO method insertEvents. The last task is to take the fetch these events again from the database, to do this first we extract the Ids of the events we just inserted into the database and call the DAO method getEventsWithIds passing the extracted eventIds, getEventsWithids simply returns the Events with given Ids.

Given below are the implementations of the functions getEventWithIds and getFavoriteEventWithinIds

@Query(“SELECT * from Event WHERE id in (:ids)”)
   fun getEventWithIds(ids: List<Long>): Single<List<Event>>

@Query(“SELECT id from Event WHERE favorite = 1 AND id in (:ids)”)
   fun getFavoriteEventWithinIds(ids : List<Long>): Single<List<Long>>

getEventWithIds simply makes a select query and checks for events whose ids lies in the ids passed to the method

getFavoriteEventWithinids returns the Ids of the favorite event out of the list of event id passed to the method.

Resources

Continue ReadingMapping Events to Load from Database

Displaying Charts on Lightbox

Susper displayed charts which shows graphical representation of Result Frequency and Protocol Distribution(http or https). But since results from these charts added little value it was decided to display charts on a click of a button on a lightbox. (Issue: https://github.com/fossasia/susper.com/issues/1066 ). In this blog,I will describe how I have implemented charts on lightbox.

Uniting the graphs(Line and Bar Graph) in a same class:

Before beginning the implementation of lightbox, we should unite all the components that will be displayed on lightbox in a single class.

Here I have put the different types of charts in a class named graph.

<div class="graph large"> <div class="linegraph"><canvas baseChart
           [datasets]="lineChartData"
           [labels]="lineChartLabels"
           [options]="lineChartOptions"
           [chartType]="lineChartType"
           [colors]="lineChartColors"
></canvas></div><div class="bargraph"><canvas baseChart
           [datasets]="barChartData"
           [labels]="barChartLabels"
           [options]="barChartOptions"
           [chartType]="barChartType"
           [colors]="lineChartColors"
   ></canvas></div></div>

Implementing button to toggle display of light box:

To show and hide lightbox along with charts we must have a button.

Clicking on which should trigger a function which will internally execute the logic in typescript file to show and hide the lightbox.

<button class="btn" id="toggle-chart-button" (click)="BoxToggle()">
           {{analyticsStatus}} Analytics
 </button>

Creating a translucent black background:

Now to have a black overlay for lightbox we need a div element and inside it we need a closing button.

Clicking on the black overlay should toggle status of light box therefore we have binded click event with BoxToggle() method.

<div id="fade" class="black_overlay" (click)='BoxToggle()'><a class="closeX" id="closeX">&#10006;</a></div>

Creating a white block to display charts:

Now after creating a black overlay in background we need a white foreground to display charts.

Therefore I have implemented a div element and put all the chart elements inside the div element so that it will be displayed on a white foreground on a translucent black background.

<div id="light" class="white_content">
<div class="graph large"><div class="linegraph"><canvas baseChart
           [datasets]="lineChartData"
           [labels]="lineChartLabels"
           [options]="lineChartOptions"
           [chartType]="lineChartType"
           [colors]="lineChartColors"
   ></canvas></div>
<div class="bargraph"><canvas baseChart
           [datasets]="barChartData"
           [labels]="barChartLabels"
           [options]="barChartOptions"
           [chartType]="barChartType"
           [colors]="lineChartColors"
   ></canvas></div></div></div>

CSS for all above elements:

All the CSS which is written for charts, toggle button, white block and black overlay

To make it look attractive and similar to market leader can be found here.

Implementing the logic in typescript file:

Now we have created all the elements in frontend to display the lightbox but we need a little logic to show and hide the lightbox on clicking the button. For this I have used a variable analyticsStatus which is a string and is initialised with value ‘Show Chart’. Its value is displayed on the toggle button. I have used this variable with BoxToggle() function for implementing the logic for showing and hiding the lightbox.

This function checks the status of message and toggles it along with toggling lightbox with some javascript code. The code for the function is here.

BoxToggle() { if (this.analyticsStatus === 'Show Chart') {
     this.analyticsStatus = 'Hide Chart';
     document.getElementById('light').style.display = 'block';
     document.getElementById('fade').style.display = 'block';
   } else {
     this.analyticsStatus = 'Show Chart';
     document.getElementById('light').style.display = 'none';
     document.getElementById('fade').style.display = 'none'; }}

Resources

1.Creating a lightbox: https://www.emanueleferonato.com/2007/08/22/create-a-lightbox-effect-only-with-css-no-javascript-needed/

2.W3School lightbox: https://www.w3schools.com/howto/howto_js_lightbox.asp

3.Angular 2 Lightbox: https://lokeshdhakar.com/projects/lightbox2/

Continue ReadingDisplaying Charts on Lightbox

Extending the News Feature to Show results from multiple organisations

News Tab in Susper was earlier implemented to show results only from a single organisation using  site: modifier for query facet. In this blog I will discuss about how I have modified the current News Tab to show results from various News organisation like BBC, Al Jazeera, The Guardian etc.

Implementation:

Step 1:

Creating a JSON file to store organisations detail:

We need to decide from which organisations  we will fetch results using YaCy Server and then display it in Susper. We have provided a JSON file where user can add or delete News sources easily. The results will only limited to the organisations which are present in the JSON file.

Step 2:

Creating a service to fetch details from JSON file:

Now after creating the the JSON file we need a service which will fetch results from the JSON file according to our need. This service will be a simple  Angular Service having a class GetJsonService and it will access the newsFile.json and map the results in JSON format. Here is the service which does this task for us.

Step 3:

Creating a service to fetch news accordingly:

Now after fetching the JSON result we need a service to fetch the News Results from YaCy Server. I have created a separate service to do this task where I have fetched results using site: modifier from each organisation and returned the results.The code for the news.service.ts is below.

export class NewsService {
 constructor(private jsonp: Jsonp) { }
 getSearchResults(searchquery, org) {
   let searchURL = 'https://yacy.searchlab.eu/solr/select?query=';
   searchURL += searchquery.query + ' site:' + org;
   let params = new URLSearchParams();
   for (let key in searchquery) {
     if (searchquery.hasOwnProperty(key)) {
       params.set(key, searchquery[key]); } }
  //Set other parameters
   return this.jsonp
     .get(searchURL, {search: params}).map(res =>
       res.json()[0]
     ).catch(this.handleError); }

 

Step 4:

Updating the results section:

Now we have a service that gives results from a single organisation and a JSON list of organisations. Now in results.component.ts we can simply subscribe to getJsonService and in a loop we will call getNewsService by changing the organisation in every iteration. We will then check that whether we are getting the valid results or not (undefined). The results which are not valid can cause errors when we will try to read any field of an undefined variable. Then, We will simply append the 2 result items from each organisation in an empty array and later use this array to show results.

this.getJsonService.getJSON().subscribe(res => { this.newsResponse = [];
for (let i = 0; i < res.newsOrgs.length; i++) { this.getNewsService.getSearchResults(querydata,res.newsOrgs[i].provider).subscribe( response => {
    if (response.channels[0].items[0] !== undefined) {
    this.newsResponse.push(response.channels[0].items[0]); }
    if (response.channels[0].items[1] !== undefined) {
    this.newsResponse.push(response.channels[0].items[1]); } } ); }
    });

 

The newsClick() function is activated on clicking News Tab and it updates the query and its details in store.

Step 5

Displaying the results:

Now we will modify the results.component.html to show results from new newsResponse array which have 2 results each from 5 organisations.

For this we will use each item of newsResponse using *ngFor and display its title,link and description in html template. We will also use [style.color] property of our element and set the color according to theme.

<div *ngFor="let item of newsResponse" class="result"> <div class="title">
<a class="title-pointer" href="{{item.link}}" [style.color]="themeService.titleColor">{{item.title}}</a>
</div> <div class="link">
<p  [style.color]="themeService.linkColor">{{item.link}}</p>
 </div> </div>

 

Here is the view of Susper’s News Tab where we are getting results from 5 different organisations.

Resources

  1. YaCy Modifiers: http://www.yacy-websuche.de/wiki/index.php/En:SearchParameters
  1. Angular Services: https://angular.io/tutorial/toh-pt4
  2. Reading JSON data in Angular: https://stackoverflow.com/questions/43275995/angular4-how-do-access-local-json

 

Continue ReadingExtending the News Feature to Show results from multiple organisations

Setting an Event Favorite in Open Event Android

The favorite events feature in Open Event Android allows any user to favorite an event and those events can are available in the favorite events fragment which is easily accessible from the bottom navigation bar. This blog article will walk you through on how favorite works in Open Event Android. There are a couple of different ways to do it, in Open Event Android we are keeping track of favorite events using a favorite Boolean stored along with the information of the event ie we have added a favorite boolean field inside the event entity class. However, this method has its own pros and cons. The big plus point of this method is the fact that we can simply check the favorite field of the event object to check if that particular event is favorite or not. But since we have set onConflict strategy as replace in the event insert DAO method (shown below)

 @Insert(onConflict = REPLACE)
   fun insertEvents(events: List<Event>)

This leads to a big problem when the same event is fetched or is present in the response while inserting the favorite field will be set to default value (false) and the favorite information would be lost. Hence extra care needs to be taken while updating events.

Given following is the event model class for serializing / deserializing JSON responses. Note the extra field favorite added here which helps us to provide user favorite feature locally.

@Type(“event”)
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)
@Entity
data class Event(
      @Id(LongIdHandler::class)
      @PrimaryKey
      val id: Long,
      val name: String,
      val identifier: String,
      val isMapShown: Boolean = false,
      val favorite: Boolean = false
)

Since we added a new field to the Event model class, to access the favorite events we will have to create DAO methods. To fetch all the favorite events we will have to create a Query method which returns all the events wherever favorite property is set. This can be done using a simple select statement which looks for events with favorite boolean set ie. 1, the method implementation is shown below.

@Query(“SELECT * from Event WHERE favorite = 1”)
   fun getFavoriteEvents(): Flowable<List<Event>>

To set an event favorite we will have to add one more method to the EventDao. We can create a generalized method which sets according to the boolean passed as a parameter. The function implementation is shown below, setFavorite takes in EventId, Id of the event which has to be updated and the boolean favorite which is what the new value for the favorite field is. setFavorite makes an SQL update query and matches for EventId and sets favorite for that event.

@Query(“UPDATE Event SET favorite = :favorite WHERE id = :eventId”)
   fun setFavorite(eventId: Long, favorite: Boolean)

In Open Event Android we use the service class which can be used to expose the DAO methods to the rest of the project. The idea behind creating service layer is the separation of concerns this service method is then called from the view model function (following the MVVM approach). Given following is the implementation of the get and set favorite methods.

fun getFavoriteEvents(): Flowable<List<Event>> {
       return eventDao.getFavoriteEvents()
   }
fun setFavorite(eventId: Long, favourite: Boolean): Completable {
       return Completable.fromAction {
           eventDao.setFavorite(eventId, favourite)
       }
   }

The method getFavoriteEvents calls the DAO method to fetch the favorite evens and returns a flowable list of favorite events and setFavorite method generates a completable from the DAO action to favorite any event.

EventViewModel uses these methods and specifies where the observables should be observedOn and subscribedOn also defines what should happen when some error occurs. Given below is implementation for the two methods.

fun setFavorite(eventId: Long, favourite: Boolean) {
       compositeDisposable.add(eventService.setFavorite(eventId, favourite)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe({
                   Timber.d(“Success”)
               }, {
                   Timber.e(it, “Error”)
                   error.value = “Error”
               }))
   }

For every event, we have a floating action bar to toggle the favorite state of the same whenever user clicks on the FAB it checks the current state of the event and calls the view model function setFavorite passing the Event Id of the clicked event and the negation of current event.favorite state.

val favouriteFabClickListener = object : FavoriteFabListener {
           override fun onClick(eventId: Long, isFavourite: Boolean) {
               eventsViewModel.setFavorite(eventId, !isFavourite)
           }
       }

What we discussed in this blog post works well unless we are not inserting the events with the same event id, this will cause replace on the stored events and current favorite information will be lost. To overcome this limitation we follow the following procedure

  • Fetch event id’s of all the events returned by the API response
  • Use the DAO method to find out the id’s of favorite events out of these
  • Set the favorite in the new events where IDs are in the ids returned in the second point
  • Insert the events into the database
  • Return the events from the database with ID’s as of point one

References

Continue ReadingSetting an Event Favorite in Open Event Android

Join Codeheat Coding Contest 2018/19

Codeheat is a coding contest for developers interested in contributing to Open Source software and hardware projects at FOSSASIA.  Join development of real world software applications, build up your developer profile, learn new new coding skills, collaborate with the community and make new friends from around the world! Sign up for #CodeHeat here now and follow Codeheat on Twitter.

The contest runs until 1st February 2019. Different FOSSASIA projects take part in the Codeheat contest including:

Grand prize winners will be invited to present their work at the FOSSASIA OpenTechSummit in Singapore in March 2019 and will get 600 SGD in travel funding to attend, plus a free speaker ticket and beautiful Swag.

Our jury will choose three winners from the top 10 contributors according to code quality and relevance of commits for the project. The jury also takes other contributions like submitted weekly scrum reports and monthly technical blog posts into account, but of course awesome code is the most important item on the list.

Other participants will have the chance to win Tshirts, Swag and vouchers to attend Open Tech events in the region and will get certificates of participation.

codeheat-logo

Team mentors and jury members from 10 different countries support participants of the contest.

Participants should take the time to read through the contest FAQ and familiarize themselves with the introductory information and Readme.md of each project before starting to work on an issue.

Developers interested in the contest can also contact mentors through project channels on the FOSSASIA Gitter Chat.

Links

Website: codeheat.org

Codeheat Twitter: twitter.com/codeheat_

Codeheat Facebook: facebook.com/codeheat.org

Participating Projects Code Repositories: github.com/fossasia

Continue ReadingJoin Codeheat Coding Contest 2018/19

Plot a Horizontal Bar Graph using MPAndroidChart Library in SUSI.AI Android App

Graphs and charts provide a visual representation of the data. They provide a clearer and quicker understanding of the impact of certain statistics. Thus, SUSI.AI Android app makes use of bar charts to display statistics related to user ratings for SUSI skills. This blog guides through the steps to create a Horizontal Bar Chart, using MPAndroidChart library, that has been used in the SUSI.AI Android app skill details page to display the five star skill rating by the users.

On vertical axis : Labels of the rating shown
On horizontal axis : Percentage of total number
of users who rated the skill with the corresponding
number of stars on the vertical axis

Step – 1 : Add the required dependencies to your build.gradle.

(a) Project level build.gradle

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

(b) App level build.gradle

dependencies {
    implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
}

 

Step – 2 : Create an XML layout.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
   android:layout_height="match_parent">

    <!-- Add a Horizontal Bar Chart using MPAndroidChart library -->
    <com.github.mikephil.charting.charts.HorizontalBarChart
       android:id="@+id/skill_rating_chart"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>

 

Step – 3 : Create an Activity and initialize the Horizontal Bar Chart.

class MainActivity : Activity {

   lateinit var skillRatingChart : HorizontalBarChart

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.chart)

       setSkillGraph( )

   }
}

 

Step – 4 : Create a method in your MainActivity to set up the basic properties and the axes.

/**
* Set up the axes along with other necessary details for the horizontal bar chart.
*/
fun setSkillGraph(){
   skillRatingChart = skill_rating_chart              //skill_rating_chart is the id of the XML layout

   skillRatingChart.setDrawBarShadow(false)
   val description = Description()
   description.text = ""
   skillRatingChart.description = description
   skillRatingChart.legend.setEnabled(false)
   skillRatingChart.setPinchZoom(false)
   skillRatingChart.setDrawValueAboveBar(false)

   //Display the axis on the left (contains the labels 1*, 2* and so on)
   val xAxis = skillRatingChart.getXAxis()
   xAxis.setDrawGridLines(false)
   xAxis.setPosition(XAxis.XAxisPosition.BOTTOM)
   xAxis.setEnabled(true)
   xAxis.setDrawAxisLine(false)


   val yLeft = skillRatingChart.axisLeft

//Set the minimum and maximum bar lengths as per the values that they represent
   yLeft.axisMaximum = 100f
   yLeft.axisMinimum = 0f
   yLeft.isEnabled = false

   //Set label count to 5 as we are displaying 5 star rating
   xAxis.setLabelCount(5)

//Now add the labels to be added on the vertical axis
   val values = arrayOf("1 *", "2 *", "3 *", "4 *", "5 *")
   xAxis.valueFormatter = XAxisValueFormatter(values)        

   val yRight = skillRatingChart.axisRight
   yRight.setDrawAxisLine(true)
   yRight.setDrawGridLines(false)
   yRight.isEnabled = false

   //Set bar entries and add necessary formatting
   setGraphData()

   //Add animation to the graph
   skillRatingChart.animateY(2000)
}


Here is the XAxisValueFormatter class that is used to add the custom labels to the vertical axis :

public class XAxisValueFormatter implements IAxisValueFormatter {

   private String[] values;

   public XAxisValueFormatter(String[] values) {
       this.values = values;
   }

   @Override
   public String getFormattedValue(float value, AxisBase axis) {
       // "value" represents the position of the label on the axis (x or y)
       return this.values[(int) value];
   }

}

 

Step – 5 : Set the bar entries.

/**
* Set the bar entries i.e. the percentage of users who rated the skill with
* a certain number of stars.
*
* Set the colors for different bars and the bar width of the bars.
*/
private fun setGraphData() {

   //Add a list of bar entries
   val entries = ArrayList<BarEntry>()
   entries.add(BarEntry(0f, 27f))
   entries.add(BarEntry(1f, 45f))
   entries.add(BarEntry(2f, 65f))
   entries.add(BarEntry(3f, 77f))
   entries.add(BarEntry(4f, 93f))

  //Note : These entries can be replaced by real-time data, say, from an API

  ......

}

 

Step – 6 : Now create a BarDataSet.

To display the data in a bar chart, you need to initialize a
BarDataSet instance. BarDataSet is the Subclass of DataSet class. Now, initialize the BarDataSet and pass the argument as an ArrayList of BarEntry object.

val barDataSet = BarDataSet(entries, "Bar Data Set")

 

Step – 7 : Assign different colors to the bars (as required).

private fun setGraphData() {
    .....

   //Set the colors for bars with first color for 1*, second for 2* and so on
      barDataSet.setColors(
              ContextCompat.getColor(skillRatingChart.context, R.color.md_red_500),
              ContextCompat.getColor(skillRatingChart.context, R.color.md_deep_orange_400),
              ContextCompat.getColor(skillRatingChart.context, R.color.md_yellow_A700),
              ContextCompat.getColor(skillRatingChart.context, R.color.md_green_700),
              ContextCompat.getColor(skillRatingChart.context, R.color.md_indigo_700)

   .....
)


Step – 8 : Populate data into Bar Chart.

To load the data into Bar Chart, you need to initialize a
BarData object  with bardataset. This BarData object is then passed into setData() method to load Bar Chart with data.

//Set bar shadows
   skillRatingChart.setDrawBarShadow(true)
   barDataSet.barShadowColor = Color.argb(40, 150, 150, 150)
   val data = BarData(barDataSet)

   //Set the bar width
   //Note : To increase the spacing between the bars set the value of barWidth to < 1f
   data.barWidth = 0.9f

   //Finally set the data and refresh the graph
   skillRatingChart.data = data
   skillRatingChart.invalidate()
}


Your Horizontal Bar Chart is now ready.
Note: You can format the labels as per your need and requirement with the help of XAxisValueFormatter.

Resources

Continue ReadingPlot a Horizontal Bar Graph using MPAndroidChart Library in SUSI.AI Android App