MorkaLork Development

Interesting stuff I've picked up over the years...

PHP/Charts

2009-06-05 18:08:48 | 207 views | php diagram chart gd gd-library functions captchas geometrical statistics image picture requirements

Introduction:


This was a project I just thought of doing when I was throwing away my precious time exploring all the different image funtions that GD-library provides PHP with. With these functions you can do a lot of cool stuff, like captchas, geometrical programs, and so on, or you can do what I did: a program that shows statistics of data information as a staple diagram.


Prerequisites:


You should have a basic knowledge of the different image functions in PHP that are used in this tutorial:

imagecreate(), imagecolorallocate(), imagecolorallocatealpha(), imageline(), imagerectangle(), imagefilledrectangle(), imagettfbbox(), imagettftext(), header(), imagepng(), imagedestroy()

If you don't, you should at least make an effort checking them up at php.net.


Requirements:


PHP 4+ with GD Library


Getting the business started:


Let's start out by creating a class, with a constructor, for our program:


class DiagramImage
{
function __construct()
{
}
}

(Note that it's two underscores not one, for the contructor)


Actually these are the most vital parts of our program. Well I know they don't contain anything for the moment, so right now they really suck, but that's going to change pretty soon.

To start with, we should decide which parameters our contructor should take. I decided that there should be two arrays one with the alternatives in the shape of text and one array with their values, we also need values for the image's height and width, that we will create.

The code for this, could look similar to this (where we also create a new instance of the class object):


class DiagramImage
{
function __construct(array $altText, array $altValue, $width, $height)
{
}
}

new DiagramImage($altText, $altValue, $width, $height);


At the moment we don't send anything to the class constructor. The variables we send do not really exist yet and for the moment we won't even botter.

We continue to write code that will create the image foundation. We start out with creating the image itself and mixing some colors for later use:


class DiagramImage
{
function __construct(array $altText, array $altValue, $width, $height)
{
$image = imagecreate($width, $height);
$backcolor = imagecolorallocate($image, 102, 102, 102);
$basecolor = imagecolorallocate($image, 0, 0, 0);
$linecolor = imagecolorallocatealpha($image, 102, 102, 102, 20);
}
}

new DiagramImage($altText, $altValue, $width, $height);


What happens here is that we get a new picture, at start it's all black and it's adjusted to the sizes we sent to our contructor when we instantiated the class object. When you use the imagecreate()-function to create a new image the image gets the first mixed color in the program as it's background color. Therefore we now have a gray image in the adjusted sizes.

Now we advance a bit by drawing some lines:


function __construct(array $altText, array $altValue, $width, $height)
{
$image = imagecreate($width, $height);
$backcolor = imagecolorallocate($image, 102, 102, 102);
$basecolor = imagecolorallocate($image, 0, 0, 0);
$linecolor = imagecolorallocatealpha($image, 102, 102, 102, 20);

$dl = 5; $dr = $width - 15; // left & right inner borders
$dt = 5; $db = $height - 10; // top & bottom inner borders

imageline($image, $dl, $dt, $dl, $db, $basecolor);
imageline($image, $dl, $db, $dr, $db, $basecolor);
imagerectangle($image, 0, 0, $width - 1, $height-1, $basecolor);
}


First of all we get the positions of the inner borders that we have for our image (dl = left, dr = right, dt = top, db = bottom). Then we write out a line top down a the left part of the image and one from left to right at the bottom part of the image. Here we also draw a black border around the image.

Our work should now look similar to this:

imagehttp://www.morkalork.com/admin/uploads/WimpySE/images/BILD1.png

Well I can agree with you if you don't think of this as a piece of art but hey, we have a lot of work left and a heap of loops to code.

From here we continue with setting a few values:


function __construct(array $altText, array $altValue, $width, $height)
{
...

imageline($image, $dl, $dt, $dl, $db, $basecolor);
imageline($image, $dl, $db, $dr, $db, $basecolor);
imagerectangle($image, 0, 0, $width - 1, $height-1, $basecolor);

$stplCount = count($altText);
$btm = 35;
$fntsz = 10;
}


Here we counted the number of staples to create, we set the value of how big part of the bottom of the image that should be reserved for informative stuff(and to make it look "awesome"). We also set a font size. Now we're going to advance a bit by coding a simple loop that will do some complicated work for us:


function __construct(array $altText, array $altValue, $width, $height)
{
...

$stplCount = count($altText);
$btm = 35;
$fntsz = 10;

$val = 1.0;
$tal = 100;
for($i = 0; $i < 4; $i++)
{
imageline($image, $dr * $val, $db, $dr * $val, $db + 3, $basecolor);
imageline($image, $dr * $val, $db - 1, $dr * $val, $dt, $linecolor);

$txtbx = imagettfbbox($fntsz, 0, $this->font, $tal."%");
$txtbxsz = ($txtbx[2] - $txtbx[0])/2;

($image, $fntsz, 0, $dr * $val - $txtbxsz, $db - 3, $basecolor, $this->font, $tal."%");
$tal -= 25;
$val -= 0.25;
}
}


This loop puts out some lines to make it easier for us to see what value a staple might have, it also puts out text with the percent values at the bottom line.

First of all we set start values for the loop, $val and $tal, that is needed to calculate where we should place our lines and our text. We have used imageline() earlier in the program, it draws lines, the first draws small snippets align the bottom line in the image, and the other one draws lines through the image bottom up.

Then we use the function imagettfbbox() to calculate how big the textboxes will be for our text. This function provides us with an array with values for the corners of the textbox, [0] is the top left corner's position side ways and [1] is the position of the top left corner in height, [2]is the top right corners position sideways and so on. With this information we can figure out how to center the text at its positions. ([2] - [0]) / 2.

The last function writes out the text. And thereafter we change the values of $val and $tal before the next iteration.

At this point you might wonder what $this->font is and that's absolutely fine, because I skipped that part until now: Tobe able to write text you must provide the program with a font in the format .ttf (true-type-font). We load the font in the beginning of the class:


class DiagramImage
{
private $font = 'URL';

function __construct()
{
...
}
}

...


Now the image should look something like this:

imagehttp://www.morkalork.com/admin/uploads/WimpySE/images/BILD2.png

At this time we should collect some colors for the staples, I don't know about you but I don't like the idea of having the same color for all the staples so we are going to create a new class that randomizes the colors for us. This means that the colors never will be the same twice.

To make our code as reusable as possible we put the color randomizer in a new class:


class RandomColors
{
function newColors($image, $colorCount)
{
for($i = 0; $i < $colorCount; $i++)
{
$colors[$i] = imagecolorallocate($image, mt_rand(50, 255), mt_rand(50, 255), mt_rand(50, 255));
}
return $colors;
}
}


With this new class we get our staple colors by putting this code into our DiagramImage class constructor:


function __construct(array $altText, array $altValue, $width, $height)
{
...

$val = 1.0;
$tal = 100;
for($i = 0; $i < 4; $i++)
{
...
}

$colors = new RandomColors();
$stapleColors = $colors->newColors($image, $stplCount);
}


Now we have an array with as many colors as is needed for all the staples which we soon will begin drawing, but first we need to do some math:


function __construct(array $altText, array $altValue, $width, $height)
{
...

$colors = new RandomColors();
$stapleColors = $colors->newColors($image, $stplCount);

$total = array_sum($altValue);
$factor = $dr / $total;

for($i = 0; $i < $stplCount; $i++)
{
$stplWidth[$i] = $factor * $altValue[$i];
}
}


What we did here was to summerize all the values in the $altValue array, and with a little help of simple mathmatics we calculate the width of the staples and placed these values in a new array, $stplWidth. Now we begin drawing staples:


function __construct(array $altText, array $altValue, $width, $height)
{
...

for($i = 0; $i < $stplCount; $i++)
{
$stplWidth[$i] = $factor * $altValue[$i];
}

$start = $height - $btm + 10;
$end = $start - 10;
$stplsz = ($height - $btm + 10) / $stplCount;

for($i = 0; $i < $stplCount; $i++)
{
imagefilledrectangle($image, $dl + 5, $start, $stplWidth[$i], $end, $stapleColors[$i]);
imagerectangle($image, $dl + 5, $start, $stplWidth[$i], $end, $basecolor);
$start -= $stplsz;
$end -= $stplsz;
}
}


We begin with calculating the start and end positions for the staples in height. Then we set a staplesize $stplsz which we will use to get new start and end positions. After that we enter the loop where we first draw a filled rectangle with the previously calculated sizes and with one of the randomized colors. Then we draw an empty rectangle as a border around the filled one, only to make it look greater. Last of all we change the start and end values before the next iteraton.

At this moment the image should look similar to this:

imagehttp://www.morkalork.com/admin/uploads/WimpySE/images/BILD3.png

What's missing is now only som informative text and then it's almost done:


function __construct(array $altText, array $altValue, $width, $height)
{
...

for($i = 0; $i < $stplCount; $i++)
{
imagefilledrectangle($image, $dl + 5, $start, $stplWidth[$i], $end, $stapleColors[$i]);
...
}

$pos = $height - $btm - 1;
for($i = 0; $i < $stplCount; $i ++)
{
$text = $altText[$i];
imagettftext($image, $fntsz, 0, $dl + 5, $pos, $basecolor, $this->font, $text);
$pos -= $stplsz;
}
}


First we calculate where the text should be placed ($pos) and then we print it out, actually without calculating sizes of textboxes and stuff. In this case we don't have to, because we know how the text will be placed and where. Last of all we change the value of $pos before the next iteration.

Now the image should look something like this:

imagehttp://www.morkalork.com/admin/uploads/WimpySE/images/BILD4.png

The only thing missing is now the total value of $altValue to make sure the user gets an idea of the value that each alternative/option/whatever hold. Then we also have to send the image to the browser or create a file from it:


function __construct(array $altText, array $altValue, $width, $height)
{
...

for($i = 0; $i < $stplCount; $i ++)
{
$text = $altText[$i];
...
}

imagettftext($image, $fntsz, -90, $dr+5, 5, $basecolor, $this->font, "Total: ".$total);
header('Content-Type: image/png');
imagepng($image);
imagedestroy($image);

}


First thing we do here is to write out "Total: ?value?" top down in the top right corner. After that we tell the browser what it should expect receiving from the program with header(), and then we send our image to the browser in png-format, and last of all we free up the memory of the image.

Now we're all done with the diagram creating stuffiness and the image should look similar to this:

imagehttp://www.morkalork.com/admin/uploads/WimpySE/images/BILD5.png

Here's the whole code for those who don't have the time, or something, to copy/paste it together from the guide:


<?php
class RandomColors
{
function newColors($image, $colorCount)
{
for($i = 0; $i < $colorCount; $i++)
{
$colors[$i] = imagecolorallocate($image, mt_rand(50, 255), mt_rand(50, 255), mt_rand(50, 255));
}
return $colors;
}
}

class DiagramImage
{
private $font = 'URL';

function __construct(array $altText, array $altValue, $width, $height)
{
$image = imagecreate($width, $height);
$backcolor = imagecolorallocate($image, 102, 102, 102);
$basecolor = imagecolorallocate($image, 0, 0, 0);
$linecolor = imagecolorallocatealpha($image, 102, 102, 102, 20);

$dl = 5; $dr = $width - 15;
$dt = 5; $db = $height - 10;

imageline($image, $dl, $dt, $dl, $db, $basecolor);
imageline($image, $dl, $db, $dr, $db, $basecolor);
imagerectangle($image, 0, 0, $width - 1, $height-1, $basecolor);

$stplCount = count($altText);
$btm = 35;
$fntsz = 10;

$val = 1.0;
$tal = 100;
for($i = 0; $i < 4; $i++)
{
imageline($image, $dr * $val, $db, $dr * $val, $db + 3, $basecolor);
imageline($image, $dr * $val, $db - 1, $dr * $val, $dt, $linecolor);

$txtbx = imagettfbbox($fntsz, 0, $this->font, $tal."%");
$txtbxsz = ($txtbx[2] - $txtbx[0])/2;

imagettftext($image, $fntsz, 0, $dr * $val - $txtbxsz, $db - 3, $basecolor, $this->font, $tal."%");
$tal -= 25;
$val -= 0.25;
}

$colors = new RandomColors();
$stapleColors = $colors->newColors($image, $stplCount);

$total = array_sum($altValue);
$factor = $dr / $total;

for($i = 0; $i < $stplCount; $i++)
{
$stplWidth[$i] = $factor * $altValue[$i];
}

$start = $height - $btm + 10;
$end = $start - 10;
$stplsz = ($height - $btm + 10) / $stplCount;

for($i = 0; $i < $stplCount; $i++)
{
imagefilledrectangle($image, $dl + 5, $start, $stplWidth[$i], $end, $stapleColors[$i]);
imagerectangle($image, $dl + 5, $start, $stplWidth[$i], $end, $basecolor);
$start -= $stplsz;
$end -= $stplsz;
}

$pos = $height - $btm - 1;
for($i = 0; $i < $stplCount; $i ++)
{
$text = $altText[$i];
imagettftext($image, $fntsz, 0, $dl + 5, $pos, $basecolor, $this->font, $text);
$pos -= $stplsz;
}

imagettftext($image, $fntsz, -90, $dr+5, 5, $basecolor, $this->font, "Total: ".$total);
header('Content-Type: image/png');
imagepng($image);
imagedestroy($image);
}
}

new DiagramImage($altText, $altValue, $width, $height);
?>


Here's a page where you can see the real thing: Link


Stuff to think of:


if the variables/parameters you send to the class constructor, while instansiation an object from it, does not exist or if $altText or $altValue does not come in the shape of arrays this program will crash.

---

This program writes out the values of $altText in the wrong order! With this I mean that if $altText looks like this ("1", "2", "3"), these values will be printed like this:

3
2
1

The solution to this could be array_reverse() or giving the array the values in the wrong order to start with or just settle with it! :)

---

This is only an example of how you could build a program such as this and therefore I have omitted almost all of the safety controls and bugfixes. The code for the real thing is almost 3 times as many lines so before using this for something, you really should look it over and secure it from bugs and stuff like that! Ask yourself what needs to be checked, what bugs could it throw at you? How, why and when?

---

Use a clear font for the diagram that looks good even in smaller sizes, there's a lot of sites on the web where you can download free type fonts free of charge! Google it! Google it! Google it!

I used a font that's called Acknowledge TT BRK that can be downloaded here.

---

For my image examples I used the following data instantiating the class object:


$altValue = array(mt_rand(50, 200), mt_rand(50, 200), mt_rand(50, 200), mt_rand(50, 200));
$altText = array("Alt 4", "Alt 3", "Alt 2", "Alt 1");
$width = 225;
$height = 150



Tutorial created by,
WimpySE


Article comments

Feel free to comment this article using a facebook profile.

I'm using facebook accounts for identification since even akismet couldn't handle all the spam I receive every day.