Youtube search as a Console Service Endpoint in SUSI.AI

SUSI.AI now has the ability to search and play any song or video directly in the webclient and the speaker. When a user asks for a query regarding playing a song, the clients sends a search request to the server. In this post I will discuss about the server side implementation of the Youtube search. Every time a request is made by any client, the client sends a query request to the server in the form of a json object. For more on the working on the webclient side can be seen here.

The endpoint for youtube search is  http://api.susi.ai/susi/console.json

Continue ReadingYoutube search as a Console Service Endpoint in SUSI.AI

Delete User Account Service using LoginService.java API in SUSI.AI

SUSI.AI has an api to handle all account related services called accounts.susi.ai. This API provides services like change password and delete account. the main goal of accounts.susi.ai is to centralise the accounts’ related settings from web, android and iOS clients into a single place like other accounting services. In this post we will discuss one of these features which is the delete user account from SUSI.AI.

The api accounts.susi.ai has a react file DeleteAccount.react.js. This file handle the delete account service and interacts with the LoginServce.java api, which is the core file for authentication. I will discuss in details how these two interact with each other and with other files.

Continue ReadingDelete User Account Service using LoginService.java API in SUSI.AI

How User preferences data is stored in Chat.susi.ai using Accounting Model

Like any other accounting services SUSI.AI also provides a lot of account preferences. Users can select their language, timezone, themes, speech settings etc. This data helps users to customize their experience when using SUSI.AI.

In the web client these user preferences are fetch from the server by UserPreferencesStore.js and the user identity is fetched by UserIdentityStore.js. These settings are then exported to the settings.react.js file. This file is responsible for the settings page and takes care of all user settings. Whenever a user changes a setting, it identifies the changes and show an option to save these changes. These changes are then updated on the server using the accounting model of SUSI.AI. Let’s take a look at each file discussed above in detail.

Continue ReadingHow User preferences data is stored in Chat.susi.ai using Accounting Model

Integrating Gravatar and Anonymizing Email Address in Feedback Section

SUSI skills are having a very nice feedback system that allows the user to rate skills from 1-star to 5-star and showing ratings in skills screens. SUSI also allow the user to post feedback about skills and display them. You can check out how posting feedback implemented here and how displaying feedback feature implemented here. To enhance the user experience, we are adding user gravatar in the feedback section and to respect user privacy, we are anonymizing the user email displayed in the feedback section. In this post, we will see how these features implemented in SUSI iOS.

Integrating Gravatar –

We are showing gravatar of the user before feedback. Gravatar is a service for providing globally-unique avatars. We are using user email address to get the gravatar. The most basic gravatar image request URL looks like this:

https://www.gravatar.com/avatar/HASH

where HASH is replaced with the calculated hash for the specific email address we are requesting. We are using the MD5 hash function to hash the user’s email address.

The MD5 hashing algorithm is a one-way cryptographic function that accepts a message of any length as input and returns as output a fixed-length digest value to be used for authenticating the original message.

In SUSI iOS, we have MD5Digest.swift file that gives the hash value of email string. We are using the following method to set gravatar:

if let userEmail = feedback?.email {
setGravatar(from: userEmail)
}
func setGravatar(from emailString: String) {
let baseGravatarURL = "https://www.gravatar.com/avatar/"
let emailMD5 = emailString.utf8.md5.rawValue
let imageString = baseGravatarURL + emailMD5 + ".jpg"
let imageURL = URL(string: imageString)
gravatarImageView.kf.setImage(with: imageURL)
}

Anonymizing User’s Email Address –

Before the implementation of this feature, the user’s full email address was displayed in the feedback section and see all review screen. To respect the privacy of the user, we are now only showing user email until the `@` sign.

In Feedback object, we have the email address string that we modify to show until `@` sign by following way:

if let userEmail = feedback?.email, let emailIndex = userEmail.range(of: "@")?.upperBound {
userEmailLabel.text = String(userEmail.prefix(upTo: emailIndex)) + "..."
}

 

Final Output –

Resources –

  1. Post feedback for SUSI Skills in SUSI iOS
  2. Displaying Skills Feedback on SUSI iOS
  3. What is MD5?
Continue ReadingIntegrating Gravatar and Anonymizing Email Address in Feedback Section

Countrywise Usage Analytics of a Skill in SUSI.AI

The statistics regarding which country the skills are being used is quite important. They help in updating the skill to support the native language of those countries. SUSI.AI must be able to understand as well as reply in its user’s language. So mainly the server side and some client side (web client) implementation of country wise skill usage statistics is explained in this blog.

Fetching the user’s location on the web client

  1. Add a function in chat.susi.ai/src/actions/API.actions.js to fetch the users location. The function makes a call to freegeoip.net API which returns the client’s location based on its IP address. So country name and code are required for country wise usage analytics.

export function getLocation(){
  $.ajax({
    url: 'https://cors-anywhere.herokuapp.com/http://freegeoip.net/json/',
    success: function (response) {
      _Location = {
        lat: response.latitude,
        lng: response.longitude,
        countryCode: response.country_code,
        countryName: response.country_name
      };
    },
  });
}
  1. Send the location parameters along with the query while fetching reply from SUSI server in chat.json API. The parameters are country_name and country_code.

if(_Location){
	url += '&latitude='+_Location.lat+'&longitude='+_Location.lng+'&country_code='+_Location.countryCode+'&country_name='+_Location.countryName;
}

Storage of Country Wise Skill Usage Data on SUSI Server

  1. Create a countryWiseSkillUsage.json file to store the country wise skill usage stats and make a JSONTray object for that in src/ai/susi/DAO.java file. The JSON file contains the country name, country code and the usage count in that country.
  1. Modify the src/ai/susi/server/api/susi/SusiService.java file to fetch country_name and country_code from the query parameters and pass them SusiCognition constructor.

String countryName = post.get("country_name", "");
String countryCode = post.get("country_code", "");
...

SusiCognition cognition = new SusiCognition(q, timezoneOffset, latitude, longitude, countryCode, countryName, language, count, user.getIdentity(), minds.toArray(new SusiMind[minds.size()]));
  1. Modify the src/ai/susi/mind/SusiCognition.java file to accept the countryCode and countryName in the constructor parameters. Check which skill is being currently used for the response and update the skill usage stats for that country in countryWiseSkillUsage.json. Call the function updateCountryWiseUsageData() to update the skill usage data.

if (!countryCode.equals("") && !countryName.equals("")) {
    List<String> skills = dispute.get(0).getSkills();
    for (String skill : skills) {
        try {
            updateCountryWiseUsageData(skill, countryCode, countryName);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The updateCountryWiseUsageData() function accepts the skill path , country name and country code. It parses the skill path to get the skill metadata like its model name, group name, language etc. The function then checks if the country already exists in the JSON file or not. If it exists then it increments the usage count by 1 else it creates an entry for the skill in the JSON file and initializes it with the current country name and usage count 1.

for (int i = 0; i < countryWiseUsageData.length(); i++) {
  countryUsage = countryWiseUsageData.getJSONObject(i);
  if (countryUsage.get("country_code").equals(countryCode)) {
    countryUsage.put("count", countryUsage.getInt("count")+1);
    countryWiseUsageData.put(i,countryUsage);
  }
}

API to access the Country Wise Skill Usage Data

  1. Create GetCountryWiseSkillUsageService.java file to return the usage stats stored in countryWiseSkillUsage.json

public ServiceResponse serviceImpl(Query call, HttpServletResponse response, Authorization rights, final JsonObjectWithDefault permissions) {
        
  ...  // Fetch the query parameters
  JSONArray countryWiseSkillUsage = languageName.getJSONArray(skill_name);
  return new ServiceResponse(result);
}
  1. Add the API file to src/ai/susi/server/api/susi/SusiServer.java

services = new Class[]{
	...
	//Skill usage data
	GetCountryWiseSkillUsageService.class
	...
}

 

Endpoint : /cms/getCountryWiseSkillUsage.json

Parameters

  • model
  • group
  • language
  • Skill

Sample query: /cms/getCountryWiseSkillUsage.json?model=general&group=Knowledge&language=en&skill=aboutsusi

Sample response:

{  
   "skill_usage":[  
      {  
         "country_code":"MYS",
         "country_name":"Malaysia",
         "count":1
      },
      {  
         "country_code":"MYS",
         "country_name":"Malaysia",
         "count":1
      }
   ],
   "session":{  
      "identity":{  
         "type":"host",
         "name":"162.158.154.147_81c88a10",
         "anonymous":true
      }
   },
   "skill_name":"ceo",
   "accepted":true,
   "message":"Country wise skill usage fetched"
}

Resources

Continue ReadingCountrywise Usage Analytics of a Skill in SUSI.AI

Disable editing for non-editable skills for non-admin users

As the Skills in SUSI Skill CMS are publicly editable, any user has the access to edit them. Hence, there needed to be a better control over who can edit the Skills in CMS. We needed to implement a feature to allow Admins and higher user roles to change the status of a Skill to non-editable. The subsequent implementation on CMS would require disabling editing for non-editable Skills for non-admin users. This blog post explains how this feature has been implemented in SUSI.AI.

Adding a boolean parameter ‘editable’ to the Skill metadata

We needed to add a boolean parameter in the Skill metadata for each Skill. The boolean parameter is ‘editable’. If its value is true, then it implies that editing should be allowed for that Skill. If it is set to false, then the Skill should not be editable for non-admin users. By default, its value has been set to true for all Skills. This is implemented as follows in the SusiSkill.java file:

    // in the getSkillMetadata() method
  skillMetadata.put("editable", getSkillEditStatus(model, group, language, skillname));

    // declaration of the getSkillEditStatus() method
    public static boolean getSkillEditStatus(String model, String group, String language, String skillname) {
        // skill status
        JsonTray skillStatus = DAO.skillStatus;
        if (skillStatus.has(model)) {
            JSONObject modelName = skillStatus.getJSONObject(model);
            if (modelName.has(group)) {
                JSONObject groupName = modelName.getJSONObject(group);
                if (groupName.has(language)) {
                    JSONObject languageName = groupName.getJSONObject(language);
                    if (languageName.has(skillname)) {
                        JSONObject skillName = languageName.getJSONObject(skillname);

                        if (skillName.has("editable")) {
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

 

Allowing Admin and higher user roles to change edit status of any Skill

This is facilitated by the endpoint ‘/cms/changeSkillStatus.json’. Its minimum base user role is set to Admin so that only Admins and higher user roles are able to change status of any Skill. A sample API call to this endpoint to change the edit status of any Skill to ‘false’ is as follows:

http://127.0.0.1:4000/cms/changeSkillStatus.json?model=general&group=Knowledge&language=en&skill=aboutsusi&editable=false&access_token=zdasIagg71NF9S2Wu060ZxrRdHeFAx

 

If we want to change the edit status of any Skill to ‘false’, then we need to add the Skill to the ‘skillStatus.json’ file. For this, we need to traverse inside the JSONObject in the ‘skillStatus.json’ file. We need to traverse inside the model, group and language as specified in the query parameters. This is done as follows:

   if(editable.equals("false")) {
       skill_status.put("editable", false);
   }

   JsonTray skillStatus = DAO.skillStatus;

   if (skillStatus.has(model_name)) {
        modelName = skillStatus.getJSONObject(model_name);
        if (modelName.has(group_name)) {
            groupName = modelName.getJSONObject(group_name);
            if (groupName.has(language_name)) {
                languageName = groupName.getJSONObject(language_name);

                if (languageName.has(skill_name)) {
                    skillName = languageName.getJSONObject(skill_name);

                    if(editable != null && editable.equals("false")) {
                        skillName.put("editable", false);
                    }
                    else if(editable != null && editable.equals("true")) {
                        skillName.remove("editable");
                    }

                    skillStatus.commit();
                    result.put("accepted", true);
                    result.put("message", "Skill status changed successfully.");
                    return new ServiceResponse(result);
                }
            }
        }
    }

 

If we want to change the edit status of any Skill to ‘true’, then we need to remove the Skill from the ‘skillStatus.json’ file. We also need to remove all the empty JSONObjects inside the ‘skillStatus.json’ file, if they are created in the process of removing Skills from it. This is done as follows:

   if (skillStatus.has(model_name)) {
        modelName = skillStatus.getJSONObject(model_name);
        if (modelName.has(group_name)) {
            groupName = modelName.getJSONObject(group_name);
            if (groupName.has(language_name)) {
                languageName = groupName.getJSONObject(language_name);
                if (languageName.has(skill_name)) {
                    skillName = languageName.getJSONObject(skill_name);
                    if(editable != null && editable.equals("true")) {
                        skillName.remove("editable");
                    }
                    if(skillName.length() == 0) {
                        languageName.remove(skill_name);
                        if(languageName.length() == 0) {
                            groupName.remove(language_name);
                            if(groupName.length() == 0) {
                                modelName.remove(group_name);
                                if(modelName.length() == 0) {
                                    skillStatus.remove(model_name);
                                }
                            }
                        }
                    }
                    skillStatus.commit();
                }
            }
        }
    }

 

Disabling editing for non-editable Skills for non-admin users on Skill CMS

For the Skills whose edit status has been set to ‘false’ by the Admins, we need to allow the non-admin users to only be able to view the code of the Skill, and not permit them to change the code and save the changes to the Skill. We need to display a message to the users about the possible reasons. All the code for displaying the message is put in an if() condition as follows:

   if (
      cookies.get('loggedIn') &&
      !this.state.editable &&
      !this.state.showAdmin
    )

 

This is how the Skill edit page for a non-editable Skill would look like for a non-admin user:

For an Admin user, this would look exactly same like an editable Skill page. Admin user would be able to edit and make changes to the Skill code and save the changes.

This is how editing of non-editable Skills have been disabled for non-admin users.

Resources

Continue ReadingDisable editing for non-editable skills for non-admin users

Giving users option to switch between All and Reviewed Only Skills on SUSI Skill CMS

There are a lot of Skills on SUSI Skill CMS. Any registered user has the access to creating his/her own Skills. Hence, we need to give the users an option on SUSI Skill CMS whether they want to see all the Skills, or only those Skills that have been tested thoroughly and have been approved by the Admin and higher user roles. This blog post explains how this feature has been implemented on the SUSI Skill CMS.

How is review status of any Skill changed on the server?

The API endpoint which allows Admin and higher user roles to change the review status of any Skill on the server is ‘/cms/changeSkillStatus.json’. It takes the following parameters:

  • model: Model of the Skill
  • group: Group of the Skill
  • language: Language of the Skill
  • skill: Skill name
  • reviewed: A boolean parameter which if true, signifies that the Skill has been approved.

Sample API call:

https://api.susi.ai/cms/changeSkillStatus.json?model=general&group=Knowledge&language=en&skill=aboutsusi&reviewed=true&access_token=yourAccessToken

 

Fetching reviewed only Skills from the server

The ‘/cms/getSkillList.json’ endpoint has been modified to facilitate returning only the Skills whose review status is true. This is done by the following API call:

https://api.susi.ai/cms/getSkillList.json?reviewed=true

 

Creating checkbox to switch between All and Reviewed Only Skills

Checkbox is one of the many Material-UI components. Hence, we need to first import it before we can use it directly in our BrowseSkill component.

import Checkbox from 'material-ui/Checkbox';

 

In the constructor of the BrowseSkill class, we set states for two variables as follows:

constructor(props) {
  super(props);
    this.state = {
      // other state variables
      showSkills: '',
      showReviewedSkills: false,
    };
}

 

In the above code for the constructor, we have set two state variables. ‘showSkills’ is a string which can either be an empty string, or ‘&reviewed=true’. We want to append this string to the ‘/cms/getSkillList.json’ API call because it would determine whether we want to fetch All Skills or reviewed only Skills. The second variable ‘showReviewedSkills’ is a boolean used to keep record of the current state of the page. If it is true, then it means that currently, only the reviewed Skills are being displayed on the CMS site.

Implementation of the checkbox

This is how the Checkbox has been implemented for the purpose of switching between All and Reviewed Only Skills:

 <Checkbox
    label="Show Only Reviewed Skills"
    labelPosition="right"
    className="select"
    checked={this.state.showReviewedSkills}
    labelStyle={{ fontSize: '14px' }}
    iconStyle={{ left: '4px' }}
    style={{
      width: '256px',
      paddingLeft: '8px',
      top: '3px',
    }}
    onCheck={this.handleShowSkills}
  />

 

As can be seen from the above code, the initial state of the checkbox is unchecked as initially, the value of the state variable ‘showReviewedSkills’ is set to false in the constructor. This means that initially all Skills will be shown to the user. On clicking on the checkbox, handleShowSkills() function is called. Its implementation is as follows:

  handleShowSkills = () => {
    let value = !this.state.showReviewedSkills;
    let showSkills = value ? '&reviewed=true' : '';
    this.setState(
      {
        showReviewedSkills: value,
        showSkills: showSkills,
      },
      function() {
        this.loadCards();
      },
    );
  };

 

In the handleShowSkills() function, firstly we store the current value of the state variable ‘showReviewedSkills’. The value of ‘showSkills’ string is determined according to the value of ‘showReviewedSkills’. Then the states of both these variables are updated in the setState() function. Lastly, loadCards() function is called.

In the loadCards() function, we append the value of the state variable ‘showSkills’ to the AJAX call to the ‘/cms/getSkillList.json’ endpoint. The URL used for the API call is as follows:

https://api.susi.ai/cms/getSkillList.json?group=' +
  this.props.routeValue +
  '&language=' +
  this.state.languageValue +
  this.state.filter +
  this.state.showSkills;

 

This is how the implementation of the feature to give users an option to switch between All and Reviewed Only Skills has been done.

Resources

Continue ReadingGiving users option to switch between All and Reviewed Only Skills on SUSI Skill CMS

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

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