Strona używa cookies (ciasteczek). Dowiedz się więcej o celu ich używania i zmianach ustawień. Korzystając ze strony wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.    X

Generator wykresów w Gnuplot w GTKmm

Tematem tego wpisu jest opracowanie podstawowych założeń takiego generatora i napisanie go z użyciem GTKmm 3+. Na początek parę informacji związanych z Gnuplotem:

  • Zanim wygenerujesz ładny wykes, musisz się namęczyć z poleceniami
  • „Klepanie poleceń” i tego typu rzeczy dzieją się głównie w trybie interaktywnym, w którym ustala się najwięcej parametrów takiego wykresu
  • Możesz wyskalować osie dzięki włączeniu automatycznego skalowania
  • Możesz włączyć skalę logarytmiczną na osi X lub Y
  • Możesz nazwać linie i punkty na wykresie, osie, ustawić tytuł wykresu itd.

Może dla niektórych wyżej wymienione rzeczy wydają się oczywiste, ale mają znaczenie przy naszym zadaniu. Naszym zadaniem jest przyporządkowanie tych poleceń odpowiednim zdarzeniom w naszym generatorze. Aby ułatwić sobie zadanie, będziemy generować skrypt, który będzie przechowywać te wszystkie polecenia niezbędne do utworzenia wykresu. Ten skrypt wczytamy jako parametr gnuplota i dostaniemy wykres w jego oknie.

Po co nam ten generator?!

Ten program ma ułatwić generowanie wykresów na komputerkach z gnuplotem i ułatwić wykorzystanie jego potencjału tym wszystkim, którzy potrzebują wykresów, np. do zrobienia sprawozdania z laborek z (tu wstawcie nazwę przedmiotu), a nie mają czasu lub nie mogą bądź nie chcą uczyć się kolejnego języka programowania (tu skryptów).

Co będzie potrzebne?

  • Zainstalowany w systemie i działający Gnuplot,
  • Kompilator i biblioteka Gtkmm
  • Edytor tekstu
  • Do sprawdzenia poprawności generowania wykresów: plik z danymi do wykresu

Jeśli chodzi o wymagania dotyczące pliku z danymi do wykresu, to jedną kolumnę trzeba zarezerwować na oś X, drugą na oś Y, ewentualnie trzecią na błędy pomiarowe. Kolumny oddzielamy spacją lub tabulatorem, a dane dla kolejnych punktów są w wierszach.

Przykładowy plik z danymi będzie dostępny pod tym linkiem.

Interfejs

Opis interfejsu zacznę od najwyższego poziomu, a skończę na najniższych. Wykorzystamy układ Box (Pudełko) w rozmieszczeniu pionowym. Użyjemy 16 elementów. Pierwsze trzy będą w układzie Box w rozmieszczeniu poziomym i zawierają dwa elementy: etykietę i liniowe wejście tekstowe (GtkEntry).

Następnie umieszczamy etykietę tekstową, a pod nią w następnej komórce układu układ poziomy (GtkBox) z dwiema etykietami i liniowymi polami tekstowymi (GtkEntry) ułożonymi na przemian. Powtarzamy dwukronie.

Kolejnym elementem jest przycisk, a następnie pole do zaznaczenia (GtkCheckBox) wraz z położonym poniżej liniowym wejściem tekstowym.

Następne dwa elementy są takie jak te trzy pierwsze, a następnie dwa pola do zaznaczenia (ptaszkiem) ułożone kolejno po sobie w pionie.

Na koniec idzie przycisk.

[img=projinterfesju]

Etykiety tekstowe po kolei: Graph title, X axis label, Y axis label, X range, Begin, End, Y Range, Begin, End, Load data file, Graph label, Fit function below to loaded data, Fitting function label, Parameters, Draw linear regression, Show grid, Generate graph

Kod

Zanim zaczniemy dalsze programowanie, należy dorzucić nagłówki fstream oraz cstdlib. UWAGA: jeśli ktoś użyje innego sposobu uruchomienia Gnuplota z poziomu tego programu, użyć innego nagłówka, który zapewnia odpowiednią funkcję.

Jeżeli projekt tworzymy z pomocą Anjuta, pod linijką #define UI_FILE "src/gnuplot_graph__generator.ui"umieszczamy deklaracje zmiennych. Użyjemy typów Gtk::Entry, Gtk::Button, Gtk::CheckButton, Glib::ustring, bool.

