plotting masters - a professional guide - part II + IIIGrafische Interpolation und Bestapproximation von numerischen Wertepaaren. Mit anderen Worten: Wir wollen Punkte auf einer Zeichenebene über verschiedene Verfahren miteinander verbinden. Die beispielorientierte Anwendung (Teil I) ist einfach und für jeden verständlich. Die formale Umsetzung (Teil II + III) sowie das individuelle Erweiterungspotential richtet sich hingegen an fortgeschrittene Programmierer.
1
4
mit PHP 1. Fassung Berlin Jun 09 Daniel Krompass X.P. 2. Fassung Berlin Sep 09 Daniel Krompass Prototyp ![]() Ich habe mehr Werkzeuge, als meine Freundin Schuhe hat. Feine Waffeln kommen aus meinem Waffeleisen und Schokolade kaufe ich im KDW - that's berlin.
Alle Inhalte befinden sich bis zur 3ten und endgültigen Fassung noch in Arbeit. Im gleichen Zuge geben wir das Produkt für den Einsatz frei, und stellen den Quellcode zum Download bereit. Ich bedanke mich an dieser Stelle bei den Plattformbetreibern für das regelmäßige Update meiner Dokumentation. Aus dem Inhalt:
Informelle Spezifikation
Zielgruppe
Hintergrund
Motivation
Ein wissenschaftlicher Schachzug brächte eine Technologie hervor, die auf Grund des vorhandenen Potenzials alle bisherigen Techniken überholt. Ob der König am Leben bleibt, muss noch entschieden werden. Ausblick
Teil II
|
||||||||||||||
![]() |
Die richtige Idee an der richtigen Stelle ist maßgeblich für die Qualität des Programms verantwortlich. Wir berechnen folgend den Koordinatenursprung, sowie die effektiven Abmessungen der Zeichenebene. Die Existenz solcher Werte bildet für uns eine solide Grundlage für den Zeichenvorgang aller bisher entwickelten Komponenten. |
Der folgende Zeichenvorgang kann nur mit gültigen Intervallen fortgesetzt werden.
1
2 if ($maxY - $minY && $maxX - $minX)
3 {
4 ...
5 }
6 return $im;
7
Zum Zeichenvorgang gehört zunächst die Berechnung des Koordinatenursprungs $cx, $cy. An den Intervallgrenzen $minX, $maxX, $minY und $maxY rechnen wir mit den exakten Werten. Der horizontale Platzanspruch zur Beschriftung der Y-Achse errechnet sich über:
1
2 $cw = $this->charWidth;
3 # Wenn eine Map-Funktion gegeben ist.
4 if ($mapY)
5 {
6 # Obere Grenze
7 $num = $maxY;
8 $val = call_user_func($mapY, $num);
9 $cx = $cw * strlen($val);
10 $step = ($maxY - $minY) / $stepsY;
11
12 # Zwischenwerte
13 for ($i = 1; $i < $stepsY; $i++)
14 {
15 $num = $num - $i * $step;
16 $val = call_user_func($mapY, $num);
17 $cx = max($cx, $cw * strlen($val));
18 }
19
20 # Untere Grenze
21 $num = $minY;
22 $val = call_user_func($mapY, $num);
23 $cx = max($cx, $cw * strlen($val));
24 }
25
26 # Wenn keine Map-Funktion gegeben ist.
27 else
28 {
29 # Obere Grenze
30 $num = $maxY;
31 $cx = $cw * strlen($num);
32 $step = ($maxY - $minY) / $stepsY;
33
34 # Zwischenwerte
35 for ($i = 1; $i < $stepsY; $i++)
36 {
37 $num = $num - $i * $step;
38 $cx = max($cx, $cw * strlen($num));
39 }
40
41 # Untere Grenze
42 $num = $minY;
43 $cx = max($cx, $cw * strlen($num));
44 }
45
Doch damit nicht genug, denn auch der erste Wert auf der X-Achse könnte über alle Grenzen hinauswachsen. Gegebenfalls ist also eine Korrektur vorzunehmen.
1
2 if ($mapX)
3 {
4 $val = call_user_func($mapX, $minX);
5 $cx = max($cx, $cw * strlen($val) / 2);
6 }
7 else $cx = max($cx, $cw * strlen($minX) / 2);
8
Folgewerte interessieren uns nicht, da diese ohnehin ihren Vorgänger überdecken würden, was niemand gebrauchen kann. Wir rücken noch ein wenig ein.
1
2 $cx += 10;
3
Der vertikale Platzanspruch soll ein fester Wert sein.
1
2 $cy = $yDim - 25;
3
Das Spektakel setzen wir nun mit der Berechnung der effektiven Größe der Zeichenfläche fort.
1
2 # Wir halten oben einen Abstand von 5 Pixel ein,
3 $h = $cy - 5;
4
5 # und ermitteln den rechten Abstand über
6 if ($mapX)
7 {
8 $val = call_user_func($mapX, $maxX);
9 $w = $xDim - $cx - $cw * strlen($val) / 2 - 5;
10 }
11 else
12 $w = $xDim - $cx - $cw * strlen($maxX) / 2 - 5;
13
Da wir jetzt den Koordinatenursprung und die effektiven Abmessungen der Zeichenfläche kennen, können wir die Koordinatenachsen zeichnen.
1
2 imageline($im, $cx, $cy, $cx + $w, $cy, $fgcolor);
3 imageline($im, $cx, $cy, $cx, $cy - $h, $fgcolor);
4
5 # Stauchung $pY und effektive Zeichenhöhe miteinbeziehen.
6 $cy -= $pY;
7 $h -= 2 * $pY;
8
Was noch fehlt, sind die Intervallstriche, sowie die entsprechenden Beschriftungen. Zunächst legen wir fest:
1
2 $f = $this->font;
3 $ch = $this->charHeight / 2;
4
Wir beginnen mit der Y-Achse,
1
2 $a = $h / $stepsY;
3 $b = ($maxY - $minY) / $stepsY;
4 for ($i = 0; $i <= $stepsY; $i++)
5 {
6 if ($i < $stepsY)
7 {
8 $x1 = $cx - 5;
9 $y1 = $cy - $i * $a;
10 $x2 = $cx;
11 $y2 = $y1;
12 $num = $minY + $i * $b;
13 }
14
15 # Für den letzten Schritt nehmen wir den exakten Wert.
16 else
17 {
18 $x1 = $cx - 5;
19 $y1 = $cy - $h;
20 $x2 = $cx;
21 $y2 = $y1;
22 $num = $maxY;
23 }
24
25 # Intervallstrich
26 imageline($im, $x1, $y1, $x2, $y2, $fgcolor);
27
28 # Beschriftung
29 if ($mapY) $num = call_user_func($mapY, $num);
30 $m = $cw * strlen($num) + 2;
31 imagestring($im, $f, $x1 - $m, $y1 - $ch, $num, $fgcolor);
32 }
33
und setzen auf der X-Achse analog fort. Dabei machen wir zuerst die Y-Stauchung kurzfristig rückgängig, sonst würde nämlich unsere Achsenbeschriftung verrutschen. Dafür beziehen wir jetzt die horizontale Stauchung $pX und die effektive Breite mit ein.
1
2 $cy += $pY;
3 $cx += $pX;
4 $w -= 2 * $pX;
5 $a = $w / $stepsX;
6 $b = ($maxX - $minX) / $stepsX;
7 for ($i = 0; $i <= $stepsX; $i++)
8 {
9 # Berechnung des ersten und aller folgenden Zwischenschritte.
10 if ($i < $stepsX)
11 {
12 $x1 = $cx + $i * $a;
13 $y1 = $cy;
14 $x2 = $x1;
15 $y2 = $cy + 5;
16 $num = $minX + $i * $b;
17 }
18
19 # Für den letzten Schritt nehmen wir den exakten Wert.
20 else
21 {
22 $x1 = $cx + $w;
23 $y1 = $cy;
24 $x2 = $x1;
25 $y2 = $cy + 5;
26 $num = $maxX;
27 }
28
29 # Intervallstrich
30 imageline($im, $x1, $y1, $x2, $y2, $fgcolor);
31
32 # Beschriftung
33 if ($mapX) $num = call_user_func($mapX, $num);
34 $m = $cw * strlen($num) / 2;
35 imagestring($im, $f, $x1 - $m, $y2 + 2, $num, $fgcolor);
36 }
37
Zum Abschluss delegieren wir den Zeichenvorgang noch an alle Komponenten weiter. Man beachte, die vertikale Stauchung wiederherzustellen.
1
2 $this->image = $im;
3 $this->centerX = $cx;
4 $this->centerY = $cy - $pY;
5 $this->width = $w;
6 $this->height = $h;
7 foreach ($this->components as $c)
8 $c->Draw($this);
9
![]() |
Der geübte Programmierer kann den obigen Zeichenvorgang Draw ohne Mühe auf individuelle Bedürfnisse anpassen. So z.B. ein übliches Diagrammgitter oder eine andere Darstellung der Achsenbeschriftungen. Eine Legende erfordert hingegen etwas Geschick. |
Diese Klasse definiert noch eine kleine Hilfsfunktion zum Zeichnen von Verläufen. Sie hat nichts mit dem eigentlichen Typ Graph zu tun.
1
2 public static function Gradient($r1, $g1, $b1, $r2, $g2, $b2, $width, $im)
3 {
4 $verlauf = array();
5 if ($width)
6 {
7 $offset1 = (($r2 - $r1) / $width);
8 $offset2 = (($g2 - $g1) / $width);
9 $offset3 = (($b2 - $b1) / $width);
10 for ($i = 0; $i < $width - 1; $i++)
11 {
12 $rcol = $r1 + $offset1 * $i;
13 $gcol = $g1 + $offset2 * $i;
14 $bcol = $b1 + $offset3 * $i;
15 $verlauf[$i] = imagecolorallocate($im, $rcol, $gcol, $bcol);
16 }
17 $verlauf[$width-1] = imagecolorallocate($im, $r2, $g2, $b2);
18 }
19 return $verlauf;
20 }
21
Die Schnittstelle GraphComponent
-
Die Schnittstelle GraphComponent gibt spezifische Eigenschaften vor, die jede Komponente erfüllen muss, die Graph fordert.
1
2 interface GraphComponent
3 {
4 public function Draw(Graph $g);
5 public function GetMaxX();
6 public function GetMinX();
7 public function GetMaxY();
8 public function GetMinY();
9 }
10
Die häufig verwendete Methode AddPoint gehört nicht dazu.
Die Klasse GraphPoints
-
Die Klasse GraphPoints ist leicht zu implementieren und stellt die übergebenen Wertepaare als quadratische «Punkte» dar. Sie muss die Eigenschaften von GraphComponent erfüllen. Die meisten Komponenten besitzen auch eine Basis, die die Werte der minimalen X- und Y-Werte maßgeblich beeinflusst, wie wir gleich noch sehen werden.
1
2 class GraphPoints implements GraphComponent
3 {
4 ...
5 }
6
Die internen Eigenschaften sind durch die Menge aller Wertepaare, sowie durch einen RGB-Farbwert gegeben.
1
2 private $points = array();
3 private $red = 0;
4 private $green = 0;
5 private $blue = 0;
6 private $baseX = 0;
7 private $baseY = 0;
8
Der Konstruktor nimmt einen solchen RGB-Farbwert, sowie eine optionale Basis entgegen.
1
2 public function __construct($col, $bX=0, $bY=0)
3 {
4 $this->blue = $col % 0x100;
5 $col = $col >> 8;
6 $this->green = $col % 0x100;
7 $this->red = $col >> 8;
8 $this->baseX = $bX;
9 $this->baseY = $bY;
10 }
11
Das Hinzufügen von Wertepaaren ist selbsterklärend. Die Punkte werden gemäß der Einfügereihenfolge gezeichnet.
1
2 public function AddPoint($xPos, $yPos)
3 {
4 array_push($this->points, array($xPos, $yPos));
5 }
6
Die folgenden Methoden ermittel den maximalen bzw. minimalen X- und Y-Wert dieser Komponente.
1
2 public function GetMaxX()
3 {
4 $maxX = $this->points[0][0];
5 foreach ($this->points as $point)
6 $maxX = max($maxX, $point[0]);
7 return $maxX;
8 }
9 public function GetMinX()
10 {
11 $minX = $this->points[0][0] - $this->baseX;
12 foreach ($this->points as $point)
13 $minX = min($minX, $point[0] - $this->baseX);
14 return $minX;
15 }
16 public function GetMaxY()
17 {
18 $maxY = $this->points[0][1];
19 foreach ($this->points as $point)
20 $maxY = max($maxY, $point[1]);
21 return $maxY;
22 }
23 public function GetMinY()
24 {
25 $minY = $this->points[0][1] - $this->baseY;
26 foreach ($this->points as $point)
27 $minY = min($minY, $point[1] - $this->baseY);
28 return $minY;
29 }
30
Auch hier gilt wieder, dass wir offensichtlich wenigstens einen Punkt voraussetzen. Wie üblich ist es wieder die Methode Draw, die ein wenig mehr Erklärung bedarf.
1
2 public function Draw(Graph $g)
3 {
4 ...
5 }
6
Wir ermitteln zunächst wieder die Ober- und Untergrenze der Intervalle für die Skalierung und legen einige Variablen fest.
1
2 $im = $g->image;
3 $c = imagecolorallocate($im, $this->red, $this->green, $this->blue);
4 $maxX = $g->GetMaxX();
5 $minX = $g->GetMinX();
6 $maxY = $g->GetMaxY();
7 $minY = $g->GetMinY();
8 $pt = $this->points;
9 $w = $g->width;
10 $h = $g->height;
11 $cx = $g->centerX;
12 $cy = $g->centerY;
13
Der Zeichenvorgang skaliert jeden Punkt auf die richtigen Werte, und errechnet daraus dann die Koordinaten für das «Quadrat», dessen Seitenlänge 4 Pixel sein soll.
1
2 for ($i = 0; $i < sizeOf($pt); $i++)
3 {
4 # Koordinaten skalieren und Koordinaten für das "Quadrat" bestimmen.
5 $x1 = $w * ($pt[$i][0] - $minX) / ($maxX - $minX) - 2;
6 $y1 = $h * ($pt[$i][1] - $minY) / ($maxY - $minY) - 2;
7 $x2 = $x1 + 4;
8 $y2 = $y1 + 4;
9
10 # Quadrat zeichnen.
11 imagerectangle($im, $cx + $x1, $cy - $y1, $cx + $x2, $cy - $y2, $c);
12 }
13
Die Klasse GraphLinear
-
Die lineare Interpolation ist nicht schwieriger als die punktweise Interpolation. Die Punkte werden gemäß der Einfügereihenfolge über Linien miteinander verbunden. Wir betrachten erneut ein Beispiel.

