5.3. 🚀 Visualisierung der Ergebnisse der Einstellungserkennung#
Dieses Jupyter Notebook erstellt aus den Ergebnissen der automatischen Einstellungserkennung eine interaktive Visualisierung zum Vergleich mehrerer Filme. Dargestellt werden
die Anzahl der erkannten Einstellungen,
verschiedene Kennwerte zur Einstellungslänge und
die Laufzeit der Filme.
Die Diagramme lassen sich nach den jeweiligen Werten, alphabetisch oder chronologisch sortieren.
Als Eingabedaten dienen die Metadaten-CSV-Dateien, die vom Notebook zur Einstellungserkennung erzeugt werden. Eine Erläuterung dieser Dateien bietet der OER-Abschnitt zur Ausgabe der Einstellungserkennung.
Als Ergebnis entstehen eine Darstellung direkt im Notebook und eine .html-Datei, die sich im Browser öffnen und in Websites einbetten lässt.
5.3.1. Ausführung des Notebooks#
In der Jupyter-Book-Version der OER sind die Ergebnisse der Ausführung bereits gespeichert. Sie können daher betrachtet werden, ohne den Code selbst auszuführen. Für ein tieferes Verständnis der Lerninhalte und der Funktionen des Codes und für die Bearbeitung der späteren Übungen empfehlen wir jedoch, das Notebook in einer Cloud-Umgebung oder lokal auszuführen.
Cloud-Umgebung: Klicken Sie oben rechts in der Menüleiste auf das Raketen-Symbol 🚀 und wählen Sie „Colab“, um das Notebook auszuführen. Eine GPU ist für dieses Notebook nicht erforderlich.
Lokale Umgebung: Klicken Sie oben rechts in der Menüleiste auf das Download-Symbol ↓ und wählen Sie „.ipynb“, um das Notebook herunterzuladen und lokal auszuführen. Abhängig von Browser und Betriebssystem müssen Sie die Datei gegebenenfalls per Rechtsklick herunterladen.
Wir empfehlen, die Codeblöcke einzeln und nacheinander auszuführen, statt Funktionen wie „Alle ausführen“ in Colab oder „Run All Cells“ in anderen Anwendungen zu verwenden. Die separate Ausführung der Codeblöcke erleichtert das Verständnis der damit verbundenen Funktionen und stellt sicher, dass alle vorbereitenden Schritte vor den nachfolgenden Codeblöcken ausgeführt wurden.
5.3.2. Festlegen der Parameter#
Zunächst werden die zentralen Einstellungen für den Import und die Darstellung der Daten festgelegt. Diese Parameter können angepasst werden, ohne den späteren Code verändern zu müssen.
CSV_PATH bezeichnet den Ordner mit den Metadaten-CSV-Dateien. Neben den Metadatendateien kann dieser Ordner auch andere Dateien und Unterordner enthalten. Die Unterordner werden ebenfalls durchsucht. Welche Dateien als Metadatendateien erkannt werden, legt CSV_PATTERN fest. Die Voreinstellung *metadata*.csv erfasst alle CSV-Dateien, deren Name an einer beliebigen Stelle das Wort metadata enthält. Sollten Sie Ihre Dateien umbenannt haben, können Sie das Muster entsprechend anpassen.
EXPORT_PATH legt fest, wo die fertige Visualisierung gespeichert wird. Sind die unter CSV_PATH und EXPORT_PATH angegebenen Ordner noch nicht vorhanden, werden sie automatisch erstellt.
Mit CHART_WIDTH wird die Breite der Visualisierungen in Pixeln festgelegt, ohne die Filmtitel links von den Balken. ROW_HEIGHT bestimmt die Höhe einer Filmzeile in Pixeln. Die Gesamthöhe eines Diagramms ergibt sich somit aus der Anzahl der dargestellten Filme.
DOWNLOAD_TESTFILES aktiviert bei Bedarf den Download des bereitgestellten Testmaterials. Alternativ kann mit CUSTOM_ZIP_LINK ein eigenes ZIP-Archiv heruntergeladen werden. Für einen ersten Durchlauf empfiehlt es sich, die vorgegebenen Einstellungen beizubehalten.
# Creating the input and export directories.
import os
# Path to the directory containing one metadata CSV per film.
# Relative paths are resolved from the location where the notebook is executed.
CSV_PATH = "vis_files_shots"
# Match filenames containing "metadata", including files in subfolders.
CSV_PATTERN = "*metadata*.csv"
# Path to the directory for the exported visualisation.
EXPORT_PATH = "export_vis_shots"
# Width of each plot in pixels, excluding the film labels on the left.
CHART_WIDTH = 500
# Height of each film row in pixels.
ROW_HEIGHT = 30
# If True, provided test metadata files are downloaded and extracted.
# Set this option to False when using your own files.
DOWNLOAD_TESTFILES = True
# Add your own download link inside quotes to load an additional ZIP archive.
CUSTOM_ZIP_LINK = "insert link here"
# An archive with the same name is reused instead of downloaded again.
CUSTOM_ARCHIVE_NAME = "custom_archive"
# Create both directories. exist_ok=True allows them to exist already.
os.makedirs(CSV_PATH, exist_ok=True)
os.makedirs(EXPORT_PATH, exist_ok=True)
5.3.3. Technische Vorbereitung#
Bevor die Visualisierung erstellt werden kann, werden die benötigten Programmbibliotheken installiert und importiert. Optional können außerdem Metadatendateien zu den Filmen von Konrad Wolf als Testmaterial heruntergeladen werden.
Installation der Programmbibliotheken#
Der folgende Codeblock installiert die Python-Bibliotheken pandas und Altair. Mit pandas lassen sich die Metadaten aus mehreren CSV-Dateien in einer gemeinsamen Tabelle zusammenführen und bearbeiten. Altair ist eine Python-Bibliothek zur Erstellung von Diagrammen aus tabellarischen Daten. Die eigentliche Visualisierung wird in einem späteren Schritt definiert.
Anders als die Notebooks zur Einstellungserkennung und Figurenerkennung benötigt dieses Notebook keine GPU-Beschleunigung. Da hier keine Videodateien, sondern lediglich CSV-Dateien mit Metadaten ausgewertet werden, wäre durch eine GPU keine wahrnehmbare Beschleunigung zu erwarten.
print("Installing dependencies.")
# Install all third-party dependencies once in the active notebook kernel.
!pip install -q pandas
!pip install altair==6.0.0
print("Dependencies installed.")
Installing dependencies.
Requirement already satisfied: altair==6.0.0 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (6.0.0)
Requirement already satisfied: jinja2 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from altair==6.0.0) (3.1.6)
Requirement already satisfied: jsonschema>=3.0 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from altair==6.0.0) (4.26.0)
Requirement already satisfied: narwhals>=1.27.1 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from altair==6.0.0) (2.27.1)
Requirement already satisfied: packaging in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from altair==6.0.0) (26.3)
Requirement already satisfied: typing-extensions>=4.12.0 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from altair==6.0.0) (4.16.0)
Requirement already satisfied: attrs>=22.2.0 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from jsonschema>=3.0->altair==6.0.0) (26.1.0)
Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from jsonschema>=3.0->altair==6.0.0) (2025.9.1)
Requirement already satisfied: referencing>=0.28.4 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from jsonschema>=3.0->altair==6.0.0) (0.37.0)
Requirement already satisfied: rpds-py>=0.25.0 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from jsonschema>=3.0->altair==6.0.0) (2026.9.1)
Requirement already satisfied: MarkupSafe>=2.0 in /Users/winter/Documents/MyCode/Quadriga/Bewegtes-Bild-Fallstudie-4/python3/lib/python3.11/site-packages (from jinja2->altair==6.0.0) (3.0.4)
Dependencies installed.
Import der Programmbibliotheken#
Durch den Import stehen die installierten Bibliotheken und weitere Python-Funktionen in der verwendeten Programmierumgebung und damit im Notebook zur Verfügung. Sie werden unter anderem für den Umgang mit Dateien, die Verarbeitung der Metadaten, die Erstellung der Diagramme und deren Anzeige im Notebook benötigt.
Das Notebook prüft außerdem, ob es in der Cloud-Umgebung Google Colab ausgeführt wird, um später bei Bedarf den Download der erzeugten Dateien als gebündeltes .zip-Archiv anbieten zu können.
# Handling file paths when reading metadata and exporting the visualisation.
from pathlib import Path
# Downloading files from a URL and extracting ZIP archives.
from urllib.request import urlretrieve
import zipfile
# Generating unique HTML IDs so separate chart outputs do not interfere.
from uuid import uuid4
# Packaging export files and identifying Google Colab.
import shutil
import sys
# Reading metadata tables and creating interactive charts.
import pandas as pd
import altair as alt
# Displaying the visualisation within the notebook.
from IPython.display import HTML, display
# Import the browser download function only within a Google Colab runtime.
colab = "google.colab" in sys.modules
if colab:
from google.colab import files
else:
print("Using non-Colab environment.")
print("Dependencies imported.")
Using non-Colab environment.
Dependencies imported.
Download des Analysematerials#
Die Visualisierung benötigt keine Videodateien, sondern ausschließlich die Metadaten der Einstellungserkennung. Das Notebook kann hierfür ein .zip-Archiv mit Testdateien herunterladen oder ein eigenes Archiv über die zuvor festgelegten Parameter laden. Die darin enthaltenen .csv-Dateien werden in CSV_PATH entpackt.
Bei den Testdateien handelt es sich um Einstellungsmetadaten zu allen Spielfilmen, bei denen Konrad Wolf Regie geführt hat. Wie aus den Metadatendateien hervorgeht, wurde bei der Einstellungserkennung ein Schwellenwert von 0.5 verwendet und die Verarbeitung auf einer CUDA-fähigen GPU durchgeführt.
Ist DOWNLOAD_TESTFILES = False eingestellt und kein eigener Download-Link unter CUSTOM_ZIP_LINK angegeben, findet kein Download statt. In diesem Fall müssen die Metadaten-CSV-Dateien bereits im Eingabeordner liegen. Ein bereits heruntergeladenes ZIP-Archiv wird bei erneutem Ausführen nicht noch einmal heruntergeladen.
# Define a reusable function for downloading and extracting ZIP archives.
def download_zip(url: str, archive_name: str, target_directory: str):
# Store the archive in the notebook's working directory.
archive_path = Path(f"{archive_name}.zip")
# Download only if an archive with the same name is not already present.
if not archive_path.is_file():
print(f"Downloading {archive_path}...")
urlretrieve(url, archive_path)
else:
print(f"{archive_path} already downloaded.")
# Extract the files into the selected metadata directory.
with zipfile.ZipFile(archive_path) as zip_ref:
zip_ref.extractall(target_directory)
print(f"Extracted {archive_path} to {target_directory}.")
# Download and extract the test metadata if the setting is enabled.
if DOWNLOAD_TESTFILES:
download_zip(
"https://owncloud.gwdg.de/index.php/s/21pPnALEiJ2O4Ar/download",
"shot_visualisation_testfiles", CSV_PATH,
)
else:
print("No testfile download requested.")
# Download and extract the custom archive if a link is provided.
if CUSTOM_ZIP_LINK != "insert link here":
download_zip(CUSTOM_ZIP_LINK, CUSTOM_ARCHIVE_NAME, CSV_PATH)
Downloading shot_visualisation_testfiles.zip...
Extracted shot_visualisation_testfiles.zip to vis_files_shots.
5.3.4. Einlesen der Metadaten und Erstellung der Visualisierung#
Im nächsten Schritt werden die Metadaten der einzelnen Filme zusammengeführt und für die Visualisierung vorbereitet. Anschließend erzeugt das Notebook aus dieser gemeinsamen Datengrundlage mehrere interaktive Balkendiagramme.
Import und Transformation der Metadaten#
Die Metadaten-CSV-Dateien speichern ihre Angaben als Schlüssel-Wert-Paare, also mit einem Metadatennamen und dem dazugehörigen Wert in jeder Zeile. Eine Datei kann beispielsweise so aussehen:
video_name,Goya_1971.mp4
title,Goya
release_year,1971
frame_count,192858
frame_height,470
frame_width,720
frame_rate,25.0
duration_hh_mm_ss,2:08:34
model,transnetv2
device,cuda
analysis_timestamp,2026-10-28T12:56:46+00:00
processing_time_hh_mm_ss,0:01:13
threshold,0.5
shot_count,1074
mean_duration_s,7.18
median_duration_s,3.76
max_duration_s,134.64
min_duration_s,0.32
min_prediction_score,0.0
max_prediction_score,1.0
Eine Erläuterung dieser einzelnen Datenpunkte finden Sie erneut im OER-Abschnitt zur Ausgabe der Einstellungserkennung
Für den Vergleich mehrerer Filme sucht das Notebook im Ordner CSV_PATH nach den durch CSV_PATTERN festgelegten Metadatendateien. Mit pd.read_csv() wird jede Datei zunächst als Folge von Metadatenbezeichnungen und zugehörigen Werten eingelesen. Anschließend werden die Ergebnisse in einem pandas-DataFrame namens films zusammengeführt. Ein DataFrame ist ein tabellarisches Datenformat. In der neuen Tabelle entspricht jede Zeile einem Film und jede Spalte einem Metadatum, beispielsweise shot_count oder mean_duration_s. Weitere Informationen zu DataFrames bietet dieses Kapitel des deutschen Python-Kurses.
Das Dictionary metrics legt fest, welche sechs Kennwerte später visualisiert werden und welche deutschen Titel die Diagramme erhalten. Ein Dictionary (kurz dict) ist ein Python-Datentyp, der Informationen in Schlüssel-Wert-Paaren speichert. Anders als die eingelesenen CSV-Dateien ist dieses Dictionary direkt im Python-Code definiert, Sie können die Titel der Diagramme also bei Bedarf direkt hier im Code ändern.
Weitere Informationen bietet dieses Dictionary-Kapitel eines deutschsprachigen Python-Kurses.
Mit metrics werden in diesem Fall die vier Kennwerte zur Einstellungslänge, die Filmlaufzeit und die Anzahl der erkannten Einstellungen ausgewählt. Die Reihenfolge der Einträge bestimmt zugleich die spätere Reihenfolge der sechs Diagramme. Das Dictionary wird außerdem bei der Datenaufbereitung durchlaufen, um die benötigten Metadatenspalten in Zahlen umzuwandeln. Eine Ausnahme bildet runtime_min: Dieser Wert steht nicht unmittelbar in der CSV-Datei, sondern wird erst aus duration_hh_mm_ss berechnet.
Damit Altair numerische Werte korrekt darstellen und sortieren kann, wandelt der Code das Erscheinungsjahr (release_year) in eine Ganzzahl (int) und die ausgewählten Kennwerte in Dezimalzahlen (float) um. Aus der Videolaufzeit im Format Stunden:Minuten:Sekunden wird mit pd.to_timedelta() zusätzlich die Laufzeit in Minuten (runtime_min) berechnet. Für die Sortierauswahl erhält jeder Film eine alphabetische Position (alpha_rank) und eine chronologische Position (year_rank). Schließlich entstehen für jeden Kennwert zusätzliche Spalten wie mean_duration_s_hover. Sie enthalten bereits formatierte Texte für die Anzeige beim Bewegen des Mauszeigers über einen Balken, einschließlich deutscher Zahlenformatierung und passender Einheit (Sekunden, Minuten oder Einstellungen).
Hinweis: Die CSV-Dateien müssen die im Code verwendeten Metadatenfelder enthalten. Das Erscheinungsjahr (release_year) wird für die chronologische Sortierung benötigt und muss als Zahl vorliegen.
# Initialise the list of metadata files.
metadata_files = []
# Find metadata files in CSV_PATH matching CSV_PATTERN.
for csv_path in Path(CSV_PATH).rglob(CSV_PATTERN):
# Check whether the path refers to a file.
if csv_path.is_file():
metadata_files.append(csv_path)
# Raise an error if no metadata files are found.
if not metadata_files:
raise FileNotFoundError(f"No files matching {CSV_PATTERN} in {CSV_PATH}.")
# Assign a German chart title to each metric. The order determines the chart order.
metrics = {
"mean_duration_s": "Durchschnittliche Einstellungslänge in Sekunden",
"median_duration_s": "Mediane Einstellungslänge in Sekunden",
"max_duration_s": "Längste Einstellung in Sekunden",
"min_duration_s": "Kürzeste Einstellung in Sekunden",
"runtime_min": "Filmlaufzeit in Minuten",
"shot_count": "Anzahl der Einstellungen",
}
# Read the metadata CSV files and store their values in a list.
# Initialise the list.
metadata_records = []
for csv_file in metadata_files:
# Read each metadata CSV file as a Series.
metadata = pd.read_csv(csv_file, header=None, index_col=0).iloc[:, 0]
# Add the metadata Series to the list.
metadata_records.append(metadata)
print(f"Loaded metadata for {metadata['title']} from {csv_file.name}.")
# Build a DataFrame with one row per metadata file.
films = pd.DataFrame(metadata_records)
# Convert the release year to an integer.
films["release_year"] = films["release_year"].astype(int)
# Convert the metric values to decimal numbers.
for metric in metrics:
# Runtime in minutes is calculated later, so skip it here.
if metric == "runtime_min":
continue
films[metric] = films[metric].astype(float)
# Store the alphabetical sort order:
# Create lowercase titles for sorting without considering capitalisation.
films["sort_title"] = films["title"].str.casefold()
# Sort the films alphabetically.
films = films.sort_values("sort_title")
# Replace the previous row numbers with consecutive numbers.
films = films.reset_index(drop=True)
# Store the alphabetical position of each film.
films["alpha_rank"] = films.index
# Store the chronological position, starting with the oldest film.
# For identical years, keep the alphabetical order established above.
films["year_rank"] = films["release_year"].rank(method="first")
# Convert the runtime to minutes for visualisation.
films["runtime_min"] = pd.to_timedelta(films["duration_hh_mm_ss"]).dt.total_seconds() / 60
# Prepare formatted hover values for each metric.
for metric in metrics:
# Initialise the list of hover values.
hover_values = []
for value in films[metric]:
# Display shot counts without decimal places.
if metric == "shot_count":
number = f"{int(value):,}"
else:
number = f"{value:,.2f}"
# Convert English number separators to German separators.
number = number.translate(str.maketrans(",.", ".,"))
# Add the appropriate unit.
if metric == "runtime_min":
number = number + " Minuten"
elif metric == "shot_count":
number = number + " Einstellungen"
else:
number = number + " Sekunden"
hover_values.append(number)
# Add the hover values to the film DataFrame.
films[f"{metric}_hover"] = hover_values
Loaded metadata for Sonnensucher from Sonnensucher_1958_shots_0.5_metadata.csv.
Loaded metadata for Professor Mamlock from Professor Mamlock_1961_shots_0.5_metadata.csv.
Loaded metadata for Leute mit Flügeln from Leute mit Flügeln_1960_shots_0.5_metadata.csv.
Loaded metadata for Genesung from Genesung_1956_shots_0.5_metadata.csv.
Loaded metadata for Solo Sunny from Solo Sunny_1980_shots_0.5_metadata.csv.
Loaded metadata for Der nackte Mann auf dem Sportplatz from Der nackte Mann auf dem Sportplatz_1974_shots_0.5_metadata.csv.
Loaded metadata for Einmal ist keinmal from Einmal ist keinmal_1955_shots_0.5_metadata.csv.
Loaded metadata for Mama ich lebe from Mama ich lebe_1976_shots_0.5_metadata.csv.
Loaded metadata for Der geteilte Himmel from Der geteilte Himmel_1964_shots_0.5_metadata.csv.
Loaded metadata for Goya from Goya_1971_shots_0.5_metadata.csv.
Loaded metadata for Der kleine Prinz from Der kleine Prinz_1966_shots_0.5_metadata.csv.
Loaded metadata for Lissy from Lissy_1957_shots_0.5_metadata.csv.
Loaded metadata for Ich war neunzehn from Ich war neunzehn_1968_shots_0.5_metadata.csv.
Loaded metadata for Sterne from Sterne_1959_shots_0.5_metadata.csv.
Erstellung der interaktiven Visualisierung#
Für die Erstellung der Diagramme wird die Python-Bibliothek Altair verwendet. Anstatt die einzelnen Balken und Achsen selbst durch Code zu zeichnen, beschreibt man, welche Daten dargestellt werden sollen, welche grafische Form sie erhalten und wie diese Form auf Eingaben reagieren soll. Altair erzeugt daraus interaktive Diagramme, die sich sowohl im Notebook als auch im Browser darstellen lassen.
Eine Einführung in diesen Ansatz bietet die offizielle Altair-Dokumentation zur Erstellung von Diagrammen.
Datengrundlage und Darstellung#
Zunächst wird ein weiteres Dictionary mit dem Namen embed_options definiert. Es wird später bei chart.to_html(embed_options=embed_options) verwendet und legt fest, wie die fertigen Altair-Diagramme im Notebook und in der exportierten HTML-Seite eingebettet und angezeigt werden:
"renderer": "svg"stellt die Diagramme als skalierbare Vektorgrafiken dar."actions": Falseblendet das zusätzliche Aktionsmenü von Altair aus."formatLocale"legt die deutsche Zahlenformatierung mit Komma als Dezimal- und Punkt als Tausendertrennzeichen (z.B. bei1.500) fest.
Der Begriff Embedding bedeutet hier also lediglich das Einbetten einer Visualisierung in HTML. Er ist nicht mit den Vektor-Embeddings aus der Figurenerkennung zu verwechseln.
Ausgangspunkt ist das zuvor erstellte pandas-DataFrame films, das mit alt.Chart(films) als Datengrundlage festgelegt wird. Anschließend bestimmt mark_bar(), dass die Werte durch Balken dargestellt werden. Die Methode .encode() verknüpft schließlich die Spalten der Tabelle mit Eigenschaften des Diagramms, den sogenannten Encodings.
Der Code erstellt nicht jedes Diagramm einzeln. Stattdessen durchläuft eine for-Schleife das während der Transformation der Metadaten erstellte Metriken-Dictionary metrics. Für jeden Kennwert wird so automatisch ein eigenes Diagramm erzeugt. Dabei werden die jeweilige Datenspalte (metric), die Überschrift und eine Farbe aus der Liste colors eingesetzt. Über .properties() werden außerdem die Titel und die Größe der Diagramme festgelegt. Die Größe kann mit den Parametern CHART_WIDTH und ROW_HEIGHT angepasst werden, siehe Festlegen der Parameter.
Sortierung und Interaktion#
Die Diagramme besitzen jeweils eine eigene Auswahl mit den Optionen „Werte“, „Alphabetisch“ und „Chronologisch“. Diese Auswahl wird über einen Altair-Parameter (alt.param) und daran gebundene Auswahlfelder (alt.binding_radio) erstellt. Die Sortierung (dynamic_sort) wird anschließend durch .transform_calculate() abhängig von dieser Auswahl bestimmt. Dafür verwendet der Code entweder den jeweiligen Kennwert oder die zuvor während der Transformation der Metadaten berechneten Positionen alpha_rank und year_rank.
Standardmäßig werden die meisten Kennwerte absteigend sortiert. Bei der kürzesten Einstellungslänge (min_duration_s) stehen dagegen die kleinsten Werte zuerst.
Eine zweite Interaktion entsteht beim Bewegen des Mauszeigers über die Balken. alt.selection_point() erkennt den aktuell berührten Balken. Mit alt.condition() erhält dieser eine höhere Deckkraft (opacity), während die übrigen Balken weniger deckend dargestellt werden. Zusätzlich zeigt ein Tooltip das Erscheinungsjahr und den zugehörigen Kennwert neben dem Mauszeiger an. Hierfür nutzt der Code die bereits im vorherigen Abschnitt vorbereiteten Spalten wie mean_duration_s_hover.
Zusammenstellung mit HTML und CSS#
Die sechs Diagramme sollen nicht getrennt ausgegeben werden, sondern als gemeinsame Visualisierung erscheinen. Dazu wird jedes Altair-Diagramm mit chart.to_html(fullhtml=False) in ein HTML-Fragment umgewandelt. HTML (Hypertext Markup Language) beschreibt die Struktur einer Webseite. Im vorliegenden Code erhält die Visualisierung einen gemeinsamen Container (shot-vis) mit einer Überschrift (shot-title). Jedes Diagramm wird in einem eigenen Bereich (shot-panel) untergebracht. Die HTML-Fragmente werden anschließend zum HTML-Text chart_html zusammengesetzt. Dadurch bleiben auch die Sortierfelder direkt beim jeweiligen Diagramm.
CSS (Cascading Style Sheets) bestimmt das Erscheinungsbild dieses HTML-Texts. In chart_styles werden Schriftgröße und Schriftgewicht der Überschrift, die Abstände zwischen den Diagrammen sowie die Anordnung der Sortierfelder festgelegt. Diese Werte können Sie an Ihre Präferenzen anpassen.
Abschließend zeigt display(HTML(chart_html)) die zusammengesetzte Visualisierung direkt im Notebook an.
# Use SVG for responsive sizing and German decimal and thousands separators.
# Hide the chart action menu in the notebook and the exported HTML file.
embed_options = {"actions": False, "renderer": "svg", "formatLocale": {
"decimal": ",", "thousands": ".", "grouping": [3],
}}
# Assign one colour per metric.
colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"]
# Initialise the list of charts.
charts = []
# Create one chart per metric directly from the film table.
for index, metric in enumerate(metrics):
# Get the chart title, colour, and formatted hover column.
chart_title = metrics[metric]
color = colors[index]
hover_column = f"{metric}_hover"
# Give every chart its own sorting control.
sort_name = f"sort_mode_{index}"
sort_mode = alt.param(
name=sort_name,
# Set the default value.
value="Werte",
# Set the sort options.
bind=alt.binding_radio(
options=["Werte", "Alphabetisch", "Chronologisch"],
name="Sortierung: ",
),
)
# Define the hovering condition: highlight a film under the pointer.
hovered_film = alt.selection_point(
fields=["video_name"],
on="pointerover",
clear="pointerout",
empty=False,
)
# Sort minimum shot duration values in ascending order and other values in descending order.
if metric == "min_duration_s":
value_sort = f"datum.{metric}"
else:
value_sort = f"-datum.{metric}"
# Create the base chart from the films DataFrame.
base = alt.Chart(films).transform_calculate(
# Switch between value, alphabetical, and chronological sorting.
dynamic_sort=(
f"{sort_name} === 'Alphabetisch' ? datum.alpha_rank "
f": {sort_name} === 'Chronologisch' ? datum.year_rank : {value_sort}"
)
# Define the properties of the chart.
).encode(
# Define the horizontal x-axis of the chart.
x=alt.X(
# Use the metric as quantitative ('Q') values.
f"{metric}:Q",
# Do not use an axis title.
title=None,
# Set the axis maximum to the largest metric value.
scale=alt.Scale(domainMax=float(films[metric].max()), nice=False),
),
# Define the vertical y-axis of the chart.
y=alt.Y(
# Use film titles as categories. N means nominal (categorical).
"title:N",
# Do not use an axis title.
title=None,
# Order the films using the calculated dynamic_sort field.
sort=alt.EncodingSortField("dynamic_sort"),
),
# Define the information shown when hovering over a bar.
tooltip=[
# Read the release year from the film table.
# Use "Erscheinungsjahr" as the label in the tooltip.
alt.Tooltip("release_year:N", title="Erscheinungsjahr"),
# Read the formatted text from this metric's hover column.
# Use "Wert" as the label in the tooltip.
alt.Tooltip(f"{hover_column}:N", title="Wert"),
],
).properties(
# Set the chart width using the CHART_WIDTH parameter.
width=CHART_WIDTH,
# Set the height of each film row using ROW_HEIGHT.
height=alt.Step(ROW_HEIGHT),
)
# Set the color of the bars.
bars = base.mark_bar(color=color).encode(
# Increase the opacity when hovering over the bar.
opacity=alt.condition(hovered_film, alt.value(1), alt.value(0.6)),
)
# Attach this chart's independent sorting and hover interactions.
chart = bars.add_params(
sort_mode, hovered_film,
).properties(
# Add the left-aligned (anchor="start") title.
title=alt.TitleParams(chart_title, anchor="start"),
)
charts.append(chart)
# Use a unique identifier to keep repeated notebook outputs separate.
chart_output_id = f"shots-{uuid4().hex}"
# Use CSS rules to style the visualisation for notebook and Jupyter Book display.
chart_styles = """<style>
/* Emphasise the overall heading. */
.shot-vis .shot-title {
font-size: 17px;
font-weight: bold;
margin-bottom: 18px;
}
/* Leave space between charts. */
.shot-vis .shot-panel {
margin-bottom: 28px;
}
/* Place the sorting controls below each chart.
Override Altair's default layout with !important. */
.shot-vis .vega-embed {
display: block !important;
width: 100%;
padding: 0;
}
</style>"""
# Start with the CSS rules for the visualisation.
chart_html = chart_styles
# Open the container holding all charts.
chart_html += f'<div id="{chart_output_id}" class="shot-vis">'
# Add the overall heading.
chart_html += '<div class="shot-title">Einstellungslängen und Einstellungsanzahl im Filmvergleich</div>'
# Convert each chart to HTML.
for index, chart in enumerate(charts):
chart_content = chart.to_html(
# Create an HTML fragment rather than a complete webpage.
fullhtml=False,
# Use Altair's loader for notebook and webpage display.
template="universal",
# Give each chart a unique HTML identifier.
output_div=f"{chart_output_id}-{index}",
# Apply the display settings defined earlier.
embed_options=embed_options,
)
# Place the chart inside its own panel for spacing.
chart_html += '<div class="shot-panel">'
chart_html += chart_content
chart_html += '</div>'
# Close the container holding all charts.
chart_html += '</div>'
# Display the assembled HTML in the notebook.
display(HTML(chart_html))
5.3.5. Export der Ergebnisse#
Die interaktive Visualisierung wird anschließend als HTML-Datei gespeichert. Dadurch kann sie auch außerhalb des Jupyter Notebooks in einem Webbrowser betrachtet und weitergegeben werden. Die zuvor eingelesenen Metadaten-CSV-Dateien bleiben dabei unverändert.
Export der Visualisierung als .html-Datei#
Der folgende Codeblock speichert die zuvor erstellte Visualisierung unter dem Namen shot_meta.html im Exportordner (EXPORT_PATH). Da die sechs Altair-Diagramme einzeln eingebettet und mit CSS angeordnet werden, wird für den Export eine vollständige HTML-Seite um die bereits zusammengestellte Visualisierung erstellt.
Durch export_directory / "shot_meta.html" wird der Speicherort der Datei festgelegt. Die Variable chart_html enthält bereits die sechs Diagramme, die gemeinsame Überschrift und die CSS-Regeln aus dem vorherigen Codeblock.
Anschließend erstellt der Code mit html_document = f"""...""" den Quelltext einer vollständigen HTML-Seite. Das vorangestellte f kennzeichnet einen f-String, in den der Inhalt der Variablen {chart_html} eingesetzt wird. Die HTML-Struktur ergänzt die Informationen, die für eine eigenständige Webseite erforderlich sind:
<!DOCTYPE html>und<html lang="de">kennzeichnen das Dokument als HTML-Seite mit deutscher Spracheinstellung.Im
<head>werden die UTF-8-Zeichenkodierung, die Anpassung an die Fensterbreite und der Titel des Browser-Tabs festgelegt.Im
<body>wird die zuvor erzeugte Visualisierung eingefügt.style="margin:0"entfernt den standardmäßigen Seitenrand des Browsers.
Zum Schluss schreibt output_html.write_text(html_document, encoding="utf-8") den vollständigen HTML-Text in die Datei. Die UTF-8-Kodierung stellt sicher, dass beispielsweise Umlaute korrekt gespeichert werden. Beim erneuten Ausführen wird eine gleichnamige Datei im Exportordner überschrieben.
Die HTML-Datei kann anschließend im Browser geöffnet werden. Die Sortierfelder und Informationen beim Überfahren der Balken bleiben erhalten.
Hinweis: Zum Laden der eingebundenen JavaScript-Bibliotheken wird beim Öffnen der HTML-Datei normalerweise eine Internetverbindung benötigt.
# Convert the export folder setting into a path.
export_directory = Path(EXPORT_PATH)
# Combine the folder path and filename.
output_html = export_directory / "shot_meta.html"
# Build a complete HTML page containing the visualisation.
# The f-string inserts the chart_html created earlier.
html_document = f"""<!DOCTYPE html>
<html lang="de">
<head>
<!-- Use UTF-8 to display German characters correctly. -->
<meta charset="UTF-8">
<!-- Adapt the page to the width of the browser window. -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Set the title shown in the browser tab. -->
<title>Einstellungsmetadaten</title>
</head>
<!-- Remove the browser's default margin around the page content. -->
<body style="margin:0">
{chart_html}
</body>
</html>
"""
# Save the page as an HTML file.
# UTF-8 ensures German characters are written correctly.
output_html.write_text(html_document, encoding="utf-8")
# Show where the file was saved.
print(f"Saved HTML visualisation to: {output_html}")
Saved HTML visualisation to: export_vis_shots/shot_meta.html
Herunterladen des Google-Colab-Exportordners#
Wird das Notebook in Google Colab ausgeführt, kann der gesamte Exportordner als ZIP-Archiv heruntergeladen werden. So bleibt die erstellte Visualisierung auch nach dem Ende der Colab-Sitzung verfügbar.
Bei einer lokalen Ausführung ist dieser zusätzliche Schritt nicht erforderlich. Die HTML-Datei befindet sich bereits im zuvor festgelegten Exportordner.
# Package the export directory as a ZIP archive and download it in Google Colab.
# Local users can open the HTML file directly from the export directory.
if colab:
archive_base = export_directory.parent / f"{export_directory.name}_ergebnisse"
zip_path = shutil.make_archive(str(archive_base), "zip", root_dir=str(export_directory))
files.download(zip_path)
print(f"Downloaded {Path(zip_path).name}.")
else:
print("An additional download is only required in Google Colab.")
An additional download is only required in Google Colab.