在 GDI+ 中绘制折线图






4.86/5 (10投票s)
在笛卡尔坐标系中绘制折线图(VB.NET 和 GDI+)
引言
几年前,我使用了 Felix Haas 的 VB 6.0 项目的示例代码,名为《无需 ActiveX / OCX 控件的快速图表》。他在其中提出了一种相对简单但有效的方法,使用 GDI 构建带有网格线的笛卡尔坐标系。
当我在 VB 6.0 中需要绘制基于手动输入值、从数据库查询的数据或实时数据的折线图时,我将此代码用作模板。在 VB.NET 和 GDI+ 引入后,与图形相关的代码发生了巨大变化。
此示例代码是初始 VB 6.0 代码对 GDI+ 类的改编,并升级到 VB 2010。
背景
Windows GDI/GDI+ 使用镜像的笛卡尔坐标系第一象限,原点 (0,0) 位于左上角,而不是笛卡尔模型中的左下角原点。Windows 坐标以这种方式绘制的原因来自初始的 GDI 映射模式(控制逻辑坐标如何转换为设备坐标的设备上下文的属性)。
摘自 Jeff Prosise 著的《使用 MFC 编程 Windows》,O Reilly, 1999 年,章节《Windows GDI》:
在 GDI 中,当您使用 MM_TEXT 映射模式绘图时,您使用的是坐标系统,其中原点位于窗口的左上角,正 x 轴指向右侧,正 y 轴指向下方,并且 1 个单位等于 1 个像素。您切换到其中一种“公制”映射模式 - MM_LOENGLISH, MM_HIENGLISH, MM_LOMETRIC, MM_HIMETRIC 或 MM_TWIPS - y 轴翻转,使得正 y 指向上方,并且逻辑单位被缩放以表示真实距离,而不是原始像素计数。
我们绘制在屏幕上的文本从左上角开始,行数递增。
而默认的 GDI 映射模式是 MM_TEXT
,这对于图像绘制来说有点令人困惑。
在 Visual Studio、C++ 和 GDI 中,您可以应用公制映射模式或用户定义的映射模式来获得图表功能。
在 VB 6.0 和 GDI 中,您可以使用方法 Scale
、Line
以及属性 AutoRedraw
、CurrentX
、CurrentY
来构建坐标并绘制图表。
在 VB.NET 和 GDI+ 中,您必须使用转换(Translate
、Scale
等)才能获得类似的结果。
Using the Code
该项目由两个模块组成:frmChart.vb(带有绘图过程的主窗体)和 modSettings.vb(图表的初始设置、转换函数和 public
变量)。在 Visual Basic 6 中,默认的图形单位是 twips(因此初始项目是基于它们的)。
在 VB.NET 中,它们被像素取代,为了升级,我们使用 VB.NET 中可用的 points 单位,因为它更容易重新计算。为了在窗体重绘时重绘图形对象(在 VB 6.0 中使用 AutoRedraw
完成),使用了 Form.Paint
事件。
在 VB 6.0 中,我们可以使用 Scale
方法来镜像第二象限,并将原点设置为左下角。在 VB.NET 中,此图形方法被删除,我们必须使用转换来获得类似的图表功能。
来自 MSDN 转换概述
转换定义了如何将点从一个坐标空间映射或转换为另一个坐标空间。
此映射由一个转换矩阵描述,该矩阵是一个包含三行三列 Double 值的集合.
这是相应的代码
Dim graphicsObj As Graphics = eventArg.Graphics
'For this simple chart we actually don't need a Matrix object,
'but I left here as it can be needed for more complex tasks:
Dim chartMatrix As New Drawing2D.Matrix()
graphicsObj.Transform = chartMatrix
请注意,我们只是在此代码中定义了图形对象,而没有对标签(在本例中为数字)进行转换,以便它们能够正确绘制。
'Draw labels and markers at X-axis:
For I = Xmin To Xmax
graphicsObj.DrawString(Trim(Str(I)), drawFont, drawBrush, _
XinPoints(I) - 2 * Len(Trim(Str(I))), YinPoints(0) + 100)
Next
'Draw labels and markers at Y-axis:
For I = Ymin To Ymax ' Y-Axis
graphicsObj.DrawString(Trim(Str(-scaleFactorY * I + Ymax + Ymin)), _
drawFont, drawBrush, XinPoints(scaleFactorX) - 10 - _
2 * Len(Trim(Str(scaleFactorY * I))), YinPoints(scaleFactorY * I) - 12)
Next
现在,我们可以将转换应用于图形对象
'Move the origin to the bottom left corner
'(position it inside Picture Box = Picture Height + border from the top).
graphicsObj.TranslateTransform(0.0F, PictureHeight + 20)
graphicsObj.ScaleTransform(1, -1) ' Scales an element by the specified
' ScaleX and ScaleY amounts
' multiplying the transformation matrix
' by a diagonal matrix whose elements are (1,-1)
现在,我们使用 graphicsObj.DrawLine
方法构建带有网格线的坐标(这里仅用于 X 轴)。
'Construct X-axis and Y-axis:
'X axis:
' use DrawLine(Pen, Single, Single, Single, Single)
' Draws a line connecting the two points specified by the coordinate pairs.
graphicsObj.DrawLine(blackPen, XinPoints(Xmin) - 100, _
YinPoints(0), XinPoints(Xmax) + 5, YinPoints(0))
'Add arrow to X+ axis:
graphicsObj.DrawLine(blackPen, XinPoints(Xmax) + 5, _
YinPoints(0), XinPoints(Xmax), YinPoints(0) - 5)
graphicsObj.DrawLine(blackPen, XinPoints(Xmax) + 5, _
YinPoints(0), XinPoints(Xmax), YinPoints(0) + 5)
'Gridlines at X-axis:
For I = Xmin To Xmax ' X-axis
'Create a dashed line:
blackPen.DashStyle = Drawing2D.DashStyle.Dash
If I <> 0 Then
' Gridlines vertical:
graphicsObj.DrawLine(blackPen, XinPoints(I), _
YinPoints(Ymin), XinPoints(I), YinPoints(Ymax))
End If
Next
最后 - 在坐标上绘制测试线。
'Draw test line from 0,0
x = 0
y = 0
graphicsObj.DrawLine(bluePen, XinPoints(1), YinPoints(6), _
XinPoints((Xmax - Xmin) / 20 * x + Xmin), YinPoints(y))
x = 1
y = 6
graphicsObj.DrawLine(bluePen, XinPoints(2), YinPoints(4), _
XinPoints((Xmax - Xmin) / 20 * x + Xmin), YinPoints(y))
这只是一个在笛卡尔坐标系中绘制的简单图表。如果您想绘制实时数据或从数据库查询的数据的图表,您将不得不解决更困难的任务。
参考文献
- 无需 ActiveX / OCX 控件的快速图表,作者:Felix Haas
- MSDN 将 Microsoft Visual Basic 6.0 升级到 Microsoft Visual Basic .NET
- MSDN Visual Basic 6.0 用户的坐标系统
- MSDN 转换
历史
- 08.2007 - 在 Visual Basic 6.0 中构建
- 04.2011 - 升级到 Visual Basic 2010