你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 跟高手学编程
自适应坐标格绘制的实现
 

  要:本文介绍了如何在VB中实现二维自适应坐标的绘制。根据显示区域大小和字体大小自适应地调整坐标,使各坐标标注不会发生重叠,也不会太疏散。其中的关键是自适应调整的算法,其原理同样适合于其它编程语言。

关键词:自适应,规范化,坐标标注

 

 函数在数学中几乎无处不在,在许多与数学有关的应用软件中要把函数显示在坐标中,但是如何在显示各种不同的函数或者显示同一个函数的不同部分时保证坐标格及相应的坐标标注疏密得当美观大方呢?这就需要采取一定的自适应算法实现这种功能。可以说自适应也是人工智能的重要组成部分。结合编写的演示程序(参见图1),我们详述如下。


                    1  演示程序界面  

 

一、规范化坐标标注

这里的规范化坐标标注指的是坐标标注的数值以125102050等数中的一个为间隔的坐标标注法。我们可以看到这些数的特点是:要么其本身是10的某次幂,要么是10的某次幂的一半或五分之一。所以实际上0.010.020.050.10.2的小数也是可以作为规范化的坐标间隔。我们暂且称这些数为规范间隔数。

为了合理的规范化坐标间隔,在给定一个允许的最小坐标间隔StepMin (StepMin>0)后,我们要找到比StepMin大的并且最接近StepMin的一个规范间隔数。为此我们编了一个函数dFindStep(),源代码如下:

Private Function dFindStep(StepMin As Double) As Double

Dim dStep As Double             '搜索比较值

Dim dStepOld As Double

If StepMin = 0 Then              'StepMin等于零则返回零

   dFindStep = 0

   Exit Function

End If

StepMin = Abs(StepMin)          '若小于零则取其绝对值

dStep = 50                      '从规范间隔数序列中的50开始搜索

If StepMin < dStep Then           'X<50,则不断往左搜索

    While StepMin < dStep        '此时dStep=50,5,0.5...

    dStepOld = dStep

    dStep = dStep / 2.5            '此时dStep=20,2,0.2...

    If StepMin < dStep Then

        dStepOld = dStep

        dStep = dStep / 2         '此时dStep=10,1,0.1...

        If StepMin < dStep Then

            dStepOld = dStep

            dStep = dStep / 2     '此时dStep=5,0.5,0.05...

        End If

    End If

    Wend

    dFindStep = dStepOld      ' 因为dStep<StepMin<dStepOld,所以返回值为dStepOld

Else                            'X>=50,则不断往右搜索

    While StepMin > dStep        '此时dStep=50,500,5000...

    dStep = dStep * 2             '此时dStep=100,1000,10000...

    If StepMin > dStep Then

        dStep = dStep * 2         '此时dStep=200,2000,20000...

        If StepMin > dStep Then

            dStep = dStep * 2.5    '此时dStep=500,5000,50000...

        End If

    End If

    Wend

    dFindStep = dStep            '返回dStep

End If

End Function

从上面的代码及其注释中很容易看到实现此函数的思路,在规范间隔数序列中从50开始向左或向右逐个与最小允许间隔StepMin比较,直到找到其右邻的规范间隔数为止。这个函数作为演示程序的示例函数供读者观察(参见图1)。这是一个具有自相似性质的超越函数。

二、自适应调整算法

    VB提供很强的可视化编程环境,许多功能可以通过鼠标点击来实现,但是真正能灵活控制程序行为的还

是实实在在的程序代码,而且利用程序代码往往是程序更加高效。比如坐标的文本标注,很容易想到的是利用工具栏中的Lable控件,但事实上坐标标注文本的数目较多多少不定长度各异位置不同,并且他们随着被观察函数的变化而变化,即使用Lable控件数组也非常繁琐,而且要占用较多内存资源。采用Windows API函数TextOut在窗口中显示文本是一个较好的方法。其声明可从VB外接程序API Viewer中拷贝得到,具体如下:

