Tuesday 6 June 2017

Web Tutorial: The Bar Chart (Part 2/2)

Now that we have data, and placeholders to display that data with, let's focus on displaying the data.

Add this to your displayData() function. We'll declare four more variables - fill, p, percentage and actual. Then we'll use a For loop to iterate through the cols array of the graphdata object.
            function displayData()
            {
                var row = document.getElementById("ddlRow").value;
                var stat = document.getElementById("ddlStat").value;

                var max = getMaxStatistic(row,stat);
                var fill,p;
                var percentage,actual;

                for (var i=0;i<graphdata.cols.length;i++)
                {

                }

                displayScale(max);
            }


Here, we set actual to the figure derived from the getStatistic() function which we've created below.
            function displayData()
            {
                var row = document.getElementById("ddlRow").value;
                var stat = document.getElementById("ddlStat").value;

                var max = getMaxStatistic(row,stat);
                var fill,p;
                var percentage,actual;

                for (var i=0;i<graphdata.cols.length;i++)
                {
                    actual = getStatistic(graphdata.cols[i],row,stat);
                }

                displayScale(max);
            }

            function getStatistic(data,row,stat)
            {

            }


Then we'll use actual and max to define the percentage that the red bar should go down by. percentage, in this case, would be the 100% less the percentage of the actual statistic, actual, against the maximum statistic, max.
                for (var i=0;i<graphdata.cols.length;i++)
                {
                    actual = getStatistic(graphdata.cols[i],row,stat);
                    percentage = 100 - ((actual/max) * 100);
                }


Next, we set fill to the element whose id we defined in the previous part of this tutorial, and set the margin-top property to the value of percentage. To obtain the exact number of pixels the red bar should be offset by, we'll use this value, divided by 100 (because it's a percentage, see?) and multiply it by 80% of 500! Why 80% of 500? Well, the containing element is 80% of its containing element's height, which in turn is 100% of the graph_container's height, which is 500 pixels! Go look at the CSS again if you are in doubt!

And of course, we'll set p to the paragraph element whose id we also defined in the previous part of this tutorial, and use the innerHTML property to display the value of actual.
                for (var i=0;i<graphdata.cols.length;i++)
                {
                    actual = getStatistic(graphdata.cols[i],row,stat);
                    percentage = 100 - ((actual/max) * 100);

                    fill = document.getElementById("fill_" +i);
                    fill.style.marginTop = (percentage/100 * (0.8*500)) + "px";

                    p = document.getElementById("p_" +i);
                    p.innerHTML = actual;
                }


Now we're going to work on the getStatistic() function. It's even simpler than the getMaxStatistic() function we wrote earlier. It's similar, except that in this case, you don't need the entire data object. You just need the appropriate dataset, which is passed in as the first argument. Similar to getMaxStatistic(), row and stat are passed in as arguments.

Here, we define a variable, temp, and use the filter() method to return data the same way we did with getMaxStatistic(). If there isn't such a row, the temp array will be empty and you'll return 0. Otherwise, you return the required statistic of the first (and only) element of temp.
            function getStatistic(data,row,stat)
            {
                var temp;

                temp = data.stats.filter(function (x) {return x.year==row;});

                if (temp.length>0)
                {
                    return temp[0][stat];
                }

                return 0;
            }


Ohhhkay, the data appears to be correct. Check the data at the top of the red columns and see that it corresponds to the scale! But this is messy and we're going to clean it up.


Add the overflow property to the CSS class data_container, and set the value to hidden.
            .data_container
            {
                margin: 0 auto 0 auto;
                width:80%;
                height:80%;
                background-color:transparent;
                overflow:hidden;
            }


Better now!


Let's test the dataset by altering the code for the drop-down lists. So the drop-down lists will call displayData() all over again when the value changes.
        <select id="ddlRow" onchange="displayData();">

        </select>

        <select id="ddlStat" onchange="displayData();">

        </select>


So now when you change the values, you should see the data change with them!


The transition is a little abrupt, so do this...
            .data_fill
            {
                width:100%;
                height:100%;
                color:#000000;
                background-color:#FF0000;
                text-align:center;
                font-weight:bold;
                font-family:verdana;
                font-size:0.8em;
                margin-top:0%;
                -webkit-transition:all 1s;
                transition:all 1s;
            }


Displaying the Average

OK, so far so good. But let's add more value to your bar chart. Let's display the average value! To do that, let's add some HTML. So you have a div with an id of average_container, and a div nested inside it with an id of average_fill. Inside it is a paragraph tag with the id average_label.
        <div id="graph_container">
            <div id="average_container">
                <div id="average_fill">
                    <p id="average_label"></p>
                </div>
            </div>           

            <div id="scale_container">

            </div>

            <div id="col_container">

            </div>
        </div>


And here's the CSS styling. I've given average_container and average_fill a nice orange outline for better visibility.

average_container, same as data_container, has 80% of the parent div, graph_container. That's not a coincidence. margin-bottom is set to -100% because average_container comes before scale_container and col_container in the HTML and we don't want it to force them out of the graph_container div. The overflow property is set to hidden for the exact same reason it's set to hidden for the data_container class. Because we'll be performing a similar operation with the average data.

The width of average_fill div is set to 100% of average_container's. I've given it a dotted black top border. text alignment, and transition are just there to make things look pretty, so just ignore them if you want to.

average_label's styling is purely cosmetic.
            .col_section
            {
                width:100px;
                height:100%;
                float:left;
            }

            #average_container
            {
                height:80%;
                margin-bottom:-100%;
                overflow:hidden;
                outline:1px solid #FF4400;
            }

            #average_fill
            {
                width:100%;
                border-top:1px dotted #000000;
                text-align:center;
                -webkit-transition:all 1s;
                transition:all 1s;
                outline:1px solid #FF4400;
            }

            #average_label
            {
                width:100%;
                color:#000000;
                font-weight:bold;
                font-family:verdana;
                text-align:center;
            }           

            .data_container
            {
                margin: 0 auto 0 auto;
                width:80%;
                height:80%;
                background-color:transparent;
                overflow:hidden;
            }


