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 theFibonacci sequence. The key Fibonacci ratios are 0%, 23.6%, 38.2%, and 100%. More details about Fibonacci retracement you could find in Wikipedia.




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.

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.

<span class="xml-punctuation"><</span><span class="xml-tagname">script </span><span class="xml-attname">src</span><span class="xml-punctuation">=</span><span class="xml-attribute">"http://cdn-na./jquery/20121/2023/js/infragistics.loader.js"</span><span class="xml-punctuation">>

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.

<span class="js-variable">$</span><span class="js-punctuation">.</span><span class="js-property">ig</span><span class="js-punctuation">.</span><span class="js-property">loader</span><span class="js-punctuation">(</span><span class="js-punctuation">{</span>
<span class="whitespace">    </span><span class="js-property">scriptPath</span><span class="js-punctuation">: </span><span class="js-string">"http://cdn-na./jquery/20121/2023/js/"</span><span class="js-punctuation">,</span>
<span class="whitespace">    </span><span class="js-property">cssPath</span><span class="js-punctuation">: </span><span class="js-string">"http://cdn-na./jquery/20121/2023/css/"</span><span class="js-punctuation">,</span>
<span class="whitespace">    </span><span class="js-property">resources</span><span class="js-punctuation">: </span><span class="js-string">"igDataChart.*"</span>
<span class="js-punctuation">}</span><span class="js-punctuation">)</span><span class="js-punctuation">;</span>

 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. 



FibRetracer.prototype.attachCanvas = function (chartElement) {
	if (this.attached) {
		return;
	}
	this.element = chartElement;
	var canvases = chartElement.find("canvas");
	this.renderCanvas = $("<canvas class='fibCanvas' style='position: absolute; top: 0; left: 0;'></canvas>");
	if (canvases.length > 0) {
		this.overlay = $(canvases[canvases.length - 1]);
		this.overlay.before(this.renderCanvas);
		this.renderCanvas.attr("width", this.overlay.attr("width"));
		this.renderCanvas.attr("height", this.overlay.attr("height"));
		this.attached = true;
	}
};


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

FibRetracer.prototype.draw = function () {
	if (this.context == null) {
		this.context = this.renderCanvas[0].getContext("2d");
	}

	if (this.fibPoint == null) {
		return;
	}

	this.context.clearRect(0, 0, this.renderCanvas.attr("width"), this.renderCanvas.attr("height"));

	var viewport = this.element.igDataChart("option", "gridAreaRect");
	this.context.save();
	this.context.beginPath();
	this.context.moveTo(viewport.left, viewport.top);
	this.context.lineTo(viewport.left + viewport.width, viewport.top);
	this.context.lineTo(viewport.left + viewport.width, viewport.top + viewport.height);
	this.context.lineTo(viewport.left, viewport.top + viewport.height);
	this.context.lineTo(viewport.left, viewport.top);
	this.context.clip();

	var scaledPoint = this.getScaled(this.fibPoint);
	var scaledRightBottom = this.getScaled({
		x: this.fibPoint.x + this.fibWidth,
		y: this.fibPoint.y - this.fibHeight
	});

	var viewport = {
		top: scaledPoint.y,
		left: scaledPoint.x,
		width: scaledRightBottom.x - scaledPoint.x,
		height: scaledRightBottom.y - scaledPoint.y
	};

	var values = [
			0.0,
			23.6,
			38.2,
			100.0
		];

	var entries = [];
	var entry;

	for (var i = 0; i < values.length; i++) {
		var val = values[i];
		entries.push(
			{
				text: val.toString(),
				value: val
			});

	}

	for (var j = 0; j < entries.length; j++) {
		entry = entries[j];
		entry.desiredWidth = this.context.measureText(entry.text).width;
	}

	if (viewport.width == 0 ||
	viewport.height == 0 ||
	isNaN(viewport.width) ||
	isNaN(viewport.height)) {
		return;
	}

	this.context.strokeStyle = "red";
	this.context.fillStyle = "red";
	this.context.lineWidth = 1.0;

	for (var j = 0; j < entries.length; j++) {
		entry = entries[j];
		var yPos = (viewport.top + viewport.height) -
		(viewport.height * entry.value / 100.0);
		var xLeft = viewport.left;
		var xRight = viewport.left + viewport.width;
		if (isNaN(yPos) ||
		isNaN(xLeft) ||
		isNaN(xRight)) {
			continue;
		}

		this.context.beginPath();
		this.context.moveTo(xLeft, yPos);
		this.context.lineTo(xRight, yPos);
		this.context.stroke();

		this.context.textBaseline = "bottom";
		this.context.fillText(entry.text, xRight - entry.desiredWidth, yPos);
	}

	this.context.restore();
};



Points of Interest

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.

History

You can use jQuery Chart fiddle to look at the code and play with this sample and make updates.

More details you could find in my blog here.