HTML5 Sparkline chart that is a perfect fit for your jQuery Grid

Damyan Petev / Wednesday, March 12, 2014

HTML5 Sparkline chart in the Ignite UI jQuery GridsSparklines are small, word-sized charts intended to fit anywhere a word or a number would, essentially packing a lot of data into minimum space, yet remaining very easy to read and analyze. After the introductory HTML5 Sparkline chart for data-intense, small-scale Data Visualization post and what seems like a long time ago when I wrote about Sparkline and Grid integration and it was for our XAML controls, I think it’s time to revisit this Sparkline in its sort of natural habitat. Since it’s lightweight and small enough to fit inline with text, inside our jQuery grids’ row should be just fine as well. Of course, there are a few points to take into consideration and I have this favorite case – using the Sparkline as a preview and for quick trend-spotting of child layout data in a hierarchical grid. I also have a few tips on improving the general experience with this control combination.

Essentials

First off though, the obligatory getting started list. There’s a very good guide in our documentation and elaborate samples too, so this blog is going to be more advanced level. You can visit the Sparkline Documentation to get started or steal some quick snippets from the Sparkline samples. Also don’t forget to check out my previous blog as I will be using some of the styling and event handling from that one.

Data requirements

As I mentioned I like to use one hierarchical data source and preview child data for each row in the chart. That means you can bind that to either a Hierarchical grid or a flat one, but take notice the latter case will require you to prevent data scheme transformation by setting the “localSchemaTransform” option to false. Another option would be using a completely separate source for the Sparkline, potentially some remote API, but still based on the grid data. Perhaps a mash up data source is also an interesting option.

Events

Adding an external control to the grid usually involves a templates and events – but which ones? Well, your options really boil down to how complicated your scenario will be – what other features are involved. From simple to complex – if you have a static grid (for whatever reason) you can use “rendered”, which fires once when the grid is loaded and that’s it. Going forth when you add some features that require re-rendering of the data (such as Paging, Sorting) “dataRendered” would probably do the trick. However, when the Column Hiding /  Responsive features kick in they wouldn’t modify the data view and you would need (“as seen on the samples”) the “rowsRendered” event which is your regular data-refreshed generic event.

Take one

Piecing together all the elements, starting with a grid definition and a template for one column to be a target. You can mark those any way you want - special format id-s, classes or nothing at all. Also make sure you pick an appropriate block display container – pretty much a DIV, rather than a span or something else that is inline. The reason behind this is that the table layout will otherwise ignore the size you set to the chart (namely height ) and cut/squish your visuals. Last but not least, consider making the column unbound (thus the schema option), rather than binding it to the child option –  in this case the template won’t be using the data anyways.

Once you have you template column, handle the event you picked from above – I’ll use “rowsRendered” to keep everything playing nice with paging, responsive mode enabled or just about anything else. Because the event’s owner is the grid you can also use the arguments to extract any information you might need about the grid and grab the data source as well. Sadly that is fired for bulk operations, so you will have to iterate and also find row id. One option is to embed the id in the container like the sample does. The default row template will also have id you can reuse:

JavaScript

  1. $('#grid').igGrid({
  2.     dataSource: '/Home/Departments',
  3.     primaryKey: 'DepartmentID',
  4.     columns: [
  5.         //....
  6.     {
  7.         unbound: true,
  8.         key: 'Vacations',
  9.         headerText: 'Vacation Status',
  10.         template: '<div class="sparky"></div>'
  11.     }],
  12.     localSchemaTransform: false,
  13.     rowsRendered: function (evt, ui) {
  14.         $(".sparky").each(function () {
  15.             var ds = ui.owner.dataSource;
  16.  
  17.             $(this).igSparkline({
  18.                 displayType: "column",
  19.                 dataSource: ds.findRecordByKey($(this).closest("tr").data("id")).Employees,
  20.                 valueMemberPath: "VacationHours",
  21.                 height: "50px",
  22.                 width: "100%",
  23.                 normalRangeMaximum: 60,
  24.                 normalRangeMinimum: 10,
  25.                 normalRangeVisibility: "visible",
  26.                 verticalAxisVisibility: "visible"
  27.             });
  28.         });
  29.     }
  30. });

MVC

  1. @(Html.Infragistics().Grid<SparklineInGrid.Models.Department>()
  2.     .ID("Grid1").Columns(column =>
  3.     {
  4.         //..
  5.         column.Unbound("Vacations").HeaderText("Vacation Qtrly balance").Template("<div class='sparky'></div>");
  6.     })
  7.     .DataSource(Model)
  8.     .LocalSchemaTransform(false)
  9.     .DataBind()
  10.     .Render()
  11. )
  12. <script>
  13.     $("#Grid1").on("iggridrowsrendered", function (evt, ui) {
  14.         $(".sparky").each(function () {
  15.             var ds = ui.owner.dataSource;
  16.  
  17.             $(this).igSparkline({
  18.                 displayType: "column",
  19.                 dataSource: ds.findRecordByKey($(this).closest("tr").data("id")).Vacations,
  20.                 height: "50px",
  21.                 width: "100%",
  22.                 verticalAxisVisibility: "visible"
  23.             });
  24.         });
  25.     });
  26. </script>

Ignite UI flat jQuery Grid with Sparkline templated column

One last thing to make you chart fit perfectly like that– make sure there’s no default padding (included in some themes). Also if you don’t want the sparklines to blend with the row background, make sure to remove theirs:

.ui-sparkline {
    background: rgba(255,255,255,0) !important;
    /*Remember to disable the Sparkline default padding when inside a grid cell*/
    padding: 0px;
}

Up your UX game

Now I’m no specialist by any measure, but I judge based on common sense and what feels right to me. So, looking at those axis values above – why do they need to constantly take up my precious space? Why not have that information available only when needed, and the rest of the time the chart can be used for trends and comparison. Tooltips come to mind, but I also consider mobile with responsive and the standard tooltips following my finger, quite possibly under it, are not too ideal. Option one would be to use the positioning trick I used in my last blog and find a better place for those tooltips. But because I also like consistency, when using the Grids tooltips in Popover mode – why not reuse those for the chart as well? So I want something like this:

Hierachical Grid with Sparkline column in a responsive mode on mobile device scene with touch-friendly popover tooltips

I shamelessly stole some storyboard scenes from Indigo Studio :)

So my favorite scenario with hierarchical data and the chart being a quick preview and what you need to do is obviously enable the Grid tooltips feature with Popover style. On a side note, with the last release the Popover actually grew into its own control, but even before that it was around as utility for the grid with almost the same API. That means that despite using the control, my demos below will work with both 13.1 and 13.2. The steps for replacing the tooltips:

  • Disable default popovers for the target columns’ tooltips to avoid overriding the content
  • Handle the sparkline’s inherited “updatetooltip” event and cancel it to prevent the chart from using it’s own tooltip. Use the argument data and trigger a popover instead.
  • Maintain the target for your popover as the chart will fire the update event multiple times and also to prevent unwanted closure of your popup by the grid
  • Optionally handle events to close the popover (it won’t hide automatically unless it’s triggered by the grid on another column). My bet is on mouse leave and touch end – the first will work for desktop and surprisingly mobile IE will emulate it as well and touch end will hide the popover on other mobile devices.
  1. $('#grid').igHierarchicalGrid({
  2.     dataSource: nwCustomersWithOrders,
  3.     primaryKey: 'CustomerID',
  4.     columns: [
  5.         //...
  6.     {
  7.         key: 'Orders',
  8.         headerText: 'Orders',
  9.         template: '<div class="sparky"></div>'
  10.     }],
  11.     columnLayouts: [{
  12.         columns: [
  13.             //...
  14.         {
  15.             key: 'OrderID',
  16.             dataType: 'number',
  17.             headerText: 'Order ID'
  18.         }, {
  19.             key: 'Freight',
  20.             dataType: "number",
  21.             headerText: 'Freight'
  22.         }],
  23.         key: 'Orders',
  24.         foreignKey: 'CustomerID',
  25.         primaryKey: 'OrderID'
  26.     }],
  27.     features: [
  28.         //...
  29.         {
  30.         name: "Tooltips",
  31.         style: "popover",
  32.         tooltipShowing: function (evt, args) {
  33.             if (toolTipState.target != args.element) {
  34.                 //reset tootlip cached target
  35.                 toolTipState.target = null;
  36.             }
  37.         },
  38.         tooltipHiding: function (evt, args) {
  39.             // prevent closing the popover when Sparkline one is visible
  40.             if (toolTipState.target) {
  41.                 return false;
  42.             }
  43.         }
  44.     }],
  45.     rowsRendered: function (evt, ui) {
  46.         $(".sparky").each(function () {
  47.             var ds = ui.owner.dataSource;
  48.  
  49.             $(this).igSparkline({
  50.                 tooltipTemplate: "High: ${High} <br>Low: ${Low}<br>First: ${First}<br> Last: ${Last}",
  51.                 dataSource: ds.findRecordByKey($(this).closest("tr").data("id")).Orders,
  52.                 valueMemberPath: "Freight",
  53.                 unknownValuePlotting: "linearInterpolate",
  54.                 height: "50px",
  55.                 width: "100%",
  56.                 normalRangeMaximum: 100,
  57.                 normalRangeMinimum: 10,
  58.                 normalRangeVisibility: "visible",
  59.                 highMarkerVisibility: "visible",
  60.                 lowMarkerVisibility: "visible"
  61.             }).on("igsparklineupdatetooltip", function (evt, ui) {
  62.                 var gridCell = $(this).closest("td");
  63.  
  64.                 if (toolTipState.target != gridCell[0]) {
  65.                     toolTipState.target = gridCell[0];
  66.                     toolTipState.content = $.ig.tmpl(ui.owner.options.tooltipTemplate, ui.item);
  67.                     $("#" + ui.element.closest("table").igGridTooltips("id")).igPopover('show', gridCell, toolTipState.content);
  68.                 }
  69.                 return false;
  70.             }).on("mouseleave touchend", function (evt) {
  71.                 // optionally hide the popover when user is no longer focusing the sparkline.
  72.                 // mouseleave handles desktop and IE mobile, Touchend handles mobile WebKit
  73.                 $("#" + $(evt.target).closest("table").igGridTooltips("id")).igPopover('hide');
  74.                 toolTipState.target = null;
  75.             });
  76.         });
  77.     }
  78. });

Essentially I’m using the sparkline’s own template and data item to create the content and pass it to the Popover’s show method. The rest is handling closing and current target.

The resulting Hierachical Grid with Sparkline column with touch-friendly popover tooltips with the new 13.2 styling

Tips

  • You can set/override defaults of the popover globally by extending its options:
$.extend($.ui.igPopover.prototype.options, {
    //you can override Popover defaults if needed
    direction: "top"
});
  • The Sparkline tooltip template (and therefore the argument item) accept/have 4 properties – High Low, First, Last.
  • Further improvements can be made by using optimized custom build rather than the loader. Potentially you can delegate the Sparkline tooltip update event to the grid to reduce the number of handlers, although with reasonable page sizes I don’t think it’ll be a noticeable difference. Also keep in mind the each loop is synchronous and if you’re doing many sparklines or heavy work (requests for data) consider making initializations async.
  • with 13.2 the Popover got a bit cleaner design specifically when used with the grid – loosing the borders and the small “x” in the corner. With 13.1 the above screenshot looks like this.

Resources

We’ve seen the Ignite UI Sparkline charts as a column in the jQuery Grids along with Responsive and Tooltips features enabled. I’ve covered some options that require attention and some improvements you can make. The chart's built-in tooltips are replaced with the Popovers used by the grid that are more touch-friendly. This way the app makes the best of the space it has initially, while still providing range information with through the tooltips and in-depth data by expanding the child layouts. And the best part is the sparkline can help you go through the rows and spot outliers super fast!

Donwload your Ignite UI Free Trial now!

I’d love to hear some thoughts, so leave a comment down below or @DamyanPetev.

And as always, you can follow us on Twitter @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!