Click here to Skip to main content
Licence Apache
First Posted 13 Dec 2011
Views 66,911
Downloads 4,615
Bookmarked 201 times

ASP .NET Plotter (HTML5, C# and Plot'N'Roll)

By | 19 May 2012 | Article
Nowadays, There are a plenty of Javascript charting libraries used in organizations such as NASA, Eutelsat, MongoDB, Google. Justly, this article aims to show you how you can integrate Javascript charting libraries in ASP .NET and build your own charting user controls depending on your needs.

 Saturday, May 19, 2012: TagPrefix and Script Key generation issues Fixed (version 1.5).

 Friday, May 18, 2012: Random charts generation sample updated (version 1.5).

 Thursday, May 17, 2012: Version 1.5 released. This version provides an ASP .NET Server Control located in a dedicated assembly that can be used within ASP .NET applications. This is the first ASP .NET Server Control that integrates the dygraphs library in ASP .NET. You will find the description of the installation, the options, the architecture of the source code, and samples, in this article. The dygraphs library is used in organizations such as NASA, Eutelsat, MongoDB, Google and much more.



Introduction

Nowadays, There are a plenty of Javascript charting libraries. Justly, this article aims to show you how you can integrate Javascript charting libraries in ASP .NET and build your own charting user controls depending on your flavor and your needs. 

This article describes the architecture and the source code of the released versions. Each version has its own section. Each section describes mock-ups, client side code, server side code and some concepts. 

Below the main technical concepts discussed in each version of this article and/or source code.  

Version 1.0  

  • How to interface with the flot library from C# code?  
  • How to reference Javascript code from the code behind as a startup script? 
  • How to set the Culture of the ASP .NET Worker Thread
  • How to parse XML data with LINQ to XML?
  • How to set up a view with paging through the Repeater server control?
  • How to lazy load data?  (Lazy Loading Design Pattern

Version 1.1  

  • How to get in touch with Adam.JSGenerator library?
  • How to project IEnumerable with LINQ?

Version 1.2  

  • How to interface with the jqPlot library from C# code?
  • How to enable HTML5 canvas for IE7+?  (Graceful fallback)
  • How to pass parameters to a Javascript object from C# code?

Version 1.3

  • How to interface with the dygraphs library from C# code?
  • How to use the X-UA-Compatible Meta tag?
  • How to add a color picker in a Wepage?
  • How to reference Javascript code from the code behind as a startup script when MS Ajax is enabled?
  • How to flatten an IEnumerable?
  • How to aggregate an IEnumerable?

Version 1.4

  • How to lazy load and draw dense data sets?
  • How to use the advanced settings of the dygraphs library from C# code? 
  • How to customize the dygraphs library in order to respond to your needs?  

Version 1.5 

  • How to set up an ASP .NET server control in a dedicated assembly?
  • How to set up the icon of an ASP .NET server control?
  • How to set up the TagPrefix of an ASP .NET server control through AssemblyInfo?
  • How to reference Javascript code from a server control as a startup script? 
  • How to reference Javascript code from a server control as a startup script when MS Ajax is enabled?
  • How to set up a random generator of charts?

Introducing versions 

This section gives a quick overview of the released versions. Screencasts are available in this section. I strongly recommend reading this section. The full description of the versions is available after this section.

Version 1.0 released on Wednesday, December 14, 2011

In this version, I used the flot Javascript library to draw charts on the client side. The Javascript code is generated from the code behind with StringBuilder.Append

Version 1.1 released on Thursday, January 12, 2012

In this version, I integrated Adam.JSGenerator library in order to generate the Javascript code from the code-behind in a more readable and maintainable way.

In the version 1.0, I used StringBuilder to build the Javascript code, 23 code statements were written to produce the desired Javascript code. In the version 1.1, only 1 code statement was written to produce the desired Javascript code.

Adam.JSGenerator allows you to write code that produces JavaScript snippets in a more readable and maintainable way. Instead of having to write tedious string concatenation code, interlined with String.Format and StringBuilder.Append lines, you use a fluent syntax to produce an object structure that produces the JavaScript output that you need.

Instead of writing this line of unmaintainable code (selector, color and background are strings):

return "jQuery('" + selector + "').css({color:'" + color + "';background:'" + background +"'});"; 

Can be written as:

return JS.JQuery(selector).Dot("css").Call(JS.Object(new {color, background}));

The library is available on NuGet[^] and on CodePlex.

The official announcement is available on ADAM's Blog.

You will find the description of the version 1.1 after the "Version 1.0" section.

Version 1.2 released on Sunday, January 22, 2012

In this version, I used jqPlot. jqPlot is a pure JavaScript plotting plugin for the jQuery library created by Chris Leonello. jqPlot renders charts using the HTML5 <canvas> tag.

You will find the description of the version 1.2 after the "Version 1.1" section.

Below a video demonstration.

Version 1.3 released on Sunday, February 12, 2012

In this version, I used dygraphs for plotting, and Ajax for partial postbacks.

dygraphs is a pure Javascript library created by Dan Vanderkam, that produces interactive and zoomable charts of time series, open source code (MIT license). It is designed to display dense data sets, enables users to explore and interpret data, and relies heavily on the HTML5 <canvas> tag.

Some of the features of dygraphs:

  • Supports dense data sets
  • Plots time series without using an external server or Flash
  • Works in Internet Explorer 7+, Firefox, Chrome, Safari and Opera
  • Lightweight and responsive
  • Displays values on mouseover, making interaction easily discoverable
  • Supports error bands around data series
  • Interactive zoom
  • Displays Annotations on the chart
  • Adjustable averaging period
  • Can intelligently chart fractions
  • Customizable click-through actions
  • Compatible with the Google Visualization API
  • Intelligent defaults make it easy to use
I also added the ability to customize charts style by defining the grid line color, the circle size, and the stroke width.

You will find the description of the version 1.3 after the "Version 1.2" section.

Below a video demonstration.

Version 1.4 Plot'N'Roll released on Sunday, March 4, 2012   

This version aims to show the power of the dygraphs library that I used in the version 1.3 of ASP .NET Plotter.  

In this version, I used dense data sets, date time for X coordinates, added rolling periods support and custom bars.

Let's have a look at this chart generated by ASP .NET Plotter.

To navigate in the chart, download the source code of this version, click on the fourth thumbnail, mouse over to highlight individual values. Click and drag to zoom. Double-click to zoom back out. Change the number and hit enter to adjust the averaging period.

Sometimes your data has asymetric uncertainty or you want to specify something else with the error bars around a point. This plot illustrates this. The point is the daily average and the bars denote the low and high temperatures for the day.

Some things to notice:

  • There's less seasonal temperature variation in San Francisco than in New York.
  • The difference is about 15° F for San Francisco vs. 50° F for New York.
  • The daily data (set rolling period to 1) is quite noisy and hides this conclusion.
  • Using a 14-day moving average makes it clearer. A 100-day rolling period averages out nearly all the specifics from the data.
  • There's a gap in the data for San Francisco, when the weather station was down (zoom into October 2007 to see it).
  • The bands around each point indicate average highs and lows.
  • There is a lot of data in this chart: low, average and high for each city on each day of a three year period ˜ 6000 data points in all.

The dygraphs library allows the user to explore the data and discover these facts.

You will find the description of the version 1.4 after the "Version 1.3" section.

Below a video demonstration.

Version 1.5 ASP .NET Server Control released on Thursday, May 17, 2012

This version provide an ASP .NET Server Control of the dygraphs library that can be used within ASP .NET applications.

To install the ASP .NET Server Control within an ASP .NET Application, you will need to follow these steps:

  1. Download DygraphControl.zip
  2. Extract it 
  3. Open the ASP .NET Web Page where you want to install it
  4. Add the required Javascript files in the Web Application and reference them
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9" />
    <!--[if lt IE 9]><script src="excanvas.js" type="text/javascript"> </script><![endif]-->
    <script src="dygraph-combined.js" type="text/javascript"> </script>
    
  5. Install the control in the Visual Studio ToolBox

    5.1. Open the ToolBox



    5.2. Open the Controls Dialog, click Browse and choose the DygraphControl.dll



    5.3. Click OK



     
  6. Install and configure the control in the Web Page

    6.1. Installation: Drag and Drop the control in the Web Page and set the desired parameters (The "version 1.5" section describes how to set the parameters in details through a sample). If you are in a Design View, You can set the parameters in Visual Studio Settings Dialog. Otherwise, you can set them in the ASPX page directly.



    6.2. Set up the curves to plot in the code-behind (This is explained in the section "Version 1.5")

The section "Version 1.5" explains how to get started with the control, the different options, samples and the source code. If you want only to use the server control, you can skip the description of the  previous versions and jump directly to the "Version 1.5" section. However, I strongly recommend reading the previous versions. 

Browser compatibilty  

All the versions available have been tested on IE7, IE8, IE9, Firefox, Chrome, Safari and Opera, and are browser compatible with IE7, IE8, IE9, Firefox, Chrome, Safari and Opera.

IE9 includes native support for the HTML5 canvas element. But IE8 and IE7 don't support it. Thus, I use excancas if the IE browser version is lower than IE9

The directive <!DOCTYPE html> at the top of the Default.aspx page is very important to ensure that the page will be rendered properly.

Versions 

Version 1.0 

Models

Using the code

I used the flot library for plotting charts on client side and XML files to store plots informations. You can of course use another library or another data source. I'll explain at the end of this article how to do that.

The plots are stored in an XML file.

<plots>
  <plot id="1" dataUrl="App_Data/plot1.xml" thumbnailUrl="Images/plot1.png" title="Empty plot" />
  <plot id="2" dataUrl="App_Data/plot2.xml" thumbnailUrl="Images/plot2.png" title="One curve" />
  <plot id="3" dataUrl="App_Data/plot3.xml" thumbnailUrl="Images/plot3.png" title="Two curves" />
  <plot id="4" dataUrl="App_Data/plot4.xml" thumbnailUrl="Images/plot4.png" title="Three curves"/>
  <plot id="5" dataUrl="App_Data/plot5.xml" thumbnailUrl="Images/plot5.png" title="Four curves" />
</plots> 

A plot is defined by an Identifier, an XML file that contains curves informations, a thumbnail and a title.

The curves informations are stored in an XML file.

<plot id="2">
  <curves>
    <curve id="1">
      <points>
        <point x="1999" y="3.0" />
        <point x="2000" y="3.9" />
        <point x="2001" y="2.0" />
        <point x="2002" y="1.2" />
        <point x="2003" y="1.3" />
        <point x="2004" y="2.5" />
        <point x="2005" y="2.0" />
        <point x="2006" y="3.1" />
        <point x="2007" y="2.9" />
        <point x="2008" y="0.9" />
      </points>
      <label>Europe (EU27)</label>
    </curve>
  </curves>
  <description>
    <![CDATA[
          Basic plot of one curve.
        ]]>
  </description>
</plot>  

A Plot can have zero or multiple Curves and a Description. A Curve is defined by a set of Points and a Label.

A Point is defined by X and Y coordinates.

public class Point
{

    #region Constructor

    public Point(float x, float y)
    {
        X = x;
        Y = y;
    }

    #endregion

    #region Properties

    public float X { get; private set; }
    public float Y { get; private set; }

    #endregion

} 

A Curve is defined by a Label and a set of Points.

public class Curve
{
    #region Constructor

    public Curve(string label, IList<Point> points)
    {
        Label = label;
        Points = points;
    }

    #endregion

    #region Properties

    public IList<Point> Points { get; private set; }
    public string Label { get; set; }

    #endregion

} 

A Plot is defined by an Identifier, a Title, a data Path where the Curves and Description are stored and a thumbnail Path. The IsLoaded property is used for the lazy loading design pattern.

public class Plot
{
    #region Constructor

    public Plot(int id, string title, string dataPath, string thumbnailPath, bool isRelativePath)
    {
        Id = id;
        Title = title;
        DataPath = dataPath;
        ThumbnailPath = thumbnailPath;
        IsRelativePath = isRelativePath;
        Description = string.Empty;
        Curves = new List<Curve>();
        IsLoaded = false;
    }

    #endregion

    #region Properties

    public int Id { get; private set; }
    public string Title { get; private set; }
    public string Description { get; private set; }
    public string DataPath { get; private set; }
    public string ThumbnailPath { get; private set; }
    public bool IsRelativePath { get; private set; }
    public IList<Curve> Curves { get; private set; }
    public bool IsLoaded { get; private set; }

    #endregion

    #region Methods

    public void Load()
    {
        // If HttpContext is null and paths are relative, Throw an exception
        if (IsRelativePath && HttpContext.Current == null)
        {
            throw new Exception("HttpContext is null, you cannot use relative paths.");
        }

        // load the curves from the XML file
        XDocument data = XDocument.Load(IsRelativePath 
                                    ? HttpContext.Current.Server.MapPath(DataPath) 
                                    : DataPath);

        // Initialize the Curves collection
        Curves = new List<Curve>();

        // Set current thread culture to en-US for float parsing
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

        // Retrieve the plot current plot element from the XML file
        XElement xplot = data.Element("plot");

        // Retrieve the current Description<
        Description = xplot.Element("description").Value;

        // Retrieve the current curves
        IEnumerable<XElement> xcurves = xplot.Descendants("curve");

        // Enumerate curves
        // Build a the Curve object 
        // and Add it to the current Curves collection
        foreach (XElement xcurve in xcurves)
        {
            // Retrive the label
            string label = xcurve.Element("label").Value;

            // Initialize a new Set
            // Retrieve the points from the XML file
            var xpoints = xcurve.Descendants("point");

            // Enumerate the points
            // Parse and add their values to a new Point object
            // Build the Set
            List<Point> set = (from xpoint in xpoints
                       let x = Single.Parse(xpoint.Attribute("x").Value)
                       let y = Single.Parse(xpoint.Attribute("y").Value)
                       select new Point(x, y)).ToList();

            // Add the curve to the Curves collection
            Curves.Add(new Curve(label, set));
        }

        // Set data as loaded
        IsLoaded = true;
    }

    #endregion
}

The Load method loads the Curves of the Plot from their XML file and the plot's Description.

The plots' thumbnails are displayed with a Repeater server control.

<asp:Repeater ID="RepeaterPlots" runat="server" OnItemCommand="RepeaterImages_ItemCommand">
    <HeaderTemplate>
        <ul class="thumb">
    </HeaderTemplate>
    <ItemTemplate>
        <li>
            <asp:LinkButton ID="LinkButtonThumbnail" runat="server" CommandName="Plot" CommandArgument='<%#DataBinder.Eval(Container.DataItem, "Id")%>'>
        <img src='<%#DataBinder.Eval(Container.DataItem, "ThumbnailPath")%>' alt="" title='<%#DataBinder.Eval(Container.DataItem, "Title")%>' />
            </asp:LinkButton>
        </li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater> 

I used a LinkButton because I need to do a Postback in order to load and draw the plots lazily as you will see.

The binding is done by the LoadPlots method:

private void LoadPlots()
{
    // Populate the repeater control with the plots collection
    // Indicate that the data should be paged
    // Set the number of plots you wish to display per page
    // Set the PagedDataSource's current page
    var pds = new PagedDataSource
    {
        DataSource = Plots,
        AllowPaging = true,
        PageSize = 4,
        CurrentPageIndex = CurrentPage - 1
    };

    LabelCurrentPage.Text = "Page " + CurrentPage + " of " + pds.PageCount;

    // Hide Previous or Next buttons if necessary
    ImageButtonPrevious.Visible = !pds.IsFirstPage;
    ImageButtonNext.Visible = !pds.IsLastPage;

    // Hide the Description view
    metadata.Visible = false;

    // Bind data to the Plots Repeater
    RepeaterPlots.DataSource = pds;
    RepeaterPlots.DataBind();
} 

The current page number is persisted in the ViewState.

protected int CurrentPage
{
    get
    {
        // Look for current page in ViewState
        object o = ViewState["CurrentPage"];
        if (o == null) return 1; // default page index of 1
        return (int)o;
    }

    set { ViewState["CurrentPage"] = value; }
} 

The collection of Plots is persisted in the Cache in order to boost paging performance.

protected IList<Plot> Plots
{
    get
    {
        if (Cache["Plots"] == null)
        {
            // Read plots info from XML document
            XDocument xdoc = XDocument.Load(MapPath("/App_Data/plots.xml"));
            IList<Plot> plots = xdoc.Descendants("plot").
                Select(p =>
                       new Plot(int.Parse(p.Attribute("id").Value),
                                  p.Attribute("title").Value,
                                  p.Attribute("dataUrl").Value,
                                  p.Attribute("thumbnailUrl").Value,
                                  true)).ToList();

            Cache["Plots"] = plots;
        }
        return (IList<Plot>) Cache["Plots"];
    }
}

The paging is done through two buttons and a label that contains the current page number.

<div id="pager">
    <asp:Label ID="LabelCurrentPage" runat="server"></asp:Label>
    <asp:ImageButton ID="ImageButtonPrevious" runat="server" ImageUrl="Styles/Images/Arrow-Left-icon.png"
        OnClick="ImageButtonPrevious_Click" CssClass="previous" />
    <asp:ImageButton ID="ImageButtonNext" runat="server" ImageUrl="Styles/Images/Arrow-right-icon.png"
        OnClick="ImageButtonNext_Click" CssClass="next" />
</div> 

If the page is being rendered for the first time or isn't being loaded in response to a Postback, we load the thumbnails.

protected void Page_Load(object sender, EventArgs e)
{
    // Reload control if the page is being rendered for the first time 
    // or isn't being loaded in response to a postback
    if (!IsPostBack)
    {
        LoadPlots();
    }
} 

When the user clicks on the next button, the current page number is updated and the plots' thumbnails are loaded.

protected void ImageButtonNext_Click(object sender, EventArgs e)
{
    // Set viewstate variable to the next page
    CurrentPage++;

    // Reload control
    LoadPlots();
}

When the user clicks on the previous button, the current page number is updated and the plots' thumbnails are loaded.

protected void ImageButtonPrevious_Click(object sender, EventArgs e)
{
    // Set viewstate variable to the previous page
    CurrentPage--;

    // Reload control
    LoadPlots();
}  

You will notice that I disabled the SessionState because I don't need it and for performance reasons If you don't need it is recommended to disable it.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="jQuery.Plotter.Default"
    EnableSessionState="false" %> 
You will notice that I used the ItemCommand event in the plots Repeater server control in order to draw the Plot when the user clicks on its thumbnail.
protected void RepeaterImages_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    if (e.CommandName.Equals("Plot"))
    {
        // Retrieve the plot Id from the command argument
        int plotId = int.Parse(e.CommandArgument.ToString());

        // Load the selected plot
        Plot plot = Plots.First(i => i.Id == plotId);

        // Plot the selected plot
        Plot(plot);

        // Display the Description View
        metadata.Visible = true;
    }
}

The Plot method retrieves the curves and the description, and display them on the client side. We retrieve the Points and the Label in order to generate the jQuery code that plots the charts. The generated jQuery code is then registered with ClientScriptManager as a startup script of the Default page. I used the Flot library and the basic options but you can of course use other options, for this you will need to read the Flot library documentation. The plots are loaded lazily. In other words, a plot is loaded only when the user clicks on its thumbnail for the first time and once it is loaded we never load it again.

I strongly recommend reading this code and its comments.

private void Plot(Plot plot)
{
    // Load the plot lazily
    if (!plot.IsLoaded)
    {
        plot.Load();
    }

    // Encapsulate curves into jQuery code
    var jQuery = new StringBuilder();
    var jQueryPlot = new StringBuilder();

    // Build the jQuery $(document).ready function
    // Build the jQuery $.plot function
    jQuery.Append("$(document).ready(function () {" + Environment.NewLine);
    jQueryPlot.Append("$.plot($('#" + main_view.ID + "')," + 
              (plot.Curves.Count > 0 
              ? " [ " 
              : "[ d ]);"));

    // If this plot does not have curves
    // Initialize an empty jQuery data matrix
    if (plot.Curves.Count == 0)
    {
        jQuery.Append("var d = [];" + Environment.NewLine);
    }

    // Set current thread culture to en-US for float parsing
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

    // Enumerate the curves
    // Buid the jQuery code
    for (int i = 0; i < plot.Curves.Count; i++)
    {
        var c = plot.Curves[i];

        // Retrieve the current label
        // Check if the label is not empty
        string label = string.Empty;
        bool labelExists = !string.IsNullOrEmpty(c.Label);

        // If the label is not empty
        // Initialize the jQuery label variable
        if (labelExists)
        {
            label = "label" + i;
            jQuery.Append("var " + label + " = " + "'" + c.Label + "'" + ";" + Environment.NewLine);
        }

        // Initialiaze the name of the jQuery data matrix
        // Start building the jQuery code of data matrix
        string d = "d" + i;
        jQuery.Append("var " + d + " = [");

        // Enumerate the points of the current curve
        // Buid the matrix set
        // Close the matrix If the last point is reached
        for (int j = 0; j < c.Points.Count; j++)
        {
            var p = c.Points[j];
            jQuery.Append("[" + p.X + ", " + p.Y + "]" +
                (j < c.Points.Count - 1
                ? ", "
                : "];" + Environment.NewLine));
        }

        // If the label is not empty
        // Build the arguments of the jQuery function $.plot with the label and the data matrix
        // Else Build the arguments of the jQuery function $.plot with the data matrix only
        // Close the jQuery $.plot If the last point is reached
        if (labelExists)
        {
            jQueryPlot.Append("{ label: " + label + ", data: " + d + "}" +
                                (i < plot.Curves.Count - 1
                                    ? ", "
                                    : "]);" + Environment.NewLine));
        }
        else
        {
            jQueryPlot.Append(d +
                                (i < plot.Curves.Count - 1
                                    ? ", "
                                    : "]);" + Environment.NewLine));
        }
    }

    // Add the jQuery code of the arguments to the main jQuery code
    // Close the $(document).ready jQuery function
    jQuery.Append(jQueryPlot);
    jQuery.Append("});" + Environment.NewLine);

    // Get a ClientScriptManager reference from the current Page
    // Get the type of the current Page
    Type pageType = GetType();
    ClientScriptManager csm = ClientScript;

    // Initialize a unique plot Identifier for the current ClientScriptManager 
    string id = "plot" + plot.Id;

    // Check to see if the startup script is already registered
    if (!csm.IsStartupScriptRegistered(pageType, id))
    {
        // Register the startup script with the current Page object using its type,
        // the unique plot id, the jQuery code, and indicate to add script tags
        csm.RegisterStartupScript(pageType, id, jQuery.ToString(), true);
    }

    // Bind the description view
    // If the description is empty, 
    // Display "No description available."
    // Else Display the current description
    LabelDescription.Text = string.IsNullOrEmpty(plot.Description)
                                ? "No description available."
                                : plot.Description;
}
The images that I used for paging are from Icon Archive and under Creative Commons license.

Version 1.1

In the version 1.1, nothing changed on the client side. Only the server side code has been modified in order to make the dynamic Javascript code generation more readable and maintainable.

I used the Adam.JSGenerator Library for this purpose.

In the code-behind of the Default page, only the Plot method has been modified in order to make the Javascript code generation more readable and maintainable.

Remember, in the initial version (1.0), I used StringBuilder to write tedious string concatenation code, interlined with StringBuilder.Append lines in order to build the Javascript code. 23 code statements were written to produce the desired Javascript code.

In the version 1.1, Thanks to Adam.JSGenerator Library only 1 code statement was written to produce the desired Javascript code.

private void Plot(Plot plot)
{
    // Load the plot lazily
    if (!plot.IsLoaded)
    {
        plot.Load();
    }

    // Set current thread culture to en-US for float parsing
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

    // Encapsulate curves into jQuery code by using Adam.JSGenerator
    // Only 1 code statements (v1.1) instead of 23 code statements (v1)
    string script =
        JS.JQuery(JS.Expression("document")).Dot("ready").
            Call(JS.Function().
                        Do(JS.Expression("$.plot").
                            Call(JS.Expression("$('#" + main_view.ID + "')"), PlotHelper.GetArray(plot)))).
            ToString();

    // Get a ClientScriptManager reference from the current Page
    // Get the type of the current Page
    Type pageType = GetType();
    ClientScriptManager csm = ClientScript;

    // Initialize a unique plot Identifier for the current ClientScriptManager 
    string id = "plot" + plot.Id;

    // Check to see if the startup script is already registered
    if (!csm.IsStartupScriptRegistered(pageType, id))
    {
        // Register the startup script with the current Page object using its type,
        // the unique plot id, the jQuery code, and indicate to add script tags
        csm.RegisterStartupScript(pageType, id, script, true);
    }

    // Bind the description view
    // If the description is empty, 
    // Display "No description available."
    // Else Display the current description
    LabelDescription.Text = string.IsNullOrEmpty(plot.Description)
                                ? "No description available."
                                : plot.Description;
} 

PlotHelper class provides a static method to project a Plot object into a Javascript Expression.

public class PlotHelper
{
    #region Public Static Methods

    public static Expression GetArray(Plot plot)
    {
        // Project the curves collection into a javascript Expression
        return plot.Curves.Count > 0
                    ? JS.Array(plot.Curves.Select(CurveHelper.GetExpression))
                    : JS.Array(JS.Array());
    }

    #endregion
}

CurveHelper class provides a static method to project a Curve object into a Javascript Expression.

public class CurveHelper
{
    #region Public Static Methods

    public static Expression GetExpression(Curve curve)
    {
        return string.IsNullOrEmpty(curve.Label)
                    ? JS.Object(new { data = GetMatrix(curve) })
                    : JS.Object(new { label = curve.Label, data = GetMatrix(curve) });
    }

    #endregion

    #region Private Static Methods

    private static ArrayExpression GetMatrix(Curve curve)
    {
        // Project the points collection into a javascript ArrayExpression
        return
            JS.Array(
                curve.Points.Select(
                    point => new[] { point.X, point.Y }));
    }

    #endregion
}

Version 1.2

Models



Using the code

In this version, I updated both client side code and server side code.

Client side

In the client side, I updated the Javascript and CSS references in order to integrate jqPlot. Below, the new head of the Default page.

<head runat="server">
    <title>jQuery.Plotter</title>
    <link href="Styles/Default.css" rel="stylesheet" type="text/css" />
    <link href="Styles/jquery.jqplot.min.css" rel="stylesheet" type="text/css" />
    <script src="Scripts/jquery.min.js" type="text/javascript"> </script>
    <script src="Scripts/default.js" type="text/javascript"> </script>
    <script src="Scripts/jquery.jqplot.min.js" type="text/javascript"> </script>
    <!--
        excanvas is required only for IE versions below 9. 
        IE 9 includes native support for the canvas element and does not require excanvas.
        -->
    <!--[if lt IE 9]><script src="Scripts/excanvas.js" type="text/javascript"> </script><![endif]-->
</head>

This version is browser compatible with IE7, IE8, IE9, Firefox, Chrome, Safari and Opera.

IE9 includes native support for the HTML5 canvas element. But IE8 and IE7 doesn't support the HTML5 canvas element. Thus, I use excancas if the IE browser version is lower than IE9.

I added a setting box in the page in order to offer to the end-user the ability to enable or disable the labels and the animation of charts. Below, the client side code of this box.

<div class="settings">
    <img src="Styles/Images/Settings-icon.png" alt="" />
    <span>Options</span>
    <div class="options">
        <asp:CheckBox ID="CheckBoxAnimation" runat="server" Text="Enable animation" Checked="true" />
        <br />
        <asp:CheckBox ID="CheckBoxLabels" runat="server" Text="Show labels" Checked="true" />
    </div>
</div>

By default, the animation and the labels are enabled.

Below, the CSS code of this box. 

.settings {
    background: #f0f0f0;
    border: 1px solid #ddd;
    height: 100px;
    left: 175px;
    overflow: hidden;
    padding: 0px 3px 3px 3px;
    position: absolute;
    top: 10px;
    width: 135px;
}

.settings .options {
    background: #FAFAFA;
    border-top: 1px solid #ddd;
    margin-top: 1px;
}

.settings .options input { margin-right: 5px; } 
Server side   

I updated the Plot method of the Default page in order to generate dinamically the Javascript code needed to plot the charts with the ability to pass the checkboxes options.

private void Plot(Plot plot)
{
    // Load the plot lazily
    if (!plot.IsLoaded)
    {
        plot.Load();
    }

    // Set current thread culture to en-US for float parsing
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

    // Encapsulate curves into jQuery code by using Adam.JSGenerator
    // Only 1 code statements (v1.1) instead of 23 code statements (v1)
    // v1.2 create the actual plot by calling the $.jqplot plugin
    string script =
        JS.JQuery(JS.Expression("document")).Dot("ready").
            Call(JS.Function().
                        Do(JS.Expression("$.jqplot").
                            Call(main_view.ID, PlotHelper.GetCurves(plot),
                                    PlotHelper.GetOptions(plot, CheckBoxLabels.Checked, CheckBoxAnimation.Checked))))
            .
            ToString();

    // Get a ClientScriptManager reference from the current Page
    // Get the type of the current Page
    Type pageType = GetType();
    ClientScriptManager csm = ClientScript;

    // Initialize a unique plot Identifier for the current ClientScriptManager 
    string id = "plot" + plot.Id;

    // Check to see if the startup script is already registered
    if (!csm.IsStartupScriptRegistered(pageType, id))
    {
        // Register the startup script with the current Page object using its type,
        // the unique plot id, the jQuery code, and indicate to add script tags
        csm.RegisterStartupScript(pageType, id, script, true);
    }

    // Bind the description view
    // If the description is empty, 
    // Display "No description available."
    // Else Display the current description
    LabelDescription.Text = string.IsNullOrEmpty(plot.Description)
                                ? "No description available."
                                : plot.Description;
}

PlotHelper class provides a static method to project a Plot object into a Javascript Expression and a static method to project the settings and labels into a Javascript Expression.

public class PlotHelper
{
    #region Public Static Methods

    public static Expression GetCurves(Plot plot)
    {
        // Project the curves' points collection into a javascript Expression
        return plot.Curves.Count > 0
                    ? JS.Array(plot.Curves.Select(CurveHelper.GetExpression))
                    : JS.Array(JS.Array(0));
    }

    public static Expression GetOptions(Plot plot, bool showLabels, bool enableAnimation)
    {
        // Project the curves' labels into a javascript Expression if showLabels is true
        Expression labels = showLabels && plot.Curves.Count > 0
                                ? JS.Array(plot.Curves.Select(c => JS.Object(new {label = c.Label})))
                                : JS.Array();

        // options
        return JS.Object(
            new
                {
                    animate = enableAnimation,
                    legend = JS.Object(new {show = showLabels}),
                    series = labels
                }
            );
    }

    #endregion
} 

CurveHelper class provides a static method to project a Curve object into a Javascript ArrayExpression.

public class CurveHelper
{
    #region Public Static Methods

    public static ArrayExpression GetExpression(Curve curve)
    {
        // Project the points collection into a javascript ArrayExpression
        return
            JS.Array(
                curve.Points.Select(
                    point => new[] {point.X, point.Y}));
    }

    #endregion
}

Bonus

I included a bonus in the source code of the version 1.2. I let you check out the surprise by yourself.

Version 1.3

Models 

jQuery_Plotter/scc6.PNG

jQuery_Plotter/scc7.PNG

jQuery_Plotter/scc8.PNG

jQuery_Plotter/scc9.PNG

Using the code

In this version, I updated both server side code and client side code.

Client side

I updated the Default.aspx page header in order to include the refrences of the dygraphs library and the colorpicker jQuery plugin.

You will notice that I used the X-UA-Compatible Meta tag in order to force IE8 to use IE7's standards mode and IE9 to use IE9's standards mode. For further informations about IE’s compatibility features, I advise you to read this article from IEBlog.

IE9 includes native support for the HTML5 canvas element. But IE8 and IE7 don't support it. Thus, I use excancas only if the IE browser version is lower than IE9.

The directive <!DOCTYPE html> at the top of the Default.aspx page is very important to ensure that the page will be rendered properly.

Thus, this version and the previous versions are browser compatible with IE7, IE8, IE9, Firefox, Chrome, Safari and Opera.

<head>
    <!--IE8 renders the webpage in IE7 Standards mode while IE9 renders the webpage in IE9’s Standards mode-->
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9" />
    <title>jQuery.Plotter</title>
    <!--excanvas is required only for IE versions below 9.-->
    <!--[if lt IE 9]><script src="Scripts/excanvas.js" type="text/javascript"> </script><![endif]-->
    <link href="Styles/Default.css" rel="stylesheet" type="text/css" />
    <link href="Styles/colorpicker/css/colorpicker.css" rel="stylesheet" type="text/css" />
    <script src="Scripts/jquery.min.js" type="text/javascript"> </script>
    <script src="Scripts/default.js" type="text/javascript"> </script>
    <script src="Scripts/dygraph-combined.js" type="text/javascript"> </script>
    <script src="Scripts/colorpicker.js" type="text/javascript"> </script>
</head>

I also updated the setting box in order to offer to the end user the ability to modify the grid lines color by using a color picker, the circle size, and the stroke width.

<div class="settings">
    <img src="Styles/Images/Settings-icon.png" alt="" />
    <span>Options</span>
    <div class="options">
        <table>
            <tr>
                <td colspan="2">
                    <asp:CheckBox ID="CheckBoxLabels" runat="server" Text="Show labels" Checked="true" />
                </td>
            </tr>
            <tr>
                <td>
                    Grid line color
                </td>
                <td>
                    <asp:TextBox ID="TextBoxColor" runat="server" Text="FF0000" Width="50px"></asp:TextBox>
                </td>
            </tr>
            <tr>
                <td>
                    Circle size
                </td>
                <td>
                    <asp:TextBox ID="TextBoxCircleSize" runat="server" Text="7" Width="50px"></asp:TextBox>
                </td>
            </tr>
            <tr>
                <td>
                    Stroke width
                </td>
                <td>
                    <asp:TextBox ID="TextBoxStrokeWidth" runat="server" Text="4" Width="50px"></asp:TextBox>
                </td>
            </tr>
        </table>
    </div>
</div>

In order to use partial postbacks, I included the container div in the UpdatePanel server control.

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanelContainer" runat="server">
    <ContentTemplate>
        <div class="container">
            <div class="settings">
                <img src="Styles/Images/Settings-icon.png" alt="" />
                <span>Options</span>
                <div class="options">
                    <table>
                        <tr>
                            <td colspan="2">
                                <asp:CheckBox ID="CheckBoxLabels" runat="server" Text="Show labels" Checked="true" />
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Grid line color
                            </td>
                            <td>
                                <asp:TextBox ID="TextBoxColor" runat="server" Text="FF0000" Width="50px"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Circle size
                            </td>
                            <td>
                                <asp:TextBox ID="TextBoxCircleSize" runat="server" Text="7" Width="50px"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Stroke width
                            </td>
                            <td>
                                <asp:TextBox ID="TextBoxStrokeWidth" runat="server" Text="4" Width="50px"></asp:TextBox>
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
            <asp:Repeater ID="RepeaterPlots" runat="server" OnItemCommand="RepeaterImages_ItemCommand">
                <HeaderTemplate>
                    <ul class="thumb">
                </HeaderTemplate>
                <ItemTemplate>
                    <li>
                        <asp:LinkButton ID="LinkButtonThumbnail" runat="server" CommandName="Plot" CommandArgument='<%# DataBinder.Eval(Container.DataItem, "Id") %>'>
                                    <img src='<%# DataBinder.Eval(Container.DataItem, "ThumbnailPath") %>' alt="" title='<%# DataBinder.Eval(Container.DataItem, "Title") %>' />
                        </asp:LinkButton>
                    </li>
                </ItemTemplate>
                <FooterTemplate>
                    </ul>
                </FooterTemplate>
            </asp:Repeater>
            <div id="pager">
                <asp:Label ID="LabelCurrentPage" runat="server"></asp:Label>
                <asp:ImageButton ID="ImageButtonPrevious" runat="server" ImageUrl="Styles/Images/Arrow-Left-icon.png"
                    OnClick="ImageButtonPrevious_Click" CssClass="previous" />
                <asp:ImageButton ID="ImageButtonNext" runat="server" ImageUrl="Styles/Images/Arrow-right-icon.png"
                    OnClick="ImageButtonNext_Click" CssClass="next" />
            </div>
            <div id="main_view" runat="server">
            </div>
            <div id="metadata" runat="server">
                <img src="Styles/Images/asset-green-16.png" alt="" />
                <span>Description</span>
                <div class="description">
                    <asp:Label ID="LabelDescription" runat="server" Text="Label"></asp:Label>
                </div>
            </div>
        </div>
    </ContentTemplate>
</asp:UpdatePanel>
</form>

Now that we used the UpdatePanel we have to update the jQuery code. Indeed, the $(document).ready() function will work only the first time when the page is viewed and after that the fancy thumbnail hover disappears. You can easily see that by using the $(document).ready() and clicking on the next page for example, The fancy thumbnail hover effect disappears. But don't worry there is a solution to this issue. I used the pageLoad() Ajax function. pageLoad() is called after every UpdatePanel refresh and that is exactly what we were looking for. I advise you to take a look at this article regarding the Ajax pageLoad() function and the jQuery $(document).ready() function.

Thus, I updated the Javascript code of the Default.aspx page in order to add the colorpicker plugin and to use the pageLoad() Ajax function.

 function pageLoad() {

    // colorPicker
    $('#TextBoxColor').ColorPicker({
        onSubmit: function(hsb, hex, rgb, el) {
            $(el).val(hex);
            $(el).ColorPickerHide();
        },
        onBeforeShow: function() {
            $(this).ColorPickerSetColor(this.value);
        }
    }).bind('keyup', function() {
        $(this).ColorPickerSetColor(this.value);
    });

    // Larger thumbnail preview 
    // hoverIntent
    $("ul.thumb li").hover(function() {
        $(this).css({ 'z-index': '10' });
        $(this).find('img').addClass('hover').stop()
            .animate({
                marginTop: '-110px',
                marginLeft: '-110px',
                top: '50%',
                left: '50%',
                width: '174px',
                height: '174px',
                padding: '20px'
            }, 200);

    }, function() {
        $(this).css({ 'z-index': '0' });
        $(this).find('img').removeClass('hover').stop()
            .animate({
                marginTop: '0',
                marginLeft: '0',
                top: '0',
                left: '0',
                width: '100px',
                height: '100px',
                padding: '5px'
            }, 400);
    });
}

I also updated the CSS code of the settings box.

.settings {
    background: #f0f0f0;
    border: 1px solid #ddd;
    height: 60px;
    left: 175px;
    overflow: hidden;
    padding: 0px 3px 3px 3px;
    position: absolute;
    top: 10px;
    width: 120px;
}

.settings .options {
    background: #FAFAFA;
    border-top: 1px solid #ddd;
    margin-top: 1px;
}

.settings .options input { margin-right: 5px; }
Server side

I updated the Plot method in order to dynamically generate the dygraphs Javascript code depending on the settings box options. I also modified the way I register the Javascript code as startup script of the Default.aspx page in order to manage the case when Ajax is enabled on the page and the case when It is not.

 private void Plot(Plot plot)
{
    // Load the plot lazily
    if (!plot.IsLoaded)
    {
        plot.Load();
    }

    // Set current thread culture to en-US for float parsing
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

    // Encapsulate curves into jQuery code by using Adam.JSGenerator
    // Only 1 code statements (v1.1) instead of 23 code statements (v1)
    // v1.2 create the actual plot by calling the $.jqplot plugin
    // v1.3 dygraphs & style options
    string script = JS.New(JS.Expression("Dygraph"),
                            JS.Expression("document").Dot("getElementById").Call(main_view.ID),
                            PlotHelper.GetCurves(plot),
                            PlotHelper.GetOptions(plot,
                                                    CheckBoxLabels.Checked,
                                                    "#" + TextBoxColor.Text,
                                                    int.Parse(TextBoxCircleSize.Text),
                                                    int.Parse(TextBoxStrokeWidth.Text))).ToString();

    // Get the type of the current Page
    Type pageType = GetType();

    // Initialize a unique plot Identifier for the current ClientScriptManager 
    string id = "plot" + plot.Id;

    //Determines if MsAjax is available in this Web application
    bool isMsAjax = ScriptManager.GetCurrent(this) != null;

    if (isMsAjax)
    {
        ScriptManager.RegisterClientScriptBlock(this, pageType, id, script, true);
    }
    else
    {
        // Get a ClientScriptManager reference from the current Page
        ClientScriptManager csm = ClientScript;

        // Check to see if the startup script is already registered
        if (!csm.IsStartupScriptRegistered(pageType, id))
        {
            // Register the startup script with the current Page object using its type,
            // the unique plot id, the jQuery code, and indicate to add script tags
            csm.RegisterStartupScript(pageType, id, script, true);
        }
    }

    // Bind the description view
    // If the description is empty, 
    // Display "No description available."
    // Else Display the current description
    LabelDescription.Text = string.IsNullOrEmpty(plot.Description)
                                ? "No description available."
                                : plot.Description;
}

Regarding data formats, There are five types of input that the dygraphs library accepts.

  1. CSV data
  2. URL
  3. array
  4. function
  5. DataTable

As we construct the data set from the server side, I used the array format.

Below an example of array format.

new Dygraph(document.getElementById("graphdiv2"), 
            [ [1,10,100], [2,20,80], [3,50,60], [4,70,80] ], 
            { labels: [ "x", "A", "B" ] });
Headers

Headers for native format must be specified via the labels option. There's no other way to set them.

y-values

You can specify errorBars, fractions or customBars with the array format. If you specify any of these, the values become arrays (rather than numbers). Below what the format looks like for each one.

errorBars: [x, [value1, std1], [value2, std2], ...] 
fractions: [x, [num1, den1], [num2, den2], ...] 
customBars: [x, [low1, val1, high1], [low2, val2, high2], ...]

To specify missing data, set the value to null. You may not set a value inside an array to null. Use null instead of the entire array.

A little bit of LINQ

Thus, I had to set up a LINQ query that projects the curves into a Javascript array that will be interpreted by dygraphs.

The diagrams below explain the transformation. I decided to illustrate the transformation with only three set of points. But the transformation could be made for N set of points where N is a positive integer.

If the three points of the three curves have the same X coordinate, we construct an array of array with the X coordinate and Y coordinates.

jQuery_Plotter/flatten1.final.png

If two curves have the same X coordinate and the third one a different one, we construct an array of two arrays. The first one contains the common X coordinate and the two Y coordinates, and the second one contains the other X coordinate and the associated Y coordinate .

jQuery_Plotter/flatten2.png


If the three curves have different X coordinates, we construct an array of three arrays. Each one contains the X coordinate and Its associated Y coordinate.

jQuery_Plotter/flatten3.png


Now that you saw the transformation on three set of points, you can see how the transformation is made for N set of points.

I set up the transformation with LINQ and used Flatten and Aggregate functions.

public static class PlotHelper
{
    #region Constants

    private const double Epsilon = double.Epsilon;

    #endregion

    #region Public Static Methods

    public static Expression GetCurves(Plot plot)
    {
        // Flatten and aggregate to get the Javascript array of Javascript arrays
        // Flatten the curves
        // Aggregate the points in Javascript arrays grouped by X coordinate
        return plot.Curves.Count > 0
                    ? plot.Curves.
                            SelectMany(c => c.Points). // <- Sorry Curves but you must be Flattened
                            Aggregate(JS.Array(), (array, p) => // <- Build the Javascript array of arrays
                            {
                                // Retrieve the Javascript array with the 
                                // same X coordinate from the accumulator
                                var currentArray =
                                    array.Elements.
                                        FirstOrDefault(
                                            a =>
                                            Math.Abs(((NumberExpression)
                                           ((ArrayExpression)a).Elements[0]).Value - p.X) < Epsilon)
                                    as ArrayExpression;

                                if (currentArray != null)
                                {
                                    // add the Y coordinate to the existing Javascript array
                                    currentArray.Elements.Add(p.Y);
                                }
                                else
                                {
                                    // Create a new Javascript array with X and Y coordinates
                                    array.Elements.Add(new[] { p.X, p.Y });
                                }

                                // return the accumulator
                                return array;
                            })
                    : JS.Array(JS.Array(0));
    }

    public static Expression GetOptions(Plot plot, bool showLabels, string color, int highlightCircleSize,
                                        int strokeWidth)
    {
        if (showLabels && plot.Curves.Count > 0)
        {
            // Project the curves' labels into a javascript Expression if necessary
            ArrayExpression labels = JS.Array(plot.Curves.Select(c => c.Label));

            // insert "x" at the index 0
            labels.Elements.Insert(0, JS.String("x"));

            return JS.Object(new
                                {
                                    gridLineColor = color,
                                    highlightCircleSize,
                                    strokeWidth,
                                    legend = "always",
                                    labels
                                });
        }
        else
        {
            return JS.Object(new
                                {
                                    gridLineColor = color,
                                    highlightCircleSize,
                                    strokeWidth
                                });
        }
    }

    #endregion
}

Version 1.4 Plot'N'Roll

Models

Using the code

In this version, I updated both server side code and client side code.

Client side

In the client side, I only changed the settings box in order to add the rolling options.

<div class="settings">
    <img src="Styles/Images/Settings-icon.png" alt="" />
    <span>Options</span>
    <div class="options">
        <table>
            <tr>
                <td colspan="2">
                    <asp:CheckBox ID="CheckBoxLabels" runat="server" Text="Show labels" Checked="true" />
                </td>
            </tr>
            <tr>
                <td>
                    Grid line color
                </td>
                <td>
                    <asp:TextBox ID="TextBoxColor" runat="server" Text="FF0000" Width="50px"></asp:TextBox>
                </td>
            </tr>
            <tr>
                <td>
                    Circle size
                </td>
                <td>
                    <asp:TextBox ID="TextBoxCircleSize" runat="server" Text="4.25" Width="50px"></asp:TextBox>
                </td>
            </tr>
            <tr>
                <td>
                    Stroke width
                </td>
                <td>
                    <asp:TextBox ID="TextBoxStrokeWidth" runat="server" Text="0.75" Width="50px"></asp:TextBox>
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <asp:CheckBox ID="CheckBoxRoller" runat="server" Text="Show roller" Checked="True" />
                </td>
            </tr>
            <tr>
                <td>
                    Rolling Period
                </td>
                <td>
                    <asp:TextBox ID="TextBoxRoller" runat="server" Text="14" Width="50px"></asp:TextBox>
                </td>
            </tr>
        </table>
    </div>
</div>
Server side

I added the ylabel for the plots.

<plots>
  <plot id="1" dataUrl="App_Data/plot1.xml" thumbnailUrl="Images/plot1.png" title="Empty plot" ylabel="" />
  <plot id="2" dataUrl="App_Data/plot2.xml" thumbnailUrl="Images/plot2.png" title="Daily Temperatures in New York"
        ylabel="Temperature (F)" />
  <plot id="3" dataUrl="App_Data/plot3.xml" thumbnailUrl="Images/plot3.png" title="Daily Temperatures in San Francisco"
        ylabel="Temperature (F)" />
  <plot id="4" dataUrl="App_Data/plot4.xml" thumbnailUrl="Images/plot4.png"
        title="Daily Temperatures in New York vs. San Francisco" ylabel="Temperature (F)" />
</plots>

I also added the support of custom bars. Thus I modified the XML format of the datasource.

This is a sample of the new format. This sample contains only some lines. But, in order to use dense data sets there are thousands of lines in the XML files in the App_Data folder.

<plot id="2">
  <curves>
    <curve id="1">
      <points>
        <point x="1/1/2007" y="51" ymin="46" ymax="56" />
        <point x="1/2/2007" y="48" ymin="43" ymax="52" />
        <point x="1/3/2007" y="46" ymin="39" ymax="53" />
        <point x="1/4/2007" y="51" ymin="44" ymax="58" />
        <point x="1/5/2007" y="57" ymin="51" ymax="62" />
        <point x="1/6/2007" y="64" ymin="55" ymax="72" />
        <point x="1/7/2007" y="51" ymin="46" ymax="56" />
        <point x="1/8/2007" y="49" ymin="40" ymax="57" />
        <point x="1/9/2007" y="41" ymin="37" ymax="45" />
        <point x="1/10/2007" y="35" ymin="31" ymax="38" />
        <point x="1/11/2007" y="35" ymin="29" ymax="41" />
        <point x="1/12/2007" y="45" ymin="39" ymax="50" />
      </points>
      <label>NY</label>
    </curve>
  </curves>
  <description><![CDATA[Daily Temperatures in New York.]]></description>
</plot>

Thus, I updated the Point class in order to use a DateTime for X coordinates and to add the YMin and YMax coordinates.

public class Point
{
    #region Constructors and Destructors

    public Point(DateTime x, float y)
    {
        X = x;
        Y = y;
    }

    public Point(DateTime x, float y, float ymin, float ymax)
        : this(x, y)
    {
        YMin = ymin;
        YMax = ymax;
    }

    #endregion

    #region Public Properties

    public DateTime X { get; private set; }
    public float Y { get; private set; }
    public float YMin { get; private set; }
    public float YMax { get; private set; }

    #endregion
}

I updated the Plot class in order to load the ylabel, to load the X coordiates as DateTime, and the custom bars.

public class Plot
{
    #region Constructors and Destructors

    public Plot(int id, string title, string ylabel, string dataPath, string thumbnailPath, bool isRelativePath)
    {
        Id = id;
        Title = title;
        YLabel = ylabel;
        DataPath = dataPath;
        ThumbnailPath = thumbnailPath;
        IsRelativePath = isRelativePath;
        Description = string.Empty;
        Curves = new List<Curve>();
        IsLoaded = false;
    }

    #endregion

    #region Public Properties

    public IList<Curve> Curves { get; private set; }
    public string DataPath { get; private set; }
    public string Description { get; private set; }
    public int Id { get; private set; }
    public bool IsLoaded { get; private set; }
    public bool IsRelativePath { get; private set; }
    public string ThumbnailPath { get; private set; }
    public string Title { get; private set; }
    public string YLabel { get; private set; }

    #endregion

    #region Public Methods

    public void Load()
    {
        // If HttpContext is null and paths are relative, Throw an exception
        if (IsRelativePath && HttpContext.Current == null)
        {
            throw new Exception("HttpContext is null, you cannot use relative paths.");
        }

        // load the curves from the XML file
        XDocument data =
            XDocument.Load(IsRelativePath ? HttpContext.Current.Server.MapPath(DataPath) : DataPath);

        // Initialize the Curves collection
        Curves = new List<Curve>();

        // Set current thread culture to en-US for float parsing
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

        // Retrieve the plot current plot element from the XML file
        XElement xplot = data.Element("plot");

        // Retrieve the current Description<
        Description = xplot.Element("description").Value;

        // Retrieve the current curves
        IEnumerable<XElement> xcurves = xplot.Descendants("curve");

        // Enumerate curves
        // Build a the Curve object 
        // and Add it to the current Curves collection
        foreach (XElement xcurve in xcurves)
        {
            // Retrive the label
            string label = xcurve.Element("label").Value;

            // Initialize a new Set
            // Retrieve the points from the XML file
            IEnumerable<XElement> xpoints = xcurve.Descendants("point");

            // Enumerate the points
            // Parse and add their values to a new Point object
            // Build the Set
            List<Point> set = (from xpoint in xpoints
                                let x = DateTime.Parse(xpoint.Attribute("x").Value)
                                let ymin = Single.Parse(xpoint.Attribute("ymin").Value)
                                let y = Single.Parse(xpoint.Attribute("y").Value)
                                let ymax = Single.Parse(xpoint.Attribute("ymax").Value)
                                select new Point(x, y, ymin, ymax)).ToList();

            // Add the curve to the Curves collection
            Curves.Add(new Curve(label, set));
        }

        // Set data as loaded
        IsLoaded = true;
    }

    #endregion
}

Then in the code-behind of the Default.aspx page, I only changed the Plot method in order to take the rolling options and to parse the circle size and stroke width as decimal values.

private void Plot(Plot plot)
{
    // Load the plot lazily
    if (!plot.IsLoaded)
    {
        plot.Load();
    }

    // Set current thread culture to en-US for float and date parsing
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

    // Encapsulate curves into jQuery code by using Adam.JSGenerator
    // Only 1 code statements (v1.1) instead of 23 code statements (v1)
    // v1.2 create the actual plot by calling the $.jqplot plugin
    // v1.3 dygraphs & style options
    string script = JS.New(JS.Expression("Dygraph"),
                            JS.Expression("document").Dot("getElementById").Call(main_view.ID),
                            PlotHelper.GetCurves(plot),
                            PlotHelper.GetOptions(plot,
                                                    CheckBoxLabels.Checked,
                                                    "#" + TextBoxColor.Text,
                                                    decimal.Parse(TextBoxCircleSize.Text),
                                                    decimal.Parse(TextBoxStrokeWidth.Text),
                                                    CheckBoxRoller.Checked,
                                                    int.Parse(TextBoxRoller.Text))).ToString();

    // Get the type of the current Page
    Type pageType = GetType();

    // Initialize a unique plot Identifier for the current ClientScriptManager 
    string id = "plot" + plot.Id;

    //Determines if MsAjax is available in this Web application
    bool isMsAjax = ScriptManager.GetCurrent(this) != null;

    if (isMsAjax)
    {
        ScriptManager.RegisterClientScriptBlock(this, pageType, id, script, true);
    }
    else
    {
        // Get a ClientScriptManager reference from the current Page
        ClientScriptManager csm = ClientScript;

        // Check to see if the startup script is already registered
        if (!csm.IsStartupScriptRegistered(pageType, id))
        {
            // Register the startup script with the current Page object using its type,
            // the unique plot id, the jQuery code, and indicate to add script tags
            csm.RegisterStartupScript(pageType, id, script, true);
        }
    }

    // Bind the description view
    // If the description is empty, 
    // Display "No description available."
    // Else Display the current description
    LabelDescription.Text = string.IsNullOrEmpty(plot.Description)
                                ? "No description available."
                                : plot.Description;
}

I also updated the GetCurves method in order to construct the javascript array that will be interpretted by the dygraphs library.

public static Expression GetCurves(Plot plot)
{
    // Flatten and aggregate to get the Javascript array of Javascript arrays
    // Flatten the curves
    // Aggregate the points in Javascript arrays grouped by X coordinate
    int n = plot.Curves.Count;
    var points = plot.Curves.
        SelectMany(c => c.Points). // <- Sorry Curves but you must be Flattened
        Select(p => new {X = p.X.ToString(), p.YMin, p.Y, p.YMax}). // Date time X Coordinates
        ToList();

    return points.Count > 0
                ? points.
                    Aggregate(JS.Array(),
                    (array, p) => // <- Build the Javascript array of Javascript arrays
                    {
                        // Retrieve the Javascript array with the same X coordinate from the accumulator
                        var currentArray =
                            array.Elements.
                                FirstOrDefault(
                                    a => ((StringExpression)
                                            ((CallOperationExpression)
                                            ((UnaryOperationExpression)
                                            (((ArrayExpression) a).Elements[0])).Operand).
                                                Arguments[0]).Value == p.X)
                            as ArrayExpression;

                        if (currentArray != null)
                        {
                            // add the Y coordinate to the existing Javascript array
                            currentArray.Elements.Add(JS.Array(p.YMin, p.Y, p.YMax));
                        }
                        else
                        {
                            // Create a new Javascript array with X and Y coordinates
                            // Javascript Date for X coordinate
                            // Javascript array for Y coordinates [ymin, y, ymax]
                            ArrayExpression ae = JS.Array(
                                JS.New(JS.Expression("Date"), p.X),
                                JS.Array(p.YMin, p.Y, p.YMax));

                            // If the current X coordinate count (m) is lower than the number of sets (n), 
                            // add n-m empty Javascript arrays
                            int m = points.Count(xp => xp.X == p.X);
                            if (m < n)
                            {
                                for (int i = 0; i < n - m; i++)
                                {
                                    ae.Elements.Add(JS.Array());
                                }
                            }

                            array.Elements.Add(ae);
                        }

                        // return the accumulator
                        return array;
                    })
                : JS.Array(JS.Array(0));
}

I also updated the GetOptions method in order to construct the rolling and the custom bars options that will be interpretted by the dygraphs library, and to set DateTime type for X coordinates.

public static Expression GetOptions(Plot plot, bool showLabels, string color, decimal highlightCircleSize,
                                    decimal strokeWidth, bool showRoller, int rollPeriod)
{
    if (showLabels && plot.Curves.Count > 0)
    {
        // Project the curves' labels into a javascript Expression if necessary
        ArrayExpression labels = JS.Array(plot.Curves.Select(c => c.Label));

        // insert "Date" at the index 0
        labels.Elements.Insert(0, JS.String("Date"));

        return JS.Object(new
                                {
                                    customBars = true,
                                    gridLineColor = color,
                                    highlightCircleSize,
                                    strokeWidth,
                                    legend = "always",
                                    labels,
                                    title = plot.Title,
                                    ylabel = plot.YLabel,
                                    rollPeriod = showRoller ? rollPeriod : 0,
                                    showRoller
                                });
    }

    return JS.Object(new
                            {
                                gridLineColor = color,
                                highlightCircleSize,
                                strokeWidth
                            });
}

Version 1.5

Installation 

The installation is described in the section "Introducing versions" at the begining of the article. 

Options and events

The Dygraph server control inherits from the Panel server control. Thus, All the options of the Panel server control are available in the Dygraph server control. The events of the Panel server control are also inherited. 

Why inherit from the Panel server Control?

As you can see, in all the previous version of ASP .NET Plotter, the charts are drawn in a div, a division of the HTML page. Inheriting from the Panel server Control brings to the control an area of the page where the curves are drawn. It also provides a set of useful options for rendering this area. So that's why the Dygraph control inherits from the Panel server control.

Also, all the options provided by the version 1.4 of ASP .NET Plotter and the previous versions are supported. Below the description of each option. 

Name Description Default value
Title The title of the charts Empty
CircleSizeThe circle size 4.25
StrokeWidthThe stroke width 0.75
GridLineColorThe Grid Line Color Gray
EnableErrorBandsIndicates whether error bands are enabled or not False
ShowRollerIndicates whether error bands should be enabled or not False
ShowLabelsIndicates whether curves' labels are shown or not False
YLabelY axis Label Empty
RollPeriodRoll Period 0
CurvesCollection of curves Empty Collection of DygraphCurve

 

Set up the options and the curves  

All the options can be set in designer setting box except the Curves option. The Curves option is a collection of DygraphCurve. To illustrate how this option can be set, I describe the sample jQuery.Plotter.YRandom that is available in downloads.

In the Default.aspx page, I added a DygraphControl and setted up the options in the Design View Setting box except the Curves option. The Curves option has been set in the code behind. As you can see, the Curves option is a collection of DygraphCurve objects. A DygraphCurve is defined by a Label and a collection of DygraphPoint objects. A DygraphPoint is defined by X coordinate (DateTime) and Y, YMin, YMax coordiantes (float). The YMin and YMax defines error bands and are optional.

Thereby, In the code-behind, I created a curve with fixed X coordinates and random Y coordinates.

//
// Build our random curve
//
DygraphCurve curve = new DygraphCurve();
DateTime startDate = new DateTime(2005, 01, 01);
Random random = new Random();

for (int i = 0; i < 10; i++)
{
    int y = random.Next(0, 7);
    curve.Points.Add(new DygraphPoint(startDate.AddMonths(i), y));
}

//
// Plot our random curve
//
Dygraph.Curves.Add(curve);

You will notice three things, the DygraphCurve and DygraphPoint objects are pretty similar to the Curve and Point objects used in the  previous versions. Also, It is very easy to construct the curves to draw (just like adding items in a ListBox). Finally, the Curves option can be set directly.

Samples  

The package of the version 1.5 available in downloads provides three samples. A sample with dense data sets and error bands, a sample without error bands and finally a sample with random charts.

The first two samples are similar to the samples described in the previous versions. Thus, I will only explain the new sample that I added. 

First of all, I setted up the installation of the Dygraph server control in Visual Studio then I setted up the options in the design view by dragging and dropping the server control and setting the Width, Height and Title options in the Settings Box. I also added a button in order to redraw the random charts. After all these steps, below the source code of the Web page.   

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="jQuery.Plotter.YRandom.Default" %>

<%@ Register Assembly="DygraphControl" Namespace="DygraphControl" TagPrefix="asp" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>jQuery.Plotter.YRandom</title>
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9" />
    <!--[if lt IE 9]><script src="excanvas.js" type="text/javascript"> </script><![endif]-->
    <script src="dygraph-combined.js" type="text/javascript"> </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Dygraph ID="Dygraph" runat="server" Width="600px" Height="300px" Title="Random Y Values">
        </asp:Dygraph>
        <asp:Button ID="ButtonRedraw" runat="server" Text="Redraw" OnClick="ButtonRedraw_Click" />
    </div>
    </form>
</body>
</html>

Then, I setted up a simple method that draws a random Y values. Below the source code of the method. 

protected void RandomDraw()
{
    //
    // Set current thread culture to en-US
    //
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

    //
    // Build our random curve
    //
    DygraphCurve curve = new DygraphCurve();
    DateTime startDate = new DateTime(2005, 01, 01);
    Random random = new Random();

    for (int i = 0; i < 10; i++)
    {
        int y = random.Next(0, 7);
        curve.Points.Add(new DygraphPoint(startDate.AddMonths(i), y));
    }

    //
    // Plot our random curve
    //
    Dygraph.Curves.Add(curve);
}

In this method, a new DygraphCurve without Label is created. A startDate is set to 2005/01/01 and is updated in the for loop by adding months. A random object is created in order to provide random y coordinates generation located between 0 and 7. The Points of the curve are created in the for loop. Finally, the curve is added to the Dygraph server control and is displayed on the client.

A binding occurs when the page is loaded for the first time.

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack) RandomDraw();
}

