Wer's genau wissen will ...

Der Anfang sieht folgendermaßen aus:

/*
 * Demo-Skript für Berechnung der Mandelbrotmenge
 * von speerchen, www.speerchen.de
 */
function demo() {

Der function()-Befehl sorgt verkürzt ausgedrückt dafür, dass das Skript das Drücken der Schaltfläche "abwartet" und nicht gleich bei Aufruf der Seite startet.
Danach erfolgt die Javascript-spezifische Initialisierung des Grafikbildschirmes, die ich hier nicht weiter erläutert werden soll:

    bildElement = document.getElementById('fraktal');
    if (bildElement.getContext('2d')) {
        bild = bildElement.getContext('2d');
    } else {
        alert("Die erforderliche Grafikfunktion ist im Browser leider nicht vorhanden.");
    }

Als nächstes fasse ich - gleich in der Variablendeklaration - einen Beschluss über die Höhe und Breite der Grafik:

    /*
     * Breite des Bildes in Pixel
     */
    var breite = 320;
    
    /*
     * Höhe des Bildes in Pixel
     */
    var hoehe = 240;

Der "mathematische Teil" fängt mit der Überlegung an, dass die Bildschirmoberfläche nix anderes als einen Ausschnitt aus einem mathematischen Koordinatensystem darstellt. Die Position jedes Pixels steht für ein Zahlenpaar aus "x" und "y". Und jedem dieser Zahlenpaare wird durch die Berechnung eine "natürliche" (d.h. positive ganze) Zahl und der wiederum eine Farbe zugeordnet.

Um dies zu programmieren, muss man sich deshalb erstmal Gedanken darüber machen, welcher Ausschnitt des Koordinatensystems dargestellt werden soll, also welcher Wert für x "ganz links" steht und welche Wert für y "ganz unten". Und das Gleiche auch für "ganz rechts" und "ganz oben" - oder alternativ die "Breite" und "Höhe" auf der x- bzw. y-Achse. Am besten, Du glaubst mir einfach mal, dass folgende Werte sich bewährt haben:

    /*
     * Linke Kante der Grundfigur
     */
    var intervallLinks = -3.0;
    
    /*
     * Untere Kante der Grundfigur
     */
    var intervallUnten = -1.875;
    
    /*
     * Breite der Grundfigur
     */
    var intervallBreite = 5.0;
    
    /*
     * Höhe der Grundfigur
     */
    var intervallHoehe = 3.75;

Und jetzt noch die Deklaration von ein paar anderen Variablen:

    /*
     * Maximale Rekursionstiefe
     */
    var grenzeRekursion = 50;
    
    /*
     * Betragsgrenze für Rekursion
     */
    var grenzeBetrag = 4.0;
    
    /*
     * Quadrat der Betragsgrenze für Rekursion
     */
    var grenzeBetragQuadrat = grenzeBetrag * grenzeBetrag;
    
    /*
     * Farbtabelle
     */
    var farben = new Array("rgb(0,0,0)",
            "rgb(0,0,64)", "rgb(0,0,85)", "rgb(0,0,106)", "rgb(0,0,127)", "rgb(0,0,148)",
            "rgb(0,0,170)", "rgb(0,0,191)", "rgb(0,0,212)", "rgb(0,0,233)", "rgb(0,0,255)",
            "rgb(6,6,249)", "rgb(12,12,243)", "rgb(19,19,236)", "rgb(25,25,230)", "rgb(31,31,224)",
            "rgb(38,38,218)", "rgb(44,44,211)", "rgb(51,51,204)", "rgb(57,57,198)", "rgb(63,63,192)",
            "rgb(70,70,185)", "rgb(76,76,179)", "rgb(82,82,173)", "rgb(89,89,166)", "rgb(95,95,160)",
            "rgb(102,102,153)", "rgb(108,108,147)", "rgb(114,114,141)", "rgb(121,121,134)", "rgb(127,127,128)",
            "rgb(133,133,122)", "rgb(140,140,115)", "rgb(146,146,109)", "rgb(153,153,102)", "rgb(159,159,96)",
            "rgb(165,165,90)", "rgb(172,172,83)", "rgb(178,178,77)", "rgb(184,184,71)", "rgb(191,191,64)",
            "rgb(197,197,58)", "rgb(204,204,51)", "rgb(210,210,45)", "rgb(216,216,39)", "rgb(223,223,32)",
            "rgb(229,229,26)", "rgb(235,235,20)", "rgb(242,242,13)", "rgb(248,248,7)", "rgb(0,0,0)");

Wozu all diese Variablen nun alle gut sind, werden wir gleich noch sehen. An dieser Stelle nur zwei Dinge: Die Variable grenzeRekursion ist eine willkürliche Festlegung, wie viele verschiedene natürliche Zahlen bei der Berechnung herauskommen können - und infolgedessen wie viele Farben die Grafik insgesamt hat. Und: Das Array farben[] beinhaltet die insgesamt 51 (von 0 bis 50 durchnummerierten) Farben. Was in diesen Arrays vorgegeben wird, ist im Prinzip Geschmackssache. (Ich empfehle Farbverläufe und mag Blau am liebsten :-)

Jetzt aber zum eigentlichen Algorithmus.

    /*
     * Bildaufbau
     */

Am Anfang stehen erstmal zwei ineinandergeschachtelte for-Schleifen, die nix anderes zu tun haben, als nacheinander jedes Pixel auf dem Programmarbeitsbereich abzuklappern. Gleichzeitig mit der Bestimmung der pixelmäßigen Position auf dem Bildschirm wird auch gleich mit berechnet, welcher Punkt auf dem "wirklichen" Koordinatensystem dazugehört. Dieser wird in den Variablen x und y abgelegt.

    for (var pixelY = 0; pixelY < hoehe; pixelY++)  {
        var y = intervallUnten + intervallHoehe * pixelY / hoehe;
        for (var pixelX = 0; pixelX < breite; pixelX++)  {
            var x = intervallLinks + intervallBreite * pixelX / breite;

Und erst hier fängt die Berechnung eines der einzelnen Punkte an. Zunächst werden diverse Variablen, die später gebraucht werden, auf den Anfangswert von 0 gesetzt.

            var zaehler = 0;
            var gliedX = 0;
            var gliedY = 0;
            var gliedXQuadrat = 0;
            var gliedYQuadrat = 0;

Als nächstes wird eine weitere Schleife gestartet. Deren Inhalt macht jetzt die eigentliche Arbeit zur Berechunung eines Punktes.

            do {

Die Variable zaehler hat dabei verschiedene Funktionen: Nicht nur, dass sie die Anzahl der Schleifendurchläufe mitzählt - und die "Notbremse" zieht, wenn die in der Variablen rekursionsgrenze festgelegte Obergrenze erreicht wird - Der Inhalt dieser Variablen ist nach erledigtem Schleifendurchlauf gleichzeitig diejenige "natürliche Zahl", aus der sich die Farbe des entsprechenden Pixels ergibt! Die Variablen gliedX und gliedY werden bei jedem Schleifendurchlauf neu berechnet, und zwar basierend auf ihrem Wert beim jeweils vorherigen Schleifendurchlauf. Die Variablen gliedX2 und gliedY2 dienen eigentlich nur dazu, den Programmablauf "performanter"" zu gestalten. In ihnen befindet sich jeweils das Quadrat von gliedX bzw.gliedY. Wird an zwei verschiedenen Stellen gebraucht und soll da nicht jedes Mal neu berechnet werden. Die Variable hilfX ist eine Hilfsvariable, die zum Berechnen benötigt wird.

Und jetzt wird's interessant: Die Zahlenpaare "gliedX  /  gliedY" und auch "x  /  y" werden hier als komplexe Zahlen angesehen. Eine komplexe Zahl ist ein Zahlenpaar, das sich zusammensetzt aus x + yi, wobei gilt: i2  = -1. Es gibt folgende Rechenregeln, die wir hier brauchen:

Summe:
(x + yi) + (a + bi) = (x + a) + (y + b)i
Quadrat:
(x + yi)2 = (x2  - y2) + (2xy)i
Betrag:
|(x + yi)| = √ (x2  + y2)

Die Schleife fing an mit der komplexen Zahl "0 + 0i". Diese Beträge sind in gliedX und gliedY gespeichert. Diese Zahl wird nun quadriert, und anschließend wird die komplexe Zahl "x + yi" hinzuaddiert. (Dies ganze ist übrigens eine Art "rekursive Folge"; die einzelnen Zahlen, die so nacheinander herauskommen, nennt man "Folgeglieder". Man kann auch zur Berechnung der Folgeglieder schreiben: cn  = (cn-1)2  + d).

Aus diesem Berechnungvorgang ergibt sich folgende Befehlsfolge:

                var hilfX = gliedX * gliedY;
                gliedX = gliedXQuadrat - gliedYQuadrat + x;
                gliedY = hilfX + hilfX + y;
                gliedXQuadrat = gliedX * gliedX;
                gliedYQuadrat = gliedY * gliedY;
                zaehler++;

An dieser Stelle endet der Inhalt der Schleife. Hier wird geprüft, ob ein weiteres Folgeglied zu berechnen ist. Schluss ist dann, wenn entweder die vorgegebene maximale Zahl von Durchläufen erreicht ist - oder schon eher, wenn der "Betrag" der komplexen Zahl eine bestimmte - ebenfalls am Programmanfang vorgegebene - Größe überschreitet. Diese Größe ist in der Variable Betragsgrenze gespeichert.

            } while (zaehler < grenzeRekursion
                    && gliedXQuadrat + gliedYQuadrat < grenzeBetragQuadrat);

Hier konnte ich nicht der Versuchung widerstehen, das Ganze Perfomance-mäßig etwas zu tunen: Es wird hier nicht (wie es sich aus der Berechungsformel ergäbe) der Betrag mit der Betragsgrenze verglichen, sondern das Quadrat des Betrages mit dem Quadrat der Betragsgrenze (betragsgrenze2) - kommt auf's Gleiche 'raus und spart das "zeitraubende" Wurzelziehen ein ;-)

Die eigentliche Zahl, die bei der Berechnung herauskommt und farblich dargestellt werden soll, ist dabei die Anzahl der Schleifendurchläufe. Diese ist gespeichert in der Variable zaehler.

So, jetzt muss nur noch die Farbe aus der Variablen zaehler bestimmt und der Punkt mit dieser Farbe gezeichnet werden. Sieht in meinem Skript folgendermaßen aus. (Das "hoehe - " vor dem Y-Wert ist erforderlich, weil die Y - Achse auf dem normalen Koordinatensystem und dem Grafikbildschirm einander entgegengesetzt ausgerichtet sind; ohne dieser Korrektur würde das Bild auf dem Kopf stehen.)

            bild.fillStyle = farben[zaehler];
            bild.fillRect(pixelX, hoehe - pixelY,1,1);

Und nun noch das Ende der beiden for-Schleifen kennzeichnen:

        }
    }

(In BASIC würde hier so etwas wie "next pixelX" ... "next pixelY" stehen....)

Und - "Schluss" natürlich nicht vergessen, sonst gibt es Fehlermeldungen:

}

Zugegeben: Ein "Cocktail" aus einigen verschiedenen mathematischen Grundbegriffen, mit denen man sich durchaus länger beschäftigen kann. Deshalb hab ich hier noch ein paar Exkurse im Angebot.

Im Vergleich zum Skript, das im Hintergrund der verschiedenen Elemente der Sammlung seinen Dienst verrichtet, handelt sich bei dem Programm hier nur um eine "Magerversion". Es geht mir hier nur darum, das Funktionsprinzip zu verdeutlichen, und ich habe deshalb nach Kräften alles vereinfacht oder ganz weg gelassen, was nicht unbedingt vom Berechnungsalgorithmus oder einer einfachen Darstellung benötigt wird.

Aber - das grundsätzliche Prinzip ist gleich.