Saturday, May 19, 2012: TagPrefix and Script Key generation issues Fixed (version 1.5).
Friday, May 18, 2012: Random charts generation sample updated (version 1.5).
Thursday, May 17, 2012: Version 1.5 released. This version provides an ASP .NET Server Control located in a dedicated assembly that can be used within ASP .NET applications. This is the first ASP .NET Server Control that integrates the dygraphs library in ASP .NET. You will find the description of the installation, the options, the architecture of the source code, and samples, in this article. The dygraphs library is used in organizations such as NASA, Eutelsat, MongoDB, Google and much more.

Introduction
Nowadays, There are a plenty of Javascript charting libraries. Justly, this article aims to show you how you can integrate Javascript charting libraries in ASP .NET and build your own charting user controls depending on your flavor and your needs.
This article describes the architecture and the source code of the released versions. Each version has its own section. Each section describes mock-ups, client side code, server side code and some concepts.
Below the main technical concepts discussed in each version of this article and/or source code.
Version 1.0
- How to interface with the flot library from
C# code? - How to reference
Javascript code from the code behind as a startup script? - How to set the
Culture of the ASP .NET Worker Thread? - How to parse
XML data with LINQ to XML? - How to set up a view with paging through the
Repeater server control? - How to lazy load data? (
Lazy Loading Design Pattern)
Version 1.1
- How to get in touch with
Adam.JSGenerator library? - How to project
IEnumerable with LINQ?
Version 1.2
- How to interface with the
jqPlot library from C# code? - How to enable HTML5
canvas for IE7+? (Graceful fallback) - How to pass parameters to a
Javascript object from C# code?
Version 1.3
- How to interface with the
dygraphs library from C# code? - How to use the
X-UA-Compatible Meta tag? - How to add a color picker in a Wepage?
- How to reference
Javascript code from the code behind as a startup script when MS Ajax is enabled? - How to flatten an
IEnumerable? - How to aggregate an
IEnumerable?
Version 1.4
- How to lazy load and draw dense data sets?
- How to use the advanced settings of the
dygraphs library from C# code? - How to customize the
dygraphs library in order to respond to your needs?
Version 1.5
- How to set up an ASP .NET server control in a dedicated assembly?
- How to set up the icon of an ASP .NET server control?
- How to set up the
TagPrefix of an ASP .NET server control through AssemblyInfo? - How to reference
Javascript code from a server control as a startup script? - How to reference
Javascript code from a server control as a startup script when MS Ajax is enabled? - How to set up a random generator of charts?
Introducing versions
This section gives a quick overview of the released versions. Screencasts are available in this section. I strongly recommend reading this section. The full description of the versions is available after this section.
Version 1.0 released on Wednesday, December 14, 2011
In this version, I used the flot Javascript library to draw charts on the client side. The Javascript code is generated from the code behind with StringBuilder.Append.
Version 1.1 released on Thursday, January 12, 2012
In this version, I integrated Adam.JSGenerator library in order to generate the Javascript code from the code-behind in a more readable and maintainable way.
In the version 1.0, I used StringBuilder to build the Javascript code, 23 code statements were written to produce the desired Javascript code. In the version 1.1, only 1 code statement was written to produce the desired Javascript code.
Adam.JSGenerator allows you to write code that produces JavaScript
snippets in a more readable and maintainable way. Instead of having to
write tedious string concatenation code, interlined with String.Format
and StringBuilder.Append lines, you use a fluent syntax to produce an
object structure that produces the JavaScript output that you need.
Instead of writing this line of unmaintainable code (selector, color and background are strings):
return "jQuery('" + selector + "').css({color:'" + color + "';background:'" + background +"'});";
Can be written as:
return JS.JQuery(selector).Dot("css").Call(JS.Object(new {color, background}));
The library is available on NuGet[^] and on CodePlex.
The official announcement is available on ADAM's Blog.
You will find the description of the version 1.1 after the "Version 1.0" section.
Version 1.2 released on Sunday, January 22, 2012
In this version, I used jqPlot. jqPlot is a pure JavaScript plotting plugin for the jQuery library created by Chris Leonello. jqPlot renders charts using the HTML5 <canvas> tag.
You will find the description of the version 1.2 after the "Version 1.1" section.
Below a video demonstration.

Version 1.3 released on Sunday, February 12, 2012
In this version, I used dygraphs for plotting, and Ajax for partial postbacks.
dygraphs is a pure Javascript library created by Dan Vanderkam, that produces interactive and zoomable charts of time series,
open source code (MIT license). It is designed to display dense data
sets, enables users to explore and interpret data, and relies heavily on
the HTML5 <canvas> tag.
Some of the features of dygraphs:
- Supports dense data sets
- Plots time series without using an external server or Flash
- Works in Internet Explorer 7+, Firefox, Chrome, Safari and Opera
- Lightweight and responsive
- Displays values on mouseover, making interaction easily discoverable
- Supports error bands around data series
- Interactive zoom
- Displays Annotations on the chart
- Adjustable averaging period
- Can intelligently chart fractions
- Customizable click-through actions
- Compatible with the Google Visualization API
- Intelligent defaults make it easy to use
I also added the ability to customize charts style by defining the grid line color, the circle size, and the stroke width.
You will find the description of the version 1.3 after the "Version 1.2" section.
Below a video demonstration.
Version 1.4 Plot'N'Roll released on Sunday, March 4, 2012
This version aims to show the power of the dygraphs library that I used in the version 1.3 of ASP .NET Plotter.
In this version, I used dense data sets, date time for X coordinates, added rolling periods support and custom bars.
Let's have a look at this chart generated by ASP .NET Plotter.
To navigate in the chart, download the source code of this version, click on the fourth thumbnail, mouse over to highlight individual values. Click and drag to zoom. Double-click to zoom back out. Change the number and hit enter to adjust the averaging period.
Sometimes your data has asymetric uncertainty or you want to specify something else with the error bars around a point. This plot illustrates this. The point is the daily average and the bars denote
the low and high temperatures for the day.
Some things to notice:
- There's less seasonal temperature variation in San Francisco than in New York.
- The difference is about 15° F for San Francisco vs. 50° F for New York.
- The daily data (set rolling period to 1) is quite noisy and hides this conclusion.
- Using a 14-day moving average makes it clearer. A 100-day rolling period averages out nearly all the specifics from the data.
- There's a gap in the data for San Francisco, when the weather station was down (zoom into October 2007 to see it).
- The bands around each point indicate average highs and lows.
- There is a lot of data in this chart: low, average and high for each city on each day of a three year period ˜ 6000 data points in all.
The dygraphs library allows the user to explore the data and discover these facts.
You will find the description of the version 1.4 after the "Version 1.3" section.
Below a video demonstration.
Version 1.5 ASP .NET Server Control released on Thursday, May 17, 2012
This version provide an ASP .NET Server Control of the
dygraphs library that can be used within ASP .NET applications.
To install the ASP .NET Server Control within an ASP .NET Application, you will need to follow these steps:
- Download DygraphControl.zip
- Extract it
- Open the ASP .NET Web Page where you want to install it
- Add the required Javascript files in the Web Application and reference them
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9" />
<!---->
<script src="dygraph-combined.js" type="text/javascript"> </script>
- 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

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

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

The section "Version 1.5" explains how to get started with the control, the different options, samples and the source code. If you want only to use the server control, you can skip the description of the previous versions and jump directly to the "Version 1.5" section. However, I strongly recommend reading the previous versions.
Browser compatibilty
All the versions available have been
tested on IE7, IE8, IE9, Firefox, Chrome, Safari and Opera, and are
browser compatible with IE7, IE8, IE9, Firefox, Chrome, Safari and
Opera.
IE9 includes native support for the HTML5 canvas element. But IE8 and IE7 don't support it. Thus, I use excancas if the IE browser version is lower than IE9.
The directive <!DOCTYPE html> at the top of the Default.aspx page is very important to ensure that the page will be rendered properly.
Versions
Version 1.0
Models
Using the code
I used the flot library for plotting charts on client side and XML files
to store plots informations. You can of course use another library or
another data source. I'll explain at the end of this article how to do
that.
The plots are stored in an XML file.
<plots>
<plot id="1" dataUrl="App_Data/plot1.xml" thumbnailUrl="Images/plot1.png" title="Empty plot" />
<plot id="2" dataUrl="App_Data/plot2.xml" thumbnailUrl="Images/plot2.png" title="One curve" />
<plot id="3" dataUrl="App_Data/plot3.xml" thumbnailUrl="Images/plot3.png" title="Two curves" />
<plot id="4" dataUrl="App_Data/plot4.xml" thumbnailUrl="Images/plot4.png" title="Three curves"/>
<plot id="5" dataUrl="App_Data/plot5.xml" thumbnailUrl="Images/plot5.png" title="Four curves" />
</plots>
A plot is defined by an Identifier, an XML file that contains curves informations, a thumbnail and a title.
The curves informations are stored in an XML file.
<plot id="2">
<curves>
<curve id="1">
<points>
<point x="1999" y="3.0" />
<point x="2000" y="3.9" />
<point x="2001" y="2.0" />
<point x="2002" y="1.2" />
<point x="2003" y="1.3" />
<point x="2004" y="2.5" />
<point x="2005" y="2.0" />
<point x="2006" y="3.1" />
<point x="2007" y="2.9" />
<point x="2008" y="0.9" />
</points>
<label>Europe (EU27)</label>
</curve>
</curves>
<description>
<![CDATA[]]>
</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.
I strongly recommend reading this code and its comments.
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: 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)
{
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
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>
-->
<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: 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)
{
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
}
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[]]></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 (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;
IEnumerable<XElement> xpoints = xcurve.Descendants("point");
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();
Curves.Add(new Curve(label, set));
}
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)
{
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,
decimal.Parse(TextBoxCircleSize.Text),
decimal.Parse(TextBoxStrokeWidth.Text),
CheckBoxRoller.Checked,
int.Parse(TextBoxRoller.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;
}
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)
{
int n = plot.Curves.Count;
var points = plot.Curves.
SelectMany(c => c.Points). Select(p => new {X = p.X.ToString(), p.YMin, p.Y, p.YMax}). ToList();
return points.Count > 0
? points.
Aggregate(JS.Array(),
(array, p) => {
var currentArray =
array.Elements.
FirstOrDefault(
a => ((StringExpression)
((CallOperationExpression)
((UnaryOperationExpression)
(((ArrayExpression) a).Elements[0])).Operand).
Arguments[0]).Value == p.X)
as ArrayExpression;
if (currentArray != null)
{
currentArray.Elements.Add(JS.Array(p.YMin, p.Y, p.YMax));
}
else
{
ArrayExpression ae = JS.Array(
JS.New(JS.Expression("Date"), p.X),
JS.Array(p.YMin, p.Y, p.YMax));
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 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)
{
ArrayExpression labels = JS.Array(plot.Curves.Select(c => c.Label));
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 |
CircleSize | The circle size | 4.25 |
StrokeWidth | The stroke width | 0.75 |
GridLineColor | The Grid Line Color | Gray |
EnableErrorBands | Indicates whether error bands are enabled or not | False |
ShowRoller | Indicates whether error bands should be enabled or not | False |
ShowLabels | Indicates whether curves' labels are shown or not | False |
YLabel | Y axis Label | Empty |
RollPeriod | Roll Period | 0 |
Curves | Collection 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.
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));
}
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" />
<!---->
<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()
{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
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));
}
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)
{
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();
Type type = this.GetType();
bool isMsAjaxEnabled = ScriptManager.GetCurrent(Page) != null;
string key = Guid.NewGuid().ToString();
if (isMsAjaxEnabled)
ScriptManager.RegisterClientScriptBlock(this, type, key, script, true);
else
Page.ClientScript.RegisterStartupScript(type, key, script, true);
}
The description of the Javascript generation is described in details in the previous versions.
The DygraphPoint class is very simple and described below.
public class DygraphPoint
{
#region Constructors
public DygraphPoint(DateTime x, float y)
{
X = x;
Y = y;
}
public DygraphPoint(DateTime x, float y, float ymin, float ymax)
: this(x, y)
{
YMin = ymin;
YMax = ymax;
}
#endregion
#region Public Properties
public DateTime X { get; private set; }
public float Y { get; private set; }
public float YMin { get; private set; }
public float YMax { get; private set; }
#endregion
}
The DygraphCurve class is described below.
public class DygraphCurve
{
#region Constructors
public DygraphCurve()
{
Points = new List<DygraphPoint>();
}
public DygraphCurve(string label)
: this()
{
Label = label;
}
public DygraphCurve(string label, List<DygraphPoint> points)
: this(label)
{
Points = points;
}
#endregion
#region Public Properties
public string Label { get; private set; }
public List<DygraphPoint> Points { get; private set; }
#endregion
}
In the AssemblyInfo class, an attribute was added:
[assembly: TagPrefix("DygraphControl", "asp")]
This attribute sets the TagPrefix of the DygraphControl.
You will notice that the Javascript library dygraph-combined.js is not persisted in the DygraphControl's
Ressources. This is made by design in order to allow the end-user to
update the dygraph library if a new release is available. Check out the
git repository in order to stay in sync with the updates.
Also, You will notice that the dygraph-combined.js library that I am using is not minified. This is also made by design in order to have a readable source code version. For production environments, use the minified version located here.
Using another library or another Data source
In the version 1.5, you can choose any data source. In the previous versions and the first two samples of the version 1.5, If you want to use another data source you will have to modify the Load method of the Plot class and the Plots property. If you want to use another library you will have to modify the Plot method of the Default page.
It would be very interesting to make a .NET API that handles the charts in a generic way. The API would integrate some jQuery and Javascript libraries:
And The API would offer the ability to integrate
Javascript libraries by using a plugin architecture.