Jump to content

SUBIECTE NOI
« 1 / 5 »
RSS
teava rezistenta panou apa calda

Acces in Curte din Drum National

Sub mobila de bucatarie si sub fr...

Rezultat RMN
 Numar circuite IPAT si prindere t...

Pareri brgimportchina.ro - teapa ...

Lucruri inaintea vremurilor lor

Discuții despre TVR Sport HD
 Cost abonament clinica privata

Tremura toata, dar nu de la ro...

Renault Android

Recomandare bicicleta e-bike 20&#...
 Bing-Content removal tool

Nu pot accesa monitorulsv.ro de l...

Cum sa elimini urmele de acnee?

Wc Geberit
 

Reprezentarea graficului unei funcții în C

- - - - -
  • Please log in to reply
6 replies to this topic

#1
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,189
  • Înscris: 24.02.2007
Cum desenez graficul unei funcții în C?

O intrebare pe care mulți ne-am pus-o la un moment dat, dar la care nu prea am găsit răspuns. Unii se gândesc la celebrul graphics.h al Borland, însă ... ce naiba ... suntem in 2014, trebuie să existe ceva mai modern. Și chiar există ceva mai modern.

În ziua de azi, sistemul de operare este cel care controlează interacțiunile cu perificericele (inclusiv partea video) așa că va trebui să luam legătura cu el pentru a obține ceea ce dorim. Acest lucru este chiar benefic, fiindcă altfel ar fi trebuit să scriem cod diferit funcție de ce placă video are utilizatorul, lucru care chiar nu ne interesează.

Deoarece majoritatea celor ce vor citi acest tutorial folosesc Windows, voi exemplifica folosind particularitățiile acestui sistem de operare.
Logica rămâne aceeași indiferent de tehnologia folosită, ce diferă fiind funcțiile efective de desenare a liniilor, textului, etc.

Attached File  Untitled.png   21.09K   218 downloads

Desenarea sub Windows

API-ul tradițional de desenare sub Windows, GDI, există de când lumea. Întretimp au mai apărut tehnologii mai moderne pentru lucrul în 2D: GDI+, Direct2D; însă pentru acest exemplu sunt suficiente facilitățiile oferite de GDI. Astfel (cu mici ajustări) aplicația ar putea rula și pe Windows 95.
Citirea celuilalt tutorial pe această temă, Visual C++ 2010 Express pentru tăţi, este recomandată.

Crearea unei ferestre

Mediul Windows, așa cum îi indică și numele, este compus din ferestre. Astfel, primul pas este să creăm și noi o fereastră.
Acest lucru nu este chiar ușor la început, însă, din fericire, majoritatea IDE-urilor au șabloane minimale pentru acest lucru.
Așa arată șablonul specific din Visual Studio.

// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[] = _T("Grafice de functii");		// The title bar text
TCHAR szWindowClass[] = _T("FUNCTIONPLOTTER");	// the main window class name

// Forward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
					 _In_opt_ HINSTANCE hPrevInstance,
					 _In_ LPTSTR	lpCmdLine,
					 _In_ int	   nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	MSG msg;

	MyRegisterClass(hInstance);

	// Perform application initialization:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FUNCTIONPLOTTER));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_3DSHADOW);
	wcex.lpszMenuName	= 0;
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
	  CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
	  return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);

		RefreshPlot(hWnd, hdc);

		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}


Acest cod face următoarele:

  • Crează & afisează o fereastră personalizată cu titlul "Grafice de functii"
  • Definește o funcție (WndProc) ce va fi apelată de către sistem atunci când dorește să ne anunțe ceva. Această funcție e apelată pentru a ne înștiința că s-a apăsat pe un buton, că trebuie redesenat, etc.
  • Apelează GetMessage într-un while, pentru a preveni oprirea aplicației cât timp utilizatorul nu inchide fereastra.

Când/cum/unde desenez?

