Introduction

What is an Injection?

ChartIQ uses the term “injection" to refer to a function that you, as a developer, create in order to override, augment or bypass core library functionality (software "hooking"). Injections are attached to functions in the library ("injection points"). Your injection is automatically invoked when the library reaches the injection point.

Most core library functions can invoke injections. These injectable functions are listed here.

What is the purpose of the Injection API?

Charting library functions were developed to interact with each other in a very specific way. They are not meant to be overwritten, redefined, or forked. The Injection API is provided as a way for you, the developer, to augment, override, or even bypass the functionality of key library functions without having to modify ("monkey patch") source code. This gives developers the same flexibility as forking code, but maintains compatibility with future upgrades.

What can be done with this API?

The Injection API is extremely powerful. A developer can interject behavior throughout the library, essentially treating the library as a series of events.

For example:

  • Price values displayed in a DIV tag (a "head's up" display) could be updated by attaching an injection to CIQ.ChartEngine.prototype.headsUpHR
  • A custom visualization could be added to the chart by attaching an injection to CIQ.ChartEngine.prototype.draw
  • Mouse movements could be intercepted and modified by attaching an injection to CIQ.ChartEngine.prototype.mousemoveinner

Injection functions receive the same arguments as the functions that invoke them. They can be set to invoke either at the beginning (prepend) or at the end (append) of the core library function. Injections have access to the CIQ.ChartEngine instance as the this object.

Multiple injections may be attached to the same injection point. Injections can be added or removed at any time.

Creating Injections

A sample injection

Here is an function which will update a custom DOM element with the closing price:

function updateMyDOMElement(close){
    myDOMElement.innerHTML=close;
}

Suppose that we want this function to be called whenever the user moves the crosshairs on the chart. The closing price intersected by the crosshairs would appear in the DOM element. A logical place to attach this injection would be the headsUpHR function mentioned above, since that is the function responsible for updating the date and price labels on the axes.

Design your injection function to accept the same arguments as the core library function. In the case of headsUpHR, there are no arguments.

function myInjection(){
    // get the price under the crosshairs by finding the bar and then looking in the xaxis array
    var bar=this.barFromPixel(this.backOutX(CIQ.ChartEngine.crosshairX));
    var prices=this.chart.xaxis[bar];

    // pass the Close from the intersected xaxis bar into the function which populates the DOM element
    if(prices && prices.data) updateMyDOMElement(prices.data.Close);
}

Now we need to attach the injection. You must choose whether to invoke the injection at the beginning or at the end of the injectable function. To attach at the beginning you would call stxx.prepend and to attach at the end you would call stxx.append.

Appending an injection

Let's now attach our injection to the end of the library's headsUpHR function:

stxx.append("headsUpHR", myInjection);

Now, every time headsUpHR() is called, myInjection() will be invoked afterwards.

Note: An injection which is appended to a function is not guaranteed to be executed. Appended injections will only execute if the library code successfully completes (if no return statements are reached in the code).

See class reference for more information on append().

Prepending an injection

As an alternative example, let's set our injection to run before the core library function executes:

stxx.prepend("headsUpHR", myInjection);

When an injection is prepended, it executes before the core library function. (This is nearly always guaranteed to be reached.)

See class reference for more information on prepend().

Bypassing core functionality

When an injection returns true, the core library function is bypassed.

This is a very powerful feature that allows the developer to essentially replace core functionality without modifying any actual source code. For example, by prepending the myInjection function above and having it return true, any internal headsUpHR functionality would be skipped. The internal functionality has essentially been replaced by myInjection.

Note: Returning true also bypasses any subsequent injections attached to that injection point. For example, if there are 3 injections appended to headsUpHR, and the second injection returns true, the third injection will not be invoked.

Global Injections

The examples above, illustrating how to append and prepend injections to a single chart instance. It is also possible to attach an injection to the CIQ.ChartEngine prototype so that it runs for all chart instances. This may be desirable when running multiple charts on a page.

CIQ.ChartEngine.prototype.prepend("headsUpHR", myInjection);

Injections added to an instance take precedence over injections added to the prototype.

Order of execution

When multiple injections are appended and prepended to a function, the order of execution is as follows:

  1. Prepends to the instance, LIFO
  2. Prepends to the prototype, LIFO
  3. The injectable function itself
  4. Appends to the instance, FIFO
  5. Appends to the prototype, FIFO

Example:

CIQ.ChartEngine.prototype.append("headsUpHR", injection1);
stxx.prepend("headsUpHR", injection2);
CIQ.ChartEngine.prototype.prepend("headsUpHR", injection3);
stxx.append("headsUpHR", injection4);
CIQ.ChartEngine.prototype.prepend("headsUpHR", injection5);
/*
Injections are fired as follows:
injection2, injection5, injection3, body of headsUpHR, injection4, injection1
*/

Removing Injections

There are two methods for removing injections.

1) Remove all injections at a specific injection point:

CIQ.ChartEngine.prototype.remove("headsUpHR");  // to remove prototype injections
stxx.remove("headsUpHR");  // to remove instance injections

See CIQ.ChartEngine#remove.

2) Remove a specific injection by keeping track of it with a reference handle:

var refID=CIQ.ChartEngine.prototype.prepend("headsUpHR", myInjection);
var refID2=stxx.prepend("headsUpHR", myInjection);

// I can remove the injections at any time, using the refIDs:
CIQ.ChartEngine.prototype.removeInjection(refID);
stxx.removeInjection(refID2);

If an injection was added to the prototype, it must be removed from the prototype. Similarly, if an injection was added to the instance, it must be removed from the instance.

See CIQ.ChartEngine#removeInjection.

List of Injection Points

Documentation for all injectable functions, including their argument lists, can be found in the AdvancedInjectable reference.

Note: some library functions are called more often than others. As a result, use discretion when deciding which function to register an injection. For example, it is tempting to append to the CIQ.ChartEngine.draw function since it is called quite often. However, this may cause the library to gratuitously call the injection when it is not necessary to do so, slowing down the overall animation.

Performance Considerations

When a user interacts with a chart, scrolling or zooming, an animation loop is in effect. For instance, when a user grabs the chart with their mouse and scrolls back and forth, the chart engine is redrawing the chart many times per second. Each time the chart is drawn is referred to as a "frame". To provide a smooth, responsive user experience a chart should be able to render itself at the rate of 60 frames per second (fps).

The charting library has been highly optimized to maximize the frame rate (fps). Essentially, the more work that the library needs to do for each frame, the slower the frame rate. If a chart is asked to do too much, then it will fall short of 60 fps and the user experience will be compromised.

With this in mind, be cautious about injections that attached into the animation loop (the documentation for each injection point will note whether it is inside the animation loop). Generally speaking, it is safe to inject into the animation loop so long as the attached code performs reasonably well. Be careful about updates to the DOM (if the load becomes heavy, consider putting DOM updates into a setTimeout() method or use a library such as bottleneck.js or a virtual DOM to govern changes). Of course try to avoid intensive loops. Be cautious about building large arrays inside the animation loop since the browser's garbage collector will not run frequently enough to maintain a small memory footprint.

Injection Examples

There are several interesting injection examples available in Popular API Injections.