Serwis Edukacyjny
w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

obrazek

Autor artykułu: mgr Jerzy Wałaszek

©2020 mgr Jerzy Wałaszek
I LO w Tarnowie

obrazek

Interpolacja

Wykresy funkcji

SPIS TREŚCI
Podrozdziały

Tworzenie wykresu

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:

Określenie obszaru graficznego okna, w którym ma powstać wykres.

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.

Osie układu współrzędnych

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

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().

Na początek:  podrozdziału   strony 

Opis osi

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ł:

Algorytm wyznaczania opisu osi

Dane wejściowe:

n liczba jednostek na osi, n  ∈ N.
vmin minimalna wartość w przedziale, vmin  ∈ R.
vmax maksymalna wartość w przedziale, vmax  ∈ R.

Dane wyjściowe

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.

Zmienne pomocnicze

len długość przedziału, lenR.
ldv,pdv,mdv zmienne do wyliczania odstępu dv, ldv,pdv,mdv  ∈ R.

Lista kroków

K01: Jeśli vmin = vmax,
to vminvmin - 1
i   vmaxvmax +1
Jeśli przedział ma zerową długość, to modyfikujemy początek i koniec
K02: lenvmax - vmin Obliczamy długość przedziału
K03: Jeśli n < 2,
to n ← 2
Ustalamy liczbę działek
K04: Jeśli n > 2,
to nn - 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
Na początek:  podrozdziału   strony 

Wykres funkcji 2D

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.
yvlow,yvhigh,ydv : To samo dla osi OY

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.
Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2020 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.