Another binding occurs when the Redraw button is clicked. 

protected void ButtonRedraw_Click(object sender, EventArgs e)
{
    RandomDraw();
}

Done. Just a simple sample to illustrate how to get in touch with the new server control.

Note. The Curves property can also be set directly. 

Using the code

This section describes how the server control has been setted up. First of all a new project of type ASP .NET Server Control was created in Visual Studio in order to make a separate assembly of the DygraphControl. Then the Dygraph class was created within this project. This class encapsulates the ASP .NET Server Control. 

The Dygraph class inherits from the Panel server control and has two defined attributes: 

[ToolboxData("<{0}:Dygraph runat="server"></{0}:Dygraph>")]
[ToolboxBitmap(typeof(Dygraph), "Dygraph.ico")]
public class Dygraph : Panel

The first attribute ToolboxData specifies the default tag generated for the control when it is dragged from the ToolBox. The second attribute specifies the icon to represent the control in the ToolBox. The icon file Dygraph.ico is persisted in the assembly ressources in order to be accessible to the method GetImage, this method gets the icon image associated with the ToolboxBitmap attribute wich in this case is Dygraph.ico.

The RenderContents method registers the Javascript code that displays the charts in the Web Page and handles the case where MS Ajax is enabled and the case where it is not. Below the source code of the method. 

protected override void RenderContents(HtmlTextWriter output)
{
    // Build the script
    string script = JS.New(JS.Expression("Dygraph"),
                        JS.Expression("document").Dot("getElementById").Call(ID),
                        DygraphHelper.GetCurves(Curves, EnableErrorBands),
                        DygraphHelper.GetOptions(Curves,
                                                    EnableErrorBands,
                                                    Title,
                                                    YLabel,
                                                    ShowLabels,
                                                    ColorTranslator.ToHtml(GridLineColor),
                                                    CircleSize,
                                                    StrokeWidth,
                                                    ShowRoller,
                                                    RollPeriod)).ToString();

    // Get the type of the current control
    Type type = this.GetType();

    // Determines whether MS Ajax is enabled in this Web Page or not
    bool isMsAjaxEnabled = ScriptManager.GetCurrent(Page) != null;

    // Script Key
    string key = Guid.NewGuid().ToString();

    // Register the script
    if (isMsAjaxEnabled)
        ScriptManager.RegisterClientScriptBlock(this, type, key, script, true);
    else
        Page.ClientScript.RegisterStartupScript(type, key, script, true);
}

The description of the Javascript generation is described in details in the previous versions.

The DygraphPoint class is very simple and described below. 

public class DygraphPoint
{
    #region Constructors

