Ok. Your site has recorded the data you want, now you want to display it in an easy to read format. The first method that comes to mind is simply to put the data in an HTML table. Although this is probably the easiest way out, there is a much better way of displaying your information: a graph. This was originally only possible by using sneaky tables with <tr>'s of different lengths. But now, thanks to the GD library and PHP, you can actually have a PNG or JPG image generated dynamically from data in a MySQL database. This article will help you start out in dynamic image generation.
The technical requirements for this article are listed below:
PHP Scripting language (obviously): Without this language, this article is absolutely useless. If you don't have it, stop reading and get it now!
MySQL Server: For data storage. Any other SQL (Oracle, MSQL, etc.) would be fine, this is just my personal preference. The queries in this article were written for MySQL, however, and to use another SQL would mean you would have to translate them.
GDLib: An extension to PHP that offers image manipulation capabilities. It can be found at http://www.boutel.com/gd. Installation of this extension is unique to each environment that it is being set up in, and will not be covered.
Any paint program: To draw the base image for the graph (there is not artistic talent required, don't worry).
Once you have all of these set up and running, you are ready to start coding.
Basic Image Dynamics
Before we jump into content driven graphs, lets try some simple image manipulation. Create a new php file and name it image.php. You can check on your progress at anytime by entering the php file as a URL in your browser (ex: http://127.0.0.1/image.php) This file is going to be the image. I can already hear people saying "But its just text! How can it be a picture?" Its all in the first line of code:
header ("Content-type: image/png");
This tells the browser that this file is, in fact, an image (as compared to a "text/html" document). This line has to be present in every image generated by PHP/GD, and it should be as is, although the "image/png" can be changed to "image/jpeg" if you want (I encourage using PNG's, however, as their quality is superior to JPEG's). Now the next line:
$im = imagecreate (300, 300);
imagecreate is a new function that is only available with GDLib. It creates a blank image with the specified dimensions. Now, we need to allocate some colors that were going to use in the image. To do this we use the imagecolorallocate function:
$blue = imagecolorallocate ($im,0,0,255);
$im was used as reference to which image the color will be allocated to (you can work with more than one image at a time). Most image functions require you to specify which image you are operating on. The three integers that follow are the color code in RGB (NOT hexadecimal). Interestingly enough, and this is something I haven't read anywhere, the first color you allocate becomes the background. If no colors are allocated, then the image takes on a black background. So far, our 300x300 image has a blue background. Put another imagecolorallocate function before it allocating white (255,255,255) so that you get a nice subtle white background. Lets take a look at our code so far.
Now you have a blank white image that is 300x300 pixels in size. Lets do something with it. Lets tell the world how much we love GDLib. Enter this under what you already have:
imagestring ($im, 5, 0, 0, "I Love GDLib!!!", $blue);
Now take a look at your image. If all went well, you should have the text "I Love GDLib!!!" written in blue in your previously empty image. Lets examine why this happened. The first argument, $im, refers to which image the text should be written to. The second refers to which of the five built in fonts to use. Each is mono space, with the width of the characters matching the font number (ie. Font 3 will have a character width of 3 pixels). The third and fourth arguments are the x and y coordinates of the upper left hand corner of the string (a string put at 0,0 will show, but a string put at 0, 300 would go completely off the image). The fifth argument is the actual string, and the last is the color. The color must be one of the colors allocated from before. Now for some geometry. Were going to draw a rectangle using the same blue color as the text. The function to draw rectangles is imagerectangle (complex, eh?). Enter this line into your script and we'll analyze:
Take a look at the image, and, if all went well, you'll see the outline of a rectangle drawn in blue. The first argument, $im, refers to which image the rectangle is to be drawn on (like the imageallocate and the imagestring function). The last argument specifies which color it is to be drawn in. Only an allocated color can be used. The 2nd to 5th arguments are the x1,y1, x2 and y2 coordinates of the rectangle. x1,y1 is the upper left hand corner, and x2, y2 is the lower right hand corner. As you can see, most image functions have similar arguments. They all must identify which image they are to be drawn to as their first argument, and most require the color they should be in as their last argument. Imagerectangle and image string are two of the many drawing functions that come with GDLib. Others include imagedrawcircle, imagedrawpolygon, imageline, and many, many others. This article will only scratch the surface of this powerful extension, and the only way to learn how to use all the drawing functions is to open up the PHP manual and experiment. Now try the same coding again, only this time write imagefilledrectangle instead of imagerectangle. If done properly, you should have, as the name implies, a filled rectangle in blue. Now allocate the color black to a variable, and use it in an imagerectangle after the imagefilledrectangle to get a nice outline. To finish it all off, add imagepng($im); to draw the image. The final code is below:
Now that you know the basics of image dynamics, we'll move on to a real world application: dynamic graphs.
To demonstrate some of the power of GDLib, we will use a voting poll in which a bar graph of the results is generated as an example. To see a graph like this in action, check out http://www.tamocomics.com, my web comic (shameless advert), and click on "View Results" on the homepage. The first step of making a voting poll is, obviously, to set up the database. I made two tables in MySQL to handle the voting; one that held the names and HTML values of the different voting options, and another that kept track of the actual votes. There are millions of ways one could set up a voting poll; this is not the only way and definitely not the best. The "HTML values" in the first table are simply shorter versions of the actual names that were used in the HTML forms (ex: "Yes" would be "y"). This was made to take into account vote options with spaces (ex: "I like the site a lot"). To avoid any later trouble, I used the "name" only for display, and all the calculations took place on the "HTML value." In the second table, all that is really necessary is a column for the HTML value of the user's vote. IP addresses and id numbers can be added as well depending on the site's needs. A sample "options" table would be as follows:
| id | name | value |
| 1 | Hat | hat |
| 2 | Tee shirt | shirt |
| 3 | Pants | pants |
| 4 | Mug | mug |
And a "poll" table (where the actual votes are recorded; the second of the above mentioned tables) might look like this:
Note that all the IP addresses in this example were 127.0.0.1, as the results were generated locally off of my server. Had these been real votes, the UP addresses would be different. Now assuming that you have all of this information (recording votes is a whole different story, and wont be covered in this article), its time to finally get to the point of all this: displaying it in a graph. The first step in making any graph is to use a base image. A base image is a ready-made image that is used as a base for your dynamic art. It is a graphic template that goes under the imagereactangle and imagefilledreactangle commands. For a graph, this image is usually a set of axes, and the bars/lines/dots are drawn over it. Any image will do, but you have to keep several things in mind: The distance of the x-axes from the left hand side, the distance of the y-axes from the top side, the height of the x-axes and the length of the y-axes (all in pixels). Most drawing programs have a ruler tool to help get measurements exact. Be careful, however, being off by two of three pixels can ruin the entire effect of the graph. A note before we begin: as said before, PNG's seem to surpass JPEG's in terms of quality, and PNG's will be used in this example. Now on to the coding. The first line is, of course, the header, followed by a line that may look familiar, but is in fact a new function.
Instead of imagecreate, imagecreatefrompng was used. This stores the base image, "graphtemp.png", resource into $im, rather than just white. Here are the dimensions etc. for the picture, incase you decide to make one to follow along: it is 400x220, the y-axis is 25 pixels away from the left hand of the image and the x-axis is 200 pixels away from the top of the image, the y-axis is 184 pixels in height and the y-axis is 360 pixels in length. Keep these numbers in mind, as they will be used later in the calculations. Next, we allocate the colors to be used. Using black for the outline gives a good effect, and the inside color is you choice. I will use red in this example. So
Remember, of course, to change the host, user, password and database to match your servers set up
(so that's why it wouldn't work! :)). Now that we have the connection, we can begin to call data.
The first and most important number, yes even more important than knowing how many votes have been
cast, is the number of vote options you are working with. Without this piece pf data, there would
be no way to ensure that the right number of bars has been drawn. Call this magic variable as such:
Using simple MySQL functions, we now know the number of vote options were working with, and that number is stored in the variable $numoptions. Even though just be looking at the MySQL table we can tell that there are four options, this may change. Having this number loaded dynamically allows the coder to have a truly dynamic graph system. Now, we need to know how many people have voted. This is a number that can't be checked manually, not with any ease for that matter.
Following the method used to get the number of options, we can similarly get the number of votes. Observe:
Both queries select all entries to the databases without discriminating. Using mysql_num_rows will give us the number of entries in the database, which is the number of vote options in the voteoptions database, or number of votes in the poll database. Now we have all the raw data that has to do with the actual votes, lets get back to the actual graph. There are two more variables that need to be set before the drawing begins:
Lets analyze. $xval is the value for the x position of the left-most side of the graph. This value will be manipulated later, but for now, it will be set to 30 (the y-axis's distance from the left hand side of the image, plus 5 pixels for some breathing space). $barwidth determines how wide each bar should be. The 300 used is simply 360 (the length of the x-axis) minus 60 for some more breathing space. This is then divided by the number of vote options. As you may have noticed, a lot of the original values and measurements from the image have been changed to account for "breathing space." This is my term for the space between the bars so that the graph doesn't seem to crowded. Technically, the script would work with out this extra space. But try it, and I assure you will develop a case of claustrophobia.
The actual drawing of the graph, what you started reading this article for. It would be obvious to any programmer that to execute any number of indefinite commands, a loop is required. So if you guessed that we need a loop to draw the bars, pat your self on the back. If not, its okay. My personal favorite loop is the for loop. This script can be rewritten using a while loop, or any other loop that you please, however. Well take this a line or two at a time. The first line is as such:
The setting of $i to 0 and subtracting one from $numoptions may seem pointless, but its necessary. The mysql_result function, that will be used later, reads rows from MySQL tables starting at row 0, not row 1. Subtracting one from $numoptions is done so that the loop doesn't overshoot its target number, and try to draw an extra bar that doesn't exist. This might be a bit confusing, but the next two line will make things a bit clearer:
Since each loop deals with and draws the bar for a different voting option, we need to know which option were dealing with. $voteoption takes on the value of the actual name (the 'name' column of the vote options table), and $votevalue stores the HTML value (the 'value' column of the same table), of the current vote option. The $optionsquery query was the query used earlier to get the total number of vote options. Keep in mind that this loop deals with one vote option at a time, the option that is on row $i. Since $i is incremented after each loop, all vote options are dealt with. We already know how many people have voted overall ($numvotes), we need to know how many people have voted for the current option specifically.
Take a look at these two lines:
$currentnumquery = mysql_query("SELECT * FROM poll WHERE vote='$votevalue'");
$currentnum = mysql_num_rows($currentnumquery);
The query in the first line is almost identical to the one used to find how many total votes there were ("SELECT * FROM poll"). The only exception is that there was a bit of discrimination here: only the votes with their 'HTML value' column matching the HTML value of the current vote option will be selected. This means that only the people who voted for the current vote option will be counted. This way, we get the number of votes for the current vote option, and we store it in the variable $currentnum using mysql_num_rows. Now that we have the total number of votes and the number of votes for our current vote option, we no longer need any outside data. The rest of the calculations will take place within the script. The graph in this example uses percents, not actual values. Were going to need to calculate these percents before the drawing begins. As usual, I put up two lines for analysis:
The two calculations are identical, but with one difference: one is in relation to 184, and the other to 100. $per is used to determine how high the current graph should be. It is multiplied by 184, the height of the y-axis. $rper (real percent) is simply the actual percentage that will be used only for display later on. We have determined, called, calculated and processed all the necessary data. Now its time to actually get results (the graph is still quite empty if you haven't checked).
It may look like a lot, but the coordinates in both functions are the same. Each coordinate is an expression based off of all the calculations we just went through (except the last one). $xval is the current value for the left most side of the bar (essentially, the x coordinate for the upper-left hand corner) that will be incremented by $barwidth+10 later on. 200-$per uses $per, the height of the bar. Since we have the actual whole height of the bar, we are forced to work from down up, i.e. from 200 (the bottom-most line in the actual graph, but necessarily in the actual image) to the top, 0. Hence, we minus $per. The second x value is the same as the first, but with $barwidth added to it. The last value is the easiest: its 200 no mater what happens. 200 is the bottom-most point on the graph, and the last y value (the lowest y value) has to be on it. Mind you, if your lowest point is not 200, don't put and expect it to work :).
Let's take a look at our code so far, as its enough to draw a full graph:
Note the addition of the closing brackets and the incrementing of $xval (as I promised). If all went well, you should get a nice red graph. Well, actually, a nice series of red bars. There is no indication as to which bar means what, and how much each bar is. Although nice, this graph is essentially useless. Not to worry, this can be fixed. A few imagestrings will solve all your problems. The most important thing is to label the bars. Take a look at this bit of code:
I used font one, its small and practical, but it really doesn't matter. The x coordinate is the current $xval plus half the $barwidth. This is an attempt to center the text, and it works pretty well (it's not that good actually, there are many better ways to center text, but for simplicity, I'm not going to include it). The y coordinate, 205, puts it a little lower than the bottom line. The variable $voteoption is used as the text to be written, and the color $black is used as the, well, color. This line goes right after the imagerectangle, and before the $xval increment. To finish it all off, we'll add text on to of the bar saying what percent of the voters chose this option. The code is as follows:
The last analysis, I promise! The font used doesn't matter, but using a big font (4, 5) might hurt the look and even the readability of your graph. The x coordinate here is the same as the x coordinate in the previous imagestring. The y coordinate uses the same calculation that was used to determine the height of the bar (200-$per) with 15 subtracted from it so the text isn't drawn inside the bar. "$rper%" is the variable $rper with the percent symbol added to it (otherwise, its just a number). Well, that's all the code. Here's the final script:
Dynamic graphs are a great way to combine the power of PHP with MySQL and GDLib. It offers data in an easily readable format to your viewers. This article barley scratches the surface of what is possible with dynamic graphing, let alone GDLib. One could make bars of alternating colors, or even color-code them from highest to lowest. Different types of graphs can be made just as easily: line graphs, pie graphs etc. With a little creativity, you can even pull off a pseudo-3d effect using imageline. The possibilities are endless, and the only way to ensure that you are getting the most out of GDLib is to read through all the functions in the PHP manual. I hope you enjoyed and benefited from this article as much as I would have had some one wrote it before me :). Good luck, and happy graphing.