JLION.COM
SparkLine InLine Charts
06/10/2007 ASP.NET, Charting

Code for creating in-line Sparkline Charts using VB.NET 2.0/ASP.NET 2.0

A few years back I attended one of Edward Tufte's seminars. I enjoyed the seminar and while lots of good ideas were presented the one that stuck with me most firmly was the SparkLine. SparkLines are small in-line charts included along with text in a report. For example, a stock price might tell you one thing, but seeing that the price is the highest that the stock has had in 12 weeks tells you something more. Moreover, being able to see at a glance a page of these trends might enable you to quickly pick out those stocks which are most quickly rising in value.

I really liked the sparkline idea and took away from Tufte's lecture an enhanced sense of the importance of not wasting screen real-estate, of not requiring the user to click through multiple screens when a single screen can show the same data and of not engaging in unnecessary prettification of charts and reports.

Recently I started work on a new project, a dashboard for a sales and marketing department and wanted to use sparklines to show trend information in a forecast report. I looked around and found several sparkline implementations including this very nice one written in PHP. My dashboard is a DotNet 2.0 application however and I didn't want to mix languages. I also wasn't sure how I would want to present the data (line? bar? line for average? shaded bar to show range of standard deviation?) and thought that if I rolled my own then I'd be able to take it in whatever direction I needed to.

What I present here is that sparkline implementation. After creating my sparkline chart, I found this article on CodeProject by Igor Krupitsky. If I had found it prior to creating my sparkline chart, I probably would have used Igor's code.

As a sample, here is the Dow Jones Utility Average (w/e 6/8) * Table courtesty finance.yahoo.com

Symbol Name Last Trade Change Volume Last 10 Days (line) Last 10 Days (bar)
AEP AMER ELECTRIC POW CO 44.91 Jun 8 Up 0.41 (0.92%) 2,877,985
AES AES CP INC 21.62 Jun 8 Up 0.31 (1.45%) 5,361,230
CNP CENTERPOINT ENERGY 17.56 Jun 8 Up 0.13 (0.75%) 2,266,200
D DOMINION RES NEW 82.47 Jun 8 Up 0.75 (0.92%) 2,896,287
DUKDUKE ENERGY CP HL CO 18.38 Jun 8 0.00 (0.00%) 13,922,725
EDCONS EDISON INC 46.59 Jun 8 Up 0.20 (0.43%) 3,834,940
EIX EDISON INTL 54.76 Jun 8 Up 0.51 (0.94%) 2,782,822
EXC EXELON CORPORATION 70.66 Jun 8 Up 0.74 (1.06%) 4,587,103
FE FIRSTENERGY CP 65.53 Jun 8 Up 0.80 (1.24%) 2,806,404
NI NISOURCE INC HLDG CO21.05 Jun 8 Up 0.10 (0.48%) 2,395,235
PCGPG&E CP 45.57 Jun 8 Up 0.04 (0.09%) 3,491,900
PEG PUB ENTRPR GP 83.29 Jun 8 Up 0.86 (1.04%) 1,573,312
SO SOUTHERN CO 34.60 Jun 8 Up 0.29 (0.85%) 5,094,421
TXU TXU CORP 67.41 Jun 8 Up 0.21 (0.31%) 2,978,700
WMB WILLIAMS COS 30.27 Jun 8 Up 0.19 (0.63%) 6,421,647

This is the source code for the web page that actually displays the sparkline chart. The chart is displayed by referencing the page (my page is called ILC for in-line-chart) in an img src tag as in this example:

<img src="../tools/ilc.aspx?T=L&W=100&H=20&TH=2&MN=MN&MX=MX&FG=008800&BG=FFFFFF&DP=1,2,3,4" />

The above img tag displays this chart:


The querystring parameters define the format of the chart and provide the data that composes it.

Parameter Description
T=L The chart will be a line chart (the other option is B for bar).
W=100 The chart will be 100 pixels wide
H=20 The chart will be 20 pixels high
MN=MN Minimum value. "MN" is min value in chart - 2%. If omitted, 0 is used.
MX=MX Maximum value. "MX" is max value in chart + 2%. If omitted, max provided value + 2% is used.
TH=2 The trend line will be 2 pixels thick. (The default is 1)
FG=008800 The trend line will be green.
BG=FFFFFF The chart background color will be white.
DP=1,2,3,4 Specifies the values to be charted.
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="ilc.aspx.vb" Inherits="ilc" %>
<%@ Import namespace="Microsoft.VisualBasic" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Web" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
        '---Page accepts these querystring values:
        '   T: B (bar) or L (line). If no type is specified then L is assumed.
        '   DP: One or more data points.
        '   W: Width
        '   H: Height
        '   MN: Minimum value. "MN" is min value in chart - 2%. If omitted, 0 is used.
        '   MX: Maximum value. "MX" is max value in chart + 2%. If omitted, max provided value + 2% is used.
        '   FG: Line Color
        '   BG: Background Color
        '   TH: Line thickness (line chart) or space between bars (bar chart)

        Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            Dim sDPList As String = Request.QueryString("DP")
            Dim sDP() As String = sDPList.Split(",")

            Dim oChart As New JInLineChart
            If Request.QueryString("T") = "L" Then
                oChart.ChartType = JInLineChart.Type.Line
            ElseIf Request.QueryString("T") = "B" Then
                oChart.ChartType = JInLineChart.Type.Bar
            End If

            oChart.Data = sDP
            oChart.Width = Request.QueryString("W")
            oChart.Height = Request.QueryString("H")
            oChart.LineHeight = Request.QueryString("TH")
            oChart.ForegroundColor = Request.QueryString("FG")
            oChart.BackgroundColor = Request.QueryString("BG")

            Dim sMinMode As String = Request.QueryString("MN")
            Dim sMaxMode As String = Request.QueryString("MX")
            
            If sMinMode = "MN" Then
                oChart.DisplayMin = oChart.ActualMin * 0.98
            
            ElseIf IsNumeric(sMinMode) Then
                oChart.DisplayMin = Val(sMinMode)
            End If
            
            If sMaxMode = "MX" Then
                oChart.DisplayMax = oChart.ActualMax * 1.02
                
            ElseIf IsNumeric(sMaxMode) Then
                oChart.DisplayMax = Val(sMaxMode)
            End If
            
            oChart.ShowChart()
        End Sub

        Private Class JInLineChart
            Public Enum Type As Integer
                Line = 1
                Bar = 2
            End Enum

            Private miImageWidth As Integer = 100
            Private miImageHeight As Integer = 30

            Private miChartX As Integer = 3
            Private miChartY As Integer = 3

            Private miLineWidth As Integer = 3

            Private miChartWidth As Integer = miImageWidth - miChartX * 2
            Private miChartHeight As Integer = miImageHeight - miChartY * 2

            Private moForegroundColor As Color = Color.Black
            Private moBackgroundColor As Color = Color.White

            Private mdDisplayMin As Single
            Private mdDisplayMax As Single
            
            Private mlChartType As Type
            Private mdData() As Single

            Private miCount As Integer
            Private mdActualMin As Single
            Private mdActualMax As Single

            Public Property ChartType() As Type
                Get
                    Return mlChartType
                End Get
                Set(ByVal value As Type)
                    mlChartType = value
                End Set
            End Property

            Public WriteOnly Property Data() As String()
                Set(ByVal value As String())
                    miCount = value.GetUpperBound(0)
                    If miCount > 0 Then
                        mdActualMin = 0
                        mdActualMax = 0

                        mdActualMin = Val(value(0))
                        mdActualMax = Val(value(0))
                        
                        ReDim mdData(miCount)
                        For i As Integer = 0 To miCount
                            If IsNumeric(value(i)) Then
                                mdData(i) = Val(value(i))

                                If mdData(i) > mdActualMax Then
                                    mdActualMax = mdData(i)
                                End If

                                If mdData(i) < mdActualMin Then
                                    mdActualMin = mdData(i)
                                End If
                            Else
                                mdData(i) = 0
                            End If
                        Next
                    
                        mdDisplayMin = mdActualMin * 0.98
                        mdDisplayMax = mdActualMax * 1.02
                    Else
                        mdActualMin = Single.NaN
                        mdActualMax = Single.NaN
                        
                        mdDisplayMin = Single.NaN
                        mdDisplayMax = Single.NaN
                    End If
                End Set
            End Property

            Public WriteOnly Property Width() As Integer
                Set(ByVal value As Integer)
                    miImageWidth = value
                    miChartWidth = miImageWidth - miChartX * 2
                End Set
            End Property

            Public WriteOnly Property Height() As Integer
                Set(ByVal value As Integer)
                    miImageHeight = value
                    miChartHeight = miImageHeight - miChartY * 2
                End Set
            End Property

            Public WriteOnly Property LineHeight() As Integer
                Set(ByVal value As Integer)
                    miLineWidth = value
                End Set
            End Property

            Public WriteOnly Property ForegroundColor() As String
                Set(ByVal value As String)
                    moForegroundColor = HTMLColorToColor(value)
                End Set
            End Property

            Public WriteOnly Property BackgroundColor() As String
                Set(ByVal value As String)
                    moBackgroundColor = HTMLColorToColor(value)
                End Set
            End Property

            Public ReadOnly Property ActualMin() As Single
                Get
                    Return mdActualMin
                End Get
            End Property
            
            Public ReadOnly Property ActualMax() As Single
                Get
                    Return mdActualMax
                End Get
            End Property
            
            Public Property DisplayMin() As Single
                Get
                    Return mdDisplayMin
                End Get
                Set(ByVal value As Single)
                    mdDisplayMin = value
                End Set
            End Property
            
            Public Property DisplayMax() As Single
                Get
                    Return mdDisplayMax
                End Get
                Set(ByVal value As Single)
                    mdDisplayMax = value
                End Set
            End Property
            
            Public Sub ShowChart()
                '---Create new image for composite
                Dim oImage As Bitmap = New Bitmap(miImageWidth, miImageHeight, PixelFormat.Format24bppRgb)

                '---Paste in the parts
                Dim oG As Graphics = Graphics.FromImage(oImage)
                oG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias

                '---Initialize graphic
                oG.FillRectangle(New SolidBrush(moBackgroundColor), New Rectangle(0, 0, miImageWidth, miImageHeight))

                If mlChartType = Type.Line Then
                    DrawLineChart(oG)
                ElseIf mlChartType = Type.Bar Then
                    DrawBarChart(oG)
                End If

                HttpContext.Current.Response.ContentType = "image/JPEG"
                oImage.Save(HttpContext.Current.Response.OutputStream, ImageFormat.Jpeg)

                oG.Dispose()
                oImage.Dispose()
            End Sub

            Private Sub DrawLineChart(ByRef oG As Graphics)
                If miCount > 0 Then
                    Dim iInterval As Integer = miChartWidth / (miCount + 1)

                    Dim dRange As Decimal = mdDisplayMax - mdDisplayMin
                    If dRange > 0 Then
                        Dim iCurX As Integer = 0
                        Dim iCurY As Integer = 0

                        Dim iPrevX As Integer = 0
                        Dim iPrevY As Integer = 0

                        For iPoint As Integer = 0 To miCount
                            Dim dAdjValue As Decimal = mdData(iPoint) - mdDisplayMin

                            iPrevX = iCurX
                            iPrevY = iCurY

                            iCurX = iInterval * iPoint + (iInterval / 2)
                            iCurY = miChartHeight - (miChartHeight * (dAdjValue / dRange))

                            If iPoint > 0 Then
                                oG.DrawLine( _
                                        New Pen(moForegroundColor, miLineWidth), _
                                        miChartX + iPrevX, _
                                        miChartY + iPrevY, _
                                        miChartX + iCurX, _
                                        miChartY + iCurY)
                            End If
                        Next
                    End If
                End If
            End Sub

            Private Sub DrawBarChart(ByRef oG As Graphics)
                If miCount > 0 Then
                    Dim iInterval As Integer = miChartWidth / (miCount + 1)

                    Dim dRange As Decimal = mdDisplayMax - mdDisplayMin
                    If dRange > 0 Then
                        For iPoint As Integer = 0 To miCount
                            Dim dAdjValue As Decimal = mdData(iPoint) - mdDisplayMin

                            Dim iCurX As Integer = iInterval * iPoint + (iInterval / 2)
                            Dim iCurY As Integer = miChartHeight * (dAdjValue / dRange)

                            Dim oRect As New Rectangle( _
                                                miChartX + iCurX - iInterval / 2 + miLineWidth, _
                                                miChartY + (miChartHeight - iCurY), _
                                                iInterval - miLineWidth * 2, _
                                                iCurY)

                            oG.FillRectangle(New SolidBrush(moForegroundColor), oRect)
                        Next
                    End If
                End If
            End Sub

            Private Function HTMLColorToColor(ByVal sColor As String) As Color
                Dim iRed As Integer = CLng("&H" & sColor.Substring(0, 2))
                Dim iGreen As Integer = CLng("&H" & sColor.Substring(2, 2))
                Dim iBlue As Integer = CLng("&H" & sColor.Substring(4, 2))

                Return Color.FromArgb(iRed, iGreen, iBlue)
            End Function
        End Class
</script>