Delphi如何使用基本的绘图函数绘制统计图?

Delphi如何使用基本的绘图函数绘制统计图

一个windows自带的画图工具是无论如何也不能满足我们的画图需要的,很多效果都需要我们在另外的工具中来实现。这些高级的功能是如何实现的呢,如何操纵一些基本的属性和函数,让它们最终能作出我们想要的效果呢?这里我们以绘制统计图来说明这些问题。

解决思路――

这里,我们暂且先撇开具体的问题,综合地一下讨论画图的问题。

  画图工具是基本元素的具体实现,对于我们初学者来说,还是有很好的参考价值的,在delphi 5中有一个自带的工程例子“……Borland\Delphi5\Demos\Doc\Graphex”,这个例子可以实现一些基本的绘图功能。对这个例子多加修改,一定会有所收获的。这里就不列出它的详细代码了,有心的读者可以自己找到这个例子。我这里只是想综合地讨论这方面的问题。使用DELPHI编写绘图软件的灵魂就在于操作画布,画笔和刷子,尽可能地挖掘它们的属性和相关参数的设置。

(一)画布

画布,画笔和刷子之间的关系很明了.其实,画笔和刷子都是画布的一个属性.而画布也只是TForm,TImage,TShape等组件对象的一个属性,专门负责与图象相关的信息打交道.它的主要作用可以概括如下几点:

1.指定使用画笔,刷子和字体的使用类型;

2.绘制和填充指定形状的线或图形;

3.修饰和改变图象;

画布的主要属性有:

Brush--指定填充图形和背景的样式

CanvasOrientation--指定画布的定位类型,有coLeftToRight, coRightToLeft两个属性;

ClipRect--指定剪切矩形的边界;

CopyMode--指定图形图象的复制模式;

Font--指定画布上使用的字体;

Handle--为画布指定窗口GDI对象的设备描述表;

LockCount--指定画布被别的线程锁定的次数;

Pen--指定画布上使用的画笔,具体见下面描述;

PenPos--指定画笔当前的位置;

Pixels--指定当前剪切矩形的象素颜色;

TextFlags--指定字体在画布上的显示方式,有ETO_CLIPPED,ETO_OPAQUE,ETO_RTLREADING, ETO_GLYPH_INDEX,ETO_IGNORELANGUAGE,ETO_NUMERICSLOCALETO_NUMERICSLATIN等值可选;

画布相关的API函数及其注释如下:

Arc--按指定方式画一条弧;

BrushCopy--把位图复制到指定的画布的矩形中,用画布刷子颜色替换位图的颜色;

Chord--按指定方式画弦;

CopyRect--从一个矩形区域复制部分图象到另一个矩形区域;

Draw--用指定参数在指定位置画图;

DrawFocusRect--按指定焦点风格,通过异或操作来绘制一焦点矩形;

Ellipse--按指定参数画一椭圆;

FillRect--按指定的刷子填充一矩形;

FloodFill--使用当前选定的刷子填充指定设备描述表中的一块区域;

FrameRect--使用指定的方式画一矩形的边框;

LineTo--使用当前画笔从当前位置到指定点画一条直线;

Lock--防止其它线程在画布上绘图;

MoveTo--指定一新的当前画笔位置;

Pie--按指定方式画饼状图;

PolyBezier--按指定方式画多条贝塞尔线;

PolyBezierTo--按指定方式画多条贝塞尔线并更新当前的画笔位置值;

Polygon--绘制一个由多个顶点的任意序列组成 的多边形;

Polyline--使用当前画笔画一系列的多边形;

Rectangle--绘制矩形;

RoundRect--绘制圆角矩形;

StretchDraw--在指定的矩形区域通过指定的绘图参数来绘制图形;

TextExtent--返回使用当前字体设置的字符的象素宽度和高度等参数;

TextHeight--返回使用当前字体设置的字符的象素高度;

TextOut--在指定位置绘制文本,并更新画笔的当前位置;

TextRect--在一剪切矩形区域中绘制文本;

TextWidth--返回使用当前字体设置的字符的象素宽度;

TryLock--对当前没加锁的画布进行加锁;

Unlock--对当前加锁的画布进行解锁;

例如以下是两个小例子:

procedure TForm1.Button2Click(Sender: TObject);

var

ARect: TRect;

begin //实现了剪切效果;

with Image1.Canvas do

begin

CopyMode := cmWhiteness; //设置复制模式;

