plotting masters - a professional guide - Teil I

Grafische Interpolation und Bestapproximation von numerischen Wertepaaren: Wir wollen Punkte auf einer Zeichenebene über verschiedene Verfahren miteinander verbinden.

Herkömmliche Programmiertechnik mit PHP
Berlin Jun 09


Mit dem Erscheinen der Beta-Version wurden die «prototypischen Schlampereien» bei der Rechnung mit Gleitkommazahlen durch geeignete Rundungstechniken besser konditioniert.

Informelle Spezifikation

    Es ist nach einem kleinen aber flexiblem PHP-Programm zum Zeichnen von kartesischen Diagrammen gesucht. Wir folgen dabei einer intuitiven Vorgehensweise: Eine Zeichenebene dient als Container für unterschiedliche Diagrammtypen, so z.B. Linien-, Punkt- oder Kurvendiagramme. Jeder derartigen Komponente werden numerische Wertepaare übergeben, die dann über die jeweiligen Verfahren interpoliert oder approximiert werden. Wir wollen hier eine stückweise lineare Interpolation (Polygonzug), sowie eine natürliche Spline-Interpolation von Wertepaaren realisieren. Wir werden auch sehen, wie sich sogar Balkendiagramme mit diesem Kontext vertragen können.

Motivation

    Das folgende Programm zeigt, wie mit PHP die obige Anforderung umgesetzt werden kann. Der Programmumfang umfasst keine 1000 Zeilen. Somit ist der geübte Programmierer in der Lage, das Programm an die jeweiligen Bedürfnisse anzupassen.

Ausblick

    Neben den hier vorgestellten Interpolationsverfahren spielt ebenso die Ausgleichsrechnung (Bestapproximation) eine praktische Rolle, deren Graphen im Unterschied dazu nicht zwangsläufig durch alle Wertepaare hindurchgehen. Vielmehr ist man an einer bestmöglichen (gewichteten) Annäherung durch einen Graphen mit vorgegebenen Eigenschaften interessiert. Je nach freien Kapazitäten komme ich darauf zurück.

Beispiel 1 (Interpolation von numerischen Wertepaaren)

    Zu Beginn jeder grafischen Visualisierung von Wertepaaren initialisiert man stets ein Exemplar vom Typ Graph - unsere Zeichenebene. Diese Instanz nimmt anschließend mittels Add die verschiedene Komponenten vom Typ GraphComponent auf. Der Zeichenvorgang berechnet aus allen Komponenten die jeweilige Skalierung für jede einzelne Komponente, so dass alle Komponenten in der Zeichenebene Platz finden und damit vollständig sichtbar sind. Der Konstruktor von Graph hat kein Argument.

    1 
    2    $g = new Graph();
    3 


    Wir wollen folgend drei Komponenten mit gleichen Wertepaaren grafisch visualisieren, um die spezifischen Unterschiede zu demonstrieren. Die jeweiligen Konstruktoren der einzelnen Komponenten nehmen einen hexadezimalcodierten RGB-Farbwert entgegen, in der die Komponente gezeichnet werden soll. Die Komponente c1 soll unsere Wertepaare durch Punkte interpolieren, d.h. die Punkte als schwarze «kleine Quadrate» zeichnen.

    1 
    2    $c1 = new GraphPoints(0x000000);
    3 


    Die Komponente c2 soll unsere Wertepaare als stückweise lineare Interpolierende darstellen, d.h. alle Punkte über eine blaue Linie verbinden.

    1 
    2    $c2 = new GraphLinear(0x0000FF);
    3 


    Die Komponente c3 soll schließlich unsere Wertepaare als natürliche Spline-Interpolierende darstellen, d.h. die Punkte werden über eine rote Kurve verbunden.

    1 
    2    $c3 = new GraphNaturalSpline(0xFF0000);
    3 


    Die Komponenten müssen nun an die obige Zeichenebene $g gebunden werden. Unser «Container» wird also über alle Komponenten, die wir zeichnen wollen, «informiert». Die Bindereihenfolge bestimmt dabei die Sichtbarkeit, d.h. die zuletzt eingefügte Komponente überdeckt stets ihre Vorgänger.

    1 
    2    $g->AddComponent($c1);
    3    $g->AddComponent($c2);
    4    $g->AddComponent($c3);
    5 


    Als nächstes übergeben wir den Komponenten konkrete Wertepaare. Wir wählen dabei für alle Komponenten dieselben Wertepaare, um die spezifischen Unterschiede zu sehen.

     1 
     2    $c1->AddPoint(-1, 5);
     3    $c1->AddPoint(0, -2);
     4    $c1->AddPoint(1, 9);
     5    $c1->AddPoint(2, -4);
     6 
     7    $c2->AddPoint(-1, 5);
     8    $c2->AddPoint(0, -2);
     9    $c2->AddPoint(1, 9);
    10    $c2->AddPoint(2, -4);
    11 
    12    $c3->AddPoint(-1, 5);
    13    $c3->AddPoint(0, -2);
    14    $c3->AddPoint(1, 9);
    15    $c3->AddPoint(2, -4);
    16 


    Bemerkung

      Ob die Komponenten $c1, $c2 und $c3 erst an die Zeichenebene gebunden und dann die Wertepaare hinzugefügt werden oder umgekehrt, spielt keine Rolle.

    Abschließend wollen wir den Container mit allen darin enthaltenen Komponenten noch zeichnen. Die Funktion Draw liefert uns den Bildkontext, den wir entweder lokal speichern, oder über ein Netzwerk versenden können. Als Argument sind Achsen- und Hintergrundfarbe, Breite und Höhe, sowie die Anzahl der äquidistanten Intervalle auf der X- bzw. Y-Achse anzugeben. Letztere Angaben dienen ausschließlich der Achsenbeschriftung, d.h. das Programm errechnet aus diesen, sowie dem minimalen und maximalen X- bzw. Y-Wert die entsprechende Beschriftung.

    1 
    2    $image = $g->Draw(0x000000, 0xFFFFCF, 500, 250, 3, 7);
    3    header("Content-type: image/gif");
    4    imagegif($image);
    5 


    Als Ausgabe erhalten wir:



    Es können anschließend problemlos weitere Wertepaare und/oder auch weitere Komponenten nachgetragen, und das Diagramm erneut gezeichnet werden. Wir ergänzen

     1 
     2    $c4 = new GraphNaturalSpline(0xFF0000);
     3    $g->AddComponent($c4);
     4    $c4->AddPoint(0, 10);
     5    $c4->AddPoint(1, 15);
     6    $c4->AddPoint(2, 10);
     7    $c4->AddPoint(3, -1);
     8 
     9    $c5 = new GraphPoints(0x000000);
    10    $g->AddComponent($c5);
    11    $c5->AddPoint(0, 10);
    12    $c5->AddPoint(1, 15);
    13    $c5->AddPoint(2, 10);
    14    $c5->AddPoint(3, -1);
    15 
    16    $image = $g->Draw(0x000000, 0xFFFFCF, 500, 250, 4, 19);
    17    header("Content-type: image/gif");
    18    imagegif($image);
    19 


    und erhalten mit den angepassten Werten 4 und 19 für die Anzahl der äquidistanten Intervalle auf den Koordinatenachsen:


