Adding History and Trend Lines to Our Live Twitter Chart

Graham Murray / Monday, November 7, 2011

Last time, we built a chart that used Infragistics Motion Framework™ and SignalR to display information about live Tweets per minute streamed from Twitter. Now, let’s extend the sample with a second chart that shows the data over time, along with trend lines to give a better idea of how the data is trending.

Setting up the project

First, either go through the steps from the last post, or download the sample project attached to it.

Modifying the Client

The current state of the sample is that we have one column series that is always displaying the latest values, which arrive every two seconds. It smoothly animates between the previous values and the new set of values.
This looks great, but it doesn’t give us much sense of the history of what is going on. So why don’t we add a second chart that shows the changes in the tweet rates over time?
Before we add the second chart, though, why don’t we make the first one a bar chart rather than a column chart? First we modify the chart declaration to look like this:

$("#chart1").igDataChart({
            width: "400px",
            height: "500px",
            dataSource: tweetData,
            axes: [{
                name: "yAxis",
                type: "categoryY",
                label: "label"
            }, {
                name: "xAxis",
                type: "numericX",
                minimumValue: 0,
                maximumValue: 100,
		  labelExtent: 50
            }],
            series: [{
                name: "tweetMeter",
                type: "bar",
                xAxis: "xAxis",
                yAxis: "yAxis",
                title: "Tweet Meter",
                valueMemberPath: "rate",
                transitionDuration: 1800
            }]
        });
  • We’ve turned our declaration for a categoryX axis into a declaration for a categoryY axis, with otherwise similar settings. 
  • We’ve turned our declaration for a numericY axis into a declaration for a numericX axis, with otherwise similar settings.
  • We’ve changed the type property of the series to bar.
  • We’ve squeezed the width of our chart a bit so that we can put the second chart side by side.

The one other thing we need to do is modify the code that is updating the axis range to talk to the x axis instead:

if (maxRate > currMax) {
                currMax += 100;
                $('#chart1').igDataChart("option", "axes", [{ name: "xAxis", maximumValue: currMax}]);
            } else if (currMax - maxRate > 100) {
                maxOutOfDate++;
                if (maxOutOfDate > 5) {
                    maxOutOfDate = 0;
                    currMax -= 100;
                    $('#chart1').igDataChart("option", "axes", [{ name: "xAxis", maximumValue: currMax}]);
                }
            }

Here, the only change from last time is to direct the changes to maximum value at “xAxis” rather than “yAxis”. By the way, we are manually modifying the axis range here to minimize the number of axis range changes that occur, otherwise the range would change after every new update we made to the chart! In addition, the chart currently disables animation when the range of the axis changes. The code above, though, will keep the range from fluctuating around too much as the values of the items increase and decrease.
And that’s it! If we run that we get this:

Adding the History Chart

Now, let’s add the second chart, which will track the historic values.
First, add a second div under the first with the id chart2:

<div id="chart2"></div>

Under that, add a second div for its legend:

<div id="legend"></div>

Next we will define the chart using the chart's jQuery Widget API, you can insert this under the declaration of chart1 in the javascript

$("#chart2").igDataChart({
            width: "500px",
            height: "500px",
            dataSource: tweetHistoryData,
	     legend: { element: "legend"},
            axes: [{
                name: "xAxis",
                type: "categoryX",
                label: "label",
                labelExtent: 50
            }, {
                name: "yAxis",
                type: "numericY",
                minimumValue: 0,
                maximumValue: 100
            }]
        });

Notice we aren’t defining any series to begin with, we will add those as keywords are added and removed. We also need to add an array to store our history data to the top of the script block:

, tweetHistoryData = [];

Now let’s add this style block to put the charts side by side:

<style>
    #chart1
    {  
        float:left;
    }
    #chart2
    {
        float:left;
        padding-left: 10px;
    }
    #legend
    {
        float: left;
        padding-left: 10px;
    }
</style>

Now, we have to make act of adding and removing keywords modify the series in the second chart.
Under this line in the addKeyword handler:

$("#chart1").igDataChart("notifyInsertItem", "tweetMeter", tweetData.length - 1, tweetData[tweetData.length - 1]);

Add this:

$("#chart2").igDataChart("option", "series", [{
                name: $("#keyword").val().toLowerCase(),
                title: $("#keyword").val(),
                type: "line",
                xAxis: "xAxis",
                yAxis: "yAxis",
                valueMemberPath: $("#keyword").val().toLowerCase()
            }]);

This will add a new line series to the chart that has a name equal to the keyword, a title equal to the keyword and will expect a property with the name the same as the keyword on the data items.
Under this line in the removeKeyword handler:

$("#chart1").igDataChart("notifyRemoveItem", "tweetMeter", i, removed);

Add this:

$("#chart2").igDataChart("option", "series", [{
                    name: $("#keyword").val().toLowerCase(),
                    remove: true
                }]);

This will remove a series with the same name as the keyword from the second chart when the keyword is removed.
Now we need to make the changes to our tweetMeterHub.rateUpdate method such that we insert items into tweetHistoryData with the relevant values.
First, we will be time-stamping these items so let’s change the variable declarations at the top of the method to read:

var items = rateMessage.Rates, output = "", first = true, maxRate = 0, i, key, currDate = new Date(), hours = currDate.getHours(), minutes = currDate.getMinutes(), seconds = currDate.getSeconds(), currDateString, newHistoryItem;

Let’s start adding some additional code at the bottom of the method. We’ll start with this rudimentary timestamp printing:

            if (hours > 12) {
                hours = hours - 12;
            }
            if (hours < 10) {
                hours = "0" + hours;
            }
            if (minutes < 10) {
                minutes = "0" + minutes;
            }
            if (seconds < 10) {
                seconds = "0" + seconds;
            }
            currDateString = hours + ":" + minutes + ":" + seconds;

Now, what we need to do is add the values to the new item to add to the array, and then add the item to tweetHistoryData:

     newHistoryItem = {};
            newHistoryItem.label = currDateString;
            $.each(items, function (i, item) {
                newHistoryItem[item.Keyword.toLowerCase()] = item.Rate;
            });
     tweetHistoryData.push(newHistoryItem);

Next, we need to notify the second chart that there is a new item in the array:

$("#chart2").igDataChart("notifyInsertItem", "xAxis", tweetHistoryData.length - 1, newHistoryItem);

And finally, lets only display the last 2000 values in the chart, we’ll remove old items from the head of the array and notify the chart if the count gets over 2000:

    if (tweetHistoryData.length > 2000) {
                oldHistoryItem = tweetHistoryData.shift();
                $("#chart2").igDataChart("notifyRemoveItem", "xAxis", 0, oldHistoryItem);
            }

If you want to display a longer history, simply increase that value to a larger number.
Finally, we need to add some code to adjust the y axis range for this second chart also. So under both instances of this line, earlier in the method:

$('#chart1').igDataChart("option", "axes", [{ name: "xAxis", maximumValue: currMax}]);

Add this line:

$('#chart2').igDataChart("option", "axes", [{ name: "yAxis", maximumValue: currMax}]);

This will modify the axis range of the y axis in the history chart.

Pretty cool huh?

Adding Some Trend Lines


Now, how about we add support for some trend lines to the series on the right chart. Trend lines help us analyze the shape of the data over time, and the NetAdvantage jQuery Chart supports a whole variety of them. Let’s modify the series being added in $("#addKeyword").click
And specify the following extra properties:

  trendLineType: "linearFit",
  trendLineBrush: "rgba(117,5,33,.6)",
  thickness: 3,
  trendLineThickness: 2,

Next, lets add some nicer looking styling to the lines in the chart:

<style>
    .ui-chart-palette-1
    {
        border-color: rgb(35, 128, 168);
        border-color: rgba(35, 128, 168);
        background-color: rgb(68, 172, 214);
        background-color: rgba(68, 172, 214);
    }
    
    .ui-chart-palette-2
    {
        border-color: rgb(51, 51, 51);
        border-color: rgba(51, 51, 51);
        background-color: rgb(73, 73, 73);
        background-color: rgba(73, 73, 73);
    }
    
    .ui-chart-palette-3
    {
        border-color: rgb(128, 128, 128);
        border-color: rgba(128, 128, 128);
        background-color: rgb(168, 168, 168);
        background-color: rgba(168, 168, 168);
    }
    
    .ui-chart-palette-4
    {
        border-color: rgb(24, 81, 112);
        border-color: rgba(24, 81, 112);
        background-color: rgb(33, 110, 153);
        background-color: rgba(33, 110, 153);
    }
    
    .ui-chart-palette-5
    {
        border-color: rgb(135, 153, 34);
        border-color: rgba(135, 153, 34);
        background-color: rgb(164, 186, 41);
        background-color: rgba(164, 186, 41);
    }
    
    .ui-chart-axis
    {
        border-color: #989EA3;
        background-color: #989EA3;
    }
</style>

The above defines a palette for the series of the chart. The border color represents the main color of a series and the background-color represents the outline color for a series. We can also set the colors of a series directly through the brush and outlineBrush properties.

See how the dark red lines are a linear fit of the data? You can experiment with other types of trend lines to see the different options:

  • linearFit
  • quadraticFit
  • cubicFit
  • quarticFit
  • qunticFit
  • logarithmicFit
  • exponentialFit
  • powerLawFit
  • simpleAverage
  • exponentialAverage
  • modifiedAverage
  • cumulativeAverage
  • weightedAverage

When using the average type trend lines, you can also set a trendLinePeriod to specify the period of the moving average calculation.


You can download the final project here.
-Graham