În exemplu vom desena de fiecare dată când sistemul ne anunță că o redesenare este necesară (mesajul WM_PAINT). Există și metode mai eficiente, însă acesta este un mod foarte simplu de a obține un grafic redimensionabil.

Pasul 1 - Informații

Ce avem la dispoziție?
Avem la dispoziție o fereastră (un dreptunghi) de o anumită dimensiune. Dacă fereastra e maximizată, dimensiunea ei va fi apropiată de rezoluția ecranului (ex. 1920x1080). În această fereastră vom desena graficul, lăsând o ușoară spațiere față de marginea ferestrei. Vom putea controla fiecare pixel din această fereastră.

Ce vrem să afisăm?
Trebuie să ne hotărâm ce parte a graficului dorim să afisăm. În continuare vom fixa afișa x de la -6 la 6, iar y de la -1.5 la 1.5. Fiind o aplicație cu interfață grafică, acești parametrii ar fi fain de introdus de către utilizator, dar aceasta rămâne temă de casă. O alta informație importantă este frecvența marcajelor pe axe. Am ales să pun un marcaj din 1 în 1 pe abscisă și din 0.5 în 0.5 pe ordonată.

Toți acești parametrii globali ai graficului sunt stocați într-o structură.

typedef struct
{
	RECT plotArea;
	double xFrom;
	double xTo;
	double xGridSpacing;
	TCHAR *xMarkerFormat;
	double yFrom;
	double yTo;
	double yGridSpacing;
	TCHAR *yMarkerFormat;
} PlotParameters;



De ce altă informație avem nevoie?
Cea mai importantă informație e ... funcția al cărei grafic vrem să-l desenăm. Deoarece codul vrem să funcționeze cu orice funcție, va trebui ca funcția de desenare sa primească un pointer la funcția dorită. Am definit astfel un sinonim pentru acest de tip funcție, o funcție ce acceptă un parametru de tip double, și returnează tot un double:

typedef double(*FunctionType)(double);

Apoi, vrem să putem afișa mai multe funcții în același timp. Astfel, pentru a le diferenția, voi asocia fiecărei funcții o culoare, și un scurt text de afișat în legendă. Deasemenea vom putea specifica stilul graficului (normal, punctat, etc).

typedef struct
{
	FunctionType function;
	COLORREF color;
	int style;
	TCHAR *legend;
} PlotAction;


Informații auxiliare
Pe lângă toate acest informații, mai sunt o sumedenie de informații mai puțin relevante în primă fază: culoarea marginii, fontul folosit, culoarea de fundal, etc. Pentru simplitate, aceastea au fost definite drept constante în macro-uri.

#define PlotAreaBorderColor		RGB(0, 0, 0)
#define PlotAreaBackgroundColor	RGB(255, 255, 255)
#define PlotAreaBorderStyle		PS_DASH
#define AxisColor				RGB(0, 0, 200)
#define AxisStyle				PS_SOLID
#define AxisWidth				2
#define AxisMarkerLength		20
#define AxisMarkerFont			_T("Comic Sans MS")
#define AxisMarkerFontSize		10
#define AxisArrowLength			15
#define AxisArrowAngle			20
#define GridColor				RGB(200, 200, 200)
#define GridStyle				PS_DOT
#define GridWidth				1
#define LegendFont				_T("Comic Sans MS")
#define LegendFontSize			12
#define LegendTextColor			RGB(0, 0, 0)
#define LegendBackgroundColor	RGB(240, 240, 240)
#define LegendStyle				PS_DASH
#define LegendItemWidth			30
#define LegendWidthSpacing		10
#define LegendHeightSpacing		10


Pasul 2 - Transpunerea coordonatelor

Cele mai multe calcule vor fi legate de transpunerea unui punct din coordonatele matematice, în coordonatele ecranului.

Să luăm un exemplu: Într-un dreptunghi de 500x400 pixeli vom afișa valorile cuprinse între -5 si 5 pe abscisă și -1.5 si 1.5 pe ordonată. Știm deasemenea că acei 500x400 au originea în colțul din stânga sus (nu în centru, cum suntem obișnuiți de la matematică). La ce coordonate va trebui astfel afișat punctul ... să zicem ... (2, 1).

În intervalul -5..5, valoarea 2 se află mai in dreapta (spre 5), mai exact la (2 - (-5)) / (5 - (-5)) * 100% = 7 / 10 * 100% = 70%.
Aplicând acesti 70% pe lungimea in pixeli, 500, obținem: 70% * 500 = 350.

În intervalul -1.5..1.5, valoarea 1 se află mai la (1 - (-1.5)) / (1.5 - (-1.5)) * 100% = 2.5 / 3 * 100% = 83.33%.
Aplicând acesti 83.33% pe lațimea in pixeli, 400, obținem: 83.33% * 400 ~= 333. Trebuie însă să ținem cont că valoarea 0 este sus și valoarea 400 jos, astfel valoarea finală va fi 400 - 333 = 67. (valoarea 1 se află în jumătatea de sus, mai spre 1.5 decât spre -1.5)

Astfel punctul (2, 1) va fi afișat pe ecran la (350, 67).

Pasul 3 - RefreshPlot

Atunci când sistemul cere o redesenare a conținutului, apelăm funcția RefreshPlot. Aceasta arată în felul următor:

void RefreshPlot(HWND hWnd, HDC hdc)
{
	RECT plotArea;
	int windowWidth;
	int windowHeight;
	const int topMargin = 10;
	const int leftMargin = 10;
	const int rightMargin = 10;
	const int bottomMargin = 10;

	derivativeFrom = f;

	PlotAction toPlot[] = 
	{
		{ f, RGB(200, 0, 0), PS_SOLID, _T("f(x) = 0.4x^3 + 0.2x^2 - x + 0.6") },
		{ sin, RGB(0, 200, 0), PS_DASH, _T("sin(x)") },
		{ derivative, RGB(150, 150, 0), PS_SOLID, _T("f'(x) = 1.2x^2 + 0.4x - 1") },
		{ NULL, 0, 0, NULL }
	};

	GetClientRect(hWnd, &plotArea);

	windowWidth = plotArea.right - plotArea.left;
	windowHeight = plotArea.bottom - plotArea.top;

	plotArea.top = topMargin;
	plotArea.left = leftMargin;
	plotArea.right = windowWidth - rightMargin;
	plotArea.bottom = windowHeight - bottomMargin;

	PlotParameters parameters;
	parameters.plotArea = plotArea;
	parameters.xFrom = -6;
	parameters.xTo = 6;
	parameters.xGridSpacing = 1;
	parameters.xMarkerFormat = _T("%0.0f");
	parameters.yFrom = -1.5;
	parameters.yTo = 1.5;
	parameters.yGridSpacing = 0.5;
	parameters.yMarkerFormat = _T("%0.1f");

	Plot(hdc, toPlot, &parameters);
}


  • hWnd identifică ecranul principal și e folosit pentru a-i afla dimensiunea
  • hdc identifică unde va avea loc desenarea (în acest caz tot pe ecranul principal) și este un parametru ce trebuie transmis tuturor funcțiilor de desenare
  • după ce am aflat dimensiunea ecranului, doresc să las un spațiu gol de 10 pixeli pe fiecare latură, pentru a nu lipi graficul de margini
  • zona astfel obținută va fi stocată & transmisă mai departe sub forma parametrului plotArea, aici fiind reprezentat efectiv graficul
  • specific că doresc afișarea funcției f (definită de mine drept return 0.4 * x * x * x + 0.2 * x * x - x + 0.6;), a funcției sin precum și a derivatei funcției f (a se citi spre final despre derivare)
  • introduc manual diverși parametrii
  • apelez funcția de ce reprezentare, Plot

Pasul 4 - Plot

Funcția de reprezentare mai sus apelată, Plot, apelează la rându-i mai departe alte funcții, după ce determină câte funcții are de reprezentat.

void Plot(HDC hdc, PlotAction actions[], const PlotParameters* parameters)
{
	int i;

	DrawPlottingArea(hdc, parameters);

	for (i = 0; actions[i].function != NULL; i++)
	{
		DrawFunction(hdc, &actions[i], parameters);
	}

	DrawLegend(hdc, actions, parameters);
}


Pasul 5 - DrawPlottingArea

DrawPlottingArea se ocupă de crearea unui chenar în care vor fi afișate graficele, de desenarea axelor și a valorilor de pe axe, de desenarea unui grid pentru a repera mai ușor valorile.

Desenarea liniilor se face cu ajutorul unui "pen" (identificat printr-un HPEN).
  • CreatePen returnează un astfel de obiect cu parametrii ceruți (stil, grosime, culoare).
  • Pen-ul creat este selectat folosind SelectObject, funcție ce returnează precedentul pen ce este reținut
  • Se deseneaza folosind MoveToEx, LineTo, Rectangle, ...
  • Precedentul pen e reselectat la final

În cazul desenării axelor de coordonate se observă o aplicare a geometriei pentru a determina coordonatele vărfurile săgețiilor.
Exempul de mai jos desenează o axă doar dacă valorile sunt potrivite. De exemplu, ordonata nu apare dacă valorile afișate a lui x sunt doar pozitive sau negative.

void DrawPlottingArea(HDC hdc, const PlotParameters* parameters)
{
	int xGridSpacing, yGridSpacing;
	DrawPlottingRectangle(hdc, parameters);

	xGridSpacing = (int)round(parameters->xGridSpacing / (parameters->xTo - parameters->xFrom)
		* (parameters->plotArea.right - parameters->plotArea.left));
	yGridSpacing = (int)round(parameters->yGridSpacing / (parameters->yTo - parameters->yFrom)
		* (parameters->plotArea.bottom - parameters->plotArea.top));

	DrawGrid(hdc, parameters, xGridSpacing, yGridSpacing);
	DrawAxes(hdc, parameters, xGridSpacing, yGridSpacing);
}


void DrawPlottingRectangle(HDC hdc, const PlotParameters* parameters)
{
	HPEN previousPen, pen = CreatePen(PS_DASH, 1, PlotAreaBorderColor);
	HBRUSH previousBrush, brush = CreateSolidBrush(PlotAreaBackgroundColor);

	previousPen = (HPEN)SelectObject(hdc, pen);
	previousBrush = (HBRUSH)SelectObject(hdc, brush);

	Rectangle(hdc, parameters->plotArea.left, parameters->plotArea.top,
		parameters->plotArea.right, parameters->plotArea.bottom);

	SelectObject(hdc, previousPen);
	SelectObject(hdc, previousBrush);
}


void DrawGrid(HDC hdc, const PlotParameters* parameters, int xGridSpacing, int yGridSpacing)
{
	HPEN previousPen, pen = CreatePen(GridStyle, GridWidth, GridColor);
	int position;

	previousPen = (HPEN)SelectObject(hdc, pen);

	position = parameters->plotArea.left;
	while (position + xGridSpacing < (parameters->plotArea.right - xGridSpacing * 3 / 4))
	{
		position += xGridSpacing;
		MoveToEx(hdc, position, parameters->plotArea.top, NULL);
		LineTo(hdc, position, parameters->plotArea.bottom);
	}

	position = parameters->plotArea.top;
	while (position + yGridSpacing < (parameters->plotArea.bottom - yGridSpacing * 3 / 4))
	{
		position += yGridSpacing;
		MoveToEx(hdc, parameters->plotArea.left, position, NULL);
		LineTo(hdc, parameters->plotArea.right, position);
	}

	SelectObject(hdc, previousPen);
}


void DrawAxes(HDC hdc, const PlotParameters* parameters, int xGridSpacing, int yGridSpacing)
{
	HPEN previousPen, pen = CreatePen(AxisStyle, AxisWidth, AxisColor);
	int xAxisY = 0, yAxisX = 0, arrowX, arrowY, markerPosition;

	previousPen = (HPEN)SelectObject(hdc, pen);

	if (parameters->yFrom < 0 && parameters->yTo > 0)
	{
		// the X axis is visible
		xAxisY = (int)round(parameters->plotArea.top + parameters->yTo / (parameters->yTo - parameters->yFrom)
			* (double)(parameters->plotArea.bottom - parameters->plotArea.top));

		MoveToEx(hdc, parameters->plotArea.left, xAxisY, NULL);
		LineTo(hdc, parameters->plotArea.right, xAxisY);
	}

	if (parameters->xFrom < 0 && parameters->xTo > 0)
	{
		// the Y axis is visible
		yAxisX = (int)round(parameters->plotArea.right - parameters->xTo / (parameters->xTo - parameters->xFrom)
			* (double)(parameters->plotArea.right - parameters->plotArea.left));

		MoveToEx(hdc, yAxisX, parameters->plotArea.top, NULL);
		LineTo(hdc, yAxisX, parameters->plotArea.bottom);
	}

	if (xAxisY > 0)
	{
		markerPosition = parameters->plotArea.left;
		while (markerPosition + xGridSpacing < (parameters->plotArea.right - xGridSpacing * 3 / 4))
		{
			markerPosition += xGridSpacing;
			// check if marker is too close to the Y Axis
			if (yAxisX > 0 && (abs(markerPosition - yAxisX) < min(10, xGridSpacing / 2)))
				continue;
			MoveToEx(hdc, markerPosition, xAxisY - AxisMarkerLength / 2, NULL);
			LineTo(hdc, markerPosition, xAxisY + AxisMarkerLength / 2);
		}

		// drawArrow
		arrowX = (int)round(parameters->plotArea.right + AxisArrowLength * cos(90 + AxisArrowAngle));
		arrowY = (int)round(AxisArrowLength * sin(AxisArrowAngle));

		MoveToEx(hdc, parameters->plotArea.right, xAxisY, NULL);
		LineTo(hdc, arrowX, xAxisY - arrowY);

		MoveToEx(hdc, parameters->plotArea.right, xAxisY, NULL);
		LineTo(hdc, arrowX, xAxisY + arrowY);
	}
	if (yAxisX > 0)
	{
		markerPosition = parameters->plotArea.top;
		while (markerPosition + yGridSpacing < (parameters->plotArea.bottom - yGridSpacing * 3 / 4))
		{
			markerPosition += yGridSpacing;
			// check if marker is too close to the X Axis
			if (xAxisY > 0 && (abs(markerPosition - xAxisY) < (min(10, yGridSpacing / 2))))
				continue;
			MoveToEx(hdc, yAxisX - AxisMarkerLength / 2, markerPosition, NULL);
			LineTo(hdc, yAxisX + AxisMarkerLength / 2, markerPosition);
		}

		// drawArrow
		arrowY = (int)round(parameters->plotArea.top - AxisArrowLength * cos(90 + AxisArrowAngle));
		arrowX = (int)round(AxisArrowLength * sin(AxisArrowAngle));

		MoveToEx(hdc, yAxisX, parameters->plotArea.top, NULL);
		LineTo(hdc, yAxisX - arrowX, arrowY);

		MoveToEx(hdc, yAxisX, parameters->plotArea.top, NULL);
		LineTo(hdc, yAxisX + arrowX, arrowY);
	}

	SelectObject(hdc, previousPen);

	DrawMarkerValues(hdc, parameters, xGridSpacing, yGridSpacing, xAxisY, yAxisX);
}


