Tworzenie wykresów w PHP przy użyciu GD
W tej chwili niemal w każdym miejscu w internecie możemy znaleźć grafikę. Jest ona wszechobecna. Trudno wyobrazić sobie profesjonalną stronę bez odpowiedniej grafiki. Podobnie rzecz ma się w systemach statystyk. Trudno sobie wyobrazić sondę czy system statystyk nie umożliwiający generowanie wykresów.
PHP został wyposażonyt w możliwość dynamicznego tworzenia obrazów. We wcześniejszych artykułach przedstawione niektóre funkcje blblioteki GD. Dziś użyjemy tej biblioteki, aby rysować wykresy. Będziemy rysowali zarówno wykresy słupkowe jak i kołowe. Użyjemy wersji 2.x, ponieważ w tej wersji dodano ciekawe funkcje obsługi rysowania części łuku.
Użycie biblioteki GD
O podstawach wykorzystania tej biblioteki pisaliśmy w poprzednich artykułach. Zachęcam do zapoznania się z tym materiałem: Generowanie miniatur w PHP, Autoryzacja kodem z obrazka w PHP, Przerzucanie obrazów w poziomie i pionnie w PHP oraz Dynamiczny avatar. Teraz możemy wziąć się do pisania skryptu rysującego wykresy.
Projekt skryptu
Do napisania skryptu użyjemy PHP5. Na początku zajmijmy się klasą Chart. Będą z niej dziedziczyć wszystkie klasy, których zadaniem będzie rysowanie wykresów. Jakie cele postawimy przed tą klasą? Przede wszystkim będzie ona odpowiedzialna za stworzenie obrazu o odpowiednim rozmiarze, przechowywanie danych i odpowiedni sposób wyświetlenia lub zapisania do pliku. Poniżej przedstawiam kod tej klasy z wytłumaczeniem poszczególnych metod w komentarzach:
![]()
/* Definiujemy szerokość czcionki*/
define('FONT_WIDTH', 6.5);
![]()
abstract class Chart{
protected $labels;
protected $values;
protected $canvas;
protected $width;
protected $height;
protected $title;
protected $colors;
![]()
/*Ustawia kolor dla podanego x'a*/
public function setColor($x, $color){
$this->colors[$x] = $color;
}
![]()
/*Ustawia tytuł*/
public function setTitle($str){
$this->title = $str;
}
![]()
/*Ustawia etykiety dla poszczególnych wartości*/
public function setLabel($val, $label){
$this->labels[$val] = $label;
}
![]()
/*Ustawia wartości*/
public function setValue($x, $y){
$this->values[$x] = $y;
}
![]()
/*Tworzy nową instację klasy o rozmiarach w na h*/
public function __construct($w, $h){
$this->prepareCanvas($w, $h, new Color(255,255,255));
}
![]()
![]()
![]()
/*Tworzy nowy obraz o podanych rozmiarach i podanym tle*/
public function prepareCanvas($width, $height, Color $back){
$this->width = $width;
$this->height = $height;
![]()
//Tworzymy obraz
$this->canvas = imagecreatetruecolor($width, $height);
//Allokujemy zadany kolor
$backColor = imagecolorallocatealpha($this->canvas, $back->R, $back->G,
$back->B, $back->A);
![]()
//Wypełniamy tłem cały obraz
imagefill($this->canvas, 0,0, $backColor);
}
![]()
//Abstrakcyjna metoda - w niej będzie odbywało się rysowanie wykresów
//Każda klasa dziedzicząca musi tę metodę deklarować
public abstract function draw();
![]()
//Metoda odpowiedzialna za wyświetlenie obrazka
public function publish(){
header('Content-type: image/png');
imagepng($this->canvas);
imagedestroy($this->canvas);
}
![]()
}
![]()
Jak widać w kodzie zadeklarowaliśmy tę klasę jako abstrakcyjną. Co oznacza, że nie da się utworzyć instancji tej klasy. Trzeba najpierw stworzyć klasę dziedziczącą po tej klasie. Zauważmy też, że używamy w niektórych miejscach klasy Color. Jej kod jest którki i nie powinien przysparzać nawet początkujaćym problemów:
![]()
class Color{
public $R, $G, $B, $A;
![]()
public function __construct($R, $G, $B, $A = 0){
if($R > 255 || $R < 0 || $G > 255 || $G < 0
|| $B > 255 || $B < 0 || $A > 127 || $A < 0){
throw new Exception('Incorrect color: RGB('.$R.', '.$G.', '
.$B.', '.$A.')');
}
$this->R = $R;
$this->G = $G;
$this->B = $G;
$this->A = $A;
}
}
![]()
Tworzymy wykres słupkowy
Zastanówmy się jak będzie działał nasze rysowanie wykresów słupkowych. Można to zrealizować na przynajmniej 2 sposoby. Pierwszy to taki, w którym wszystkie słupki sumują się do 100%. Drugi sposób to taki, że największa wartość to 100% i ten będzie zajmował całą szerokość. Pozostałe będą odpowiednio krótsze. Który sposób wybierzemy zależo od tego co chcemy uwypuklić. Jest to oczywiście kwestia gustu. My zajmiemy się drugim wariantem. Właściwie różnica jest w miejscu pobierania $maxVal zamiast max byłoby sum.
![]()
/* Klasa BarChart będzie reprezentowała wykres słupkowy.
Dziedziczy ona z klasy Chart */
class BarChart extends Chart{
![]()
/*Metoda draw odpowiada za rysowanie po obrazie odpowiednich rzeczy*/
public function draw(){
![]()
/*Pobieramy długość najdłuższej etykiety. Musimy wiedzieć
ile miejsca zostawić na rysowanie etykiet*/
$maxLabel = $this->getLongestLabelSize();
![]()
/*Inicjujemy kolor czarny. Zmienna forecolor zawsze oznacza
kolor pisaka, którym aktualnie rysujemy po płutnie*/
$black = $forecolor = imagecolorallocate($this->canvas, 0, 0, 0);
![]()
/*Rysujemy tytuł wykresu na samej górze po środku obrazu
Bierzemy szerokość pomniejszoną o rozmiar tekstu i dzielimy przez 2*/
imagestring($this->canvas, 3,
($this->width-FONT_WIDTH*strlen($this->title))/2,1,$this->title
,$forecolor);
![]()
$yoffset = 20;
/*Rysujemy linie poziomom oddzielajacą tytuł od reszty obrazka*/
imageline($this->canvas, $maxLabel, $yoffset, $maxLabel, $this->width, $forecolor);
$drawY = 1+$yoffset;
![]()
/*Sprawdzamy czy czasem szerokość obrazu nie jest za mała*/
$max = $this->width - $maxLabel - 5;
if($max < 0)throw new Exception('Obraz musi byc szerszy o: '.(-$max).'px');
![]()
/*Pobieramy w maksymalną wartość*/
$maxVal = max($this->values);
![]()
/*Dla każdej wartości będziemy wypisywać etykietę i rysować prostokącik*/
foreach($this->values as $x=>$y){
![]()
/*Ustawiamy kolor pisaka na czarny*/
$forecolor = $black;
![]()
$label = $this->labels[$x];
![]()
/*Rysujemy tekst*/
imagestring($this->canvas, 2, 1, $drawY, $label, $forecolor);
![]()
/* Ustawiamy kolor pisaka na kolor podany dla danego słupka*/
$forecolor = imagecolorallocatealpha($this->canvas,
$this->colors[$x][0], $this->colors[$x][1], $this->colors[$x][2],
$this->colors[$x][3]);
![]()
/*Rysujemy prostokącik o odpowiednich rozmiarach */
imagefilledrectangle($this->canvas, $maxLabel+1, $drawY,
$maxLabel+1+(int)$y*$max/$maxVal, $drawY+10, $forecolor);
![]()
/*Następna linia 20 px niżej*/
$drawY += 20;
}
}
![]()
/*Pobieramy długość najdłuższej etykiety*/
private function getLongestLabelSize(){
$max = 0;
foreach($this->labels as $id=>$label){
$max = max(strlen($label), $max);
}
return FONT_WIDTH*$max;
}
![]()
}
![]()
Tak prezentuje się kod klasy BarChart poniżej zamieszczam przykład wykorzystania tej klasy:
![]()
![]()
//Dołączamy plik z klasą Chart i BarChart
require('charts.php');
![]()
//Tworzymy nowy obiekt
$chart = new BarChart(150, 96);
![]()
//Ustawiamy tytuł
$chart->setTitle('Wyniki: ');
![]()
//Ustawiamy etykiety
$labels = array(0=>'PHP', 1=>'ASP', 2=>'JSP', 3=>'ColdFusion');
foreach($labels as $id=>$label){
$chart->setLabel($id, $label);
}
![]()
//Ustawiamy wartości
$values = array(0=>50, 1=>20, 2=>25, 3=>5);
foreach($values as $id=>$val){
$chart->setValue($id, $val);
}
![]()
//Ustawiamy kolory
$colors = array(0=>array(90, 0, 0, 0),
1=>array(0, 84, 151, 0),
2=>array(240, 195, 0, 0),
3=>array(38, 164, 0, 0)
);
foreach($colors as $id=>$color){
$chart->setColor($id, $color);
}
![]()
//Rysujemy po płutnie
$chart->draw();
//Rysujemy na ekran
$chart->publish();
![]()
Wykres kołowy
Zajmiemy się teraz stworzeniem wykresu kołowego. Tutaj niezbędne okaże się wykorzystanie GD2, ponieważ ta wersja pozwala używać funkcji rysujących łuki. Tutaj inaczej niż w przypadku wykresu słupkowego zobaczymy w jaki sposób rysuje się legędy. Najważniejszą zmianą w stosunku do wykresu słupkowego jest użycie funkcji imagefilledarc, którea pozwala w łatwy sposób rysować łuki.
Oto kod klasy PieChart
![]()
class PieChart extends Chart{
![]()
private $legendPosition;
![]()
public function setLegendPosition($pos){
$this->legendPosition = $pos;
}
![]()
public function draw(){
![]()
//wyznaczamy współrzędne środka koła
$cx = ($this->width - $this->legendPosition)/2;
$cy = min($this->width - $this->legendPosition, $this->height)/2 + 15;
![]()
$black = $forecolor = imagecolorallocate($this->canvas, 0, 0, 0);
![]()
//wyznaczamy wysokość i szerokość elispy (jako że rysujemy koło obie wartości są równe)
$oval_w = $oval_h = min($this->width-$this->legendPosition, $this->height) / 1.125- 10;
![]()
//początek (w stopniach) koła
$pos = 0;
![]()
$r_s = 10;
$r = $oval_w / 2;
![]()
//wyznaczamy 100% koła
$maxVal = array_sum($this->values);
![]()
$xpos = $this->width - $this->legendPosition;
$ypos = 35;
![]()
//dla każdej wartości rysujemy wpis w legendzie oraz odpowiedniej wielkości część koła
foreach($this->values as $x=>$y){
![]()
//wyznaczamy koniec (w stopniach) kawałka koła
$end = $pos + 360 * $y / $maxVal;
![]()
$forecolor = imagecolorallocatealpha($this->canvas,
$this->colors[$x][0], $this->colors[$x][1], $this->colors[$x][2],
$this->colors[$x][3]);
![]()
//rysujemy kolorem ($forecolor) odpowiedni kawałek koła
//używamy trybu rysowania IMG_ARC_PIE
imagefilledarc($this->canvas, $cx, $cy, $oval_w, $oval_h, $pos, $end, $forecolor, IMG_ARC_PIE);
![]()
//rysujemy kwadracik z kolorem do legendy
imagefilledrectangle($this->canvas, $xpos, $ypos, $xpos+9, $ypos+9, $forecolor);
![]()
//wypisujemy w legendzie etykietkę
imagestring($this->canvas, 2, $xpos + 15, $ypos, $this->labels[$x], $black);
![]()
//zwiększamy y
$ypos += 20;
![]()
//koniec (w stopniach) kawałka koła staje się początkiem nowej częsci
$pos = $end % 360;
}
![]()
//rysujemy tytuł wykresu
imagestring($this->canvas, 3,
($this->width-FONT_WIDTH*strlen($this->title))/2,1,$this->title
,$black);
![]()
}
}
![]()
Warto zwrócić uwagę na możliwości funkcji imagefilledarc. Jako ostatni argument przekazujemy jej sposób rysowania łuku. Możemy narysować łuk nie wypełniony kolorem, używając IMG_ARC_NOFILL. Jest jeszcze kilka innych ciekawych opcji, o których można przeczytać tutaj: imagefilledarc. Poniżej podaje sposób wykorzystania tej klasy.
![]()
![]()
require('charts.php');
![]()
$chart = new PieChart(275, 175);
$chart->setLegendPosition(100);
![]()
$chart->setTitle('Wyniki: ');
$labels = array(0=>'PHP', 1=>'ASP', 2=>'JSP', 3=>'ColdFusion');
foreach($labels as $id=>$label){
$chart->setLabel($id, $label);
}
![]()
$values = array(0=>50, 1=>20, 2=>25, 3=>5);
foreach($values as $id=>$val){
$chart->setValue($id, $val);
}
![]()
$colors = array(0=>array(90, 0, 0, 0),
1=>array(0, 84, 151, 0),
2=>array(240, 195, 0, 0),
3=>array(38, 164, 0, 0)
);
![]()
foreach($colors as $id=>$color){
$chart->setColor($id, $color);
}
![]()
$chart->draw();
$chart->publish();
![]()
Podsumowanie
Mam nadzieję, że artykuł był ciekawy. Postarałem się przedstawić po krótce sposób tworzenia wykresów w PHP przy użyciu bilbioteki GD2. Biblioteka ta daje ogromne możliwości. Zachęcam do tworzenia własnych, innych wykresów.