Gtk::Entry *title, *xlabel, *ylabel, *xstart, *ystart, *xend, *yend, *graphlabel, *fitfun, *fitfunlab, *vars; Gtk::Button *load_data, *generate; Gtk::CheckButton *enfitting, *linreg, *grid; Glib::ustring data_file_filename; bool with_linreg = false; bool with_fitting = false;

Po deklaracji zmiennych, możemy przystąpić do deklaracji funkcji. Najpierw przyjrzymy się funkcji odpowiedzialnej za włączenie dopasowania funkcji do danych:

void enable_fitting() { if(!fitfun->get_sensitive()) { fitfun->set_sensitive(); fitfunlab->set_sensitive(); vars->set_sensitive(); with_fitting = true; } else { with_fitting = false; fitfun->set_sensitive(false); fitfunlab->set_sensitive(false); vars->set_sensitive(false); } }

W funkcji sprawdza się, czy jest dostępne dla użytkownika pole zawierające wzór funkcji dopasowania do wykresu. Jeśli nie, zostaje włączone (właściwość sensitive == true), a wraz z nim pole pozwalające na definicję etykiety dla takiej funkcji (będzie widoczna w legendzie wykresu) oraz pole zawierające parametry funkcji dopasowania do danych — bez parametrów taka funkcja nie zadziała prawidłowo w Gnuplot. Ponadto wartość with_fitting zostanie ustawiona na „true”

Jeśli jest inaczej, wszystkie wskazane wyżej elementy interfejsu będą całkowicie wyłączone z użytku, a wartość with_fitting zostanie ustawiona na „false”.

Następna funkcja ma za zadanie zmienić wartość with_linreg z „false” na „true” lub na odwrót:

void enable_linreg() { if(!with_linreg) with_linreg = true; else with_linreg = false; }

Ta funkcja obsługuje zdarzenie kliknięcia na przycisk z etykietą „Load data file” i jej zadaniem jest otwarcie okna dialogowego otwierania pliku i ustalenie nazwy wybranego pliku z danymi.

void open_data_file() { Gtk::FileChooserDialog open_file("Open data file"); open_file.add_button(Gtk::Stock::OK, Gtk::RESPONSE_ACCEPT); open_file.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); if(open_file.run()) { data_file_filename = open_file.get_filename(); } }

Metoda add_button() pozwala na dodanie do dialogu dwóch przycisków. Pierwszy argument to etykieta (tu z zestawu standardowych), a drugi to sygnał wysyłany po kliknięciu.

Ostatnia funkcja, którą dodamy to poniższa funkcja:

void generate_graph() { double xs = std::strtod(xstart->get_text().c_str(),NULL); double xe = std::strtod(xend->get_text().c_str(),NULL); double ys = std::strtod(ystart->get_text().c_str(),NULL); double ye = std::strtod(yend->get_text().c_str(),NULL); Glib::ustring xrange = Glib::ustring::compose("set xrange[%1:%2]\n", Glib::ustring::format(xs), Glib::ustring::format(xe)); Glib::ustring yrange = Glib::ustring::compose("set yrange[%1:%2]\n", Glib::ustring::format(ys), Glib::ustring::format(ye)); Glib::ustring gtitle = Glib::ustring::compose("set title \"%1\"\n", title->get_text()); Glib::ustring gxlabel = Glib::ustring::compose("set xlabel \"%1\"\n", xlabel->get_text()); Glib::ustring gylabel = Glib::ustring::compose("set ylabel \"%1\"\n", ylabel->get_text()); Glib::ustring plot = Glib::ustring::compose("plot '%1' using 1:2 title '%2'", data_file_filename, graphlabel->get_text()); Glib::ustring ffun = Glib::ustring::compose("f(x)=%1;\nfit f(x) '%2' using 1:2 via %3\n", fitfun->get_text(), data_file_filename, vars->get_text()); Glib::ustring glinreg = Glib::ustring::compose("g(x)=aa*x+bb;\nfit g(x) '%1' using 1:2 via aa, bb\n", data_file_filename); std::ofstream script("/tmp/generating-script"); script << "set autoscale\n"; script << "unset log\n"; script << "unset label\n"; script << gtitle; script << gxlabel; script << gylabel; script << xrange; script << yrange; if(grid->get_active()) { script << "set grid\n"; } if(with_fitting == true && with_linreg == true) { script << ffun; script << glinreg; script << Glib::ustring::compose("%1, \\\nf(x) title '%2', \\\ng(x) title 'Linear regression'\n", plot, fitfunlab->get_text()); } if(with_fitting == true && with_linreg == false) { script << ffun; script << Glib::ustring::compose("%1, \\\nf(x) title '%2'\n", plot, fitfunlab->get_text()); } if(with_fitting == false && with_linreg == true) { script << glinreg; script << Glib::ustring::compose("%1, \\\ng(x) title 'Linear regression'\n", plot); } if(with_fitting == false && with_linreg == false) { script << plot << std::endl; } script.close(); system("gnuplot -persist /tmp/generating-script"); }

