Dynamic Charting with .NET 2.0
Third-party tools, exports to Excel, SVG, VML, and Java applets; these are the tools for the Domino developer who wants to create charts from their Lotus Notes\Domino applications. Wouldn’t be great if you could create chart images dynamically with native code? Probably never going to happen, but you can use .NET to generate the images and have any application refer to the images in a browser.
I recently wrote about dynamic report views with .NET 2.0, which led to the need to read up on dynamic image generation for charting purposes. .NET 2.0 provides the developer with the GDI+ package that allows us to create chart graphics at runtime. For this article I’ll cover a basic bar chart, and a pie chart, but the capabilities of the GDI+ package go far beyond this.
These examples return the actual stream for the image, so I created an ASPX page with two functions, one to create the bar chart, and one to create the pie chart. I then used a querystring parameter to select which function to run. In another page, I reference the chart generator in an href like this: “chart.aspx?type=bar”. The data for the charts is loaded into two arrays, but the data could also be passed in querystring parameters.
First is the bar chart. These are always the easiest since they are basically colored rectangles with their heights equal to the value to diaply divided by the maximum value times maximum bar height. Let’s start with some data in two arrays (like all my examples, this one uses VB.NET).
Dim aMonths As ArrayList = New ArrayList()
Dim aProfits As ArrayList = New ArrayList()
aMonths.Add("Jan")
aMonths.Add("Feb")
aMonths.Add("Mar")
aMonths.Add("Apr")
aMonths.Add("May")
aMonths.Add("Jun")
aMonths.Add("Jul")
aMonths.Add("Aug")
aMonths.Add("Sep")
aMonths.Add("Oct")
aMonths.Add("Nov")
aMonths.Add("Dec")
aProfits.Add(240500)
aProfits.Add(220950)
aProfits.Add(283500)
aProfits.Add(340000)
aProfits.Add(325750)
aProfits.Add(123456)
aProfits.Add(235621)
aProfits.Add(345235)
aProfits.Add(290451)
aProfits.Add(152345)
aProfits.Add(653456)
aProfits.Add(785620)
Now let’s build our chart. We place this code into a function that takes three paramters, a chart title, and our two value arrays. First we set our variables that will define the column width, spacing, total chart height, the space for the legend, space for title, total width, and total height.
Const iColWidth As Integer = 30
Const iColSpace As Integer = 10
Const iMaxHeight As Integer = 400
Const iHeightSpace As Integer = 25
Const iXLegendSpace As Integer = 30
Const iTitleSpace As Integer = 50
Dim iMaxWidth As Integer = (iColWidth + iColSpace) * aX.Count + iColSpace
Dim iMaxColHeight As Integer = 0
Dim iTotalHeight As Integer = iMaxHeight + iXLegendSpace + iTitleSpace
Next we create our graphics object and create our image and title background colors. Then we establish the highest value in our array for scaling purposes.
Dim objBitmap As Bitmap = New Bitmap(iMaxWidth, iTotalHeight)
Dim objGraphics As Graphics = Graphics.FromImage(objBitmap)
objGraphics.FillRectangle(New SolidBrush(Color.Bisque), 0, 0, iMaxWidth, iTotalHeight)
objGraphics.FillRectangle(New SolidBrush(Color.Ivory), 0, 0, iMaxWidth, iMaxHeight)
Dim iValue As Integer
For Each iValue In aY
If iValue > iMaxColHeight Then iMaxColHeight = iValue
Next
Then let’s create our brushes for the various fonts that we will use
Dim iBarX As Integer = iColSpace, iCurrentHeight As Integer
Dim objBrush As SolidBrush = New SolidBrush(Color.FromArgb(70, 20, 20))
Dim objLegBrush As SolidBrush = New SolidBrush(Color.FromArgb(255, 0, 0))
Dim fontLegend As Font = New Font("Arial", 11)
Dim fontValues As Font = New Font("Arial", 7)
Dim fontTitle As Font = New Font("Arial", 24)
Then we loop through all of our values and draw the individual bars. At the top of each bar is the value that the bar represents, and at the bottom of the bar is the legend that shows the month.
For iLoop = 0 To aX.Count - 1
iCurrentHeight = ((Convert.ToDouble(aY(iLoop)) / Convert.ToDouble(iMaxColHeight)) * Convert.ToDouble(iMaxHeight - iHeightSpace))
objGraphics.FillRectangle(objBrush, iBarX, iMaxHeight - iCurrentHeight, iColWidth, iCurrentHeight)
objGraphics.DrawString(aX(iLoop), fontLegend, objBrush, iBarX, iMaxHeight)
objGraphics.DrawString(Format(aY(iLoop), "#,###"), fontValues, objLegBrush, iBarX, iMaxHeight - iCurrentHeight - 15)
iBarX += (iColSpace + iColWidth)
Next
objGraphics.DrawString(strTitle, fontTitle, objBrush, (iMaxWidth / 2) - strTitle.Length * 6, iMaxHeight + iXLegendSpace)
The very last piece is to save the image to the response stream in the JPEG format, and clean up our objects.
objBitmap.Save(Response.OutputStream, ImageFormat.Jpeg)
objGraphics.Dispose()
objBitmap.Dispose()
Next we’ll do the Pie chart. Since there are alot of similiarities between the two code chunks, I’ll just mention a few of the basic differences. First, drawing the individual wedges of the chart require X,Y coordinates that represent the upper left of the area that the pie chart will occupy, the width and height of the chart area, the start angle, and finally the degrees of sweep.
So to create the basic chart, we are going to loop through all of the values in the array, starting at “0″ degrees (which is at 3 o’clock on the circle), and set the degrees of sweep of each pie slice equal to the individual slices value divided by the total of all values times 360. Each time we loop, we add the sweep degrees to a variable that is our start angle. Each loop also randomly picks a color.
Lastly, is the setting that will make your charts look alot better, which is the anti-aliasing setting. Without this setting, your images will be very choppy, but feel free to try the code without that setting. Anyway, here is the code to create a basic pie chart with our monthly data.
Sub drawPieChart(ByVal aX As ArrayList, ByVal aY As ArrayList)
Const intBmpWidth As Integer = 300
Const intBmpHeight As Integer = 300
Const intChartWidth As Integer = 200
Const intChartHeight As Integer = 200
Dim objBitmap As Bitmap = New Bitmap(intBmpWidth, intBmpHeight)
Dim objGraphics As Graphics = Graphics.FromImage(objBitmap)
Dim objBrush As SolidBrush = New SolidBrush(Color.Aqua)
Dim iLoop As Integer = 0
Dim iLoop2 As Integer = 0
Dim intChartX As Integer = 50
Dim intChartY As Integer = 20
Dim intStartAngle As Integer = 0
Dim intSweepAngle As Integer = 0
Dim rand As Random = New Random()
Dim a As Integer = 0
Dim intTotalValue As Integer
For a = 0 To (aY.Count - 1)
intTotalValue += aY(a)
Next
'Create a black background for the border
objGraphics.FillRectangle(New SolidBrush(Color.Ivory), 0, 0, intBmpWidth, intBmpHeight)
objGraphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
For a = 0 To (aY.Count - 1)
intSweepAngle = CType((aY(a) / intTotalValue) * 360, Integer)
objBrush.Color = Color.FromArgb(rand.Next(255), rand.Next(255), rand.Next(255))
objGraphics.FillPie(objBrush, intChartX, intChartY, intChartWidth, intChartHeight, iLoop, intSweepAngle)
iLoop += intSweepAngle
Next
objBitmap.Save(Response.OutputStream, ImageFormat.Jpeg)
objGraphics.Dispose()
objBitmap.Dispose()
End Sub
If you wanted to create a “floating pie slice” you would simply change the X,Y coordinates to “trick” the image into drawing it away from the rest of the pie.
When done, you have some basic charts that look like these:
May 18th, 2006 at 11:44 am
Hi Jeff,
Been following your .net stuff with interest. We are looking at moving to .net here.
For Graphing needs myself and Jerry Carter have been working on a Domino system which utilises Flash based graphs which take an XML feed.
Makes the code a little easier
Mark
May 18th, 2006 at 3:05 pm
If you can guarantee the distribution of the Flash plug-in, that is definitely a good way to go. You can then use some AJAX methods to refresh/populate the graphs.
May 28th, 2006 at 9:18 pm
You can use SVG to generate graph too. A more detail writeup here.
http://notesweb2.blogspot.com/2006/05/creating-dynamic-svg-graph-using-agent.html