plot — Eine minimale C++-Plotting-Bibliothek
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.