Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Narzędzia dla programistów C++

[C++] Programistyczne ustawianie wykresów dla programu Gnuplot

[artykuł] Artykuł opisuje sposób obsługi programu Gnuplot z poziomu języka C++.

Intro - co, po co, dlaczego?

Zdecydowałem się na napisanie tego artykułu, gdyż bardzo często brakuje mi opisów narzędzia do rysowania wykresów takiego jak Gnuplot w języku polskim. Wielu rzeczy brakuje, wg mnie, nawet w angielskich stronach, a wiele jest niepotrzebnych informacji, w złej do nauki kolejności. Nieraz potrzebowałem artykułu opisującego ważne, podstawowe funkcje tego programu oraz oczywiście jak z nich korzystać przy użyciu języków programowania takich jak C++ generującego zarówno dane do narysowania, jak i pewne polecenia konfigurujące wykres. Innymi słowy chcę napisać artykuł którego mi brakowało na pierwszych latach studiów i którego mi nadal brakuje. Myślę że komuś się może przydać.

Gnuplot -co to jest, po co jest

Gnuplot jest super programem do rysowania wszelakich wykresów 2D i 3D, z olbrzymimi możliwościami ustawienia wykresów rysunków końcowych (w tym artykule skupię się na wykresach 2D, które się rysuje zdecydowanie częściej). Co więcej jest open-sourcowy i wieloplatformowy, dzięki czemu już na początku go lubimy. Łatwo jest coś narysować, gorzej jak chcemy trochę poustawiać nasz wykres.
Na początek strona główna Gnuplota na której można pobrać program, oraz pobuszować szukając przykładów.

Podstawy podstaw Gnuplota

Zacznę od najważniejszych rzeczy o obsłudze programu.

Jak coś narysować

Wchodzimy do programu pisząc: gnuplot, po wejściu możemy ustawić co chcemy, a potem narysować komendą plot (dla wykresów 2D), lub splot (dla wykresów 3D) jakieś funkcje, lub dane z pliku/-ów. Aby rysować z pliku, wystarczy napisać: plot 'dane.dat' (nazwę pliku piszemy cudzysłowiu). Aby wyjść wystarczy wpisać standardowy exit, a jak ktoś chce szybciej można również wyjść pisząc q.

Wbudowane funkcje i operatory

Nie ma na razie sensu na niepotrzebne generowanie danych służących do rysunków jak mamy pewne funkcje matematyczne wbudowane w Gnuplota, za pomocą których możemy rysować plot funkcja(x), jeśli chcemy zmodyfikować możemy naszego x zapisać w ciekawszy sposób (np. plot funkcja1(funkcja2(3*x+8)-funkcja3(x/3))).

Funkcje wbudowane w program Gnuplot:
znane i używane
      sin(x)            sinus X
      cos(x)            cosinus X
      tan(x)            tangens X
      exp(x)            exponenta (czyli e do potęgi x) X
      log(x)            logarytm naturalny (czyli o podstawie e) X
      log10(x)          logarytm o podstawie 10 z X
      sqrt(x)           pierwiastek kwadratowy X
      sgn(x)            signum X, zwany znakiem liczby X
zaokrąglanie
      abs(x)            wartość absolutna (bez ewentualnego -) X
      ceil(x)           zaokrąglenie w górę
      floor(x)          zaokrąglenie w dół
      int(x)            część całkowita z liczby X
funkcje czasami znane
      acos(x)           arcus cosinus X
      asin(x)           arcus sinus   X
      atan(x)           arcus tangens X
      cosh(x)           cosinus hiperboliczby X
      erf(x)            funkcja błędu X
      inverf(x)         funkcja odwrotna do błędu X
      norm(x)           dystrybuanta rozkładu normalnego dla X
      rand(x)           pseudolosowa wartość inicjowana X     
      sinh(x)           sinus hiperboliczny X
      tanh(x)           tangens hiperboliczny X
inne funkcje
      invnorm(x)        "inverse normal distribution of X"
      lgamma(x)         "any- lgamma function of real(x)"
      gamma(x)          "any- gamma function of real(x)"
      ibeta(p,q,x)      "any- ibeta function of real(p,q,x)"
      igamma(a,x)       "any- igamma function of real(a,x)"
      imag(x)           "complex- imaginary part of x as a real number"
      besj0(x)          "radians- j_0 Bessel function of x"
      besj1(x)          "radians- j_1 Bessel function of x"
      besy0(x)          "radians- y_0 Bessel function of x"
      besy1(x)          "radians- y_1 Bessel function of x"
Do kompletu umieszczam również spis operatorów:

*,/,+,-   wiadomo
a**b   podniesienie a do potegi b
a%b       operacja modulo
a==b,a!=b porównywanie

a && b    logiczne AND
a || b    logiczne OR
a & b     bitowe AND
a | b     bitowe OR
a ^ b     bitowe wykluczające OR (XOR)

a?b:c *   operator if/else taki jak w C++

-a        wartość przeciwna do a
!a        negacja a
a!        silnia z a

Przykład na animacje wykresu

Doprecyzuję - pisząc ANIMACJĘ, mam na myśli rysowanie raz za razem wykresu pewnej funkcji przy zmianie parametrów przez co obserwujemy jak się ona zmienia. Jest to przykład działający na linuxie z wykorzystaniem potoków, napisany w C:
C/C++
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<unistd.h> /* do funkcji popen() */

FILE * otworzPotokGnuplota() {
    FILE * g_potok = popen( "gnuplot", "w" ); /* otwarcie potoku do zapisu */
    return g_potok;
}

void funkcja( FILE * potok, char * jaka, char * a, char * b ) {
    fprintf( potok, "plot %s(%s*x+%s)\n", jaka, a, b ); // y=f(a*x+b)
}

int main() {
    FILE * gnupotok = otworzPotokGnuplota();
    double i;
    char buf[ 5 ];
   
    for( i = 0; i < 10; i += 0.1 ) {
        sprintf( buf, "%f", i ); /* konwersja double ->char* */
        funkcja( gnupotok, "sin", buf, "3" ); /** nasza funkcja f(a*x+b) */
        fflush( gnupotok ); /* czyszczenie bufora zapisu */
        usleep( 10000 ); /* czekanie w milisekundach */
    }
    pclose( gnupotok );
    return 0;
} /* gcc p.c && ./a.out */

Rysowanie "wsadowe", ustawianie wykresów, rysowanie

Powyższy sposób rysowania z naszej aplikacji okazuje się niewygodny, gdyż kod jest nieprzenośny, poza tym nie zawsze chcemy od razu wygenerować wykres, czasami chcemy to tylko ustawić i wysłać komuś kod żeby sobie sam narysował.
Rozwiązaniem jest coś co nazwałem "rysowaniem wsadowym", dzięki któremu mając wszystkie polecenia rysowania w pliku możemy bardzo łatwo narysować podając plik z poleceniami jako argument gnuplota: gnuplot 'dane.g' taką komendę możemy bez problemu wywołać z użyciem zwykłej funkcji C: system("");, działającej nie tylko na linuxie:).

Proste ustawianie wykresu

Powyższy akapit nasuwa pytanie: "po co w ogóle nam własna aplikacja skoro można odpalić z wiersza poleceń?", dlatego że nasz program może ułatwiać nam konfigurowanie wykresu.
Zanim pokażę fajny przykład trzeba znowu zarzucić trochę teorią.

Składnia funkcji plot

Zwykła komenda rysująca plot jest bardzo rozbudowana (szczegóły czegokolwiek można poznać pisząc help {cos co nas interesuje} np. help plot), nie ma sensu pokazywać jej możliwości inaczej niż na przykładach.

Przedstawię teraz przykłady o zupełnie innym stopniu skomplikowania

plot sin(x) # jest to po prostu narysowanie funkcji sinus dla parametrów domyślnych lub wcześniej ustawionych
#####################
plot [] [-1.1*pi:5] sin(x)  title 'sinus' with linespoints linetype 2 linewidth 2 pointtype 12 pointsize 3
# zakres osi X domyślny, zakres osi Y w przedziale [-1.1*pi, 5], tutuł wykresu 'sinus',
# styl rysowania - linespoints (możiwe jeszcze `lines`, `points`, `linespoints`, `impulses`, `dots`),
# typ linii nr 2, szerokosc linii 2, typ punktow nr 12, rozmiar punktów 3
plot sin(x), cos(x) # narysowane na jednym wykresie 2 funkcje, można na jednym wykresie narysować dowolną liczbę funkcji oddzielając ","
plot cos(x), (x>-5 && x<5 ? 0.5 : 1/0) with filledcurve y1=-1, sin(x) # tutaj na uwagę zasługuje pewne wyrażenie
# w nawiasie, które dla -5<x<5 rysuję wypełniony w środku prostokąt dla współrzędnych -1<=y<0.5,
# dziwnie wygląda dzielenie przez 0, ale to jest przykład z gnuplotowego helpa więc nie należy się tym przejmować, bo działa
Zwracam uwagę że parametry linetype, linewidth, pointtype, pointsize są dostępne jeżeli rysujemy w sposób zawierający odpowiednio linie lub punkty.
Jak wyglądają wykresy dla powyższych przykładów:

Przykład programistycznej (oczywiście od strony C++) konfiguracji wykresów

C/C++
#include <iostream>
#include <fstream>
#include <iomanip>
#include <boost/ptr_container/ptr_list.hpp> //(1)
using namespace std;
using namespace boost;

struct CosDoGnuplota { // (0)
    virtual ostream & drukuj( ostream & os ) const = 0;
    virtual ~CosDoGnuplota() { }
};

