This topic is not actually new but I thought it is important to revisit it and provide real-life usage of it. We used to find that public sites such as bbc.com and cnn.com provide their visitors with the recent Stocks Markets activities and indicators such as these of NASDAQ indicators in NY stock markets. These indicators (actually data) are represented in charts and graphics and these data are live minute by minute.
So, how this happens? To answer this question, we have to discriminate between two techniques of data representation on a web page; the server-side and the client-side representation of data. Well, most of the live activities rely on server-side techniques and vice versa. For client-side representation, we may create dashboards and charts using JQuery or JavaScript for example but this is out of context here. I shall show you how to do it using server-side technique. (For more information, please locate the book “Pro ASP.NET 4 in CSharp 2010 4th Edition- Chapter 28: Graphics, GDI+, and Charting”)
Simply, I shall dynamically change images’ colours in ASP .NET page through the Flood-fill technique and I shall use ASPX web page as an input for an ASP image control.
For the sake of demonstration, I’ve attached with this article a sample web application with source code and a backup of a SQL Server 2008 database for the sample project. The following points represent a sample usage of the sample web application so, let’s start. The following figure represents the sample application at work.
Background
I was developing a web site that provides rich graphical representation of data on maps and charts dynamically. Among a huge list of requirements, there was a require that each area on a map (Region, Governorate or District/County) should have only one colour degree from three degrees of the Green Colour (light green, green and dark green) as shown in the figure.
Well, the application is huge to mention how it works and what it does; so, the point I’m aiming here in this Tips and Tricks is how to dynamically change the colour of an area such as a governorate on the map based on data located in SQL Server Database and when the user changes the factors on screen (such as the Year drop down list box, the Indicator’s Category or the Indicator itself list item), then a web page draw based on the flood-fill technique.
Database Design
To do that, I’ve created few database tables to hold the locations, the sub-locations, the indicators’ values and then, I’ve invented the Geometrics of each area in pairs (x, y) of point-based location in the middle of each area (such as governorate) on the map; as shown in the below ERD figures.
Then, I’ve created the necessary tables in database and filled them with sample data. For more information, please restore the backup of SampleDB database provided with this article. As shown in the following figure.
To be clear, I used a real GIS tool to generate the borders between the areas on the map (governorates) in each region in SHP files then exported these files into BMPs and JPEGs; voila we got samples of 400 pixel square BMP images such as the following image and I picked a random pair (X, Y) inside each area to be point that initiates the flood-fill method with the dedicated colour.
Database Handling
A good data handling technique is needed to communicate with the data source (Data Access Layer) and in most cases this layer is usually generated via DAL generator utility. The code below in C# shows a sample method I used to communicate with SQL Server to retrieve Data Table.
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 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);
}
}
User Interface Layer
Now, it is time to show the data yield from database into good and dynamic representation and draw on the aspx web page through the flood-fill technique.
Here is the code of the GraphicsHandler class used for flood-fill the maps’ areas with different colours in C#. As you can see a Stack object (collection that represents a last-in-first-out -LIFO) is used to fill colour from a starting point (i.e. pixel on the BMP image map) and set its colour then popup to fill the colour around in flood manner.
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;
}
}
Later on we use GDI+ classes through the System.Drawing
and System.Drawing.Drawing2D
namespaces to draw the map with the following code sample
private void DrawCurrentMap(int iMainFactor,
int iSubFactor,
int iMainLocation,
int iReportYear,
int iMapSize,
double dHigh,
double dMed,
double dMin)
{
try
{
string fileName = @"MapImgs\Maps400\" + iMainLocation + ".bmp";
Bitmap oCanvas = (Bitmap)Image.FromFile(Server.MapPath(fileName));
using (Graphics gfx = Graphics.FromImage(oCanvas))
{
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));
}
}
}
Response.ContentType = "image/jpeg";
oCanvas.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
oCanvas.Dispose();
}
catch (Exception)
{
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();
}
Points of Interest
It worth mentioning that you can change the colours of the DataGrid cells through changing their styles with the same colours of those on the map and the result is amazing application as shown in the following figures
Conclusion
ASP .NET is flexible so that we can apply lots of coding techniques with it including drawing on a web page on the fly.