Earlier I read a couple of articles on charting, but not being familiar with GDI, or GDI+ it was easy to get the code to chart, but hard to understand the code well enough to see how to create a totallly different type of graph. The goal here is simple. A chart class for line charts that creates one line graph on the chart for each record in the datatable that you input to the class's single method, render, but that method must output the stream into an asp:image server control in a way that works in design time as well as run time so you can easily position the chart and see it as you work on the rest of the page without using other pages or controls to make this happen. I want to describe each non-trivial line of code. This is vb .NET, but easily converted to c# as most of the code is object manipulation, not language manipulation. The motivation for this kind of a chart is for a graph that compares many things on one graph to help people make decisions about things like if one compared average utility usage for all customers with one's own usage it might motivate one to reduce usage of the utility somewhat. First let's put some code in a page_load event to build some test data for us that we can use with the graph. Public Class usage1 : Inherits System.Web.UI.Page ' this code is from usage.aspx.vb
Protected WithEvents Image1 As System.Web.UI.WebControls.Image Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load If Request.QueryString("image") = 1 Then Dim dt As New DataTable("usage") ' note that we must have a table object to add the columns to Dim jan As New DataColumn("Jan", GetType(Double)) ' here the column gets defined, with jan as the column object name and Jan as the table field or column name dt.Columns.Add(jan) ' here the column object is added to the column collection of the table dt Dim feb As New DataColumn("Feb", GetType(Double)) dt.Columns.Add(feb) Dim mar As New DataColumn("Mar", GetType(Double)) dt.Columns.Add(mar) Dim apr As New DataColumn("Apr", GetType(Double)) dt.Columns.Add(apr) Dim may As New DataColumn("May", GetType(Double)) dt.Columns.Add(may) Dim jun As New DataColumn("Jun", GetType(Double)) dt.Columns.Add(jun) Dim jul As New DataColumn("Jul", GetType(Double)) dt.Columns.Add(jul) Dim aug As New DataColumn("Aug", GetType(Double)) dt.Columns.Add(aug) Dim sep As New DataColumn("Sep", GetType(Double)) dt.Columns.Add(sep) Dim oct As New DataColumn("Oct", GetType(Double)) dt.Columns.Add(oct) Dim nov As New DataColumn("Nov", GetType(Double)) dt.Columns.Add(nov) Dim dec As New DataColumn("Dec", GetType(Double)) dt.Columns.Add(dec) Dim dr As DataRow = dt.NewRow ' with all of our columns added, lets add 2 rows to our defined table structure dr("jan") = 44 ' a row can except a value for each column if the value is the proper type dr("feb") = 55 dr("mar") = 66 dr("apr") = 77 dr("may") = 65 dr("jun") = 64 dr("jul") = 63 dr("aug") = 67 dr("sep") = 68 dr("oct") = 70 dr("nov") = 77 dr("dec") = 67 dt.Rows.Add(dr) Dim dr1 As DataRow = dt.NewRow dr1("jan") = 33 dr1("feb") = 44 dr1("mar") = 55 dr1("apr") = 77 dr1("may") = 87 dr1("jun") = 92 dr1("jul") = 44 dr1("aug") = 55 dr1("sep") = 44 dr1("oct") = 33 dr1("nov") = 22 dr1("dec") = 44 dt.Rows.Add(dr1) Dim ourChart As New LineChart() ' here we instantiate an object from our custom class ourChart.Render("Usage Graph", "(versus average)", 1000, 600, dt, Response.OutputStream) ' render is the only method of our class that we will need to call, and this method returns a stream of Gif type End If End Sub End Class
Note that this page_load event code only runs when request.querystring("image")=1. Why is this? The first time this page loads we simple call the name of the page, usage.aspx, right? Well there are no parameters at the end of that url, correct? So that is why upon loading of this aspx page the code does not run. So what does run the code? An image tag url. I put this code next from the page usage.aspx.
Imports System.IO ' this is only needed so that we can conveniently use a stream as an input parameter for the render method Imports System.Drawing.Text ' this is only needed so that we can conveniently use the TextRenderingHint enumeration, note that system.drawing is already imported by default by the project Imports System.Drawing.Drawing2D ' this is only needed so that we can conveniently use the SmoothingMode enumeration Imports System.Drawing.Imaging ' this is only needed so that we can conveniently use the ImageFormat.Gif class and shared property Public Class LineChart ' the class only has one main member, the render method which returns nothing, and receives 6 input parameters Public Sub Render(ByVal title As String, ByVal subTitle As String, ByVal width As Integer, ByVal height As Integer, ByVal dt As DataTable, ByVal target As Stream) End Sub ' title, and subtitle should be clear, width and height is the size you want the output stream to be, dt holds the records for the lines ' the target is where you want the formatted stream to go. In our case it is going to be Response.OutputStream Public Shared Function GetColor(ByVal row As Integer) As Color ' this simple method just returns a color object for each row to color each line chart differently Dim currentColor As Color ' add more colors if you want more line charts Select Case row Case 0 currentColor = Color.Blue Case 1 currentColor = Color.Red Case 2 currentColor = Color.Green Case 3 currentColor = Color.Purple Case 4 currentColor = Color.Orange Case Else currentColor = Color.Navy End Select Return currentColor End Function End Class
Const CANVAS As Integer = 1000 ' I could draw directly on the 1000 by 600 size graphic, but this will show how to draw on 1000x1000 and then scale to 1000x600 Const CHART_TOP As Integer = 90 ' Start the charting area 90 pixels below the canvas top to leave room for the title and subtitle Const CHART_HEIGHT As Integer = 800 ' The chart height is most of the 1000 height canvas Const CHART_LEFT As Integer = 0 ' Let's start with 0 because there are some adjustments we will have to make Const CHART_WIDTH As Integer = 1000 ' Go the whole width, but with adjustments Dim base As Double = 1 + highPoint / 10 ' This is an adjustment factor that we will use in a few places Dim myBitMap As Bitmap = New Bitmap(width, height) ' A bitmap is like a stream, a memory representation of a picture format that can convert to many formats Dim graph As Graphics = Graphics.FromImage(myBitMap) ' A graphics object has many methods for drawing on the abstract bitmap object Dim highPoint As Single = 0 ' determine highpoint of all your data so we can relate that amount to the CHART_HEIGHT Dim row As DataRow Dim col As DataColumn For Each row In dt.Rows ' each row has a set of columns to consider for highpoint For Each col In dt.Columns highPoint=iif(highPoint