


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





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[]]>
</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 (IsRelativePath && HttpContext.Current == null)
{
throw new Exception("HttpContext is null, you cannot use relative paths.");
}
XDocument data = XDocument.Load(IsRelativePath
? HttpContext.Current.Server.MapPath(DataPath)
: DataPath);
Curves = new List<Curve>();
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
XElement xplot = data.Element("plot");
Description = xplot.Element("description").Value;
IEnumerable<XElement> xcurves = xplot.Descendants("curve");
foreach (XElement xcurve in xcurves)
{
string label = xcurve.Element("label").Value;
var xpoints = xcurve.Descendants("point");
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();
Curves.Add(new Curve(label, set));
}
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()
{
var pds = new PagedDataSource
{
DataSource = Plots,
AllowPaging = true,
PageSize = 4,
CurrentPageIndex = CurrentPage - 1
};
LabelCurrentPage.Text = "Page " + CurrentPage + " of " + pds.PageCount;
ImageButtonPrevious.Visible = !pds.IsFirstPage;
ImageButtonNext.Visible = !pds.IsLastPage;
metadata.Visible = false;
RepeaterPlots.DataSource = pds;
RepeaterPlots.DataBind();
}
The current page number is persisted in the ViewState.
protected int CurrentPage
{
get
{
object o = ViewState["CurrentPage"];
if (o == null) return 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)
{
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)
{
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)
{
CurrentPage++;
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)
{
CurrentPage--;
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"))
{
int plotId = int.Parse(e.CommandArgument.ToString());
Plot plot = Plots.First(i => i.Id == plotId);
Plot(plot);
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)
{
if (!plot.IsLoaded)
{
plot.Load();
}
var jQuery = new StringBuilder();
var jQueryPlot = new StringBuilder();
jQuery.Append("$(document).ready(function () {" + Environment.NewLine);
jQueryPlot.Append("$.plot($('#" + main_view.ID + "')," +
(plot.Curves.Count > 0
? " [ "
: "[ d ]);"));
if (plot.Curves.Count == 0)
{
jQuery.Append("var d = [];" + Environment.NewLine);
}
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
for (int i = 0; i < plot.Curves.Count; i++)
{
var c = plot.Curves[i];
string label = string.Empty;
bool labelExists = !string.IsNullOrEmpty(c.Label);
if (labelExists)
{
label = "label" + i;
jQuery.Append("var " + label + " = " + "'" + c.Label + "'" + ";" + Environment.NewLine);
}
string d = "d" + i;
jQuery.Append("var " + d + " = [");
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 (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));
}
}
jQuery.Append(jQueryPlot);
jQuery.Append("});" + Environment.NewLine);
Type pageType = GetType();
ClientScriptManager csm = ClientScript;
string id = "plot" + plot.Id;
if (!csm.IsStartupScriptRegistered(pageType, id))
{
csm.RegisterStartupScript(pageType, id, jQuery.ToString(), true);
}
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)
{
if (!plot.IsLoaded)
{
plot.Load();
}
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
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();
Type pageType = GetType();
ClientScriptManager csm = ClientScript;
string id = "plot" + plot.Id;
if (!csm.IsStartupScriptRegistered(pageType, id))
{
csm.RegisterStartupScript(pageType, id, script, true);
}
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)
{
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)
{
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>
-->
-->
</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)
{
if (!plot.IsLoaded)
{
plot.Load();
}
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
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();
Type pageType = GetType();
ClientScriptManager csm = ClientScript;
string id = "plot" + plot.Id;
if (!csm.IsStartupScriptRegistered(pageType, id))
{
csm.RegisterStartupScript(pageType, id, script, true);
}
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)
{
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)
{
Expression labels = showLabels && plot.Curves.Count > 0
? JS.Array(plot.Curves.Select(c => JS.Object(new {label = c.Label})))
: JS.Array();
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)
{
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





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>
-->
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9" />
<title>jQuery.Plotter</title>
-->
-->
<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() {
$('#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);
});
$("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)
{
if (!plot.IsLoaded)
{
plot.Load();
}
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
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();
Type pageType = GetType();
string id = "plot" + plot.Id;
bool isMsAjax = ScriptManager.GetCurrent(this) != null;
if (isMsAjax)
{
ScriptManager.RegisterClientScriptBlock(this, pageType, id, script, true);
}
else
{
ClientScriptManager csm = ClientScript;
if (!csm.IsStartupScriptRegistered(pageType, id))
{
csm.RegisterStartupScript(pageType, id, script, true);
}
}
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.
CSV data
URL
array
function
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.

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 .

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.
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)
{
return plot.Curves.Count > 0
? plot.Curves.
SelectMany(c => c.Points). Aggregate(JS.Array(), (array, p) => {
var currentArray =
array.Elements.
FirstOrDefault(
a =>
Math.Abs(((NumberExpression)
((ArrayExpression)a).Elements[0]).Value - p.X) < Epsilon)
as ArrayExpression;
if (currentArray != null)
{
currentArray.Elements.Add(p.Y);
}
else
{
array.Elements.Add(new[] { p.X, p.Y });
}
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)
{
ArrayExpression labels = JS.Array(plot.Curves.Select(c => c.Label));
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.