Getting started
Chart interface
Web components
Chart internals
Data integration
Frameworks and bundlers
Mobile development
Time Span Events
Term structures

Customizing the X-Axis

Using a variety of different tools and configurations, you can fully customize rendering of the x-axis.

Background information

The x-axis is self-adjusting to display labels (times/dates/months/years) in a way they will not overlap and with enough space between them to prevent crowding. As such, depending on the zoom level, there may not be enough space to display every label on the axis and some will be skipped.

Additionally, the x-axis uses ‘precedence’ logic, giving labels different rankings. Labels with higher rankings will take precedence over labels with lower rankings. For example, if ‘month’ labels are displayed on a chart, but the data spans 2 years, the 'year' label will take precedence over a month label.

Here is an example:
self-adjusting x axis

You can see Oct, Dec, 2020, Feb.

Jan is not displayed because 2020 is shown instead to let the user know a new year has started. Sep, Nov and Mar are not displayed to prevent crowding.

You may prefer a different type of display. The good news is that this is all configurable and you can adjust your axis labels to be spaced as you see fit and to be as granular as you want.

First, review the complete list of available parameters to see if you can accomplish your desired customization by adjusting a flag. A complete list can be found at CIQ.ChartEngine.XAxis

Also see:

Colors and fonts

These can be controlled by manipulating the CSS styles associated with the x-axis.

You can override the stx_xaxis class in stx-chart.css to change the font and the color. Remember that if you are using the default dark theme you will need to override the defaults for .ciq-night .stx_xaxis style.

Fully customized X-Axis

Option 1:

Set CIQ.ChartEngine.XAxis#formatter to a function which will override the x-axis labels to anything you want.

Option 2:

Use the locale/internationalizer to format dates.


CIQ.I18N.setLocale(stxx, "en"); // set localization services -- before any UI or chart initialization is done
// override date formatting of the intraday hover label to add year
stxx.internationalizer.monthDay = new Intl.DateTimeFormat(this.locale, {
	year: "numeric",
	month: "numeric",
	day: "numeric"

// override time formatting on both the hover and the x axis s labels to enable 12 hour clock (hour12:true)
stxx.internationalizer.hourMinute = new Intl.DateTimeFormat(this.locale, {
	hour: "numeric",
	minute: "numeric",
	hour12: true
stxx.internationalizer.hourMinuteSecond = new Intl.DateTimeFormat(this.locale, {
	hour: "numeric",
	minute: "numeric",
	second: "numeric",
	hour12: true

// override time formatting to enable 12 hour clock (hour12:true)
stxx.internationalizer.hourMinute = new Intl.DateTimeFormat(this.locale, {
	hour: "numeric",
	minute: "numeric",
	hour12: true
stxx.internationalizer.hourMinuteSecond = new Intl.DateTimeFormat(this.locale, {
	hour: "numeric",
	minute: "numeric",
	second: "numeric",
	hour12: true

More details on localization can be found in the Localization tutorial.

Option 3:

Use an API injection on createXAxis() to totally override all of the default functionality. This is an advanced feature and only recommended for someone very experienced with the library.

CIQ.ChartEngine.prototype.prepend("createXAxis", function(chart) {
	// add your logic to create your own X-Axis. We can help...
	//return axisRepresentation; // This is your array of CIQ.ChartEngine.XAxisLabel() elements used by CIQ.ChartEngine.drawXAxis()

See CIQ.ChartEngine.AdvancedInjectable#createXAxis for function signature and other details.

The injection should return an axisRepresentation array (normally created internally by createXAxis() ). drawXAxis() uses this array to draw the axis. Your injection can put anything you want to see in the x-axis labels (text). Each entry should specify the type of label (boundary or line) and the pixel location where you want to set it (hz). This is done by pushing CIQ.ChartEngine.XAxisLabel() elements into the axisRepresentation array.

See CIQ.ChartEngine.XAxisLabel for additional details on format and requirements.

For instance, call either:

axisRepresentation.push(new CIQ.ChartEngine.XAxisLabel(hz,"boundary",text));


axisRepresentation.push(new CIQ.ChartEngine.XAxisLabel(hz,"line",text));

Here is a sample of what the resulting axisRepresentation array will look like. Notice there are both "line" and "boundary" type labels, and only bars needing a label will be present in the array :

	{ "hz": -969, "grid": "boundary", "text": "9/4" },
	{ "hz": -965, "grid": "line", "text": "0:00" },
	{ "hz": -773, "grid": "line", "text": "4:00" },
	{ "hz": -581, "grid": "line", "text": "8:00" },
	{ "hz": -389, "grid": "line", "text": "12:00" },
	{ "hz": -197, "grid": "line", "text": "16:00" },
	{ "hz": -5, "grid": "line", "text": "20:00" },
	{ "hz": 183, "grid": "boundary", "text": "9/5" },
	{ "hz": 187, "grid": "line", "text": "0:00" },
	{ "hz": 379, "grid": "line", "text": "4:00" },
	{ "hz": 571, "grid": "line", "text": "8:00" },
	{ "hz": 763, "grid": "line", "text": "12:00" },
	{ "hz": 955, "grid": "line", "text": "16:00" },
	{ "hz": 1147, "grid": "line", "text": "20:00" },
	{ "hz": 1335, "grid": "boundary", "text": "9/6" },
	{ "hz": 1339, "grid": "line", "text": "0:00" },
	{ "hz": 1531, "grid": "line", "text": "4:00" },
	{ "hz": 1723, "grid": "line", "text": "8:00" },
	{ "hz": 1915, "grid": "line", "text": "12:00" }

For example, sometimes it is not desirable to display partial date labels, which sometimes appear on the left and right of the chart depending on selected range. Often this is a requirement for charts that need to be printed. This is an example of an injection that removes the edge labels from the chart in the event they do not completely fit on the axis.

CIQ.ChartEngine.prototype.prepend("createXAxis", function(chart) {
	var axisRepresentation = this.createTickXAxisWithDates(chart);
	var newAxisRepresentation=[];
	for (var i = 0; i < axisRepresentation.length; i++) {

			// exclude if you want to see major boubdries on the axis.
			if(axisRepresentation[i].grid == "boundary") continue;

			// adjust this buffer size (20)  as needed to manage the display of the left-most label.
			if(axisRepresentation[i].hz < 20) continue;

			//adjust this buffer size (10)  as needed to manage the display of the right-most label.
			if(axisRepresentation[i].hz > this.chart.width-10) continue;

	return newAxisRepresentation;

Your injection will also need to create the chart.xaxis array which is used by the HUD and cross-hair labels as you slide the mouse through the chart.

This array takes objects of the following format:

var obj = {
	DT: dataSegment[i].DT, // bar date
	data: dataSegment[i] // bar data

and should contain an entry for every bar in the current dataSegment as well as any data you wish to display in the areas where the is no chart data.

The following image shows an area where there is no data. On those, you can just send the DT field so at least a date can be shown, and the data portion can just be 'null':

No data on x axis

You can add each item like so:


Here is a subset sample of what the resulting xaxis array will look like (showing first 3 elements with data and last few elements where there is no data):

		"DT": "2018-09-05T03:40:00.000Z",
		"data": {
			"DT": "2018-09-05T03:40:00.000Z",
			"Open": 105.43,
			"High": 106.28,
			"Low": 105.17,
			"Close": 106.14,
			"Volume": 1285494,
			"Date": "20180904234000000",
			"displayDate": "2018-09-05T03:40:00.000Z",
			"ratio": 1,
			"iqPrevClose": 106,
			"atr": 1.2086354208048136,
			"trueRange": 1.1099999999999994,
			"hl/2": 105.725,
			"hlc/3": 105.86333333333333,
			"hlcc/4": 105.9325,
			"ohlc/4": 105.755,
			"cache": {},
			"tick": 765,
			"candleWidth": null
		"DT": "2018-09-05T03:50:00.000Z",
		"data": {
			"DT": "2018-09-05T03:50:00.000Z",
			"Open": 105.75,
			"High": 106.31,
			"Low": 105.18,
			"Close": 106.09,
			"Volume": 1162072,
			"Date": "20180904235000000",
			"displayDate": "2018-09-05T03:50:00.000Z",
			"ratio": 1,
			"iqPrevClose": 106.14,
			"atr": 1.2047036497645727,
			"trueRange": 1.1299999999999955,
			"hl/2": 105.745,
			"hlc/3": 105.86000000000001,
			"hlcc/4": 105.9175,
			"ohlc/4": 105.83250000000001,
			"cache": {},
			"tick": 766,
			"candleWidth": null
		"DT": "2018-09-05T04:00:00.000Z",
		"data": {
			"DT": "2018-09-05T04:00:00.000Z",
			"Open": 105.03,
			"High": 105.63,
			"Low": 104.7,
			"Close": 105.35,
			"Volume": 944560,
			"Date": "20180905000000000",
			"displayDate": "2018-09-05T04:00:00.000Z",
			"ratio": 1,
			"iqPrevClose": 106.09,
			"atr": 1.213968467276344,
			"trueRange": 1.3900000000000006,
			"hl/2": 105.16499999999999,
			"hlc/3": 105.22666666666665,
			"hlcc/4": 105.2575,
			"ohlc/4": 105.17750000000001,
			"cache": {},
			"tick": 767,
			"candleWidth": null
	{ "DT": "2018-09-07T09:00:00.000Z", "data": null },
	{ "DT": "2018-09-07T09:10:00.000Z", "data": null },
	{ "DT": "2018-09-07T09:20:00.000Z", "data": null },
	{ "DT": "2018-09-07T09:30:00.000Z", "data": null },
	{ "DT": "2018-09-07T09:40:00.000Z", "data": null },
	{ "DT": "2018-09-07T09:50:00.000Z", "data": null },
	{ "DT": "2018-09-07T10:00:00.000Z", "data": null },
	{ "DT": "2018-09-07T10:10:00.000Z", "data": null },
	{ "DT": "2018-09-07T10:20:00.000Z", "data": null },
	{ "DT": "2018-09-07T10:30:00.000Z", "data": null },
	{ "DT": "2018-09-07T10:40:00.000Z", "data": null },
	{ "DT": "2018-09-07T10:50:00.000Z", "data": null },
	{ "DT": "2018-09-07T11:00:00.000Z", "data": null },
	{ "DT": "2018-09-07T11:10:00.000Z", "data": null },
	{ "DT": "2018-09-07T11:20:00.000Z", "data": null },
	{ "DT": "2018-09-07T11:30:00.000Z", "data": null },
	{ "DT": "2018-09-07T11:40:00.000Z", "data": null }

Option 4:

Allow the default axisRepresentation array to be created, but intercept it and adjust it before it is used. For this you can use an API injection similar to this:

CIQ.ChartEngine.prototype.prepend("createXAxis", function(chart) {
	axisRepresentation = this.createTickXAxisWithDates(chart);
	// add you logic here to adjust the default axisRepresentation array as needed
	// iterate through each element and adjust labels(text) or horizontal location (hz)
	return axisRepresentation;
Equidistant grid - injection sample:

Here is a fully functional example of an injection that removes the boundary lines, which depending on periodicity can produce an uneven grid. It then prints the boundary labels as regular labels where it makes sense to do so. The result is a more equidistant grid, at the expense of not always seeing the boundary lines.

This is the code:

More granular x-axis

CIQ.ChartEngine#createTickXAxisWithDates is the method called by CIQ.ChartEngine.AdvancedInjectable#createXAxis to generate the axisRepresentation object. It is algorithmically designed to create an x-axis that is responsive to various amounts of user panning, zooming, and periodicity selection.

Depending on those factors, it will print different dates or times formats, will prevent label overlaps, and will evenly space labels. It will do this by finding the closest match in a pre-set timeInterval map, which contains allowable label values. (This algorithm will only be used if CIQ.ChartEngine.XAxis#timeUnit and CIQ.ChartEngine.XAxis#timeUnitMultiplier are not set).

The map contains an entry for every time unit that can be display (second, minute, hour, etc). Each map entry contains:

arr: list of possible timeUnit Multipliers
minTimeUnit: lower range for the interval
maxTimeUnit: upper range for the interval

The default map is as follows, but you can override this as well:

stxx.timeIntervalMap = {};
stxx.timeIntervalMap[CIQ.MILLISECOND] = {
	// a label will be attempted to be placed every
	// 1,2,5,10,20,50,100,250 or 500 MILLISECONDS
	// as space permits.
	arr: [1, 2, 5, 10, 20, 50, 100, 250, 500],
	minTimeUnit: 0,
	maxTimeUnit: 1000
stxx.timeIntervalMap[CIQ.SECOND] = {
	arr: [1, 2, 5, 10, 15, 30],
	minTimeUnit: 0,
	maxTimeUnit: 60
stxx.timeIntervalMap[CIQ.MINUTE] = {
	arr: [1, 2, 5, 10, 15, 30],
	minTimeUnit: 0,
	maxTimeUnit: 60
stxx.timeIntervalMap[CIQ.HOUR] = {
	arr: [1, 2, 3, 4, 6, 12],
	minTimeUnit: 0,
	maxTimeUnit: 24
stxx.timeIntervalMap[CIQ.DAY] = {
	arr: [1, 2, 7, 14],
	minTimeUnit: 1,
	maxTimeUnit: 32
stxx.timeIntervalMap[CIQ.MONTH] = {
	arr: [1, 2, 3, 6],
	minTimeUnit: 1,
	maxTimeUnit: 13
stxx.timeIntervalMap[CIQ.YEAR] = {
	arr: [1, 2, 3, 5],
	minTimeUnit: 1,
	maxTimeUnit: 20000000
stxx.timeIntervalMap[CIQ.DECADE] = {
	arr: [10],
	minTimeUnit: 0,
	maxTimeUnit: 2000000

The timeUnit multipliers will be used in the order in which they appear as long as the resulting label date is within the minTimeUnit and maxTimeUnit range.

For example, if the chart is currently displaying a zoom level where day labels should be displayed as determined by time differential and distance between bars, the following entry on the map will be activated:

stxx.timeIntervalMap[CIQ.DAY] = {
	arr: [1, 2, 7, 14],
	minTimeUnit: 1,
	maxTimeUnit: 32

The algorithm will first attempt to add a label every 1 day. If there is not enough room, it will attempt every 2 days and so on. Once the correct multiplier is found, that multiplier will be used until maxTimeUnit is reached. So if there is only room to fit daily labels every 7 days, you will see them on dates that are multiples of 7. i.e. there will be a label on :

  • 7th of the month
  • 14th or the month
  • 21st of the month
  • 28th of the month

Assuming those dates match existing bars - otherwise they will be skipped.

Example for overriding the default algorithm with hard coded values:

stxx.chart.xAxis.timeUnit = CIQ.DAY;
stxx.chart.xAxis.timeUnitMultiplier = 7;

The above will display a chart with daily labels every 7 days.

Labels every 7 days

This kind of hard coded approach is only recommended for static charts because you will need to manually adjust your override as your periodicity changes to maintain a reasonable interval between tags.


Once label spacing has been calculated, the rendering process will begin. Just because a specific spacing has been set, doesn't mean there will be enough space to use it. When in tick periodicity, for example, since tick charts do not have a constant time interval, there sometimes won't be enough ticks to create enough space for a label to display, so the label will be skipped at rendering time. To address this, you can try changing the zoom level by setting a smaller span and thereby creating more space between the ticks. See CIQ.ChartEngine#setSpan and Understanding Chart Range.

You can also try to change the xaxis label width settings to allow less whitespace between the labels using CIQ.ChartEngine.XAxis#minimumLabelWidth


stxx.chart.xAxis.minimumLabelWidth = 20;

Customizing the the floating label over the x axis

The floating label over the x axis uses the same formatting functions available for the rest of the x axis labels. As outlined above, you can use the CIQ.ChartEngine.XAxis#formatter or locale/internationalizer.

But if you need to format the floating label independent of other formatting, override the CIQ.displayableDate to return the label formatted any way you need it.

Using multiple methods at once.

You can also combine multiple methods to create the desired look.

For example, the following code will first display a 5 day span of data, and then format the x axis labels so they display every 7 days and they are formatted in dd/mm fashion.

		multiplier: 5,
		base: "day"
	function() {
		stxx.chart.xAxis.timeUnit = CIQ.DAY; // set to 'null' when you no longer want this.
		stxx.chart.xAxis.timeUnitMultiplier = 1; // set to 'null' when you no longer want this.
		stxx.chart.xAxis.formatter = function(labelDate, gridType, timeUnit, timeUnitMultiplier) {
			// set to null when you no longer want this.
			var text = labelDate.getDate() + "/" + labelDate.getMonth();
			return text;

Labels every 7 days custom

Next steps