How to add annotations with JavaScript and HTML in Infragistics jQuery Chart

[Infragistics] Mihail Mateev / Monday, August 20, 2012

This article will help you add annotations to igDataChart using HTML Canvas. This approach can be used to add graphic elements to other components using HTML Canvas

Introduction

Very often developers want to add text or graphics to the layout of an existing jQuery widget. To add annotations is not that difficult if you know the architecture of the component. This article refers to the Infragistics jQuery Chart (part of NetAdvantage for jQuery), but the approach can be used for different components. To understand the example you need a basic knowledge of HTML5, JavaScript and jQuery.

Background

In this example you will learn how to add annotation as Fibonacci retracement to the Infragistics jQuery Chart. Fibonacci retracement is created by taking two extreme points on a chart and dividing the vertical distance by the key Fibonacci ratios. 0.0% is considered to be the start of the retracement, while 100.0% is a complete reversal to the original part of the move. Fibonacci ratios are mathematical relationships, expressed as ratios, derived from the Fibonacci sequence. The key Fibonacci ratios are 0%, 23.6%, 38.2%, and 100%. More details about Fibonacci retracement you could find in Wikipedia.

fibonacci-retracement-downtrend-after-sm[1] 

The focus in this blog  is on how to add annotations, not on Fibonacci retracement. Code comments are mainly related to technical implementation.

 

Using the code

This is a pure HTML/JavaScript/jQuery example. You have no need to implement a server-side logic. The base application contains igDataChart with financial series inside it. The application uses test data from random values.

igChartAnnotations_Pic03[1]

To start you need to have references to Infragistics NetAdvantage for jQuery scripts and styles. The easiest approach is to use Infragistics Content Delivery Network (CDN) for NetAdvantage for jQuery. The files on the CDN are arranged with the same folder structure as is installed locally on a development machine. The best approach is add only Infragistics Loader (igLoader) as reference and after that to use this component to load references in the proper order.

<script src="http://cdn-na.infragistics.com/jquery/20121/2023/js/infragistics.loader.js"></script>

The Infragistics Loader resolves all the Infragistics resources (styles and scripts) for you. You just need to provide the path to required CSS and JavaScript files and declare which resources the loader will fetch for the page.

 

igLoader[1]

   1: $.ig.loader({
   2:     scriptPath: "http://cdn-na.infragistics.com/jquery/20121/2023/js/",
   3:     cssPath: "http://cdn-na.infragistics.com/jquery/20121/2023/css/",
   4:     resources: "igDataChart.*"
   5: });

 

Helper object

It is a good practice to have a helper, that contains all required code to add annotations. Helper is named “FibRetracer” (from Fibonacci retracement). This helper contains a method “attachCanvas” that adds an additional HTML canvas over the canvas elements, used inside the igDataChart.

   1: function FibRetracer() {
   2:     this.enabled = false;
   3:     this.attached = false;
   4: }
   5: FibRetracer.prototype.attachCanvas = function (chartElement) {
   6:     if (this.attached) {
   7:         return;
   8:     }
   9:     this.element = chartElement;
  10:     var canvases = chartElement.find("canvas");
  11:     this.renderCanvas = $("<canvas class='fibCanvas' style='position: absolute; top: 0; left: 0;'></canvas>");
  12:     if (canvases.length > 0) {
  13:         this.overlay = $(canvases[canvases.length - 1]);
  14:         this.overlay.before(this.renderCanvas);
  15:         this.renderCanvas.attr("width", this.overlay.attr("width"));
  16:         this.renderCanvas.attr("height", this.overlay.attr("height"));
  17:         this.attached = true;
  18:     }
  19: };

 

For annotations you need to translate coordinates from canvas coordinates to igDataChart axis values and back. Fortunately igDataChart widget has methods “scaleValue” and “unscaleValue” that can do all this work.

   1: FibRetracer.prototype.getUnscaled = function (point) {
   2:     return {
   3:         x: this.element.igDataChart("unscaleValue", "xAxis", point.x),
   4:         y: this.element.igDataChart("unscaleValue", "yAxis", point.y)
   5:     }
   6: },
   7: FibRetracer.prototype.getScaled = function (point) {
   8:     return {
   9:         x: this.element.igDataChart("scaleValue", "xAxis", point.x),
  10:         y: this.element.igDataChart("scaleValue", "yAxis", point.y)
  11:     }
  12: },

 

