 Now that we have a rectangle that takes our given variables, what we need is...
Some more variables! Wouldn't it be nice if we could feed these variables to the image from the html document? Let's say we have all the data and labels we need in there, we can use the querystring to get it to the image. Below, I've put together a small html file that calls the image with a querystring. I had to cut the querystring here, which is not possible in real life or company life. We need a couple of things when building the image: the width and height, the data we want to present (net result in 1,000 euros in this example), the labels that go along with it, and for the enjoyment of management, they can click on each bar to see details of that year. The last three have to be arrays, so I've just delimited the values with a semi-colon.
<html>
<head>
<title>SVG and PHP: because we can!</title>
</head>
<body>
<embed src="bar_horizontal.php?width=410&height=300&data=110;-255;311.5;-296;-223;365&
labels=1995;1996;1997;1998;1999;2000&
links=1995.php;1996.php;1997.php;1998.php;1999.php;2000.php"
type="image/svg+xml" pluginspage="http://www.adobe.com/svg/viewer/install/" width="500" height="300" />
</body>
</html>
And, in the image file, we then get the variables from the querystring. Just to speed things up with this whole tutorial business (if you can even call it that) I've added some more variables we'll use. The first five variables simply take the data from the querystring, so that's nothing special. We know what margin and textspace do, so there's no surprise either. Minvalue and maxvalue will be taken from the data array just a little later. So, we get to textheight, which for some reason is actually half of the textheight. Later on, I'll set the font-size to 10 pts, and for those of us who are still lost: 10 / 2 = 5. The barheight is a bit more complicated. We have defined the space for our graph as the image width minus twice the margin, right? So how do we know how high the bars will be? We don't yet. But what we can define for now, is the percentage of the total height to be used for the bars: 60% in this case.
$width = $_GET["width"] ? $_GET["width"] : 500;
$height = $_GET["height"] ? $_GET["height"] : 300;
$data = $_GET["data"] ? split(";", $_GET["data"]) : Array(0);
$labels = $_GET["labels"] ? split(";", $_GET["labels"]) : Array("");
$links = $_GET["links"] ? split(";", $_GET["links"]) : Array("");
$margin = 10;
$textspace = 80;
$textheight = 5; // Is HALF the text height!
$barheight = 60; // Percentage of total room per bar
$maxvalue = 0;
$minvalue = 0;
The nasty part
Now let's get our hands dirty on some mathematics! We all knew it had to come to this, and the sooner we get it over with, the better. The foreach statement is easy enough. We find the minimum and maximum value in the data array. We have to have some whitespace between the edge of the graph and the bars for aesthetic reasons, so we increase both the minimum and maximum a little. So how much width is 1,000 euros? It's our space of the graph divided by the maximum range of values we can get (maxvalue minus minvalue since the latter is a negative).
Another variable is defined here (how sloppy of me): gridpos. Unlike you'd expect this is the horizontal position of 0 in our graph. If there are no negatives, this is the left edge of the graph, but if there are, it's the left edge plus the room needed for negative values (stepwidth times the minimum value). The textspacer is the space we want between labels, which is of course simply the graph height divided by the number of labels. But what's this business with the barheight again? Well, we take the height of our graph space, and divide it by the number of bars. That gives us the space we have for each bar and its whitespace. And remember the percentage was set at 60? That's right: 60% of that space we have, is used for the bar.
foreach ($data as $element) {
if ($element < $minvalue) { $minvalue = $element; }
if ($element > $maxvalue) { $maxvalue = $element; }
}
// Allow some room for whitespace
$minvalue *= 1.1;
$maxvalue *= 1.1;
$stepwidth = ($width - (2 * $margin) - $textspace) / ($maxvalue - $minvalue);
$gridpos = $margin + $textspace;
if ($minvalue < 0) { $gridpos -= $minvalue * $stepwidth; }
$textspacer = ($height - (2 * $margin)) / (count($labels));
$barheight = (($height - (2 * $margin)) / count($data)) * $barheight / 100;
How about some SVG then?
Now, we need another namespace defined for the links we'll use in the image, because management needs to be happy and we don't want them complaining. Everybody works best when management is happy, right? Below you see that I've changed the root element a bit. The rectangle is still here, unchanged. I've also inserted a line that represents the 0 line of our graph. The line is pretty basic, we have x1 and y1 which define the start of the line and x2 and y2 define the end. I've given it the same color as our rectangle, which might not be beautiful, but it's functional. That's just the way I like it!
Above that I've included two things by the name of linearGradients inside a tag called 'defs'. The 'defs' tag is used to define things we can use later on, and I'll be using two gradients to make the bars look pretty. I'll use a blue-to-green one for the positive ones, and a red-to-blue one for the negative numbers since negative is bad when it comes to results. So how do these things work? I'll use the positive bar as an example. The viewer needs to know where the gradient starts and ends, and in this case, I let it start on the 0 line (x1 is on gridpos) and end at the end of my graph area (x2 is on the end of the image minus the margin). Because they'll be horizontal gradients, y1 and y2 are simply 0.
To make sure the viewer understands that I'm talking about pixels here, I tell it that the units are userSpaceOnUse. In other words, use the units I've been using all along, because for some reason the viewer doesn't take that as a default. We can reference to the gradient by its id, 'posbar'. Within the linearGradient, I've defined two colors, the first one at 0 (the beginning of the gradient) is blue, and the second one is green at the end (or 1). You can define things in between there if you like, for instance a light blue at 0.5, but personally I think it's ugly and not worth the effort so I haven't.
<svg width="<? echo $width; ?>" height="<? echo $height; ?>" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="posbar" x1="<? echo $gridpos; ?>" y1="0"
x2="<? echo $width - $margin; ?>" y2="0" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:rgb(160,160,224);"/>
<stop offset="1" style="stop-color:rgb(128,224,160); "/>
</linearGradient>
<linearGradient id="negbar" x1="<? echo $margin + $textspace; ?>" y1="0"
x2="<? echo $gridpos; ?>" y2="0" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:rgb(255,128,128); "/>
<stop offset="1" style="stop-color:rgb(128,160,224); "/>
</linearGradient>
</defs>
<rect x="<? echo $margin + $textspace; ?>" y="<? echo $margin; ?>"
width="<? echo $width - (2 * $margin) - $textspace; ?>" height="<? echo $height - (2 * $margin); ?>"
style="fill: rgb(255,255,255); stroke: rgb(0,0,0); stroke-width: 1; " />
<line x1="<? echo $gridpos; ?>" y1="<? echo $margin; ?>" x2="<? echo $gridpos; ?>"
y2="<? echo $height - $margin; ?>" style="stroke: rgb(0,0,0); stroke-width: 1; "/>
...
</svg>
Pretty labels!
I want to define some default stuff, like the font-size. This is simply done with CSS, no big issue here. I've inserted this in the place where you saw the dots above. The 'g' tag is a group, and all elements in between will inherit its properties unless explicitly told not to. I've set the text-anchor to end, which means that when I insert text, it'll use the right-most edge of the text for its positioning instead of the default left-most edge (similar to aligning something right).
<g style="text-anchor: end; font-size: 10pt; stroke: rgb(0,0,0); stroke-width: 0.1; ">
...
</g>
Now we're ready to insert our labels, and we do so between the tags above to get that style we wish to use. The horizontal position, attribute x, is simply our textspace (the label space). I know, I'll drink coffee before coding from now on. That variable name is terrible. Anyway, the vertical position, which has the attribute name y, is trickier. Unlike the rectangle, the text uses the low corners by default. But that's no problem, we can compensate. We want it to be in the center of the space we have for each bar, so our text height variable comes in handy because it's half the text height. If we would place it at the margin plus half the text height, it'll be exactly centered (vertically) on the top line of our rectangle, so that's a start. Now if we add half the space between labels, we've got the first label properly positioned. So all we need to do for each label is add the space between labels again. A little mathematics and we come up with the formula used below.
<?
foreach ($labels as $key => $element) {
?>
<text x="<? echo $textspace; ?>" y="<? echo $margin + $textheight + ($textspacer * ($key + 0.5)); ?>"><? echo $element; ?></text>
<?
}
?>
The stuff that really matters
Finally, let's loop through our data array. Here's what happens after the foreach statement. First, there is an opening 'a' tag, and you've guessed it: it is the link. That's easy enough, and it works much easier than any image map you can think of. Then we need to determine if it's a positive or negative amount. If it is negative, insert a rectangle that is between the negative point (the value multiplied by our stepwidth) and line 0 (gridpos). If not, insert a rectangle from line 0 to the positive point (the value multiplied by our stepwidth). Like with our labels, we use the textspacer to define where the center of the bar is by multiplying the textspacer by bar number plus one half, and we use half bar width to go back enough to properly center it like we used half the text height. The only special thing about the rectangles is that we use our gradient here with the line style="fill: url(#idname)".
Also centered in the space for the bar is the data value in text. It is aligned exactly 10 pixels from the end of the bar line for both positive and negative. We finish up with closing our link tag, and that's it! We have our graph, based on company data.
<? foreach ($data as $key => $element) { ?> <a xlink:href="<? echo $links[$key]; ?>" target="_blank"> <? if ($element < 0) { ?> <rect x="<? echo $gridpos + ($element * $stepwidth); ?>" y="<? echo $margin + ($textspacer * ($key + 0.5)) - ($barheight / 2); ?>" width="<? echo -1 * ($element * $stepwidth); ?>" height="<? echo $barheight; ?>" style="fill: url(#negbar); stroke: rgb(0,0,0); stroke-width: 1; " /> <text x="<? echo $gridpos + ($element * $stepwidth) + 10; ?>" y="<? echo $margin + $textheight + ($textspacer * ($key + 0.5)); ?>" style="text-anchor: start; "><? echo $element; ?></text> <? } else { ?> <rect x="<? echo $gridpos; ?>" y="<? echo $margin + ($textspacer * ($key + 0.5)) - ($barheight / 2); ?>" width="<? echo $element * $stepwidth; ?>" height="<? echo $barheight; ?>" style="fill: url(#posbar); stroke: rgb(0,0,0); stroke-width: 1; " /> <text x="<? echo $gridpos + ($element * $stepwidth) - 10; ?>" y="<? echo $margin + $textheight + ($textspacer * ($key + 0.5)); ?>" style="text-anchor: end; "><? echo $element; ?></text> <? } ?> </a> <? } ?>
Instead of this simple bar chart, it's easy enough to create a line graph or even a combined line/bar graph. Use your imagination and the time given by your boss to create this cool looking stuff he can claim his own doing. Actually, I think we've done such a good job (did you see the smile on the IT-manager's face?) that we can take the rest of the day off and go get a drink. Ad fundum!
Author : NeoTeq, Read 3888 times, Comments: 0| Rating : |           | | Monday, 9. January 2006 |
Add new comment/Comments
Your rating : Poor     Excellent |
  Do you have something to say? Then say it! NetBulge.com is devoted to web development. We are always looking for fresh and relevant ideas. If you are a web designer with a voice, this is the place to make it heard.
Join us!
  Featured * Programmer’s editor. Although at first look it seems similar to all the other free code editors out there, PsPad is a well oiled machine with many strong features that makes it, in my opinion, the best of them all (and I’ve tried quite a few.) At the very least, a good tool to have around. Often people ask the same question: What is the best PHP book? - The answer is so simple they still go ahead and buy some book. Besides being one of the most thorough manuals ever written, the online PHP manual includes user comments adding greatly to its richness. This is the first and most important resource a PHP coder should have. http://www.php.net/manual/en/ |