Pierwsza część: zamieniamy na liczby zakres osi X i Y.
Druga część: tworzymy linijki tekstu zawierające polecenia dla programu Gnuplot. Wykorzystujemy tutaj funkcję Glib::ustring::compose(const Glib::ustring format, ...) do formatowania tekstu. Do formatowania liczb (zamiany ich na Glib::ustring) używana jest funkcja Glib::ustring::format().

Trzecia część: Tworzymy strumień wyjściowy w pliku „/tmp/generating-script”, a następnie zapisujemy do niego linie tekstu. Używamy również funkcji sprawdzających włączenie określonych opcji i zawarcie ich w generowanym pliku. Na koniec zostaje zamknięty.

Czwarta część: Wywołujemy program Gnuplot funkcją system(const char*) i używamy trybu wsadowego (pliku skryptu, który utworzyliśmy). Parametr „-persist” oznacza, że nie zostanie zamknięte okno z wykresem, który Gnuplot wygeneruje, dzięki temu możemy obejrzeć wykres i zapisać.

Kompilacja i uruchomienie

Kompilacja jest typowa dla prostych programów wykorzystujących GTKmm, więc w Anjuta wystarczy domyślna konfiguracja dla tego typu projektów. Po uruchomieniu powinno pojawić się takie okno:

Dla przykładowych danych można wygenerować następujący wykres:

Podsumowanie

Taki generator co prawda nie zapewnia pełnej obsługi funkcji oferowanych przez Gnuplota, ale pozwala na wykonanie wykresów opierających się na danych doświadczalnych z pliku wraz z nazwami osi, tytułem, możliwością dopasowania funkcji wprowadzonej przez użytkownika do wczytanych danych oraz włączenia rysowania regresji liniowej — aproksymacji liniowej funkcji z punktami danymi w pliku.

Przy okazji pozwoli na opanowanie podstaw tworzenia skryptów dla Gnuplota w celu usprawnienia generowania wykresów. 

porady programowanie hobby

Komentarze

0 nowych
michal7642   3 #1 10.09.2012 15:22

Bardzo fajny wpis. Czyta się go przede wszystkim dobrze. Mam wrażenie, że podszedłeś do tematu profesjonalnie za co dodatkowy plus. Podoba mi się również temat, na pewno zdobyłem na tym polu dodatkową wiedzę. Pozdrawiam i proszę o więcej...

Autor edytował komentarz.
GBM MODERATOR BLOGA  19 #2 10.09.2012 19:50

Dobre ! :)

W miarę możliwości, zamieść więcej takich wpisów :)

Jaahquubel_   12 #3 11.09.2012 10:21

Fajne narzędzie. I wpis przydatny, w razie miałbym potrzebę zrobić jakiś GUI w linuksie.

Ale ja bym taki wykres zrobił inaczej - w Octave'ie (wybacz, nie mogłem się oprzeć ;P)
a=load('samochod.txt');
x=a(:,1); y=a(:,2);
plot(x,y,'r+'); hold on; line([min(x),max(x)],polyval(polyfit(x,y,1),[min(x),max(x)]),'color','g'); hold off
title('Speed'); xlabel('Velocity'); ylabel('Time'); grid on; axis([0 10 0 10]); legend('Measured speed','Linear regression');

Efekt:
http://images44.fotosik.pl/396/0ee640a3414efc95.png

mikolaj_s   13 #4 12.09.2012 09:13

Jakoś nie przypadł mi do gustu cały Gtkmm. Jak się patrzy na wygląd kodu to jakoś tak się niedobrze robi ;) Pisałem kiedyś sporo na QT i tam kod wygląda znacznie lepiej. Chociaż nie podoba mi się z kolei działanie qmake który wykonuje magiczne sztuczki za moimi plecami.
A ponieważ muszę napisać mini programik pod Gtk to wolałem wybrać Valę. Próbowałem też Seeda, ale jakoś tak ciężko się pisze. Czy Gtkmm podąża za nazwami funkcji z oryginalnej biblioteki Gtk w C, tak jak Vala i Seed?