Data Driven Websites mit PHP, Teil 3

Grafik mit PHP

Kristian Köhntopp

Mit einem Monat Verspätung hier der dritte und letzte Teil des kleinen PHP-Workshops. Beim letzten Mal hatten wir uns um wiederverwertbaren Code gekümmert. Heute geht es etwas weniger abstrakt um das Erzeugen von Grafiken.

Das Skript jump.php3, das wir beim letzten Mal zwar besprochen, dessen Code wir aber nicht gezeigt haben, weil er zu trivial ist, erzeugt für jeden Klick auf ein Werbebanner einen Location-Header, der auf die Präsentation verweist und trägt dann die geklickte Kennung in einer Tabelle banner ein. Listing11a zeigt die Struktur dieser Tabelle. Neben der tatsächlich angeklickten Kennung werden außerdem noch der Zeitpunkt des Klicks, die IP-Adresse des Abrufers sowie der Typ der von ihm verwendeten Browsersoftware abgespeichert.

Aus diesen Informationen kann man mit einer einfachen Query feststellen, welche Anzeige wie oft angeklickt worden ist. Ein SQL-Select, das nach den Kennungen gruppiert, ermöglicht es in einem Statement festzustellen, welche Werbung wie oft angeklickt wurde:

select count(kennung) as klick, kennung 
       from banner 
       group by kennung 
       order by klick desc

Diese Anweisung erzeugt eine Ergebnistabelle mit zwei Spalten und so vielen Zeilen, wie angeklickte Kennungen existieren. Die erste Spalte "klick" enthält die Anzahl der Klicks für jede Anzeige. Die zweite Spalte enthält die Kennung der Anzeige. Die Ausgabe erfolgt so, dass die Werbung mit den meisten Klicks als erstes ausgegeben wird (Hitliste, siehe Abbildung11).

PHP erlaubt es, aus diesen Daten dynamisch ein Bild im PNG-Format zu erzeugen, mit dem diese Zahlen in einem aussagekräftigen Balkendiagramm dargestellt werden. Dazu muss PHP mit der GD-Bibliothek von Thomas Boutell gelinkt werden [3] - nur dann stehen die entsprechenden Image-Befehle zur Verfügung, die in diesem Anwendungsbeispiel benutzt werden.

PHP-Skripte, die PNG-Bilder generieren wollen, können nur dieses eine PNG erzeugen. Sie können kein HTML generieren und sie können auch nicht mehr als Bild ausgeben. Daher kann es unter Umständen notwendig sein, den entsprechenden Code auf mehr als eine Datei zu verteilen. Ein PHP-Programm, das ein PNG generiert, beginnt immer mit einem Aufruf von header("image /png") und einem $im = ImageCreate (breite, höhe);. Die Header-Anweisung teilt dem PHP-Interpreter und dem Webserver mit, dass wir kein HTML erzeugen möchten, sondern eine Bilddatei. Die ImageCreate-Anweisung reserviert einen bemalbaren Zeichenpuffer. Das Resultat ist ein Imagehandle, das wir bei allen weiteren Zeichenaufrufen angeben müssen. Am Ende unseres PHP-Skriptes sorgt dann der Aufruf ImagePng($im) für die Ausgabe des fertigen Bildes.

Solche PHP-Programme sind jedoch sehr schwer zu debuggen, weil Fehlermeldungen nun einmal "im Grafikmodus" sehr schwer zu lesen sind. Es ist daher sinnvoll, zum Debuggen die Header-Anweisung und den ImagePng()-Aufruf auszukommentieren und erst zu aktivieren, wenn das fertige Skript läuft.

Um in einem Bild zeichnen zu können, muss man sich passende Farben definieren. PNG-Bilder sind Palettenbilder, das heißt man kann bis zu 256 verschiedene "Stifte" festlegen, die jeweils einer beliebigen Farbe zugeordnet sind. Das Bild wird dann nicht mit den Farben gemalt, sondern mit den definierten Stiften. Einen weißen Stift definiert man sich beispielsweise mit der Anweisung $white = ImageColorAllocate($im, 255,255,255): Anzugeben sind neben dem Imagehandle die RGB-Werte der gewünschten Farbe, die Funktion liefert dafür eine Stiftnummer.

Der mit ImageCreate() reservierte Bildspeicher ist nicht initialisiert: Im Puffer kann noch allerlei Speicherdreck stehen, der in irgendwelchen Farben dargestellt wird. Es ist sehr empfehlenswert, das Bild zunächst einmal mit der gewünschten Grundfarbe zu initialisieren. Dies kann zum Beispiel mittels ImageFilledRectangle($im, 0,0, breite,höhe, $white) geschehen. Dabei ist zu beachten, dass alle Zeichenanweisungen mit einem Koordinatensystem arbeiten, bei dem der Punkt in der linken oberen Ecke des Bildes die Koordinaten 0,0 hat.

Die GD-Bibliothek kann in Bildern auch Texte erstellen. Falls man sie standalone übersetzt hat, stehen dazu allerdings nur die sehr eingeschränkten eingebauten Zeichensätze zur Verfügung. Bindet man jedoch auch noch FreeType mit ein, lässt sich statt der beschränkten ImageString()-Funktion das flexiblere ImageTTFText() verwenden, welches beliebige TTF-Zeichensätze verarbeiten kann.

Listing11b zeigt ein PHP-Programm, das die in Listing11a definierte banner-Tabelle auswertet und ein Balkendiagramm über die Klicks pro Anzeige generiert. In einer anderen Tabelle, zugriffe, wurde sämtliche Zugriffe auf die Hauptseite dieses Webservers registriert. Durch eine leichte Abwandlung der SQL-Select-Anweisung kann man aus dieser Tabelle mit dem oben erwähnten Programm eine Zugriffstatistik nach Tagen erstellen:

select dayofmonth(datum) as kennung, 
       count(dayofmonth(datum)) as klick
    from zugriffe 
group by kennung 
order by datum

Noch feiner aufgelöst ergibt sich eine stundenweise Zugriffskurve des Wahltages und des folgenden Montages:

select date_format(datum, 'dH')
  as kennung, count(datum) as klick from zugriffe
  where date_format(datum, 'dH') between '2700' and '2900'

  group by kennung order by datum

(uwo)

Abb. 11: Auswertung der Bannertabelle

+-------+---------+ | klick | kennung | +-------+---------+ | 412 | blank | | 327 | kaufen | | 240 | film | | 105 | franken | +-------+---------+ 4 rows in set (0.13 sec)

Listing 11a

Die Tabelle banner enthält eine Zeile für jeden Klick auf ein Werbebanner.

CREATE TABLE banner (
  id int(11) DEFAULT '0' NOT NULL auto_increment,
  datum timestamp(14),
  kennung varchar(127) DEFAULT '' NOT NULL,
  browser_kennung varchar(127) DEFAULT '' NOT NULL,
  remote_addr varchar(127) DEFAULT '' NOT NULL,
  PRIMARY KEY (id),
  KEY datum (datum),
  KEY kennung (kennung),
  KEY remote_addr (remote_addr)
);

Listing 11b

GenerierungeinesBalkendiagrammes(PNG)ausden Klickdatenderbanner-TabelleunterVerwendungderGD-Bibliothek

<?php
header("Content-Type:image/png");
include("db_mysql.inc");//DB_Sql
include("local.inc");//DB_WahlausDB_Sqlableiten

//Querygenerieren
$db=newDB_Wahl;
$query=sprintf("selectcount(kennung)asklick,kennung
frombanner
groupbykennung
orderbyklickdesc");
$db->query($query);
$num=$db->num_rows();

//Array$kennungerzeugen,Maximalwertbestimmen
$max=0;
while($db->next_record()){
$kennung[$db->f("kennung")]=$db->f("klick");
$max=$db->f("klick")>$max?$db->f("klick"):$max;
}

//BildpufferundFarbenallozieren
$im=ImageCreate(200,220);
$white=ImageColorAllocate($im,255,255,255);
$black=ImageColorAllocate($im,0,0,0);
for($i=0;$i<$num;$i++){
$col[$i]=ImageColorAllocate($im,255-250/$num*$i,250/$num*$i,250/$num*$i);
}

//Hintergrundfarbesetzen
ImageFilledRectangle($im,0,0,$siz_x,$siz_y,$white);

$i=0;
reset($kennung);
while(list($k,$v)=each($kennung)){
//Säulenhöheskalieren
$h=$v/$max*(200);

//Säulemalenundschwarzumrahmen
ImageFilledRectangle($im,$i*50+5,205-$h,$i*50+45,205,$col[$i]);
ImageRectangle($im,$i*50+5,205-$h,$i*50+45,205,$black);

//Bildunterschrift:Kennung,inderSäule:Höhe
ImageString($im,2,$i*50+5,207,$k,$black);
ImageString($im,2,$i*50+10,207-$h,round($v),$black);
$i++;
}

ImagePng($im);
?>

Infos

[1] Der vollständige Code zur Klasse DB_Sql ist Bestandteil der Bibliothek PHPLIB (http://phplib.netuse.de) und kann von der PHPLIB-Site geladen werden. PHPLIB enthält außerdem Versionen von DB_Sql für Postgres, ODBC und Oracle.
[2] Der vollständige Code zur Klasse DB_Sql aus [1] enthält noch einige Funktionen mehr, die für das Beispiel hier aber nicht von Bedeutung sind.
[3] libgd, http://www.boutell.com/

Copyright © 2000 Linux New Media AG