Viewing Steem Blockchain Data in a Web Browser. Part 9: Compute the Voting Power and Reputation Score of a user, Javascript "const" and "let" with block scope and loop scope

in #javascript6 years ago (edited)

This is Part 9 of a series of posts describing how to use the Steem Javascript API to view data from the Steem blockchain in a web browser. The previous 8 posts were:

  • Part 1: Text editors and minimal HTML
  • Part 2: Metadata, Unicode, and inline CSS
  • Part 3: Metadata, nav and footer bars, divs, and HTML entities
  • Part 4: Forms, scripts, and the developer console
  • Part 5: Using HTML id attributes to modify HTML elements using Javascript
  • Part 6: Using the Steem API CDN and the getAccounts() function, and examining the account data model
  • Part 7: HTML tables, javascript for/in loop, CSS in the HTML style element
  • Part 8: HTML, CSS, and Javascript comments; Javascript functions and MVC

In the last post we organized our showUserData() function by breaking out blocks of code and putting them in their own functions. We were then able to use our generateTable() function in a generic manner to mark up the data from any Object for display as an HTML table. We wrote such an Object that contained entries for user name, STEEM balance, SBD balance, and vesting shares. Now we will write functions to compute the voting power and the reputation score from the user account data and add them to our object for display in a table. We will also discuss the utility in the Steem Javascript API for calculating reputation score.

Computing the Voting Power

The voting power must be computed because the “voting_power” property of the accountData output from the API getAccounts() function is the voting power recorded at the last time a user voted. It is not the current voting power. Thus, to get the current voting power, we must compute it using the "voting_power" and "last_vote_time" properties and the current time. The voting power increases 20% per day. Let voting power (in per cent) at the last vote time equal VP0, the current voting power (in per cent) equal VPC and the amount of time elapsed since the last vote equal ΔT. Then,

VPC = VP0+ΔT*20%/day.

The voting power at the last vote time and the last vote time are provided in the account data but we need a datum for the present time in order to compute the change in time. We will use the Javascript Date objects to find the current time. The last_vote_time property is in an International Organization for Standardization (ISO) UTC date format without the appended “Z”. The acceptable date format that javascript recognizes for making a new Date object is "YYYY-MM-DDTHH:MM:SSZ". There are four digits for year (YYYY), two digits for month (MM), 2 digits for day of month (DD), then a “T”, then 2 digits for hour (HH), 2 digits for minute (MM), and 2 digits for second (SS) followed by “Z”.

Here is a javascript function that computes the current voting power:

function computeVotingPower(userAccountData){
        var lastVoteTime = userAccountData.last_vote_time;
        var votingPower = userAccountData.voting_power;
        var timeBeforeNow = (Date.now() - new Date(lastVoteTime+"Z"))/1000;
        var votingPowerNow = Math.min(votingPower/100+timeBeforeNow*20/86400,100).toFixed(2);
        return votingPowerNow+"%";
 }

The function uses “seconds” as the common unit of time. Because the javascript Date objects report time in milliseconds, the time change is divided by 1000 to convert to seconds. There are 86,400 seconds in a day so the recharge rate is computed in percent per second (20/86400 %/s). The units of the value of the voting_power property in the account data is percent times 100 so it is divided by 100 to convert it to percent. We use the Javascript Math object’s “Math.min()” method which returns the smaller of the voting power or 100% because voting power is not refreshed beyond 100%. Then, we use the Javascript Number object's Number.toFixed() to format the output string to have only 2 digits after the decimal point. The function returns a string that is the current voting power with a percent sign on the end.

Computing the Reputation Score

The reputation score is calculated from the raw reputation score using its log10, some scaling, and addition. I found an older Steemit post on reputation score with the javascript code for it. This has changed a bit: there is now a formatter in the api with a reputation score function that takes the raw reputation score already extracted from the user account data and converts it to the reputation score we are used to seeing.

The function in the formatter is based on the same math as that in the older Steemit post but the code is a bit different. The code in the formatter uses every possible way to avoid using the javascript Math.log10() function. It calculates the integer part of the log10 from the length of the number and then estimates the decimal part using the first four digits of the raw reputation score, the Math.log() function, and the formula log10(x)=ln(x)/ln(10).

Here is the code from the API:

function(reputation) {
      if (reputation == null) return reputation;
      reputation = parseInt(reputation);
      let rep = String(reputation);
      const neg = rep.charAt(0) === "-";
      rep = neg ? rep.substring(1) : rep;
      const str = rep;
      const leadingDigits = parseInt(str.substring(0, 4));
      const log = Math.log(leadingDigits) / Math.log(10);
      const n = str.length - 1;
      let out = n + (log - parseInt(log));
      if (isNaN(out)) out = 0;
      out = Math.max(out - 9, 0);
      out = (neg ? -1 : 1) * out;
      out = out * 9 + 25;
      out = parseInt(out);
      return out;
}

Here is a breakdown of what the code does:

  1. Check to see if raw reputation is null. If it is, return null.
  2. Turn the string into an integer.
  3. Turn the integer into a string.
  4. Determine if the raw score is negative
  5. If the raw score is negative, make it positive.
  6. Get the first 4 digits of the raw reputation score from the string.
  7. Use natural log to calculate the log base 10 of the first 4 digits.
  8. Use the length of the raw reputation score string to compute the integer part of the log base 10.
  9. Add the integer part of the log to the decimal part of the log estimated from the first 4 digits of the raw reputation score.
  10. Set the output to zero if it is not a number.
  11. Subtract 9 from the output and set to zero if less than zero
  12. Redundantly turn the output positive if it is negative (already done in 11)
  13. Multiply the output by 9 and add 25
  14. Turn the output into its integer part and return it as the reputation score.

const and let, Block Scope and Loop Scope
You may notice that the API code uses “const” and “let” instead of just "var" to declare variables. "const" and "let" were introduced in a new version of Javascript called ES2015 (a.k.a. ES6) along with two new types of “scope”. Before ES2015, there was only global scope and function scope and variables were declared using only “var”. ES2015 introduced block scope and loop scope and the "const" and "let" declaration types which have block scope and loop scope. "const" variables behave like "let" variables but "const" variables have the additional feature that they can’t be reassigned. "let" and "const" are relatively new but are supported in most browsers now. They are not fully supported in Internet Explorer 11 and earlier.

"let" variables have loop scope, block scope, global scope, and function scope. They are not hoisted (they can’t be used before they are declared) and they do not belong to the HTML window object like "var" variables do. "let" variables can’t be redeclared in the same scope but "var" variables can be. For examples of "let" variable behavior see here and here.

"const" variables behave like "let" variables but can’t be re-assigned and you can (and must) only assign to them when they are declared. Primitive values in a "const" variable can’ t be changed but elements can be added and removed from "const" objects and arrays. "const" objects and arrays themselves can’t be re-assigned. For examples of "const" variable behavior see here and here.

An Alternative Reputation Score Function

Before I noticed the formatter, I wrote a function to compute the reputation score and report it with two decimal places. The function is based on the old code in a Steemit post written by @digitalnotvir. Here it is:

function computeReputationScore(userAccountData){
        var repScore = Math.sign(userAccountData.reputation)*userAccountData.reputation;
        repScore = Math.log10(repScore);
        if(isNaN(repScore)){repScore = 0};
        repScore = Math.max(repScore-9,0);
        repScore = Math.sign(repScore)*repScore;
        repScore = (repScore*9)+25;
        repScore = repScore.toFixed(2);
        return repScore;
}

