Showing posts with label jQplot. Show all posts
Showing posts with label jQplot. Show all posts

Saturday, February 11, 2012

jqPlot: crash/hang/infinite loop in the DateAxisRenderer

Hi everyone,

This seems oddly specific. If you've tried using the DateAxisRenderer in jqPlot with a single data point, or multiple data points with the same y value, you might have found yourself stuck with a browser crash.

For example, see this question on stackoverflow or the issue tracker.

Well, the problem has a partial fix which is now in the codebase. I believe there are still some cases where there might be a problem so it's being worked on.

Until its released, you can incorporate the changes into dateAxisRenderer.js by copying them from here. 

Sunday, June 5, 2011

Labeling the top 5 results on a graph with jqPlot

Hey everyone,


As you can guess by the title, we're back with more on jqPlot! Last time I talked about jqPlot I had wanted to use tick marks in the Highlighter plugin for axes where the data was non-numerical. The whole reason of using the highlighter in the first place was because some of our graphs have large numbers of data points, so we have to hide the axes labels or it looks terrible.

A large data set (129 points). 
So we hide the axes labels and it looks better, but now you can't tell what any of the points are. We added the highlighter plugin so that at least when you hover over a data point, you see its value. That's helpful, but what if you wanted to, for example, print this graph out?

We decided to label the graph for the points with the top X (where X=5 at the moment) y-values on the graph. In the end we get this:

Labeling the top 5 results looks pretty nice. 
Now if you print it out, at least you see the most important values on the graph.

The Plugin

So in order to accomplish this I started from an existing plugin (Highlighter since that's what I'm familiar with). I'm going to explain how I changed the highlighter plugin to accomplish what I needed, but you don't have to start with it at all. There are a couple things we have to change to make it work:

  • Modify the postPlotDraw hook so that it sets up labels for the number of points we want to draw the label for.
  • Make a call to our own function that will determine which points to draw labels
  • Modify the showTooltip function so that it uses more than one label 
None of these are really hard, so let's just start with the first step.

The first thing is to add two new variables to the plugin object itself:

$.jqplot.TopResultDisplay = function(options) {
        this._tooltipElements = []; 
        this.numberOfPoints = 5; 

        /*   the rest of the options .... */ 
};

TopResultDisplay is the name of the plugin. _tooltipElements is empty right now but will contain a number of div elements that represent the labels. numberOfPoints determines how many top points we want to label.

Now we need to modify the postPlotDraw() function so that it creates those divs. It will also make a call to our function that will eventually draw the labels. After modifying it, the postPlotDraw() function looks like this:

// called within context of plot
    // create a canvas which we can draw on.
    // insert it before the eventCanvas, so eventCanvas will still capture events.
    $.jqplot.TopResultDisplay.postPlotDraw = function() {
        this.plugins.TopResultDisplay.highlightCanvas = new $.jqplot.GenericCanvas();
        
        this.eventCanvas._elem.before(this.plugins.TopResultDisplay.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions));
        this.plugins.TopResultDisplay.highlightCanvas.setContext();
        
        var p = this.plugins.TopResultDisplay;
        
        for(i =0;i<p.numberOfPoints;i++)
        {
            elem = $('<div class="jqplot-highlighter-tooltip" style="position:absolute;display:none"></div>');
            p._tooltipElements[i] = elem;
            this.eventCanvas._elem.before(elem);
        }        
        
        drawLabelsForPoints(this); 
    }; 

The changes I made start at line 10: we simply create a number of div elements and insert them on the _tooltipElements array.

Then we add a call to drawLabelsForPoints(this) which will be our label drawing function that takes in a plot object.

Now let's write that function. When I was changing the Highlighter plugin this function was originally handleMove, but I ended up changing all of the functionality. The function itself is not that exciting:

function drawLabelsForPoints(plot) 
    {
        var hl = plot.plugins.TopResultDisplay; 
        
        var sortedData = []; 
                
        for(i =0;i<plot.series[0].data.length;i++)
        {            
            var newPoint = new Object(); 
            newPoint.data = plot.series[0].data[i];
            newPoint.seriesIndex = 0; 
            newPoint.gridData = plot.series[0].gridData[i];             
                        
            sortedData.push(newPoint);                                        
        }        
        
        sortedData.sort(function(a, b) { return b.data[1] - a.data[1]; }); 
                
        for(i=0;i<hl.numberOfPoints;i++)
        {            
            showTooltip(plot, plot.series[0], sortedData[i]);
        }    
    }

First we create a new array called sortedData. Then we fill this array with all of our unsorted points, but we use the format that showTooltip of Highlighter expects. The data variable refers to the actual data array of the form [x,y]. seriesIndex is the series that the point is on, and gridData refers to the points x/y pixel coordinates on the canvas. You might notice that I'm only using one series here since all of our graphs have only one series. You could however modify it to support more than one if needed.

After filling the array, we sort in place using a function that sorts the data in descending order based on the y-value. Finally, we call showTooltip on the top results with the number of points that was specified.

The next step is to modify showTooltip to use more than one label element. To do this I changed a small part of the showTooltip function at the top to look like this:

    var hl = plot.plugins.TopResultDisplay;
        
        if(!hl.enabled) return;        
        var elem = hl._tooltipElements.splice(0, 1)[0];         
        if(!elem) return; 
        
        if (hl.useAxesFormatters) {
        /* .. . the rest of the function */

The important line is line 4, where we make the call to _tooltipElements.splice. This removes one element from the array of divs and returns it, which means that as long as it returns non-null we still have labels to use. After that the rest of the code remains the same and references the elem object to draw on.

The very last step is to remove the line

$.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
from the top of the code. This line wires up the handleMove method to be called whenever the mouse is moved over the canvas.

After that, we're done with changing the code. All that's left is to enable the plugin. We do that just as any other plugin. Below is the configuration for the plugin as we have it on our graphs page.

TopResultDisplay:
{
 tooltipAxes: 'xy',
 useAxesFormatters:true,
 tooltipLocation: 'n', 
 numberOfPoints: 5
}, 

And the results again for reference:

So that's it! If you have any questions/comments/etc. feel free to leave a comment.

Thursday, April 14, 2011

Enabling the jqPlot highlighter to use tick labels instead of tick indices.

Background

So I'm a big fan of jqPlot, which we've been using in our project for our graphing page. Right now it's coming along pretty, well, we even have Real Data now.

Real Data.
Now, one "gotcha" you have to watch out for in jqPlot is how non-numeric data points are rendered. For example, let's say we have a plot of cities versus year. Our x-axis is simple, it's just numeric year values. On the

y-axis however, we have city names. Now I'd expect a decent graphing library to be handle non-numeric axes just as well as numerical ones. It would be "good enough" to just display the data points in no particular order, but even better if you could specify a custom comparator. Right now we handle that server-side and just send a pre-sorted list to the client for jqPlot to render.

The Problem

The way jqPlot handles these text data points is a little awkward (unless I'm missing something). Instead of specifying a point such as (1986, "Boston"), you specify (1986, 0) and then have a list of tick labels, where ticks[0] = "Boston."

So a little weird, but not deal-breaking. However, if you try and use one of jqPlot's plugins called the Highlighter, things don't work so well. Highlighter enables displaying of the x/y values when you hover the mouse over a data point on the graph. Unfortunately, if your data point is (1986, 0), it will display "1986, 0" instead of "1986, Boston." Not good.

As far as I can tell, it's not possible to use highlighter to display string values instead of the tick index. So, being bored and not wanting to study for a chemistry class, I dived into the jqPlot highlighter source to change that.

The Source Code

After spending some time in the source code and experimenting through Firebug, the relevant function responsible for displaying the data point is called showTooltip. This function first sets up a list of y-values (since a series can have more than one y-value), and then adds the x-values depending on the options specified.

For example, the relevant line of code to display the y values of the data point is:
ystrs.push(yf(yfstr, neighbor.data[i]));
 Where neighbor.data[i] is the actual value of the data point, yf is a function that formats the string to be displayed, and yfstr is the "format string" which follows the conventions similar to sprintf.

How do we change this so that it will display the tick mark value instead of just the y tick index? Well, it turns out that showTooltip is passed a series object, which contains the ticks array already. The exact name of the array is series._yaxis._ticks. There is also one for the x axis.

How do we know the tick index? Our y data point will indicate it, so that the line above can become:
ystrs.push(yf(yfstr, series._yaxis._ticks[neighbor.data[1]].label));
 Where neighbor.data[1] is the first y-value. Since we don't want this to happen all the time (just for non-numeric data points), we can add a few parameters to the highlighter plugin to specify whether to enable displaying of tick labels or not. In our situation, we know when to enable it because numeric data points have no associated ticks array that's sent by the server. Some similar modifications for the x-axis give us the results we want:

jqPlot modified Highlighter plugin in action
Success!

Now you ask, why go through all this hassle? Well, our particular reason was that we can have too many data labels (such as 50 cities) so the client will cut off displaying them. This way, the user can still hover over interesting data points and see what the values are.

For anyone interested, the modified highlighter plugin is hosted on my website. The relevant Highlighter parameters to set are useXTickMarks and useYTickMarks.

Saturday, March 12, 2011

Implementing interaction between jQplot and java servlets

Today I'm going to share my experiences developing a way to let users plot graphs of certain interesting data.


Way back when, we originally were going with the Google chart API to accomplish this. It's simple and it looks good. However, we decided instead to take a jQuery approach (for learning and to avoid making a call to a second server) with jqPlot. jqPlot is a free graphing library which generates the graph on the client side and accepts data in a convenient format.

After playing around with it, we were able to develop a nice implementation:

Now aside from a few things that need to be worked out (such as fractional years), it looks good and was very simple to implement. Our approach to this using java servlets was:
  • Client sends an AJAX graph query to the GraphsController class with a parameter ("getgraph") set to true to signify a graph request
  • Servlet gets request, builds a GraphRequestBean and validates it. If validation fails the server sends back an error object using the JSON library for Java called google-gson
  • Servlet sends request to a GraphsManager, which generates the Graph by interacting with the data layer. 
  • Graph is sent as a JSON object and is read by the javascript, where a call to the plotting library is made. 
  • User gets nice looking graph
 We also added some error handling and the ability for users to regenerate the graph through a link. I'm pretty excited about this implementation (and jQuery in general...) because it worked very well. And also I have even more respect for unit tests because they saved a lot of time debugging, since I was constantly changing code around while developing.