Serwis Edukacyjny w I-LO w Tarnowie ![]() Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej
Autor artykułu: mgr Jerzy Wałaszek |
©2023 mgr Jerzy Wałaszek |
SPIS TREŚCI |
|
Podrozdziały |
Wyświetlenie wykresu funkcji polega na odwzorowaniu punktów płaszczyzny ( lub przestrzeni ), na której powstał wykres, w piksele okna graficznego. Wyprowadzimy tutaj podstawowe wzory. Wykresy będziemy tworzyć za pomocą funkcji udostępnianych przez bibliotekę BGI, którą opisaliśmy skrótowo w poprzednim rozdziale. Jeśli chcesz skorzystać z grafiki w naszym artykule, zainstaluj bibliotekę BGI w swoim środowisku programowania.
Metoda tworzenia wykresu funkcji dwuwymiarowej jest następująca:
Zrobimy to za pomocą współrzędnych dwóch punktów:
xs,ys | – | lewy górny narożnik obszaru graficznego wykresu |
xe,ye | – | prawy dolny narożnik obszaru graficznego |
Jeśli obszar ten ma obejmować całe okno graficzne, to współrzędne obszaru możemy w prosty sposób uzyskać za pomocą funkcji biblioteki BGI:
int xs,ys,xe,ye; ... xs = ys = 0; xe = getmaxx(); ye = getmaxy(); ... |
Jeśli obszar nie obejmuje całego okna graficznego, to należy zdefiniować obszar roboczy ( ang. viewport ), w którym będą wykonywane operacje graficzne. W bibliotece BGI mamy do tego celu specjalną funkcję:
void setviewport(int left, int top, int right, int bottom, int clip); |
Funkcja tworzy w bieżącym oknie graficznym wydzielony obszar, w którym będą wykonywane wszystkie operacje graficzne. Po utworzeniu tego obszaru współrzędne pikseli będą się odnosiły do lewego górnego narożnika obszaru roboczego, a nie do lewego górnego narożnika okna.
left,right | – | współrzędne lewego górnego narożnika obszaru roboczego |
right,bottom | – | współrzędne prawego dolnego narożnika obszaru graficznego |
clip | – | parametr określa obcinanie grafiki do obszaru roboczego. Jeśli jest różny od zera, to wszystkie piksele wychodzące poza granice obszaru roboczego nie będą rysowane w oknie graficznym |
Gdy obszar roboczy zostanie zdefiniowany, operacje graficzne będą wykonywane w tym obszarze. Współrzędne (0,0) będą się odnosiły do lewego górnego narożnika obszaru. Funkcje getmaxx() i getmaxy() będą zwracały maksymalną wartość współrzędnych x i y w obszarze roboczym. Przydatna może być jeszcze jedna funkcja:
void clearviewport(void); |
Funkcja czyści zawartość obszaru roboczego. Treść okna graficznego leżąca na zewnątrz tego obszaru nie jest naruszana. Po wyczyszczeniu bieżąca pozycja graficzna jest ustawiana na pozycji (0,0), czyli w lewym górnym narożniku obszaru graficznego.
Gdy mamy już zdefiniowane współrzędne obszaru wykresu w oknie graficznym ( xs,ys xe,ye ), musimy zdefiniować współrzędne obszaru wykresu na płaszczyźnie, na której ten wykres jest tworzony:
xgs,ygs | – | lewy górny narożnik obszaru wykresu na płaszczyźnie |
xge,yge | – | prawy dolny narożnik obszaru wykresu na płaszczyźnie |
Oczywiste jest, iż w przypadku ogólnym obszar wykresu na płaszczyźnie nie będzie identyczny z obszarem roboczym w oknie graficznym. Jeśli chcemy odwzorować punkt wykresu (x,y) w piksel (xd,yd) obszaru roboczego, to musimy znaleźć wzory przeliczające x na xd oraz y na yd.
Wykorzystamy proporcję matematyczną. Wybierzmy dowolny punkt (x.y) na płaszczyźnie wykresu:
Oznaczamy odległości współrzędnych punktu (x,y) od współrzędnych lewego górnego narożnika obszaru wykresu (xgs,ygs):
Lx | – | odległość współrzędnej x od xgs |
Ly | – | odległość współrzędnej y od ygs |
Przejdźmy teraz do obszaru roboczego w oknie graficznym. Załóżmy, że piksel o współrzędnych (xd,yd) jest obrazem punktu (x,y) w obszarze wykresu na płaszczyźnie:
Oznaczmy współrzędne tego punktu na obszarze roboczym. Współrzędne te są jednocześnie odległościami od lewego górnego narożnika obszaru w pionie i w poziomie:
Współrzędna xd w obszarze roboczym okna odwzorowuje współrzędną x obszaru na płaszczyźnie wykresu. Tak samo współrzędna yd odwzorowuje współrzędną y. Tworzymy proporcje:
Wg | – | szerokość obszaru wykresu na płaszczyźnie |
Hg | – | wysokość obszaru wykresu na płaszczyźnie |
W | – | szerokość obszaru roboczego w oknie graficznym |
H | – | wysokość obszaru roboczego w oknie graficznym |
Ponieważ interesują nas tutaj tylko odległości pomiędzy współrzędnymi, a nie konkretne wartości współrzędnych, możemy przyjąć, iż szerokość obszaru w pikselach jest równa maksymalnej wartości współrzędnej xd w obszarze plus 1. To samo dla wysokości:
Obszary muszą być proporcjonalne:
Zatem:
W, H, Wg i Hg są wartościami stałymi i wystarczy je policzyć jeden raz przed tworzeniem wykresu:
Gdy mamy już wzory przeliczeniowe ze współrzędnych wykresu na płaszczyźnie na współrzędne w obszarze roboczym okna graficznego, możemy przystąpić do tworzenia wykresu w obszarze roboczym.
Zaczynamy od narysowania osi układu współrzędnych. Osie te będą odcinkami rysowanymi na całej szerokości i całej wysokości obszaru roboczego.
Obliczamy współrzędną yd osi OX:
Obliczamy współrzędną xd osi OY:
Jeśli wyznaczone współrzędne mieszczą się w obszarze roboczym, to rysujemy odpowiednie odcinki: poziomy dla osi OX na wysokości yd; pionowy dla osi OY na współrzędnej xd. Odcinek poziomy ma szerokość obszaru roboczego. Odcinek pionowy ma wysokość obszaru roboczego.
Wykres funkcji stworzymy z łamanej zbudowanej z odcinków. Aby wyznaczyć współrzędne wierzchołków łamanej, postępujemy następująco:
Wykres będzie tworzony dla argumentów funkcji w przedziale od xgs do xge:
Przedział [xgs,xge] dzielimy na n równoodległych punktów:
Dla n punktów przedział [xgs,xge] dzielony jest na n - 1 równych segmentów. Stąd odległość między dwoma sąsiednimi punktami jest stała i wynosi:
Wartości punktów x0, x1, ..., xn wyliczymy z prostego wzoru:
Gdy mamy punkty xi, to obliczamy dla nich wartości funkcji:
Wyliczone punkty (xi,yi) definiują wierzchołki łamanej:
Łamana przybliża linię wykresu funkcji. Przybliżenie jest tym lepsze im więcej punktów łamanej.
Gdy mamy policzone współrzędne wierzchołków łamanej na obszarze wykresu, musimy je przeliczyć na współrzędne w obszarze roboczym okna graficznego. Tutaj wykorzystujemy poprzednio wyprowadzone wzory:
Wartości współrzędnych zaokrąglamy do liczb całkowitych (współrzędne pikseli w oknie graficznym są liczbami całkowitymi ) i umieszczamy w tablicy. Następnie tablicę wykorzystujemy do narysowania łamanej za pomocą funkcji drawpoly().
Wiemy już, jak narysować wykres funkcji, mając jej wartości. Pozostaje problem umieszczenia jednostek na osiach układu współrzędnych, aby wykres stał się bardziej czytelny. Wbrew pozorom nie jest to wcale takie proste i do rozwiązania tego problemu opracowano różne algorytmy. My wybierzemy najprostszy algorytm.
Załóżmy, iż mamy wartości ( argumentów lub samej funkcji ) mieszczące się w przedziale [ vmin,vmax ]. Umieśćmy ten przedział na osi liczbowej:
Przedział ten chcemy opisać n działkami, przy czym skrajne działki mogą wybiegać poza przedział:
n | – | liczba jednostek na osi, n ∈ N. |
vmin | – | minimalna wartość w przedziale, vmin ∈ R. |
vmax | – | maksymalna wartość w przedziale, vmax ∈ R. |
vlow | – | wartość pierwszej działki na osi, vlow ∈ R. |
vhigh | – | wartość ostatniej działki na osi, vhigh ∈ R. |
dv | – | odległość pomiędzy sąsiednimi działkami, dv ∈ R. |
len | – | długość przedziału, len ∈ R. |
ldv,pdv,mdv | – | zmienne do wyliczania odstępu dv, ldv,pdv,mdv ∈ R. |
K01: | Jeśli vmin = vmax, to vmin ← vmin - 1 i vmax ← vmax +1 |
Jeśli przedział ma zerową długość, to modyfikujemy początek i koniec |
K02: | len ← vmax - vmin | Obliczamy długość przedziału |
K03: | Jeśli n < 2, to n ← 2 |
Ustalamy liczbę działek |
K04: | Jeśli n > 2, to n ← n - 2 |
|
K05: | ![]() |
Obliczamy wstępną odległość pomiędzy działkami |
K06: | ![]() |
Modyfikujemy odległość dv do "ładnej wartości" |
K07: | ![]() |
|
K08: | ![]() |
|
K09: | Jeśli mdv > 5, to mdv ← 10 inaczej jeśli mdv > 2, to mdv ← 5 inaczej jeśli mdv > 1, to mdv ← 1 |
Modyfikujemy współczynnik mdv do wartości 1, 2 lub 5 |
K10: | ![]() |
Mamy zmodyfikowaną odległość |
K11: | ![]() |
Obliczamy pierwszą działkę |
K12: | ![]() |
Obliczamy ostatnią działkę |
K13: | Zakończ |
Poniższy program demonstruje działanie algorytmu opisu osi. Na wejściu program pobiera 3 liczby, n, vmin i vmax, czyli liczbę działek, początek i koniec przedziału. Na wyjściu otrzymujemy pozycję pierwsze i ostatniej działki, a następnie program wypisuje wartości kolejnych działek, które obejmują przedział [ vmin,vmax ]. Liczba działek wynikowych może się różnić od n, ponieważ program stara się dobrać "ładny" odstęp pomiędzy działkami.
Dane wejściowe:
15 -1 2.2 |
Przykładowy program w języku C++
// Generacja opisu osi // (C)2020 mgr Jerzy Wałaszek // Metody numeryczne //------------------------------------- #include <iostream> #include <iomanip> #include <cmath> using namespace std; // Program główny //--------------- // Przybliżenie zera double const EPS = 0.0000000001; int main() { setlocale(LC_ALL,""); cout << fixed << setprecision(4); unsigned int n; // Liczba działek na osi double vmin,vmax; // Punkty krańcowe przedziału wartości double vlow,vhigh; // Wartości pierwszej i ostatniej działki double dv; // Odstęp pomiędzy działkami double ldv,pdv,mdv,len; // Odczytujemy liczbę działek cin >> n; // Odczytujemy krańce przedziału cin >> vmin >> vmax; // Jeśli przedział ma zerową długość, to modyfikujemy jego końce if(fabs(vmin - vmax) < EPS) { vmin -= 1; vmax += 1; } // Obliczamy długość przedziału len = vmax - vmin; // Modyfikujemy liczbę działek if(n < 2) n = 2; // Obliczamy wstępną odległość pomiędzy działkami dv = len / ( n - 1 ); // Modyfikujemy dv do "ładnej wartości" ldv = floor(log10(dv)); pdv = pow(10,ldv); mdv = (int)(dv/pdv + 0.5); // Modyfikujemy współczynnik mdv do wartości 1, 2 lub 5 if(mdv > 5.0) mdv = 10.0; else if(mdv > 2.0) mdv = 5.0; else if(mdv > 1.0) mdv = 2.0; else mdv = 1.0; // Obliczamy ostateczną odległość pomiędzy działkami dv = mdv * pdv; // Obliczmy pozycję pierwszej działki vlow = dv * floor(vmin / dv); // Obliczamy pozycję ostatniej działki vhigh = dv * ceil(vmax / dv); // Wyświetlamy wyniki double v; int i; cout << vlow << ", " << vhigh << endl << endl; i = 0; do { v = vlow + i * dv; cout << "v[" << setw(2) << i << "] = " << setw(9) << v << endl; i++; } while(v < vhigh); return 0; } |
Wynik |
15 -1 2.2 -1.0000, 2.2000 v[ 0] = -1.0000 v[ 1] = -0.8000 v[ 2] = -0.6000 v[ 3] = -0.4000 v[ 4] = -0.2000 v[ 5] = 0.0000 v[ 6] = 0.2000 v[ 7] = 0.4000 v[ 8] = 0.6000 v[ 9] = 0.8000 v[10] = 1.0000 v[11] = 1.2000 v[12] = 1.4000 v[13] = 1.6000 v[14] = 1.8000 v[15] = 2.0000 v[16] = 2.2000 |
Poniższy program tworzy wykres funkcji dwuwymiarowej w oknie graficznym przy wykorzystaniu biblioteki BGI. Program został tak zaprojektowany, aby można go było wykorzystać w innych programach. Działanie programu zostało dokładnie opisane w komentarzach.
Przykładowy program w języku C++
// Procedura rysująca wykres funkcji // (C)2020 mgr Jerzy Wałaszek // Metody numeryczne //---------------------------------- #include <iostream> #include <iomanip> #include <cmath> #include <graphics.h> using namespace std; // Przybliżenie zera double const EPS = 0.0000000001; // Zmienne globalne const int NX = 512; // Liczba punktów na wykresie double xg[NX],yg[NX]; // Współrzędne punktów wykresu, posortowane wg x double xgs,ygs,xge,yge; // Współrzędne obszaru wykresu na płaszczyźnie const int NV = 9; // Przybliżona liczba działek + 2 double xvlow,xvhigh,xdv; // Zmienne do opisu osi x double yvlow,yvhigh,ydv; // Zmienne do opisu osi y // Funkcja, której wykres będzie tworzony //--------------------------------------- double f(double x) { return x * sin(x*x + 2) * cos(x*(x*(x+1)+1)); } // Funkcja oblicza parametry działek // vmin,vmax - przedział działkowany // vlow - pierwsza działka // vhigh - ostatnia działka // dv - odstęp pomiędzy działkami //---------------------------------- void get_v(double vmin, double vmax, double & vlow, double & vhigh, double & dv) { double ldv,pdv,mdv,len; // Jeśli przedział ma zerową długość, to modyfikujemy jego końce if(fabs(vmin - vmax) < EPS) { vmin -= 1; vmax += 1; } // Obliczamy długość przedziału len = vmax - vmin; // Obliczamy wstępną odległość pomiędzy działkami dv = len / ( NV - 1 ); // Modyfikujemy dv do "ładnej wartości" ldv = floor(log10(dv)); pdv = pow(10,ldv); mdv = (int)(dv/pdv + 0.5); // Modyfikujemy współczynnik mdv do wartości 1, 2 lub 5 if(mdv > 5.0) mdv = 10.0; else if(mdv > 2.0) mdv = 5.0; else if(mdv > 1.0) mdv = 2.0; else mdv = 1.0; // Obliczamy ostateczną odległość pomiędzy działkami dv = mdv * pdv; // Obliczmy pozycję pierwszej działki vlow = dv * floor(vmin / dv); // Obliczamy pozycję ostatniej działki vhigh = dv * ceil(vmax / dv); } // Funkcja wylicza punkty wykresu oraz ustala wstępne wartości // współrzędnych xgs,ygs,sge,yge. // W parametrach przekazywany jest przedział argumentów funkcji. //-------------------------------------------------------------- void set_xy(double xp, double xk) { double dx = (xk - xp) / (NX - 1); // Obliczamy odległość dwóch punktów x int i; for(i = 0; i < NX; i++) { xg[i] = xp + i * dx; // Współrzędna x punktu i-tego yg[i] = f(xg[i]); // Współrzędna y punktu i-tego // Wyznaczamy max i min funkcji w przedziale, aby objąć ją prostokątem if(!i) // tzn. jeśli i == 0 ygs = yge = yg[i]; else { if(yg[i] > ygs) ygs = yg[i]; if(yg[i] < yge) yge = yg[i]; } } // Obliczamy działki na osi OX i OY get_v(xp,xk,xvlow,xvhigh,xdv); xgs = xvlow; xge = xvhigh; get_v(yge,ygs,yvlow,yvhigh,ydv); ygs = yvhigh; yge = yvlow; } // Funkcja rysuje wykres w zadanym przedziale // xp,xk - początek i koniec przedziału //------------------------------------------- void graph2D(double xp, double xk) { // Najpierw ustawiamy tablicę współrzędnych punktów wykresu wraz z danymi globalnymi set_xy(xp,xk); // Ustalamy rozmiary obszaru wykresu w oknie graficznym int xs = 0; int xe = getmaxx() - 80; int ys = 0; int ye = getmaxy() - 32; // Obliczamy szerokość i wysokość obszarów int W = xe - xs + 1; int H = ye - ys + 1; double Wg = xvhigh - xvlow; double Hg = yvhigh - yvlow; // Obszar wykresu wypełniamy kolorem białym setfillstyle(SOLID_FILL,WHITE); bar(xs,ys,xe+2,ye+2); // Rysujemy linie podziałkowe w kolorze jasnoniebieskim // Na końcach linii umieszczamy wartość podziałki int xd,yd; double x,y; // Pionowe x = xvlow; while(x <= xvhigh) { xd = (x - xgs) * W / Wg; setlinestyle(DOTTED_LINE,0,NORM_WIDTH); setcolor(LIGHTCYAN); line(xd,ys,xd,ye); setcolor(YELLOW); bgiout << x; outstreamxy(xd, ye+16); x += xdv; } // Poziome y = yvlow; while(y <= yvhigh) { yd = (ygs - y) * H / Hg; setlinestyle(DOTTED_LINE,0,NORM_WIDTH); setcolor(LIGHTCYAN); line(xs,yd,xe,yd); setcolor(LIGHTRED); bgiout << y; outstreamxy(xe+4, yd); y += ydv; } // Ustawiamy parametry linii osi współrzędnych setcolor(BLUE); setlinestyle(SOLID_LINE,0,NORM_WIDTH); // Jeśli oś wpada w obszar wykresu, to ją rysujemy // Najpierw oś OX yd = ygs * H / Hg; if((yd >= 0) && (yd <= ye)) line(xs,yd,xe,yd); // Następnie oś OY xd = -xgs * W / Wg; if((xd >= 0) && (xd <= xe)) line(xd,ys,xd,ye); // Ustawiamy parametry linii wykresu setcolor(GREEN); setlinestyle(SOLID_LINE,0,THICK_WIDTH); // Rysujemy wykres int i; for(i = 0; i < NX; i++) { // Obliczamy współrzędne ekranowe punktu wykresu xd = (xg[i] - xgs) * W / Wg; yd = (ygs - yg[i]) * H / Hg; // W pierwszym punkcie ustawiamy pozycję, w kolejnych rysujemy linie if(!i) moveto(xd,yd); else lineto(xd,yd); } } //---------------- // PROGRAM GŁÓWNY //---------------- int main() { // Tworzymy okno graficzne o wybranym przez użytkownika rozmiarze initwindow(800, 600,"Wykres"); // Ustawiamy parametry strumienia wyjściowego (potrzebne do opisu osi) bgiout << setprecision(4) << fixed; // Rysujemy wykres graph2D(-1.6,1.7); // Czekamy na klawisz getch(); // Zamykamy okno graficzne i kończymy closegraph(); return 0; } |
Zasady wykorzystania programu do własnych potrzeb są następujące:
Zmienne globalne:
EPS | : | Stała używana przy porównywaniu dwóch liczb zmiennoprzecinkowych. Należy zachować jej wartość. |
NX | : | Stała określająca liczbę punktów wykresu. Im więcej punktów, tym dokładniej odwzorowywana jest funkcja. |
xg[ ] | : | Tablica wartości współrzędnych x kolejnych punktów wykresu. Jej rozmiar zależy od stałej NX. |
yg[ ] | : | Tablica wartości współrzędnych y kolejnych punktów wykresu. Elementy xg[ ] i yg[ ] tworzą parę współrzędnych. |
xgs,ygs | : | Współrzędne lewego górnego narożnika obszaru obejmującego wykres na płaszczyźnie. Wartości te są automatycznie obliczane w programie. |
xge,yge | : | Współrzędne prawego dolnego narożnika obszaru obejmującego wykres na płaszczyźnie. Wartości te są automatycznie obliczane w programie. |
NV | : | Stała określająca liczbę działek na osiach wykresu. Jest to wartość wstępna, którą program zmodyfikuje wg potrzeb, aby otrzymać ładne wartości działek. |
xvlow,xvhigh,xdv | : | Parametry działek na osi OX. Kolejno: współrzędna x pierwszej działki, współrzędna x ostatniej działki, odstęp pomiędzy dwoma sąsiednimi działkami. Program wyznacza te wartości automatycznie. |
yvlow,yvhigh,ydv | : | To samo dla osi OY. Program wyznacza te wartości automatycznie. |
Funkcje:
f() | : | Tutaj umieszczasz przepis funkcji, której wykres ma być utworzony. |
get_v() | : | Ta funkcja wylicza parametry działek na osiach OX i OY, tak aby działki posiadały "ładne" wartości. Funkcja jest wywoływana wewnętrznie w programie i nie powinno się jej modyfikować bez potrzeby. |
set_xy() | : | Funkcja oblicza punkty wykresu funkcji w zadanym przedziale. Dodatkowo wywołuje funkcję get_v() dla wyznaczenia działek na osiach OX i OY. Modyfikuje odpowiednio prostokąt obejmujący wykres na płaszczyźnie wg wyznaczonych działek. |
graph2D | : | Funkcja rysuje kompletny wykres. Należy ja wywołać z krańcami przedziału, w którym będzie rysowany wykres funkcji. Reszta odbywa się automatycznie. Użytkownik musi jedynie zdefiniować zmienne globalne oraz utworzyć okno graficzne o odpowiednim do potrzeb rozmiarze. |
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2023 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.