Private Declare Function TextOut Lib "gdi32" Alias "TextOutA" _

    (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long, _

ByVal lpString As String, ByVal nCount As Long) As Long

   Windows API 例程一般需要以像素为度量单位,TextOut函数中的参数X Y也不例外,但通常VB中缺省的单位是缇,我们可以从Screen对象(指整个 Windows 桌面)返回每一像素中水平 (TwipsPerPixelX属性) 或垂直 (TwipsPerPixelY属性)的缇数,这样就可以在缇和像素之间转换。

    在具体的绘制过程中我们需要用到两个坐标,一个是与我们要表示函数相对应的物理坐标,一个是屏幕上的窗口坐标,窗口左上角的坐标为(00)。所以同一个点有两种坐标。下面介绍演示程序中具体的坐标绘制函数DrawCoordinate(),其源代码如下。其中的参数(iCrossX,iCrossY)为X轴和Y轴交点的窗口坐标,(dStartX,dStartY)为此交点的物理坐标,iWidth,iHeight为坐标轴表示范围的窗口坐标宽度和高度,dEndX,dEndYXY轴表示范围的上界。这个函数在名为picCoordinatePictureBox控件中画坐标轴。

Private Sub DrawCoordinate( _

    iCrossX As Integer, iCrossY As Integer, iWidth As Integer, iHeight As Integer, _

    dStartX As Double, dStartY As Double, dEndX As Double, dEndY As Double)

Dim dCdnStep As Double    '物理数值步长

Dim lCdnStep As Long      '窗口数值步长

Dim crtPosition As Long   '当前窗口坐标位置

Dim UpN As Double         '最大格数

Dim dRemain As Double     '第一个坐标格的物理坐标长度

Dim dValue As Double      '当前物理坐标位置

Dim i As Integer

Dim strTemp As String     '存储坐标标注文本

Dim strFormat As String   '坐标标注文本的格式

Dim dHeight As Double     '物理坐标高度

Dim dWidth As Double      '物理坐标宽度

dHeight = dEndY - dStartY

dWidth = dEndX - dStartX

picCoordinate.Cls    '对画图区域进行清屏

'画带有箭头的两根坐标轴

picCoordinate.Line (iCrossX, iCrossY)- _

    (iCrossX + iWidth + 200, iCrossY), RGB(0, 0, 255)

picCoordinate.Line (iCrossX, iCrossY)- _

    (iCrossX, iCrossY - iHeight - 200), RGB(0, 0, 255)

picCoordinate.Line (iCrossX, iCrossY - iHeight - 200)- _

    (iCrossX - 30, iCrossY - iHeight), RGB(0, 0, 255)

picCoordinate.Line (iCrossX, iCrossY - iHeight - 200)- _

    (iCrossX + 30, iCrossY - iHeight), RGB(0, 0, 255)

picCoordinate.Line (iCrossX + iWidth + 200, iCrossY)- _

    (iCrossX + iWidth, iCrossY - 30), RGB(0, 0, 255)

picCoordinate.Line (iCrossX + iWidth + 200, iCrossY)- _

    (iCrossX + iWidth, iCrossY + 30), RGB(0, 0, 255)

'接着画Y轴坐标格

'计算屏幕显示的像素限制下的坐标格最大数目

UpN = iHeight / Me.picCoordinate.TextHeight("8")

'计算自适应坐标格间隔大小

dCdnStep = dFindStep(dHeight / UpN)

'计算坐标格起始位置

dRemain = (Int(dStartY / dCdnStep) + 1) * dCdnStep - dStartY

If dRemain = dCdnStep Then dRemain = 0

dValue = dStartY + dRemain

lCdnStep = dCdnStep * iHeight / dHeight

crtPosition = iCrossY - dRemain * iHeight / dHeight

'计算自适应坐标间隔后的实际坐标格数目

UpN = Int(dHeight / dCdnStep)

If crtPosition - UpN * lCdnStep > iCrossY - iHeight Then UpN = UpN + 1

'根据坐标间隔大小选择不同的坐标值显示格式

If dCdnStep > 1 Then

     strFormat = "######0"

Else

     strFormat = "#####0.#####"

End If

'画坐标格

For i = 1 To UpN

    picCoordinate.Line (iCrossX, crtPosition) _

                 -(iCrossX + 100, crtPosition), RGB(0, 0, 255)

    strTemp = CStr(Format(dValue, strFormat))

    TextOut picCoordinate.hdc, (iCrossX - 40 - Len(strTemp) * picCoordinate.TextWidth("8")) / Screen.TwipsPerPixelX, _

           (crtPosition - picCoordinate.TextHeight("8") / 2) / Screen.TwipsPerPixelY, _

            strTemp, Len(strTemp)

    dValue = dValue + dCdnStep ' WaveScreen.Left - 100 ,

    crtPosition = crtPosition - lCdnStep

Next i

'然后画X轴坐标格,方法步骤与画Y轴坐标类似

UpN = iWidth / Me.picCoordinate.TextWidth("8") / CInt(Abs(Log(dEndX - dStartX) / Log(10#)) + 4)

dCdnStep = dFindStep(dWidth / UpN)

dRemain = Int(dStartX / dCdnStep + 1) * dCdnStep - dStartX

If dRemain = dCdnStep Then dRemain = 0

dValue = dStartX + dRemain

lCdnStep = dCdnStep * iWidth / dWidth

crtPosition = iCrossX + dRemain * iWidth / dWidth

UpN = Int(dWidth / dCdnStep)

If crtPosition + UpN * lCdnStep < iCrossX + iWidth Then UpN = UpN + 1

If dCdnStep > 1 Then

     strFormat = "######0"

Else

     strFormat = "#####0.#####"

End If

For i = 1 To UpN

    picCoordinate.Line (crtPosition, iCrossY) _

                 -(crtPosition, iCrossY - 100), RGB(0, 0, 255)

    strTemp = CStr(Format(dValue, strFormat))

    TextOut picCoordinate.hdc, (crtPosition - Len(strTemp) * picCoordinate.TextWidth("8") / 2) / Screen.TwipsPerPixelX, _

           (iCrossY + 240 - picCoordinate.TextHeight("8")) / Screen.TwipsPerPixelY, _

            strTemp, Len(strTemp)

    dValue = dValue + dCdnStep

    crtPosition = crtPosition + lCdnStep

   

Next i

End Sub

由上面可以看出,以Y轴为例自适应调整算法的思路是:先根据窗口高度和字符高度算出坐标格数目的上

UpN,然后根据物理坐标高度和UpN算出允许的最小规范间隔,最后根据这个规范间隔和物理坐标起始值算出实际的坐标格位置和数目。对于X轴,不仅需要单个字符的宽度,还要估计坐标标注文本的字符个数,则可以用Cint(Abs(Log(dEndX - dStartX) / Log(10#)))估计标注文本长度,加上标注文本为小数时的第一个"0"和小数点,其误差最大为3,所以在其中加一个4就可以保证标注文本不会重叠。其它步骤与画Y轴坐标类似。

三、演示程序生成步骤

本演示程序用Windows98下的MicroSoft Visual Basic 6.0开发而成。具体步骤如下:

1.    进入VB6开发环境,打开新的标准工程(Standard EXE),工程名称可改为prjCdntDemo,Form1Name改为frmMainCaption为“自适应坐标演示:dFindStep()函数”,Width6150 Height4995

2.    按图一布置9CommandButton控件和一个PictureBox控件,CommandButtonName,Caption属性为:cmdXUp, 水平放大;cmdXDown, 水平缩小;cmdYUp, 垂直放大;cmdYDown, 垂直缩小;cmdLeft, 向左移动;cmdRight, 向右移动;cmdUp, 向上移动;cmdDown, 向下移动;cmdClose, 退出;PictureBox控件的NamepicCoordinate,Top1020,Left0,Width6000,Height3500。其它属性缺省。

3.在窗体代码的开头粘贴上TextOut的声明,在声明后头写上4个局部变量如下:

Private dStartX As Double   '物理坐标水平起始值

Private dEndX As Double     '物理坐标水平终止值

Private dStartY As Double   '物理坐标垂直起始值

Private dEndY As Double     '物理坐标垂直终止值

4.    然后粘贴上dFindStep(),DrawCoordinate()的源代码(见前文),最后写上如下事件相应函数代码:

Private Sub cmdClose_Click()

End      '结束程序

End Sub

Private Sub cmdDown_Click()

Dim dStep As Double

dStep = (dEndY - dStartY) / 4

dStartY = dStartY + dStep

dEndY = dEndY + dStep

Call DrawAll

End Sub

Private Sub cmdLeft_Click()

Dim dStep As Double

dStep = (dEndX - dStartX) / 4

dStartX = dStartX + dStep

dEndX = dEndX + dStep

Call DrawAll

End Sub

Private Sub cmdRight_Click()

Dim dStep As Double

dStep = (dEndX - dStartX) / 4

dStartX = dStartX - dStep

dEndX = dEndX - dStep

Call DrawAll

End Sub

Private Sub cmdUp_Click()

Dim dStep As Double

dStep = (dEndY - dStartY) / 4

dStartY = dStartY - dStep

dEndY = dEndY - dStep

Call DrawAll

End Sub

Private Sub cmdXDown_Click()

dEndX = dStartX + (dEndX - dStartX) * 1.5

Call DrawAll

End Sub

Private Sub cmdXUp_Click()

dEndX = dStartX + (dEndX - dStartX) / 1.5

Call DrawAll

End Sub

Private Sub cmdYDown_Click()

dEndY = dStartY + (dEndY - dStartY) * 1.5

Call DrawAll

End Sub

Private Sub cmdYUp_Click()

dEndY = dStartY + (dEndY - dStartY) / 1.5

Call DrawAll

End Sub

Private Sub Form_Load()

dStartX = 0

dStartY = 0

dEndX = 1000

dEndY = 600

End Sub

Private Sub DrawAll()

Dim iCrossX As Integer   '两坐标轴交点的窗口坐标X

Dim iCrossY As Integer   '两坐标轴交点的窗口坐标Y

Dim iHeight As Integer   '坐标轴的窗口坐标高度

Dim iWidth As Integer    '坐标轴的窗口坐标宽度

Dim i As Integer

Dim dX As Double         '示意波形的物理坐标X

Dim dY As Double         '示意波形的物理坐标Y,或窗口坐标Y

iCrossX = 600

iCrossY = picCoordinate.Height - 500

iWidth = picCoordinate.Width - 1000

iHeight = picCoordinate.Height - 1000

Call DrawCoordinate(iCrossX, iCrossY, iWidth, iHeight, dStartX, dStartY, dEndX, dEndY)

'画示意波形,以本程序的dFindStep()函数为例

dY = dFindStep(CDbl(dStartX))

If dY < dStartY Then

    dY = dStartY

ElseIf dY > dEndY Then

    dY = dEndY

End If

picCoordinate.PSet (iCrossX, iCrossY - (dY - dStartY) * iHeight / (dEndY - dStartY)), RGB(0, 0, 255)

For i = iCrossX To iCrossX + iWidth Step 2

    dX = dStartX + (i - iCrossX) * (dEndX - dStartX) / iWidth

    dY = dFindStep(dX)

    If dY >= dStartY And dY <= dEndY Then

        picCoordinate.Line -(i, iCrossY - (dY - dStartY) * iHeight / (dEndY - dStartY)), RGB(255, 0, 0)

    Else

        If dY < dStartY Then

            dY = dStartY

        ElseIf dY > dEndY Then

            dY = dEndY

        End If

        dY = iCrossY - (dY - dStartY) * iHeight / (dEndY - dStartY)

        If picCoordinate.CurrentY <> dY Then

            picCoordinate.Line -(i, dY), RGB(255, 0, 0)

        Else

            If dY = iCrossY - iHeight Then picCoordinate.PSet (i, dY), picCoordinate.BackColor

            If dY = iCrossY Then picCoordinate.PSet (i, dY), RGB(0, 0, 255)

        End If

    End If

Next i

End Sub

Private Sub Form_Resize()

If Me.Height < 4000 Then Me.Height = 4000

picCoordinate.Width = Me.Width - 150

picCoordinate.Height = Me.Height - 1500

Call DrawAll

End Sub

Private Sub picCoordinate_Paint()

Call DrawAll

End Sub

 这时就可以运行程序了。运行过程中可以点击命令按钮改变函数的观察范围或改变窗口大小观察坐标的自适应功能。

 

  推荐精品文章

·2024年12月目录 
·2024年11月目录 
·2024年10月目录 
·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089