void DrawMarkerValues(HDC hdc, const PlotParameters* parameters, int xGridSpacing, int yGridSpacing,
	int xAxisY, int yAxisX)
{
	static HFONT font;
	HFONT previousFont;
	int markerPosition;
	TCHAR stringValue[128];
	RECT valueRect;

	font = CreateFont(-MulDiv(AxisMarkerFontSize, GetDeviceCaps(hdc, LOGPIXELSY), 72),
		0,
		0,
		0,
		FW_NORMAL, //grosime
		FALSE, //italic
		FALSE, //underline
		FALSE, //strikeout
		DEFAULT_CHARSET,
		OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS,
		CLEARTYPE_QUALITY,
		FF_DONTCARE,
		AxisMarkerFont);
	previousFont = (HFONT)SelectObject(hdc, font);

	if (xAxisY > 0)
	{
		markerPosition = parameters->plotArea.left;
		while (markerPosition + xGridSpacing < (parameters->plotArea.right - xGridSpacing * 3 / 4))
		{
			markerPosition += xGridSpacing;

			valueRect.left = markerPosition - xGridSpacing / 2;
			valueRect.right = markerPosition + xGridSpacing / 2;
			valueRect.top = xAxisY + AxisMarkerLength;
			valueRect.bottom = parameters->plotArea.bottom;

			double value = (double)(markerPosition - parameters->plotArea.left)
				/ (double)(parameters->plotArea.right - parameters->plotArea.left)
				* (parameters->xTo - parameters->xFrom) + parameters->xFrom;

			if (fabs(value) < (parameters->xGridSpacing / 4.0))
			{
				value = 0;
				valueRect.left -= 20;
				_stprintf(stringValue, _T("%d"), 0);
			}
			else
			{
				_stprintf(stringValue, parameters->xMarkerFormat, value);
			}

			DrawText(hdc, stringValue, -1, &valueRect, DT_CENTER | DT_TOP);
		}
	}

	if (yAxisX > 0)
	{
		markerPosition = parameters->plotArea.top;
		while (markerPosition + yGridSpacing < (parameters->plotArea.bottom - yGridSpacing * 3 / 4))
		{
			markerPosition += yGridSpacing;

			valueRect.left = parameters->plotArea.left;
			valueRect.right = yAxisX - AxisMarkerLength;
			valueRect.top = markerPosition - yGridSpacing / 2;
			valueRect.bottom = markerPosition + yGridSpacing / 2;

			double value = parameters->yTo - (double)(markerPosition - parameters->plotArea.top)
				/ (double)(parameters->plotArea.bottom - parameters->plotArea.top)
				* (parameters->yTo - parameters->yFrom);

			if (fabs(value) < (parameters->yGridSpacing / 10.0))
				continue;

			_stprintf(stringValue, parameters->yMarkerFormat, value);

			DrawText(hdc, stringValue, -1, &valueRect, DT_RIGHT | DT_SINGLELINE | DT_VCENTER);
		}
	}

	SelectObject(hdc, previousFont);
}


Pasul 6 - DrawFunction

Pentru fiecare funcție de reprezentat se apelează DrawFunction. Aceasta funcționează în felul următor:

  • pentru fiecare pixel din lungimea suprefeței de desenat se calculează valoarea x corespunzătoare
  • se calculează y = f(x)
  • se verifică dacă y se regăsește în intervalul ymin, ymax ce se dorește afișat
  • dacă y se regăsește în acel interval, se calculează coordonata relativă la ecran și se verifică dacă a mai fost desenat un punct precedent
  • dacă un punct precedent a fost deja desenat, se trasează o dreaptă de la acel punct, la noul punct
  • dacă este primul punct din serie, se apelează MoveToEx spre coordonata punctului

