65.9K
CodeProject 正在变化。 阅读更多。
Home

一个简单的直方图显示控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (13投票s)

2005 年 10 月 31 日

CPOL

6分钟阅读

viewsIcon

223594

downloadIcon

15657

一个用于显示数据的直方图控件。

引言

为什么有人会想在直方图控件(或条形图控件)中显示数据呢?嗯,想象一下,您有一系列值想要显示给用户。您该如何做到呢?一种方法是将所有这些数据加载到列表框控件中,然后这样显示给用户。然而,这样做的话,用户将无法立即看到最大值或最小值在哪个索引处。用户可能被迫查看所有这些值才能找到他们想要的东西。

解决上述问题的一种方法是使用对用户有意义的控件显示所有数据,例如条形图(或直方图)。条形图将以图形方式显示所有数据,允许用户查看图像并确定最大值(或最小值)。

此控件可能很有用的一个实际情况是,您想向用户展示图像的颜色直方图。演示就做了这一点,下面附有屏幕截图。

直方图控件

我们来详细看看,好吗?直方图控件实现为一个用户控件,允许用户将其拖放到窗体上。控件的Paint事件被修改,以便在屏幕上绘制直方图。

显示数据就像调用DrawHistogram方法一样简单,该方法接受一个数字数组。

public void DrawHistogram(long[] Values)
{
    myValues = new long[Values.Length];
    Values.CopyTo(myValues,0);
    
    myMaxValue = getMaxim(myValues);
    myIsDrawing = true;

    ComputeXYUnitValues();
    this.Refresh();
}

这段代码是做什么的?嗯,它将数组复制到一个内部数组(供以后使用)。然后它确定该数组中的最大值。它这样做是为了计算一个缩放因子,我稍后将介绍。最后,它将计算垂直轴和水平轴的缩放单位。

代码行this.Refresh();用于指示用户控件强制重绘自身并显示数据。

缩放数据

因为我们需要能够在一个 100 像素的高度区域显示例如 10000 的值,所以我们需要缩放输入数据。这在ComputeXYUnitValues函数中完成。缩放同时在 X 轴和 Y 轴上进行。

  • 在 X 轴上:我们希望每个具有相同宽度的值都有条形。为了确定每个条形的最佳宽度,我们将控件的宽度除以对象的数量。结果数字就是 XUnit,我们使用这个单位在控件上绘制条形。
  • 在 Y 轴上:正如我之前所说,我们必须缩放对象。为了确定缩放的因子,我们将控件的高度除以必须显示的最大数字。结果数字 YUnit 将乘以每个必须显示的值。通过这样做,我们确保最长的条形将适合绘图区域。(如果您还记得一些数学知识,这可以看作是归一化值。)

下面提供了计算 XUnit 和 YUnit 值的代码。

private void ComputeXYUnitValues()
{
  myYUnit = (float) (this.Height - (2 * myOffset)) / myMaxValue;
  myXUnit = (float) (this.Width - (2 * myOffset)) / (myValues.Length-1);
}

当发生大小调整事件时,也将调用此函数。我们这样做是因为,当我们调整控件大小时,这些值会发生变化。

在控件上绘制数据

在知道用于缩放条形的这些值后,就可以在屏幕上实际绘制它们了。如果您对 GDI+ 绘图不太了解,我推荐 CodeProject 上这篇示例书的章节:Professional C# - Graphics with GDI+

让我们开始吧。绘图方法如下。我将指导您完成代码。

if (myIsDrawing)
{
 Graphics g = e.Graphics;
 Pen myPen = new Pen(new SolidBrush(myColor),myXUnit);
 //The width of the pen is given by the XUnit for the control.
 for (int i=0;i<myValues.Length;i++)
 {
   //We draw each line
   g.DrawLine(myPen,new PointF(myOffset + (i*myXUnit),
      this.Height - myOffset),new PointF(myOffset + (i*myXUnit),
      this.Height - myOffset - myValues[i] * myYUnit));
   //We plot the coresponding index for the maximum value.
   if (myValues[i]==myMaxValue)
   {
     SizeF mySize = g.MeasureString(i.ToString(),myFont);
     g.DrawString(i.ToString(),myFont,
        new SolidBrush(myColor), 
        new PointF(myOffset + (i*myXUnit) - (mySize.Width/2), 
        this.Height - myFont.Height), 
        System.Drawing.StringFormat.GenericDefault);
    }
 }
 //We draw the indexes for 0 and for 
 //the length of the array beeing plotted
 g.DrawString("0",myFont, new SolidBrush(myColor),
    new PointF(myOffset,this.Height - myFont.Height),
    System.Drawing.StringFormat.GenericDefault); 
 g.DrawString((myValues.Length-1).ToString(),myFont, 
    new SolidBrush(myColor), 
    new PointF(myOffset + (myValues.Length * myXUnit) 
      - g.MeasureString((myValues.Length-1).ToString(),myFont).Width, 
    this.Height - myFont.Height), 
    System.Drawing.StringFormat.GenericDefault);
 //We draw a rectangle surrounding the control. 
 g.DrawRectangle(new System.Drawing.Pen(new SolidBrush(Color.Black),1),0,0,
 this.Width-1,this.Height-1); 
}

首先,我们确定是否处于绘图模式。此模式由DrawHistogram方法设置,表示我们有数据可绘制。

然后我创建一个笔,用于在屏幕上绘制条形。请注意,笔的宽度是之前计算出的 XUnit。

对于输入数组中的每个值,我绘制一条线,其高度由将值本身乘以缩放单位 YUnit 来指定。执行此操作的代码有些复杂。如果您不明白,请告诉我。基本上,我正在测量条形的高度,并确定绘制线的起点。我必须从控件的底部开始绘制线。

然后,我检查该值是否为最大值。如果是,我将在此值下方绘制一个字符串,对应于该值的索引。我这样做是为了显示最大值的位置。当然,对于最小值也可以采用相同的行为。

在绘制完所有条形之后,我们绘制直方图的图例。图例由 0 和值数组的长度组成。我还绘制了一个矩形,以给人一种控件边框的印象。

正如您所见,绘制直方图的代码非常简单。

一个演示应用程序

正如我一开始所说,人们可能会使用此应用程序来显示图片颜色直方图。让我们看看如何做到这一点。此应用程序的代码可供下载。

计算颜色直方图

计算颜色直方图也很简单。计算机中的颜色表示基本上由三个元素(称为通道)组成。

  • 该颜色的红色量 (R)。
  • 该颜色的绿色量 (G)。
  • 该颜色的蓝色量 (B)。

颜色 (R=0, G=0, B=0) 是黑色,(R=255, G=255, B=255) 是白色。如果其中每个数量使用 8 位显示,则这三个元素总共将产生 16,581,375 种颜色。

要计算直方图,我们可以选择仅显示一个通道,或显示 2 个通道的平均值,或显示 3 个通道的平均值。我选择了后一种方法。通过对每个通道的值取平均,我将始终得到一个介于 0 和 255 之间的值。这个值代表该颜色的灰度表示。

因此,我们必须做的是获取图像中每个像素的颜色,对其进行平均,然后将其添加到数组的相应 bin 中,如下所示:

public long[] GetHistogram(System.Drawing.Bitmap picture)
{
 long[] myHistogram = new long[256];

 for (int i=0;i<picture.Size.Width;i++)
  for (int j=0;j<picture.Size.Height;j++)
  {
   System.Drawing.Color c  = picture.GetPixel(i,j);

   long Temp=0;
   Temp+=c.R;
   Temp+=c.G;
   Temp+=c.B;

   Temp = (int) Temp/3;
   myHistogram[Temp]++;
  }

 return myHistogram;
}

我们只需要一张图片的Bitmap表示。

显示直方图

在获得显示颜色直方图的数组后,我们必须将其显示在控件中,如下所示:

Histogram.DrawHistogram(myValues);

这就是您需要做的全部!

结论

本文旨在向您展示如何创建自己的显示数据的控件。希望您从中有所收获。现在,您拥有一个可以使用的直方图控件了!

我很乐意回答问题!

祝您编码愉快!

© . All rights reserved.