1
2 class GraphLinear implements GraphComponent
3 {
4 ...
5 }
6
Die Klasse GraphLinear unterscheidet sich von der Klasse GraphPoints nur im Zeichenvorgang Draw, den wir uns genauer ansehen wollen.
1
2 public function Draw(Graph $g)
3 {
4 ...
5 }
6
Wie üblich ermitteln wir zunächst wieder die Ober- und Untergrenze der Intervalle für die Skalierung und legen einige Variablen fest.
1
2 $im = $g->image;
3 $c = imagecolorallocate($im, $this->red, $this->green, $this->blue);
4 $maxX = $g->GetMaxX();
5 $minX = $g->GetMinX();
6 $maxY = $g->GetMaxY();
7 $minY = $g->GetMinY();
8 $pt = $this->points;
9 $w = $g->width;
10 $h = $g->height;
11 $cx = $g->centerX;
12 $cy = $g->centerY;
13
Als naheliegende Lösung bietet sich in Analogie zur Zeichenmethode GraphPoints.Draw folgender Ansatz an.
1
2 for ($i = 1; $i < sizeOf($pt); $i++)
3 {
4 # Koordinaten der Linieneckpunkte auslesen.
5 $x1 = $pt[$i][0];
6 $y1 = $pt[$i][1];
7 $x2 = $pt[$i-1][0];
8 $y2 = $pt[$i-1][1];
9
10 # Koordinaten skalieren.
11 $x1 = $w * ($x1 - $minX) / ($maxX - $minX);
12 $y1 = $h * ($y1 - $minY) / ($maxY - $minY);
13 $x2 = $w * ($x2 - $minX) / ($maxX - $minX);
14 $y2 = $h * ($y2 - $minY) / ($maxY - $minY);
15
16 imageline($im, $cx + $x1, $cy - $y1, $cx + $x2, $cy - $y2, $c);
17 }
18
Dies ist ein Beispiel, das wir oft in ähnlicher Form vorfinden. Die Iteration eines Arrays greift zusätzlich auf seinen unmittelbaren Vorgänger oder Nachfolger zu. Man kann den Zugriff auf Arrayelemente durch folgenden Ansatz vermindern.
1
2 $x1 = $w * ($pt[0][0] - $minX) / ($maxX - $minX);
3 $y1 = $h * ($pt[0][1] - $minY) / ($maxY - $minY);
4 for ($i = 1; $i < sizeOf($pt); $i++)
5 {
6 $x2 = $w * ($pt[$i][0] - $minX) / ($maxX - $minX);
7 $y2 = $h * ($pt[$i][1] - $minY) / ($maxY - $minY);
8 imageline($im, $cx + $x1, $cy - $y1, $cx + $x2, $cy - $y2, $c);
9 $x1 = $x2;
10 $y1 = $y2;
11 }
12
Wir setzen auch hier wieder voraus, dass es mindestens einen Punkt geben muss (im Unterschied zum obigen Ansatz!), aber der Laufzeitfehler tritt ohnehin schon bei der Bestimmung von MaxXY und MinXY auf. Gezeichnet wird erst bei mindestens zwei Punkten.
Die Klasse GraphBar
-
Die punktweise Interpolation über Diagrammbalken ist auch nicht weiter tragisch. Wir betrachten dazu noch einmal ein Beispiel.

Die zugehörige Klasse ist definiert durch:
1
2 class GraphBar implements GraphComponent
3 {
4 ...
5 }
6
Die privaten Eigenschaften werden durch einen weiteren RGB-Farbwert für den links-rechts-Farbverlauf erweitert,
1
2 private $points = array();
3 private $r1 = 0;
4 private $g1 = 0;
5 private $b1 = 0;
6 private 
Comments to the Tutorial
comment the Tutorial
Rate the tutorial
Did you like this tutorial? Than rate it now! Five stars means "very good", one star "very bad".
![]() EVAMasters |
State Premium Member Occupation Member since: Last activity |
aktuelle Artikel
PHP 5.3.2 60 Fehler weniger |
500. Artikel bei phphatesme.com22.02.2010 | |

|2009.09.12 |