void DrawFunction(HDC hdc, const PlotAction* action, const PlotParameters* parameters)
{
	HPEN pen, previousPen;
	double x, y;
	int xCoord, yCoord;
	BOOL inArea = FALSE;

	pen = CreatePen(action->style, 1, action->color);
	previousPen = (HPEN)SelectObject(hdc, pen);

	for (xCoord = parameters->plotArea.left; xCoord <= parameters->plotArea.right; xCoord++)
	{
		x = parameters->xFrom + (double)(xCoord - parameters->plotArea.left)
			/ (parameters->plotArea.right - parameters->plotArea.left)
			* (parameters->xTo - parameters->xFrom);
		y = action->function(x);

		if (isnan(y) || isinf(y) || y < parameters->yFrom || y > parameters->yTo)
		{
			// cannot display this point
			inArea = FALSE;
			continue;
		}

		yCoord = (int)round(parameters->plotArea.bottom - (double)(y - parameters->yFrom)
			/ (parameters->yTo - parameters->yFrom)
			* (parameters->plotArea.bottom - parameters->plotArea.top));

		if (inArea == FALSE)
		{
			MoveToEx(hdc, xCoord, yCoord, NULL);
			inArea = TRUE;
		}
		else
		{
			LineTo(hdc, xCoord, yCoord);
		}
	}

	SelectObject(hdc, previousPen);
}

Pasul 6 - DrawLegend

Legenda joacă și ea un rol important. Ea este amplasată în colțul din dreapta sus și prezintă o particularitate interesantă: dimensiunea chenarului de legentă depinde de lungimea maximă a mesajului ce identifică funcțiile de reprezentat. Această lungime se poate calcula însă ușor cu ajutorul funcției GetTextExtentPoint32.

void DrawLegend(HDC hdc, PlotAction actions[], const PlotParameters* parameters)
{
	int numberOfPlots = 0, legendWidth, legendHeight, maxTextWidth = 0, maxTextHeight = 0;
	HPEN pen, previousPen;
	HBRUSH brush, previousBrush;
	static HFONT font;
	HFONT previousFont;
	SIZE currentTextSize;
	RECT textPosition, identifierPosition;
	int i;

	for (i = 0; actions[i].function != NULL; i++)
	{
		numberOfPlots++;
	}

	font = CreateFont(-MulDiv(LegendFontSize, GetDeviceCaps(hdc, LOGPIXELSY), 72),
		0,
		0,
		0,
		FW_NORMAL, //grosime
		FALSE, //italic
		FALSE, //underline
		FALSE, //strikeout
		DEFAULT_CHARSET,
		OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS,
		CLEARTYPE_QUALITY,
		FF_DONTCARE,
		LegendFont);
	previousFont = (HFONT)SelectObject(hdc, font);

	for (i = 0; i < numberOfPlots; i++)
	{
		GetTextExtentPoint32(hdc, actions[i].legend, _tcslen(actions[i].legend), &currentTextSize);
		if (currentTextSize.cx > maxTextWidth)
		{
			maxTextWidth = currentTextSize.cx;
		}
		if (currentTextSize.cy > maxTextHeight)
		{
			maxTextHeight = currentTextSize.cy;
		}
	}

	legendWidth = LegendItemWidth + 20 + maxTextWidth + 2 * LegendWidthSpacing;
	legendHeight = numberOfPlots * maxTextHeight + 2 * LegendHeightSpacing;

	pen = CreatePen(LegendStyle, 1, PlotAreaBorderColor);
	brush = CreateSolidBrush(LegendBackgroundColor);

	previousPen = (HPEN)SelectObject(hdc, pen);
	previousBrush = (HBRUSH)SelectObject(hdc, brush);

	Rectangle(hdc, parameters->plotArea.right - legendWidth, parameters->plotArea.top,
		parameters->plotArea.right, parameters->plotArea.top + legendHeight);

	pen = CreatePen(PS_SOLID, 1, LegendTextColor);
	previousPen = (HPEN)SelectObject(hdc, pen);

	SetBkMode(hdc, TRANSPARENT);

	for (i = 0; i < numberOfPlots; i++)
	{
		textPosition.top = parameters->plotArea.top + maxTextHeight * i + LegendHeightSpacing;
		textPosition.bottom = textPosition.top + maxTextHeight;
		textPosition.left = parameters->plotArea.right - legendWidth;
		textPosition.right = parameters->plotArea.right - LegendWidthSpacing;

		DrawText(hdc, actions[i].legend, -1, &textPosition, DT_RIGHT | DT_BOTTOM);

		identifierPosition.top = textPosition.top + maxTextHeight / 4;
		identifierPosition.bottom = textPosition.bottom - maxTextHeight / 4;
		identifierPosition.left = parameters->plotArea.right - legendWidth + LegendWidthSpacing;
		identifierPosition.right = identifierPosition.left + LegendItemWidth;

		brush = CreateSolidBrush(actions[i].color);

		FillRect(hdc, &identifierPosition, brush);
	}

	SelectObject(hdc, previousPen);
	SelectObject(hdc, previousBrush);
	SelectObject(hdc, previousFont);
}