ARect := Rect(0, 0, Image1.Width, Image1.Height);

CopyRect(ARect, Image1.Canvas, ARect);

CopyMode := cmSrcCopy; //恢复复制模式;

end;

end;

procedure TForm1.Button3Click(Sender: TObject);

var

W: Word;

begin //在窗口中画一条彩线;

for W := 10 to 200 do

Canvas.Pixels[W, 10] :=RGB(random(255),random(255),random(255));;

end;

灵活使用这些函数及其内部参数会让我们得到意想不到的效果;

(二) 画笔

画笔是一个GDI对象,定义了绘制直线或轮廓形状的方法.

画笔内部共有五种属性:颜色,句柄,模式,风格和宽度.

Color--决定指定直线或轮廓形状的RGB颜色。

Handle--指向了窗口画笔对象句柄。

Mode--指定了画笔以何种方式在画布(canvas)上画线,在帮助文档中的该定义是(全部以pm_开头):

type TPenMode =( pmBlack, //总是黑色;

pmWhite, //总是白色;

pmNop, //颜色不变;

pmNot, //画布颜色取反;

pmCopy, //颜色属性中指定的画笔颜色;

pmNotCopy, //画笔颜色取反;

pmMergePenNot, //画笔颜色和画布背景色取反后颜色的结合;

pmMaskPenNot, //画笔颜色和画笔背景色取反后颜色共同色的结合;

pmMergeNotPen, //画笔颜色取反后和画布背景色的结合;

pmMaskNotPen, //画布颜色和画笔颜色取反后颜色共同色的结合;

pmMerge, //画笔和画布背景色的结合;

pmNotMerge, //画笔颜色和画布背景色的结合;

pmMask, //画笔和画布背景色共同色的结合;

pmNotMask, //pmMask取反,画笔和画布背景色共同色的结合;

pmXor, //取画笔或画布背景中的任一种颜色;

pmNotXor //pmXor取反,取画笔或画布背景中的任一种颜色;

);

Style--则指定了画笔操作的风格,在线文档中的定义是(全部以ps_开头):

type TPenStyle=( psSolid, //画笔是───

psDash, //画笔是------

psDot, //画笔是......

psDashDot, //画笔是_._._.

psDashDotDot, //画笔是_.._..

psClear, //画笔是透明色

psInsideFrame //画笔是实线,但设置大于1时会抖动;

);

另外,在windows.pas中还有其他扩展的画笔风格定义,只在特殊的支持设备上

才有效,如PS_ENDCAP_ROUND, PS_JOIN_ROUND等;

Width--指定了待使用画笔的宽度,单位是象素.

和画笔相关的函数有:

CreatePen--用指定风格创建画笔;

CreatePenIndirect--根据LOGPEN数据结构创建一画笔;

ExtCreatePen-- 创建带指定风格,宽度和刷子属性的几何画笔;

(三)刷子

刷子定义了区域填充的GDI对象,刷子是一个8×8象素的区域,它可以被绘制在指定的设

备上.刷子不仅可以是纯色的,也可以由不同的位图图案组成.

刷子的属性有位图,颜色,句柄和风格四种:

Bitmap--是指定一个外部位图文件来填充指定的区域.如果指定的图象比填充的区域大,

则只有左上角与填充区域等大的部分有效,其余的被自动裁减了.

Color--指定了刷子的颜色.当刷子风格为bsClear时,该属性无效.

Handle--指向指定设备窗口.

Style--则指定了当前刷子的填充风格,在线文档中的定义是(都以bs_开头):

type TBrushStyle=( bsSolid, //填充格式为实体填充

bsClear, //填充格式为透明填充

bsHorizontal, //填充格式为------

bsVertical, // 填充格式为|||||

bsFDiagonal, // 填充格式为/////

bsBDiagonal, // 填充格式为\\\\\

bsCross, // 填充格式为+++++

bsDiagCross // 填充格式为xxxxx

);

和刷子有关的API函数有:

CreateBrushIndirect--根据LOGBRUSH创建一刷子;

CreateDIBPatternBrushPt--使用设备无关位图来创建刷子,以便指定刷子的模式;

CreateHatchBrush--创建一带有阴影模式的刷子,阴影模式为以HS_开头的常数;

CreatePatternBrush--用位图来创建刷子,以便指定刷子的模式;

CreateSolidBrush--创建一实体颜色刷子;

GetBrushOrgEx--获取指定设备描述表中当前选择刷子的原点;

