Monday, 17 December 2012

A Real-time chart using SignalR and Flot

I have discussed about SignalR in some of my earlier posts. I lately found Flot, a jQuery plugin that makes it very easy to create charts on HTML pages without doing a lot of work. Since both SignalR and Flot are based on jQuery at the client end, they work well together. In this post, we will see how to create a chart showing price of a stock that changes every second.


On Server Side

As we are using SignalR, we need a hub class. To represent a stock value object, we need a class with a couple of properties in it. Following is the StockValue class that we will use to send data to client:
public class StockValue
{
    public int Time { get; set; }
    public double Price { get; set; }
}

For this solution, I have used Visual Studio 2012 and SignalR 1.0 RC. There are a number of changes made to SignalR in this version. Following are the changes that we will be using in this post:

Calling a client function from Server: Clients.All.functionName(data);
Calling a server function from client: proxy.server.functionName(data);
Defining a JavaScript client function: proxy.client.functionName(data);

We need a StockMarket class that does the following tasks:

  • Initializes stock values
  • Updates the stock values after certain period
  • Sends the updated data to all clients
Code of this class is as follows:
public class StockMarket
    {
        public static readonly Lazy<StockMarket> market = new Lazy<StockMarket>(() => new StockMarket());
        static List<StockValue> stockValues;
        Timer _timer;
 
        public static StockMarket Instance
        {
            get
            {
                return market.Value;
            }
        }
 
        public void InitializeStockvalues()
        {
            stockValues = new List<StockValue>()
                {
                    new StockValue(){Time=10,Price=10.4},
                    new StockValue(){Time=20,Price=10.5},
                    new StockValue(){Time=30,Price=10.2},
                    new StockValue(){Time=40,Price=10.5}
                };
            GetClients().All.drawChart(stockValues);
        }
 
        public void GenerateNextStockValue(object state)
        {
            Random randomGenerator = new Random();
            int changeInCost = randomGenerator.Next(100, 110);
 
            var lastStockValue = stockValues[stockValues.Count - 1];
            var nextStockValue = new StockValue();
            nextStockValue.Time = lastStockValue.Time + 10;
            nextStockValue.Price = changeInCost / 10.0;
 
            stockValues.Add(nextStockValue);
 
            GetClients().All.drawChart(stockValues);
        }
 
        public void PublishPrices()
        {
            if (stockValues == null)
            {
                InitializeStockvalues();
            }
            else
            {
                GenerateNextStockValue(null);
            }
            _timer = new Timer(GenerateNextStockValue, null, 1000, 1000);
        }
 
        public IHubConnectionContext GetClients()
        {
            return GlobalHost.ConnectionManager.GetHubContext<StockValuesHub>().Clients;
        }
    }

Finally, we need a Hub class to kick off the process on the server. The hub class is very simple as we have assigned most of the responsibilities to StockMarket class.
public class StockValuesHub : Hub
    {
        public void GetStockValues()
        {
            StockMarket.Instance.PublishPrices();
        }
    }


On Client Side

On the client HTML page, we need to add references to following JavaScript libraries:
  • jQuery library
  • SignalR jQuery library
  • Flot jQuery library
  • Reference to SignalR’s dynamic proxy library

Flot requires a div element to render chart. So, let’s add the following div element to our page:

<div id="canvasChart" style="height: 300px; width: 300px;"></div>

On page load, we need to invoke the GetStockValues() function to start the process.
var hub = $.connection.stockValuesHub;
var dataToChart = [];
 
$.connection.hub.start()
    .done(function () {
        hub.server.getStockValues();
     });

We need to define the drawChart function on client to consume the data sent by server and render the chart. It involves a bit of logic as Flot doesn’t operate on JSON data. We need to convert our data to a form that Flot understands.

Flot accepts values in the form of an array. In the API documentation of Flot, it is specified that, Flot accepts values in the form of an array of a series. Following is an example of the data to be passed into Flot (copied from API documentation):

[ [1, 3], [2, 14.01], [3.5, 3.14] ]

Since SignalR sends JSON data, we need to convert the data to above format before we render the chart. Each object in the collection has to be converted to an array with two elements. Following function does this job for us:

function convertToArray(data) {
    var dataArray = [];
    dataArray.push(data.Time);
    dataArray.push(data.Price);
    return dataArray;
}

Finally, let’s add the drawChart function to client proxy. Implementation of this function is pretty straight forward.
hub.client.drawChart = function (data) {
    if (dataToChart.length == 0) {
        $.each(data, function () {
            dataToChart.push(convertToArray(this));
        });
    }
    else {
        dataToChart.push(convertToArray(data[data.length - 1]));
    }
    $.plot($("#canvasChart"), [dataToChart]);
};


You can download the complete code here.

Happy Coding!

2 comments:

  1. Is resending the whole data collection and rewriting the chart each time necessary?

    It would be much better to push the new value only and update the chart, but I don't know if Flot supports it?

    Good stuff btw!

    ReplyDelete
    Replies
    1. Thanks Piotr.

      We can send the new value alone from the hub to client every time and add the new value to the collection on client side. But as you told, flot doesn't support updating the chart.

      Delete