To use this function, we will place it inside the “<script>” tags above the showUserData() function in the HTML document from previous posts and will call it in the showUserData() function. Here is the complete HTML document for reference:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="description" content="Show a Steemit User's Account Data">
    <title>Show User's Data</title>
    <script src="https://cdn.steemjs.com/lib/latest/steem.min.js"></script>
    <style>
      td,th,footer,nav {
        border: 1px solid black;
      }
      table {
        width: 100%;
      }
      div {
        padding: 10px;
        margin: 5px;
        background-color: pink;
      }
      #dataView {
        background-color: lightsalmon;
        overflow: auto;
        height: 200px;
      }
      #formDiv {
        background-color: lightblue;
      }
    </style>
  </head>
  <body>
    <nav>
    </nav>
    <br>
    <div id="formDiv">
      <br>
      <form action="#" onsubmit="showUserData();return false;" id="nameForm">
        Enter your steemit userName: <input type="text" name="sUser" id="userNid">
        <input type="submit" value="Submit">
      </form>
      <br>
    </div>
    <div id="dataView">
      <br>
      <p></p>
      <br>
    </div>
    <br>
    <footer>
    </footer>
    <script>
      var htmlElements = {};
      var userInputs = {};
      function generateTable(header1,header2,objectToTable){
        var tableText = "<table>";
        tableText = tableText+"<tr><th>"+header1+"</th><th>"+header2+"</th></tr>";
        for (var key in objectToTable){
            var keyStr = key.replace(/_/g," ");
            tableText = tableText+"<tr><td>"+keyStr+"</td><td>"+objectToTable[key]+"</td></tr>";
        }
        tableText = tableText+"</table>";
        return tableText;
      }
      function computeVotingPower(userAccountData){
        var lastVoteTime = userAccountData.last_vote_time;
        var votingPower = userAccountData.voting_power;
        var timeBeforeNow = (Date.now() - new Date(lastVoteTime+"Z"))/1000;
        var votingPowerNow = Math.min(votingPower/100+timeBeforeNow*20/86400,100).toFixed(2);
        return votingPowerNow+"%";
      }
      function computeReputationScore(userAccountData){
        var repScore = Math.sign(userAccountData.reputation)*userAccountData.reputation;
        repScore = Math.log10(repScore);
        if(isNaN(repScore)){repScore = 0};
        repScore = Math.max(repScore-9,0);
        repScore = Math.sign(repScore)*repScore;
        repScore = (repScore*9)+25;
        repScore = repScore.toFixed(2);
        return repScore;
      }
      function getElementReferences(elementNames){
        for (index in elementNames){
          var name = elementNames[index];
          htmlElements[name] = document.getElementById(name);
        }
      }
      function getUserInput(formElements){
        for( var i=0;i<formElements.length;i++){
          if(formElements[i].name !== ""){
            userInputs[formElements[i].name]=formElements[i].value;
          }
        }
      }
      function showUserData() {
        getElementReferences(['nameForm','dataView']);
        getUserInput(htmlElements.nameForm.elements);
        steem.api.getAccounts([userInputs.sUser], function(err,result){
          var userData = result[0];
          var dataToTable = {
            User_Name: userData.name,
            STEEM_Balance: userData.balance,
            SBD_Balance: userData.sbd_balance,
            Voting_Power: 0,
            Reputation_Score: 0,
            Reputation_Score_Formatter: 0
          };
          dataToTable.Voting_Power = computeVotingPower(userData);
          dataToTable.Reputation_Score_Formatter = steem.formatter.reputation(userData.reputation);
          dataToTable.Reputation_Score = computeReputationScore(userData);
          var theText = generateTable('Metric', 'Value',dataToTable);
          htmlElements.dataView.innerHTML = theText;
        });
      }
    </script>
  </body>
</html>

The new "computeReputationScore()" function is called in "showUserData()" with the userData as an argument and the output is placed directly into the "Reputation_Score" property of the "dataToTable" Object. The use of the API formatter utility is also shown and its output is placed in "dataToTable.Reputation_Score_Formatter".
(Notice that the "generateTable()" function has been altered from previous versions by changing String.replace("_"," "); to String.replace(/_/g," "); to replace the underscore globally in the key name rather than just the first underscore.)

After saving the HTML document using a text editor, loading it into the web browser, entering a valid Steemit user name, and clicking the submit button, the web browser looks like this:
Screen Shot 2018-09-18 at 11.26.05 AM.png

Next Post Preview

In the next post we will discuss computing the Steem Power. This relies on the global properties obtained using the "getDynamicGlobalProperties()" API function and the "getVestingDelegations()" API function and thus Promises returned from asynchronous functions will be required to make sure we have the global properties, vesting delegations data, and user account data before trying to compute Steem Power. Asynchronous functions will be required because javascript does not wait for data to be fetched from the internet before it starts running other lines of code. If we try to use values we haven't yet received, the computation will not be correct.

Sort:  

Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!

Your post had been curated by the @buildawhale team and mentioned here:

https://steemit.com/curation/@buildawhale/buildawhale-curation-digest-09-21-18

Keep up the good work and original content, everyone appreciates it!

Congratulations @numberjocky! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

Award for the total payout received

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Support SteemitBoard's project! Vote for its witness and get one more award!