struct WykresFunkcji
    : public CosDoGnuplota
{
    const static char style_rysowania[ 8 ][ 20 ];
    const static char funkcje_wbudowane[ 12 ][ 10 ];
   
    double zakresX[ 3 ], zakresY[ 3 ]; // trzeci parametr oznaczać będzie czy interesuje nas inny niż domyślny zakres
    char wybrana_funkcja[ 100 ];
    string nazwa_funkcji;
    bool chce_nazwe_funkcji;
    unsigned styl_rysowania;
    bool jest_liniami, jest_punktami;
    double linetype, linewidth, pointtype, pointsize;
   
    WykresFunkcji( unsigned nr_funkcji, double * zakrX = NULL, double * zakrY = NULL, string tytul = "", unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true ) {
        pelnaKonstrukcja( nr_funkcji, zakrX, zakrY, tytul, nr_stylu, lt, lw, pt, ps, ch_na_fun ); // (2)
    }
   
    WykresFunkcji( string tytul, unsigned nr_funkcji, unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true ) { // (3)
        pelnaKonstrukcja( nr_funkcji, NULL, NULL, tytul, nr_stylu, lt, lw, pt, ps, ch_na_fun );
    }
   
    void pelnaKonstrukcja( unsigned nr_funkcji, double * zakrX = NULL, double * zakrY = NULL, string tytul = "", unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true ) {
        zakresX[ 2 ] = 0.;
        zakresY[ 2 ] = 0.;
       
        if( zakrX != NULL ) for( int i = 0; i < 3; ++i ) zakresX[ i ] = zakrX[ i ];
       
        if( zakrY != NULL ) for( int i = 0; i < 3; ++i ) zakresY[ i ] = zakrY[ i ];
       
        copy( funkcje_wbudowane[ nr_funkcji % 12 ], funkcje_wbudowane[ nr_funkcji % 12 ] + 10, wybrana_funkcja );
        nazwa_funkcji = tytul;
        styl_rysowania = nr_stylu % 8;
       
        linetype = lt;
        linewidth = lw;
        pointtype = pt;
        pointsize = ps;
       
        chce_nazwe_funkcji = ch_na_fun;
    }
   
    virtual ostream & drukuj( ostream & os ) const { // (4)
        os << setprecision( 2 ) << fixed << "plot ";
        if( zakresX[ 2 ] != 0 )
             os << '[' << zakresX[ 0 ] << ':' << zakresX[ 1 ] << "] ";
       
        if(( zakresY[ 2 ] ) &&( zakresX[ 2 ] ) != 0 )
             os << '[' << zakresY[ 0 ] << ':' << zakresY[ 1 ] << "] ";
       
        os << wybrana_funkcja << ' ';
       
        if( chce_nazwe_funkcji ) {
            if( !nazwa_funkcji.empty() )
                 os << "title '" << nazwa_funkcji << "' ";
           
        }
        else
             os << "notitle ";
       
        if( styl_rysowania != 0 ) {
            os << "with " << style_rysowania[ styl_rysowania ] << ' ';
           
            if( linetype > 0 ) os << "linetype " << linetype << ' ';
           
            if( linewidth > 0 ) os << "linewidth " << linewidth << ' ';
           
            if( pointtype > 0 ) os << "pointtype " << pointtype << ' ';
           
            if( pointsize > 0 ) os << "pointsize " << pointsize << ' ';
           
        }
       
        os << endl;
        return os;
    }
   
    friend ostream & operator <<( ostream & os, const WykresFunkcji & w ) {
        return w.drukuj( os );
    }
};

const char WykresFunkcji::style_rysowania[ 8 ][ 20 ] = { "lines", "points", "linespoints", "impulses", "docs", "steps", "fsteps", "histeps" };
const char WykresFunkcji::funkcje_wbudowane[ 12 ][ 10 ] = { "sin(x)", "cos(x)", "tan(x)", "exp(x)", "log(x)", "log10(x)", "sqrt(x)", "sgn(x)", "abs(x)", "ceil(x)", "floor(x)", "int(x)" };

void wypisz( ptr_list < CosDoGnuplota > & wykresy, ostream & os = cout ) { // (6)
    for( ptr_list < CosDoGnuplota >::iterator it = wykresy.begin(); it != wykresy.end(); ++it )
        ( * it ).drukuj( os );
   
}

main() {
    double zakres0[] = { 12, 34, 1 }, zakres1[] = { 2, 4, 1 }, zakres2[] = { 1, 3, 1 };
   
    ptr_list < CosDoGnuplota > wykresy;
   
    wykresy.push_back( new WykresFunkcji( 0 ) );
    wykresy.push_back( new WykresFunkcji( 1, zakres0 ) );
    wykresy.push_back( new WykresFunkcji( 2, zakres1, zakres2, "wykres tangensa", 2, 4, 5, 2, 7 ) );
   
    wykresy.push_back( new WykresFunkcji( "Absolut", 8, 3 ) );
   
    wypisz( wykresy );
} //         g++     gnu.cc   -o gnu && ./gnu
Oraz wydruk z programu, jak widać jest taki jak chcieliśmy:

plot sin(x)
plot [12.00:34.00] cos(x)
plot [2.00:4.00] [1.00:3.00] tan(x) title 'wykres tangensa' with linespoints linetype 4.00 linewidth 5.00 pointtype 2.00 pointsize 7.00
plot abs(x) title 'Absolut' with impulses
Dzięki takiemu, prostemu programowi w C++ możemy sobie wyświetlać dowolne funkcje wbudowane. Zanim przejdziemy dalej opiszę parę miejsc powyższego programu:
Nr bledukodopis
0
C/C++
struct CosDoGnuplota {
    virtual ostream & drukuj( ostream & os ) const = 0;
    virtual ~CosDoGnuplota() { }
};
interfejs którego istnienie zostanie jeszcze wykorzystane, proszę zwrócić uwagę na virtualny destruktor - przy korzystaniu z polimorfizmu jest głęboko zalecany
1
#include <boost/ptr_container/ptr_list.hpp>
warto skorzystać z czegoś więcej niż absolutne minimum i poznać przy okazji nowe rzeczy, dlatego używam kontenera z biblioteki boost, który podczas destrukcji wywoła operator delete dla każdego ze znajdujących się w nim obiektów
2
pelnaKonstrukcja( nr_funkcji, zakrX, zakrY, tytul, nr_stylu, lt, lw, pt, ps, ch_na_fun );
ze wzgledu na niemozliwosc delegowania kanstruktorow w C++ 2007 korzystam z funkcji która konstruuje
3
WykresFunkcji( string tytul, unsigned nr_funkcji, unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true )
warto zrwócić uwagę na zmianę kolejności w pierwszego argumentu w obydwu konstruktorach, dlatego żeby nie pojawiałą się dwuznaczność przy jednym argumencie
4
virtual ostream & drukuj( ostream & os ) const
funkcja zadekladowana jako wirtualna z pewnego powodu. Musi być const jeżeli chcę ją wywołać w operatorze <<, jako argument tej funkcji jest dowolny strumień wyjściowy
5
wypisz( ptr_list < CosDoGnuplota > & wykresy, ostream & os = cout )
funkcja, która ma za zadanie wypisanie całej listy poleceń na wybrany strumień

Więcej ustawień, czyli ustawienia poza funkcją plot

To co wcześniej opisałem, daję naprawdę wiele możliwości, ale Gnuplot umożliwia znacznie, znacznie, znacznie więcej, ciężko byłoby wszystkie opisać (i nawet nie byłoby to potrzebne), wymienię najpotrzebniejsze rzeczy (do których dotarłem).
Zacznę od tego że pewne rzeczy które ustawialiśmy dla samego rysunku da się również ustawić globalnie.
Zmieniamy ustawienia czegoś poleceniem set cos {ewentualne argumenty}, analogicznie usuwamy ustawienia poleceniem unset cos, gdy przedobrzymy i chcemy przywrócić ustawienia domyślne wystarczy komenda reset. Mamy naprawdę wiele rzeczy do ustawiania (a one często mają wiele pod-opcji:D), chcąc wyświetlić wszystkie możliwe ustawienia używamy show all, ale jest tego więcej niż sporo.
Nie ma sensu wymieniać możliwości z opisem teoretycznym a potem pokazywać przykład, lepiej od razu zarzucić przykładem (oczywiście zawierającym również opis teoretyczny), jednak zanim polecę na przykładach, pozwolę sobie wprowadzić trochę teorii dla komendy set terminal ... .

Eksport wykresu

Aby nasz wykres mieć w formie trwałej -pliku, nie musimy robić PrintScreena, wklejać do painta, przycinać itp., sam Gnuplot ma możliwość eksportu do wybranego formatu z pewnymi opcjami. Możliwych formatów eksportu jest naprawdę wiele (nawet jakieś egzotyczne km-tek40xx), niestety (albo na szczęście) nie mamy na start bibliotek do eksportu wszystkich formatów, fajnę rzeczą jest to że mamy możliwość sprawdzenia możliwych do wyeksportowania, bez doinstalowania czegokolwiek, formatów pisząc po prostu set terminal (bez parametrów).
Skupię się na formacie .png, dla tego typu "terminala" mamy możliwość ustawienia ... (czas na przykład):

       set terminal png transparent xffffff medium size 800, 500 \
                        xffffff x000000 x404040 \
                        xff0000 xffa500 x66cdaa xcdb5cd \
                        xadd8e6 x0000ff xdda0dd x9500d3
       set out 'wykres.png'
  • set terminal png -ustawia format na .png
  • transparent {kolor} -to przeźroczystość
  • medium -to typ czcionki, możliwe tiny | small | medium | large | giant
  • size ..., ... -rozmiar okna w px, możemy go też zmienić pisząc [b]set size x, y[/b]
  • 'xrrggbb' -kolory w formacie szesnastkowym, odpowiadają w kolejności: dla tła, dla ramki, osie X i Y, wykres
  • set out 'wykres.png' ustawiamy nazwę pliku wyjściowego
Teraz czas na przykład programu, który ustawia parę rzeczy dla terminala, może nie jest to bardzo potrzebne już teraz, ale nie zaszkodzi.
C/C++
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <boost/ptr_container/ptr_list.hpp>
using namespace std;
using namespace boost;

struct CosDoGnuplota {
    virtual ostream & drukuj( ostream & os ) const = 0;
    virtual ~CosDoGnuplota() { }
};

