Click here to Skip to main content
Click here to Skip to main content
Go to top

ASP .NET Plotter

, 28 Jul 2012
Rate this:
Please Sign up or sign in to vote.
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 easily integrate Javascript charting libraries in ASP .NET and build your own charting controls depending on your needs.
This is an old version of the currently published article.


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.

Starting from the version 1.5, the article brings, and shows how to build ASP .NET charting server controls based on HTML5 and Javascript libraries, multi-device compliant (pc, tablets, and smart-phones).  

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?   

Version 1.6 

  • How to add new options to the Dygraph ASP .NET server control?
  • How to handle AJAX callbacks in custom server controls?
  • How to make an ASP .NET server control initialize properly on the page startup and after AJAX callbacks?  

Version 1.7 

  • How to embed JavaScript files in the Resources of an ASP .NET server control?
  • How to get the browser's version in an ASP .NET server control through user agent?
  • How to add Meta tags in the page's header from an ASP .NET server control?
  • How to include Javascript files persisted in the user control's Ressources in the page?

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, released on December 14, 2011

In this version, I used the Javascript library Flot in order to draw charts on the client side. Flot is a Javascript plotting library for jQuery. It produces graphical plots of arbitrary datasets on-the-fly client-side. The focus is on simple usage, attractive looks and interactive features like zooming and mouse tracking. The plugin works with Internet Explorer 6+, Firefox 2.x+, Safari 3.0+, Opera 9.5+ and Konqueror 4.x+ with the HTML5 canvas tag (excanvas is used for IE < 9). 

The Javascript code is generated from the code behind with StringBuilder.Append

Version 1.1 released on Thursday, released on 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, released on 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, released on 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, released on 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 provides 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.1.5.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.

Version 1.6 Range selection and zoom animation, released on Friday, June 1, 2012 

This version provides the following options:

  • ShowRangeSelector
  • RangeSelectorHeight
  • RangeSelectorPlotStrokeColor
  • RangeSelectorPlotFillColor 
  • AnimatedZooms

These options are described in the "Version 1.6" section. The installation of the ASP .NET server control is the same as in the version 1.5. 

The screenshot below illustrates the ShowRangeSelector option

An online demo is available here regarding ShowRangeSelector and here regarding AnimatedZooms.

Version 1.7 Easy-To-Install ASP .NET Server Control, released on Sunday, June 10, 2012 

This version provides an ASP .NET Server Control of the dygraphs library that can be used within ASP .NET applications, that does not require referencing Javascript files. The options of all the versions before this one are included. All the samples were also updated.

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

  1. Download DygraphControl.1.7.zip and extract it
  2. Open the ASP .NET Web Page where you want to install it
  3. Install the control in the Visual Studio ToolBox
    3.1. Open the ToolBox



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



    3.3. Click OK



     
  4. Install and configure the control in the Web Page

    4.1. Installation: Drag and Drop the control in the Web Page and set the desired parameters (The version 1.5 and version 1.6 sections describe how to set the parameters in details through samples). 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.



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

     

The sections Version 1.5 and Version 1.6 explain 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.

Note. 

  • Ensure that the doctype directive is like this: <!DOCTYPE html>  
  • If you want to use the control in IE8, ensure that the head of the ASP page is like this: <head runat="server">

The section vesrsion 1.7 explains the changes that have been made in order to handle Javascript files including automatically and smartly. Likewise, the section describes in detail the browser compatibility with all the versions of IE through nice screenshots. 

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 in this version. This were 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. In the version 1.7, the Javascript library is persisted in ressources and thus the referencing is not required, however, to update the library in the version 1.7, all you need to do is to update the Javascript file in the Dygraph server control project, this is detailed in the Version 1.7 section.

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

Version 1.6   

Installation  

The installation of the ASP .NET server control is the same as in the version 1.5.  

Options   

Five options were added in this version and are described below.

Name Description Default value
ShowRangeSelectorIndicates whether the range selector is shown or not. When activated, this option disables zooming with the cursor  False
RangeSelectorHeight The Height of the range selector 30px
RangeSelectorPlotStrokeColor The color of the RangeSelector plot stroke Yellow
RangeSelectorPlotFillColor The RangeSelector plot fill color LightYellow
AnimatedZooms Indicates whether the zoom animation is enabled or not. This option must be disabled if ShowRangeSelector is enabledFalse

 

