DotNetCurry Logo

Using D3.js and ASP.NET SignalR for Real-time Data Visualization

Posted by: Ravi Kiran , on 9/13/2014, in Category ASP.NET
Views: 57597
Abstract: Using D3.js and ASP.NET SignalR, in this article, you will build a basic real-time graphics chart application for the modern web.

The wondrous web is evolving! It constantly changes and sometimes these changes and standards can be overwhelming enough for any web developer to ignore. In its modern incarnation, one of the hottest things that the web has delivered is the support for real-time communication. In this mode of communication, the server pushes new data to the client as soon as it is available. This communication could be in the form of a live chat, notification, in exciting apps like Twitter, Facebook and even real-time financial data. The client can then receive this data and project it to the end user the way he/she wants.

“I decided that if I could paint that flower in a huge scale, you could not ignore its beauty.” – Georgia O’ Keefe

 

Another area where the web has taken big leaps is in Data Visualization. HTML5 Canvas and Scalable Vector Graphics (SVG) are both web technologies that enable us to create rich graphics in the browser, without the need of a plugin like Flash or Silverlight. One of the several useful cases where we need graphics in the browser is to draw some interactive charts. These charts may range from simple line charts to very sophisticated 3D graphic charts.

By combining Real-time communication with Data Visualization, we can build some real cool sites that would impress our customers. In this article, we will see how to leverage ASP.NET SignalR (real-time communication) and D3 (JavaScript charting library) to build real-time charts in browsers.

 

A brief note on SignalR and D3.js

SignalR

There are several techniques to implement real-time communication in web applications like Interval Polling, Long Polling, Server Sent Events, Web Sockets etc. HTML 5 Web Sockets is one of the most popular techniques amongst them. In order for Web Sockets to work, they should be supported on both the client as well as the server. If any of them doesn’t support Web sockets, the communication can’t happen. In such cases, we need a polyfill that includes some fallback mechanisms. ASP.NET SignalR provides this feature for free. If we use SignalR, we don’t need to manually detect the supported technologies on the running platforms. SignalR abstracts all the plumbing logic needed to detect the feature supported by both communication ends and uses the best possible feature. SignalR checks and uses one of the following techniques in the order listed below:

  • Web Sockets: A feature of HTML5 for real-time communication using a special protocol ‘ws’
  • Server Sent Events: A feature of HTML5 for real-time communication over HTTP and HTTPS
  • Forever Frames: Adds a hidden iFrame on the page and manages communication using the frame
  • Long Polling: Client keeps on invoking the server after certain interval to check if the server has any data to be pushed to the client

To learn more about SignalR, you can refer to the following sources:


 

D3.js

As already mentioned, most of the modern browsers have good support for graphics because of features like: Canvas and SVG. JavaScript API on the browser enables us to talk to these components through code, and manipulate them as needed. When we start learning these new features, the APIs seem very nice initially, but we start getting tired of them once we get into rendering graphics beyond simple drawing. This is where we need an abstraction that wraps all default browser APIs and makes it easier to get our work done.

D3 is an abstraction over SVG. D3 makes it easier to work with SVG and provides APIs to create very rich graphics. To learn more about D3, please check the official site.

Stock Charts Application using SignalR and D3.js

We are going to create a Stock Charts application. This is a simple application that maintains details of stocks of some companies and then displays a simple line chart to the users that shows the change in stock value of a company. Values of stocks keep changing after every 10 seconds. A real-time notification is sent to all the clients with the latest value of the stock data. Charts on the client browsers are updated as soon as they receive new data from the server.

Open Visual Studio 2013 and create a new Empty Web Project named StockCharts-SignalR-D3. In the project, install the following NuGet packages:

  • EntityFramework
  • Microsoft.AspNet.SignalR
  • Microsoft.AspNet.WebApi
  • Bootstrap
  • D3

Now that we have all the required hooks, let’s start building the application

 

Building Server Side Components

Setting up database using Entity Framework

On the server side, we need Entity Framework to interact with SQL Server and Web API, SignalR to serve data to the clients. In the database, we need two tables: Companies and StockCosts. We will use Entity Framework Code First to create our database. To get started, let’s define the entity classes:

public class Company
{
    [Key]
    public int CompanyId { get; set; }
    public string CompanyName { get; set; }
}

public class StockCost
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public double Cost { get; set; }
    public DateTime Time { get; set; }

    public Company Company;
}

We need a DbContext class that defines DbSets for the above classes and tables are generated automatically for these classes when the application is executed or when the migrations are applied. Following is the context class:

public class StocksContext: DbContext
{
    public StocksContext()
        : base("stocksConn")
    {

    }

    public DbSet Companies { get; set; }
    public DbSet StockCosts { get; set; }
}

The parameter passed into the base class (DbContext) in the above snippet is the name of the connection string. Let’s add a connection string with the name specified to the Web.config file:


I am using SQL Server as my data source. You can change it to SQL CE, local DB or any source of your choice.

When a database with the above tables gets created, the tables don’t contain any data. Entity Framework migrations provide an easier way to seed the database with some default data. To enable migrations, open Package Manager Console and type the following command:

>Enable-Migrations

The above command creates a new folder named Migrations and adds a class called Configuration to the folder. The command is smart enough to detect the DbContext class and use it to create the Configuration class.

The constructor of the Configuration class contains a statement that switches off the automatic migrations. For this application, we need to enable the option. Replace the statement as:

AutomaticMigrationsEnabled = true;

The Seed method is executed every time we create the database using migrations. Seed method is the right place to seed some data to the database. One important point to remember is, by default the Seed method doesn’t check for existence of data. We need to check a condition and run the logic of seeding data, only when the database doesn’t have any data. Following is the definition of the seed method:

protected override void Seed(StocksContext context)
{
    if (!context.StockCosts.Any())
    {
        context.Companies.AddRange(new List() { 
            new Company(){CompanyName="Microsoft"},
            new Company(){CompanyName="Google"},
            new Company(){CompanyName="Apple"},
            new Company(){CompanyName="IBM"},
            new Company(){CompanyName="Samsung"}
        });
        var randomGenerator = new Random();

        for (int companyId = 1; companyId <= 5; companyId++)
        {
            double stockCost = 100 * companyId;

            for (int count = 0; count < 10; count++)
            {
                context.StockCosts.Add(new StockCost() 
                { 
                    CompanyId = companyId, 
                    Cost = stockCost, 
                    Time = DateTime.Now - new TimeSpan(0, count, 0) 
                });

                if (count % 2 == 0)
                {
                    stockCost = stockCost + randomGenerator.NextDouble();
                }
                else
                {
                    stockCost = stockCost - randomGenerator.NextDouble();
                }
            }
        }
    }
}

Open the Package Manager Console again and run the following command to create the database with the default data:

>Update-Database

Now if you open your SQL Server, you will see a new database created with the required tables and the tables containing default data.

We need repositories to interact with the data. The StockCostRepository class should include the following functionalities:

1. Add a new cost

2. Get last 20 costs for a specified company. If the number of records is less than 20, it should return all rows

3. Get cost of the last stock. This value will be used for further calculations

Following is the implemented class:

public class StockCostRepository
{
    StocksContext context;

    public StockCostRepository()
    {
        context = new StocksContext();
    }

    public List GetRecentCosts(int companyId)
    {
        var count = context.StockCosts.Count();

        if (count > 20)
        {
            var stockCostList = context.StockCosts.Where(sc => sc.CompanyId == companyId)
                .OrderByDescending(sc=>sc.Id)
                .Take(20)
                .ToList();
            stockCostList.Reverse();
            return stockCostList;
        }
        else
        {
            return context.StockCosts.Where(sc => sc.CompanyId == companyId)
                .ToList();
        }
    }

    public double GetLastStockValue(int companyId)
    {
        return context.StockCosts.Where(sc => sc.CompanyId == companyId)
                .OrderByDescending(sc => sc.Id)
                .First()
                .Cost;
    }

    public StockCost AddStockCost(StockCost newStockCost)
    {
        var addedStockCost = context.StockCosts.Add(newStockCost);
        context.SaveChanges();
        return addedStockCost;
    }

}

The company repository has the functionality to return data. Following is the implementation:

public class CompanyRepository
{
    StocksContext context;

    public CompanyRepository()
    {
        context = new StocksContext();
    }

    public List GetCompanies()
    {
        return context.Companies.ToList();
    }
}

Web API to serve Companies data

We need a Web API endpoint to expose the list of companies. Add a new folder to the project and name it Web API. Add a new Web API controller class to this folder and name it CompaniesController. The controller needs to have just one Get method to serve the list of companies. Following is the implementation:

[Route("api/companies")]
public class CompaniesController : ApiController
{
    // GET api/
    public IHttpActionResult Get()
    {
        try
        {
            return Ok(new CompanyRepository().GetCompanies());
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }
    }

}

The Route attribute added to the CompaniesController is a new feature added in Web API 2. It doesn’t work by default. We need to add the following statement to the Application_Start event of Global.asax to enable attribute routing:

GlobalConfiguration.Configure((config) =>
{
    config.MapHttpAttributeRoutes();
});

Note: To learn more about the Route attribute, read What’s New in Web API 2.0.

Adding SignalR to serve Stock data

ASP.NET SignalR has two mechanisms to talk to the clients: Hubs and Persistent connections. Persistent connections are useful to deal with the low-level communication protocols that SignalR wraps around. Hubs are one level above Persistent Connection and they provide API to communicate using data without worrying about underlying details. For the Stock charts application, we will use Hubs as the communication model.

We need a hub to send the initial stock data to the clients. Create a new folder for adding functionalities of SignalR and add a new Hub named StockCostsHub to this folder. Following is the code of StockCostsHub:

public class StockCostsHub : Hub
{
    public void GetInitialStockPrices(int companyId)
    {
        Clients.Caller.initiateChart(StockMarket.Instance.PublishInitialStockPrices(companyId));
    }
}

As we see, all Hub classes are inherited from Microsoft.AspNet.SignalR.Hub class. The Hub classes have an inherited property, Clients that holds information about all the clients connected. Following are some important properties of the Clients object:

· All: A dynamic object, used to interact with all clients currently connected to the hub

· Caller: A dynamic object, used to interact with the client that invoked a hub method

· Others: A dynamic object, used to interact with the clients other than the one that invoked a hub method

The initiateChart method called above has to be defined by the client. To this method, we are passing the list of stocks for the given company. We haven’t built the StockMarket class yet, it is our next task.

The StockMarket is a singleton class containing all the logic to be performed. It includes the following:

1. Start the market

2. Return last 20 stock prices of a company

3. Run a timer that calls a method after every 10 seconds to calculate next set of stock prices and return this data to all clients

Add a new class to the SignalR folder and name it StockMarket. Following is the initial setup needed in this class:

public class StockMarket
{
    //Singleton instance created lazily
    public static readonly Lazy market = new Lazy(() => new StockMarket());
    
    //Flag to be used as a toggler in calculations
    public static bool toggler = true;

    //A dictionary holding list of last inserted stock prices, captured to ease calculation
    public static Dictionary lastStockPrices;

    //To be used to prevent multiple insert calls to DB at the same time
    private readonly object stockInsertingLock = new Object();

    //Repositories and data
    CompanyRepository companyRepository;
    StockCostRepository stockCostRepository;
    List companies;

    //Timer to be used to run market operations automatically
    Timer _timer;

    public StockMarket()
    {
        companyRepository = new CompanyRepository();
        stockCostRepository = new StockCostRepository();
        companies = companyRepository.GetCompanies();
    }

    static StockMarket()
    {
        lastStockPrices = new Dictionary();
    }

    public static StockMarket Instance
    {
        get
        {
            return market.Value;
        }
    }
}

Once we receive the first request for the stock prices, we can start the market and trigger the timer with an interval of 10 seconds. This is done by the following method:

public IEnumerable PublishInitialStockPrices(int companyId)
{
    //Get the last 20 costs of the requested company using the repository
    var recentStockCosts = stockCostRepository.GetRecentCosts(companyId);

    //If timer is null, the market is not started
    //Following condition is true only for the first time when the application runs
    if (_timer==null )
    {
        _timer = new Timer(GenerateNextStockValue, null, 10000, 10000);
    }

    return recentStockCosts;
}

The GenerateStockValue method is responsible for the following:

  • Calculating the next stock value
  • Insert stock value to the database using StockCostRepository
  • Notify the new stock values to all the connected clients

It uses the lock object we created above to avoid any accidental parallel insertions to the database.

To get the list of currently connected clients to the StockCostHub, we need to use GlobalHost.ConnectionManager. GlobalHost is a static class that provides access to the information of the Host on which SignalR runs. ConnectionManager provides access to the current Hubs and PersistentConnections. Using the generic GetHubContext method of the connection manager, we can get the currently connected clients and then call a client method using the dynamic Clients object. Following is the implementation of GenerateNextStockValue method:

public void GenerateNextStockValue(object state)
{
    lock (stockInsertingLock)
    {
        Random randomGenerator = new Random();
        int changeInCost = randomGenerator.Next(100, 110);
        double lastStockCost, newStockCost;
        List stockCosts = new List();

        foreach (var company in companies)
        {
            if (!lastStockPrices.TryGetValue(company.CompanyId, out lastStockCost))
            {
                lastStockCost = stockCostRepository.GetLastStockValue(company.CompanyId);
            }

            if (toggler)
            {
                newStockCost = lastStockCost + randomGenerator.NextDouble();
            }
            else
            {
                newStockCost = lastStockCost - randomGenerator.NextDouble();
            }

            var newStockAdded = stockCostRepository.AddStockCost(new StockCost() { Cost = newStockCost, Time = DateTime.Now, CompanyId = company.CompanyId });
            stockCosts.Add(newStockAdded);
            lastStockPrices[company.CompanyId] = newStockCost;
        }
        toggler = !toggler;
        GetClients().All.updateNewStockCosts(stockCosts);
    }
}

public IHubConnectionContext GetClients()
{
    return GlobalHost.ConnectionManager.GetHubContext().Clients;
}

Now that all of our server side components are ready, let’s build a client to consume the server components.

Building Client Side Components

First, let’s build a simple page in which we will render the chart. The page has very small amount of mark-up, later we will add a lot of JavaScript to it. Following is the mark-up inside the body tag:

A sample chart application demonstrating usage of SignalR and D3

Companies:

The third script library file added in the above mark-up is generated dynamically by SignalR. It contains client side proxies for all the hub methods.

 

Initializing and Hooking SignalR Pieces

As soon as the page loads, we need to perform the following tasks:

  • Get the list of companies and bind them to the select control
  • Create a client hub object
  • Set SignalR client callbacks
  • Bind change event to the select control. The event callback has to invoke the SignalR server method to get the initial stock prices
var companiesSelector, hub, stockCostData, lineData, chartObj, companies;
$(function () {
    companiesSelector = $("#selCompanies");
    setCompanies().then(function () {
        initiateSignalRConnection();
    });
 
    hub = $.connection.stockCostsHub;
    setSignalRCallbacks();
 
    companiesSelector.change(function () {
        hub.server.getInitialStockPrices(companiesSelector.val());
    });
});
 
function setCompanies() {
    return $.get("api/companies").then(function (data) {
        companies = data;
        var html = "";
        $.each(data, function () {
            html = html + "";
        });
        companiesSelector.append(html);
    });
}

function setSignalRCallbacks() {
    hub.client.initiateChart = function (stocks) {
        console.log(stocks);
        stockCostData = stocks;
        transformData();
        //TODO: draw D3 chart
    };
 
    hub.client.updateNewStockCosts = function (stocks) {
        stockCostData.push(stocks[stockCostData[0].CompanyId - 1]);
        stockCostData.splice(0, 1);
        transformData();
        //TODO: redraw chart
    };
}

The initiateSignalRConnection method called above in the callback of setCompanies establishes a real-time connection with the server. It is called in the callback as it needs data returned by the Web API. Following is the implementation:

function initiateSignalRConnection() {
    $.connection.hub.start().then(function () {
        hub.server.getInitialStockPrices(companies[0].CompanyId);
    });
}

The StockCost class has a property called Time, that holds date and time when the value of the cost was changed. In the line chart, it makes more sense to show the relative time when the value was changed. Also, we don’t need all the properties returned by the server in the object to be plotted. Let’s perform a projection on the data and get a lighter version of the data that will be easier to plot. Following transformData function does this for us:

function transformData() {
    lineData = [];
    stockCostData.forEach(function (sc) {
        lineData.push({ cost: sc.Cost, timeAgo: (new Date() - new Date(sc.Time)) / 1000 });
    });
}

Drawing charts using D3.js

Now we have all the data ready on the client side to draw the line charts. Let’s define a module function that wraps all the logic needed to draw the charts and returns an object using which we can update the chart later. Following is the skeleton of the function, and the data that the function needs:

function initChart() {
    var svgElement = d3.select("#svgStockChart"),
      width = 1000,
      height = 200,
      padding = 45,
      pathClass = "path";
    var xScale, yScale, xAxisGen, yAxisGen, lineFun;

    //Logic for initializing and drawing chart
    function redrawLineChart(){
        //Logic for redrawing chart
    }

    return {
        redrawChart: redrawLineChart
    }
}

First statement in the function selects an object of the SVG DOM element on which the chart has to be drawn. We used D3’s selection API instead of jQuery, as we need a D3 object for further work. Selectors in D3 are similar to the jQuery selectors.

Let’s start with setting all parameters for the chart. It includes the following:

  1. Setting domain and range for x-axis
  2. Setting domain and range for y-axis
  3. Drawing lines for x and axes with right orientation
  4. Define a function that draws the line on the chart

Domain and range can be defined using D3’s scale API. Range is the physical co-ordinate on which the axis will reside and domain is the minimum and maximum values to be plotted on the axis. Following snippet sets domain and range for x-axis:

xScale = d3.scale.linear()
           .range([padding + 5, width - padding])
           .domain([lineData[0].timeAgo, lineData[lineData.length - 1].timeAgo]);

Now we need to define an axis generator for x-axis using the scale object created above. The generator sets the position, scale and number of ticks on the axis. We will use the above scale object in the generator of x-axis.

xAxisGen = d3.svg.axis()
  .scale(xScale)
  .ticks(lineData.length)
  .orient("bottom");

Similarly, we need to define scale and generator for y-axis. Following is the code for the same:

yScale = d3.scale.linear()
           .range([height - padding, 10])
           .domain([d3.min(lineData, function (d) {
               return d.cost - 0.2;
           }),
            d3.max(lineData, function (d) {
                return d.cost;
            })]);

yAxisGen = d3.svg.axis()
  .scale(yScale)
  .ticks(5)
  .orient("left");

The last and most important parameter to be set is a function to draw the chart. This function reads values of the co-ordinates to be plotted and also defines how to join an adjacent pair of points. Following is the function that we will use to generate a linear basis chart:

lineFun = d3.svg.line()
            .x(function (d) {
                return xScale(d.timeAgo);
            })
            .y(function (d) {
                return yScale(d.cost);
            })
            .interpolate('basis');

Now, let’s apply the above parameters on the SVG element to see the chart. At first, let’s apply both of the axes on the element. Axes on SVG are appended as a special element, g. All we need to do is, append the element g and call the corresponding axis generator defined above.

svgElement.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + (height - padding) + ")")
  .call(xAxisGen);

svgElement.append("g")
  .attr("class", "y axis")
  .attr("transform", "translate(" + padding + ",0)")
  .call(yAxisGen);

Finally, we need to append a path element using lineFun created above. We can set style properties like color, width and fill style using attributes on the path element.

svgElement.append("path")
  .attr({
      "d": lineFun(lineData),
      "stroke": "blue",
      "stroke-width": 2,
      "fill": "none",
      "class": pathClass
  });

Now, if you run the page, you will see a line chart similar to the following image:

signalr-d3js-charts

The last and final task is to update the chart when the client receives a ping back from the server with new set of stock values. Now, the parameters of the chart would change as there will be some change in the domain of the values of either axes. So we need to redraw the axes with new set of values and update the chart with new data. Following function performs all of these tasks:

function redrawLineChart() {
    setChartParameters();
    svgElement.selectAll("g.y.axis").call(yAxisGen);
    svgElement.selectAll("g.x.axis").call(xAxisGen);
    svgElement.selectAll("." + pathClass).attr({
            d: lineFun(lineData)
    });
}

Now you should be able to see the chart updating after every 10 seconds. The chart will also be updated when a new company is selected from the companies select control.

Conclusion

In this article, we saw how to leverage the rich features supported by modern browsers to build a basic real-time graphics chart application using D3 and SignalR. As we go on exploring, there are vast number of possibilities if we use these technologies creatively. Such features make the sites interactive and also help in catching attention of the users very easily. I encourage you to checkout more features of the technologies we discussed in this article, as they have numerous capabilities to build feature rich and sophisticated applications.

Download the entire source code from our GitHub Repository at bit.ly/dncm14-signalr-d3

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active blogger, an author at SitePoint and at DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Nk54 on Saturday, September 13, 2014 7:50 AM
nice article! thank you Ravi
Comment posted by Azi on Tuesday, September 23, 2014 10:58 AM
i am getting following Error: Invalid value for <g> attribute transform="NaN)" when i run your sample. any idea?
Comment posted by Prolifik Lexzy on Saturday, September 27, 2014 1:36 AM
Damn you should created a nuget package for this
Comment posted by Ravi on Sunday, September 28, 2014 5:45 AM
Azi,

Sorry for catching late. I didn't see any issues while running the sample. May I know more details about your issue?
Comment posted by Ravi on Sunday, September 28, 2014 11:15 AM
Azi,

Sorry for catching late. I didn't see any issues while running the sample. May I know more details about your issue?
Comment posted by Ahmad Ragab on Sunday, May 24, 2015 2:22 PM
Thank you this was really a lot of fun. Encouraged to learn more about SignalR, and EF.