When you want to display data, there are quite a number of options available to you - bar graphs, pie graphs and line graphs, among others.
Bar graphs are meant for comparing numerical data between different groups, or progress over time, though it's fairly limited in the case of the latter. Today, we'll be creating an animated bar graph in HTML, CSS and JavaScript.
For data, we will be using statistics collected from
Wikipedia, pertaining to the football club I support - Liverpool FC. We'll compare appearances and goals from six players across four seasons.
Let's get started!
Here's the HTML. We'll create placeholders for CSS and JavaScript.
<!DOCTYPE html>
<html>
<head>
<title>Bar Graph</title>
<style>
</style>
<script>
</script>
</head>
<body>
</body>
</html>
Here, we have a temporary styling for div elements, giving them a nice
orange outline. And we specify that the
graph_container div is 500px in height. In the HTML, we have the
graph_container div and two drop down lists with ids of
ddlRow and
ddlStat respectively.
<!DOCTYPE html>
<html>
<head>
<title>Bar Graph</title>
<style>
div {outline:1px solid #FFAA00;}
#graph_container
{
height:500px;
}
</style>
<script>
</script>
</head>
<body>
<div id="graph_container">
</div>
<select id="ddlRow">
</select>
<select id="ddlStat">
</select>
</body>
</html>
See the result!
Create two more divs within the
graph_container div, and give them ids of
scale_container and
col_container respectively.
<div id="graph_container">
<div id="scale_container">
</div>
<div id="col_container">
</div>
</div>
Style
col_container and
scale_container like so.
scale_container is the placeholder for the scale of the bar graph while
col_container holds the data.
scale_container has only a 20px width and its height is 80% of
graph_container. Its border at the bottom is a fine
black line.
col_container is just as tall as the
graph_container div, and the width is set to 95% of
graph_container's. Both of these divs have the
float property set to
left, so they align nicely left.
<style>
div {outline:1px solid #FFAA00;}
#graph_container
{
height:500px;
}
#col_container
{
height:100%;
width:95%;
float:left;
}
#scale_container
{
height:80%;
width:20px;
float:left;
border-bottom:1px solid #000000;
}
</style>
Take a look at it now. You have a nice thin column for the scale (with a
black border on the bottom) and a larger space for your data.
All good? We'll be populating these placeholder divs soon. First, change your HTML.
<body onload="populate();">
Then add the
populate() function.
<script>
function populate()
{
}
</script>
The data
Before working on the
populate() function, let's create the data. Here we define an object,
graphdata, with three properties -
cols,
rows and
stats. Each of these is an array.
<script>
var graphdata =
{
"cols":[],
"rows":[],
"stats":[]
};
function populate()
{
}
</script>
Let's begin defining values for
rows. These years represent the seasons which the datasets are for. Each season begins in the middle of the year, and ends in the middle of next year. But it's enough to just take the starting year.
var graphdata =
{
"cols":[],
"rows":[2007,2008,2009,2010],
"stats":[]
};
Now, we define what stats there are for any dataset. Namely, goals and appearances.
var graphdata =
{
"cols":[],
"rows":[2007,2008,2009,2010],
"stats":["goals","appearances"]
};
The next one is going to be huge. Each element in the
cols array is an object. Each object has a property,
title, and an array,
stats. Here, the
title for the first element is the name of one of Liverpool FC's most prolific strikers, ever.
var graphdata =
{
"cols":
[
{
"title":"Fernando Torres",
"stats":[]
},
],
"rows":[2007,2008,2009,2010],
"stats":["goals","appearances"]
};
Now, add the stats. Each element in the stats array is, in turn, another object with the properties
year,
goals and
appearances. Boy, this guy really
did score a lot of goals, didn't he?!
var graphdata =
{
"cols":
[
{
"title":"Fernando Torres",
"stats":
[
{"year":2007,"goals":24,"appearances":33},
{"year":2008,"goals":14,"appearances":24},
{"year":2009,"goals":18,"appearances":22},
{"year":2010,"goals":9,"appearances":23},
]
},
],
"rows":[2007,2008,2009,2010],
"stats":["goals","appearances"]
};
Add the other players in the
cols array.
var graphdata =
{
"cols":
[
{
"title":"Fernando Torres",
"stats":
[
{"year":2007,"goals":24,"appearances":33},
{"year":2008,"goals":14,"appearances":24},
{"year":2009,"goals":18,"appearances":22},
{"year":2010,"goals":9,"appearances":23},
]
},
{
"title":"Steven Gerrard",
"stats":
[
{"year":2007,"goals":11,"appearances":36},
{"year":2008,"goals":16,"appearances":34},
{"year":2009,"goals":9,"appearances":31},
{"year":2010,"goals":4,"appearances":33},
]
},
{
"title":"Dirk Kuyt",
"stats":
[
{"year":2007,"goals":3,"appearances":34},
{"year":2008,"goals":12,"appearances":32},
{"year":2009,"goals":9,"appearances":38},
{"year":2010,"goals":13,"appearances":37},
]
},
{
"title":"Ryan Babel",
"stats":
[
{"year":2007,"goals":4,"appearances":30},
{"year":2008,"goals":3,"appearances":27},
{"year":2009,"goals":4,"appearances":25},
{"year":2010,"goals":1,"appearances":1},
]
},
{
"title":"Yossi Benayoun",
"stats":
[
{"year":2007,"goals":4,"appearances":30},
{"year":2008,"goals":8,"appearances":32},
{"year":2009,"goals":6,"appearances":30},
]
},
{
"title":"David N'gog",
"stats":
[
{"year":2008,"goals":2,"appearances":14},
{"year":2009,"goals":5,"appearances":24},
{"year":2010,"goals":2,"appearances":25},
]
},
],
"rows":[2007,2008,2009,2010],
"stats":["goals","appearances"]
};
Let's hammer this into shape!
Add this to the
populate() function. There are some undefined variables, and there's the variable
graph, which we will peg to the div
col_container.
function populate()
{
var col, container, label, fill, p;
var graph = document.getElementById("col_container");
}
Now do this. Create a
For loop that iterates across the
cols array of the
graphdata object.
Then, within the loop, create a div element and use the variable
col to store that object. Then set the class of that object to
col_section. And append that object to
graph.
function populate()
{
var col, container, label, fill, p;
var graph = document.getElementById("col_container");
for (var i=0;i<graphdata.cols.length;i++)
{
col = document.createElement("div");
col.className = "col_section";
graph.appendChild(col);
}
}
Modify your CSS. Add the
col_section CSS class. The width will be 100px but the height will be 100% of its parent. Don't mix the two up! And of course, the
float property of this is
left, to properly align everything.
<style>
div {outline:1px solid #FFAA00;}
#graph_container
{
height:500px;
}
#col_container
{
height:100%;
width:95%;
float:left;
}
.col_section
{
width:100px;
height:100%;
float:left;
}
#scale_container
{
height:80%;
width:20px;
float:left;
border-bottom:1px solid #000000;
}
</style>
Run your code. You'll see 6 columns have been added inside the
col_container div! One for each footballer we added in the
graphdata object!
We're going to do more now. We'll create a div element, set it to the variable
container and set the class to
data_container, then append
container within
col before appending
col within
graph.
function populate()
{
var col, container, label, fill, p;
var graph = document.getElementById("col_container");
for (var i=0;i<graphdata.cols.length;i++)
{
col = document.createElement("div");
col.className = "col_section";
container = document.createElement("div");
container.className = "data_container";
col.appendChild(container);
graph.appendChild(col);
}
}
And then we'll add more styling, this time the data_container CSS class. The
margin properties ensure that it will be centered within its parent, and only occupy 80% height and width. The
background property is set to
transparent explicitly, just in case.
<style>
div {outline:1px solid #FFAA00;}
#graph_container
{
height:500px;
}
#col_container
{
height:100%;
width:95%;
float:left;
}
.col_section
{
width:100px;
height:100%;
float:left;
}
.data_container
{
margin: 0 auto 0 auto;
width:80%;
height:80%;
background-color:transparent;
}
#scale_container
{
height:80%;
width:20px;
float:left;
border-bottom:1px solid #000000;
}
</style>
Now you see that within the columns is a smaller column!
Now we're going to do the same with the
fill variable, setting the class to
data_fill. But before appending it within
container, we will create a paragraph element and set it to the variable
p. Then we'll append
p within
fill before appending
fill within
container. We'll give each of these elements an id as well.
function populate()
{
var col, container, label, fill, p;
var graph = document.getElementById("col_container");
for (var i=0;i<graphdata.cols.length;i++)
{
col = document.createElement("div");
col.className = "col_section";
container = document.createElement("div");
container.className = "data_container";
fill = document.createElement("div");
fill.className = "data_fill";
fill.id = "fill_" + i;
p = document.createElement("p");
p.className = "value_label";
p.id = "p_" + i;
fill.appendChild(p);
container.appendChild(fill);
col.appendChild(container);
graph.appendChild(col);
}
}
Now we add the CSS styles
data_fill and
value_label.
data_fill is meant for styling the visual representation of the bars. It will fill 100% of the height and width of its parent. I've set the background color to
red.
value_label is meant for styling the numerical data. I've set the color to
white. The rest of it is cosmetic styling of the font, which you can alter without affecting functionality (much).
<style>
div {outline:1px solid #FFAA00;}
#graph_container
{
height:500px;
}
#col_container
{
height:100%;
width:95%;
float:left;
}
.col_section
{
width:100px;
height:100%;
float:left;
}
.data_container
{
margin: 0 auto 0 auto;
width:80%;
height:80%;
background-color:transparent;
overflow:hidden;
}
.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%;
}
.value_label
{
color:#FFFFFF;
font-weight:bold;
font-family:verdana;
font-size:0.5em;
}
#scale_container
{
height:80%;
width:20px;
float:left;
border-bottom:1px solid #000000;
}
</style>
You can see that the data columns are filled with
red. Just as planned. You won't see the labels because we haven't given them any data. Yet.
Now add this! Create a div element, set it to the
label variable with a class of
data_label, and the
innerHTML property to the
title property of the
graphdata array element you are currently accessing within the
For loop. But instead of appending it within
container, append it within
col after
container.
function populate()
{
var col, container, label, fill, p;
var graph = document.getElementById("col_container");
for (var i=0;i<graphdata.cols.length;i++)
{
col = document.createElement("div");
col.className = "col_section";
container = document.createElement("div");
container.className = "data_container";
fill = document.createElement("div");
fill.className = "data_fill";
fill.id = "fill_" + i;
p = document.createElement("p");
p.className = "value_label";
p.id = "p_" + i;
fill.appendChild(p);
container.appendChild(fill);
label = document.createElement("div");
label.className = "data_label";
label.innerHTML = graphdata.cols[i].title;
col.appendChild(container);
col.appendChild(label);
graph.appendChild(col);
}
}
OK, now add the
data_label CSS style. width is set to 100% and height to 20%. The
margin property ensures that the div stays centered. The rest is font styling, so do as you please.
<style>
div {outline:1px solid #FFAA00;}
#graph_container
{
height:500px;
}
#col_container
{
height:100%;
width:95%;
float:left;
}
.col_section
{
width:100px;
height:100%;
float:left;
}
.data_container
{
margin: 0 auto 0 auto;
width:80%;
height:80%;
background-color:transparent;
overflow:hidden;
}
.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;
}
.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%;
}
.value_label
{
color:#FFFFFF;
font-weight:bold;
font-family:verdana;
font-size:0.5em;
}
#scale_container
{
height:80%;
width:20px;
float:left;
border-bottom:1px solid #000000;
}
</style>
And there we have the names of the labels on the bottom! It's not nicely aligned yet, but we can take care of that later.
Now outside of the
For loop, we have new code. Remember the drop-down lists? We're going to populate them with data. First, define two variables,
ddl and
option.
function populate()
{
var col, container, label, fill, p;
var graph = document.getElementById("col_container");
for (var i=0;i<graphdata.cols.length;i++)
{
col = document.createElement("div");
col.className = "col_section";
container = document.createElement("div");
container.className = "data_container";
fill = document.createElement("div");
fill.className = "data_fill";
fill.id = "fill_" + i;
p = document.createElement("p");
p.className = "value_label";
p.id = "p_" + i;
fill.appendChild(p);
container.appendChild(fill);
label = document.createElement("div");
label.className = "data_label";
label.innerHTML = graphdata.cols[i].title;
col.appendChild(container);
col.appendChild(label);
graph.appendChild(col);
}
var ddl,option;
}
Now set the variable
ddl to refer to the object
ddlRow. And for each element in the
rows array of the
graphdata object, create an option and populate it, then add it to the drown-down list
ddlRow. The text is slightly different from the value. That's because in football parlance, as previously mentioned, each season starts in the middle of the year and ends in the middle of next year. So a season that started in 2008 would be more appropriately known as "Season 2008/2009".
var ddl,option;
ddl = document.getElementById("ddlRow");
for (var i=0;i<graphdata.rows.length;i++)
{
option = document.createElement("option");
option.text = graphdata.rows[i] + " - " + (parseInt(graphdata.rows[i])+1);
option.value = graphdata.rows[i];
ddl.add(option);
}
Take a gander!
Now do the same for
ddlStat...
var ddl,option;
ddl = document.getElementById("ddlRow");
for (var i=0;i<graphdata.rows.length;i++)
{
option = document.createElement("option");
option.text = graphdata.rows[i] + " - " + (parseInt(graphdata.rows[i])+1);
option.value = graphdata.rows[i];
ddl.add(option);
}
ddl = document.getElementById("ddlStat");
for (var i=0;i<graphdata.stats.length;i++)
{
option = document.createElement("option");
option.text = graphdata.stats[i];
option.value = graphdata.stats[i];
ddl.add(option);
}
And both your drop-down lists are populated!
That won't do anything for us right now of course. We'll need a data scale. Let's start by defining a new function,
displayData(), and calling it at the end of
populate().
function populate()
{
var col, container, label, fill, p;
var graph = document.getElementById("col_container");
for (var i=0;i<graphdata.cols.length;i++)
{
col = document.createElement("div");
col.className = "col_section";
container = document.createElement("div");
container.className = "data_container";
fill = document.createElement("div");
fill.className = "data_fill";
fill.id = "fill_" + i;
p = document.createElement("p");
p.className = "value_label";
p.id = "p_" + i;
fill.appendChild(p);
container.appendChild(fill);
label = document.createElement("div");
label.className = "data_label";
label.innerHTML = graphdata.cols[i].title;
col.appendChild(container);
col.appendChild(label);
graph.appendChild(col);
}
var ddl,option;
ddl = document.getElementById("ddlRow");
for (var i=0;i<graphdata.rows.length;i++)
{
option = document.createElement("option");
option.text = graphdata.rows[i] + " - " + (parseInt(graphdata.rows[i])+1);
option.value = graphdata.rows[i];
ddl.add(option);
}
ddl = document.getElementById("ddlStat");
for (var i=0;i<graphdata.stats.length;i++)
{
option = document.createElement("option");
option.text = graphdata.stats[i];
option.value = graphdata.stats[i];
ddl.add(option);
}
displayData();
}
function displayData()
{
}
Start by grabbing the selected values from the
ddlRow and
ddlStat drop-down lists. Then run the values through the
getMaxStatistic() function and use the result in the
displayScale() function. We'll, of course, have to create those functions.
function displayData()
{
var row = document.getElementById("ddlRow").value;
var stat = document.getElementById("ddlStat").value;
var max = getMaxStatistic(row,stat);
displayScale(max);
}
function getMaxStatistic(row,stat)
{
}
function displayScale(maxval)
{
}
We'll work on the
getMaxStatistic() function next. First, define a variable
max with a value of 1. Then declare the variable
temp.
function getMaxStatistic(row,stat)
{
var max=1;
var temp;
}
Next, iterate through the
cols array of the
graphdata object using a
For loop. Within it, process the
stats array of the current element of the
cols array using the
filter() method to match the
year property with that of the
row parameter of the
getMaxStatistic() function. This means that if "2008" is selected, all elements with the year for "2008" will be returned in an array. That array is set to the variable
temp.
function getMaxStatistic(row,stat)
{
var max=1;
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 is an empty array, that means no data for the year was found, which is a possibility. If not, there should be
only one row. Grab the first (and only) element in the
temp array with the statistic defined by the
stat parameter of the
getMaxStatistic() function. If it's more than
max, set
max to that value.
function getMaxStatistic(row,stat)
{
var max=1;
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)
{
if (temp[0][stat]>max)
{
max = temp[0][stat];
}
}
}
}
Finally, we conduct a
Modulus test. If
max is divisible by 10, return
max with 10 added. If not, deduct the remainder so that
max is divisible by 10,
then return
max with 10 added.
function getMaxStatistic(row,stat)
{
var max=1;
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)
{
if (temp[0][stat]>max)
{
max = temp[0][stat];
}
}
}
if (max % 10 == 0)
{
return max+10;
}
else
{
return max - (max % 10) + 10;
}
}
Now for the
displayScale() function! We passed the maximum statistic of the dataset through this function, remember? Here, we grab the
graph_scale element and set it to the variable
scale. We set the
innerHTML property to a blank value to clear the contents of
scale.
Next, we define two new variables,
units and
data_unit.
units is the value passed into this function (which is the maximum value of the dataset) divided by 10.
function displayScale(maxval)
{
var scale = document.getElementById("scale_container");
scale.innerHTML="";
var units = maxval/10;
var data_unit;
}
Now we're going to work backwards, because on a scale, the top number is displayed first. So our
For loop starts from
units and works its way down to 1, decrementally.
function displayScale(maxval)
{
var scale = document.getElementById("scale_container");
scale.innerHTML="";
var units = maxval/10;
var data_unit;
for (var i=units;i>=1;i--)
{
}
}
Here, we set
data_unit to a created div element, then set its class to
scale_unit. Its height is defined by a percentage of its parent. We determine the percentage by dividing 100 by units. Finally, the div is labelled with the value of (
i x 10), which means the scale is always in intervals of 10. Then we append the created div within
scale.
function displayScale(maxval)
{
var scale = document.getElementById("scale_container");
scale.innerHTML="";
var units = maxval/10;
var data_unit;
for (var i=units;i>=1;i--)
{
data_unit = document.createElement("div");
data_unit.className = "scale_unit";
data_unit.style.height = (100/units) + "%";
data_unit.innerHTML = i * 10;
scale.appendChild(data_unit);
}
}
Here's the styling for the CSS class
scale_unit. The width is 100% of its parent, and the border for the top and left edges are set to a thin
black line. The rest is all cosmetic.
#scale_container
{
height:80%;
width:20px;
float:left;
border-bottom:1px solid #000000;
}
.scale_unit
{
width:100%;
border-top:1px solid #000000;
border-left:1px solid #000000;
color:#FF0000;
text-align:right;
font-weight:bold;
font-family:verdana;
font-size:0.5em;
}
Aaaaand you have a scale!
This is a bit messy, so just change the code here.
div {outline:0px solid #FFAA00;}
Nice. Still a bit messy, but it works. We have a placeholder for all the data we're about to manipulate!
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.
Next
Altering the display to fit the data.