GetSysColorBrush--获取和指定颜色索引相关的逻辑刷子的句柄;

SetBrushOrgEx--设置指定设备描述表中当前选择刷子的原点;

(四)画图和填充相关的API函数;

BeginPaint--准备在指定窗口绘画或对指定区域进行填充;

DrawAnimatedRects--NT支持函数,画一环有游动边框的矩形;

DrawCaption--NT支持函数,为指定窗口的标题赋值;

DrawEdge--为指定矩形画一道或多道边框;

DrawFocusRect--画焦点矩形;

DrawFrameControl--画一指定类型和风格的边框控件;

DrawState--NT支持函数,为图象画一可视效果标明其状态;

DrawStateProc--NT支持函数,调用为图象画一可视效果标明其状态的函数;

DrawTextEx--NT支持函数,在指定区域输出格式化文本;

EndPaint--结束绘画;

ExcludeUpdateRgn--将窗口无效部分(更新区域)从裁剪区中排除掉;

GdiFlush--使当前GDI闪烁;

GdiGetBatchLimit--获取缓冲GDI函数数量;

GdiSetBatchLimit--设置缓冲GDI函数数量;

GetBkColor--获取背景颜色;

GetBkMode--获取背景模式;

GetBoundsRect--获取边界矩形;

GetROP2--获取当前绘图模式;

GetUpdateRect--获取指定窗口最小的矩形;

GetUpdateRgn--获取描述窗口中无效区的区域;

GetWindowDC--获取窗口DC;

GetWindowRgn--获取窗口区域;

GrayString--在指定位置画灰色文本;

InvalidateRect--使DC指定的矩形无效;

InvalidateRgn--使DC指定的矩形无效;

LockWindowUpdate--禁止或允许在指定窗口中绘画;

OutputProc--调用输出进程,向GrayString输送文本;

PaintDesktop--NT支持函数,在指定的窗口区域用指定的桌面颜色或墙纸填充裁剪区;

RedrawWindow--更新客户区的指定区域或矩形;

SetBkColor--设置背景颜色;

SetBkMode--设置背景模式;

SetBoundsRect--设置边界矩形;

SetRectRgn--设置矩形区域;

SetROP2--设置当前绘图模式;

SetWindowRgn--设置窗口区域;

UpdateWindow--更新窗口;

ValidateRect--使客户区中指定矩形有效;

ValidateRgn--使客户区中的指定区域有效;

WindowFromDC--获取和指定窗口相关的句柄;

具体实现――

1.本例以常见的统计图来说明问题。该例能实现对统计图的动态绘制,并且可以自定义设置统计图的形状和颜色。在说明问题之前,来了解程序用到的一些比较复杂的函数或算法:

函数――

1.Polygon(Points: array of TPoint)

用于绘出指定的多边形。括号内是预定点的集合,该集合可以在使用之前定义,也可以在使用时同时定义,本例属于后者;

2.Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Longint)

用于绘制饼状图,饼状图其实就是椭圆的一部分。在这些参数中,其中(X1, Y1)和(X2, Y2)定义了框住饼状图的矩形,而从椭圆中心发出的射线经过(X3, Y3)和(X4, Y4)两点,就把一个饼状图截出来了。

3.FormatFloat(const Format: string; Value: Extended)

函数的意义是按指定方式格式化字符串,Format指定了格式化的方式,Value则指定了要格式化的文本或其他数据。下面列举了一些范例,可供我们学习时参考:

格式化符号(Format) 1234 -1234 0.5 0

1234 -1234 0.5 0

0 1234 -1234 1 0

0.00 1234.00 -1234.00 0.50 0.00

#.## 1234 -1234 .5

#,##0.00 1,234.00 -1,234.00 0.50 0.00

#,##0.00;(#,##0.00) 1,234.00 (1,234.00) 0.50 0.00

#,##0.00;;Zero 1,234.00 -1,234.00 0.50 Zero

0.000E+00 1.234E+03 -1.234E+03 5.000E-01 0.000E+00

#.###E-0 1.234E3 -1.234E3 5E-1 0E0

该例是在小数点后保留两位小数,因此用"##.##",具体见程序代码中。

算法――

本例的实现依赖一定的算法。这里介绍主要的两点:

1)在连接多边形各点时,我们要注意那几个点一定要构成一个闭合的图形,这就要保证最后一个点要和第一个点重合。至于其他的点怎么布局,则要有一定的空间感。

我们先画一个矩形,然后再根据平行关系确定其他的点:

rectangle(50,x,70,220); //画主视面;

Canvas.Polygon([Point(50, x), Point(70,x-10),Point(90,x-10), Point(70, x),Point(50, x)]);  //画顶面;

Canvas.Polygon([Point(90,x-10), Point(70, x),Point(70,220),Point(90,210),Point(90,x-10)]); //画侧面;

2)确定圆上指定角度的边与圆的交点

在该例中画饼状图时,我们要按照一定的比例画扇形,这就要确定扇形的起始点和终止点。我们把起始点设为一个定点,而终止点则根据实际情况设置,如图:

――饼状图的数学原理――

假设画图时已知一个比例数是K,则在饼状图中的角度是θ=(K*360),根据图中的关系(Y轴向下符合屏幕坐标系定义),可以用三角函数知识求得PX,PY:

PX=op*cosθ

PY=op*sinθ

上述式子的前提条件是O点是原点,OP是圆的半径。如果O点不是原点,而是坐标系中的一个点(X0,Y0),则此时的P点坐标是

PX=X0+op*cosθ

PY=Y0+op*sinθ

3.本例的界面布局可以参考程序运行的结果图,其中代表“刷子类型”的combobox1的items的属性设置如图。该例实现的主要代码如下:

procedure TForm1.Button1Click(Sender: TObject);

var

x,i,j:integer;

k:real;

begin

refresh;

//标明写上“Y”轴;

label4.left:=25;

label4.top:=2;

label4.caption:='Y';

label4.Transparent:=true;

//标明写上“X”轴;

label5.left:=395;

label5.top:=227;

label5.caption:='X';

label5.Transparent:=true;

x:=220-round(strtofloat(edit1.text)/strtofloat(edit2.text)*200);

with form1.Canvas do

begin

pen.width:=strtoint(edit3.text); //设置画笔宽度;

case combobox1.Items.IndexOf(combobox1.text) of //设置刷子的填充风格;

0: brush.style:=bsSolid;

1: brush.style:=bsClear;

2: brush.style:=bsHorizontal;

3: brush.style:=bsVertical;

4: brush.style:=bsFDiagonal;

5: brush.style:=bsBDiagonal;

6: brush.style:=bsCross;

7: brush.style:=bsDiagCross;

end;

//画出X轴;

MoveTo(2,220);

LineTo(400,220);

//画出Y轴;

MoveTo(20,5);

LineTo(20,230);

//画出Y轴的箭头方向"∧";

moveto(20,5);

lineto(15,12);

moveto(20,5);

lineto(25,12);

//画出X轴的箭头方向"∧";

moveto(400,220);

lineto(395,213);

moveto(400,220);

lineto(395,227);

if checkbox1.Checked then //绘制立体的直方柱图;

begin

//画正面的矩形图;,可以根据实际情况动态定义它的高度;

rectangle(50,x,70,220);

//画顶面,随着正面矩形的高度变化而变化;

Canvas.Polygon([Point(50, x), Point(70,x-10),Point(90,x-10), Point(70, x),Point(50, x)]);

//画侧面,随着正面矩形的高度变化而变化;

Canvas.Polygon([Point(90,x-10), Point(70, x),Point(70,220),Point(90,210),Point(90,x-10)]);

end

else

rectangle(50,x,70,220); //如果没有选中要以立体形式绘制,则以平面形式绘制的直方柱图;

//画饼状统计图

k:=(strtofloat(edit1.text)/strtofloat(edit2.text))*360; //将数据按比例转换成;

i:=round(250+100*cos(k*3.14159/180));

j:=round(120+100*sin(k*3.14159/180));

pie(150,20,350,220,i,j,350,120);

label3.caption:='比例是'+formatfloat('##.##',(k/360)*100)+'%'; //设置比例的函数;

end;

end;

procedure TForm1.Shape1MouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

if colordialog1.Execute then //设置窗口背景颜色;

shape1.Brush.color:=colordialog1.Color;

form1.color:=ColorDialog1.Color;

end;

procedure TForm1.Shape2MouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

if colordialog2.Execute then //设置刷子颜色;

shape2.Brush.color:=colordialog2.Color;

form1.Canvas.Brush.color:=colordialog2.Color;

end;

procedure TForm1.Shape3MouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

if colordialog3.Execute then //设置画笔颜色;

shape3.Brush.color:=colordialog3.Color;

form1.Canvas.Pen.color:=colordialog3.Color;

end;