Beispiel 2 (Achsenformatierung)

    In vielen Fällen wünschen wir eine spezielle Formatierung der Achsenbeschriftungen, so z.B. Zeit- oder Währungsangaben. Dazu müssen wir die numerischen Werte so wählen, dass wir daraus andere Darstellungen berechnen können. Um den formalen Overhead gering zu halten, verzichten wir auf spezielle Typvereinbarungen, und nutzen die formalen Möglichkeiten der Sprache gezielt aus. Wir geben eine Funktion an, die wahlweise auf die numerischen X- bzw. Y-Werte angewendet wird. In der funktionalen Programmierung sprechen wir vom sog. Mapping. Wir definieren für unseren Fall zwei Beispielfunktionen.

     1 
     2    function ToCurrency($item)
     3    {
     4       return number_format($item * 100, 2, 0);
     5    }
     6    function ToDate($item)
     7    {
     8       return date("d.m.Y", time() + $item * 86400);
     9    }
    10 


    Können wir keine Berechnungsvorschrift angeben, dann können wir auch die in Frage kommenden Werte auf frei wählbare Werte, z.B. durch ein assoziatives Array, projizieren. Abschließend veranlassen wir noch die (erneute) Zeichnung. Dabei geben wir jeweiligen Funktionsnamen zur Formatierung der X- bzw. Y-Achse als ergänzende Parameter von Draw an.

    1 
    2    $image = $g->Draw(0x000000, 0xFFFFCF, 500, 250, 3, 7, "ToDate", "ToCurrency");
    3    header("Content-type: image/gif");
    4    imagegif($image);
    5 


    Als Ausgabe erhalten wir:


Beispiel 3 (Datenbankauswertung)

    In vielen Webanwendungen wollen wir gerade die Daten aus einer Datenbank grafisch visualisieren. In diesem Beispiel wollen wie eine Gehaltskurve von Mitarbeitern zeichnen. Dazu benötigen wir zunächst eine geeignete Datentabelle.

    Id Name Gehalt
    1 Martin A. 3.230,00
    2 Theresa B. 3.032,00
    3 Andrea C. 1.897,00
    4 Günter D. 2.980,00

    Wie gehabt generieren wir wieder unseren Graphen in üblicher Form.

    1 
    2    $g  = new Graph();
    3    $c1 = new GraphNaturalSpline(0xFF0000);
    4    $c2 = new GraphPoints(0x000000);
    5    $g->AddComponent($c1);
    6    $g->AddComponent($c2);
    7 


    Die Werte lesen wir über eine entsprechende mySQL-Anweisung aus der obigen Datentabelle aus. Mehr zum Thema mySQL findet man unter http://www.php-resource.de/tutorials/tutorial,27 oder http://www.php-resource.de/tutorials/tutorial,44. Wir probieren zunächst einen offensichtlichen Ansatz:
    Zuerst überlegen wir uns, wie wir unsere Map-Funktion so definieren, dass wir auf der X-Achse die Namen der jeweilgen Mitarbeiter zu stehen haben. Wir müssen also von einer Zahl auf den zugehörigen Namen eindeutig schließen können. Es bietet sich dabei die Assoziation zwischen der Id-Nummer und dem zugehörigen Namen an.

     1 
     2    function ToEmployer($item)
     3    {
     4       $result = mysql_query("SELECT Name
     5                              FROM mitarbeiter
     6                              WHERE Id = $item");
     7 
     8       return mysql_result($result, 0, 0);
     9    }
    10 


    Sodann lesen wir die Werte aus der Datentabelle mit

    1 
    2    $result = mysql_query("SELECT Id, Name, Gehalt
    3                           FROM mitarbeiter");
    4    for ($i = 0; $row = mysql_fetch_array($result); $i++)
    5    {
    6       $c1->AddPoint((int)$row[0], (int)$row[2]);
    7       $c2->AddPoint((int)$row[0], (int)$row[2]);
    8    }
    9 


    aus und erhalten mit

    1 
    2    $image = $g->Draw(0x000000, 0xFFFFCF, 500, 250, 3, 5, "ToEmployer", "ToCurrency");
    3    header("Content-type: image/gif");
    4    imagegif($image);
    5 




    Wenn Sie nun eine spezielle Sortierung z.B. ORDER BY Gehalt ASC fordern, dann hätte dies keine Auswirkungen auf die grafische Ausgabe. Das liegt daran, dass der Spline seinen Kurvenzug unabhängig von der Einfügereihenfolge der Knotenpunkte zeichnet. Achten Sie stets darauf, dass jede mathematische Funktion strikt von links nach rechts gezeichnet wird. Insbesondere werden alle Punkte stets nach ihrem X-Wert aufsteigend sortiert, bevor diese zur Brechnung herangezogen werden. Wir können also nicht mit der Id aus der Datentabelle als X-Wert sinnvoll arbeiten, und müssen daher eine eigene Id und zugehörige Assoziation zu Name auf Programmebene erzeugen. Wir projizieren also eine Zahl entsprechend der Rangfolge auf den zugehörigen Mitarbeiternamen.

     1 
     2    $result = mysql_query("SELECT Id, Name, Gehalt
     3                           FROM mitarbeiter2
     4                           ORDER BY Gehalt ASC");
     5 
     6    for ($i = 0; $row = mysql_fetch_array($result); $i++)
     7    {
     8       $mitarbeiter[$i] = $row[1];
     9       $c1->AddPoint($i, (int)$row[2]);
    10       $c2->AddPoint($i, (int)$row[2]);
    11    }
    12 
    13    function ToEmployer($item)
    14    {
    15       global $mitarbeiter;
    16       return $mitarbeiter[$item];
    17    }
    18 


    Nun wird jede gewünschte Sortierung «richtig» gezeichnet.


Beispiel 4 (Polygonzüge)

    Die Komponenten vom Typ GraphLinear sind im Grunde als ein Streckenzug durch die zu interpolierenden Wertepaare gemäß ihrer Einfügereihenfolge zu sehen. Die lineare Interpolation ist also ein Spezialfall eines solchen Streckenzugs mit zusätzlich mathematischen Eigenschaften. Wir wollen uns folgend Polygonzüge ansehen.

     1 
     2    # Zeichenebene
     3    $g = new Graph();
     4 
     5    # Komponenten
     6    $c1 = new GraphLinear(0x0000FF);
     7    $c2 = new GraphLinear(0xFF0000);
     8 
     9    # Komponenten binden
    10    $g->AddComponent($c1);
    11    $g->AddComponent($c2);
    12 
    13    # Werte hinzufügen
    14    $c1->AddPoint(0, 2);
    15    $c1->AddPoint(1, 2);
    16    $c1->AddPoint(2, 3);
    17    $c1->AddPoint(2, 4);
    18    $c1->AddPoint(1, 5);
    19    $c1->AddPoint(0, 5);
    20    $c1->AddPoint(-1, 4);
    21    $c1->AddPoint(-1, 3);
    22    $c1->AddPoint(0, 2);
    23 
    24    $c2->AddPoint(0, 2);
    25    $c2->AddPoint(0, 1);
    26    $c2->AddPoint(-1, 0);
    27    $c2->AddPoint(-2, 0);
    28    $c2->AddPoint(-3, 1);
    29    $c2->AddPoint(-3, 2);
    30    $c2->AddPoint(-2, 3);
    31    $c2->AddPoint(-1, 3);
    32 
    33    # Ausgabe
    34    $image = $g->Draw(0x000000, 0xFFFFCF, 500, 250, 5, 5);
    35    header("Content-type: image/gif");
    36    imagegif($image);
    37 


    Wir erhalten:



    Der Sinn von Streckenzügen zeigt sich bei praktischen Vorgängen, die einen senkrechten Anstieg zur Folge haben. Dies können z.B. abrupte Gehalts- oder Auszahlungsänderungen sein. Wir betrachten ein Beispiel.

     1 
     2    include ("graph.php");
     3 
     4    # Zeichenebene
     5    $g = new Graph();
     6 
     7    # Komponenten
     8    $c1 = new GraphLinear(0x0000FF);
     9    $c2 = new GraphLinear(0xFF0000);
    10 
    11    # Komponenten binden
    12    $g->AddComponent($c1);
    13    $g->AddComponent($c2);
    14 
    15    # Werte hinzufügen
    16    $c1->AddPoint(0, 4);
    17    $c1->AddPoint(1, 4);
    18    $c1->AddPoint(1, 3);
    19    $c1->AddPoint(2, 3);
    20    $c1->AddPoint(2, 4);
    21    $c1->AddPoint(2.5, 4);
    22    $c1->AddPoint(2.5, -2);
    23    $c1->AddPoint(3.5, -2);
    24    $c1->AddPoint(3.5, 6);
    25 
    26    $c2->AddPoint(-1, 0);
    27    $c2->AddPoint(-1, 2);
    28    $c2->AddPoint(0, 2);
    29    $c2->AddPoint(0, 0);
    30    $c2->AddPoint(1, 0);
    31    $c2->AddPoint(1, 2);
    32    $c2->AddPoint(2, 2);
    33    $c2->AddPoint(2, 0);
    34    $c2->AddPoint(3, 0);
    35    $c2->AddPoint(3, 2);
    36    $c2->AddPoint(4, 2);
    37    $c2->AddPoint(4, 0);
    38 
    39    # Ausgabe
    40    $image = $g->Draw(0x000000, 0xFFFFCF, 500, 250, 5, 4, "ToDate", "ToCurrency");
    41    header("Content-type: image/gif");
    42    imagegif($image);
    43 


    Wir erhalten:


Erfahrungen

Es sind noch keine Kommentare vorhanden.

Hier Kannst Du einen Kommentar verfassen


Bitte gib mindestens 10 Zeichen ein.
Wird geladen... Bitte warte.
* Pflichtangabe

Verwandte Beiträge

phpinfo() zeigt nichts an

Heute möchte ich Euch zeigen wie ihr die phpinfo() ausgeben könnt. ...

Webmasterfreaky

Autor : Webmasterfreaky
Kategorie: PHP-Tutorials

SVN Server einrichten

Wie man unter Linux einen Subversion Server einrichtet. ...

admin

Autor : admin
Kategorie: Linux & Server Tutorials

Verschlüsselungsalgorithmus

Dieses Tutorial zeigt einen Verschlüsselungsalgorithmus von Texten nach dem PHP Data Encryption Standard. ...

Lukas Beck

Autor : Lukas Beck
Kategorie: PHP-Tutorials

Login Skript mit OOP, Sessions und einer MySql Datenbank (Teil 1)

Realisierung eines Login Skriptes mit einer MySql Datenbank, Sessions und PHP mit Objekt orientierter Programmierung. ...

Samir

Autor : Samir
Kategorie: PHP-Tutorials

PHP 7 Virtual Machine

Dieser Artikel zielt darauf ab, einen Überblick über die Zend Virtual Machine, wie es in PHP 7 gefunden wird. ...

admin

Autor : admin
Kategorie: PHP-Tutorials

Konfiguration eines Linux-Rechners als DSL-Router

Dieser Artikel beschreibt wie man unter LINUX einen DSL-Rooter für Windows konfiguriert. ...

tschiesser@

Autor : tschiesser@
Kategorie: Linux & Server Tutorials

Was muss ich in WordPress einstellen, damit Google Fonts nicht mehr geladen werden?

Möchten Du WordPress davon abhalten, Google Fonts zu verwenden? Hier erfährst Du, was du dafür in WordPress einstellen musst. ...

admin

Autor : admin
Kategorie: Sonstige Tutorials

Tutorial veröffentlichen

Tutorial veröffentlichen

Teile Dein Wissen mit anderen Entwicklern weltweit

Du bist Profi in deinem Bereich und möchtest dein Wissen teilen, dann melde dich jetzt an und teile es mit unserer PHP-Community

mehr erfahren

Tutorial veröffentlichen