自动缩放图形控件






4.75/5 (12投票s)
了解如何创建可以自动缩放的图形!
引言
我编写了很多从外部源(如实验室设备和其他杂项)获取数据的程序。我一直想要一个简单的图表控件来显示我读取的数据,这样我在代码运行时就能知道是否出了什么问题。这其中的一个难点是如何让数据适应图表窗口。所以,我找到了使用Matrix
类来实现自动缩放的方法!
本文更侧重于介绍Matrix
变换的使用方法,而不是一个完整的控件。如果您想将其用作应用程序中的控件,可以添加所需的功能,或者访问我的网站下载更完整的版本。
背景
这篇文章 (c-sharpcorner.com) 是一个关于使用 GDI+ 的 Matrix 变换的很好的入门介绍。
使用矩阵变换图形对象
所以,基本思路是我们可以使用一个Matrix
来改变我们图表窗口的原点和缩放比例。要实际执行变换,您可以使用类似这样的代码:
// in my Paint even handler...
// transform the graphics context to get
// the origin near the bottom left of the window
Matrix trans = new Matrix(1, 0, 0, -1, 30, this.Height - 30);
trans.Scale(scale_x, scale_y);
e.Graphics.Transform = trans;
在这段代码中,我实际上使用了Matrix
对象做了三件事。
首先,我想让图表的原点位于窗口左下角向右 30 像素、向上 30 像素的位置。这正是Matrix
构造函数最后两个参数的作用。第一个参数,30
,是 X 轴的平移因子。所以这个参数的意思是,将图表的原点向右移动 30 像素。this.Height - 30
类似,它的意思是将原点向下移动 30 像素(比图表窗口高度少 30 像素)。
我想要做的第二件事是改变图表如何处理坐标。GDI 和其他所有编程语言的坐标系都从右上角开始,Y 轴坐标增加时屏幕向下移动。这与人类对图表的期望相反。我们希望 Y 值增加时图表向上移动。所以,这就是Matrix
构造函数中前四个数字的作用。实际上,是序列中的第四个数字做了这个。如果您查看上面提到的 c-sharpcorner.com 文章,您可以弄清楚为什么在矩阵中需要那个-1
。本质上,这意味着 Y 轴坐标与计算机通常认为的相反。
我用这个矩阵做的最后一件事是缩放图表。我使用trans.Scale(scale_x, scale_y);
这一行来实现。幸运的是,这一行比Matrix
构造函数要简单一些。您所需要做的就是传入一些缩放因子,然后您就可以开始了。
最后,您只需要将当前的Graphics
对象的Transform
成员设置为您刚刚创建的Matrix
对象。我将在下一节解释缩放因子。现在,您的坐标系将设置得就像您在小学学习如何绘制数字时一样...
如果您对数学感兴趣,可以查看文章的结尾,了解这个Matrix
“胡闹”到底是怎么回事。
确定自动缩放因子
那么,我们如何获得上面一节中提到的缩放因子呢?其实很简单。在这个实现中,我使用了一个ArrayList
来存储我想在图表上显示的数据点 (PointF
)。所以我创建了以下函数来查找缩放因子:
public void Autoscale()
{
foreach (PointF p in data)
{
if (p.X > xmax)
xmax = p.X;
if (p.Y > ymax)
ymax = p.Y;
}
scale_x = (float)((this.Width - 30f) / xmax);
scale_y = (float)((this.Height - 30f) / ymax);
}
这个函数所做的就是遍历数据中的每个值,找出每个维度中的最大值。然后,为了创建缩放因子,它将图表窗口的宽度(以像素为单位)除以这个值。就是这样!(请注意,我从Width
和Height
中减去了 30,因为我将原点移动了 30 像素。)
另一种方法
如果您真的不喜欢创建Matrix
对象,还有另一种可能更简单的方法。如果您查看我的代码,您会看到我进行了一些平移和旋转来绘制我的“Y 轴”字符串。代码看起来是这样的:
e.Graphics.TranslateTransform(15, 130);
e.Graphics.RotateTransform(-90);
e.Graphics.DrawString(y_text,
new Font(FontFamily.GenericMonospace, 8f),
new SolidBrush(Color.LawnGreen), new PointF(0f, 0f));
这段代码将TranslateTransform
设置为将原点移动到 (15, 130),然后将坐标系旋转 -90 度。这样,我就可以使用Graphics.DrawString
在 Y 轴线旁边绘制垂直字符串。我通常只在只需要对一两个需要修改坐标系的调用使用此方法时使用它。就像本例一样。我认为当进行多个绘图调用时,使用实际的Matrix
对象会更好。我不太确定为什么。我猜我就是更喜欢它。
这一切 Matrix 到底是怎么回事?
好的!我将尝试解释Matrix
到底是怎么回事。如果您了解一点矩阵代数,这应该不难。如果您不关心,了解这个并不是必需的,但有些人可能会好奇。
那么,如果我们写一行像下面这样的代码,实际上创建了什么?
Matrix trans = new Matrix(1, 0, 0, -1, 0, -10);
我来告诉您。您实际上用一个构造函数得到了两个矩阵!这很棒!实际创建的是:
当然,x
和y
是输入点,x'
和y'
是输出。最左边的矩阵类似于单位矩阵,除了 -1。所以,如果您将它乘以输入点,您可以看到您只是将y
乘以 -1。如果您将输入视为 GDI 处理的点,您会发现,当我们增加y
值时,我们不再向下移动屏幕,而是开始向上移动,或者反之亦然。这很棒!方程中的下一个矩阵是平移矩阵 ([0 -10'])。如果您考虑执行此操作,您会发现它会将每个y
值向下移动 10 个单位。所以,如果您在代码中使用这个确切的Matrix
,您将把您的图表向下移动 10 像素,并反转 Y 轴上点的移动方向。这正是我们想要的这个图表的效果!
为了证明这一点,我写了一个简短的 MATLAB 脚本,并绘制了结果。
关注点
我发现编写代码时的一个有趣且令人沮丧的事情是,使用缩放变换时,Pen
的像素宽度会发生变化。如果您阅读我的代码,您会在 paint handler 中看到我将每个笔的宽度设置为1 / scale_x
。否则,如果您的缩放因子很大,您的图表线会变得很粗。这并不是一个完美的解决方案,但效果还可以。
MSDN 文档中关于Matrix
类的文档不多,所以很难确切弄清楚您需要什么样的矩阵来实现您想要的效果。这似乎是一个人们普遍想要做的变换,所以我认为我应该分享一下。现在,来一些注释怎么样?