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

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2015年6月24日

CPOL

4分钟阅读

viewsIcon

18080

downloadIcon

456

使用 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.DrawingSystem.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 非常灵活,我们可以用它应用大量的编码技术,包括即时在网页上绘图。

© . All rights reserved.