There you should see the orange outline of average_container, and the black dotted line at the top from average_fill. We didn't specify a height for average_fill or a value to put in average_label, yet, so average_fill's height is still at the minimum.

Let's add a bit to our displayData() function, at the end. Here, we declare a variable, average, and define its value by running the getAverageStatistic() function which we'll create right after this.
Then we define the variables average_container, average_fill and average_label; and then we assign them to their similarly named counterparts in the DOM.
            function displayData()
            {
                var row = document.getElementById("ddlRow").value;
                var stat = document.getElementById("ddlStat").value;

                var max = getMaxStatistic(row,stat);
                var fill,p;
                var percentage,actual;

                for (var i=0;i<graphdata.cols.length;i++)
                {
                    actual = getStatistic(graphdata.cols[i],row,stat);
                    percentage = 100 - ((actual/max) * 100);

                    fill = document.getElementById("fill_" +i);
                    fill.style.marginTop = (percentage/100 * (0.8*500)) + "px";

                    p = document.getElementById("p_" +i);
                    p.innerHTML = actual;
                }

                displayScale(max);

                var average = getAverageStatistic(row,stat);
               
                var average_container = document.getElementById("average_container");
                var average_fill = document.getElementById("average_fill");
                var average_label = document.getElementById("average_label");
            }

            function getAverageStatistic(row,stat)
            {

            }


Next, we again define percentage as 100% less the percentage of average against max. average_container's width is defined as 100 pixels multiplied by the number of columns (that is, the number of elements in the cols array of the graphdata object). average_fill is pushed downwards proportionally to the average value of the dataset. Finally, we populate the content of average_label with the value of average.
            function displayData()
            {
                var row = document.getElementById("ddlRow").value;
                var stat = document.getElementById("ddlStat").value;

                var max = getMaxStatistic(row,stat);
                var fill,p;
                var percentage,actual;

                for (var i=0;i<graphdata.cols.length;i++)
                {
                    actual = getStatistic(graphdata.cols[i],row,stat);
                    percentage = 100 - ((actual/max) * 100);

                    fill = document.getElementById("fill_" +i);
                    fill.style.marginTop = (percentage/100 * (0.8*500)) + "px";

                    p = document.getElementById("p_" +i);
                    p.innerHTML = actual;
                }

                displayScale(max);

                var average = getAverageStatistic(row,stat);
               
                var average_container = document.getElementById("average_container");
                var average_fill = document.getElementById("average_fill");
                var average_label = document.getElementById("average_label");

                percentage = 100 - ((average/max) * 100);
                average_container.style.width =  (graphdata.cols.length * 100) + "px";
                average_fill.style.marginTop = (percentage/100 * (0.8*500)) + "px";
                average_label.innerHTML = "Average: " + average;
            }

            function getAverageStatistic(row,stat)
            {

            }


And of course, we'll need to work on the function getAverageStatistic(). It's actually pretty similar to the getMaxStatistic() function. We have to iterate through the entire graphdata object's cols array. But instead of getting the maximum value, we want the average value. So here we add all the relevant values and then divide by the total number of columns. The number is fixed to one decimal place using the toFixed() method.
            function getAverageStatistic(row,stat)
            {
                var average=0;
                var temp;

                for (var i=0;i<graphdata.cols.length;i++)
                {
                    temp = graphdata.cols[i].stats.filter(function (x) {return x.year==row;});

                    if (temp.length>0)
                    {
                        average = average + temp[0][stat];
                    }
                }

                return (average / (graphdata.cols.length)).toFixed(1);
            }


Here, we've put in a If conditional to prevent the unlikely scenario that cols is an empty array, because we really don't want a divide-by-zero error.
            function getAverageStatistic(row,stat)
            {
                var average=0;
                var temp;

                for (var i=0;i<graphdata.cols.length;i++)
                {
                    temp = graphdata.cols[i].stats.filter(function (x) {return x.year==row;});

                    if (temp.length>0)
                    {
                        average = average + temp[0][stat];
                    }
                }

                if (graphdata.cols.length == 0)
                {
                    return 0;
                }
                else
                {
                    return (average / (graphdata.cols.length)).toFixed(1);
                }
            }


Here you go. If you change the dataset, you'll see the average value shift!


You might want to change these outline properties now.
            #average_container
            {
                height:80%;
                margin-bottom:-100%;
                overflow:hidden;
                outline:0px solid #FF4400;
            }

            #average_fill
            {
                width:100%;
                border-top:1px dotted #000000;
                text-align:center;
                -webkit-transition:all 1s;
                transition:all 1s;
                outline:0px solid #FF4400;
            }


Pretty! But there's something missing...


The Final Touch

Just add this line to your data_container CSS class.
            .data_label
            {
                margin: 0 auto 0 auto;
                text-align:center;
                width:100%;
                height:20%;
                color:#000000;
                font-weight:bold;
                font-family:verdana;
                font-size:0.8em;
                border-top:1px solid #000000;
            }           


OK, looking pretty much complete now. There's a nice black border on top of the column labels, forming the illusion of a solid black line separating the labels from the data.

That's the end of our web tutorial!

I had fun. Geeky, mathemathical fun, but fun nonetheless. We'll do more charts in future, so stay tuned.

Editor's Note: The dataset for Fernando Torres was found to be incorrect, and later, edited accordingly. As such, some of the screenshots are also incorrect.

You have a bar chart. Col-gratulations!
T___T

No comments:

Post a Comment