Mathematische Grafik zum Anfassen - Programmiert mit Matlab

Was ist Matlab

Matlab ist eine Mathematik-Software, deren Schwerpunkt das numerische Rechnen mit Matrizen und Vektoren ist. Der normale Gebrauch ist interaktiv, was bedeutet, dass man hinter einem Matlab-Prompt Matlab-Befehle absetzt. Hier ein kleines Beispiel:

>> X = 0:2:10
X =
    0 2 4 6 8 10

>> Y = sqrt(X)
Y =
    0 1.4142 2.0000 2.4495 2.8284 3.1623

Hier wird eine Besonderheit deutlich. Alle arithmetischen Operationen können in natürlicher Weise auch auf Listen von Zahlen (also Vektoren) angewandt werden. Dadurch läßt sich vieles sehr einfach formulieren. Zwei Dinge verstehen sich für ein anspruchsvolles Mathematik-Programm von selbst. Zum einen ist alles in eine Programmiersprache eingebettet, so dass man Aufgaben automatisieren kann und die Funktionalität des Programms nach eigenen Bedürfnissen erweitern kann. Das zweite ist die Möglichkeit, mathematische Dinge grafisch darzustellen.

>> plot(X,Y)

Die Möglichkeiten der Matlab-Graphik sind sehr weitreichend. Darauf wollen wir in diesem Artikel genauer eingehen. Wir stellen zwei Beispiele vor. Bei dem ersten Beispiel plazieren wir in einem Grafik-Fenster neben einem mathematischen Plot ein GUI-Element (GUI = Graphical-User-Interface). Das Betätigen des GUI-Elements, bei uns ist es ein Slider (Schieberegler), soll dann Auswirkungen auf die im Plot dargestellte Kurve haben. Bei dem zweiten Beispiel soll der Nutzer die Möglichkeit haben, mit der Maus direkt in das Plot-Fenster auf eine Kurve zu klicken, um auf die Grafik Einfluß zu nehmen.

Floating-Lizenzen für die Hochschule

Die Hochschule hat eine Reihe von Lizenzen für Matlab und das Zusatz-Programm Simulink erworben. Die Lizenzen werden durch einen zentralen Lizenz-Server im HRZ verwaltet. Wenn Sie Interesse haben, diese Lizenzen mit zu nutzen, wenden Sie sich im HRZ an Herrn Wald (Tel.: 2921, wald@hrz.uni-essen.de). Außerdem kann man Matlab auf den zentralen Unix-Servern des HRZ, in den PC-Pools des HRZ, sowie bereits in einigen PC-Pools der Fachbereiche nutzen.

Wie sind Matlab-Grafiken strukturiert

Betrachten wir das einfache Beispiel, das wir oben mit dem Befehl plot(X,Y) erzeugt haben. Matlab verwaltet nun eine Reihe von Grafikobjekten, die untereinander in einer Eltern-Kinder-Beziehung stehen. Arbeiten wir uns nun von den Kindern zu den Eltern, dann zu den Großeltern und schließlich den Urgroßeltern nach oben. Zunächst haben wir die Kurve, die vom Typ "line" ist. Sie ist "child" eines Objektes, das axes-Objekt heißt. Das ist der rechteckige Bereich, in dem z.B. die Linien gezeichnet werden. Zu diesem Objekt gehören auch das Koordinatenbezugssystem und die Achsen. Das axes-Objekt ist Kind eines figure-Objektes, welches das gesamte Grafik-Fenster repräsentiert. So ein figure-Objekt könnte auch mehrere axes-Objekte als Kinder enthalten, sowie diverse GUI-Objekte. Vater (oder Mutter, man hat hier auf jeden Fall eine alleinerziehende Kinderbetreuung) eines jeden figure-Objektes ist ein eindeutiges und immer existierendes root-Objekt. Die verschiedenen Objekte werden durch sogenannte Handle verwaltet. Das ist jeweils eine Zahl als Referenznummer für das Objekt, die bei der Erzeugung des Objektes zugewiesen wird. Das root-Objekt hat die Zahl 0 als Handle. Jedes Objekt hat eine Liste von Eigenschaften, die mit Hilfe der Handle und den Operationen set und get gesetzt und abgefragt werden können. Man muß dazu natürlich das Handle des Objektes kennen. Es ist deshalb ratsam, sich das Handle bei der Erzeugung eine Objektes geben zu lassen. Das geht so. Mit dem Befehl

>> myplot=plot(X,Y)

wird die Grafik erzeugt, und zusätzlich das Handle der erzeugten Linie der Variablen myplot zugewiesen. Matlab antwortet z.B.

myplot =
    1.0012

Diese Variable kann jetzt wie ein Name für das Objekt verwendet werden.

>> get(myplot,’type’)
ans =
line

>> get(myplot,’linewidth’)
ans =
    0.5000

>> set(myplot,’linewidth’,2)

Dieser letzte Befehl hat dann unmittelbar die Auswirkung, dass sich die Linienstärke der Kurve verändert. Eine Liste aller Attribute, die man für ein Objekt setzen kann, erhält man in diesem Fall durch

>> set(myplot)

Am besten sind die verschiedenen Attribute der verschiedenen Objekte in der HTML-Dokumentation beschrieben.

Das erste Beispiel

Wir erzeugen nun ein neues Fenster mit einer vorgegebenen Größe und plazieren darin ein axes-Objekt als Zeichenfeld.

>> figure('units','pixels',...
   'position',[100 100 400 450])

>> axes('units','pixels',...
   'position',[50 100 300 300],...
   'xlim',[-1 1],'ylim',[-1 1])

Als nächstes benötigen wir eine Linie. Die Y-Daten sollen später durch Interaktion immer wieder verändert werden. Damit dabei eine flüssige Bewegung entsteht, ist es günstig, den Erasemodus auf ‘background’ oder auf ‘xor’ zu setzen.

>> t=0:pi/100:2*pi;
>> a=2; b=3;
>> l=line(sin(a*t),cos(b*t),...
   'EraseMode','xor');

Schließlich bringen wir einen Schieberegler in das Fenster ein.

sli=uicontrol('Style','slider',...
   'Units','pixels',...
   'position',[50 20 300 30],...
   'Min',0,'Max',2*pi,...
   'Callback',...
'set(l,''YData'',cos(b*t+get(sli,''Value'')))'...
   ]);

Mit der Funktion uicontrol wird ein Userinterface-Objekt erzeugt und in das aktuelle Fenster eingefügt. Hier handelt es sich um einen "slider", dessen Position und Größe wir zunächst festlegen. Den Wertebereich des Schiebers legen wir von 0 bis 2*pi . Das Herzstück der Slider-Definition ist aber die Festlegung der Callback-Funktion. Hier wird diejenige Matlab-Aktion vereinbart, die jedesmal ausgeführt werden soll, wenn der Schieber betätigt wird. Dieser Matlab-Befehl muß hier allerdings als ein Textstring eingefügt werden. Hochkommata, die in diesem String vorkommen sollen, müssen maskiert werden. Das geschieht durch ein doppeltes Hochkomma.

Es ist nun wichtig, dass wir das Handle der Kurve, und das Handle des Schiebers haben. Mit get(sli,'Value') bekommen wir die aktuelle Stellung des Schiebers. Dieser Wert fließt dann ein, wenn als Callback-Aktion die Y-Daten der Kurve neu gesetzt werden.

Wir schreiben ein M-File

Um systematisch zu arbeiten, ist es ratsam, die obigen Kommandos in eine Datei zu schreiben. So ein Script nennt man bei Matlab ein M-File, weil die Dateiendungen .m seien soll. Das Kommando zum Ausführen eines M-Files ist einfach der Dateiname (ohne .m). Matlab verwaltet einen Suchpfad, also eine Liste von Verzeichnissen, in denen M-Files gesucht werden. Abfragen und Erweitern des Suchpfades erfolgen mit den Kommandos path und addpath . Eine besondere Art M-File ist einer, in dem eine Funktion definiert wird. Der Name der Funktion muß mit dem Dateinamen übereinstimmen. Hier ein kleines Beispiel. Die Datei cube.m habe folgenden Inhalt.

function res=cube(x)
res=x^3;

