Tutorials
Getting started
Chart interface
Web components
Chart internals
Data integration
Customization
Frameworks and bundlers
Mobile development
Trading
Time Span Events
Term structures
ScriptIQ
Troubleshooting
Glossary
Reference
JSFiddles

Injections Samples

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

Table of contents


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);

Drawing average lines for series on the canvas.

The following is a sample injection that shows how to draw 'average lines' for every additional series added to the chart. A y-axis floating label will also be added to display the average value. To enable the average lines, you can add UI to set stxx.averageLines=true;, and then set to stxx.averageLines=false; to once again disable.

function averageLines() {
	function avg(symbol) {
		var sum = 0;
		for (var i = 0; i < dataSegment.length; i++) {
			sum += dataSegment[i][symbol] ? dataSegment[i][symbol].Close : 0;
		}

		return sum / dataSegment.length;
	}

	if (!stxx.averageLines) return;

	var parameters = {
		//pattern: "dashed",    // options: "solid","dashed","dotted"
		//lineWidth: 2        // select any width for the line in pixels
	};

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

	if (!dataSegment.length) return;

	for (n in chart.seriesRenderers) {
		var renderer = chart.seriesRenderers[n],
			params = renderer.params,
			panelName = params.panel,
			panel = this.panels[panelName];
		var yAxis = renderer.params.yAxis || panel.yAxis;
		if (params.name == "_main_series") continue;

		for (var m = 0; m < renderer.seriesParams.length; m++) {
			var sParams = renderer.seriesParams[m];
			var symbol = sParams.symbol;

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

			var average = avg(symbol);
			var y = this.pixelFromPrice(average, panel, yAxis);

			this.plotLine(0, 1, y, y, sParams.color, "horizontal", ctx, true, parameters);

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

			var txt = average;
			if (chart.transformFunc) {
				txt = chart.transformFunc(this, chart, average);
			}
			txt = yAxis.priceFormatter
				? yAxis.priceFormatter(this, panel, txt)
				: this.formatYAxisPrice(txt, panel);
			this.createYAxisLabel(panel, txt, y, sParams.color, null, null, yAxis);
		}
	}
}

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

Add a 'dot' to indicate the current quote

Here is an injection to display a 'dot' (small circle) on the most current (last) 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.chart.standaloneBars
	) {
		var context = this.chart.context;
		var panel = this.chart.panel;
		var currentQuote = this.currentQuote("Close");
		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 visibleQuotes = this.getDataSegment();
	var last = visibleQuotes[visibleQuotes.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.pixelFromPrice(price - difference, panel);
		if (yAxis.bottom < this.crossYActualPos) this.crossYActualPos = yAxis.bottom;
		this.controls.crossY.style.top = this.crossYActualPos + "px";
	}
});

Snap Y cross-hair line to close price.

// snap the y axis
CIQ.ChartEngine.prototype.append("positionCrosshairsAtPointer", function() {
	if (this.currentVectorParameters.vectorType) return; // don't override if drawing
	var chart = this.chart,
		panel = chart.panel,
		yAxis = panel.yAxis,
		bar = this.barFromPixel(this.cx);
	if (this.chart.dataSegment[bar]) {
		this.crossYActualPos = this.pixelFromPrice(this.chart.dataSegment[bar].Close, 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";
	}
});

Shrink chart as new candles are added instead of ticking back.

By default, once there are enough candles to fill the chart, if in the home position (current var displayed), the chart will start ticking back so the current bar can still be visible. But since the width of the bars don’t change, the older bars will start going out of view. If your implementation requires the current range to remain visible even as new bars are added, this requires the bars to continuously get thinner to allow for more data in the same space; producing a shrinking effect. This injection will produce this effect:

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("new scroll", this.chart.maxTicks, this.chart.scroll, this.micropixels);
	} else {
		console.log("keep the scroll, no new candles or candle can't get smaller");
	}
	this.last = this.chart.dataSet[this.chart.dataSet.length - 1].DT;
});

Shrink chart -and scroll left to right- as new candles 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 over time.

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 on touch devices

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: document.querySelector(".chartContainer"),
	allowZoom: false,
	layout: { candleWidth: 16, crosshair: true }
});

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) {
	// Skip study panels
	if (panel.name != "chart") return;
	// set your alternate color. Add transparency so the grid lines are visible
	this.chart.context.fillStyle = "rgba(46, 97, 178,.05)";

	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

	// set your alternate color. Add transparency so the grid lines are visible
	this.chart.context.fillStyle = "rgba(46, 97, 178, .05)";

	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.

	// subtract 1 because whichPanel is designed for displaying the x axis also
	var wPanel = this.whichPanel(this.chart.canvasHeight - 1);
	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;
});