The main part (to generate annotations) is implemented in the method “draw”. In this method new elements are added inside the attached canvas

   1: FibRetracer.prototype.draw = function () {
   2:     if (this.context == null) {
   3:         this.context = this.renderCanvas[0].getContext("2d");
   4:     }
   5:  
   6:     if (this.fibPoint == null) {
   7:         return;
   8:     }
   9:  
  10:     this.context.clearRect(0, 0, this.renderCanvas.attr("width"), this.renderCanvas.attr("height"));
  11:  
  12:     var viewport = this.element.igDataChart("option", "gridAreaRect");
  13:     this.context.save();
  14:     this.context.beginPath();
  15:     this.context.moveTo(viewport.left, viewport.top);
  16:     this.context.lineTo(viewport.left + viewport.width, viewport.top);
  17:     this.context.lineTo(viewport.left + viewport.width, viewport.top + viewport.height);
  18:     this.context.lineTo(viewport.left, viewport.top + viewport.height);
  19:     this.context.lineTo(viewport.left, viewport.top);
  20:     this.context.clip();
  21:  
  22:     var scaledPoint = this.getScaled(this.fibPoint);
  23:     var scaledRightBottom = this.getScaled({
  24:         x: this.fibPoint.x + this.fibWidth,
  25:         y: this.fibPoint.y - this.fibHeight
  26:     });
  27:  
  28:     var viewport = {
  29:         top: scaledPoint.y,
  30:         left: scaledPoint.x,
  31:         width: scaledRightBottom.x - scaledPoint.x,
  32:         height: scaledRightBottom.y - scaledPoint.y
  33:     };
  34:  
  35:     var values = [
  36:             0.0,
  37:             23.6,
  38:             38.2,
  39:             100.0
  40:         ];
  41:  
  42:     var entries = [];
  43:     var entry;
  44:  
  45:     for (var i = 0; i < values.length; i++) {
  46:         var val = values[i];
  47:         entries.push(
  48:             {
  49:                 text: val.toString(),
  50:                 value: val
  51:             });
  52:  
  53:     }
  54:  
  55:     for (var j = 0; j < entries.length; j++) {
  56:         entry = entries[j];
  57:         entry.desiredWidth = this.context.measureText(entry.text).width;
  58:     }
  59:  
  60:     if (viewport.width == 0 ||
  61:     viewport.height == 0 ||
  62:     isNaN(viewport.width) ||
  63:     isNaN(viewport.height)) {
  64:         return;
  65:     }
  66:  
  67:     this.context.strokeStyle = "red";
  68:     this.context.fillStyle = "red";
  69:     this.context.lineWidth = 1.0;
  70:  
  71:     for (var j = 0; j < entries.length; j++) {
  72:         entry = entries[j];
  73:         var yPos = (viewport.top + viewport.height) -
  74:         (viewport.height * entry.value / 100.0);
  75:         var xLeft = viewport.left;
  76:         var xRight = viewport.left + viewport.width;
  77:         if (isNaN(yPos) ||
  78:         isNaN(xLeft) ||
  79:         isNaN(xRight)) {
  80:             continue;
  81:         }
  82:  
  83:         this.context.beginPath();
  84:         this.context.moveTo(xLeft, yPos);
  85:         this.context.lineTo(xRight, yPos);
  86:         this.context.stroke();
  87:  
  88:         this.context.textBaseline = "bottom";
  89:         this.context.fillText(entry.text, xRight - entry.desiredWidth, yPos);
  90:     }
  91:  
  92:     this.context.restore();
  93: };

 

When Infragistics igDataChart component is created you can attach a function to  “refreshCompleted” event. In this function the new canvas is attached.

   1: $("#priceChart").igDataChart({
   2:     width: "700px",
   3:     height: "500px",
   4:     dataSource: currDataSource,
   5:     axes: [{
   6:         name: "xAxis",
   7:         type: "categoryX",
   8:         label: "Date"
   9:     },
  10:     {
  11:         name: "yAxis",
  12:         type: "numericY",
  13:         labelExtent: 40
  14:     }],
  15:     series: [{
  16:         name: "series1",
  17:         title: "Financial Series",
  18:         type: "financial",
  19:         xAxis: "xAxis",
  20:         yAxis: "yAxis",
  21:         openMemberPath: "Open",
  22:         highMemberPath: "High",
  23:         lowMemberPath: "Low",
  24:         closeMemberPath: "Close",
  25:         diplayType: "ohlc"
  26:     }],
  27:     windowResponse: "immediate",
  28:     horizontalZoomable: true,
  29:     verticalZoomable: true,
  30:     overviewPlusDetailPaneVisibility: "visible",
  31:     rightMargin: 40,
  32:     refreshCompleted: function (e, ui) {
  33:         if (!retracer.attached) {
  34:             retracer.attachCanvas($("#priceChart"));
  35:         }
  36:         retracer.draw();
  37:     }
  38: });

You need also to add several methods to support mouse events when add annotations. Now you just  could use Infragistics jQuery Chart fiddle to look at the code and play with this sample.

Application

For this demo you do not need to install anything. Sample is placed in JS FIDDLE (http://jsfiddle.net ).
JsFiddle is a playground for web developers, a tool which may be used in many ways. One can use it as an online editor for snippets build from HTML, CSS and JavaScript.

 igChartAnnotations_Pic01[1]

When you click on button “Annotate” you could add a Fibonacci retracement as an annotation , dragging your mouse.

igChartAnnotations_Pic02[1]

 

Conclusions

To add new elements  to your jQuery control is not difficult – you just need to add to it’s DOM required HTML element. Data Visualization related components like charts, maps etc. use canvas to render graphics. You could add own canvas with additional elements.

Source code is available here. As always, you can follow us on Twitter @mihailmateev and @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!