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:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<unistd.h>
FILE * otworzPotokGnuplota() {
FILE * g_potok = popen( "gnuplot", "w" );
return g_potok;
}
void funkcja( FILE * potok, char * jaka, char * a, char * b ) {
fprintf( potok, "plot %s(%s*x+%s)\n", jaka, a, b );
}
int main() {
FILE * gnupotok = otworzPotokGnuplota();
double i;
char buf[ 5 ];
for( i = 0; i < 10; i += 0.1 ) {
sprintf( buf, "%f", i );
funkcja( gnupotok, "sin", buf, "3" );
fflush( gnupotok );
usleep( 10000 );
}
pclose( gnupotok );
return 0;
}
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
#include <iostream>
#include <fstream>
#include <iomanip>
#include <boost/ptr_container/ptr_list.hpp>
using namespace std;
using namespace boost;
struct CosDoGnuplota {
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 ];
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 );
}
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 ) {
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 {
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 ) {
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 );
}
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:
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'
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.
#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 ) {
}
else if( chciane_kolory == NULL )
for( unsigned i = 0; i < ilosc; ++i )
kolory.push_back( generujKolor() );
else
for( unsigned i = 0; i < ilosc; ++i )
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;
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 );
}
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:
#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 ];
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 ] ) {
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, ", " ) );
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 );
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 );
}
Opis paru miejsc programu:
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ł:
#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 );
}
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:
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
#include "gnuplot.hpp"
struct Komenda
: public CosDoGnuplota
{
string tresc;
Komenda( string tresc )
: tresc( tresc )
{ }
virtual ostream & drukuj( ostream & o ) const {
o << tresc << '\n';
}
};
struct Strzalka
: public CosDoGnuplota
{
unsigned tag_nazwy;
typedef pair < double, double >* ptr_pair;
ptr_pair zakres_od, zakres_do;
enum typ_pozycji { zero, first, second, graph, screen, character };
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;
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;
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 );
}
Czas wyjaśnić parę miejsc programu:
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
Sposób reprezentacji współrzędnych: "first", "second", "graph", "screen", "character"
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):
#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 );
}
Pewne rzeczy które chciałbym poruszyć w związku z powyższym kodem:
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:
#include "gnuplot.hpp"
template < typename T >
string ts( const T & t ) {
ostringstream o;
o << t;
return o.str();
}
template < typename F, typename A >
string wylicz( F f, A df ) {
ostringstream o;
o << f( df );
return o.str();
}
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, "/" );
}
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, "/" );
}
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:
#include "gnuplot.hpp"
struct WykresFunkcji2
: public WykresFunkcji
{
mutable string funkcja;
list < double > * punkty_w_linii;
template < typename F >
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 ) {
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 );
string tmp = o.str();
if( punkty_w_linii ) {
funkcja.insert( 0, "'" );
funkcja.insert( funkcja.length(), "'" );
tmp.replace( string( "plot " ).length(), string( "sin(x)" ).length(), funkcja );
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 ) ) );
wypisz( wykresy );
}
Opis paru miejsc w programie:
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:
#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 );
}
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:
#include "gnuplot.hpp"
#include <boost/algorithm/string.hpp>
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 );
trim_right_if( druk, is_any_of( "," ) );
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 ) );
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 );
wykresy.push_back( w );
wypisz( wykresy );
}
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=