Markers

Markers provide developers with the ability to superimpose DOM (html) elements, canvas drawings or images directly on the chart to highlight particular time based events. Placed markers will move with the chart as a user scrolls and zooms, and they will be hidden when they pan off of the chart. Markers can be placed on the main chart panel or on study panels.

The following working example includes a variety of different marker rendering styles using multiple HTML templates styled by CSS.

- Click on the `JavaScript`, `HTML` and `CSS` buttons to see the code for this working sample. - Click on the `Edit in JSFiddle` button to 'play' with this code.

The following is an example of markers on a study panel:

- Click on the `JavaScript`, `HTML` and `CSS` buttons to see the code for this working sample. - Click on the `Edit in JSFiddle` button to 'play' with this code.

Declaring Marker Handlers

Beginning with version 2015-07, the Marker interface has been updated. It is now much simpler for developers to create markers. The new mechanism is:

new CIQ.Marker(params);

By default, unless a Marker placement handler and its corresponding placementFunction are declared and assigned to a marker, CIQ.ChartEngine#defaultMarkerPlacement will be used. This is equivalent to a "placementFunction" in the previous version of Markers.

Note: prior to version 2015-09-01 you must assign an actual placement function to the default. Example:

CIQ.ChartEngine.defaultMarkerPlacement = CIQ.Marker.MyHandler.placementFunction;

Starting on version 2015-09-01 the defaultMarkerPlacement function is already pre-programmed with default placing logic. You can override this default by assigning any placement function you want to it.

You can directly use the CIQ.Markers class as a handler in your code or derive custom handlers from it. This allows you to be able to use different marker classes for different event types. So for example you can have a 'dividend' handler that places markers above the candle high, or a news handler that places the marker under the candle low or even all the way on the bottom edge of the chart. You decide.

The mechanics for creating new marker handler instance is as follows.

1 - create the function:

CIQ.Marker.MyHandler = function(params) {
	if (!this.className) this.className = "CIQ.Marker.MyHandler";
	CIQ.Marker.call(this, params);
};

2- Set the inheritance:

CIQ.Marker.MyHandler.ciqInheritsFrom(CIQ.Marker, false);

3- Set the placementFunction:

CIQ.Marker.MyHandler.placementFunction = function(params) {
	// your placement code here.
};

See CIQ.ChartEngine#defaultMarkerPlacement for more details

Here is a basic outline for a placement function : Remember that the marker is just an DOM element, so to place it all you have to do is set its style as you would any other HTML component.

CIQ.Marker.MyHandler.placementFunction = function(params) {
	// Iterate through the items to be placed
	for (var i = 0; i < params.arr.length; i++) {
		var marker = params.arr[i];
		var node = marker.node;
		//X axis positioning logic
		node.style.left = theXPixel;
		//Y axis positioning logic
		node.style.bottom = theYpixel;
	}
};

Creating an actual marker

Custom Marker:

marker = new CIQ.Marker.MyHandler({
	stx: stxx,
	xPositioner: "bar",
	x: x,
	node: yourHTMLElement
});

Default Marker:

marker = new CIQ.Marker({
	stx: stxx,
	xPositioner: "bar",
	x: x,
	node: yourHTMLElement
});

x is the horizontal location of the marker. This is determined by the value of xPositioner which can be either:

  • "bar" - Bar position on the screen (1st candle is on the left)
  • "master" - Position in the masterData (candle will move with the chart)
  • "date" - An absolute date. This will be converted to the closest masterData position if the provided date does not exactly match any existing points.
  • "none" - A raw pixel value. Positive values from left of chart. Negative values from right of chart.

For most implementations, this is all you need to position your markers. See CIQ.Marker for the full list of valid parameters.

Managing markers for nonexistent data points

A marker can only be added to a matching data point or it will find the closest date match. To mitigate this, you can always add an invisible placeholder for any bar spacing you want to reserve but not show. You can do this by adding these placeholders on your fetch response, before loading the data into the chart, or by calling updateChartData to add the missing points right before you add a marker. Your data object should only include a Date/DT field.

Things to know when adding invisible candles

  • You want to add these as full bars, not last sales. 'useAsLastSale' will try to find the right candle given the current periodicity. So that will not add in between periodicities, for example.
  • By default, markers are placed above the price point if no specific yPositioner is indicated. But since the invisible placeholders have no price, the markers will not show. Please specify an actual yPositioner that makes sense for the invisible candle, such as yPositioner:"top".

Add placeholder example:

stxx.updateChartData([{ Date: "2018-10-20 00:00" }]);

Use CIQ.ChartEngine#tickFromDate to find out if the element already exists. If the date of the returned tick exactly matches your marker date, there is no need to manually add a datapoint.

Or you can have the chart automatically fill gaps. See: CIQ.ChartEngine#cleanupGaps ( this will require a market definition )

Creating DOM elements for your markers.

Although any element can be attached to a marker, you normally don't want to explicitly create each one separately in your DOM. Instead, you should have prototypes which you can then clone, style and attach.

You can create different type of prototypes if you want to have different HTML elements used for different markers. For example, you can create an element with a specific picture or specific style properties like hover or on-click. Then you would clone the appropriate template for the particular marker you want to display.

Assuming the following prototype is present in your DOM:

<div id="stxEventPrototype" class="myEvents">

Here is an example directly cloning an HTML placeholder:

newNode = $$$("#stxEventPrototype").cloneNode(true);
newNode.id = null;
newNode.innerHTML = "F"; // future tick
CIQ.appendClassName(newNode, "dividend");
var someDate = new Date(stxx.masterData[stxx.masterData.length - 1].DT);
someDate = stxx.getNextInterval(someDate, 5); // 5 bars in the future
new CIQ.Marker.MyHandler({
	stx: stxx,
	xPositioner: "date",
	x: someDate,
	label: "events",
	node: newNode
});

It is very important that you ensure there is a valid and complete CSS class matching the value you use in CIQ.appendClassName. Otherwise the marker may not be rendered if elements are missing. In the above example -CIQ.appendClassName(newNode, "dividend");- the "dividend" class assigns the background color for the marker. If missing, no background color will be present (since the myEvents class does not set one up) and the marker will not be visible.

Node Creators as an alternative to explicitly Creating DOM elements.

For convenience, ChartIQ includes the Simple Marker factory that can create standard marker types dynamically. Circles, Squares and Callouts are supported.

Here is an example on how to use the Simple factory:

function showMarkers() {
	// Remove any existing markers
	hideMarkers();
	var l = stxx.masterData.length;
	// An example of a data array to drive the marker creation
	var data = [
		{
			x: l - 5,
			type: "circle",
			category: "news",
			headline: "This is a Marker for a News Item"
		},
		{
			x: l - 15,
			type: "square",
			category: "earningsUp",
			headline: "This is a Marker for Earnings (+)"
		},
		{
			x: l - 25,
			type: "callout",
			category: "earningsDown",
			headline: "This is a Marker for Earnings (-)"
		},
		{
			x: l - 35,
			type: "callout",
			category: "dividend",
			headline: "This is a Marker for Dividends"
		},
		{
			x: l - 45,
			type: "callout",
			category: "filing",
			headline: "This is a Marker for a Filing"
		},
		{ x: l - 55, type: "callout", category: "split", headline: "This is a Marker for a Split" }
	];
	var story =
		"Like all ChartIQ markers, the object itself is managed by the chart, so when you scroll the chart the object moves with you. It is also destroyed automatically for you when the symbol is changed.";

	// Loop through the data and create markers
	for (var i = 0; i < data.length; i++) {
		var datum = data[i];
		datum.story = story;
		var params = {
			stx: stxx,
			xPositioner: "master",
			x: datum.x,
			label: "events",
			node: new CIQ.Marker.Simple(datum)
		};
		var marker = new CIQ.Marker(params);
	}
	stxx.draw();
}

When using this Simple Marker factory, you can execute the following line right after you add each marker to have the pop-up enabled by default:

var marker = new CIQ.Marker(params);
CIQ.toggleClassName(marker.node, "highlight"); // <-- add this line

Creating the markers CSS

A marker is just an HTML element that can be styled any way you want to. As such a style sheet or in-line styling is required. The following is an example of the styles provided in the advanced CSS file.

.myEvents {
	position: absolute;
	text-align: center;
	width: 20px;
	height: 20px;
	line-height: 20px;
	color: white;
}

.myEvents.dividend {
	background-color: blue;
}
.myEvents.news {
	background-color: red;
}
.myEvents.earnings {
	background-color: purple;
}

A quick way to adjust the location of the markers to allow for the desired padding without having to create a custom placement function is to just manage it in the CSS. Remember this is a simple HTML div with a corresponding CSS. So you can just go to the style declaration and add a margin. See the padding line in this example:

.myEvents {
	position: absolute;
	text-align: center;
	width: 20px;
	height: 20px;
	line-height: 20px;
	color: white;
	margin-bottom: 20px; //here**
}

Simple Markers and their CSS

Simple markers allow you a quick way to get started with markers that you can easily customize. The CIQ.Marker.Simple class can quickly build DOM nodes and a features a matching set of CSS styles for the nodes to get you started quickly. Using the parameters in the CIQ.Marker.Simple constructor you can add classes to the marker to use either the built in defaults, or quickly create your own markers. To extend the default CSS of simple markers all you need to do is create a new style declaration in CSS with your new type or category as a class name:

.stx-marker.trade .stx-visual {
	background: white;
	width: 12px;
	height: 12px;
}

This code creates a small white marker category of "trade" that can be used easily with either a type of "circle" or "square" to represent when a trade has been made. Since CIQ.Marker.Performance takes the same arguments as Simple markers, you can generally reuse CSS classes between the two if your markers keep the same DOM structure.

Showing events on the chart. Code sample

Once all of the marker placement and styling is in place. Is time to place the markers on the chart.

var stxx = new CIQ.ChartEngine({ container: $$$(".chartContainer") });

function showEvents() {
	var markerTypes = ["dividend", "news", "earnings"],
		newNode;
	// markers for existing ticks
	for (var i = 0; i < stxx.masterData.length; i += 10) {
		var r = Math.floor(Math.random() * (markerTypes.length + 1));
		if (r == markerTypes.length) continue; // randomize
		newNode = $$$("#stxEventPrototype").cloneNode(true);
		newNode.id = null;
		newNode.innerHTML = markerTypes[r].capitalize().charAt(0);
		CIQ.appendClassName(newNode, markerTypes[r]);
		new CIQ.Marker.MyHandler({
			stx: stxx,
			xPositioner: "date",
			x: stxx.masterData[i].DT,
			label: "events",
			node: newNode
		});
	}
	// marker for a future tick
	newNode = $$$("#stxEventPrototype").cloneNode(true);
	newNode.id = null;
	newNode.innerHTML = "F"; // future tick
	CIQ.appendClassName(newNode, "dividend");
	var someDate = new Date(stxx.masterData[stxx.masterData.length - 1].DT);
	someDate = stxx.getNextInterval(someDate, 5); // 5 bars in the future
	new CIQ.Marker.MyHandler({
		stx: stxx,
		xPositioner: "date",
		x: someDate,
		label: "events",
		node: newNode
	});
	stxx.draw();
}

stxx.loadChart("SPY", {
	masterData: sampleData
});

showEvents();

Removing Markers

Markers can remove themselves at any time:

// remove all markers
marker.remove();

Removing by label

For convenience you may also remove markers according to their specified labels:

// add a marker with a label of "events"
new CIQ.Marker.MyHandler({
	// ...
	label: "events"
	// ...
});
// This removes all markers with label=="events"
CIQ.Marker.removeByLabel(stx, "events");

Say for example, you want to provide an interface that allows users to add and remove different types of markers individually. Assume you have News events, Dividends events and Earnings events. You can group them together so they can be removed as a group. This is managed by the 'label' within each marker.

To add them one group at a time, you would have your buttons send into the markers function some sort of label identifier that you can use to determine what group of events the user wants to see. You code should then show just these events and make sure to use the proper label. To remove those types of events you would use the CIQ.Marker.removeByLabel(stxx, evenType); method as stated above.

Your HTML would have something like this:

<div
	class="stx-btn stx-menu-btn toggle stx-collapsible"
	id="menuEvents"
	onclick="toggleEvents('news')"
>
	<span>News</span>
</div>
<div
	class="stx-btn stx-menu-btn toggle stx-collapsible"
	id="menuEvents"
	onclick="toggleEvents('earnings')"
>
	<span>Earnings</span>
</div>
<div
	class="stx-btn stx-menu-btn toggle stx-collapsible"
	id="menuEvents"
	onclick="toggleEvents('dividend')"
>
	<span>Dividends</span>
</div>

Your JS would have something like this:

// object to track th status of each marker group ( displayed or not)
var eventsAreDisplayed = {};

// This function is called by the menu item to turn on or off the particular events
function toggleEvents(typeOfEvent) {
	if (eventsAreDisplayed[typeOfEvent]) {
		// turn off the flag for these events so you know they are no longer displayed
		eventsAreDisplayed[typeOfEvent] = false;
		// remove these type of events
		CIQ.Marker.removeByLabel(stxx, typeOfEvent);
	} else {
		showEvents(typeOfEvent);
		eventsAreDisplayed[typeOfEvent] = true;
	}
}
// This function will get the requested events from the data source and displays them on the chart
function showEvents(typeOfEvent) {
	if (!stxx.masterData) return;
	// your code here to get the events from your source.
	var myEvents = ["your list of events for this type from your back office source"];
	for (var i = 0; i < myEvents.length; i++) {
		// go through your list of markers for this event and add them to the chart
		newNode = $$$("#stxEventPrototype").cloneNode(true);
		newNode.id = null;
		newNode.innerHTML = "Something to show"; // usually an initial for the marker
		CIQ.appendClassName(newNode, typeOfEvent);
		new CIQ.Marker.MyHandler({
			stx: stxx,
			xPositioner: "date",
			x: myEvents[i].date, // the date you want the marker to show on
			label: typeOfEvent, // the type of event so you can delete them by label letter on
			node: newNode
		});
	}
	stxx.draw();
}

Example:

  • Click on the JavaScript, HTML and CSS buttons to see the code for this working sample.
  • Click on the Edit in JSFiddle button to 'play' with this code.

Fixed Markers

Sometimes you may wish to place a marker in a fixed position on the chart. This can be accomplished with the following code:

new CIQ.Marker({
	node: yourNode,
	xPositioner: "none",
	yPositioner: "none"
});

xPositioner and yPositioner when set to none will not attempt to locate the marker. It will be placed in the panel as an absolutely positioned DOM element. Use CSS to alter the position.

Some placement functions may allow you to fix the X position but set a Y position based on value:

new CIQ.Marker.MyHandler({
	node: yourNode,
	xPositioner: "none"
});

Placement of Markers

Markers by default are placed in the "subholder" for a panel. The subholder is a DIV whose borders are the edges of the panel but not including any axis. Markers placed in the subholder will "disappear" as the marker slides underneath the axis (left Y axis, right Y axis and/or x-axis)

There are two alternative locations where you can place markers by setting the following fields in your params:

  • params.includeAxis=true - If this is true then the marker will be placed in the "holder" which is a DIV that spans the entire space of a panel including the axis. Markers placed with this parameter will overlap axis as you scroll the chart.
  • params.chartContainer=true - When this is true the marker is placed in the chart container itself. Placement functions that correctly do the math will still place the marker in the desired spot, but the marker will no longer be bounded to the panel and so long as overflow is displayable on the chart container, the marker can overlap the container itself. This is useful for instance with a movable heads up display so that the edges don't get cut off as the user moves their mouse.

Hover and pop-ups

The default Node Creators do not provide hover capabilities, but as previously stated, markers are simply HTML elements that are moved in sync with the chart as you zoom and pan. Feel free to create these HTML elements with any attributes you wish, including hover capabilities.

Here is a working example of markers with hover functionality:

- Click on the `JavaScript`, `HTML` and `CSS` buttons to see the code for this working sample. - Click on the `Edit in JSFiddle` button to 'play' with this code.

Also if you want to have the default markers show the pop ups by default, you can programmatically activate the pop ups by executing the following line right after you add each marker:

var marker = new CIQ.Marker(params);
CIQ.toggleClassName(marker.node, "highlight");

Managing Performance

The standard ChartIQ placement functions include code to reduce the amount of DOM manipulation by checking whether a marker is off-screen. If already off-screen then positioning of the marker is skipped. But if you are using DOM objects as your markers, it can get expensive to move them around the screen; especially if you have many of them.

By default, markers are moved only once per 25 milliseconds. This produces a visual delay effect when panning the chart but dramatically improves chart performance. You can modify this default with the stxx.markerDelay variable (CIQ.ChartEngine#markerDelay). Set this to zero or null to eliminate any delay, or set to the number of milliseconds that your system requires.

When thousands of HTML markers need to be placed on the chart, and performance becomes an issue, you may opt to directly render the markers on the canvas itself. You can use CIQ.Marker.Performance to create canvas markers directly. With a performance marker, you apply CSS styles to an HTML template but never append the template. Instead you use those styles to draw the marker directly on the canvas.

Markers and the quoteFeed

If you wish to link the display of markers to the display of that being loaded via a quoteFeed, you must add additional code to keep them in sync.

The process is simple. Inside your fetch functions, create logic to make a second call that retrieves the required markets for the same time period, and load them into the chart using the methods described above.

The following is an example using the 'fetchPaginationData' function from our simulator, but you can use the same outline on your fetch functions, which should then just call a function similar to 'addQuoteFeedMarkers' right after the callback is executed.

Add this logic to fetchInitalData, fetchPaginationData and fetchUpdateData as needed.

For more details on creating a quoteFeed see the Quotefeed Tutorial

function addQuoteFeedMarkers(newQuotes, symbol, startDate, endDate, params) {
	// if (markers are off) return;
	var stxx = params.stx;

	// This is an example of a data array to drive the marker creation.
	// In your case add code here to get your marker data from your data source.

	/** begin data simulation **/
	var l = newQuotes.length;
	var standardType = "circle";
	var data = [
		{
			x: stxx.masterData[l - 5].DT,
			type: standardType,
			category: "news",
			headline: "This is a Marker for a News Item"
		},
		{
			x: stxx.masterData[l - 15].DT,
			type: standardType,
			category: "earningsUp",
			headline: "This is a Marker for Earnings (+)"
		},
		{
			x: stxx.masterData[l - 25].DT,
			type: standardType,
			category: "earningsDown",
			headline: "This is a Marker for Earnings (-)"
		},
		{
			x: stxx.masterData[l - 35].DT,
			type: standardType,
			category: "dividend",
			headline: "This is a Marker for Dividends"
		},
		{
			x: stxx.masterData[l - 45].DT,
			type: standardType,
			category: "filing",
			headline: "This is a Marker for a Filing"
		},
		{
			x: stxx.masterData[l - 55].DT,
			type: standardType,
			category: "split",
			headline: "This is a Marker for a Split"
		}
	];
	/** end data simulation **/

	var story =
		"Like all ChartIQ markers, the object itself is managed by the chart, so when you scroll the chart the object moves with you. It is also destroyed automatically for you when the symbol is changed.";

	// Loop through the data and create markers
	for (var i = 0; i < data.length; i++) {
		var datum = data[i];
		datum.story = story;
		var mparams = {
			stx: stxx,
			label: standardType,
			xPositioner: "date",
			// make sure you add a master data placeholder if you wish to add a
			// marker where a data point does not exist.
			x: datum.x,
			// Allow markers to float out of chart. Set css .stx-marker{ z-index:20}
			// chartContainer: true,
			node: new CIQ.Marker.Simple(datum)
		};

		var marker = new CIQ.Marker(mparams);
	}

	// if you want to display the markers right away, call draw
	stxx.draw();
}

// called by chart to fetch pagination data
quoteFeedSimulator.fetchPaginationData = function(symbol, startDate, endDate, params, cb) {
	var queryUrl =
		this.url +
		"&identifier=" +
		symbol +
		"&startdate=" +
		startDate.toISOString() +
		"&enddate=" +
		endDate.toISOString() +
		"&interval=" +
		params.interval +
		"&period=" +
		params.period;

	CIQ.postAjax(queryUrl, null, function(status, response) {
		// process the HTTP response from the datafeed
		if (status == 200) {
			// if successful response from datafeed
			var newQuotes = quoteFeedSimulator.formatChartData(response);
			cb({
				quotes: newQuotes,
				attribution: {
					source: "simulator",
					exchange: "RANDOM"
				}
			}); // return fetched data (and set moreAvailable)

			/****************** add your markers now *********************/
			addQuoteFeedMarkers(newQuotes, symbol, startDate, endDate, params);
			/*************************************************************/
		} else {
			// else error response from datafeed
			cb({
				error: response ? response : status
			}); // specify error in callback
		}
	});
};

Next Steps: