PDA

View Full Version : رسم گراف در Canvas



بهروز عباسی
سه شنبه 30 آبان 1391, 13:14 عصر
درود به همه
من دارم یک برنامه می نویسم که در اون نیاز به رسم گراف دارم .
این برنامه رو برای نمونه نوشتم، که میزان استفاده از CPU رو نشون میده .
مشکلم اینه که نمیتونم کاری کنم که گراف رسم شده روی شیPaintBox ثابت باشه ،یعنی وقتی یک پنجره روی فرم من میاد گراف پاک نشه
از رویداد OnPaint هم استفاده کردم اما گراف توسط یک تایمر ایجاد میشه و اگه پنجره ای روی اون بیاد بازم پاک میشه

Felony
سه شنبه 30 آبان 1391, 13:57 عصر
از رویداد onpaint برای چی استفاده کردی ؟ توش چی کار کردی ؟! باید تو این رویداد گراف رو مجددا رسم کنید .

بهروز عباسی
سه شنبه 30 آبان 1391, 14:25 عصر
از رویداد onpaint برای چی استفاده کردی ؟ توش چی کار کردی ؟! باید تو این رویداد گراف رو مجددا رسم کنید .
ممنون از کمکت
اما گراف بصورت لحظه ای رسم میشه و من نمی خوام مقدارش رو جایی نگه دارم تا در OnPaint دوباره رسم بشه.
در ضمن من در رویداد OnPaint بک گراندش رو رسم میکنم (همون خونه ها و خط میانیش).

به غیر استفاده از رویداد OnPaint نمیشه از پاک شدن گراف جلوگیری کرد؟

Ananas
سه شنبه 30 آبان 1391, 15:23 عصر
سلام.
خوب فکر کنم روش رسمت ایرادای اساسی داره. اصلا اینطوری نباید رسم کنی. من فکر میکنم که یک لیستی از مقادیر رو باید داشته باشی مثلا تو یک آرایه، بعد موقع رسم هر بار به طور کامل تصویر رو با مقادیر اخیر رسم کنی روی یک بیتمپ بعد بیتمپ رو روی paint رسم کنی. با این کار طول آرایه طی گذشت زمان زیاد تر میشه که باید یک مقدار حد براش بگذاری که در صورت بزرگتر شدن از این مقدار ، کل آرایه رو کپی کنی یک واحد به عقب. مثال :
تو برنامتون این کد رو اضافه کنید:

//-----------------------------
var
CPU_array : array of Integer;
procedure UpdateCPU_array(MaxIndex : Integer);
var
i : Integer;
begin
i := Length(CPU_array) + 1;
if i > MaxIndex then
begin
CopyMemory(CPU_array, @CPU_array[i - MaxIndex], (MaxIndex - 1) * SizeOf(Integer));
i := MaxIndex;
end;
SetLength(CPU_array, i);
CollectCPUData;
CPU_array[High(CPU_array)] := Trunc(GetCPUUsage(0) * 100.0);
end;

procedure Redraw(dc : HDC; width, height, backRectSize : Integer; p : PInteger; count : Integer; MaxValue : Integer);
var
b : TBitmap;
i : Integer;
pV : PInteger;
verts : array of TPoint;
begin
b := TBitmap.Create;
b.Canvas.Brush.Color := 0;
b.SetSize(width, height);
//------------------- background
b.Canvas.Pen.Color := $00008000;
i := b.Width;
while i > 0 do
begin
b.Canvas.MoveTo(i, 0);
b.Canvas.LineTo(i, b.Height);
Dec(i, backRectSize);
end;
i := b.Height;
while i > 0 do
begin
b.Canvas.MoveTo(0, i);
b.Canvas.LineTo(b.Width, i);
Dec(i, backRectSize);
end;
//-------------------------
if count > 2 then
begin
pV := p;
SetLength(verts, count);
for i := 0 to count - 1 do
begin
verts[i] := Point((i * b.Width) div (count - 1), b.Height - (pV^ * b.Height) div (MaxValue + 1) - 2);
Inc(pV);
end;
end;
b.Canvas.Pen.Color := $0000FFFF;
b.Canvas.Polyline(verts);
//-------------------------
BitBlt(dc, 0, 0, b.Width, b.Height, b.Canvas.Handle, 0, 0, SRCCOPY);
b.Free;
end;
//-----------------------------

کد بالا رو ویرایش جزئی کردم (تو CopyMemory یکی از تعداد مورد نظر کم کردم و تو قسمت شروع آرایه ای از TPoint هم شروع حلقه و X ها رو تغییر دادم)
بعد تو تایمر اینطور بنویسید :

UpdateCPU_array(80);
Redraw(
pntGraph.Canvas.Handle,
pntGraph.ClientWidth,
pntGraph.ClientHeight,
10,
@CPU_array[0],
Length(CPU_array), 100);

Felony
سه شنبه 30 آبان 1391, 18:33 عصر
اون گراف یک حدی برای رسم خط ها داره ( مثلا 100 خط ) ، خط ها از 2 نقطه تشکیل شدن هر نقطه شامل یک X و Y ، درست ؟
به جای اون همه کد میتونستید با استفاده از Generic ها و کلاس TQueue یک Generic به صورت زیر تعریف کنید و مختصات خط ها رو بهش اضافه کنید و وقتی صف پر شد برای مختصات نقطه جدید عملیات Pop و Push رو پیاده کنید ، اینطوری سایز صف یک اندازه ثابتی داره ، مختصات خط هایی که از گراف خارج شده هم از صف Pop شده و الکی فضا اشغال نمیکنه .

type
TLinePoint = record
StartPos: TPoint;
EndPos: TPoint;
end;

var
MyGraph: TQueue<TLinePoint>;

برای رسم هم همون TQueue یک رویداد با نام OnNotify در اختیارتون میزاره که وقتی مقداری Posh یا Pop میشه صدا زده میشه ، پس تو این رویداد میتونید گراف رو رسم کنید ، به این ترتیب با اضافه یا حذف شدن یک نقطه به لیست به صورت خودکار گراف رسم میشه .

اینطوری هم کد کمتر میشه ، هم خواناتر میشه ، هم فنی تر !

Ananas
سه شنبه 30 آبان 1391, 20:49 عصر
اون گراف یک حدی برای رسم خط ها داره ( مثلا 100 خط ) ، خط ها از 2 نقطه تشکیل شدن هر نقطه شامل یک X و Y ، درست ؟نه دیگه. چرا باید x ها رو ذخیره کنیم؟ x ها ترتیب مشخص و فاصله های معینی دارن (این فرض ما برای ترسیم هست و درستش اینه که مقدار x باید متغیر زمان باشه) که تو هر فریم باید حساب بشه و داره به سمت چپ تصویر حرکت میکنه در حالی که مقادیر y تغییر ندارن و فقط به لیست اضافه یا از لیست کم میشن. ذخیره کردن x ها کار رو پیچیده تر میکنه.


به جای اون همه کد میتونستید با استفاده از Generic ها و کلاس TQueue یک Generic به صورت زیر تعریف کنید و مختصات خط ها رو بهش اضافه کنید و وقتی صف پر شد برای مختصات نقطه جدید عملیات Pop و Push رو پیاده کنید ، اینطوری سایز صف یک اندازه ثابتی داره ، مختصات خط هایی که از گراف خارج شده هم از صف Pop شده و الکی فضا اشغال نمیکنه .
استفاده از Generic هم شاید خوب باشه (راحت تر برای برنامه نویس) ولی چون من فقط اطلاعات Integer دارم ذخیره میکنم (حجم نوع ذخیره شونده کمه و نمی ارزه که برای هر کدوم یک اشاره گر ذخیره کنیم) من نوشتن تابع UpdateCPU_array (هر چند شاید بقیه نوشتن 16 خط کد رو انتخاب نکنن) و استفاده از یک آرایه ی معمولی رو انتخاب میکنم که طولش هم از حد معین شده بیشتر نمیشه و اطلاعات اضافی هم ذخیره نمیکنه و سرعت اجرای نسبتا خوبی هم داره.



TLinePoint = record
StartPos: TPoint;
EndPos: TPoint;
end;
چرا؟ چه مزیتی داره؟ این جا اطلاعات تکراری و اضافی داره ذخیره میشه چون ما مجموعه ای از خط ها رو نداریم ما مجموعه ای از مقادیر یک تابع (اصطلاح ریاضی) نسبت به زمان رو داریم. اگه بخوایم براش x ذخیره کنیم باید مقدار زمان رو ذخیره کنیم که کار پیچیده تر میشه و البته دقیق تر که فکر نمیکنم لازم باشه (به دلیل اینکه تایمرمون با فاصله های زمانی کاملا یکسان مقدار جدید به لیست اضافه نمیکنه پس فاصله ی x ها ممکنه تو هر فریم متفاوت باشه)


اینطوری هم کد کمتر میشه ، هم خواناتر میشه ، هم فنی تر ! موافقم. کد تمیز تر و قشنگ تر یا همه کس فهم تر (چی گفتم!!) میشه ولی ایراداتی که گفتم رو داره.

Felony
سه شنبه 30 آبان 1391, 21:34 عصر
استفاده از Generic هم شاید خوب باشه (راحت تر برای برنامه نویس) ولی چون من فقط اطلاعات Integer دارم ذخیره میکنم (حجم نوع ذخیره شونده کمه و نمی ارزه که برای هر کدوم یک اشاره گر ذخیره کنیم) من نوشتن تابع UpdateCPU_array (هر چند شاید بقیه نوشتن 16 خط کد رو انتخاب نکنن) و استفاده از یک آرایه ی معمولی رو انتخاب میکنم که طولش هم از حد معین شده بیشتر نمیشه و اطلاعات اضافی هم ذخیره نمیکنه و سرعت اجرای نسبتا خوبی هم داره.
شاید خوب باشه ؟! :لبخندساده:
اشاره گر کجا بود ؟!
اگر منظورت Generic ها هستند که Generic ها در زمان کامپایل توسط کامپایلر اعمال میشن و تقریبا در Runtime کار خاصی انجام نمیدن ، اگر هم منظورتون TQueue هست که پیاده سازی اون با همون آرایه هست ، پس شما میتونی همون کد خودت رو با Generic ها به صورت زیر بنویسی :

var
MyGraph: TQueue<Integer>;

نه اشاره گری در کار هست ، نه میزان فضای اشغال شده بیشتری ، فقط کار فنی هست !



چرا؟ چه مزیتی داره؟ این جا اطلاعات تکراری و اضافی داره ذخیره میشه چون ما مجموعه ای از خط ها رو نداریم ما مجموعه ای از مقادیر یک تابع (اصطلاح ریاضی) نسبت به زمان رو داریم. اگه بخوایم براش x ذخیره کنیم باید مقدار زمان رو ذخیره کنیم که کار پیچیده تر میشه و البته دقیق تر که فکر نمیکنم لازم باشه (به دلیل اینکه تایمرمون با فاصله های زمانی کاملا یکسان مقدار جدید به لیست اضافه نمیکنه پس فاصله ی x ها ممکنه تو هر فریم متفاوت باشه
اینکه x رو ذخیره بکنی یا نه بستگی به کارت داره ، ربطی به راه حل ارائه شده نداره ، همونطور که گفتم شما همون استفاده از Integer رو میتونه با اون صف پیاده کنی .

Ananas
سه شنبه 30 آبان 1391, 22:49 عصر
اگر منظورت Generic ها هستند که Generic ها در زمان کامپایل توسط کامپایلر اعمال میشن و تقریبا در Runtime کار خاصی انجام نمیدن ، اگر هم منظورتون TQueue هست که پیاده سازی اون با همون آرایه هست ، پس شما میتونی همون کد خودت رو با Generic ها به صورت زیر بنویسی
خوبه. این رو میگذاریم به عهده ی آقای عباسی.

بهروز عباسی
جمعه 17 آذر 1391, 07:24 صبح
اون گراف یک حدی برای رسم خط ها داره ( مثلا 100 خط ) ، خط ها از 2 نقطه تشکیل شدن هر نقطه شامل یک X و Y ، درست ؟
به جای اون همه کد میتونستید با استفاده از Generic ها و کلاس TQueue یک Generic به صورت زیر تعریف کنید و مختصات خط ها رو بهش اضافه کنید و وقتی صف پر شد برای مختصات نقطه جدید عملیات Pop و Push رو پیاده کنید ، اینطوری سایز صف یک اندازه ثابتی داره ، مختصات خط هایی که از گراف خارج شده هم از صف Pop شده و الکی فضا اشغال نمیکنه .

type
TLinePoint = record
StartPos: TPoint;
EndPos: TPoint;
end;

var
MyGraph: TQueue<TLinePoint>;

برای رسم هم همون TQueue یک رویداد با نام OnNotify در اختیارتون میزاره که وقتی مقداری Posh یا Pop میشه صدا زده میشه ، پس تو این رویداد میتونید گراف رو رسم کنید ، به این ترتیب با اضافه یا حذف شدن یک نقطه به لیست به صورت خودکار گراف رسم میشه .

اینطوری هم کد کمتر میشه ، هم خواناتر میشه ، هم فنی تر !

درود به همه
خیلی ممنون از کمک با کد جناب Ananas (http://barnamenevis.org/member.php?233990-Ananas) مشکلم حل شد
اما اقا ماهان اگه ممکنه در باره چیزی که گفتید کمی بیشتر توضیح بدید به نظر چیز جالبیه اما من نتونستم ازش استفاده کنم.

Ananas
جمعه 17 آذر 1391, 16:57 عصر
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Generics_Collections_TQueue.html

Delphi Coder
شنبه 18 آذر 1391, 14:35 عصر
اون یونیتی که شما استفاده کردی (adCpuUsage) باگ داره. روی ویندوز 64 بیت هم جواب نمیده.
برای رسم گراف میتونید از Image32 استفاده کنید و ترسیمات رو روی Bitmap اون انجام بدید، هم خیلی سریعتر هست و هم پاک نمیشه. فقط باید package مربوطه رو دانلود و نصب کنید.(Graphics32)

MohsenB
یک شنبه 19 آذر 1391, 12:32 عصر
سلام

در کل روش باید این باشه که شما یه بافر ( حالا آرایه و یا چیز دیگه ) رو هر بار ( با تایمر ) مقدار بدید و در رویداد پینت اونو روی خروجی رسم کنید .

الان به نظر من بهترین کاری که میتونید با این کدتون انجام بدید اینه که بجای رسم روی پینت یه متغیر از نوع بیتمپ بصورت عمومی تعریف کنید ، و هر بار که موقع رسم شد ( سپری شدن تایمر ) اونو توی اون بیتمپ بکشید و همینطور رویداد پینت اون پینت باکس رو هم صدا بزنید که توی اون رویداد کشیدن این بیتمپ رو روی محدوده نمایش پینت کدنویسی کرده اید .

موفق باشید