Forcing a particular panel (usually the primary panel) to remain at the top of the chart.

This will prevent other panels to be placed over the primary panel, even if the user tries to click on the panel control buttons.

CIQ.ChartEngine.prototype.prepend("adjustPanelPositions", function() {
	var keepOnTop = "chart"; // set the name of the panel here

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

	if (!firstPanel || pos == 0) return; //already at bottom or not there

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

Toggle touch and mouse events

CIQ.ChartEngine#manageTouchAndMouse is normally used to disable user interaction, making it a static chart. But sometimes you may want to temporarily enable interaction and then disable it again.

The following can toggle 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
});

Toggle mouse wheel events

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
});

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 colorFunction

CIQ.ChartEngine.prototype.prepend("displayChart", function(chart) {
	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;
});

Your chart will resemble something like this:
dotted line

A canvas alternative to HTML 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.chart.highLowBars;
				var price = useHighs ? dataSegment[i].High : dataSegment[i].Close;

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

				// 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, 0.5); /* background color for mountain.*/
	color: rgba(255, 0, 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 automatically determined at initial chart load , 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;
	}
});

Setting a static range for the y axis

If you wish to force a specific y axis range for the chart instead of the default variable range based on the data, you can use this injection:

var changeAxis = function() {
	this.chart.yAxis.lowValue = 50; // set to the lower bound
	this.chart.yAxis.highValue = 100; // set to the higher bound
};

stxx.append("initializeDisplay", changeAxis);

Disabling y-axis vertical zooming

By default if you click (or touch) and drag over the y axis, the chart will zoom vertically.

This override disables that functionality both on mouse and touch devices:

CIQ.ChartEngine.prototype.prepend("mousemoveinner", function () {
	this.grabStartYAxis=0;
});

This override disables that functionality on mouse devices only:

CIQ.ChartEngine.prototype.prepend("mousemove", function () {
	this.grabStartYAxis=0;
});

This override disables that functionality on touch devices only:

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

Disabling x-axis vertical zooming

By default if you click and drag over the x axis, the chat will zoom horizontally. This override disables that functionality on both touch and mouse devices.

Please note, this is not a bona fide injection, but rather a function override. They work similarly and can be leveraged in cases when an injection to a particular function is not available.

stxx.origZoomSet = stxx.zoomSet;
CIQ.ChartEngine.prototype.zoomSet = function(candleWidth, chart) {
	var mainSeriesRenderer = this.mainSeriesRenderer || {};
	if (!mainSeriesRenderer.params || !mainSeriesRenderer.params.volume) {
		if (this.grabMode == "zoom-x") {
			chart.spanLock = false;
			return;
		} else {
			stxx.origZoomSet(candleWidth, chart);
		}
	}
};

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, and display the x and y axis floating labels.

CIQ.ChartEngine.prototype.prepend("headsUpHR", function() {
  var crossX = this.controls.crossX;

  if (
    this.currentVectorParameters.vectorType &&
    this.currentVectorParameters.vectorType != "mouse-over-highlight"
  ) {
    crossX.style.width = "";
    crossX.style.backgroundColor = "";
    return;
  }

  // include this if you want the highlight and floating labels to also show when crosshairs are off
  if (!stxx.layout.crosshair) {
    this.currentVectorParameters.vectorType = "mouse-over-highlight";
    this.controls.crossY.style.display = "none";
  } else {
    this.currentVectorParameters.vectorType = "";
    this.controls.crossY.style.display = "";
  }
  // end include

  var left = CIQ.stripPX(crossX.style.left) - this.layout.candleWidth / 2;
  var width = this.layout.candleWidth;
  if (left + width > this.chart.width) {
    // adjust width of last bar so it does not highlight past the edge of the chart into the y axis
    width = this.chart.width - left;
  }
  crossX.style.left = left + "px";
  crossX.style.width = width + "px";
  crossX.style.backgroundColor = "rgba(191, 191, 191, .2)";

});

Your chart will resemble something like this:
crosshair-highlight

Adding an HTML object that remains nailed to primary chart panel.

If you are trying to add a label that will remain attached to 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"></div>

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, 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:

document.querySelector(".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
	document.querySelector(".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, 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";
	}
});

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 through 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";
});

Toggle Crosshair via a Long Touch

This injection allows users on touch devices to enable the crosshair using a long touch gesture. The crosshair will remain enabled until the user is no longer touching the screen.

/**
 * 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: document.querySelector('.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;
		}
	});
}

Reverse scroll direction

Here is an example of an injection that scrolls bars right to left when building historical data one bar at a time. Normally the chart starts rendering candles on the left and moves towards the right with each new bar.

stxx.prepend("createDataSet", function() {
	if (!stxx.chart.dataSet || stxx.chart.dataSet.length == 0) return;

	if (stxx.isHome()) {
		// Save the last date before the new data set is created.
		stxx.last = stxx.chart.dataSet[stxx.chart.dataSet.length - 1].DT;
	} else {
		stxx.last = undefined;
	}
});

stxx.prepend("draw", function() {
	var indexNewLast = stxx.chart.dataSet.length - 1;
	var newLast = stxx.chart.dataSet[indexNewLast].DT;
	if (stxx.isHome() && stxx.last && stxx.last < newLast) {
		// Find index "last" date
		var indexOldLast;
		for (var i = 0; i < stxx.chart.dataSet.length; ++i) {
			if (stxx.chart.dataSet[i].DT >= stxx.last) {
				indexOldLast = i;
				break;
			}
		}

		if (indexOldLast) {
			// Add the difference to maintain the scroll position
			stxx.chart.scroll += indexNewLast - indexOldLast;
		}
	}
});

Anchor chart at right edge while zooming

While zooming, anchor the bar on the right edge of the dispay so that the chart expands or contracts on the left edge only. By default, when the chart's most recent bar is not displayed, zooming will try to anchor the middle of the chart and expand/contract from both edges.

function setGrabMode() {
	this.grabMode = "zoom-x";
}
stxx.prepend("zoomIn", setGrabMode);
stxx.prepend("zoomOut", setGrabMode);

function clearGrabMode() {
	this.grabMode = "";
}
stxx.append("zoomIn", clearGrabMode);
stxx.append("zoomOut", clearGrabMode);

Sync up secondary axis to primary chart on main panel

Example 1:

Sometimes you want to have a secondary series that has a different price range on a separate y axis, but you do not want the secondary series to maintain its own range. Instead, you prefer it to follow the range of the main series, even if this causes it to go off screen. The result is 'true to life' scale where the primary series determines the range instead of otherwise shrinking (flattening) the primary series to ensure the secondary series is also fully in range.

CIQ.ChartEngine.prototype.prepend("createYAxis", function(panel, parameters) {
	if (secondaryAxis.name == parameters.yAxis.name) {
		secondaryAxis.zoom = this.chart.yAxis.zoom;
		secondaryAxis.max = this.chart.yAxis.high;
		secondaryAxis.min = this.chart.yAxis.low;
		this.calculateYAxisRange(
			panel,
			secondaryAxis,
			this.chart.yAxis.lowValue,
			this.chart.yAxis.highValue
		);
	}
});

var secondaryAxis = new CIQ.ChartEngine.YAxis();
secondaryAxis.width = 0; // hide it since the primary drives the chart and will always match

var renderer = stxx.setSeriesRenderer(
	new CIQ.Renderer.Lines({
		params: { name: "lines", yAxis: secondaryAxis }
	})
);

// make sure you call addSeries to add your data first.
renderer.attachSeries(scondarySeriesSymbol, "#FFBE00").ready();

Example 2:

Alternatively, you may want to have 2 axis in different scales for the primary symbol.

In this example there will be a left axis showing actual values (linear scale), and a right axis for the same primary symbol showing percentage change.

This is done by setting the primary series scale to 'percentage', and then adding a secondary series for the same primary instrument in linear scale. Once both series (each one with its on axis in the desired scale) are rendered, their Y-axis are linked with the injection so they stay in sync as the user zooms and pans.

// define this once only
CIQ.ChartEngine.prototype.prepend("createYAxis", function(panel, parameters) {
	if (!stxx.chart.series[stxx.chart.symbol]) return;
	var secondaryAxis = stxx.chart.series[stxx.chart.symbol].parameters.yAxis;
	if (secondaryAxis.name == parameters.yAxis.name) {
		secondaryAxis.zoom = this.chart.yAxis.zoom;
		secondaryAxis.max = this.chart.untransformFunc
			? this.chart.untransformFunc(this, this.chart, this.chart.yAxis.high)
			: this.chart.yAxis.high;
		secondaryAxis.min = this.chart.untransformFunc
			? this.chart.untransformFunc(this, this.chart, this.chart.yAxis.low)
			: this.chart.yAxis.low;
		this.calculateYAxisRange(
			panel,
			secondaryAxis,
			this.chart.untransformFunc
				? this.chart.untransformFunc(this, this.chart, this.chart.yAxis.lowValue)
				: this.chart.yAxis.lowValue,
			this.chart.untransformFunc
				? this.chart.untransformFunc(this, this.chart, this.chart.yAxis.highValue)
				: this.chart.yAxis.highValue
		);
	}
});

// do this for very new instrument loaded.
stxx.setChartScale("percent");
stxx.addSeries(stxx.chart.symbol, { color: "transparent", yAxis: { position: "left" } });

Note that the prepend injection should only be defined once. So move it as needed to prevent it from being ​defined over and over again.

Toggle Crosshair via a Double Tap

This injection allows users on touch devices to toggle the crosshair using a double tap, which by default is used to delete plots.

function touchDoubleClickOverride(finger, x, y) {
	if (this.dispatch("doubleTap", { stx: this, finger: finger, x: x, y: y })) return true;
	if (x < this.left || x > this.right || y < this.chart.panel.top || y > this.chart.panel.bottom)
		return;
	if (this.editingAnnotation) return;
	if (CIQ.ChartEngine.drawingLine) {
		this.undo();
	} else if (this.anyHighlighted) {
		this.deleteHighlighted();
	} else {
		stxx.layout.crosshair = !stxx.layout.crosshair;
		if (stxx.layout.crosshair) {
			stxx.doDisplayCrosshairs();
		} else {
			stxx.undisplayCrosshairs();
		}
	}
	this.clicks = { s1MS: -1, e1MS: -1, s2MS: -1, e2MS: -1 };

	return true;
}
stxx.prepend("touchDoubleClick", touchDoubleClickOverride);

If you want to prevent plots to be deleted with a double tap, see CIQ.ChartEngine#bypassRightClick

Prevent hover highlights on series

Although not a bonafide injection, the idea is similar. This code can be used to prevent a series from being highlighted when mousing over it:

stxx.origPlotDataSegmentAsLine = stxx.plotDataSegmentAsLine;
stxx.plotDataSegmentAsLine = function(field, panel, parameters, colorFunction) {
	parameters.highlight = false;
	var rc = this.origPlotDataSegmentAsLine(field, panel, parameters, colorFunction);
	return rc;
};

Force a specific width for a particular candle:

This example sets the with of the 10th candle displayed on the view window (dataSegment) to a width of 100.

CIQ.ChartEngine.prototype.append("createDataSegment", function(){
		var dataSegment=this.chart.dataSegment;
		var quote;
		for(var i=0;i< dataSegment.length;i++){
			quote = dataSegment[i];
			if( i == 10) quote.candleWidth=100;
		}
});

Add a 'dot' to highlight the 'mouse over' tick

Here is an injection to display a 'dot' (small circle) as you are mousing over a particular tick on the chart.

CIQ.ChartEngine.prototype.append("mousemoveinner", function() {

  var chart = this.chart;
  var tick = this.tickFromPixel(this.cx);
  var quote = chart.dataSet[tick];

  chart.tempCanvas.style.display = "none";
  if (this.overYAxis) return;

  if (!quote) return;
  if (quote.Close < stxx.chart.yAxis.low) return;

  var x = this.pixelFromTick(tick);
  var y = this.pixelFromPrice(quote.Close);

  CIQ.clearCanvas(chart.tempCanvas, this);
  var ctx = chart.tempCanvas.context;

  ctx.beginPath();
  ctx.lineWidth = 1;
  ctx.arc(x, y, 4, 0, (2 * Math.PI), false);
  ctx.strokeStyle = 'blue';
  ctx.fillStyle = 'blue';
  ctx.fill();
  ctx.stroke();
  ctx.closePath();

  chart.tempCanvas.style.display = "block";
});

Maintaining height:width ratio upon resizing

CIQ.ChartEngine.prototype.prepend("resizeChart", function () {
	let container = document.querySelector('#' + this.container.id);
	if (this.heightWidthRatio) {
		container.style.height = CIQ.elementDimensions(container).width * this.heightWidthRatio + "px";
	}
}

Set the desired heightWidthRatio as follows:

stxx.heightWidthRatio = 9 / 16;

Disabling right "click to edit" on drawings

Normally on “right-click”, a context menu with editing options will appear allowing the user to select a variety of different drawing editing features. This injection will disable this feature and immediately delete a drawing instead.

stxx.prepend("rightClickHighlighted", function(){
	for (var i = this.drawingObjects.length - 1; i >= 0; i--) {
		var drawing = this.drawingObjects[i];

		if (drawing.highlighted){
			this.deleteHighlighted(false);
			return true;
		}
	}

});

Next steps