Using the code

First of all, the new options were tested on an HTML5 page (jQuery.Plotter.Test project), then they were added to the Dygraph server control as follows:

[Bindable(true)]
[Description("Indicates whether the range selector is shown or not. False by default. When activated this option disables zooming with the cursor.")]
public bool ShowRangeSelector
{
    get
    {
        return _showRangeSelector;
    }
    set
    {
        _showRangeSelector = value;
    }
}

[Bindable(true)]
[Description("The Height of the range selector. 30 pixels by default. ")]
public int RangeSelectorHeight
{
    get
    {
        return _rangeSelectorHeight;
    }
    set
    {
        _rangeSelectorHeight = value;
    }
}

[Bindable(true)]
[Description("The color of the RangeSelector plot stroke. Yellow by default.")]
public Color RangeSelectorPlotStrokeColor
{
    get
    {
        return _rangeSelectorPlotStrokeColor;
    }
    set
    {
        _rangeSelectorPlotStrokeColor = value;
    }
}

[Bindable(true)]
[Description("the RangeSelector plot fill color. LightYellow by default.")]
public Color RangeSelectorPlotFillColor
{
    get
    {
        return _rangeSelectorPlotFillColor;
    }
    set
    {
        _rangeSelectorPlotFillColor = value;
    }
}

[Bindable(true)]
[Description("Indicates whether the zoom animation is enabled or not. False by default.")]
public bool AnimatedZooms
{
    get
    {
        return _animatedZooms;
    }
    set
    {
        _animatedZooms = value;
    }
}

The default values were set in the constructor of the Dygraph control as follows:

public Dygraph()
{
    _title = string.Empty;
    _circleSize = 4.25m;
    _strokeWidth = 0.75m;
    _gridLineColor = Color.Gray;
    _showRoller = false;
    _showLabels = false;
    _enableErrorBands = false;
    _yLabel = string.Empty;
    _rollPeriod = 0;
    _showRangeSelector = false;
    _rangeSelectorHeight = 30;
    _rangeSelectorPlotStrokeColor = Color.Yellow;
    _rangeSelectorPlotFillColor = Color.LightYellow;
    _animatedZooms = false;
    _curves = new List<DygraphCurve>();
}

Then, the DygraphHelper.GetOptions method was updated in order to take in consideration these new options. This method returns a Javascript Object that is passed to the Dygraph Javascript Object. Thus, this method was updated as follows:

public static Expression GetOptions(List<DygraphCurve> curves,
                                    bool customBars,
                                    string title,
                                    string ylabel,
                                    bool showLabels,
                                    string gridLineColor,
                                    decimal highlightCircleSize,
                                    decimal strokeWidth,
                                    bool showRoller,
                                    int rollPeriod,
                                    bool showRangeSelector,
                                    int rangeSelectorHeight,
                                    string rangeSelectorPlotStrokeColor,
                                    string rangeSelectorPlotFillColor,
                                    bool animatedZooms)
{
    if (showLabels)
    {
        // Project the curves' labels into a javascript Expression if necessary
        ArrayExpression labels = JS.Array(curves.Select(c => c.Label));

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

        return JS.Object(new
        {
            customBars,
            gridLineColor,
            highlightCircleSize,
            strokeWidth,
            title,
            ylabel,
            rollPeriod = showRoller ? rollPeriod : 0,
            showRoller,
            labels,
            legend = "always",
            showRangeSelector,
            rangeSelectorHeight,
            rangeSelectorPlotStrokeColor,
            rangeSelectorPlotFillColor,
            animatedZooms
        });
    }

    return JS.Object(new
                            {
                                customBars,
                                gridLineColor,
                                highlightCircleSize,
                                strokeWidth,
                                title,
                                ylabel,
                                rollPeriod = showRoller ? rollPeriod : 0,
                                showRangeSelector,
                                rangeSelectorHeight,
                                rangeSelectorPlotStrokeColor,
                                rangeSelectorPlotFillColor,
                                animatedZooms
                            });
}

Then the overriden method RenderContents was updated in order to pass the values of the new options of the Dygraph ASP .NET server control to the Dygraph Javascript object. 

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,
                                                    ShowRangeSelector,
                                                    RangeSelectorHeight,
                                                    ColorTranslator.ToHtml(RangeSelectorPlotStrokeColor),
                                                    ColorTranslator.ToHtml(RangeSelectorPlotFillColor),
                                                    AnimatedZooms)).ToString();

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

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

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

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

In order to handle all the cases where the Dygraph server control is embedded within an UpdatePanel server control, scriptManager.IsInAsyncPostBack was used in order to indicate whether the current postback is being executed in partial-rendering mode. Thus the  Dygraph Javascript object is registered as startup script with ScriptManager.RegisterClientScriptBlock only when the current postback is being executed in partial-rendering mode, otherwise it is registered with Page.ClientScript.RegisterStartupScript. With this change the Dygraph server control initialize properly on the page startup and after the AJAX callbacks. 

Samples  

The samples of the version 1.5 were extended to integrate these new options.

A new sample was added in order to illustrate embedding the control in the UpdatePanel. This sample is based on the random charts sample of the version 1.5. The only thing that changed is the embedding in the UpdatePanel server control. Below the client-side code.

<asp:ScriptManager ID="ScriptManager" runat="server">
</asp:ScriptManager>
<div>
    <asp:UpdatePanel ID="UpdatePanel" runat="server">
        <ContentTemplate>
            <asp:Dygraph ID="Dygraph" runat="server" Width="600px" Height="300px" Title="Random Y Values"
                ShowRangeSelector="true">
            </asp:Dygraph>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="ButtonRedraw" EventName="Click" />
        </Triggers>
    </asp:UpdatePanel>
    <asp:Button ID="ButtonRedraw" runat="server" Text="Redraw" OnClick="ButtonRedraw_Click" />
</div>

The server-side code didn't change, and thus is the same as in the version 1.5.

Version 1.7

Installation 

The installation is described in this section.

Embedding JavaScript files in Resources

In order to make the server control easy to install, the minified versions of dygraph-combined.js and excanvas.js are now embedded in the ressources.

To do so, first of all the dygraph-combined.js and excanvas.js files were added in the Dygraph server control project in Visual Studio:

Then, the build action of the dygraph-combined.js file was set as an embedded resource: 

   

Then, the build action of the excanvas.js file was set as an embedded resource:

Access the JavaScript files in the embedded resources

To access the Javascript files in the embedded ressouces, the WebRessources were declared in the AssemblyInfo.cs of the Dygraph control as follows:

// dygraph-combined.js
[assembly: WebResource("DygraphControl.dygraph-combined.js", "text/javascript")]
// excanvas.js
[assembly: WebResource("DygraphControl.excanvas.js", "text/javascript")]

This declaration can also be made directly in the Dygraph class.

This allow us to make the files dygraph-combined.js and excanvas.js accessible through the WebResource.axd HTTP handler. 

Including the Javascript files

I created a helper method that includes the Javascript files persisted in ressources in the page where the Dygraph control is referenced.

Let's dig into it: 

private void RegisterClientScriptInclude(string key, Type type, string resourceName)
{
    if (!Page.ClientScript.IsClientScriptIncludeRegistered(key))
    {
        Page.ClientScript.RegisterClientScriptInclude(key,
                Page.ClientScript.GetWebResourceUrl(type, resourceName));
    }
}

This helper method takes the following parameters as input:

  • key: the key that identifies the current including
  • type: the type of the ressource 
  • resourceName: the fully qualified name of the resource in the assembly  

This method registers lazily the client script include with the page object using the type, the key, and the ressource URL.

Thus, two fields were added in the Dygraph control for the script include keys as follows:

private const string ScriptIncludeDygraphKey = "dygraph";
private const string ScriptIncludeExcanvasKey = "excanvas";

Then, on PreRender event, the Javascript files are included as follows:

protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);

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

    // Get the UserAgent
    string userAgent = Page.Request.UserAgent;

    // include dygraph-combined.js
    RegisterClientScriptInclude(ScriptIncludeDygraphKey, type, "DygraphControl.dygraph-combined.js");

    // Handle IE case
    if (userAgent.Contains("MSIE"))
    {
        // IE8 renders the webpage in IE7's Standards mode while IE9 renders the webpage 
        // in IE9’s Standards mode
        if (Page.Header != null)
            // runat="server" is necessary in the header (<head runat="server">)
            // otherwise, dygraph won't render properly in IE8
            Page.Header.Controls.Add(new HtmlMeta() { HttpEquiv = "X-UA-Compatible", 
                                                      Content = "IE=EmulateIE7; IE=EmulateIE9" });

        // excanvas.js is required only for IE versions below 9
        if (userAgent.Contains("MSIE 8") || userAgent.Contains("MSIE 7"))
            RegisterClientScriptInclude(ScriptIncludeExcanvasKey, type, "DygraphControl.excanvas.js");
    }
}

As you can see in the code of the method above, excanvas.js is included only when for the versions 8 and 7 of IE, because these versions does not support the HTML5 canvas, this is called Graceful fallback, another library called modernizr or others can be used for this purpose.

To understand how this code works, lets dig into IE case. We will see how the control behaves on the versions of IE9, IE8 and IE7 through the sample application jQuery.Plotter.YRandom that is available in jQuery.Plotter.v1.7.zip.

Let's start with the simple case: IE9  

When the application is opened under IE9, we'll get the following façade:

Under IE9, you can press F12 to get the developer tool bar. The navigator mode and the document mode are highlighted in yellow.

If we see the source code of this page, we will get something as follows:

All the things surrounded by yellow rectangles are generated automatically and dynamically by the server control.

Let's start with the first rectangle. The Meta tag is generated automatically only for IE browsers, If you open the application under a browser different from IE (Chrome, Opera, FireFox, Dolphin for instance) the Meta tag is not added. As explained in the version 1.3 section, this tag forces IE8 to  render the webpage in IE7's Standards mode while IE9 renders the webpage in IE9’s Standards mode.

The second rectangle is our dygraph-combined.js accessible through the WebResource.axd HTTP handler.

And finally the last rectangle, is the one that creates the Dygraph Javascript object on the client side. The one that lets you have these nice charts.

IE8  

When the application is opened under IE8, we'll get the following façade:


The navigator mode and the document mode are highlighted in yellow.

If we see the source code of this page, we will get something as follows:

All the things surrounded by yellow rectangles are generated automatically and dynamically by the server control.

For the first rectangle. Same explanation as in IE9 section.

The second rectangle is our dygraph-combined.js accessible through the WebResource.axd HTTP handler and excanvas.js that is also accessible through the WebResource.axd HTTP handler. We can notice that  excanvas.js was added for IE8 and not for IE9.

And finally the last rectangle, is the one that creates the Dygraph Javascript object on the client side.

IE7

When the application is opened under IE7, we'll get the following façade:


The navigator mode and the document mode are highlighted in yellow.

If we see the source code of the page, we will get something as follows:

All the things surrounded by yellow rectangles are generated automatically and dynamically by the server control.

For the first rectangle. Same explanation as in IE9 section.

The second rectangle is our dygraph-combined.js accessible through the WebResource.axd HTTP handler and excanvas.js that is also accessible through the WebResource.axd HTTP handler. We can notice that  excanvas.js was added for IE8 and IE7 and not for IE9.

And finally the last rectangle, is the one that creates the Dygraph Javascript object on the client side.

Also, all the sample projects were updated in this version. If you want to update the version of the dygraph library, all you need to do is to update the file in the Dygraph control project, recompile and test.

That's it! I hope you enjoyed these little explanations on IE.

Using another library or another Data source   

In the version 1.5 and above, you can use 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.