Derivarea funcțiilor

O aplicabilitate interesantă o reprezintă posibilitatea reprezentării unei funcții f'(x) care este derivata unei funcții f(x), fără însă a fi cunoscut/specificat codul funcției derivate.

Quote

Cum se poate una ca asta?

Foarte simplu, pornind de la definiția unei derivate, luând un h suficient de mic pentru o prezicie decentă, putem calcula valoarea derivatei în orice punct, având la dispoziție doar funcția f(x).

Astfel am creat o funcție de același tip ca precendentele (pentru a putea fi reprezentată folosind acelasi cod), ce calculează valoarea derivată în orice punct.
Pentru a specifica funcția ce se dorește derivată am introdus o variabilă globală, derivativeFrom, ce este un pointer la funcția ce se dorește derivată.

double derivative(double x)
{
	double h = 1e-6;
	FunctionType f = derivativeFrom;

	return (f(x + h) - f(x - h)) / 2 / h;
}


Figura de mai jos arată graficul lui f(x) și f'(x) pentru o funcției mai complicată. Ce se poate observa? Peste tot unde f(x) are un maxim/minim local, f'(x) este 0.

Attached File  derivata.png   18.85K   213 downloads

#2
dan-s

dan-s

    Active Member

  • Grup: Banned
  • Posts: 1,699
  • Înscris: 02.07.2013
Mai bine folosești o librărie gen Cinder sau openFrameworks....

#3
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,189
  • Înscris: 24.02.2007
S-a inteles tot, nici o intrebare? :D

#4
danzi23

danzi23

    Senior Member

  • Grup: Senior Members
  • Posts: 6,072
  • Înscris: 14.08.2013
cate ore ai lucrat la proiectul asta ?

#5
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,189
  • Înscris: 24.02.2007
O dupa-masa.

#6
psycho22

psycho22

    Member

  • Grup: Members
  • Posts: 334
  • Înscris: 11.02.2014
Un punct specific functiei va fi reprezentat doar de un pixel?

#7
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,189
  • Înscris: 24.02.2007
Da, dar daca ai 2-3 puncte foarte apropiate, posibil ca toate sa fie reprezentate de acelasi pixel

Anunturi

Second Opinion Second Opinion

Folosind serviciul second opinion ne puteți trimite RMN-uri, CT -uri, angiografii, fișiere .pdf, documente medicale.

Astfel vă vom putea da o opinie neurochirurgicală, fără ca aceasta să poată înlocui un consult de specialitate. Răspunsurile vor fi date prin e-mail în cel mai scurt timp posibil (de obicei în mai putin de 24 de ore, dar nu mai mult de 48 de ore). Second opinion – Neurohope este un serviciu gratuit.

www.neurohope.ro

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

Forumul Softpedia foloseste "cookies" pentru a imbunatati experienta utilizatorilor Accept
Pentru detalii si optiuni legate de cookies si datele personale, consultati Politica de utilizare cookies si Politica de confidentialitate