class Terminal
    : public CosDoGnuplota
{
    vector < string > kolory;
   
    unsigned rozmiar[ 2 ];
    unsigned czcionka;
    char przezroczystosc[ 7 ];
   
    string nazwa_pliku;
    string format;
   
public:
    const static char formaty[ 10 ][ 20 ];
    const static char podstawowe_czcionki[ 5 ][ 8 ];
   
    Terminal( const char * form = "png", string plik = "wykres", unsigned rozmX = 0, unsigned rozmY = 0, unsigned czcionka =- 1, const char * przez = "xffffff", string * chciane_kolory = NULL, int ilosc_kolorow =- 1 ) {
        pelnaKonstrukcja( form, plik, rozmX, rozmY, czcionka, przez, chciane_kolory, ilosc_kolorow );
    }
   
    void pelnaKonstrukcja( const char * form = "png", string plik = "wykres", unsigned rozmX = 0, unsigned rozmY = 0, unsigned czcionka =- 1, const char * przez = "xffffff", string * chciane_kolory = NULL, int ilosc_kolorow =- 1 ) {
        format = form;
        nazwa_pliku = plik;
        copy( przez, przez + strlen( przez ), przezroczystosc );
        rozmiar[ 0 ] = rozmX; rozmiar[ 1 ] = rozmY;
        this->czcionka = czcionka;
        nadajKolory( chciane_kolory, ilosc_kolorow );
    }
   
    void nadajKolory( string * chciane_kolory = NULL, int ilosc =- 1 ) {
        if( ilosc < 0 ) { // jesli nie chcemy podawac zadnych kolorow
        }
        else if( chciane_kolory == NULL ) // jesli chcemy je wygenerowac
        for( unsigned i = 0; i < ilosc; ++i )
             kolory.push_back( generujKolor() );
        else
        for( unsigned i = 0; i < ilosc; ++i ) // odczyt z tablicy
             kolory.push_back( chciane_kolory[ i ] );
       
    }
   
    ostream & drukuj( ostream & os ) const {
        os << "set terminal " << format;
        if( strcmp( przezroczystosc, "xffffff" ) != 0 )
             os << " transparent " << przezroczystosc;
       
        if( czcionka >= 0 )
             os << ' ' << podstawowe_czcionki[ czcionka ];
       
        if(( rozmiar[ 0 ] > 0 && rozmiar[ 1 ] > 0 ) )
             os << " size " << rozmiar[ 0 ] << ',' << rozmiar[ 1 ];
       
        os << ' ';
        copy( kolory.begin(), kolory.end(), ostream_iterator < string >( os, " " ) );
        os << "\n set out '" << nazwa_pliku << '.' << format << "'\n";
        return os;
    }
   
    friend ostream & operator <<( ostream & os, const Terminal & t ) {
        return t.drukuj( os );
    }
   
    static unsigned losuj( unsigned od = 0, unsigned doo = 15 ) {
        return( rand() %( doo - od ) + od );
    }
   
    static string generujKolor() {
        ostringstream os; // strumien eksportujacy do stringa
        os << 'x' << hex;
        for( int i = 0; i < 6; ++i )
             os << losuj();
       
        return os.str();
    }
};

const char Terminal::formaty[ 10 ][ 20 ] = { "gif", "jpeg", "png", "postscript" };
const char Terminal::podstawowe_czcionki[ 5 ][ 8 ] = { "tiny", "small", "medium", "large", "giant" };

void wypisz( ptr_list < CosDoGnuplota > & wykresy, ostream & os = cout ) {
    for( ptr_list < CosDoGnuplota >::iterator it = wykresy.begin(); it != wykresy.end(); ++it )
        ( * it ).drukuj( os );
   
}

main() {
    ptr_list < CosDoGnuplota > wykresy;
   
    srand( time( NULL ) );
    string chciane_kolory[ 2 ] = { "x113399", "xffcc99" };
   
    wykresy.push_back( new Terminal );
    wykresy.push_back( new Terminal( Terminal::formaty[ 2 ], "wyjsciowy", 640, 480, 2, "x04902a", chciane_kolory, 2 ) );
    wykresy.push_back( new Terminal( "png", "wyjsciowy2", 400, 600, 2, "x01c02a", NULL, 5 ) );
    wykresy.push_back( new Terminal( "jpeg", "wyjsciowy3", 1000, 800, 0, "xffffff", NULL, 0 ) );
   
    wypisz( wykresy );
}
//    g++     gnu2.cc   -o tmp && ./tmp
Wydruk z powyższego kodu:

set terminal png 
 set out 'wykres.png'
set terminal png transparent x04902a medium size 640,480 x113399 xffcc99
 set out 'wyjsciowy.png'
set terminal png transparent x01c02a medium size 400,600 x42624b x406281 xd8891e xcb5e0a xae44d5
 set out 'wyjsciowy2.png'
set terminal jpeg tiny size 1000,800
 set out 'wyjsciowy3.jpeg'

Inne, pożyteczne (a zarazem zname mi) ustawienia

Czyli przykład kodu zmieniającego ustawienia:
C/C++
#include <iostream>
#include <sstream>
#include <string>
#include <boost/ptr_container/ptr_list.hpp>
using namespace std;
using namespace boost;

struct CosDoGnuplota {
    virtual ostream & drukuj( ostream & os ) const = 0;
    virtual ~CosDoGnuplota() { }
};

class Ustawienia
    : public CosDoGnuplota
{
    bool reset_wczesniejszych_ustawien;
    string tytul_rysunku;
    string nazwaX, nazwaY;
    bool autoskalowanie[ 2 ];
    bool usuniecie_dodatkowych_napisow, usuniecie_dodatkowych_strzalek;
    string format_czasu;
    double polozenie_legend[ 3 ], zakresX[ 4 ], zakresY[ 4 ], przesuniecie_wykresu[ 2 ]; // (1)
    double odstep_funkcji_od_granic[ 5 ];
   
public:
    Ustawienia( string tytul_rysunku = "", bool reset = false, double * zakrX = NULL, double * zakrY = NULL, bool autoscX = false, bool autoscY = false, string nazwaX = "", string nazwaY = "", bool usuniecie_nap = false, bool usuniecie_str = false, string format_czasu = "", double * pol_leg = NULL, double przesX = 0, double przesY = 0, double * ods = NULL )
        : tytul_rysunku( tytul_rysunku )
        , reset_wczesniejszych_ustawien( reset )
        , usuniecie_dodatkowych_napisow( usuniecie_nap )
        , usuniecie_dodatkowych_strzalek( usuniecie_str )
        , format_czasu( format_czasu )
        , nazwaX( nazwaX )
        , nazwaY( nazwaY )
    {
        zakresX[ 3 ] = zakresX[ 4 ] = zakresY[ 3 ] = zakresY[ 4 ] = 0;
        if( zakrX != NULL )
        for( int i = 0; i < 5; ++i )
             zakresX[ i ] = zakrX[ i ];
       
        if( zakrY != NULL )
        for( int i = 0; i < 5; ++i )
             zakresY[ i ] = zakrY[ i ];
       
        autoskalowanie[ 0 ] = autoscX; autoskalowanie[ 1 ] = autoscY;
       
        polozenie_legend[ 2 ] = 0;
        if( pol_leg != NULL ) {
            polozenie_legend[ 0 ] = pol_leg[ 0 ]; polozenie_legend[ 1 ] = pol_leg[ 1 ]; polozenie_legend[ 2 ] = 1;
        }
       
        przesuniecie_wykresu[ 0 ] = przesX; przesuniecie_wykresu[ 1 ] = przesY;
       
        odstep_funkcji_od_granic[ 4 ] = 0;
        if( ods != NULL ) {
            for( int i = 0; i < 4; ++i )
                 odstep_funkcji_od_granic[ i ] = ods[ i ];
           
            odstep_funkcji_od_granic[ 4 ] = 1;
        }
    }
   
    virtual ostream & drukuj( ostream & os ) const {
        if( reset_wczesniejszych_ustawien )
             os << "reset\n";
       
        if( usuniecie_dodatkowych_napisow )
             os << "unset label\n";
       
        if( usuniecie_dodatkowych_strzalek )
             os << "unset arrow\n";
       
        if( !tytul_rysunku.empty() )
             os << "set title '" << tytul_rysunku << "'\n";
       
        if( !nazwaX.empty() )
             os << "set xlabel '" << nazwaX << "'\n";
       
        if( !nazwaY.empty() )
             os << "set ylabel '" << nazwaY << "'\n";
       
        if( zakresX[ 2 ] || zakresX[ 3 ] ) { // (2)
            os << "set xrange [";
            if( zakresX[ 2 ] )
                 os << zakresX[ 0 ];
           
            os << ':';
            if( zakresX[ 3 ] )
                 os << zakresX[ 1 ];
           
            os << "]\n";
        }
        if( zakresY[ 2 ] || zakresY[ 3 ] ) {
            os << "set yrange [";
            if( zakresY[ 2 ] )
                 os << zakresY[ 0 ];
           
            os << ':';
            if( zakresY[ 3 ] )
                 os << zakresY[ 1 ];
           
            os << "]\n";
        }
        if( autoskalowanie[ 0 ] || autoskalowanie[ 1 ] ) {
            os << "set autoscale ";
            if( autoskalowanie[ 0 ] )
                 os << 'x';
           
            if( autoskalowanie[ 1 ] )
                 os << 'y';
           
            os << '\n';
        }
        if( polozenie_legend[ 2 ] )
             os << "set key " << polozenie_legend[ 0 ] << ',' << polozenie_legend[ 1 ] << '\n';
       
        if( przesuniecie_wykresu[ 0 ] || przesuniecie_wykresu[ 1 ] )
             os << "set origin " << przesuniecie_wykresu[ 0 ] << ',' << przesuniecie_wykresu[ 1 ] << '\n';
       
        if( odstep_funkcji_od_granic[ 4 ] ) {
            os << "set offset ";
            copy( odstep_funkcji_od_granic, odstep_funkcji_od_granic + 4, ostream_iterator < double >( os, ", " ) ); // (3)
            os << '\n';
        }
        if( !format_czasu.empty() )
             os << "set time \"" << format_czasu << "\"\n";
       
    }
   
    friend ostream & operator <<( ostream & os, const Ustawienia & u ) {
        return u.drukuj( os );
    }
};

void wypisz( ptr_list < CosDoGnuplota > & wykresy, ostream & os = cout ) {
    for( ptr_list < CosDoGnuplota >::iterator it = wykresy.begin(); it != wykresy.end(); ++it )
        ( * it ).drukuj( os );
   
}

