plot — Eine minimale C++-Plotting-Bibliothek

plot — Eine minimale C++-Plotting-Bibliothek

🌐 Read in English
📅 2026-06-28 ✏️ 2026-10-18 ✍️ Andreas Wittmann 👁️ ... c++ sdl2 graphics

plot ist eine kleine C++-Bibliothek, die SDL2 in eine minimale Zeichen-API verpackt. Sie verwaltet Fenster, Renderer und die Event-Schleife — du implementierst nur drei Callbacks.

Download

Datei Beschreibung
plot.hpp Header — in dein Programm einbinden
plot.cpp Implementierung — zusammen mit deinem Code kompilieren
vera_ttf.c Eingebettete Schriftart (wird von plot.cpp automatisch eingebunden)
demo.cpp Beispiel: animierte Sinuswelle mit Tastatursteuerung
plot_server.cpp Socket-Server, der plot über ein Textprotokoll bereitstellt (siehe unten)
Makefile Build-Datei für die Demo und den Socket-Server

Abhängigkeiten

SDL2, SDL2_ttf, SDL2_image. Unter Debian/Ubuntu:

apt install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev

Kompilieren

plot.cpp zusammen mit der eigenen Quelldatei kompilieren:

g++ -std=c++11 -o demo demo.cpp plot.cpp $(sdl2-config --cflags --libs) -lSDL2_ttf -lSDL2_image

Oder das mitgelieferte Makefile verwenden:

make

Um eine andere Quelldatei zu bauen:

make SRC=myprogram.cpp OUT=myprogram

Verwendung

plot.hpp einbinden und die Callbacks implementieren. Nur frame() ist erforderlich — alle anderen sind optional.

#include "plot.hpp"

void setup() {
    PlotCanvas(640, 480, PLOT_MODE_CROSS);
}

void frame() {
    PlotBackground(255, 255, 255);
    PlotColor(200, 0, 0);
    PlotLine(-100, 0, 100, 0);   // horizontale Linie durch den Ursprung
    PlotLine(0, -100, 0, 100);   // vertikale Linie durch den Ursprung
    PlotText(5, 5, "Frame: %d", g_current_frame);
}

bool event(SDL_Event* e) {
    return false; // true zurückgeben, wenn das Event behandelt wurde
}

Canvas-Modi

PlotCanvas(width, height, mode) richtet die Zeichenfläche ein. Der Modus bestimmt, wo der Ursprung liegt und in welche Richtung y zunimmt:

Modus-Konstante Ursprung Y-Richtung
PLOT_MODE_DEFAULT oben links nach unten
PLOT_MODE_CROSS Mitte nach oben
PLOT_MODE_MATH unten links nach oben

API-Referenz

// Setup
void PlotCanvas(int width, int height, int mode = PLOT_MODE_DEFAULT);
void PlotMode(int mode);

// Zeichnen
void PlotBackground(Uint8 r, Uint8 g, Uint8 b);
void PlotColor(Uint8 r, Uint8 g, Uint8 b);
void PlotColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a);
void PlotCross();                                        // zeichnet x/y-Achsen
void PlotPixel(double x, double y);
void PlotLine(double x1, double y1, double x2, double y2);
void PlotTriangle(PlotPoint a, PlotPoint b, PlotPoint c);
void PlotRectangle(double x1, double y1, double x2, double y2);
void PlotFilledRectangle(double x1, double y1, double x2, double y2);
void PlotText(double x, double y, const char* format, ...);  // printf-artig

// Timing
void PlotSetFPS(int fps);
void PlotSetFrameDurationMS(Uint32 ms);
void PlotSetFrameLimit(int frames);
uint32_t PlotGetTicks();

// Globaler Zustand (nur lesend)
extern int plot_width, plot_height;
extern uint64_t g_current_frame;

Alle Zeichenfunktionen akzeptieren außerdem PlotPoint-Structs (double x, y) als Alternative zu getrennten Koordinaten.

plot von außerhalb C++ ansteuern

Die API von plot.hpp ist eine direkte Funktionsaufruf-Schnittstelle: setup(), frame() und event() laufen im selben Prozess, angetrieben von plots eigener Main-Loop. Das funktioniert gut für ein C++-Programm, bedeutet aber, dass jede andere Sprache, die mit plot zeichnen möchte, diesen C++-Code direkt einbinden und aufrufen müsste.

plot_server.cpp verfolgt einen anderen Ansatz: Es linkt gegen dieselben plot.cpp/plot.hpp (mit plots eigenem main, das aus dem Weg benannt wurde) und verpackt das Canvas in einen langlebigen Prozess, der auf einem Unix-Domain-Socket lauscht, statt in frame() zurückzurufen. Jeder Prozess, der einen Socket öffnen und Textzeilen schreiben kann, kann das Canvas ansteuern — kein C++, kein Linken, kein Shared Memory.

Mit dem dafür vorgesehenen Makefile-Target bauen:

make plot-server

Starten, mit Socket-Pfad und Canvas-Größe:

./plot-server /tmp/plot.sock 800 600

Es gibt READY <path> aus, sobald der Socket Verbindungen annimmt.

Protokoll

Jede an den Socket gesendete Zeile ist ein Befehl; der Server antwortet mit einer einzelnen OK- oder ERR-Zeile pro Befehl.

Befehl Argumente Wirkung
CLEAR r g b PlotBackground(r, g, b)
COLOR r g b PlotColor(r, g, b)
COLORA r g b a PlotColor(r, g, b, a) — mit Alpha
PIXEL x y PlotPixel(x, y)
LINE x1 y1 x2 y2 PlotLine(x1, y1, x2, y2)
RECT x1 y1 x2 y2 PlotRectangle(x1, y1, x2, y2)
FILLRECT x1 y1 x2 y2 PlotFilledRectangle(x1, y1, x2, y2)
TRIANGLE x1 y1 x2 y2 x3 y3 PlotTriangle(...)
CROSS — PlotCross() — zeichnet x/y-Achsen
TEXT x y hex PlotText(x, y, "%s", text) — hex ist der UTF-8-Text, hex-kodiert (- für einen leeren String)
MODE 0, 1 oder 2 PlotMode(mode)
PRESENT — Spült den Frame ins Fenster
AUTOPRESENT 0 oder 1 Spült automatisch nach jedem Zeichenbefehl
QUIT — Fährt den Server herunter

Das Textargument von TEXT ist hex-kodiert statt als normale Wörter gesendet, weil das Protokoll zeilenbasiert ist — ein echtes Leerzeichen würde als weiteres Argument gelesen, ein echter Zeilenumbruch würde den Befehl vorzeitig beenden. Die UTF-8-Bytes als Hex zu kodieren hält beliebigen Text, auch mit Leerzeichen, auf einer einzigen Zeile.

Es wird nichts ins Fenster gezeichnet, bevor PRESENT gesendet wird — Zeichenbefehle aktualisieren nur einen internen Renderer-Zustand, genau wie sie es innerhalb eines einzelnen frame()-Aufrufs tun würden. Eine minimale Sitzung aus der Shell, über socat direkt mit dem Socket:

printf 'CLEAR 255 255 255\nCOLOR 0 0 0\nPIXEL 10 10\nPRESENT\nQUIT\n' \
    | socat - UNIX-CONNECT:/tmp/plot.sock

Jede Zeile bekommt ihr eigenes OK zurück, und das Fenster zeigt einen einzelnen schwarzen Pixel bei (10, 10) auf weißem Hintergrund, sobald PRESENT eintrifft.

AUTOPRESENT 1 dreht das um: Jeder Zeichenbefehl (alles außer COLOR, COLORA, MODE, PRESENT, AUTOPRESENT selbst und QUIT) spült danach automatisch, sodass eine interaktive Sitzung nicht nach jeder einzelnen Form ein explizites PRESENT braucht. Mit AUTOPRESENT 0 wieder abschalten, für eine Gruppe von Zeichenbefehlen, die gemeinsam erscheinen sollen.

Dieses Protokoll ist bewusst minimal gehalten — genug, damit ein externer, möglicherweise langlebiger Prozess (etwa ein REPL) eine dauerhafte Verbindung hält und interaktiv Zeichenbefehle sendet, ohne zwischendurch etwas neu zu kompilieren oder neu zu starten.

Ein Common-Lisp-Client existiert bereits

Ein kleiner Common-Lisp-Wrapper um dieses Protokoll liegt im Verzeichnis common-lisp/ neben dem Server — ein ASDF-System, das sich über sb-bsd-sockets verbindet, bei Bedarf automatisch plot-server startet, falls noch keiner läuft, und Lisp-Funktionen wie plot:pixel, plot:line sowie ein plot:with-frame-Makro bereitstellt:

Datei Beschreibung
package.lisp Package-Definition und Exporte
plot.lisp Der Client: Socket-Verbindung und plot:*-Funktionen
plot.asd ASDF-Systemdefinition

Das reicht aus, um das Canvas interaktiv aus einem SBCL-REPL heraus anzusteuern — eine völlig andere Art, diese Bibliothek zu verwenden, als alles oben Beschriebene. Für diesen C++-fokussierten Artikel ist das aber nicht das Thema — dafür gibt es einen eigenen Artikel.

Beispiel