Dokumentacja programu semestralnego - rysowanie wykresów funkcji i ich pochodnych

Michał Rudowicz
nr indeksu: 171047
Ćwiczenia prowadził: mgr inż. Karol Puchała
Kurs: Programowanie Obiektowe INEW00002
Wykładowca: dr hab. inż., prof. nadzw. Jerzy Kisilewicz

Używanie programu

Program składa się z dwóch składników. Główny z nich, rysuj.exe, pozwala na rysowanie wykresów funkcji. Drugi, cli-onp.exe, służy do testowania mechanizmu obliczania wyników wyrażeń.

cli-onp.exe

Program ten uruchamia się w konsoli tekstowej. Oczekuje na wprowadzenie wyrażenia matematycznego, po czym wypisuje wynik obliczeń bądź też komunikat błędu, jeśli coś się nie uda.
Konsola z uruchomionym cli-onp.exe

rysuj.exe

Wprowadzanie danych do programu

Po uruchomieniu programu ukazuje się okno wraz z wykresem i kontrolkami umożliwiającymi rysowanie wykresu.
Kontrolki w programie rysuj.exe
W pierwszej kontrolce, opisanej jako Wzór należy wpisać wyrażenie matematyczne, według którego program ma narysować wykres. Należy pamiętać o ograniczeniach w obecnej wersji programu - liczby ujemne należy wpisywać nie jako -x, ale 0-x1). Dodatkowo nie należy stosować skrótów, na przykład 3*x jest poprawne, ale 3x już nie. Trzeba również stosować pełne nawiasowanie, chociażby cosx zostanie wykryte przez program jako niepoprawny znak, wobec czego należy stosować cos(x).

Zakres to dwie liczby oznaczające, od którego do którego argumentu program ma rysować wykres.

Rozdzielczość oznacza ilość próbek, które program przeliczy w celu narysowania wykresu. Domyślnie, w celu zachowania gładkości obrazu liczba ta jest ustalona na 500, czyli tyle, ile wynosi szerokość wykresu. Minimalną wartością, którą można tam wpisać, jest 2. Program zawsze liczy o jedną próbkę więcej, niż wynosi wartość w polu Rozdz.

Obsługa błędów

W przypadku wykrycia błędu program w miejscu rysowania wykresu wyświetli stosowny komunikat z żółtym trójkątem w tle, mającym na pierwszy rzut oka ostrzec użytkownika. W chwili obecnej rozpoznawane są 3 rodzaje błędu:

  • Błąd nawiasowania - polegający na złym rozmieszczeniu nawiasów. Najprawdopodobniej któryś z nawiasów nie został zamknięty.
  • Nieznany znak - program wykrył w wyrażeniu nieznany znak. Bardzo możliwe, że użytkownik chciał użyć nie zaimplementowanej jeszcze funkcji, bądź zapomniał o odpowiednich operatorach ( sinx jak i 3x są nieprawidłowe, należy zamienić je na odpowiednio: sin(x) i 3*x)
  • Nieznany błąd - obejmuje wszystkie pozostałe błędy, teoretycznie nie powinien się nigdy pojawić.

Schemat blokowy rysowania wykresów

Schemat blokowy rysowania wykresów

Klasa Znak

Klasa Znak jest wykorzystywana do przechowywania znaków. Składa się z dwóch zmiennych typu double: Rodzaj i Wartość. Rodzaj określa rodzaj znaku (np. Operator, Funkcja, Nawias, Liczba, Zmienna bądź Błąd), natomiast wartość może określać wartość liczby bądź np. rodzaj operatora (mnożenie, dzielenie itp.). Obiekt tej klasy może mieć następujące znaczenie:

  • Gdy rodzaj jest równy -1, to znak oznacza błąd.
  • Gdy rodzaj jest równy 0, to znak oznacza liczbę. Jej wartość jest przechowywana w zmiennej Wartość.
  • Gdy rodzaj jest równy 1, to znak oznacza zmienną.
  • Gdy rodzaj jest równy 2, to znak jest operatorem. Wartość określa rodzaj operatora, jaki kryje się w obiekcie.

Wartość może przyjmować następujące wartości: FIXME dodać tę tabelę

  • Gdy rodzaj jest równy 3, to znak jest funkcją. Wartość określa, jaka funkcja kryje się w obiekcie.

Możliwe wartości: FIXME dodać tę tabelę

Wszystkie zmienne składające się na tę klasę są publiczne, ponieważ jest ona jedynie „kontenerem” dla nich. Klasa ta posiada również następujące funkcje:

Operator przypisania

Znak& operator=(const string);

Argument, będący obiektem typu string, jest rozpoznawany i zmienne wchodzące w skład obiektu są odpowiednio ustawiane. Jeśli nie uda się rozpoznać znaku, to wtedy rodzaj jest ustawiany jako -1 (błąd).

opWeight()

unsigned int opWeight(void);

Zwraca wagę operatora na podstawie wartości zmiennej Wartość (czyli nie rozróżnia, czy znak jest w rzeczywistości operatorem, funkcją czy liczbą). Im mniejsza zwracana wartość, tym mniejsza waga operatora.

Klasa Stosik i klasy pochodne

Klasa ta jest klasą bazową dla klas StosikZnakowy i StosikDouble. Jest klasą abstrakcyjną, więc nie można tworzyć obiektów tej klasy.
Jakiś dziwny graf, niepotrzebnie zajmujący miejsce w dokumentacji

Klasa StosikDouble

Obiekt ten jest implementacją stosu znanego z Asemblera, pozwalającym przechowyw ać obiekty typu double. Dziedziczy po klasie Stosik. Zawiera jedną prywatną zmienną, będącą wskaźnikiem na double (gdzie tworzona jest dynamiczna tablica).

Push

void push(double);
void push(int);

Funkcja ta dodaje na koniec stosu element typu double. Dla wygody użytkownika może przyjąć zarówno liczbę typu double, jak i typu int (która jest automatycznie konwertowana do typu double).

Peek

double peek(void);

Pobiera element z wierzchu stosu, ale nie usuwa go (czyli jedynie podgląda, a nie pobiera).

Pop

double pop(void);

Pobiera element z wierzchu stosu i usuwa go stamtąd.

Klasa StosikZnakowy

Obiekt ten jest implementacją stosu znanego z Asemblera, pozwalającym przechowyw ać obiekty klasy Znak. Dziedziczy po klasie Stosik. Zawiera jedną prywatną zmienną, będącą wskaźnikiem na Znak (gdzie tworzona jest dynamiczna tablica).

Push

void push(Znak);
void push(double);
void push(int,double);

Funkcja ta dodaje na koniec stosu element typu Znak. Jeśli jako argument dana jest jedynie wartość Double, to przyjmowane jest, że nowy znak jest rodzaju liczbowego. Jeśli jako argument podaje się liczbę int i liczbę double, to tworzony jest znak o rodzaju takim, jak pierwszy argument i wartości takiej, jak drugi argument.

Peek

Znak peek(void);

Pobiera element z wierzchu stosu, ale nie usuwa go (czyli jedynie podgląda, a nie pobiera).

Pop

Znak pop(void);

Pobiera element z wierzchu stosu i usuwa go stamtąd.

Klasa Rownanie

Klasa ta zawiera wskaźnik na znak (wskazujący na dynamiczną tablicę znaków) oraz liczbę typu unsigned int, zawierającą informacje o ilości znaków w tablicy. Tablica zawiera znaki już ułożone w kolejności zgodnej z regułami Odwrotnej Notacji Polskiej.

dodajZnak

void dodajZnak(Znak);

Funkcja ta dodaje znak przekazany jako argument na koniec tablicy znajdującej się w klasie. Głównie jest ona wykorzystywana wewnątrz innych metod klasy.

oblicz

double oblicz(double);

Funkcja ta zwraca wyliczoną wartość wyrażenia. Argument jest używany jako wartość zmiennej w równaniu.

wczytajInfiks

int wczytajInfiks(string*,int);

Funkcja może przyjmować jako argument wskaźnik na obiekt string, pod którym kryje się dynamiczna tablica obiektów typu string i liczbę typu int oznaczającą ilość elementów w tablicy. Zakłada się tutaj, że każdy element tablicy string zawiera tylko jeden znak, to znaczy, że albo zostały od razu oddzielnie podane, albo zostały rozdzielone. Kolejność znaków w tej tablicy jest taka, jak w normalnej, infiksowej notacji, czyli identycznie, jak normalny człowiek zapisuje wyrażenie matematyczne.

Funkcja może zwracać następujące wartości:
FIXME dodać tabelkę
Ze względu na konieczność rozdzielania znaków w wyrażeniu istnieje również przeładowana wersja tej funkcji, przyjmująca jako argument obiekt typu string. Dzieli ona wyrażenie zawarte w argumencie i wywołuje oryginalną wersję funkcji, zwracając taką samą wartość jak ona.

Klasa wykres

Klasa ta zawiera w sobie dwie tablice dynamiczne zawierające wartości i argumenty funkcji. Na ich podstawie rysowany jest wykres.

Konstruktor

double* wartosci, double* argumenty, unsigned int n

Pierwszym argumentem jest tablica double zawietająca wartości funkcji, drugim - tablica double zawierająca argumenty funkcji, a trzecim - liczba elementów w tych tablicach.

string wyrazenie, double zod, double zdo,unsigned int rozdzielczosc

Pierwszym argumentem jest wyrażenie matematyczne, kolejne dwa określają dolną i górną granicę zakresu. Czwartym jest rozdzielczość, czyli ilość punktów, które konstruktor wyliczy.

