在 ASP.NET 网页中进行数据图形表示





5.00/5 (4投票s)
使用 GDI+ 和 Flood-Fill 技术在网页上动态绘图
这个话题其实并不算新,但我认为重新审视它并提供实际使用场景很重要。我们经常发现像 bbc.com 和 cnn.com 这样的公共网站会为其访问者提供最新的股票市场活动和指标,例如纽约股市的 NASDAQ 指标。这些指标(实际上是数据)以图表和图形表示,并且这些数据是实时更新的,分钟级。
那么,这是如何发生的呢?为了回答这个问题,我们必须区分在网页上表示数据的两种技术:服务器端和客户端表示。嗯,大多数实时活动依赖于服务器端技术,反之亦然。对于客户端表示,我们可以使用 JQuery 或 JavaScript 等创建仪表板和图表,但这超出了这里的讨论范围。我将向您展示如何使用服务器端技术来实现。 (有关更多信息,请查找书籍《Pro ASP.NET 4 in CSharp 2010 4th Edition- Chapter 28: Graphics, GDI+, and Charting》)
简单来说,我将通过“洪水填充”技术动态更改 ASP.NET 页面中的图像颜色,并将 ASPX 网页用作 ASP Image 控件的输入。
为了演示,我在本文附加了一个带有源代码的示例 Web 应用程序和一个用于示例项目的 SQL Server 2008 数据库备份。以下几点代表了示例 Web 应用程序的示例用法,那么,让我们开始吧。下图代表了正在工作的示例应用程序。
背景
我正在开发一个提供丰富的动态地图和图表数据图形表示的网站。在一大堆需求中,有一项要求是地图上的每个区域(地区、省份或县/区)都应具有三种绿色颜色中的一种(浅绿色、绿色和深绿色),如图所示。
好吧,这个应用程序太庞大,无法说明它是如何工作的以及它做了什么;因此,我在这里的“技巧与窍门”中要讲的重点是如何根据 SQL Server 数据库中的数据动态更改地图上某区域(例如省份)的颜色,当用户更改屏幕上的因素(例如年份下拉列表框、指标类别或指标本身列表项)时,一个网页会基于洪水填充技术进行绘制。
数据库设计
为了做到这一点,我创建了几个数据库表来存储位置、子位置、指标值,然后,我为每个区域(例如省份)地图中间的几何图形设计了点对点的(x, y)坐标,如下图所示的 ERD 图。
然后,我在数据库中创建了必要的表并填充了示例数据。有关更多信息,请还原本文提供的 SampleDB 数据库备份。如下图所示。
为了说清楚,我使用了一个真实的 GIS 工具来生成地图上区域(省份)之间的边界,在每个区域中生成 SHP 文件,然后将这些文件导出为 BMP 和 JPEG; voilà,我们得到了 400 像素方形 BMP 图像的示例,我选择了每个区域内部的随机一对 (X, Y) 作为用指定颜色启动洪水填充方法的点。
数据库处理
需要一种良好的数据处理技术来与数据源(数据访问层)进行通信,在大多数情况下,此层通常通过 DAL 生成器实用程序生成。下面 C# 代码显示了我用来与 SQL Server 通信以检索 DataTable 的示例方法。
public DataTable SelectGeomsFactors()
{
DataTable dtReturned = new DataTable();
try
{
using (SqlConnection dbConnection = new SqlConnection(_sConnection))
{
dbConnection.Open();
using (SqlCommand cmdToExecute = new SqlCommand(_sSelectGeomsFactorsCmdText, dbConnection))
{
cmdToExecute.Parameters.Add(new SqlParameter("@iMainFactor", SqlDbType.Int, 4,
ParameterDirection.Input, false, 10, 0, "", DataRowVersion.Proposed, _iMainFactor));
cmdToExecute.Parameters.Add(new SqlParameter("@iSubFactor", SqlDbType.Int, 4,
ParameterDirection.Input, false, 10, 0, "", DataRowVersion.Proposed, _iSubFactor));
cmdToExecute.Parameters.Add(new SqlParameter("@iMainLocation", SqlDbType.Int, 4,
ParameterDirection.Input, false, 10, 0, "", DataRowVersion.Proposed, _iMainLocation));
cmdToExecute.Parameters.Add(new SqlParameter("@iReportYear", SqlDbType.Int, 4,
ParameterDirection.Input, false, 10, 0, "", DataRowVersion.Proposed, _iReportYear));
cmdToExecute.Parameters.Add(new SqlParameter("@iDataLevelID", SqlDbType.Int,
4, ParameterDirection.Input, false, 10, 0, "", DataRowVersion.Proposed, _iDataLevelId));
cmdToExecute.Parameters.Add(new SqlParameter("@iMapSize", SqlDbType.Int, 4,
ParameterDirection.Input, false, 10, 0, "", DataRowVersion.Proposed, _iMapSize));
cmdToExecute.Parameters.Add(new SqlParameter("@iErrorCode", SqlDbType.Int, 4,
ParameterDirection.Output, true, 10, 0, "", DataRowVersion.Proposed, _errorCode));
using (SqlDataAdapter dataAdapter = new SqlDataAdapter(cmdToExecute))
{
dataAdapter.Fill(dtReturned);
_errorCode = (SqlInt32)dataAdapter.SelectCommand.Parameters["@iErrorCode"].Value;
}
if (_errorCode != 0)
{
// Throw error.
throw new Exception("DataHandler::SelectGeomsFactors:The Select query from Database returned the following error: " + _errorCode);
}
}
if (dbConnection.State != ConnectionState.Closed) dbConnection.Close();
}
return dtReturned;
}
catch (Exception ex)
{
throw new Exception("DataHandler::SelectGeomsFactors::Error occured.", ex);
}
}
用户界面层
现在,是时候将数据库中产生的数据以良好且动态的表示形式显示出来,并通过洪水填充技术绘制到 aspx 网页上了。
以下是用于用不同颜色填充地图区域的 GraphicsHandler 类的 C# 代码。如您所见,Stack 对象(表示后进先出 LIFO 的集合)用于从起始点(即 BMP 图像地图上的像素)填充颜色,设置其颜色,然后弹出以洪水方式填充周围的颜色。
public class GraphicsHandler { public static Bitmap FloodFill(Bitmap bmp, Color clr, Point pnt) { Bitmap bitmap = new Bitmap(bmp); Stack stk = new Stack(); stk.Push(pnt); Color originalColor = bitmap.GetPixel(pnt.X, pnt.Y); while (stk.Count > 0) { Point top = (Point)stk.Pop(); int x = top.X; int y = top.Y; bitmap.SetPixel(top.X, top.Y, clr); if (top.X - 1 > 0 && bitmap.GetPixel(top.X - 1, top.Y) == originalColor) stk.Push(new Point(top.X - 1, top.Y)); if (top.X + 1 < bitmap.Width && bitmap.GetPixel(top.X + 1, top.Y) == originalColor) stk.Push(new Point(top.X + 1, top.Y)); if (top.Y - 1 > 0 && bitmap.GetPixel(top.X, top.Y - 1) == originalColor) stk.Push(new Point(top.X, top.Y - 1)); if (top.Y + 1 < bitmap.Height && bitmap.GetPixel(top.X, top.Y + 1) == originalColor) stk.Push(new Point(top.X, top.Y + 1)); } return bitmap; } }
稍后,我们将通过 System.Drawing
和 System.Drawing.Drawing2D
命名空间中的 GDI+ 类使用以下代码示例来绘制地图。
private void DrawCurrentMap(int iMainFactor,
int iSubFactor,
int iMainLocation,
int iReportYear,
int iMapSize,
double dHigh,
double dMed,
double dMin)
{
try
{
//We load an existing bitmap
string fileName = @"MapImgs\Maps400\" + iMainLocation + ".bmp";
Bitmap oCanvas = (Bitmap)Image.FromFile(Server.MapPath(fileName));
using (Graphics gfx = Graphics.FromImage(oCanvas))
{
//make sure you don't have smoothing problems
gfx.SmoothingMode = SmoothingMode.AntiAlias;
_iCurrDataLevelId = 2;
DataHandler dHandler = new DataHandler();
dHandler.MainFactor = iMainFactor;
dHandler.SubFactor = iSubFactor;
dHandler.MainLocation = iMainLocation;
dHandler.ReportYear = iReportYear;
dHandler.MapSize = iMapSize;
dHandler.DataLevelId = _iCurrDataLevelId;
DataTable dtReturned = dHandler.SelectGeomsFactors();
int iNumRows = dtReturned.Rows.Count;
if (iNumRows > 0)
{
for (int iCounter = 0; iCounter < iNumRows; iCounter++)
{
double dCurrParamVal = Math.Round(Convert.ToDouble(dtReturned.Rows[iCounter]["FactorValue"]), 3);
Color currColor = Color.FromArgb(235, 235, 235);
if (dCurrParamVal == 0 && dHigh == 0)
{
currColor = Color.FromArgb(235, 235, 235);
}
else if (dCurrParamVal >= dMin && dCurrParamVal < dMed)
{
currColor = Color.FromArgb(189, 247, 165);
}
else if (dCurrParamVal >= dMed && dCurrParamVal < dHigh)
{
currColor = Color.FromArgb(165, 189, 165);
}
else if (dCurrParamVal >= dHigh)
{
currColor = Color.FromArgb(130, 140, 140);
}
int iGeomX = (Int32)dtReturned.Rows[iCounter]["GeomX"];
int iGeomY = (Int32)dtReturned.Rows[iCounter]["GeomY"];
oCanvas = GraphicsHandler.FloodFill(oCanvas, currColor, new Point(iGeomX, iGeomY));
}
}
}
// Now, we only need to send it to the client
Response.ContentType = "image/jpeg";
oCanvas.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
oCanvas.Dispose();
}
catch (Exception)
{
//We load an existing bitmap
string fileName = @"MapImgs\Maps400\na.bmp";
Bitmap oCanvas = (Bitmap)Image.FromFile(Server.MapPath(fileName));
Response.ContentType = "image/jpeg";
oCanvas.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
oCanvas.Dispose();
}
Response.End();
}
关注点
值得一提的是,您可以更改 DataGrid 单元格的颜色,方法是更改它们的样式,使其颜色与地图上的颜色相同,结果是一个令人惊叹的应用程序,如下面的图所示。
结论
ASP.NET 非常灵活,我们可以用它应用大量的编码技术,包括即时在网页上绘图。