    public DygraphPoint(DateTime x, float y)
    {
        X = x;
        Y = y;
    }

    public DygraphPoint(DateTime x, float y, float ymin, float ymax)
        : this(x, y)
    {
        YMin = ymin;
        YMax = ymax;
    }

    #endregion

    #region Public Properties

    public DateTime X { get; private set; }
    public float Y { get; private set; }
    public float YMin { get; private set; }
    public float YMax { get; private set; }

    #endregion
}

The DygraphCurve class is described below. 

public class DygraphCurve
{
    #region Constructors

    public DygraphCurve()
    {
        Points = new List<DygraphPoint>();
    }

    public DygraphCurve(string label)
        : this()
    {
        Label = label;
    }

    public DygraphCurve(string label, List<DygraphPoint> points)
        : this(label)
    {
        Points = points;
    }

    #endregion

    #region Public Properties

    public string Label { get; private set; }
    public List<DygraphPoint> Points { get; private set; }

    #endregion
}

In the AssemblyInfo class, an attribute was added:

[assembly: TagPrefix("DygraphControl", "asp")] 

This attribute sets the TagPrefix of the DygraphControl.

You will notice that the Javascript library dygraph-combined.js is not persisted in the DygraphControl's Ressources. This is made by design in order to allow the end-user to update the dygraph library if a new release is available. Check out the git repository in order to stay in sync with the updates.