Die Verwendung innerhalb einer Matlab-Sitzung ist nun sehr einfach. Liegt die Datei im Suchpfad, kann die Funktion direkt aufgerufen werden.

>> cube(3)-cube(2)
ans =
    19

Auch unser nächstes Beispiel wollen wir als function-M-File realisieren.

Das zweite Beispiel

Wir wollen eine Kurve zeichnen, sagen wir eine Sinus-Kurve, dazu an einer Stelle x eine senkrechte Linie, und schließlich eine Tangente an der Kurve bei genau dieser Stelle x. Das sieht dann etwa so aus.

Das besondere soll nun sein, dass man mit der Maus auf die senkrechte Linie klicken können soll, um sie an eine andere Position zu ziehen. Die Tangente soll sich aber entsprechend mit bewegen. Eigentlich benötigen wir dazu vier verschiedene Funktionen, eine zum Initialisieren der Grafik und drei Callback-Funktionen. Wir realisieren das aber als eine einzige Funktion, indem wir der Funktion einen Parameter übergeben, der die Werte "init", "start", "move" und "stop" annehmen kann. Dadurch können wir alles in einen einzigen M-File schreiben. Wir listen jetzt den M-File example2.m in seiner Grundstruktur auf und erläutern danach die einzelnen Teile

function res=example2(action)
global fenster achse xposline ...
   tangente
if nargin == 0 action='init'; end
switch action
case 'init',
   do things
case 'start',
   do things
case 'move'
   do things
case 'stop'
   do things
end

Unser Beispiel wird nun ausgeführt, indem man in Matlab den Befehl

>> example2 'init'

oder einfach

>> example2

eingibt. Die Variable nargin gibt bei jeder Funktion an, wieviel Argumente wirklich übergeben wurden. Das erklärt die erste IF-Abfrage. Das nächste ist die Verzweigung in die vier Teile "init", "start", "move" und "stop". Da die Funktion immer wieder erneut aufgerufen wird, werden die Handle einiger Grafik-Objekte über globale Variablen übergeben.

case 'init'

Hier ist das, was zur Initialisierung der Grafik ausgeführt werden soll.

   fenster=figure;
   achse=axes('YLim',[-2 2],...
              'XLim',[0 2*pi]);
   t=0:pi/100:2*pi;
   line(t,sin(t));
   tangente=line(...
     [0 2*pi],[pi -pi],...
     'EraseMode','xor');
   xposline=line([pi pi],[-2 2],...
     'EraseMode','xor',...
     'ButtonDownFcn',...
        'example2(''start'')');

Die erste Linie ist die Sinus-Kurve. Sie wird jetzt einmal gezeichnet und bleibt sonst unberührt. Die nächsten beiden Linien verändern sich später immer wieder. Daher setzen wir hier den Erasemodus auf "xor". Die zweite der beiden ist eine Senkrechte an der Stelle Pi. Hierauf soll man schließlich "klicken" können, um die Animation zu starten. Daher vereinbaren wir, dass der Befehl example2('start') ausgeführt werden soll, wenn die Linie ein "Buttondown-Ereignis" empfängt.

case 'start'

Hier passiert nicht allzuviel. Es bedarf aber einer Erklärung.

set(fenster,...
  'WindowButtonMotionFcn',...
  'example2(''move'')')
set(fenster,...
  'WindowButtonUpFcn',...
  'example2(''stop'')')

Beim Bewegen der Maus wird der Mauscursor im Allgemeinen wieder von dem Objekt heruntergenommen, auf das ursprünglich geklickt wurde. Daher kann nur das Gesamtfenster die Bewegungssteuerung übernehmen. Das Ereignis "WindowButtonMotion" tritt immer auf, wenn sich die Maus über dem Fenster bewegt, egal ob eine Maustaste gedrückt ist, oder nicht. Daher wird erst jetzt der Befehl example2('move') als die Reaktion auf dieses Ereignis vereinbart. Damit die Bewegung, die wir mit "move" hier initiieren, auch wieder zum Stehen kommen kann, muß der Wert des Feldes 'WindowButtonMotionFcn' des Fensters irgendwann wieder auf den leeren String gesetzt werden. Der Befehl dazu steht unter "case ‘stop’". Dafür, dass er auch ausgeführt wird, sorgt hier die zweite Zeile: Wenn die Maustaste losgelassen wird, soll 'example2('stop') ausgeführt werden.

