3D Graphiken

3D Graphiken#

In den bisherigen Kapiteln haben Sie gelernt, wie man zweidimensionale Graphiken mit \(\texttt{plt.plot}\) erzeugt – also Kurven, bei denen zwei Vektoren die \(x\)- und \(y\)-Werte der Datenpunkte angeben. Solche Darstellungen eignen sich um Zusammenhänge zwischen zwei Größen zu visualisieren. Doch in vielen numerischen Anwendungen interessiert man sich für Funktionen, die nicht nur von einer, sondern von zwei Variablen abhängen – etwa Temperaturverteilungen auf einer Fläche, Höhenprofile eines Geländes oder Fehlerfunktionen in mehreren Dimensionen.

Für solche Szenarien benötigen wir eine Möglichkeit, Funktionen in drei Dimensionen zu visualisieren. Dabei wird üblicherweise ein Gitter von \((x,y)\)-Koordinaten erzeugt, auf dem eine dritte Größe \(z=f(x,y)\) als Höhe dargestellt wird. Diese Art der Darstellung nennt man eine Oberfläche oder im Englischen surface.

Oberflächen erzeugen#

Eine besonders einfache und intuitive Möglichkeit, eine Oberfläche zu erzeugen, bietet die Funktion \(\texttt{plt.plot_surface(X,Y,Z)}\). Dabei sind \(X\) und \(Y\) Vektoren, die die Koordinatenpunkte im \(x\)-\(y\)-Raum definieren, und \(Z\) ist eine Matrix, die die zugehörigen Höhenwerte (also die \(z\)-Koordinaten) an diesen Punkten angibt. Die Werte \(Z_{i,j}\) in \(Z\) entsprechen dabei den Höhen an den jeweiligen Positionen \((X_i,Y_j)\).

Ein Beispiel ist die Funktion \(f(x,y)=\sin⁡(x) \cdot \cos⁡(y)\), die eine wellenartige Oberfläche erzeugt. Sie können diese Funktion auf einem Gitter auswerten und die resultierende Matrix mit \(\texttt{plt.plot_surface}\) darstellen:

import numpy as np
import matplotlib.pyplot as plt

# Wertebereiche für x und y festlegen
x = np.linspace(- np.pi, 2 * np.pi, 100)
y = np.linspace(- np.pi, 2 * np.pi, 100)

# Ein Gitter aus x- und y-Werten erzeugen
X, Y = np.meshgrid(x, y)

# Z-Werte berechnen: Z = sin(x) * cos(y)
Z = np.sin(X) * np.cos(Y)

# Neue Abbildung und 3D-Achsen erzeugen
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# - 111 bedeutet: 1 Zeile, 1 Spalte, 1. (und einzige) Position
# - projection='3d' aktiviert die 3D-Darstellung

# Oberfläche plotten
ax.plot_surface(X, Y, Z) 

ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z = sin(x) * cos(y)")

plt.show()

Folgendes müssen Sie beim Erstellen von Oberflächenplots beachten:

  • Die Vektoren \(x\) und \(y\) müssen mit \(\texttt{np.meshgrid}\) zu einem Gitter \((X, Y)\) kombiniert werden.

  • Die Matrix \(Z\) hat die gleiche Form wie \(X\) und \(Y\) und enthält die Höhenwerte.

  • Der Befehl \(\texttt{plot_surface(X, Y, Z)}\) wird für die Darstellung verwendet.

  • Vor dem Plotten muss eine neue Figur mit 3D-Achsen erstellt werden, dabei erzeugt \(\texttt{fig = plt.figure()}\) eine neue leere Grafik und \(\texttt{fig.add_subplot(111, projection='3d')}\) fügt ein 3D-Achsensystem hinzu.

Um 3D-Oberflächenplots besser zu verstehen, verwenden wir in diesem Abschnitt ein Beispiel mit (leider fiktiven) Messdaten, der die wöchentlichen Umsätze (in Euro) des Café Soleil, aufgeteilt nach Wochentagen und Tageszeiten, enthält.

Das Café Soleil hat an jedem Tag fünf feste Zeitfenster: 8:00 – 10:00 Uhr, 10:00 – 12:00 Uhr, 12:00 – 14:00 Uhr, 14:00 – 15:30 Uhr und 15:30 – 17:30 Uhr. Am Samstag und Sonntag bleibt das Café geschlossen. Die Umsatzdaten des Café Soleil für die einzelnen Tage und Zeitfenster lassen sich wie folgt zusammenfassen:

Tag

8:00 - 10:00

10:00 - 12:00

12:00 - 14:00

14:00 - 15:30

15:30 - 17:30

Montag

120

180

250

200

160

Dienstag

100

150

300

230

180

Mittwoch

90

170

280

220

190

Donnerstag

130

160

270

210

170

Freitag

140

190

310

260

0

Um diese Daten in Python zu analysieren und zu visualisieren, speichern wir sie in einem NumPy Array namens \(\texttt{revenue}\). Der zugehörige Code lautet:

revenue = np.array([
    # 8-10  10-12  12-14  14-15:30  15:30-17:30
    [120,   180,   250,   200,      160],  # Montag
    [100,   150,   300,   230,      180],  # Dienstag
    [ 90,   170,   280,   220,      190],  # Mittwoch
    [130,   160,   270,   210,      170],  # Donnerstag
    [140,   190,   310,   260,      0],  # Freitag
])

Aufgabe 1.1

Erstellen Sie ein 3D-Surface-Plot, der die Umsätze des Café Soleil in Abhängigkeit der Wochentage (Zeilen) und der verschiedenen Zeitfenster (Spalten) visualisiert.

# Achsen vorbereiten
days = np.arange(revenue.shape[0])
times = np.arange(revenue.shape[1])
Days, Times = np.meshgrid(times, days)

Wenn Sie bei einem Plot statt numerischer Werte aussagekräftige Beschriftungen wie zum Beipsiel Wochentage oder Uhrzeiten verwenden möchten, können Sie die Achsenticks und die zugehörigen Labels manuell setzen.

Dazu stehen die Methoden \(\texttt{set_xticks()}\) und \(\texttt{set_xticklabels()}\), und analog \(\texttt{set_yticks()}\) und \(\texttt{set_yticklabels()}\), zur Verfügung:

ax.set_xticks([0, 1, 2, 3])   # Positionen auf der x-Achse
ax.set_xticklabels(["Beschriftung 1", 
                    "Beschriftung 2", 
                    "Beschriftung 3", 
                    "Beschriftung 4"])  # Beschriftung dieser Positionen

Diese Befehle geben an:

  • wo sich die Ticks befinden sollen,

  • wie diese beschriftet werden.

Das ist besonders hilfreich bei 3D-Oberflächenplots, wenn Sie die Achsen nicht nur mit Zahlen, sondern mit realitätsnahen Kategorien beschriften möchten.

Aufgabe 1.2

Passen Sie den Plot aus Aufgabe 1.1 so an, dass an den Achsen nicht nur Zahlen, sondern die tatsächlichen Bezeichnungen für Wochentage und Tageszeiten erscheinen.

# Ihr Code 

Auch bei 3D-Oberflächenplots lassen sich zahlreiche Anpassungen vornehmen. Dazu gehören unter anderem die Farbgebung, Transparenz oder die Dichte des Gitters. Die folgende Tabelle fasst die wichtigsten Parameter zusammen, mit denen sich das Aussehen von Oberflächenplots über \(\texttt{plt.plot_surface()}\) gezielt steuern lässt:

Eigenschaft

Beschreibung

Beispiel

\(\texttt{cmap}\)

Farbschema der Oberfläche

\(\texttt{cmap='viridis'}\)

\(\texttt{rstride}\) / \(\texttt{cstride}\)

Schrittweite in Zeilen- bzw. Spaltenrichtung (Rasterdichte)

\(\texttt{rstride=2, cstride=2}\)

\(\texttt{linewidth}\)

Dicke der Gitterlinien

\(\texttt{linewidth=0.5}\)

\(\texttt{antialiased}\)

Kantenglättung für weichere Übergänge

\(\texttt{antialiased=True}\)

\(\texttt{alpha}\)

Transparenz der Oberfläche (zwischen 0 und 1)

\(\texttt{alpha=0.8}\)

x = np.linspace(- np.pi, 2 * np.pi, 100)
y = np.linspace(- np.pi, 2 * np.pi, 100)

X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# Eigenschaften des 3D Plots anpassen
ax.plot_surface(X, Y, Z, cmap="coolwarm", alpha=0.9, linewidth=0, antialiased=True)

ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z = sin(x) * cos(y)")

plt.show()

Aufgabe 1.3

Nehmen Sie folgende Anpassungen für den bestehenden 3D-Surface-Plot vor:

Farbverlauf

\(\texttt{"plasma"}\)

Transparenz

\(0.8\)

Gitterliniendicke

\(0.5\)

Kantenglättung

\(\texttt{True}\)

wochentage = ["Mo", "Di", "Mi", "Do", "Fr"]
zeitfenster = ["8–10", "10–12", "12–14", "14–15:30", "15:30–17:30"]

x = np.arange(revenue.shape[1])  # Zeitfenster
y = np.arange(revenue.shape[0])  # Wochentage
X, Y = np.meshgrid(x, y)

# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, revenue)  

# Achsenbeschriftungen setzen
ax.set_xticks(x)
ax.set_xticklabels(zeitfenster)
ax.set_yticks(y)
ax.set_yticklabels(wochentage)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen (EUR)")

plt.show()

Neben \(\texttt{plt.plot_surface}\) bietet Matplotlib noch weitere Möglichkeiten, 3D-Oberflächen visuell darzustellen. Eine davon ist \(\texttt{plt.plot_wireframe}\), welche nur die Gitterlinien ohne Flächenfüllung zeichnet. Die Darstellung ist nützlich, wenn man die Struktur betonen möchte. Die Funktionen lässt sich analog zu \(\texttt{plt.plot_surface}\) verwenden.

Aufgabe 1.4

Erstellen Sie ein 3D-Wireframe-Plot, der die Umsätze des Café Soleil visualisiert.

# Ihr Code 

In 3D-Plots kann man die Perspektive ändern, um die räumliche Struktur der dargestellten Oberfläche gut zu erkennen. Mit der Methode \(\texttt{ax.view_init(elev, azim)}\) wird der Blickwinkel auf die Grafik gezielt eingestellt:

  • \(\texttt{elev}\) (engl. elevation) bestimmt die Höhe des Blicks über der \(x\)-\(y\)-Ebene (vertikaler Winkel).

  • \(\texttt{azim}\) (azimut) bestimmt die Drehung um die \(z\)-Achse (horizontaler Winkel).

x = np.linspace(- np.pi, 2 * np.pi, 100)
y = np.linspace(- np.pi, 2 * np.pi, 100)

X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z) 

ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z = sin(x) * cos(y)")

# Perspektive anpassen
ax.view_init(elev=90, azim=50)

plt.show()

Aufgabe 1.5

Erweitern Sie den Plot so, dass die Perspektive mit einer Elevation von 30° und einem Azimut von 45° eingestellt wird.

x = np.arange(revenue.shape[1])  # Zeitfenster
y = np.arange(revenue.shape[0])  # Wochentage
X, Y = np.meshgrid(x, y)

# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, revenue)

ax.set_xticks(x)
ax.set_xticklabels(zeitfenster)
ax.set_yticks(y)
ax.set_yticklabels(wochentage)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen (EUR)")

plt.show()