Also, You will notice that the dygraph-combined.js library that I am using is not minified. This is also made by design in order to have a readable source code version. For production environments, use the minified version located here.

Using another library or another Data source 

In the version 1.5, you can choose any data source. In the previous versions and the first two samples of the version 1.5, If you want to use another data source you will have to modify the Load method of the Plot class and the Plots property. If you want to use another library you will have to modify the Plot method of the Default page.

It would be very interesting to make a .NET API that handles the charts in a generic way. The API would integrate some jQuery and Javascript libraries:

And The API would offer the ability to integrate Javascript libraries by using a plugin architecture.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0

About the Author

Akram El Assas

Architect
Mediatvcom
France France

Member

Follow on Twitter Follow on Twitter


Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionShowRangeSelector? PinmemberJudeC15hrs 35mins ago 
GeneralMy vote of 5 PinmemberMonjurul Habib21:39 22 May '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas7:27 23 May '12  
GeneralMy vote of 5 PinmemberMohammad A Rahman17:37 22 May '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas7:27 23 May '12  
GeneralMy vote of 5 Pinmembermember6018:55 18 May '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas9:06 19 May '12  
GeneralDevoted to your Article Pinmemberonurag198:40 18 May '12  
GeneralRe: Devoted to your Article PinmemberAkram El Assas13:32 18 May '12  
GeneralMy vote of 5 Pinmemberonurag198:39 18 May '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas13:31 18 May '12  
GeneralMy vote of 5 Pinmembernewton.saber2:56 18 May '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas13:30 18 May '12  
GeneralMy vote of 5 PinmemberSperneder Patrick0:31 18 May '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas13:30 18 May '12  
QuestionMy Vote of 1 PinPopularmemberGary Noter13:22 9 Mar '12  
AnswerRe: My Vote of 1 PinmemberAkram El Assas6:45 14 Apr '12  
GeneralMy vote of 5 PinmemberWonde Tadesse12:21 28 Feb '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas12:42 3 Mar '12  
GeneralMy vote of 5 PinmemberSantanu Tripathy22:31 12 Feb '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas8:57 13 Feb '12  
GeneralMy vote of 5 PinmemberSanjay K. Gupta17:22 12 Feb '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas8:56 13 Feb '12  
GeneralMy vote of 5 Pinmemberjim lahey20:20 24 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas11:05 25 Jan '12  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120528.1 | Last Updated 19 May 2012
Article Copyright 2011 by Akram El Assas
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid