A basic SVG line chart in ASP.Net MVC using d3.js

D3 Line Chart

 

This is an example to show you how easy it is nowadays to display server-side data in a web-client (mobile or not). There are various ways to manipulate standard vector graphics (SVG) but in this sample we’ll use the amazing d3.js library which quickly produces great results and is easy to learn. On the server side we’ll use a standard ASP.Net MVC (razor) project and add an extra method in a controller which returns some random data in the form of a JSON blob;

1
2
3
4
5
6
7
8
9
10
[HttpGet]
public ActionResult DataBlob()
{
    var list = new List<object>();
    for (var i = 0; i < 50; i++)
    {
        list.Add(new{x=i,y=Rand.NextDouble()});
    }
    return Json(list,JsonRequestBehavior.AllowGet);
}

Note that you don’t need to define explicitly a class since the C# class structure would get lost in the translation to JSON anyway. Inside the JSON caller/parser of d3 things will be converted to a JavaScript object automatically.

Now the charting part. We’ll update the chart on a regular interval and this can be done with the setInterval method; it calls a method every now and then (you specify the interval). The method being called accesses the JSON data on the server and then executes a callback;

1
2
3
4
5
6
7
 setInterval(update, 5000);
 var update = function ()
{
            d3.json('http://localhost:2603/Home/DataBlob',
		//the callback
		);
};

Much like F#, you should be careful with some vars since they can be either functions or parameters. Here the update is actually a function. Now, the callback of the d3.json method should be a function with a parameter which will hold the parsed data (nope, you don’t need to explicitly parse it since it’s done internally in D3). So, after each call to the data we’ll refresh the canvas and redraw the lines representing the chart;

1
2
3
4
5
6
7
8
9
10
setInterval(update, 5000);
var update = function () {
    d3.json('http://localhost:2603/Home/DataBlob',
     function (thedata) {
         //clear the canvas 
         //draw a series of lines correpsonding to the chart/data 
         //adorn with some more labels and widgets, animation... 
     }
     );
};

Set up the canvas and define a group which will be refreshed on each pass:

1
2
3
4
5
6
7
8
9
10
11
12
  var c = d3.select('#chart');
  var svg = c.append('svg:svg').attr('width', 1024).attr('height', 400);
  var g;
  var update = function () {
            d3.json('http://localhost:2603/Home/DataBlob', function (d) {
                if (g)
                    g.remove();
                g = svg.append('svg:g')
                    .attr('stroke', 'red')
                    .attr('fill', 'transparent');
 
}};

Drawing a continuous curve is done through a path, very similar to the path object in Silverlight and the shorthand notation for paths is in fact the same.

1
2
3
4
5
 var makeline = d3.svg.line()
                    .x(function (d, i) { return d.x * 20; })
                    .y(function (d) { return 300 - d.y * 200; })
                    .interpolate('cardinal')
                    .tension(.6)

Here you should observe that this is again a functional and that the coordinates can be either values or functions. In our case, we use the (x,y) tuple handed over by the JSON service.
And so, now you can plug in this line-drawing function in the callback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  var c = d3.select('#chart');
  var svg = c.append('svg:svg').attr('width', 1024).attr('height', 400);
  var g;
  var update = function () {
            d3.json('http://localhost:2603/Home/DataBlob', function (d) {
                if (g)
                    g.remove();
                g = svg.append('svg:g')
                    .attr('stroke', 'red')
                    .attr('fill', 'transparent');
 
 g.selectAll("path")
                    .data(d)
                    .enter().append("svg:path")
                    .attr("d", makeline(d))
 
}};

This is the basic plot, which now can be adorned with additional fun. In particular, you can add some transition/animation fx to the build-up of the chart as can be seen in the full source code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>D3 line chart</title>
    <script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/d3.js")" type="text/javascript"></script>
    <script src="/Scripts/d3.js" type="text/javascript"></script>
    <script src="/Scripts/d3.layout.js" type="text/javascript"></script>
    <style type="text/css">
        #chart
        {
            border: solid 3px #234057;
            -moz-border-radius-topleft: 19px;
            -moz-border-radius-topright: 5px;
            -moz-border-radius-bottomleft: 0px;
            -moz-border-radius-bottomright: 29px;
            -webkit-border-top-left-radius: 19px;
            -webkit-border-top-right-radius: 5px;
            -webkit-border-bottom-left-radius: 0px;
            -webkit-border-bottom-right-radius: 29px;
            border-top-left-radius: 19px;
            border-top-right-radius: 5px;
            border-bottom-left-radius: 0px;
            border-bottom-right-radius: 29px;
            width: 1050px;
            background-color: steelblue;
            background: -moz-linear-gradient(10% 20% 90deg, silver, dimgray 100%);
        }
    </style>
</head>
<body>
    <div id="chart">
    </div>
    <script type="text/javascript">
        var w = 400;
        var h = 200;
        var margin = 20;
 
        var dataset = [];
        d3.range(10).forEach(function (d) { dataset.push({ x: d, y: Math.random() }) });
        var c = d3.select('#chart');
        var svg = c.append('svg:svg').attr('width', 1024).attr('height', 400);
        var g;
        var update = function () {
            d3.json('http://localhost:2603/Home/DataBlob', function (d) {
 
                var mapy = d3.scale.linear().domain([0, d3.max(d)]).range([0 + margin, h - margin]);
                var mapx = d3.scale.linear().domain([0, d.length]).range([0 + margin, w - margin])
                var makeline = d3.svg.line()
                    .x(function (d, i) { return d.x * 20; })
                    .y(function (d) { return 300 - d.y * 200; })
                    .interpolate('cardinal')
                    .tension(.6)
                //.interpolate('step-before');
                if (g)
                    g.remove();
                g = svg.append('svg:g')
                    .attr('stroke', 'red')
                    .attr('fill', 'transparent');
                g.selectAll('lines')
                    .data(d).enter()
                    .append('svg:line')
                    .attr('stroke', 'black')
                    .attr('y1', 300)
                    .attr('x1', function (d) { return d.x * 20; })
                    .attr('x2', function (d) { return d.x * 20; })
                    .attr('y2', function (d) { return 300 - d.y * 200; });
                g.selectAll('dots')
                    .data(d).enter()
                    .append('svg:circle')
                    .attr('cx', function (d) { return d.x * 20; })
                    .attr('cy', function (d) { return 300 - d.y * 200; })
                    .attr('r', 5)
                    .attr('fill', 'red');
                g.selectAll('labels')
                    .data(d).enter()
                    .append('svg:text')
                    .text(function (d) { return Math.round(d.y * 100) / 100; })
                    .attr('x', function (d) { return d.x * 20 + 10; })
                    .attr('y', 0)
                    .attr('stroke', 'white')
                    .attr('font-size', 10)
                    .attr("stroke-opacity", 0.6)
                    .transition()
                    .attr('y', function (d) { return 305 - d.y * 200; })
                    .delay(function () { return Math.round(2500 * Math.random()); })
                    .duration(2000)
                    .ease("cubic", 13, 3.945)
 
                g.selectAll("labels").transition()
                    .attr("visibility", "visible")
                //.delay(Math.random()))
                    .duration(500)
 
                g.selectAll("path")
                    .data(d)
                    .enter().append("svg:path")
                    .attr("d", makeline(d))
 
            });
        }
        //update();
        setInterval(update, 5000);
 
 
 
    </script>
</body>
</html>

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

top