Sunday, 22 May 2011

Python and Javascript, using the Flot plugin, part 6

In the sixth installment of this series we look at how we can make the graph more interactive by making full use of the extensibility of the Flot plugin.

Creating interactive plots

The Flot plugin mimics most other jQueryUI plugins in that although it works perfectly well without any configuration options it also provides a number of ways to extend its functionality. Here we will see how we may attach a hovering label to any point in the graph, something that may prove useful if the graph consists of many points. An example is shown in this image (the mousepointer itself is not visible in this screenshot):

  var p = $.plot($("#tempandwind")  ,da ,{
    series: { lines: { show: true }, points: { show: true } },
    xaxis : { mode:"time",ticks:12,twelveHourClock:false},
    y2axis: { position:"right"},
    grid  : { hoverable: true }
  });

  $("#tempandwind").bind("plothover",highlight);

The Flot plugin does not generate the custom plothover events by defaults so we need to turn that on as can be seen in line 5. With plothover events enables we can now bind a function to this event as can be seen in the last line.

The Flot website shows a number of interesting examples on how to enhance an extend the Flot plugin. The highlight() we define here is a slightly adapted version of some of the Javascript behind this example. Lets have a look at our implementation:

var previousPoint = null;

function highlight(event, pos, item) {

  if (item) {
    if (previousPoint != item.dataIndex) {
      previousPoint = item.dataIndex;

      $("#tooltip").remove();
      var y = item.datapoint[1].toFixed(2);
               
      showTooltip(item.pageX, item.pageY,
        item.series.label + " = " + y);
    }
  } else {
    $("#tooltip").remove();
    previousPoint = null;   
  }
};

Any function bound to the plothover event is passed three arguments: an event object, the position in the canvas and an item argument that holds graph specific data, i.e. the x and y values of the datapoint that we are hovering above, its coordinates in the canvas and the label of the dataseries that the point belongs to. If item is null or undefined we are not hovering above a datapoint. We check for this in the code and remove the tooltip if so.

Because we do not want to redraw the hovering information if we are still above the same data point we check if the index in the series of datapoints is different from the one we stored in the global variable previousPoint. If this is the case, we remove the tooltip, convert the value of the datapoint to a number with only two decimals to keep things readable and call the showTooltip() function to draw the a label at to correct position.

function showTooltip(x, y, contents) {
  $('
' + contents + '
').css( { position: 'absolute', display: 'none', top: y + 5, left: x + 5, border: '1px solid #fdd', padding: '2px', 'background-color': '#fee', opacity: 0.80 }).appendTo("body").fadeIn(200); };

The showTooltip() function is straight from the example on the Flot page but deserves some explanation because it shows off some nifty jQuery features.

It creates a div element ans styles it directly with the css() method. Its display attribute is initially none as the element is added to the body element with the appendTo() method but is made gradually visible with the fadeIn() method. Each of these methods returns the element it is operating on so all methods can be chained.

1 comment:

  1. Hello from Russia, and thanks for this solution. But it works not correct with multiple lines. Thats right function highlight for multiple lines:

    var previousPoint = {point: null, plot: null};
    function highlight(event, pos, item) {
    if (item) {
    if (previousPoint.point != item.dataIndex || previousPoint.plot != item.seriesIndex)
    {
    previousPoint = {point: item.dataIndex, plot: item.seriesIndex};

    $("#tooltip").remove();
    var y = item.datapoint[1].toFixed(2);

    showTooltip(item.pageX, item.pageY,
    item.series.label + " = " + y);
    }
    } else
    {
    $("#tooltip").fadeOut(500, function(){$(this).remove();});
    previousPoint = {point: null, plot: null};
    }
    };

    ReplyDelete