main() {
    double zakresX[] = { 0, 10, 1, 1 }, zakresY[] = { - 1, 99999, 1, 0 }, polozenie_legend[] = { 10, 30 }, odstep_funkcji_od_granic[] = { 1, 1, 0.5, 0.5 };
   
    ptr_list < CosDoGnuplota > wykresy;
   
    wykresy.push_back( new Ustawienia ); // (4)
    wykresy.push_back( new Ustawienia( "rysunek", true, zakresX, zakresY, true, true, "os X", "os Y", true, true, "godzina %H:%M dnia  %d.%m.%y", polozenie_legend, 10, - 1, odstep_funkcji_od_granic ) );
   
    wypisz( wykresy );
} //    g++     gnu3.cc   -o tmp && ./tmp
Opis paru miejsc programu:
Nr bledukodopis
1
double polozenie_legend[ 3 ], zakresX[ 4 ], zakresY[ 4 ], przesuniecie_wykresu[ 2 ];
zapewne nasuwa się pytanie dlaczego tablice mają taką liczbę parametrów -chciałem aby była możliwość nie wyświetlania niektórych ustawień, ale również nie chciałem tworzyć dodatkowej zmiennej bool (podawanej w konstruktorze), która by zawierała informacje czy chcę aby coś było ustawiane czy nie. Dlatego właśnie trzeci parametr w tej tablicy pełni rolę tego boola, w przypadku zakresX możemy podać tylko przedział od, lub tylko przedział do, w związku z tym 2 ostatnie parametry to w założeniu bool'e. Mógłbym to zrobić lepiej korzystając z wskaźników do pair
2
if( zakresX[ 2 ] || zakresX[ 3 ] );
tutaj właśnie korzystam z tych dwóch parametrów zakresu. Tego nie mógłbym łatwo zrobić przy pomocy pair, ale w C++0x oraz w boost jest tuple
3
C/C++
copy( odstep_funkcji_od_granic, odstep_funkcji_od_granic + 3, ostream_iterator < double >( os, ", " ) ); // (3)
os << odstep_funkcji_od_granic[ 3 ];
tutaj wykorzystuję fajny sposób wyświetlania na dowolny strumień wyjściowy przy pomocy iteratorów, niestety ze względu na to że na końcu linijki konfigurującej wykres gnuplota nie powinno być , (przecinka) dlatego ostatni element muszę wyświetlić normalnie
  Oraz to co program wyświetla (do wydruku dodałem komentarze opisujce za co dane komendy są odpowiedzialne):

reset # resetuje wszystkie poprzednie ustawienia do domyslnych 
unset label # usuwam poprzednio zdefiniowane, dodatkowe napisy tekstowe z wykresu
unset arrow # usuwam poprzednio zdefiniowane, dodatkowe strzalki z wykresu
set title 'rysunek' # ustawia nazwe rysunku
set xlabel 'os X' # nazwa osi X
set ylabel 'os Y' # nazwa osi Y
set xrange [0:10] # zakres osi X
set yrange [-1:] # zakres osi Y
set autoscale xy # autoskalowanie na osiach X i Y
set key 10,30 # ustawia polozenie legendy
set origin 10,-1 # przesuniecie wykresu
set offset 1, 1, 0.5, 0.5, # odstep naszych wyrysowanych funkcji od granic
set time "godzina %H:%M dnia  %d.%m.%y" # ustawienie wyswietlanej na rysunku godziny
Jako dygresję dodam że z gnuplota możemy użyć każdej komendy systemowej poprzedzając ją ! , np. ! dir

Pełne wykorzystanie całej poprzednio nabytej wiedzy i teorii w kompletnym przykładzie

Mamy już więcej teorii niż absolutne minimum, lecz na tyle dużo aby swobodnie korzystać z programu Gnuplot.
Czas wiec najwyzszy aby puscic w ruch wiedze przez nas zdobyta, zaczynając od przykładu ustawiającego z poziomu C++ naszego wykresu. Dodam jeszcze przed przykładem, że wszystkie wcześniej napisane przeze mnie klasy wrzuciłem do jednej biblioteki: gnuplot.hpp i po zademonstrowaniu każdego następnego programu będę do tej biblioteki dorzucał:
C/C++
#include "gnuplot.hpp"

void stworzZapiszRysunek( const char * nazwa_pliku, ptr_list < CosDoGnuplota > & wykresy ) {
    ofstream of( nazwa_pliku );
    wypisz( wykresy, of );
    string komenda( "gnuplot '" );
    komenda += nazwa_pliku;
    komenda += "'";
    cout << komenda.c_str() << endl;
    system( komenda.c_str() );
}

main() {
    srand( time( NULL ) );
    WykresFunkcji::funkcje.assign( WykresFunkcji::funkcje_wbudowane, WykresFunkcji::funkcje_wbudowane + 10 );
   
    ptr_list < CosDoGnuplota > wykresy;
    double zakresX[] = { - 2, 5, 1, 1 }, zakresY[] = { - 2, 2, 1, 0 }, polozenie_legend[] = { - 0.3, 2 }, odstep_funkcji_od_granic[] = { 1, 0.2, 0.2, 0.5 };
    wykresy.push_back( new Ustawienia( "rysunek", true, zakresX, zakresY, true, true, "os X", "os Y", true, true, "godzina %H:%M dnia  %d.%m.%y", polozenie_legend, 0.1, 0.01, odstep_funkcji_od_granic ) );
    string chciane_kolory[ 2 ] = { "x113399", "xffcc99" };
    wykresy.push_back( new Terminal( Terminal::formaty[ 2 ], "wyjsciowy", 640, 480, 2, "x04902a", chciane_kolory, 2 ) );
    wykresy.push_back( new WykresFunkcji( 2, zakresX, zakresY, "wykres tangensa", 2, 4, 5, 2, 7 ) );
   
    stworzZapiszRysunek( "wydrukDobry.g", wykresy );
} //         g++     gnu2.cc   -o gnu && ./gnu
Na opis zasługuje jedynie funkcja stworzZapiszRysunek, otwiera strumień do zapisu dla pliku o podanej nazwie, następnie wykorzystuje funkcję "wypisz()", której strumieniem wyjściowym nie jest teraz cout, lecz strumień zapisu do pliku, następnie przy pomocy klasy string tworzę komendę dla wywołania systemowego.

Jako wydruk (do pliku) otrzymaliśmy:

reset
unset label
unset arrow
set title 'rysunek'
set xlabel 'os X'
set ylabel 'os Y'
set xrange [-2:5]
set yrange [-2:]
set autoscale xy
set key -0.3,2
set origin 0.1,0.01
set offset 1, 0.2, 0.2, 0.5
set time "godzina %H:%M dnia  %d.%m.%y"
set terminal png transparent x04902a medium size 640,480 x113399 xffcc99
 set out 'wyjsciowy.png'
plot [-2.00:5.00] [-2.00:2.00] tan(x) title 'wykres tangensa' with linespoints linetype 4.00 linewidth 5.00 pointtype 2.00 pointsize 7.00
A wynik rysunku:
wyjsciowy.png
wyjsciowy.png
Jak widać już coś fajnego da się zrobić. Dodam jako dygresję że należy uważać z przesuwaniem całego rysunku (set origin), najlepiej nawet z tego nie korzystać, ponieważ najładniej jest mieć wykres na środku, a poza tym możliwe jest przypadkowe przesunięcie rysunku poza zakres obrazka, a wtedy zobaczymy pusty ekran.
Na powyższym przykładzie niepotrzebnie podawałem zakres X, Y w dwóch miejscach, poza tym gdy podajemy zakres dla osi X,Y to podawanie autoskalowania jest trochę sprzeczne, ale dopuściłem się tej "sprzecznośi" aby pokazać że już fajnie działa nasz kod. Im większe możliwości, tym większa odpowiedzialność, na szczęście jako parametry konstruktorów w klasach, tak dużej liczby parametrów wcale nie trzeba podawać.

Strzałki i napisy

Wydają się nam niezbyt potrzebne, ale jak na przykład chcemy zaznaczyć miejsce zerowe funkcji liczonej przy użyciu metod numerycznych i je zaznaczyć, to już zauważamy przydatność tych dwóch narzędzi, przyznam że mi również szkoda że nie wiedziałem o strzałkach i napisach generując wykresy do sprawozdań z elektroniki. Poza studiami też się ta funkcjonalność przydać może, jak chcemy coś zaznaczyć na wykresie.

Przykładowy kod obsługujący strzałki i napisy

C/C++
#include "gnuplot.hpp"

struct Komenda
    : public CosDoGnuplota
{ // (1)
    string tresc;
    Komenda( string tresc )
        : tresc( tresc )
    { }
   
    virtual ostream & drukuj( ostream & o ) const {
        o << tresc << '\n';
    }
};

struct Strzalka
    : public CosDoGnuplota
{
    unsigned tag_nazwy; // jesli jej nie podamy bedzie domyslnie numerowane
    typedef pair < double, double >* ptr_pair; // (2)
    ptr_pair zakres_od, zakres_do;
   
    enum typ_pozycji { zero, first, second, graph, screen, character }; // (3)
    typ_pozycji tp_od, tp_do;
   
    enum typ_glowy { head, nohead, backhead, heads };
    typ_glowy typ_g;
    enum wypelnienie_glowy { filled, empty, nofilled };
    wypelnienie_glowy wyp_g;
    static const char type_glowy[ 4 ][ 10 ], wypelnienia_glowy[ 3 ][ 15 ], typy_pozycji[ 6 ][ 10 ];
    int styl_linii, typ_linii, szerokosc_linii;
    double rozmiar_glowy, kat_glowy, tylny_kat_glowy;
    bool chce_FRONT_a_nie_BACK;
   
    Strzalka( ptr_pair zakr_do, ptr_pair zakr_od = NULL, typ_pozycji tp_do = zero, typ_pozycji tp_od = zero, unsigned tag = 0, typ_glowy tg = head, wypelnienie_glowy wg = empty, int sl = 0, int tl = 0, int szl = 0, double rg = 0, double kg = 0, double tkg = 0, bool chce_FRONT = false )
        : zakres_do( zakr_do )
        , zakres_ zakresX[ sin0 ] od( zakr_od )
        , tp_do( tp_do )
        , tp_od( tp_od )
        , tag_nazwy( tag )
        , typ_g( tg )
        , wyp_g( wg )
        , styl_linii( sl )
        , typ_linii( tl )
        , szerokosc_linii( szl )
        , rozmiar_glowy( rg )
        , kat_glowy( kg )
        , tylny_kat_glowy( tkg )
        , chce_FRONT_a_nie_BACK( chce_FRONT )
    {
    }
   
    ~Strzalka() {
        delete zakres_od; delete zakres_do;
    }
   
    virtual ostream & drukuj( ostream & o ) const {
        o << "set arrow ";
        if( tag_nazwy != 0 )
             o << tag_nazwy;
       
        if( zakres_od )
             o << " from " << typy_pozycji[ tp_od ] << ' ' << zakres_od->first << ',' << zakres_od->second;
       
        o << " to " << typy_pozycji[ tp_do ] << ' ' << zakres_do->first << ',' << zakres_do->second << ' ';
        if( typ_g != head )
             o << type_glowy[ typ_g ] << ' ';
       
        if( rozmiar_glowy > 0 ) {
            o << " size " << rozmiar_glowy;
            if( kat_glowy > 0 ) {
                o << ',' << kat_glowy;
                if( tylny_kat_glowy > 0 )
                     o << ',' << tylny_kat_glowy;
               
            }
            o << ' ';
        }
        if( wyp_g != empty )
             o << ' ' << wypelnienia_glowy[ wyp_g ];
       
        if( chce_FRONT_a_nie_BACK )
             o << " front ";
       
        if( styl_linii > 0 )
             o << " linestyle " << styl_linii; // (4)
        else {
            if( typ_linii > 0 )
                 o << " linetype " << typ_linii;
           
            if( szerokosc_linii > 0 )
                 o << " linewidth " << szerokosc_linii << ' ';
           
        }
        o << '\n';
        return o;
    }
   
    friend ostream & operator <<( ostream & os, const Strzalka & s ) {
        return s.drukuj( os );
    }
};
const char Strzalka::typy_pozycji[ 6 ][ 10 ] = { "", "first", "second", "graph", "screen", "character" };
const char Strzalka::type_glowy[ 4 ][ 10 ] = { "head", "nohead", "backhead", "heads" };
const char Strzalka::wypelnienia_glowy[ 3 ][ 15 ] = { "filled", "empty", "nofilled" };

