Generator wykresów w Gnuplot w GTKmm
08.09.2012 22:31
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.
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ą [code=cpp]#define UI_FILE "src/gnuplot_graph__generator.ui"[/code]
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.