case 'move'

Dieser Teil wird während einer Bewegung immer wieder ausgeführt. Jedesmal wird aber nur einmal eine neue Cursor-Position abgefragt und in unserem Fall zwei Linien neu gezeichnet.

  currPt=get(...
    achse,'CurrentPoint');
  currX=currPt(1,1);
  set(xposline,'XData',...
    [currX currX]);
  set(tangente,'YData',...
    ([0 2*pi]-currX)*cos(currX)...
    + sin(currX));

Die Cursor-Position wird von dem axes-Objekt geholt, wodurch Sie in Problem-Koordinaten vorliegt. Wir benötigen hier aber nur die X-Position. Für das Versetzen der Senkrechten Linie an der X-Position muß nur dessen XData-Feld verändert werden. Die Tangente dagegen wird durch Neusetzen des Ydata-Feldes bewegt.
 
 

case 'stop'

Hier werden nur die Vereinbarungen, die unter "start" vorgenommen wurden, zurückgenommen.

  set(fenster,...
    'WindowButtonMotionFcn','')
  set(fenster,...
    'WindowButtonUpFcn','')
 
 

Die aktuellen Objekte

Wir haben unsere Beispiele möglichst einfach gehaltet, um die Grundprinzipien herauszuarbeiten. Aus dem gleichen Grund haben wir manches etwas ausführlicher formuliert, als es vielleicht notwendig gewesen wäre. Vereinfachungen ergeben sich z.B. daraus, dass immer ein Fenster das aktuelle Fenster ist, ein axes-Objekt das aktuelle axes-Objekt und schließlich ein weiteres Objekt (eine Linie, ein Text und anderes) das aktuelle Objekt ist. Es gibt die Kurzformen gcf, gca und gco für die Handle dieser Objekte. Sie bedeuten "get current figure", "get current axes" und "get current object". Für das Schreiben von Callback-Funktionen, also Funktionen , die bei GUI-Ereignissen ausgeführt werden sollen, sind dann noch gcbf (get callback figure) und gcbo (get callback object) sehr nützlich.

Ehrlicher Weise muß man an dieser Stelle zugeben, dass unsere Beispiele nicht ganz korrekt laufen, wenn man sie mehrmals aus Matlab heraus aufruft. Jeder Aufruf erzeugt ein neues Fenster, aber die globalen Variablen, in denen wir die Handle der einzelnen Objekte verwalten, existieren leider nur einmal.

Als Lösung bietet sich an, die Handle gcf, gca, gco, gcbf und gcbo gezielt einzusetzen. In unserem Beispiel ließe sich das Handle fenster durch gcbf abfragen, achse durch gca und xposline durch gco. Es bleibt aber das Handle tangente als Problem übrig. Man könnte es als weiteren Parameter bei dem Funktionsaufruf von example2 übergeben. Das sieht aber leichter aus als es ist, weil bei den Vereinbarungen der Callback-Funktionen das Handle als Wert eingetragen werden muß.

Leicht handhaben läßt sich aber eine andere Möglichkeit. Jedes Grafik-Objekt besitzt ein Feld "UserData". Wie der Name schon sagt, kann der Nutzer (Programmierer) hier Daten abspeichern, die keinen Einfluß auf das Grafik-Objekt haben. Jede Instanz eines Objektes hat natürlich seinen eigenen "UserData"-Bereich. Für unser Beispiel würden wir bei "init" das Handle für die Tangente in das Userdata-Feld des figure-Objektes stellen und bei "move" wieder auslesen. Die Befehle dazu heißen

  set(gcbf,’UserData’,tangente)
  tangente=get(gcbf,’UserData’)

Anerkennung

Bei der Einarbeitung in die Funktionalitäten der Matlab-Grafik war mir Kim Stebel, ein Schüler der Luisen-Schule, behilflich. Er ist im 10. Schuljahr und hat am HRZ ein Schülerpraktikum absolviert.

Burkhard Wald (( 2921)
wald@hrz.uni-essen.de