3. Funktionen und Koordinatensysteme


Aufgabe: Funktion y = sin(t) zeichnen.

  1. Genau so wie der Begriff der Variable ist auch der Begriff der Funktion - und auch die Schreibweise - direkt aus der Mathematik übernommen. Wenn wir in Java y als Sinus von der Variablen tausrechnen wollen, dann müssen wir zuerst die beiden Variablen als doubledeklarieren und die Funktion sin (gehört zur Klasse Math) aufrufen:

    double y, t = 0.3;
    y = Math.sin(t);
    

    Weil der Sinus eine periodische Funktion ist, ist es sinnvoll, den Verlauf einer Periode zu zeichnen. Deswegen werden wir die Sinus-Funktion für mehrere Werte von t im Bereich zwischen 0 und 2 pi auswerten und dann die ausgerechneten y-Werte mit Linien verbinden. Hier ein Beispiel mit ein paar Werten:

  2. Die Werte von t auf dem gegebenen Interval äquidistant zu wählen ist die typische Aufgabe für die for Schleife - wir beginnen mit dem Wert 0 (t = 0.0) und dann, solange t kleiner als 2 pi (t < 2.0 * Math.PI) ist, vergrößern wir t in kleinen Schritten dt (z.B. dt = 0.1):

        double dt = 0.1;

        for(double t = 0.0; t < 2.0 * Math.PI + dt; t += dt)
    {

          y = Math.sin(t);
          ... und hier wollen wir schon zeichnen, aber...
        }

  3. ... man merkt, dass y bestimmt zwischen -1 und 1 liegt, und unser Applet-Koordinatensystem von 0 bis 200 verläuft - eigentlich auch in horizontaler Richtung zwischen 0 und 400 und nicht zwischen 0 und 2 pi... Wir stehen vor dem folgenden Problem:


     

    Es ist klar, dass man die Werte von t und y zuerst skalieren sollte, so dass sie optimal ins Applet-Fenster reinpassen. Dazu bietet es sich, dass wir unsere eigenen Funktionen definieren, die die Skalierung durchführen:

      int sx(double x) { return (int)(x * xScale + xOffset); }
      int sy(double y) { return (int)(y * yScale + yOffset); }

    Im allgemeinen Fall sind es vier Zahlen, die die zwei skalierten und verschobenen Koordinatensysteme verbinden: xScale, yScale, xOffset und yOffset, und die Werte können wir aus dem Eckpunkten der beiden Systeme ausrechnen. Ohne jetzt in Details zu gehen, ist hier die notwendige Routine (zuerst müssen die vier Variablen entsprechend deklariert werden):

      double xScale, yScale, xOffset, yOffset;

      void setRange(double xMin, double xMax, double yMin, double yMax) {
        xScale = getSize().width / (xMax - xMin);
        yScale = -getSize().height / (yMax - yMin);
        xOffset = -xMin * xScale;
        yOffset = getSize().height - yMin * yScale;
      }

    Um die Routine setRange möglichst allgemein zu halten, benutzen wir die Funktion getSize(), die die aktuelle Breite (getSize().width) und Höhe (getSize().height) des Applet-Fensters liefert.

    Damit Sie die Routine setRange und die Funktionen sx und sy in Ihren Programmen aufrufen können, kopieren Sie den oben gegebenen Code in Ihren Quelltext:

    ...

    public class MyLieblingsApplet extends Applet
    {

       -> hierhin kopieren Sie den 'grünen' Code von oben
       -> und hierhin noch die 'blaue' Definitionen von sx und sy

      public void paint(Graphics g) {

        // am Anfang der paint-Routine sollen Sie die Skala setzen:
        setRange(0.0, 2.0 * Math.PI, -1.0, 1.0);

        // und dann können Sie die sx und sy aufrufen,
        // z.B. um x-Koordinatenachse zu zeichnen
        g.drawLine(sx(0.0), sy(0.0), sx(2.0 * Math.PI), sy(0.0));

        ... hier kommt die Schleife und wird gezeichnet ...
      }
    }

  4. Es fehlt uns nur noch ein Detail: um eine Linie zu zeichnen, brauchen wir zwei Punkte, d.h. wenn wir eine Linie bis zum neu ausgerechneten Punkt ziehen wollen, brauchen wir noch die Koordinaten des Punktes, an dem die letzte gezeichnete Linie geendet hat... Klingt kompliziert? Lassen wir zuerst die ausgewerteten Punkte zeichnen, einfach um zu sehen, ob und wie das Programm funktioniert!
    Ein ganz billiger Trick, um einen Punkt zu zeichnen, ist eine Linie von (t, y) bis (t, y) zu zeichnen:

          g.drawLine(sx(t), sy(y), sx(t), sy(y));

    - damit wird aber nur ein einziger Bildschirmpixel gezeichnet - wenn wir etwas Fetteres wollen, nimmt man besser die folgende:

          int d = 4;
          g.fillOval(sx(t) - d / 2, sy(y) - d / 2, d, d);

    wobei d der Durchmesser des Ovals in x- und y-Richtung ist. Natürlich können Sie diesen Wert ganz nach Ihrem Geschmack wählen - ein bißchen Experimentieren schadet nichts!

    So, versuchen Sie jetzt alle Zutaten in Ihrem Programm zu kombinieren, das Programm mit dem Compiler javac zu übersetzen und mit appletviewer auszuführen!

  5. Jetzt wollen wir doch noch die Punkte verbinden! Dazu mässen wir uns merken, wo die letzte gezeichnete Linie geendet hat - d.h. wir sollten uns die alten Werte von t und y merken. Dafür brauchen wir zwei weitere Variablen double t_old, y_old; , welche als 'Behälter' dienen:

        t_old = 0.0; y_old = Math.sin(t_old); // Anfangswerte initialisieren!
        ... schleife ueber t ... {
          ... neues y wird berechnet ...
          g.drawLine(sx(t_old), sy(y_old), sx(t), sy(y));
          t_old = t; y_old = y;
        }

    Bauen Sie noch dieses Stück Code in Ihr Programm ein und überzeugen Sie sich, dass Sie wirklich verstehen, welche Rolle t_old und y_old spielen! Hier sieht das wie eine Art technischer Trick aus, aber das ist genau das, was Sie bei den physikalischen Problemen brauchen werden, um die Bahn eines Körpers zu berechnen und zu zeichnen.

Hier geht es weiter ...