History

 Sunday July 29, 2012: Fixed a performance issue in Dygraphs.NET, Added a project for benchmarking the dygraphs library and a project for drawing many points through the DygraphControl.

 Thursday July 12, 2012: Added ViewState suppport, MasterPage and inner MasterPage support, ValueRange Option and fixed a little bug in Dygraphs.NET.

 Saturday June 16, 2012: Dygraphs.NET released. Dygraphs.NET is an ASP .NET wrapper of the dygraphs Visualization Library. dygraphs was originally developed at Google and has found wide use on internal dashboards and servers there. Since its public release in late 2009 (MIT license), dygraphs has found many users. dygraphs is used in organizations such as NASA, Eutelsat, MongoDB, Google and much more. Dygraphs.NET provides an ASP .NET server control that produces interactive and zoomable charts of time series. It is designed to display dense data sets, enables users to explore and interpret data, and relies heavily on HTML5 and Adam.JSGenerator library. The server control supports fully the UpdatePanel server control, and initialize properly both on page startup and after AJAX callbacks. Dygraphs.NET also provides a set of sample ASP .NET applications and a CHM documentation in order to get in touch with the control. No skills required in Javascript, a developer without skills in client side scripting, can get in touch with the control easily and rapidly. The related documentation, the demo applications, and source code are available in this article. dygraphs is maintained by Dan Vanderkam, to stay in sync with the updates you can fork it on GitHub. Dygraphs.NET is now hosted on CodePlex.

 Sunday June 10, 2012: Version 1.7 released. The Dygraph server control is now really easy to install. No Javascript files referencing required, no configuration in Web Pages' header required now. The server control does all these stuff by itself for you. The server control has all the Javascript files in its embedded ressources. Javascript files are included automatically and smartly, and the HTML5 canvas graceful fallback for versions of IE lower than 9 is likewise handled smartly and automatically by the control. Now, all you need to do to get nice and outstanding HTML5 charts in your ASP .NET application, that can be browsable on pc browsers, tablet browsers and smart-phone browsers, is simply to drag and drop the server control from Visual Studio tool box into your Webpage, and set the desired options. In this update is explained, how to install the new version, the new architecture of the source code in details of details of details, and some tricks and tips. The installation of the server control is described in this section.

 Wednesday June 6, 2012: The Dygraph server control now supports fully the UpdatePanel server control, and initialize properly both on page startup and after AJAX callbacks. A Sample project with the UpdatePanel server control, asynchronous callbacks and AsyncPostBackTrigger was added (version 1.6). Likewise, the section related to this version was updated.

 Friday June 1, 2012: Version 1.6 released. This version provides 5 new options focused on range selection and zoom animations in the Dygraph Server Control, and 3 sample projects. Swipe and pinch work with Dygraphs with both the iPad and the Android phones (although with Android the new Chrome browser is much more responsive than the default), here is a demo on iPad 3, it was also tested under Samsung Galaxy Nexus and Samsung Galaxy SII, you can see this for yourself here. If you want to see some nice charts, visit the official Dygraphs Blog. You can try online demos here. This version was added in response to a comment in the forum about adding the RangeSelector option, thus in addition, the article explains how this option and the new others were added in the server control.

 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.

 Sunday March 4, 2012: Version 1.4 released.

 Sunday February 12, 2012: Version 1.3 released.

 Sunday January 22, 2012: Version 1.2 released.

 Thursday January 12, 2012: Version 1.1 released.

 Sunday December 14, 2011 : Version 1.0 released.

License

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

Share

About the Author


Comments and Discussions


Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
QuestionHow to add bar chart or candlestick chart instead of line chart PinmemberQuoc Huy Ksys6-May-14 23:34 
GeneralMy vote of 5 PinmemberMihai MOGA14-Dec-12 5:15 
GeneralMy vote of 5 PinmemberMarius Bancila11-Dec-12 9:47 
GeneralMy vote of 5 PinmemberAhmed Ibrahim Assaf10-Dec-12 4:56 
GeneralMy vote of 5 PinmentorMd. Marufuzzaman9-Dec-12 1:13 
GeneralExcellent PinmemberMohammad A Rahman6-Dec-12 13:51 
GeneralMy vote of 5 PinmemberMonjurul Habib24-Nov-12 21:23 
GeneralMy vote of 5 Pinmembercsharpbd14-Nov-12 20:18 

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.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 28 Jul 2012
Article Copyright 2012 by Akram El Assas
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid