Tuesday, June 5, 2012

jqPlot Line with asp.net MVC 3

In this post we will be exploring using jqPlot line graph with asp.net MVC 3. Before starting with MVC, let’s see how line graph works. Let’s first download the plugin from-

http://www.jqplot.com/

To start with let’s see how line graph works. First let’s include the following file references-
<link class="include" rel="stylesheet" type="text/css" href="@Url.Content("~/scripts/jqplot/css/jquery.jqplot.min.css")" />
<!--[if lt IE 9]><script language="javascript" type="text/javascript" src="@Url.Content("~/scripts/jqplot/excanvas.min.js")"></script><![endif]-->
<script type="text/javascript" src="@Url.Content("~/scripts/jqPlot/jquery.jqplot.min.js")"></script>
<script type="text/javascript" src="@Url.Content("~/scripts/jqplot/jqplot.canvasTextRenderer.min.js")"></script>
<script type="text/javascript" src="@Url.Content("~/scripts/jqplot/jqplot.canvasAxisTickRenderer.min.js")"></script>
<script type="text/javascript" src="@Url.Content("~/scripts/jqplot/jqplot.dateAxisRenderer.min.js")"></script>
@*<script type="text/javascript" src="@Url.Content("~/scripts/jqplot/jqplot.highlighter.min.js")"></script>*@
For this demo we are going to use the following CSS-
<style type="text/css">
    .jqplot-point-label
    {
        white-space: nowrap;
    }
    div.jqplot-target
    {
        height: 400px;
        width: 750px;
        margin: 70px;
    }
    .tooltipCss
    {
        position: absolute;
        background-color: #b2b1ac;
        color: White;
        z-index: 200;
        padding: 5px;
        border-radius: 5px;
        display: none;
    }
</style>
Following is the code used to activate the line plugin-
<script type="text/javascript">
    $(document).ready(function () {
        var line1 = [['2/2/2008', 10], ['2/5/2008', 56], ['2/7/2008', 39], ['2/10/2008', 81], ['2/15/2008', 10], ['2/18/2008', 56], ['2/22/2008', 39], ['2/30/2008', 81]];
        var line2 = [['2/16/2008', 43], ['2/18/2008', 45], ['2/17/2008', 50], ['2/12/2008', 40], ['2/1/2008', 10], ['2/14/2008', 56], ['2/7/2008', 39], ['2/22/2008', 81], ['2/29/2008', 81]];
        var labels = [
                   { label: 'serie name 1', lineWidth: 1 },
                   { label: 'serie name 2', lineWidth: 1 }
                ];

        var plot2 = $.jqplot('chart1', [line1, line2], {
            series: labels,
            legend: {
                show: true,
                placement: 'outsideGrid'
            },
            //                highlighter: {
            //                    show: true,
            //                    showTooltip: true,
            //                    yvalues: 2,
            //                    formatString: '<table class="jqplot-highlighter"><tr><td>Point </td><td>"%s"</td></tr><tr><td>value </td><td>"%s"</td></tr><tr><td>Series </td><td>"%s"</td></tr></table>'
            //                },
            cursor: {
                show: true,
                tooltipLocation: 'sw'
            },
            axes: {
                xaxis: {
                    tickRenderer: $.jqplot.CanvasAxisTickRenderer,
                    renderer: $.jqplot.DateAxisRenderer,
                    label: 'Date',
                    tickOptions: {
                        angle: -15,
                        formatString: '%m/%d/%y'
                    }
                },
                yaxis: {
                    label: 'Value',
                    labelRenderer: $.jqplot.CanvasAxisLabelRenderer
                }
            }
        });

        $('#chart1').bind('jqplotDataMouseOver',
                    function (ev, seriesIndex, pointIndex, data) {
                        date = new Date(data[0]);
                        $('#info2').html('series "' + labels[seriesIndex].label + '" point "' + (date.getMonth() + 1) + "/" + (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + "/" + date.getFullYear() + '"<br /> value "' + data[1] + '"');
                        $('#info2').css({ "left": ev.pageX + 3, "top": ev.pageY })
                        $('#info2').show();
                    }
                );
        $('#chart1').bind('jqplotDataUnhighlight',
                    function (ev) {
                        $('#info2').hide();
                    }
            );
    });
</script>
<div class="example-content">
    <div class="example-plot" id="chart1">
    </div>
</div>
<div id="info2" class="tooltipCss">
</div>
Here I am not going to explain the detail of the plugin. There are many options used in the plugin. Detail of various options can be found here-

http://www.jqplot.com/docs/files/jqPlotOptions-txt.html
http://www.jqplot.com/docs/files/optionsTutorial-txt.html

And we can use the options whatever serves our purpose. You can see some commented code in the plugin that is highlighter. There are many options available in the highlighter, but managing the tooltip HTML is not that great. So, I wish to use separate highlighter.

I have used the div with id info2 to hold the tooltip content. As you can, two events are attached to the plugin 'jqplotDataMouseOver' and 'jqplotDataUnhighlight' to deal with tooltip on the points on the line graph. First one is used for mouse over and the second one is used for mouse out.

If we check the input data to the plugin is nothing but an array of lines where each line is in turn array of points. Each point is intern is an array of two elements. First one is date and second is the value for that date.

So, if we want to implement using MVC is nothing but returning this data array. Also as you can see that we have used a separate array named labels to hold the names of the lines.

Model driven Line:

Let’s implement it with MVC 3. To start with let’s have the following model-
namespace Line.Models
{
    public class LineViewModel
    {
        public int id { get; set; }
        public string LineName { get; set; }
        public List<LineDataViewModel> data { get; set; } 
    }
    public class LineDataViewModel
    {
        public DateTime Date { get; set; }
        public double Value { get; set; }
    }
}
Everything is same as previous implementation except the data creation. Let’s first replace the first line of the plugin initialization by the following line-
var plot2 = $.jqplot('chart1', line, {
And also the view should accept the following model-
@model  List
Let’s now build the data array "line" and series "labels". Now gets generate some random data in action method and pass to the view like below-
        public ActionResult ModelDrivenLine()
        {
            Random r = new Random();
            List<LineViewModel> data = new List<LineViewModel>();
            
            LineViewModel l1 = new LineViewModel();
            l1.LineName = "First line";
            l1.id = 1;
            l1.data = new List<LineDataViewModel>();
            for (int i = 0; i < 10; i++)
                l1.data.Add(new LineDataViewModel() { Date = DateTime.Now.AddDays(r.Next(1, 25)), Value = r.Next(1, 10) });
            data.Add(l1);

            LineViewModel l2 = new LineViewModel();
            l2.LineName = "Second line";
            l2.id = 2;
            l2.data = new List<LineDataViewModel>();
            for (int i = 0; i < 10; i++)
                l2.data.Add(new LineDataViewModel() { Date = DateTime.Now.AddDays(r.Next(1, 25)), Value = r.Next(1, 10) });
            data.Add(l2);

            return View(data);
        }
Now we can generate the data by the following code-
        var line = new Array();
        var labels = new Array();
        @foreach (var m in Model)
        {
            <text>labels.push({label: "</text>@m.LineName<text>"}); 
            var line</text>@m.id<text> =new Array();</text>
            foreach (var l in m.data)
            {
                <text> line</text>@m.id<text>.push(["@l.Date.ToString("M/dd/yyyy")", @l.Value]);</text>
            }
            <text>line.push(line</text>@m.id<text>);</text>
        }
What we are doing is looping the model data and creating two required array. Here <text></text> is used to direct the razor engine to render the data as text. Line <text>labels.push({label: "</text>@m.LineName<text>"}); is used to create series name (the array for name of the lines(labels)). If you see the model there is a separate property as Id. This is used to uniquely identify each line. And you can see in line var line</text>@m.id<text> =new Array();</text> we are creating array for each line, so if id is one, the result of the line will be var line1 =new Array();. And with the next for loop we are pushing the line values to the corresponding array. And the next line we are adding the array to the final data array. That's all.

The result of the above code will look like-
        var line = new Array();
        var labels = new Array();
            labels.push({label: "First line"}); 
            var line1 =new Array();
                 line1.push(["6/21/2012", 2]);
                 line1.push(["6/15/2012", 8]);
                 line1.push(["6/06/2012", 9]);
                 line1.push(["6/13/2012", 2]);
                 line1.push(["6/09/2012", 2]);
                 line1.push(["6/15/2012", 8]);
                 line1.push(["6/09/2012", 5]);
                 line1.push(["6/13/2012", 8]);
                 line1.push(["6/23/2012", 5]);
                 line1.push(["6/12/2012", 1]);
            line.push(line1);
            labels.push({label: "Second line"}); 
            var line2 =new Array();
                 line2.push(["6/11/2012", 9]);
                 line2.push(["6/27/2012", 2]);
                 line2.push(["6/27/2012", 6]);
                 line2.push(["6/21/2012", 5]);
                 line2.push(["6/13/2012", 6]);
                 line2.push(["6/08/2012", 5]);
                 line2.push(["6/07/2012", 7]);
                 line2.push(["6/18/2012", 8]);
                 line2.push(["6/19/2012", 7]);
                 line2.push(["6/15/2012", 6]);
            line.push(line2);
        var plot2 = $.jqplot('chart1', line, {
Now if we want to use jQuery ajax to get data from the controller action and use it to the line graph. We can do it simply. We can have a view from the view we can do an ajax call to controller action. And the action method goes like below-
        public JsonResult AjaxLineJSON()
        {
            //This part is just like the previous action method
            return Json((from d in data
                select new {d.LineName,data= (from tl in d.data
                                         select new { Date= tl.Date.ToString("M/dd/yyyy"), Value=tl.Value}).ToList()}), JsonRequestBehavior.AllowGet);
        }
And the ajax call looks like-
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({
            type: "get",
            timeout: 30000,
            url: '@Url.Action("AjaxLineJSON")',
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            success: function (result) {
                var line = new Array();
                var series = new Array();
                $(result).each(function (i, itm) {
                    series.push(itm.LineName);
                    line[i] = new Array();
                    $(itm.data).each(function (j, item) {
                        line[i].push([item.Date, item.Value])
                    });
                });
                //initialize the graph here.
        });
    });
</script>
That is all for now. You can download the code from here.

3 comments:

  1. Hello,
    I am trying to follow your example, using Visual studio 2012.

    I get this error:
    0x800a138f - JavaScript runtime error: Unable to get property 'DateAxisRenderer' of undefined or null reference

    Any idea?

    ReplyDelete
  2. Hi,
    Check whether all the js file is getting rendered using firebug or IE developer tool.

    ReplyDelete
  3. The reason for the error could be that jqplot is not compatible with jquery > 1.8.3

    ReplyDelete