pair < double, double >* get_pair( double t, double r ) {
    pair < double, double >* p = new pair < double, double >;
    ( * p ) = make_pair( t, r );
    return p;
}

struct Napis
    : public CosDoGnuplota
{
    unsigned tag_nazwy; // jesli jej nie podamy bedzie domyslnie numerowane
    string tresc;
    typedef pair < double, double >* ptr_pair;
    ptr_pair polozenie;
   
    const static char typy_pozycji[ 6 ][ 10 ];
    enum typ_pozycji { zero, first, second, graph, screen, character };
    typ_pozycji tp;
   
    const static char justowanie[ 3 ][ 7 ];
    enum typ_justowania { left, center, right };
    typ_justowania tj;
   
    int obrot;
    string nazwa_czcionki;
    unsigned rozmiar_czcionki;
   
    bool chce_FRONT_a_nie_BACK;
    string kolor;
   
    unsigned typ_punktow, typ_linii, rozmiar_punktow;
   
    ptr_pair przesuniecie;
    typ_pozycji tprzes;
   
    Napis( string tresc, ptr_pair polozenie = NULL, typ_pozycji pozycji_typ = zero, typ_justowania justowania_typ = left, int obrot = 0, bool chce_FRONT = false, unsigned tag_nazwy = 0, string nazwa_czcionki = "", unsigned rozmiar_czcionki = 0, string kolor = "", unsigned styl_punktow = 0, ptr_pair przesuniecie = NULL, typ_pozycji przesuniecia_typ = zero, unsigned typ_punktow = 0, unsigned typ_linii = 0, unsigned rozmiar_punktow = 0 )
        : tag_nazwy( tag_nazwy )
        , polozenie( polozenie )
        , tresc( tresc )
        , tp( pozycji_typ )
        , tj( justowania_typ )
        , obrot( obrot )
        , nazwa_czcionki( nazwa_czcionki )
        , rozmiar_czcionki( rozmiar_czcionki )
        , chce_FRONT_a_nie_BACK( chce_FRONT )
        , kolor( kolor )
        , przesuniecie( przesuniecie )
        , tprzes( przesuniecia_typ )
        , typ_punktow( typ_punktow )
        , typ_linii( typ_linii )
        , rozmiar_punktow( rozmiar_punktow )
    { }
   
    ~Napis() {
        delete polozenie;
        delete przesuniecie;
    }
   
    virtual ostream & drukuj( ostream & o ) const {
        o << "set label ";
        if( tag_nazwy > 0 )
             o << tag_nazwy << ' ';
       
        o << "\"" << tresc << "\"";
        if( polozenie != NULL )
             o << " at " << typy_pozycji[ tp ] << ' ' << polozenie->first << ',' << polozenie->second << ' ';
       
        if( tj != left )
             o << justowanie[ tj ] << ' ';
       
        if( obrot != 0 )
             o << "rotate by " << obrot << ' ';
       
        if( !nazwa_czcionki.empty() ) {
            o << "font " << nazwa_czcionki << ' ';
            if( rozmiar_czcionki > 0 )
                 o << rozmiar_czcionki << ' ';
           
        }
        if( chce_FRONT_a_nie_BACK )
             o << "front ";
       
        if( kolor.size() > 6 )
             o << "textcolor linestyle \"" << kolor << "\" ";
        else if( kolor[ 0 ] == '0' )
             o << "textcolor linestyle \"" << Terminal::generujKolor() << "\" ";
       
        if(( typ_punktow + typ_linii + rozmiar_punktow ) > 0 ) {
            o << "point ";
            if( typ_punktow )
                 o << "pointtype " << typ_punktow << ' ';
           
            if( typ_linii )
                 o << "linetype " << typ_linii << ' ';
           
            if( rozmiar_punktow )
                 o << "pointsize " << rozmiar_punktow << ' ';
           
        }
        if( przesuniecie != NULL )
             o << "offset " << typy_pozycji[ tprzes ] << ' ' << przesuniecie->first << ',' << przesuniecie->second;
       
        o << '\n';
        return o;
    }
};

const char Napis::typy_pozycji[ 6 ][ 10 ] = { "", "first", "second", "graph", "screen", "character" };
const char Napis::justowanie[ 3 ][ 7 ] = { "left", "center", "right" };

main() {
    ptr_list < CosDoGnuplota > wykresy;
   
    wykresy.push_back( new Ustawienia( "", true ) );
   
    wykresy.push_back( new Strzalka( get_pair( 1., 0.5 ) ) );
    wykresy.push_back( new Strzalka( get_pair( 7.5, - 1.), get_pair( 1.5, 1 ), Strzalka::first, Strzalka::second, 2, Strzalka::heads, Strzalka::filled, 0, 5, 2, 10, 30, 20 ) );
    wykresy.push_back( new Strzalka( get_pair( 0.6, 0.9 ), get_pair( 0.1, 0 ), Strzalka::screen, Strzalka::graph, 3, Strzalka::backhead, Strzalka::nofilled, 2, 0, 0, 5, 20, 10, true ) );
   
    wykresy.push_back( new Komenda( "show arrow" ) );
   
    wykresy.push_back( new Napis( "standardowy" ) );
   
    wykresy.push_back( new Napis( "first 5,0.5", get_pair( 5, 0.5 ), Napis::first ) );
    wykresy.push_back( new Napis( "second -5,0.5 center rotate by 20", get_pair( - 5, 0.5 ), Napis::second, Napis::center, 20, 3 ) );
    wykresy.push_back( new Napis( "graph 1,0.5 right rotate by 2", get_pair( 1, 0.5 ), Napis::graph, Napis::right, 2, true, 4, "", 0 ) );
    wykresy.push_back( new Napis( "screen 0.2,0.5 left rotate by -2", get_pair( 0.2, 0.5 ), Napis::screen, Napis::left, - 2, true, 5, "", 0, "0" ) );
    wykresy.push_back( new Napis( "character 1,11 center rotate by 90 point 3 offset character 1,1", get_pair( 1, 11 ), Napis::character, Napis::center, 90, false, 6, "", 0, "#00FF01", 3, get_pair( 1, 1 ), Napis::character, 3, 4, 2 ) );
   
    wykresy.push_back( new Komenda( "show label" ) );
   
    wykresy.push_back( new WykresFunkcji( 0 ) );
   
    wypisz( wykresy );
} //         g++     gnu3.cc   -o gnu && ./gnu
Czas wyjaśnić parę miejsc programu:
Nr bledukodopis
1
C/C++
struct Komenda
    : public CosDoGnuplota
czasami zachodzi konieczność jednorazowego użycia jednej komendy, dla której nie ma sensu na konstruowanie osobnej klasy
2
typedef pair < double, double >* ptr_pair;
typedef który, wg mnie ułatwia życie
3
enum typ_pozycji { zero, first, second, graph, screen, character };
zamiast stosowania intów z operacją modulo lepiej używać typów enum. Pod spodem opiszę co znaczą poszczególne z tych wartości
4
C/C++
if( styl_linii > 0 )
     o << " linestyle " << styl_linii;
else {
    if( typ_linii > 0 )
         o << " linetype " << typ_linii;
   
    if( szerokosc_linii > 0 )
         o << " linewidth " << szerokosc_linii << ' ';
   
};
coś takiego może wydać się dziwne, ale gnuplotowi możemy podać jaki chcemy styl linii, albo jej typ i szerokość
Wydruk z programu (z komentarzami dopisanymi przeze mnie) i rysunek do tego wydruku

reset
set arrow  to  1,0.5 # narysowanie strzałki od 0,0 do punktu wskazanego we współrzędnych
set arrow 2 from second 1.5,1 to first 7.5,-1 heads  size 10,30,20  filled linetype 5 linewidth 2 # strzałka o id=2 z pozycji second 1.5,1 do pozycji first 7.5,-1, zawietająca "dziubki" po obu stronach strzałki, rozmiaru: 10 długości "dziubków", 30 stopni rozwartości i 20 stopni kąta między końcami dzióbków strzałki nieprzyległych do trzonu danej strzałki a dodatkowymi liniami łączącymi je z trzonem -najlepiej to zrozumieć na przykładzie, dodam że ta opcja jest widoczna przy wypełnionych dzióbkach w strzałce, a właśnie w naszym przypadku jest wypełniona. Typ i szerokość linii odpowiednio 5 i 2
set arrow 3 from graph 0.1,0 to screen 0.6,0.9 backhead  size 5,20,10  nofilled front  linestyle 2 # tutaj jest niewypełniona strzałka, która ponadto jest narysowana na wykresie (nie pod)
show arrow # pokazanie wszystkich strzałek
set label "standardowy" #zwyczajny, najprostszy napis, zaczynający się w punkcie 0,0
set label "first 5,0.5" at first 5,0.5
set label "second -5,0.5 center rotate by 20" at second -5,0.5 center rotate by 20 front
set label 4 "graph 1,0.5 right rotate by 2" at graph 1,0.5 right rotate by 2 front
set label 5 "screen 0.2,0.5 left rotate by -2" at screen 0.2,0.5 rotate by -2 front textcolor linestyle "xd1ca8a"
set label 6 "character 1,11 center rotate by 90 point 3 offset character 1,1" at character 1,11 center rotate by 90 textcolor linestyle "#00FF01" point pointtype 3 linetype 4 pointsize 2 offset character 1,1
show label  # pokazanie wszystkich napisów
plot sin(x)
Parę dodatkowych uwag:
Nie opisywałem napisów gdyż same się opisują na rysunku.
Justowanie: left, right, center - zależnie od typu tak się tekst ustawia względem punktu podanego jako współrzędna napisu
  • left (domyślny) - pierwsza litera z napisu zaczyna się na na punkcie podanym jako współrzędne
  • center - powoduje że środek napisu znajduje się na punkcie podanym jako współrzędne
  • right - napis kończy się na podanej współrzędnej
Sposób reprezentacji współrzędnych: "first", "second", "graph", "screen", "character"
  • first - współrzędne wg osi X,Y zaczynając od punktu 0,0 idąc w prawo i do góry
  • second - współrzędne od punktu wykresu prawo, góra idące wg osi w stronę lewego, dolnego rogu
  • graph - 0,0 to dół z lewej, 1,1 to góra z prawej
  • screen - 0,0 (lewy i dolny) - 1,1 (prawy i górny) ale zaczynając i kończąc na rogach wyświetlonego obrazka
  • character - zależy od wybranej wielkości czcionki
Przy rysowaniu 3-wymiarowym możemy podać trzecią współrzędną np: first 10,3,6.

Komentarze

Pisząc ten artykuł, podczas dodawania komentarzy do wyświetlanych linijek musiałem to robić ręcznie. W związku z tym wpadłem na pomysł by napisać mini-program tworzący komentarze w pewien sposób, może się przydać (nawet nie tylko do komentarzy gnuplota):
C/C++
#include "gnuplot.hpp"

struct Komentarz
    : public CosDoGnuplota
{
    string tresc;
    static string K;
   
    explicit Komentarz( string tresc, int promien_wysokosci_ramki = 0, unsigned promien_szerokosci_ramki = 0, string odstep = " " ) {
        if( promien_wysokosci_ramki > 0 ) {
            wlozWKomentarzSymetryczny( tresc, promien_szerokosci_ramki, odstep );
            string tmp = przestrzenWKomentarzu( tresc.length() - 2 );
            for( int i = 0; i < promien_wysokosci_ramki - 1; ++i )
                 tmp += przestrzenWKomentarzu( tresc.length() - 2, odstep );
           
            tresc = tmp + tresc + "\n" + tmp.substr( tresc.length() + 1 ) + przestrzenWKomentarzu( tresc.length() - 2 );
            this->tresc = tresc;
        }
        else {
            this->tresc = tresc;
            if( promien_szerokosci_ramki > 0 )
                 wlozWKomentarzSymetryczny( this->tresc, promien_szerokosci_ramki, odstep );
            else
                 dodajKrzyzykiPrzed( odstep );
           
        }
    }
   
    string przestrzenWKomentarzu( unsigned szerokosc, string odstep = K ) {
        string out( szerokosc, odstep[ 0 ] );
        out = K + out + K + "\n";
        return out;
    }
   
    void dodajKrzyzykiPrzed( string odstep = " " ) {
        unsigned pozycja = 0;
        tresc.insert( 0, K + odstep );
        while(( pozycja = tresc.find( "\n" ) ) != string::npos )
             tresc.insert( pozycja + 1, K + odstep );
       
    }
   
    void wlozWKomentarzSymetryczny( string & in, unsigned dlugosc = 0, string odstep = K ) {
        if( in.length() < dlugosc ) {
            int polowa =( 2 * dlugosc - in.length() ) / 2;
            string odstep_od_granic( polowa - 1, odstep[ 0 ] );
            if(( 2 * K.length() + 2 * odstep_od_granic.length() + in.length() ) % 2 )
                 in = K + odstep + odstep_od_granic + in + odstep_od_granic + K;
            else
                 in = K + odstep_od_granic + in + odstep_od_granic + K;
           
        }
        else
             in = K + in + K;
       
    }
   
    virtual ostream & drukuj( ostream & o ) const {
        o << tresc << '\n';
    }
};
string Komentarz::K( "#" );

main() {
    ptr_list < CosDoGnuplota > wykresy;
   
    wykresy.push_back( new Komentarz( "zwykly" ) );
    wykresy.push_back( new Komenda( "" ) );
   
    wykresy.push_back( new Komentarz( "wyzszy", 1 ) );
    wykresy.push_back( new Komenda( "" ) );
   
    wykresy.push_back( new Komentarz( "jeszcze wyzszy", 2 ) );
    wykresy.push_back( new Komenda( "" ) );
   
    wykresy.push_back( new Komentarz( "olbrzymi i szeroki", 3, 30 ) );
    wykresy.push_back( new Komenda( "" ) );
   
    wykresy.push_back( new Komentarz( "zapelniony", 0, 30, Komentarz::K ) );
    wykresy.push_back( new Komenda( "" ) );
   
    wykresy.push_back( new Komentarz( "", 0, 30, Komentarz::K ) );
   
    wypisz( wykresy );
} //         g++     gnu4.cc   -o gnu && ./gnu
Pewne rzeczy które chciałbym poruszyć w związku z powyższym kodem:
  • Przypominam że deklarując tym const static w klasie możemy przypisać od razu w jej ciele wartość, niestety dotyczy to tylko typów wbudowanych.
  • Pod każdym komentarzem używam new Komenda(""), gdyż chcę wizualnie oddzielić komentarze od siebie.
Powyższy program wyświetla komentarze w ładny sposób, właśnie tak jak chcieliśmy:

# zwykly

########
#wyzszy#
########


################
#              #
#jeszcze wyzszy#
#              #
################


############################################################
#                                                          #
#                                                          #
#                    olbrzymi i szeroki                    #
#                                                          #
#                                                          #
############################################################


#########################zapelniony#########################

############################################################

Własne super funkcje

Potrzebna jest możliwość łatwego definiowania własnych wyrażeń do funkcji, np: wykres 1/(4+x), ale fajnie byłoby móc to definiować w sposób pozatekstowy, w związku z tym umieszczam "prosty kod" bazujący na szablonach wyrażeń (czyli intuicyjne tworzenie skomplikowanych wyrażeń w oparciu o już istniejące funktory i przeładowane operatory). Aby to zrozumieć odsyłam do Ważniaka:
C/C++
#include "gnuplot.hpp"

template < typename T >
string ts( const T & t ) { // ts = toString
    ostringstream o;
    o << t;
    return o.str();
}
template < typename F, typename A > // A lepiej żeby było double lub string
string wylicz( F f, A df ) {
    ostringstream o;
    o << f( df );
    return o.str();
}
//////////////////////////////// Stale, zmienne, Zmienna X i Funkcje
struct Stala {
    double c;
    Stala( const double & c )
        : c( c )
    { }
    template < typename T >
    string operator ()( const T & x ) {
        return ts( c );
    }
};
struct Zmienna {
    template < typename T >
    string operator ()( const T & x ) {
        return ts( x );
    }
};
struct Funkcja {
    string f;
    Funkcja( string f )
        : f( f )
    { }
    template < typename T >
    string operator ()( T x ) {
        ostringstream o;
        o << f << '(' << x << ')';
        return o.str();
    }
};
struct X {
    template < typename T >
    string operator ()( const T & x ) {
        return ts( x );
    }
};
////////////////////////////////
template < typename Arg >
struct Minus {
    Arg f;
    Minus( Arg f )
        : f( f )
    { }
    string operator ()( double x ) {
        return f( x );
    }
};
template < typename Arg >
Minus < Arg > operator -( const Arg & f ) {
    return Minus < Arg >( f );
}

template < typename L, typename R >
struct OperacjaDwuargumentowa {
    L l;
    R r;
    string znak;
    OperacjaDwuargumentowa( L l, R r, string znak )
        : l( l )
        , r( r )
        , znak( znak )
    { }
    template < typename T >
    string operator ()( T x ) {
        ostringstream o;
        o << '(' << l( x ) << znak << r( x ) << ')';
        return o.str();
    }
};
//////////////////////////////// *
template < typename L, typename R >
OperacjaDwuargumentowa < L, R > operator *( const L & l, const R & r ) {
    return OperacjaDwuargumentowa < L, R >( l, r, "*" );
}
template < typename L >
OperacjaDwuargumentowa < L, Stala > operator *( const L & l, double r ) {
    return OperacjaDwuargumentowa < L, Stala >( l, Stala( r ), "*" );
}
template < typename R >
OperacjaDwuargumentowa < Stala, R > operator *( double l, const R & r ) {
    return OperacjaDwuargumentowa < Stala, R >( Stala( l ), r, "*" );
}
//////////////////////////////// -
template < typename L, typename R >
OperacjaDwuargumentowa < L, R > operator -( const L & l, const R & r ) {
    return OperacjaDwuargumentowa < L, R >( l, r, "-" );
}
template < typename L >
OperacjaDwuargumentowa < L, Stala > operator -( const L & l, double r ) {
    return OperacjaDwuargumentowa < L, Stala >( l, Stala( r ), "-" );
}
template < typename R >
OperacjaDwuargumentowa < Stala, R > operator -( double l, const R & r ) {
    return OperacjaDwuargumentowa < Stala, R >( Stala( l ), r, "-" );
}
//////////////////////////////// +
template < typename L, typename R >
OperacjaDwuargumentowa < L, R > operator +( const L & l, const R & r ) {
    return OperacjaDwuargumentowa < L, R >( l, r, "+" );
}
template < typename L >
OperacjaDwuargumentowa < L, Stala > operator +( const L & l, double r ) {
    return OperacjaDwuargumentowa < L, Stala >( l, Stala( r ), "+" );
}
template < typename R >
OperacjaDwuargumentowa < Stala, R > operator +( double l, const R & r ) {
    return OperacjaDwuargumentowa < Stala, R >( Stala( l ), r, "+" );
}
//////////////////////////////// /
template < typename L, typename R >
OperacjaDwuargumentowa < L, R > operator /( const L & l, const R & r ) {
    return OperacjaDwuargumentowa < L, R >( l, r, "/" );
}
template < typename L >
OperacjaDwuargumentowa < L, Stala > operator /( const L & l, double r ) {
    return OperacjaDwuargumentowa < L, Stala >( l, Stala( r ), "/" );
}
template < typename R >
OperacjaDwuargumentowa < Stala, R > operator /( double l, const R & r ) {
    return OperacjaDwuargumentowa < Stala, R >( Stala( l ), r, "/" );
}
//////////////////////////////// ^ // uznajemy za pow
template < typename L, typename R >
OperacjaDwuargumentowa < L, R > operator ^( const L & l, const R & r ) {
    return OperacjaDwuargumentowa < L, R >( l, r, "**" );
}
template < typename L >
OperacjaDwuargumentowa < L, Stala > operator ^( const L & l, double r ) {
    return OperacjaDwuargumentowa < L, Stala >( l, Stala( r ), "**" );
}
template < typename R >
OperacjaDwuargumentowa < Stala, R > operator ^( double l, const R & r ) {
    return OperacjaDwuargumentowa < Stala, R >( Stala( l ), r, "**" );
}
//////////////////////////////// *
template < typename L >
OperacjaDwuargumentowa < L, X > operator *( const L & l, string r ) {
    return OperacjaDwuargumentowa < L, X >( l, X( r ), "*" );
}
template < typename R >
OperacjaDwuargumentowa < X, R > operator *( string l, const R & r ) {
    return OperacjaDwuargumentowa < X, R >( X( l ), r, "*" );
}
//////////////////////////////// -
template < typename L >
OperacjaDwuargumentowa < L, X > operator -( const L & l, string r ) {
    return OperacjaDwuargumentowa < L, X >( l, X( r ), "-" );
}
template < typename R >
OperacjaDwuargumentowa < X, R > operator -( string l, const R & r ) {
    return OperacjaDwuargumentowa < Stala, R >( X( l ), r, "-" );
}
//////////////////////////////// +
template < typename L >
OperacjaDwuargumentowa < L, X > operator +( const L & l, string r ) {
    return OperacjaDwuargumentowa < L, X >( l, X( r ), "+" );
}
template < typename R >
OperacjaDwuargumentowa < X, R > operator +( string l, const R & r ) {
    return OperacjaDwuargumentowa < X, R >( X( l ), r, "+" );
}
//////////////////////////////// /
template < typename L >
OperacjaDwuargumentowa < L, X > operator /( const L & l, string r ) {
    return OperacjaDwuargumentowa < L, X >( l, X( r ), "/" );
}
template < typename R >
OperacjaDwuargumentowa < X, R > operator /( string l, const R & r ) {
    return OperacjaDwuargumentowa < Stala, R >( X( l ), r, "/" );
}
//////////////////////////////// ^ // uznajemy za pow
template < typename L >
OperacjaDwuargumentowa < L, X > operator ^( const L & l, string r ) {
    return OperacjaDwuargumentowa < L, X >( l, X( r ), "**" );
}
template < typename R >
OperacjaDwuargumentowa < X, R > operator ^( string l, const R & r ) {
    return OperacjaDwuargumentowa < X, R >( X( l ), r, "**" );
}
main() {
    cout << wylicz( 1./( 2.* X() - 3.), "x" ) << endl;
    cout << wylicz( 1./( 2.- 4.6 * Funkcja( "cos" ) ), 4 ) << endl;
    cout << wylicz( 1./( 2.- 4.6 * Funkcja( "cos" ) ), "x" ) << endl;
    cout << wylicz( 1./( 1.+ Zmienna() ) * 2.- 4.6 ^( 1.3 ) * Funkcja( "sin" ) / 2.8, 4.) << endl;
    cout << wylicz( 1./( 1.+ Zmienna() ) * 2.- 4.6 ^( 1.3 ) * Funkcja( "sin" ) / 2.8, "x" ) << endl;
   
    ptr_list < CosDoGnuplota > wykresy;
    wykresy.push_back( new Terminal( Terminal::formaty[ 2 ], "wyjsciowy" ) );
    wykresy.push_back( new WykresFunkcji2( 1./( 2.* X() - 3.), 3 ) );
    wykresy.push_back( new WykresFunkcji2( 1./( 2.* X() - 3.), "x", "moja dziwna funkcja", 4 ) );
    double dane[] = { 2, 4, 6, 8, 7, 5, 3, 1 };
    wykresy.push_back(( new WykresFunkcji2( 1./( 2.* X() - 3.), "x", "jeszcze dziwniejsza funkcja" ) ) );
    wykresy.push_back(( new WykresFunkcji2( X(), "'-'", "z podanymi parametrami" ) )->daneWykresu( dane, sizeof( dane ) / sizeof( double ) ) );
    wypisz( wykresy );
}
Wypisane zostanie coś takiego:

(1/((2*x)-3))
(1/(2-(4.6*cos(4))))
(1/(2-(4.6*cos(x))))
((((1/(1+4))*2)-4.6)**((1.3*sin(4))/2.8))
((((1/(1+x))*2)-4.6)**((1.3*sin(x))/2.8))
Jak widać trochę nam to ułatwia pisanie bardziej skomplikowanych funkcji.

Przykład rysowania własnych funkcji

Dzięki powyższemu mechanizmowi można utworzyć własną klasę rysującą przy pomocy operatorów, string'ów i double'ów. Zanim jednak, drogi czytelniku, popatrzysz na poniższy kod chciałbym dodać, że rysować na podstawie własnych danych można nie tylko z własnego, oddzielnego od komend pliku, ale również z linii komendy pisząc w funkcji plot '-', niestety postępując w ten sposób (tj. podając dane punktów) nie można kombinować manipulując danymi wejściowymi przy pomocy operatorów, czyli wykonując w locie operacje na tych wartościach. Po podaniu wszystkich współrzędnych trzeba umieścić na końcu literę e. Teraz czas na kod:
C/C++
#include "gnuplot.hpp"

struct WykresFunkcji2
    : public WykresFunkcji
{
    mutable string funkcja; // (1)
    list < double > * punkty_w_linii;
   
    template < typename F > // (2)
    WykresFunkcji2( F f, string df = "x", string tytul = "", unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true )
        : WykresFunkcji( tytul, 0, nr_stylu, lt, lw, pt, ps, ch_na_fun )
    {
        punkty_w_linii = NULL;
        ostringstream o;
        o << f( df );
        funkcja = o.str();
        funkcja = funkcja.substr( 1, funkcja.length() - 2 );
    }
   
    ~WykresFunkcji2() {
        delete punkty_w_linii;
    }
   
    CosDoGnuplota * daneWykresu( double tablica[], unsigned ilosc_elementow ) { // (3)
        punkty_w_linii = new list < double >( tablica, tablica + ilosc_elementow );
        return this;
    }
   
    virtual ostream & drukuj( ostream & os ) const {
        ostringstream o;
        static_cast < WykresFunkcji >( * this ).drukuj( o ); // (4)
        string tmp = o.str();
       
        if( punkty_w_linii ) {
            funkcja.insert( 0, "'" ); // (5)
            funkcja.insert( funkcja.length(), "'" );
            tmp.replace( string( "plot " ).length(), string( "sin(x)" ).length(), funkcja ); // (6)
            os << tmp;
           
            os << '\n';
            for( list < double >::iterator it = punkty_w_linii->begin(); it != punkty_w_linii->end(); ++it )
                 os << ' ' << * it << '\n';
           
            os << ' ' << 'e';
        }
        else {
            tmp.replace( string( "plot " ).length(), string( "sin(x)" ).length(), funkcja );
            os << tmp;
        }
       
        os << endl;
        return os;
    }
   
    friend ostream & operator <<( ostream & os, const WykresFunkcji2 & w ) {
        return w.drukuj( os );
    }
};

main() {
    ptr_list < CosDoGnuplota > wykresy;
    wykresy.push_back( new Terminal( Terminal::formaty[ 2 ], "wyjsciowy" ) );
   
    wykresy.push_back( new WykresFunkcji2( 1./( 2.* X() - 3.) ) );
    wykresy.push_back( new WykresFunkcji2( 1./( 2.* X() - 3.), "x", "moja dziwna funkcja", 4 ) );
    double dane[] = { 2, 4, 6, 8, 7, 5, 3, 1 };
    wykresy.push_back(( new WykresFunkcji2( 1./( 2.* X() - 3.), "x", "jeszcze dziwniejsza funkcja" ) ) );
    wykresy.push_back(( new WykresFunkcji2( X(), "'-'", "z podanymi parametrami" ) )->daneWykresu( dane, sizeof( dane ) / sizeof( double ) ) ); // (7)
   
    wypisz( wykresy );
} //    g++     gnu7.cc   -o gnu7 && ./gnu7
Opis paru miejsc w programie:
Nr bledukodopis
1
mutable string funkcja;
tutaj użyłem słówka kluczowego MUTABLE, dla przypomnienia umożliwia zmianę wartości pola klasy w jej metodzie z przydomkiem const
2
template < typename F >
ze względu na to że kompiltor generuje złożony funktor potrzebuję użyć szablonu. Warto zwrócić uwagę na to że ta klasa nie jest szablonem klasy, szablonem jest jej konstruktor. Jest tak ze względu na to że szablony klas nie mają automatycznej dedukcji typów na podstawie argumentów tak jak funkcje
3
CosDoGnuplota * daneWykresu( double tablica[], unsigned ilosc_elementow )
chciałem w sytuacji użycia '-' (czyli współrzędnych punktów w komendzie rysującej) móc dodać te współrzędne, nie ma sensu dodawać kolejnego parametru konstruktora, jak można dołożyć specjalnie do tego funkcje. Jako że wywołuję ją w chwili tworzenia powinna zwracać obiekt kosztem którego jest wywołana
4
static_cast < WykresFunkcji >( * this ).drukuj( o );
mamy już w nadklasie napisaną funkcję drukuj, po co więc pisać ją od nowa, wystarczy rzutować na nadklasę i potem jedynie obciąć ze stringa plot sin(x)
5
funkcja.insert( 0, "'" );
właśnie tutaj bez słówka kluczowego MUTABLE, pojawił by się błąd
6
tmp.replace( string( "plot " ).length(), string( "sin(x)" ).length(), funkcja );
obcięcie o którym wcześniej wspomniałem w (4)
7
wykresy.push_back(( new WykresFunkcji2( X(), "'-'", "z podanymi parametrami" ) )->daneWykresu( dane, sizeof( dane ) / sizeof( double ) ) );
tutaj właśnie dodajemy dane do wykresu w momencie konstruowania
8
wykresy.push_back( new WykresFunkcji2( X(), " 'dane.txt' ", "z pliku z danymi" ) );
nie pisałem kolejnej klasy rysującej na podstawie danych z pliku, gdyż można to zrobić przy użyciu już istniejących, aby wykorzystać już istniejące klasy otoczyć nazwę pliku nie tylko apostrofami, ale również czymś jeszcze
Oraz to co program wyświetli, wraz z rysunkiem przedostatniego wywołania funkcji plot:

set terminal png 
 set out 'wyjsciowy.png'
plot 1/((2*x)-3)
plot 1/((2*x)-3) title 'moja dziwna funkcja' with dots
plot 1/((2*x)-3) title 'jeszcze dziwniejsza funkcja'
plot '-' title 'z podanymi parametrami'
 2
 4
 6
 8
 7
 5
 3
 1
 e
plot 'dane.txt' title 'z pliku z danymi'

Siatka

Dla pewnego ułatwienia, upiękrzenia wykresu, czy nawet podkreślenia pewnych cech charakterystycznych stosuję się siatki, poniżej zamieszczam kod który z tego korzysta:
C/C++
#include "gnuplot.hpp"

struct Siatka
    : public CosDoGnuplota
{
    int styl_linii, typ_linii, szerokosc_linii;
    enum sposoby_ulozenie { layerdefault, front, back };
    const static char ulozenia[ 3 ][ 13 ];
    sposoby_ulozenie sposob_ulozenia;
    int kat_biegunowy;
    bool naKtorychOsiachSiatka[ 3 ];
   
    Siatka( int styl_linii = 0, int typ_linii = 0, int szerokosc_linii = 0, sposoby_ulozenie sposob_ulozenia = layerdefault, int kat_biegunowy = 0, bool osX = true, bool osY = true )
        : styl_linii( styl_linii )
        , typ_linii( typ_linii )
        , szerokosc_linii( szerokosc_linii )
        , sposob_ulozenia( sposob_ulozenia )
        , kat_biegunowy( kat_biegunowy )
    {
        naKtorychOsiachSiatka[ 0 ] = osX & osY;
        naKtorychOsiachSiatka[ 1 ] = osX;
        naKtorychOsiachSiatka[ 2 ] = osY;
    }
   
    virtual ostream & drukuj( ostream & os ) const {
        os << "set grid ";
        if( !naKtorychOsiachSiatka[ 0 ] ) {
            if( naKtorychOsiachSiatka[ 1 ] )
                 os << "xtics ";
            else
                 os << "noxtics ";
           
            if( naKtorychOsiachSiatka[ 2 ] )
                 os << "ytics ";
            else
                 os << "noytics ";
           
        }
        if( sposob_ulozenia != layerdefault )
             os << ulozenia[ sposob_ulozenia ] << ' ';
       
        if( kat_biegunowy != 0 )
             os << "polar " << kat_biegunowy << ' ';
       
        if( styl_linii )
             os << "linestyle " << styl_linii << ' ';
        else {
            if( typ_linii )
                 os << "linetype " << typ_linii << ' ';
           
            if( szerokosc_linii )
                 os << "linewidth " << szerokosc_linii << ' ';
           
        }
       
        os << endl;
        return os;
    }
};
const char Siatka::ulozenia[ 3 ][ 13 ] = { "layerdefault", "front", "back" };

main() {
    ptr_list < CosDoGnuplota > wykresy;
    wykresy.push_back( new Siatka );
    wykresy.push_back( new Siatka( 3 ) );
    wykresy.push_back( new Siatka( 0, 3, 5, Siatka::back ) );
    wykresy.push_back( new Siatka( 2, 3, 5, Siatka::front, 20, true, false ) );
    wypisz( wykresy );
} //    g++     gnu8.cc   -o gnu8 && ./gnu8
Oraz wydruk z dodanymi przeze mnie komentarzami opisującymi:

set grid # zwykłe dodanie siatki ze standardowymi opcjami, najczęściej wystarcza
set grid linestyle 3 # tutaj już podajemy styl linii
set grid back linetype 3 linewidth 5 # chcemy aby siatka była pod wszystkim innym, zamiast stylu linii podajemy jej typ i szerokość
set grid xtics noytics front polar 20 linestyle 2 # tutaj zaznaczamy że nie chcemy siatki na osi Y, siatka ma być z przodu wszystkiego. Używamy również słówka POLAR, po którym podajemy kąt od punktu (0,0), dzięki temu widzimy kółka rysowane na przedziałkach osi, poniżej screen to odzwierciedla

Wiele wykresów na jednej stronie

Już rysowaliśmy wiele wykresów, natomiast czasami dla porównania potrzebne jest narysowanie większej liczby funkcji, załączam poniżej kod ilustrujący właśnie opisywane użycie:
C/C++
#include "gnuplot.hpp"
#include <boost/algorithm/string.hpp> // do trim

struct Wielorysunek
    : public CosDoGnuplota
{
    ptr_list < WykresFunkcji > wykresy;
   
    Wielorysunek() { }
   
    Wielorysunek( unsigned nr_funkcji, double * zakrX = NULL, double * zakrY = NULL, string tytul = "", unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true ) {
        wykresy.push_back( new WykresFunkcji( nr_funkcji, zakrX, zakrY, tytul, nr_stylu, lt, lw, pt, ps, ch_na_fun ) );
    }
   
    Wielorysunek( string tytul, unsigned nr_funkcji, unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true ) {
        wykresy.push_back( new WykresFunkcji( nr_funkcji, NULL, NULL, tytul, nr_stylu, lt, lw, pt, ps, ch_na_fun ) );
    }
   
    virtual ostream & drukuj( ostream & os ) const {
        string druk( "plot " );
       
        for( ptr_list < WykresFunkcji >::const_iterator it = wykresy.begin(); it != wykresy.end(); ++it ) {
            ostringstream o;
            it->drukuj( o );
            string tmp = o.str();
            druk += tmp.substr( string( "plot " ).length() );
            druk.append( ", " );
        }
        druk = trim_right_copy( druk ); // odcina z końca białe znaki
        trim_right_if( druk, is_any_of( "," ) ); // odcina z końca przecinki
       
        os << druk;
       
        os << endl;
        return os;
    }
    void dodajWykres( unsigned nr_funkcji, double * zakrX = NULL, double * zakrY = NULL, string tytul = "", unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true ) {
        wykresy.push_back( new WykresFunkcji( nr_funkcji, zakrX, zakrY, tytul, nr_stylu, lt, lw, pt, ps, ch_na_fun ) );
    }
    void dodajWykres( string tytul, unsigned nr_funkcji, unsigned nr_stylu = 0, double lt = 0, double lw = 0, double pt = 0, double ps = 0, bool ch_na_fun = true ) {
        wykresy.push_back( new WykresFunkcji( nr_funkcji, NULL, NULL, tytul, nr_stylu, lt, lw, pt, ps, ch_na_fun ) );
    }
};

main() {
    ptr_list < CosDoGnuplota > wykresy;
    double zX[] = { 0, 10 }, zY[] = { - 10, 10 };
    wykresy.push_back( new Ustawienia( "trygonometria", true, zX, zY ) ); // warto ustawić zakresy osi
   
    Wielorysunek * w = new Wielorysunek( "sinus", 0, 2, 1 );
    w->dodajWykres( "cosinus", 1, 2, 2 );
    w->dodajWykres( "tangens", 2, 2, 3 );
    w->dodajWykres( "exponenta", 3, 2, 4 ); // wiem że to nie funkcja trygonometryczna, ale chcę ją również umieścić na stronie
    wykresy.push_back( w );
   
    wypisz( wykresy );
} //    g++     gnu9.cc   -o gnu9 && ./gnu9
Wydruk i rysunek:

reset
set title 'trygonometria'
set xrange [0:10]
set yrange [-10:10]
plot sin(x) title 'sinus' with linespoints linetype 1.00 , cos(x) title 'cosinus' with linespoints linetype 2.00 , tan(x) title 'tangens' with linespoints linetype 3.00 , exp(x) title 'exponenta' with linespoints linetype 4.00

Dodatkowe możliwości programu (czasami przydatne)

Teraz wymienię jeszcze inne, ciekawe funkcje programu gnuplot, bez podawania kodu w C++ tym zarządzającego.

Własne funkcje

Mamy pewne funkcje wbudowane, możemy również w locie/w komendzie plot modyfikować nasze wyrażenie, a ponadto jak zajdzie potrzeba posiadania własnej funkcji - również taką mamy:
sino_tan(x) = 10*sin(x)*tan(x)
plot sino_tan(x)

Możemy również definiować własne zmienne:
a = 2.4
b = a/2

Numeracjafirst 5,0.5typename (X(lobciąć), r,  L, typename R osi X, Y, Z

Fajną możliwością jest ustawianie sposobu numeracji osi, np:

set xtics 0,2,10 # numeracja od 0 do 10 skacząc co 2
set xtics 3      # numeracja co 3
set xtics offset 2,0.5 # przesunięcie nazw wartości na osi X
set xrange [1:1e8]; set logscale x; set xtics 1,100,1e8 # można też ustawiać numerację w skali logarytmicznej, ale trzeba pamiętać aby mieć zakres osi nie mniejszy niż 1
set ytics ("max" 1, "srodek" 0, "min" -1) # można ponazywać poszczególne punkty (nazwy podane przeze mnie pasują do sin(x)
set xtics (0,1,1,2,3,5,8,13) # tutaj wymieniam wszystkie wartości (w moim przypadku są to liczby Fibonacciego
set xtics add ("Pi" pi) # po ustawieniu osi możemy dodać jeszcze kolejna wartość
unset xtics # jak nie chcemy w ogóle numeracji osi X

Własne style linii

Jak wiemy możemy używać przy rysowaniu funkcją plot (i nie tylko) linestyle, możemy również podawać linewidth i linetype. Warto sobie w innym miejscu zdefiniować style linii, jeżeli planuje się z nich korzystać wielokrotnie:
set linestyle 20 linetype 4 linewidth 3
a potem użyć plot tan(x) linestyle 20

Pauza, czekanie

Jeżeli chcemy symulować animację podobną do tej na początku artykułu możemy między poszczególnymi poleceniami rysowania wstawić komendę pause {licza_sekund}, (np: pause 2). Czekanie jest przydatne przy przetwarzania wsadowym, gdy zależy nam jedynie na wyświetleniu wyniku, bez zapisu do pliku - jak robimy to normalnie: gnuplot "plik_komenda.g" wtedy nam niemalże od razu po pokazaniu wykres znika, dzięki czekaniu możemy pooglądać wykres dłużej. Dodam że problem ze znikaniem pojawia się jedynie przy przetwarzaniu z konsoli, gdy wywołuję w moich programach
system( "gnuplot " plik_komenda.g "" );
 nic samodzielnie nie znika.

Wybór kolumny źródłowej

Czasami zachodzi potrzeba narysowania wykresu na podstawie pliku, który ma wiele kolumn, wystarczy wtedy wpisać:
plot 'dane.txt' using 1:5
co ciekawsze można tego użyć wykorzystując napisany przeze mnie wcześniej kod:
wykresy.push_back( new WykresFunkcji2( X(), " 'dane.txt' using 1:5 ", "z pliku z danymi" ) );

Dla osób chętnych do dalszego poznawania funkcji programu Gnuplot zachęcam do zobaczenia bardzo dobrej, polskiej strony opisującej ten program

Na zakończenie załączam kompletną bibliotekę, którą pisałem równolegle do tego artykułu: gnuplot.h, dla chętnych wszystkie kody źródłowe. name=