rysuj

void rysuj(cairo_t* cr, double* kolor);

Funkcja ta rysuje wykres na zadanym w pierwszym argumencie kontekście, używając koloru wyrażonego w tablicy double, wskaźnikiem na którą jest drugi argument. Pierwszym elementem powinien określać kolor czerwony, drugim zielony, a ostatnim - niebieski.

rysujPochodna

void rysujPochodna(cairo_t* cr, double* kolor);

Funkcji tej używa się tak samo, jak funkcji rysuj, ale rysuje ona nie wykres samej funkcji, a jej pochodnej. Poniższy schemat obrazuje sposób działania tej funkcji:

Biblioteka Cairo

Ze względu na małe możliwości funkcji rysowania dostępnych standardowo wraz z MFC postanowiłem w celu rysowania wykorzystać bibliotekę Cairo. Jest ona rozpowszechniana na zasadach wolnego oprogramowania oraz zapewnia wieloplatformowość. Dodatkowo jest to biblioteka obsługująca grafikę wektorową (w przeciwieństwie do biblioteki dostępnej w MFC, która jest rastrowa), dzięki czemu takie operacje jak skalowanie wykresu (aby w pełni zmieścił się wewnątrz okna) są łatwe do uzyskania za pomocą tej biblioteki. Dodatkowym atutem, docenianym przez końcowego użytkownika aplikacji, jest wygładzanie krawędzi, dzięki czemu wykres jest bardziej estetyczny. Zdecydowaną zaletą jest także wykorzystanie sprzętowej akceleracji graficznej do rysowania obrazu, co pozwala na uzyskanie lepszej wydajności aplikacji. Niestety, biblioteka ta została napisana w C, a nie C++, jednak nie przeszkadza to w użyciu jej w tym programie. Jest to powszechnie stosowana i uznana biblioteka odpowiedzialna za dwuwymiarow ą prezentację graficzną w dużej ilości aplikacji.

W mojej aplikacji wykorzystałem następujące funkcje biblioteki Cairo:

cairo_surface_t

Abstrakcyjny typ płótna Cairo. Dziedziczą po niej inne typy płótna, dzięki czemu do wskaźnika na ten typ można przypisać wynik działania funkcji:

cairo_win32_surface_create(HDC hdc);

dzięki czemu Cairo będzie rysować wewnątrz okna systemu Windows.

cairo_t

Najważniejszy obiekt używany w Cairo, oznaczający kontekst. Zawiera wszystkie informacje dotyczące urządzenia renderującego, w tym współrzędne figur, które mają zostać namalowane. Zmienną tego typu tworzy się za pomocą funkcji cairo_create:

cairo_t* cairo_create (cairo_surface_t *target);

Zmienna ta później musi zostać podana jako argument w praktycznie każdej funkcji z biblioteki Cairo.

cairo_stroke

void cairo_stroke (cairo_t *cr);

Funkcja ta powoduje narysowanie na urządzeniu renderującym kształtu, który został wcześniej zdefiniowany za pomocą ustawionego rodzaju pędzla.

cairo_move_to

void cairo_move_to (cairo_t *cr, double x, double y);

Powoduje przesunięcie pędzla do punktu, którego współrzędne są podane w argumencie funkcji, nie powodując jednak rysowania linii.

cairo_line_to

void cairo_line_to (cairo_t *cr, double x, double y);

Powoduje przesunięcie pędzla tak samo, jak funkcja cairo_move_to, jednak po drodze rysuje linię (która nie ukaże się na ekranie, a jedynie pozostanie w pamięci - aby ją wyświetlić, należy użyć funkcji cairo_stroke)

cairo_scale

void cairo_scale (cairo_t *cr, double sx, double sy);

Funkcja transformacji obrazu. Funkcja ta skaluje obraz, w poziomie o sx, a w pionie o sy.

cairo_translate

void cairo_translate (cairo_t *cr, double tx, double ty);

Funkcja transformacji obrazu. Przesuwa obraz, w poziomie o tx, a w pionie o ty.

Uwagi do funkcji transformacji obrazu

Ponieważ funkcje te same z siebie nie modyfikują obrazu, a jedynie ustawiają w odpowiedni sposób wartości w macierzy transformacji, z której następnie biblioteka korzysta w celu rysowania obrazu, funkcje te powinny być wywołane przed rysowaniem kształtów, które powinny zostać transformowane.

cairo_set_line_witdh

void cairo_set_line_width (cairo_t *cr, double width);

Ustawia grubość pędzla. Warto zauważyć, iż wartości ustawione przez tego rodzaju funkcje są wykorzystywane dopiero przez funkcję cairo_stroke, a nie przez poszczególne funkcje rysujące kształty, więc jedynie ostatnio ustawiona wartość jest brana pod uwagę.

cairo_set_source

void cairo_set_source (cairo_t *cr, cairo_pattern_t *source);

Ustawia aktualny deseń taki, jak deseń podany w drugim parametrze funkcji.

cairo_set_source_rgba

void cairo_set_source_rgba (cairo_t *cr, double red, double green, double
 blue, double alpha);

Ustawia kolor jako aktualny deseń. Jako argumenty, poza wskaźnikiem na cairo_t, przyjmuje kolory: czerwony, zielony i niebieski, oraz kanał alpha (przezroczystość). Wartości tych argumentów powinny zawierać się od 0 (brak danego koloru bądź kompletnie przezroczyste) do 1.

cairo_device_to_user_distance

void cairo_device_to_user_distance (cairo_t *cr, double *dx, double *dy);

Pozwala na wyliczenie odległości urządzenia od użytkownika. Przydatne, aby tak ustawić grubość pędzla, żeby mimo transformacji zachowywała ona odpowiednią grubość.

cairo_pattern_t

typedef struct _cairo_pattern cairo_pattern_t;

Typ deseniu, którego można używać do wypełniania obiektów.

cairo_pattern_create_linear

cairo_pattern_t* cairo_pattern_create_linear (double x0, double y0, double
 x1, double y1);

Funkcja ta zwraca wskaźnik na deseń, będący gradientem liniowym. Argumenty x0 i y0 ustalają położenie punktu początkowego, a argumenty x1 i y1 ustalają położenie końcowego punktu gradientu.

cairo_pattern_add_color_stop_rgb

void cairo_pattern_add_color_stop_rgb (cairo_pattern_t *pattern,double offset,double red,double green,double blue);

Funkcja dodaje kolor do deseniu będącego gradientem. Argument offset definiuje miejsce, w którym kolor zostanie umieszczony (od 0 do 1, im większa wartość, tym bliżej punktu końcowego), następne argumenty określają kolor RGB.

cairo_pattern_add_color_stop_rgba

void cairo_pattern_add_color_stop_rgba (cairo_pattern_t *pattern,double offset,double red,double green,double blue,double alpha);

Funkcja ta działa tak samo, jak funkcja cairo_patern_add_color_stop_rgba, ale pozwala podać również wartość kanału alpha (czyli przezroczystości).

cairo_rectangle

void cairo_rectangle (cairo_t *cr,double x,double y,double width,ouble height);

Dodaje do obecnej ścieżki prostokąt o podanych wymiarach (width, height) zaczynający się w punkcie o podanych współrzędnych (x, y).

cairo_fill

void cairo_fill (cairo_t *cr);

Sprawia, że obecna ścieżka zostaje wypełniona zgodnie z obecnymi zasadami wypełnienia.

cairo_fill_preserve

void cairo_fill_preserve (cairo_t *cr);

Funkcja ta robi to samo, co cairo_fill, ale nie usuwa obecnej ścieżki z pamięci, dzięki czemu można np. narysować jej obrys.

cairo_pattern_destroy

void cairo_pattern_destroy (cairo_pattern_t *pattern);

Zwalnia pamięć zaalokowaną dla deseniu.

cairo_show_text

void cairo_show_text (cairo_t *cr, const char *utf8);

Prosta funkcja pozwalająca na wyświetlenie tekstu za pomocą Cairo.

cairo_text_path

void cairo_text_path(cairo_t *cr, const char *utf8);

Funkcja tworząca ścieżkę o kształcie takim, jak wprowadzony tekst.

cairo_text_extents

void cairo_text_extents (cairo_t *cr, const char *utf8, cairo_text_extents_t
 *extents);

Funkcja ta podaje wymiary tekstu zgodnie z obecnie wybraną wielkością i krojem pisma. Pozwala precyzyjnie umieścić tekst na ekranie.

cairo_select_font_face

void cairo_select_font_face (cairo_t *cr, const char *family, cairo_font_slant_t
 slant, cairo_font_weight_t weight);

Funkcja ta pozwala na ustawienie kroju pisma, którym programista chce się posłużyć do wyświtlenia tekstu na ekranie za pomocą biblioteki Cairo.

cairo_set_font_size

void  cairo_set_font_size (cairo_t *cr, double size);

Funkcja ta pozwala ustawić rozmiar pisma, które utworzy Cairo.

Algorytmy Odwrotnej Notacji Polskiej

Poniżej przedstawione są algorytmy, które wykorzystałem do tworzenia funkcji wchodzących w skład klasy Rownanie. Tekst algorytmów pochodzi z Wikipedii2) i został udostępniony na licencji GNU FDL.
FIXME wkleić algorytm z wikipedii

1) podobno jednak działa normalnie, nie pamiętam w momencie wstawiania na stronę, a nie mam możliwości sprawdzenia
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki