Tutorials
Getting started
Chart interface
Web components
Chart internals
Data integration
Customization
Frameworks and bundlers
Mobile development
Plug-ins
Troubleshooting
Glossary
Reference
JSFiddles

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 are 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.inheritsFrom(CIQ.Marker.MyHandler, 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. Be sure the same timezone as masterData is used.
  • "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 = document.querySelector("#stxEventPrototype").cloneNode(true);
newNode.id = null;
newNode.innerHTML = "F"; // future tick
newNode.classList.add("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 classList.add(). Otherwise, the marker may not be rendered if elements are missing. In the above example -newNode.classList.add("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.

Creating the marker's 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**
}

Node Creators as an alternative to explicitly Creating DOM elements.

For convenience, ChartIQ includes the Simple Marker Node creator that can create simple marker nodes dynamically. Circles, Squares and Callouts are supported.

Here is an example on how to use the Simple node creator:

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 simple node creators, 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);
marker.node.classList.toggle("highlight"); // <-- add this line

ChartIQ also includes a High Performance Marker Node creator to address cases when a very large number of markers need to be placed on a chart. High performance markers are a hybrid between HTML and Canvas.

Node Creators and their CSS

Node Creators allow you a quick way to get started with markers that you can easily customize. CIQ.Marker.Simple and CIQ.Marker.Performance 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 node creator constructor you can add classes to the marker to use either the built-in defaults, or quickly create your own markers. To extend the node creator default CSS, 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 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, it is time to place the markers on the chart.

var stxx = new CIQ.ChartEngine({ container: document.querySelector(".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 = document.querySelector("#stxEventPrototype").cloneNode(true);
		newNode.id = null;
		newNode.innerHTML = markerTypes[r].capitalize().charAt(0);
		newNode.classList.add(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 = document.querySelector("#stxEventPrototype").cloneNode(true);
	newNode.id = null;
	newNode.innerHTML = "F"; // future tick
	newNode.classList.add("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 = document.querySelector("#stxEventPrototype").cloneNode(true);
		newNode.id = null;
		newNode.innerHTML = "Something to show"; // usually an initial for the marker
		newNode.classList.add(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);
marker.node.classList.toggle("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). Either set it to zero or null to eliminate any delay or set it 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, 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.

There are two approaches to this, depending on where the marker data is coming from.

For more details on creating a quoteFeed see the Quotefeed Tutorial.

Fetching marker data from the main symbol's quoteFeed

In this approach, the data for the markers comes from the same quoteFeed as the data for the main symbol. For example, the following record may be returned:

{
	date: 2021-04-04T14:00:00Z,
	open: 34.93,
	close: 33.98,
	high: 35.32,
	low: 33.8,
	volume: 12300,
	...
	news: [
		{
			title: "Investment in Foreign Markets Seems to Pay Off",
			url: "https://newssource.com/article/4303284/Inv_Pay_Off.html"
		}
	]
}

In this case, you can create a function in your quoteFeed implementation and call it when you are formatting the incoming data. Be sure that it is reachable from fetchInitialData, fetchPaginationData and fetchUpdateData methods.

Suppose we call this function addQuoteFeedMarkers. It might look like this:

function addQuoteFeedMarkers(params, results) {
	var stx = params.stx;

	// Loop through the data and create markers
	results.forEach(function (record) {
		var news = record.news;
		if (news && news.length) {
			var type = "circle";
			news.forEach(function (item) {
				var marker = new CIQ.Marker({
					stx: stx,
					label: type,
					xPositioner: "date",
					x: new Date(record.date),
					node: new CIQ.Marker.Simple({
						type: type,
						category: "news",
						headline: item.title,
						story: "Click here for article: <a href='" + item.url + "' target='_blank'>" + item.url + "</a>"
					})
				});
			});
		};
	});

	// if you want to display the markers right away, call draw
	stx.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(params, newQuotes);
			/*************************************************************/
		} else {
			// else error response from datafeed
			cb({
				error: response ? response : status
			}); // specify error in callback
		}
	});
};

Fetching marker data from a different quoteFeed

(New for 9.0.0)

If the data for the markers comes from a separate quoteFeed, then the approach to loading them is different. In this case, we treat the event similar to how we would treat a series. We call this special series an "event", and the markers it creates, "event markers".

There are three functions that can be used to maintain events:

These functions are analogous to the addSeries, removeSeries, and removeAllSeries functions, with the important difference that the event data is not stored anywhere within the library. In fact, calling addEvent will create an actual series in stxx.chart.series.

One important thing to note about the addEvent function is that is sets the isEvent property in the series parameters, which will cause the symbolObject of the series to also have the property isEvent=true. This allows us to set up a new quoteFeed for the event using the isEvent flag as a filter. Here is an example of attaching an event feed:

	stx.attachQuoteFeed(
		eventfeed,  // engine, you create this quoteFeed.
		{  // behavior
			refreshInterval: 5,
			backgroundRefreshInterval: 60,
			bufferSize: 200
		},
		function (params) {  // filter
			return params.symbolObject.isEvent;
		}
	);

Multiple quoteFeeds can be set up by filtering on symbol as well. An example eventFeed is provided in the /examples/feeds/ folder of your license package.

Suppose we want to fetch news events. Here's how we would do it:

stxx.addEvent("news", params);

The addEvent function will call addSeries, so any parameters supported by addSeries are supported by addEvent as well. By default the addEvent function will attempt to add Simple markers to the chart after data is fetched. You can customize this action by passing a processResults parameter into addEvent.

When the event is removed, the markers will be removed by default. To change this behavior, pass a takedownResults parameter to addEvent to redefine what happens when the event is removed.

Here is an example of creating and removing event markers:

Assume records take the following form:

{
	DT: new Date("2021-04-04T14:00:00Z"),
	headline: "Investment in Foreign Markets Seems to Pay Off",
	url: "https://newssource.com/article/4303284/Inv_Pay_Off.html"
}

Here are sample processResults and takedownResults functions to create and remove markers:

parameters.processResults = function (stx, error, series, records) {
		if (error) return;

		if (records && records.length) {
			var type = "circle";
			records.forEach(function (record) {
				var item = record.data;
				if (item) {
					var marker = CIQ.Marker({
						stx: stx,
						label: type,
						xPositioner: "date",
						x: record.DT,
						node: new CIQ.Marker.Simple({
							type: type,
							category: "news",
							headline: item.headline,
							story: "Click here for article: <a href='" + item.url + "' target='_blank'>" + item.url + "</a>"
						})
					});
				}
			});
		}
		stx.draw();
};
parameters.takedownResults = function (stx, seriesId) {
	CIQ.Marker.removeByLabel(stx, seriesId);
};

JSFiddle examples

Next steps