Click here to Skip to main content
Licence Apache
First Posted 13 Dec 2011
Views 38,172
Bookmarked 122 times

ASP .NET Plotter (Ajax, jQuery and HTML5)

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 custom charting user controls depending on your flavor and your needs.
   4.92 (36 votes)

1

2

3
2 votes, 5.6%
4
34 votes, 94.4%
5
4.92/5 - 36 votes
μ 4.93, σa 1.03 [?]
 

screen-cast-jQuery.Plotter.v1.3.final.PNG

ScreenCast_jQuery.v.1.2.PNG

sc6.PNG


scb6.PNG

scc9.PNG

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 custom charting user controls depending on your flavor and your needs.

Version 1.3 released on Sunday, February 12, 2012

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

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

Below the features of the dygraphs library:

  • Plots time series without using an external server or Flash
  • Supports multiple data series
  • Supports error bands around data series
  • Displays values on mouseover
  • Interactive zoom
  • Adjustable averaging period
  • Customizable click-through actions
  • Compatible with the Google Visualization API

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.

Version 1.2 released on Sunday, January 22, 2012

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

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

Version 1.1 released on Thursday, January 12, 2012

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

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

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

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

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

Can be written as:

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

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

The official announcement is available on ADAM's Blog.

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

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

sc1.PNG

sc2.PNG

sc3.PNG

sc4.PNG

sc5.PNG

sc6.PNG

sc7.PNG

sc8.PNGsc9.PNG

sc10.PNG

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.

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

scb1.PNG

scb3.PNG


scb4.PNG

scb5.PNG

scb6.PNG

scb7.PNG

scb8.PNG

scb9.PNG

scb10.PNG

scb11.PNG

scb12.PNG

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: 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 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

scc1.PNG

scc2.final.PNG

scc3.PNG

scc4.PNG

scc5.PNG

scc6.PNG

scc7.PNG

scc8.PNG

scc9.PNG

scc10.PNG

scc11.final.PNG

scc12.PNG

scc13.PNG

Using the 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: 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 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.

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 .

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.

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
}


Using another library or another Data source

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 Plots in a generic way. The API would integrate some jQuery and Javascript libraries:

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

License

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

About the Author

Akram El Assas

Architect
Mediatvcom
France France

Member

Follow on Twitter Follow on Twitter
"Every word, Every letter, Every number has an energetic equivalent. Thus, the verb becomes vibrations and this energetic message can flow in ambiant air through billions of small particles that allow networks to be constitued. Thanks to this, you have television." Bernard Woestelandt
 
"Human brain has plenty of decoders located between receptors (unconscious) and transmitters (concious).
 
The Vibrational messages, recieved in your brain (every idea that comes in your mind) are transformed into a comprehensible language. This language allows to communicate, to share, but the information is transformed because It goes through plenty of filters. That's why you're not obliged to take an information as a thruth. Check.
 
A mistaken information can flow from generation to generation and becomes thruth.
 
The Information transmited by human is called a language and let us be aware of its nature. It's only an information, It's up to us to check and modify our receptors - transmitters." Michelle-J. Noel
 


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

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 PinmemberSantanu Tripathy23:31 12 Feb '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas9:57 13 Feb '12  
GeneralMy vote of 5 PinmemberSanjay K. Gupta18:22 12 Feb '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas9:56 13 Feb '12  
GeneralMy vote of 5 Pinmemberjim lahey21:20 24 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas12:05 25 Jan '12  
QuestionSweet! PinmemberZenRaven7:51 24 Jan '12  
AnswerRe: Sweet! PinmemberAkram El Assas8:13 24 Jan '12  
GeneralMy vote of 5 Pinmvpthatraja23:04 22 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas8:25 23 Jan '12  
QuestionVote of 5 PinmemberGanesanSenthilvel1:53 22 Jan '12  
AnswerRe: Vote of 5 PinmemberAkram El Assas8:25 23 Jan '12  
GeneralMy vote of 5 PinmvpKanasz Robert13:21 19 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas13:29 20 Jan '12  
QuestionPlease not another chart article! PinmemberMember 436622011:45 15 Jan '12  
AnswerRe: Please not another chart article! PinmemberAkram El Assas22:32 15 Jan '12  
AnswerRe: Please not another chart article! PinmvpKanasz Robert13:21 19 Jan '12  
GeneralRe: Please not another chart article! PinmemberAkram El Assas14:17 21 Jan '12  
GeneralRe: Please not another chart article! PinmemberKamyar17hrs 17mins ago 
SuggestionAdditional charting library Pinmemberpebrian2716:54 12 Jan '12  
GeneralRe: Additional charting library PinmemberAkram El Assas9:50 15 Jan '12  
QuestionExcellent ! 5/5 PinmemberDavid Zenou12:23 12 Jan '12  
GeneralGreat! PinmemberAl Moje22:54 11 Jan '12  
GeneralRe: Great! PinmemberAkram El Assas1:28 12 Jan '12  
SuggestionGreat! PinmemberGroovBird22:44 11 Jan '12  

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

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120222.1 | Last Updated 13 Feb 2012
Article Copyright 2011 by Akram El Assas
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid