See Customization Basics and “Injection API" for more details on using API injections

Adding canvas elements to the chart.

The following is a sample injection that shows how to draw different canvas elements, such as lines, text boxes and labels, at a specific price point and date coordinates.

This injection is attached to the "draw" method inside the chart's animation loop. It will therefore render when the user moves the chart back and forth. This is critical because the y-axis location of a certain price will change depending on the zooming and scrolling of the user. Also, since this is in the animation loop we are guaranteed to redraw the label whenever the actual values changes from new streaming data.

function injectionSample(){
    var chart=this.chart, panel=chart.panel, yAxis=panel.yAxis, xaxis=chart.xaxis, dataSet=chart.dataSet, ctx=chart.context;
    var parameters={
      pattern: "dashed",    // options: "solid","dashed","dotted"
      lineWidth: 2        // select any width for the line in pixels
    };

      if (!dataSet.length) return;


  /**************** display a horizontal line showing the current high price *****************/

  var price = dataSet[dataSet.length-1].High;
  var x=this.pixelFromDate(dataSet[dataSet.length-1].Date, chart);
  var y=this.pixelFromPrice(price, panel);

  this.plotLine(x, x+1, y, y, "orange", "horizontal", ctx, true, parameters);

  /***************** display  label on the y axis showing the current high price ***************/

  var txt = price;
  if( chart.transformFunc ) {
      txt = chart.transformFunc(this, chart, price);
  }
  txt=yAxis.priceFormatter?yAxis.priceFormatter(this, panel, txt):this.formatYAxisPrice(txt, panel);
  this.createYAxisLabel(panel, txt, y, "orange");

  /************* write a text message under the current high price ****************/  

  // put a nice colored background border under the text
  this.startClip(panel.name); // ensure the drawing doesn't extend outside of the panel
  ctx.fillStyle = 'gray'; // fill color
  ctx.strokeStyle = 'orange'; // border color
  ctx.lineWidth = 2; // optional border width 
  ctx.strokeRect(x-202, y-30,220,21);
  ctx.fillRect(x-202, y-30,220,21);
  this.endClip();

  // print text above line
  this.startClip(panel.name); // ensure the drawing doesn't extend outside of the panel
  ctx.font = '14pt Calibri';
  ctx.fillStyle = 'orange';
  ctx.fillText('This is the current high price', x-200, y-20);
  this.endClip();

  /******************** draw a vertical line next to the text message **************/

  //draw line using native canvas calls
  this.startClip(panel.name); // ensure the drawing doesn't extend outside of the panel
  ctx.beginPath();
  ctx.moveTo(x-202, this.pixelFromPrice(0, panel));
  ctx.lineTo(x-202, this.pixelFromPrice(400, panel));
  ctx.lineWidth = 2;
  ctx.setLineDash([2, 2]);  //creates dotted line, array values are pixels on, pixels off
  ctx.strokeStyle = "green";
  ctx.stroke();
  this.endClip();


  /************** display a vertical line at the end of the market day only for intraday charts *****************/    

  if(!CIQ.ChartEngine.isDailyInterval(this.layout.interval) ) {                    
    var lastVisibleDate = xaxis[xaxis.length-1].DT;    // these are in  dataZone
    var firstVisibleDate = xaxis[0].DT; // these are in  dataZone
    var now=new Date(); 
    // when working with the chart data, we must make sure the times are converted to the dataZone timezone. If there is a datazone set, convert the browser time to the data time zone.
    if(this.dataZone){
      // convert the current time to the dataZone
      var tzNow = CIQ.convertTimeZone(now, null, this.dataZone);
      now = new Date(tzNow.getFullYear(), tzNow.getMonth(), tzNow.getDate(), tzNow.getHours(), tzNow.getMinutes(), tzNow.getSeconds(), tzNow.getMilliseconds());
    }
    if ( !chart.market.isOpen() ) now=chart.market.getPreviousOpen(now);

    var currentClose=chart.market.getPreviousClose(now);

    if ( currentClose >= firstVisibleDate && currentClose <= lastVisibleDate ) {
      // the current close is visible in the chart...
      x = this.pixelFromDate(CIQ.yyyymmddhhmm(currentClose), chart);
      this.plotLine(x, x, 0, 0, "red", "vertical", ctx, true, parameters);
    }
  }
}

CIQ.ChartEngine.prototype.append("draw", injectionSample);

add a 'dot' to indicate the current quote

Here is an injection to display a 'dot' (small circle) on the current tick for line and mountain charts. Select any color (default: 'blue') and size (default: 5) you want.

stxx.append("draw", function() {
  if (this.chart.dataSet && this.chart.dataSet.length && !this.standaloneBars[this.layout.chartType] ) {
    var context = this.chart.context;
    var panel = this.chart.panel;
    var currentQuote = this.currentQuote();
    if(!currentQuote) return;
    var price = currentQuote.Close;
    var x = this.pixelFromTick(currentQuote.tick, this.chart);
    if( this.chart.lastTickOffset ) x = x + this.chart.lastTickOffset;
    var y = this.pixelFromPrice(price, panel);
    this.startClip();
    context.beginPath();
    context.moveTo(x, y);
    context.arc(x, y, 5, 0, Math.PI * 2, false);
    context.fillStyle = 'blue';
    context.fill();
    this.endClip();
  }
});

Current price label to follow the visible candle

By default, the current price label will always display the last (current) price for that instrument, even that last bar is not visible.

If you wish to override this behavior and have the label display the value of the last 'visible' candle you can do the following:

1- Turn off default label:

stxx.chart.yAxis.drawCurrentPriceLabel=false;

2- add an API injection to create a label match the last visible candle:

stxx.append("draw", function() { var last = this.chart.dataSegment[this.chart.dataSegment.length-1].Close; if( last ) { var chart=this.chart; var panel=chart.panel; var yAxis=panel.yAxis; if(panel.hidden) return; if(chart.transformFunc) currentClose=chart.transformFunc(this,this.chart,last); var txt; var labelDecimalPlaces=Math.max(panel.yAxis.printDecimalPlaces, panel.chart.decimalPlaces); if(yAxis.maxDecimalPlaces || yAxis.maxDecimalPlaces===0) labelDecimalPlaces=Math.min(labelDecimalPlaces, yAxis.maxDecimalPlaces); if(yAxis.priceFormatter){ txt=yAxis.priceFormatter(this, panel, last, labelDecimalPlaces); }else{ txt=this.formatYAxisPrice(last, panel, labelDecimalPlaces); } var y=this.pixelFromTransformedValue(txt, chart.panel); // select the label background and text colors. We use 'blue' and 'white' respectively. this.createYAxisLabel(panel, txt, y, 'blue', 'white'); } });

Snap Y cross-hair line to specific price intervals

This example snaps the Y cross-hair to even numbers but you can customize it to snap to any price interval you may need. For example, certain instruments only trade in 0.25 dollar intervals. You can adjust this injection to do that.

CIQ.ChartEngine.prototype.append("positionCrosshairsAtPointer", function(){
  var chart=this.chart, panel=chart.panel, yAxis=panel.yAxis;

  var price = this.priceFromPixel(this.crossYActualPos);
  var difference= price % 2;
  if( difference ) {
    this.crossYActualPos= this.pixelFromPriceTransform(price-difference, panel);
    if( yAxis.bottom < this.crossYActualPos ) this.crossYActualPos=yAxis.bottom;
    this.controls.crossY.style.top=this.crossYActualPos + "px";
  }    
});

Smooth X cross-hair movement

This example shows how to allow the x axis cross-hair do move freely between the bar interval instead of snapping to the center of the candle. Both injections are required.

// allow free movement of the cross-hair
CIQ.ChartEngine.prototype.append("positionCrosshairsAtPointer", function(){
        this.controls.crossX.style.left=this.cx + "px";
});

// allow free movement of the floating label
CIQ.ChartEngine.prototype.append("updateChartAccessories", function(){
    var floatDate=this.controls.floatDate;
    if(floatDate){
        var l=(this.cx-(floatDate.offsetWidth/2)-0.5);
        if(l<0) l=0;
        floatDate.style.left=l+"px";
    }
});        

Override default chart movement as new bars are added.

Normally, as new bars are added, the chart will scroll back from right to left. So the most current bar is flushed to the right edge, and as more bars are added, the left ticks will continue to move back and eventually go out of view.

This injection overrides that behavior so the oldest tick is flushed to the left edge of the chart and as new bars are added, the chart will grow towards the right. Once enough bars have filled the view window, instead of going out of view, the chart will continue to be compressed with each new bar so all data is always visible.

This override is aimed at charts that will initially start empty or very little bars, and gradually add more data overtime.

stxx.prepend("updateChartData", function() {
  var dataSize = this.chart.dataSet.length+1;
  if (this.chart.maxTicks < dataSize+3){
    this.setMaxTicks(dataSize+2); // if full screen, squeeze chart as new candles come in
  }
  this.chart.scroll=dataSize+1; // flush right
  this.micropixels=0;
});

To prevent user interaction from interfering with this behavior, you may also want to disable zooming and panning with the following overrides:

stxx.allowZoom=false;
stxx.allowScroll=false;

Converting zoom mode into a scroll mode

Sometimes the zoom feature can be cumbersome to use on some browsers, such as in small mobile devices. The following injection will convert the zooming into scrolling if using a touch device.

function disableZoom() {
    if(CIQ.touchDevice) {
        // fix the candlewidth (zoom level) at the desired size
        this.setCandleWidth(18);
    }
}
CIQ.ChartEngine.prototype.prepend("draw", disableZoom);

Note that if you wish to completely disable zooming you probably want to do so when you declare the chart using the allowzoom parameter.

Example:

var stxx=new CIQ.ChartEngine({container:$$$(".chartContainer"), allowZoom:false, layout:{"candleWidth": 16, "crosshair":true}});

Adding close price for comparison symbols on heads-up display

A heads-up display is an area on the chart where key data values are displayed for the bar intersected by the crosshair.

In your main HTML file set up a section for your head's up display. Here is an example:

        <ul class="hu">
            <li><span class="huLabel">O: </span><span id="huOpen" class="huField"></span></li>
            <li><span class="huLabel">H: </span><span id="huHigh" class="huField"></span></li>
            <li><span class="huLabel">V: </span><span id="huVolume" class="huField"></span></li>
            <li><span class="huLabel">C: </span><span id="huClose" class="huField"></span></li>
            <li><span class="huLabel">L: </span><span id="huLow" class="huField"></span></li>
        </ul>

Now add an injection to "headsUp". The chart will run this in all circumstances where a head's up display ought to be updated:

// Update the head's up display
function prependHeadsUpHR(){
    var tick=Math.floor((CIQ.ChartEngine.crosshairX-this.chart.left)/this.layout.candleWidth);
    var prices=this.chart.xaxis[tick];

      // We first set the divs to blank. If a user is hovering over the "future" section of the chart then there won't be any prices
    $$$("#huOpen").innerHTML="";
    $$$("#huClose").innerHTML="";
    $$$("#huHigh").innerHTML="";
    $$$("#huLow").innerHTML="";
    $$$("#huVolume").innerHTML="";
    if(prices!=null){
        if(prices.data){
            $$$("#huOpen").innerHTML=this.formatPrice(prices.data.Open);
            $$$("#huClose").innerHTML=this.formatPrice(prices.data.Close);
            $$$("#huHigh").innerHTML=this.formatPrice(prices.data.High);
            $$$("#huLow").innerHTML=this.formatPrice(prices.data.Low);
            $$$("#huVolume").innerHTML=CIQ.condenseInt(prices.data.Volume);
             /*** We'll insert comparison logic here ***/
        }
    }
}

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

Now let's add code to dynamically add the values of comparison symbols.

stxx.chart.series contains an array of all comparison symbols. Each series object will have a field called 'display'. For example, display: "IBM".

stxx.chart.dataSegment objects contain the close for all comparison symbols. So we use the stxx.chart.series array to get the list of symbols, and then use the dataSegment array to get the closing values for each.

The stxx.chart.dataSegment array elements will look something like this (commented are two symbols that would be part of a comparison):

Adj_Close: 130.12
Close: 130.12
Date: "2012-03-07"
High: 130.33096912079003
IBM: 197.77 // part of comparison
INTC: 26.91  //part of comparison
Low: 129.39119758272534
Open: 129.51586115410126
Volume: 143692200
atr: 1.1490468537796275

Here is the logic for getting comparison values:

/*** Inserted comparison logic ***/
for(var symbol in this.chart.series){
    for(var field in prices){
        if(field==symbol){
          var price=prices[field];
          var display=this.chart.series[symbol].display;
          /*** Use logic to update your head's up display! ***/
        }
    }
}

Adding a confirmation pop-up before deleting a highlighted item from the chart.

Right clicking on highlighted items (such as overlays or drawings) deletes them. You may prefer to provide a confirmation dialog to your users. This can be accomplished by injecting a prepend to the "deleteHighlighted" function:

function prependDeleteHighlighted(){
    if (confirm("Are you sure you want to delete this item?") != true) {
            return true; // bypasses the core library code, preventing the deletion
    }
}

CIQ.ChartEngine.prototype.prepend("deleteHighlighted", prependDeleteHighlighted);

Change the color of the main chart panel.

This injection changes the background color of the main chart panel, excluding the axis. Note that we inject before the draw function using a prepend:

function drawBox(){
    this.chart.context.fillStyle="orange"; /// set your color here 
    this.chart.context.fillRect(this.chart.panel.left, 0,
    this.chart.panel.width, this.chart.panel.yAxis.height);
}
CIQ.ChartEngine.prototype.prepend("draw", drawBox);

Create a horizontal striped background

Here we inject on the "plotYAxisGrid" function in order to create an alternating background. plotYAxisGrid is used to draw the horizontal grid lines on a chart.

The function utilizes the data stored in the Plotter's cache. The "Plotter" conveniently contains the locations of grid lines. This injection iterates through the Plotter's line drawing instructions (lineTo, moveTo) to locate the starting x & y coordinates and corresponding offsets. It then renders rectangles that lay perfectly within alternating grid lines.

This code also handles the special case of rendering the very first highlight from the top, which at times could be of partial height:

function appendPlotYAxisGrid(panel){
    if(panel.name !='chart') return; // Skip study panels
    this.chart.context.fillStyle="rgba(46, 97, 178,.05)"; // set your alternate color. Add transparency so the grid lines are visible

      var plotter=panel.yAxisPlotter, ctx=this.chart.context;

     for(var i=0;i<panel.yAxis.yAxisPlotter.seriesArray.length;i++){
        var series=panel.yAxis.yAxisPlotter.seriesArray[i];
        if(series.name!="grid") continue;
        var startx, starty, moves=series.moves;
        for(var j=0;j<moves.length;j++){
            var move=moves[j];
            if(move.action=="moveTo"){
                startx=move.x; starty=move.y;
                // if there are no more lines..we need to color the last partial grid (unless it is not exactly at the top of the screen)
                if(j+2 >= moves.length && starty) {
                    // last bar 
                    if (moves.length > j+1) {
                        ctx.fillRect(startx, starty, moves[j+1].x-startx, starty*=-1);
                        console.log (startx, starty, moves[j+1].x-startx,starty*=-1);
                    }
                }
                j++; //skip the next lineTo stored in the plotter cache
                j++; //skip the next moveTo    stored int he plotter cache

            }
            if(move.action=="lineTo"){        
                ctx.fillRect(startx, starty, move.x-startx, move.y-starty);
            }
        }        
    }
}
CIQ.ChartEngine.prototype.append("plotYAxisGrid", appendPlotYAxisGrid);

Create a vertical striped background

Here we inject on the "appendDrawXAxis" function in order to create an alternating background. appendDrawXAxis is used to draw the vertical grid lines on a chart.

function appendDrawXAxis(chart,axisRepresentation){
   // Only do alternating shading if the x axis at the bottom. 
   // Otherwise more code is needed to shade the panels under the axis without shading over the axis itself. 
   if( !this.xAxisAsFooter ) return;
   var panel=chart.panel;
   if(panel.name !='chart') return; // Skip study panels
   this.chart.context.fillStyle="rgba(46, 97, 178,.05)"; // set your alternate color. Add transparency so the grid lines are visible

   var ctx=this.chart.context;
   var obj;

   // find the bottom of the last y axis on the chart.
   // we need to know the y axis because otherwise the bottom of the chart will be past the x axis and we don't want that. 
   var wPanel = this.whichPanel(this.chart.canvasHeight - 1); // subtract 1 because whichPanel is designed for displaying the x axis also
   if (!wPanel) return; // happens if window height increases during resize
   var yAxis = wPanel.yAxis;
   var bottom=yAxis.bottom;
   var top = 0;

   var prevRight=-1;
   var nextBoundaryLeft=Number.MAX_VALUE;

   var edges=[];
   var edgesInterator=0;

   for(var nb=0;nb<axisRepresentation.length;nb++){
      if(axisRepresentation[nb].grid=="boundary"){
         nextBoundaryLeft=axisRepresentation[nb].left;
         break;
      }
   }

   for(var i=0;i<axisRepresentation.length;i++){
      obj=axisRepresentation[i];
      // Check for overlap
      if(i==nb){
         for(nb++;nb<axisRepresentation.length;nb++){
            if(axisRepresentation[nb].grid=="boundary"){
               nextBoundaryLeft=axisRepresentation[nb].left;
               break;
            }
         }
         if(nb>=axisRepresentation.length){ // no more boundaries
            nb=-1;
            nextBoundaryLeft=Number.MAX_VALUE;
         }
         if(prevRight>-1){
            if(obj.left<prevRight) continue;
         }
      }else{
         if(prevRight>-1){
            if(obj.left<prevRight) continue;
         }
         if(obj.right>nextBoundaryLeft) continue;
      }
      prevRight=obj.right;
      if((Math.floor(obj.unpaddedRight)<=this.chart.right)){   
       // you may add more code here to have the shading follow the tick instead of always using the first grid line as the staring point
       // which can cause some flickering and unnatural shifts of the alternating colors. 
       if( !edges[edgesInterator] ) edges[edgesInterator] = {left:obj.hz};
       else {
        edges[edgesInterator].right=obj.hz-edges[edgesInterator].left;
        edgesInterator++;
       }
      }
   }

   if ( edges[edgesInterator] && edges[edgesInterator].left && !edges[edgesInterator].right) {
    edges[edgesInterator].right = this.chart.right-edges[edgesInterator].left;
    edgesInterator++;
   }

   for(var j=0;j<edgesInterator;j++){
    if (edges[j].left && edges[j].right ) ctx.fillRect(edges[j].left,top, edges[j].right,bottom);
   }
}
CIQ.ChartEngine.prototype.append("drawXAxis", appendDrawXAxis);

Your chart will resemble something like this:
striped

Forcing a particular panel to remain at the bottom to the chart as new studies are added.

CIQ.ChartEngine.prototype.prepend("adjustPanelPositions", function(){

        var keepOnBottom= 'volume'; // set the name of the panel here

        var lastPanel;
        var newPanels={};
        var pos=0;
        var p;
        for(p in this.panels){
            if(p==keepOnBottom) {
                lastPanel= this.panels[p];
                break;
            }
            pos++;
        }

        if(!lastPanel) return;

        var length=0;
        for(p in this.panels)
            length++;
        if(pos==length-1) return; //already at bottom

        for(p in this.panels){
            if(p==lastPanel.name)continue;
            newPanels[p]=this.panels[p];
        }
        newPanels[lastPanel.name]=lastPanel;
        this.panels=newPanels;
});

Disable/enable touch and mouse events

The following disables all events except the mouse wheel:

CIQ.ChartEngine.prototype.prepend("mousemoveinner", function(){
 // add your logic to decide if you want the events handled or not...maybe via some sort of global variable, or CIQ.ChartEngine member variable....
 //return true; //when you want to have the events disabled
 //return false; //when you want to have the events enabled
});

Disabling/enabling the mouse wheel

The following only disables the mouse wheel:

CIQ.ChartEngine.prototype.prepend("mouseWheel",function(e){ 
// add your logic to decide if you want the events handled or not...maybe via some sort of global variable, or stxx.member variable....
 //return true; //when you want to have the events disabled
 //return false; //when you want to have the events enabled
});

Disabling y axis zooming on touch devices

The following will prevent a user from zooming the y axis when placing a finger and draging over it.

CIQ.ChartEngine.prototype.append("touchmove", function(){
    this.grabStartYAxis=this.grabStartZoom;
});

slowing down inertia on mouse wheel or mouse pad swipes

This injection will slow down the swiping and mouse wheel operations by ignoring requests that are too close from one another.

stxx.prepend("mouseWheel", function(e) {
    var diff=null;
    var timeLimit=500;
    if(this.lastMouseWheelEvent) diff=Date.now()-this.lastMouseWheelEvent;
    if( diff && diff < timeLimit ) {
        console.log('skip');
        return true;
    }
    console.log('go');
});

Adjust the timeLimit as needed to achieve the interaction you are looking for.

Customizing colored line charts to include dashes on flat-lines

The following will render dashed lines when there is no change or markets are closed/inactive causing a flat line on the chart. Similar customizations can be done for other chart types. See CIQ.ChartEngine.Chart#customChart.colorFunction

function dottedLines(){
    if ( this.layout.chartType =="colored_line")
        this.chart.customChart={
            colorFunction:function(stx, quote, mode){
                 var stxLineUpColor=this.getCanvasColor("stx_line_up");
                var stxLineDownColor=this.getCanvasColor("stx_line_down");
                var stxLineColor=this.getCanvasColor("stx_line_chart");

                if(quote.Close>quote.iqPrevClose) return stxLineUpColor;
                else if(quote.Close<quote.iqPrevClose) return stxLineDownColor;
                else return {color:stxLineColor,pattern:[1,3]};
            }
        }
    else 
        this.chart.customChart = null;
}
CIQ.ChartEngine.prototype.prepend("draw", dottedLines);

Your chart will resemble something like this:
dotted line

A canvas alternative to Markers

function markerInjection(){

    var markerList=[
        new Date(2015, 9, 15, 15, 12),
        new Date(2015, 9, 20, 9, 00),
        new Date(2015, 10, 30, 12, 30),
        new Date(2015, 10, 30, 13, 45),
        new Date(2015, 10, 30, 14, 30),
        new Date(2015, 10, 30, 15, 30),
        new Date(2015, 10, 30, 16, 30),
        new Date(2015, 10, 30, 17, 30)
    ];

      var chart=this.chart, dataSegment=chart.dataSegment, panel=chart.panel, ctx=chart.context;

    if (!dataSegment.length) return;

  this.startClip(panel.name); // save current context

  for(var j=0;j<markerList.length;j++){
    for(var i=dataSegment.length-1;i>=0;i--){
      // find the tick you want this on
      if ( dataSegment[i].DT <= markerList[j]) {
        // this code finds the actual tick or the one right before to put the marker on.
        var useHighs=this.highLowBars[this.layout.chartType];
        var price=useHighs?dataSegment[i].High:dataSegment[i].Close;

        var x=this.pixelFromDate(dataSegment[i].Date);
        var y=this.pixelFromPrice(price, panel)-20;    // place 20 pixels over the line/candle

        // draw a circle
        ctx.beginPath();
        ctx.lineWidth=1;
        var radius= 12;
        ctx.arc(x, y, radius, 0, 2*Math.PI, false);
        ctx.fillStyle="green";
        ctx.strokeStyle="black";
        ctx.fill();
        ctx.stroke();
        ctx.closePath();

        //write leter in the circle
        ctx.font = '10pt Calibri';
        ctx.fillStyle = 'white';
        ctx.fillText('T', x-4, y);
        break;
      }
    }
  }

  this.endClip(); // restore previous context so all is back how it was.
}

CIQ.ChartEngine.prototype.append("draw", markerInjection);

Adding a mountain gradient to the candle chart

You can add a gradient to the candle chart by using a prepend to the "displayChart" function as follows:

CIQ.ChartEngine.prototype.prepend("displayChart", function(){

 if(this.layout.chartType=="candle"){
  this.startClip(this.chart.panel.name);
  this.drawMountainChart(this.chart.panel,"custom_candle_mountain");
  this.endClip();
 }
});

Put this function call anywhere in your code after the library js files have loaded.

You will also need to have the following CSS entry:

.custom_candle_mountain {
 background-color: rgba(255, 0, 0, .5);    /* background color for mountain.*/
 color: rgba(255, 0, 0,.01);    /* Optional gradient.*/
}

Your chart will resemble something like this:
Candle Mount

Suppressing the y-Axis auto adjust as chart is panned left or right

By default, the chart will auto adjust (zoom in or out the y axis) to display all candles for the viewing period . If you wish for your chart to continue using the initial y axis zoom level, and allow ticks to fall off the chart on the top or the bottom, you can add the following prepend and append functions to maintain that level:

CIQ.ChartEngine.prototype.prepend("initializeDisplay", function(chart){    
    currentGlobalLow = chart.lowValue;
    currentGlobalHigh = chart.highValue;
});

CIQ.ChartEngine.prototype.append("initializeDisplay", function(chart){

    if ( typeof activeSymbol =="undefined" ) activeSymbol = chart.symbol;
    else if ( activeSymbol == chart.symbol ) {
        if ( currentGlobalLow ) chart.lowValue = currentGlobalLow;
        if ( currentGlobalHigh ) chart.highValue = currentGlobalHigh;
    } else {
        activeSymbol = chart.symbol;
    }
});

Suppressing y-axis click-and-dragg vertical zoom.

By default if you click and drag over the y axis, the chat will zoom vertically. This override disables that functionality.

CIQ.ChartEngine.prototype.append("mousemove", function(){
    if(this.overYAxis && this.grabMode=='zoom-y') {
        var yAxis=this.grabStartYAxis;
        if(yAxis) {
            yAxis.zoom=yAxis.initialMarginTop+yAxis.initialMarginBottom;
            if(yAxis.zoom>yAxis.height) yAxis.zoom=0;
            this.draw();
        }
    }
});

Adding a highlight on the 'mouse-over' bar.

This injection will produce a vertical highlight over the bar of the current crosshair (mouse/pointer) location, when crosshairs are on.

CIQ.ChartEngine.prototype.prepend("headsUpHR", function(){
    var crossX=this.controls.crossX;
    if(crossX.style.display==""){
        crossX.style.left = (CIQ.stripPX(crossX.style.left)-(this.layout.candleWidth/2)) + 'px';
        crossX.style.width = this.layout.candleWidth+'px';
        crossX.style.backgroundColor = 'rgba(191, 191, 191, .2)';
    }
});
  • To display the highlight even if the cross hairs are off, just remove the if(crossX.style.display=="") check.

Adding a label that stays properly placed as the chart size changes or new panels are opened.

Sometimes it is desirable to have a static label or link attached to the chart, such as for attribution or other descriptive text.

If you are trying to add a label that will remain properly located on the main chart panel as new study panels are opened or the size of the chart changes, here are the instructions:

First put a div for the disclaimer on your page:

<div class="stx-disclaimer">

Then style it (in the CSS file):

.stx-disclaimer {
    position:absolute;
    bottom:50px;
    left:10px;
    display:block;
    font-size:13px;
    color:#929292;
    padding:7px 12px 12px;
    background:rgba(0,0,0,.05);
    border-style: solid;
    border-width: 1px;
    width: 386px;
    /*height: 45px;*/  /* let it adjust based on text length */
}

Add anything you want to the HTML to display your text, or dynamically add code in the js to set it like so:

$$$(".stx-disclaimer").innerHTML='Your text here';

Then add the following injection to move it as studies are added or the size of the window changes:

CIQ.ChartEngine.prototype.append("adjustPanelPositions", function(){
    // this places and maintains the disclaimer 48 pixels above the bottom edge of the main chart panel
    $$$(".stx-disclaimer").style.bottom=(this.chart.canvasHeight-this.chart.panel.bottom+48)+"px";
});

Performing additional actions after a pinching gesture

The following injection will trigger once your pinching or reverse pinching gesture is completed. You can add code here to perform any functions you need upon completing a zoom action on a touch device. This injection may be useful for determining chart resolution for the purpose of resizing your custom on-screen elements.

Note that this will only trigger once your fingers move away from the screen and will not trigger while you continue to pinch in and out.

CIQ.ChartEngine.prototype.append("touchend", function(){
    if( this.pinchingScreen>1 ) {
    alert('was zooming');
    // add your code here to trigger any additional action once the zoom is completed on a touch device.
    }
});

Format the Crosshairs floating x-axis label

stxx.chart.xAxis.formatter formats both the x-axis labels and the crosshairs' floating x-axis label (HUD). This injection provides a means to override the crosshairs' floating x-axis label only.

CIQ.ChartEngine.prototype.append("headsUpHR", function(){
     console.log(this.currentPanel.chart.xaxis[this.barFromPixel(this.cx)]); // Original date object.
     console.log("Formatted date: " + this.controls.floatDate.innerHTML); // Formatted date.
     //Custom formatting code here...
     this.controls.floatDate.innerHTML="Custom " + this.controls.floatDate.innerHTML; // Additional formatting prior to display in HUD.
});

Disable Crosshairs during drawing operations

To override that default behavior, you can add the following injection to the cross hair display function to totally disable that functionality during drawing operations:

CIQ.ChartEngine.prototype.prepend("doDisplayCrosshairs", function(){
 if(this.displayInitialized && this.currentVectorParameters.vectorType) {
  this.undisplayCrosshairs();
  return true;
 }
});

To maintain the floating labels on the axis but just remove the line,s use this instead:

CIQ.ChartEngine.prototype.append("headsUpHR", function(){
 var controls=this.controls, crossX=controls.crossX, crossY=controls.crossY;
 if( this.displayInitialized && this.currentVectorParameters.vectorType && crossX && crossX.style.display!="none"){
  crossX.style.display="none";
  crossY.style.display="none";
 }
});

Display data for the study being hovered

This injection will allow you to display on your UI the data for the study being hovered. Simply update or build your UI with the values from outputMap[field] (name of the filed) and prices.data[field] (value for the field). Note that most studies have more than one value available to display; so you may need to allow the UI to dynamically build the display to show all required values.

function prependHeadsUpHR2(){
    //var tick=Math.floor((CIQ.ChartEngine.crosshairX-this.left-this.micropixels)/this.layout.candleWidth);
    var tick=this.barFromPixel(this.cx);
    var prices=this.chart.xaxis[tick];
    if(prices){
        if(prices.data){
            if( stxx.currentPanel && stxx.layout.studies[stxx.currentPanel.name]) {
                var outputMap = stxx.layout.studies[stxx.currentPanel.name].outputMap;

                // now we loop trough the available values and display them as needed. 
                for(field in outputMap){
                    // add your code here to display on your UI the data for the study being hovered
                    console.log(outputMap[field],prices.data[field]);
                }
            }
        }
    }
}

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

Compounded Injections Sample : Spreading y-axis labels to prevent overlapping

The following is a very advanced compounded customization that relies on several injections and function overrides. It is designed to illustrate how combining these different methods can allow you to dramatically customize the library to achieve your desired look and feel.

This code modifies the default behavior of the chart by spreading labels to prevent overlaps, instead of placing them in their actual price position.

It basically collects the label potions into an array first, instead of drawing them as they come. It then sorts them out, separates them into lists for the upper section and lower sections and spreads them up and down using the current price label as anchor to
allow the current price label to be in its original position.

This sample assumes a single y axis, but you can further brake-out each y-axis data set form the list and spread separately as needed using the same streadUp and spreadDown functions.

var listOfLabels;
CIQ.ChartEngine.prototype.updateFloatHRLabel = function(){}; // turn of the y axis floating label to prevent conflicts. 

stxx.prepend("draw", function(){
    listOfLabels={current:null,series:[]}
    stxx.yaxisLabelStyle="roundRectArrow2";
});

stxx.prepend("drawCurrentHR", function(){
    listOfLabels.drawingCurrentHR=true;
});

CIQ.roundRectArrow2 = function(params) {

    // This injection collects all of the labels to be drawn on the y axis

    if( listOfLabels.drawingCurrentHR ) {
        listOfLabels.drawingCurrentHR=false;
        listOfLabels.current={params:params}
        CIQ.roundRect(params, "arrow");
    } else {
        listOfLabels.series.push({params:params});
    }
};

stxx.append("draw", function(){

    // This injection does all the heavy lifting.
    // It iterates trough the list of labels and decides if they need to be spread up or down.
    // The current price label acts as the anchor and labels are moved up or down from it.

    listOfLabels.series.sort(function (a, b) {
      return (a.params.top+a.params.height) - b.params.top;
    });

    spreadDown = function(list) {
        for(i=1;i<list.length;i++){
            var previousBottom=list[i-1].params.top+list[i-1].params.height;
            var currentTop=list[i].params.top;
            if(currentTop < previousBottom) {
                var adjustment=previousBottom-currentTop;
                list[i].params.top+=adjustment;
                list[i].params.y+=adjustment;
            }
        }    
        for(var i=1;i<list.length;i++) CIQ.roundRect(list[i].params, "arrow");    
    }

    spreadUp = function(list) {
        for(i=list.length-2;i>=0;i--){
            var previousTop=list[i+1].params.top;
            var currentBottom=list[i].params.top+list[i].params.height;
            if(previousTop < currentBottom) {
                var adjustment=currentBottom-previousTop;
                list[i].params.top-=adjustment;
                list[i].params.y-=adjustment;
            }
        }
        for(var i=0;i<list.length-1;i++) CIQ.roundRect(overCurrent[i].params, "arrow");
    }    

    // This sample assumes a single axis and simply illustrates 
    // how you can create a list of labels and spread them up and down to prevent overlapping.
    // If multiple axis are used, additional code will need to be added to further separate into individual axis lists 
    // using the 'params.x' as the key.
    // Once separated by axis, spreadUp and spreadDown should be called for every axis.

    var overCurrent=[],underCurrent=[listOfLabels.current];
    for(var i=0;i<listOfLabels.series.length;i++){
        if( listOfLabels.series[i].params.top > listOfLabels.current.params.top )underCurrent.push(listOfLabels.series[i]);
        else overCurrent.push(listOfLabels.series[i]);
    }
    overCurrent.push(listOfLabels.current);

    spreadUp(overCurrent);
    spreadDown(underCurrent);

    stxx.yaxisLabelStyle="roundRectArrow";
});    

Enable Crosshair via a Long Touch

This injection allows the crosshair to enable on touch devices by a long touch gesture. When the user moves the chart it will cancel the crosshair enable so the user may still look at the history. Optionally, display the crosshair and allow for additional crosshair movements after the touch gesture has stopped completely.

/**
 * Enables the crosshair after a delay when the user is holding in one
 * position. Designed for touch devices.
 *
 * @param {CIQ.ChartEngine} stx
 * @param {Object} [options]
 * @param {number} [options.enable=300] delay time in milliseconds, display on touchstart + delay
 * @param {number} [options.disable=0] delay time in milliseconds, continue displaying crosshair after touchend + delay
 * @example
 * var stxx = new CIQ.ChartEngine({container: $$$('.chart-container')});
 * enableCrosshairViaLongTouch(stxx, {enable: 300, disable: 1800});
 */
function enableCrosshairViaLongTouch(stx, options) {
    var layout = stx.layout;
    var enable = options && options.enable || 300;
    var disable = options && options.disable || 0;
    var enableTimer = null;
    var disableTimer = null;

    var enableCrosshairs = function(touchevent) {
        layout.crosshair = true;
        stx.doDisplayCrosshairs();
        stx.touchstart(touchevent);
        // stx.positionCrosshairsAtPointer();
    };
    var disableCrosshairs = function() {
        layout.crosshair = false;
        stx.undisplayCrosshairs();
    };

    stx.addEventListener('move', function() {
        if (enableTimer === null) return;

        clearTimeout(enableTimer);
        enableTimer = null;
    });

    stx.prepend('touchstart', function(e) {
        if (layout.crosshair) {
            if (disableTimer !== null) {
                clearTimeout(disableTimer);
                disableTimer = null;
            }
        } else if (e.touches.length === 1) {
            enableTimer = setTimeout(enableCrosshairs, enable, e);
        }
    });

    stx.append('touchend', function() {
        if (layout.crosshair) {
            disableTimer = setTimeout(disableCrosshairs, disable);
        }

        // gesture may end before timer
        if (enableTimer !== null) {
            clearTimeout(enableTimer);
            enableTimer = null;
        }
    });
}

Shrinking the chart as new candles are added

This injection can be used to continue displaying all visible candles even as new ones are added on updates. As a result the chart slowly shrinks over time instead of scrolling back.

stxx.prepend('createDataSet',function(){
    if( ! this.chart.dataSet || this.chart.dataSet.length == 0 ) return;
    this.last=this.chart.dataSet[this.chart.dataSet.length-1].DT;
    console.log('last candle date before creating a new data set is',this.last);
});
stxx.prepend('draw',function(){
    if( this.layout.candleWidth > stxx.minimumCandleWidth && 
        this.isHome() && 
        this.last && 
        this.last<this.chart.dataSet[this.chart.dataSet.length-1].DT) {
            console.log('ready to draw. original scroll:',this.chart.maxTicks,this.chart.scroll,this.micropixels);
            this.setMaxTicks(this.chart.maxTicks+1);
            this.chart.scroll++
            console.log('news scroll',this.chart.maxTicks,this.chart.scroll,this.micropixels);
    } else {
            console.log('keep the scroll, no new candles or candle cant get smaller');    
    }
    this.last=this.chart.dataSet[this.chart.dataSet.length-1].DT;
});