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

For versions 8.3.x and prior, click here.

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 Jul, Oct, 2021, and Apr.

Jan is not displayed because 2021 is shown instead to let the user know a new year has started. The other intervening months are not displayed to prevent crowding.

Depending on the height defined for the x-axis (CIQ.ChartEngine#xaxisHeight), the labels will occupy either one row or two. By default the height is set to accommodate two rows of labels.

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. See CIQ.ChartEngine#drawXAxis for other relevant CSS styles that you can manipulate.

Fully customized x-axis

There are several ways you can redefine the way the x-axis labels are displayed.

Option 1

You can define the "level" of each label, causing them to be ranked hierarchically. You can also define the actual text to be displayed on each label. This is defined in the CIQ.ChartEngine.XAxis.setPropertiesForInterval object. Below is a more in-depth explanation.

CIQ.ChartEngine.createSpacedDateXAxis 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, the method prints different date or time formats, prevents label overlaps, and evenly spaces labels. It does this by ranking the label's raw value. Values representing milestones are given a higher ranking as represented by the level property (the lower the level, the higher the ranking). For example, a label representing midnight receives a higher ranking than a label of 10 a.m., because midnight represents a milestone of a day change.

Labels that represent the first of its kind in years, months, days, etc. are also milestones. The definition of a milestone is computed in functions belonging to the CIQ.ChartEngine.XAxis.setPropertiesForInterval object.

For a line-type label, the level is automatically increased by CIQ.ChartEngine.XAxis.lineBaseLevel. Therefore, avoid setting levels above this value in the functions within this object, as it may cause collisions with other grid-type values.

If two labels with the same level are determined to overlap each other, the entire level is suppress from rendering. In that way, you can assure that the x-axis displays only those labels most relevant to the span in which you are looking.

If a line label overlaps with a boundary label, that label is suppressed, but other line labels of the same level may continue to display as long as they are not overlapping with another line label.

If two line labels or two boundary labels overlap, all the labels of the higher level and up are all suppressed from rendering.

The following code block is the default implementation of how the properties of a month label are assigned:

CIQ.ChartEngine.XAxis.setPropertiesForInterval = {
	...
	month: (label, record, market, stx) => {
		const t = Number(label.raw),
			d = record.DT,
			y = d.getFullYear();
		// A January of year divisible by 5 gets the highest ranking, followed by any other January.
		if (t === 0) label.level = y % 5 === 0 ? 0 : 1;
		// Months beginning a quarter get the next highest priority, followed finally by any other month
		else label.level = t % 3 === 0 ? 2 : 3;

		label.text = CIQ.monthAsDisplay(t, false, stx);
		if (label.grid === "boundary") {
			// Display month only (no year) if only boundaries are displayed
			// but display year only for Jan
			label.boundaryOnlyText = t ? label.text : y.toString();
			// Otherwise display month and year
			label.text += " '" + y.toString().substring(2);
			if (!t) label.alwaysAsBoundary = true;
		}
	},
	...
}

A custom implementation may look like this:

CIQ.ChartEngine.XAxis.setPropertiesForInterval.month = (label, record, market, stx) => {
	// don't assign a ranking and just use M/YYYY format for the labeling always.
	label.text = (Number(label.raw)+1)+"/"+record.DT.getFullYear();
}

Option 2

If you do not want to use the ranking algorithm described above, and instead want to use the "brute force" method of fitting labels on the axis, you may set this property:

stxx.chart.xAxis.fitLeftToRight = true;

This is a slightly different approach. It still respects the rankings in terms of which label gets displayed when there is overlap, but it does not suppress all the other labels with that level.

For example, suppose there were 3 labels of the same level, and the label width was 50 pixels:

[
	{	hz: 10, text: "Jan", grid: "line", level: 500 },  
	{	hz: 40, text: "Feb", grid: "line", level: 500 },  
	{	hz: 70, text: "Mar", grid: "line", level: 500 }
]  

Using the default algorithm, if the first two records overlap, none of the three records would be rendered since level 500 would be completely suppressed. However, with the setting of fitLeftToRight=true, "Jan" will render, "Feb" will not render, but "Mar" will render, since nothing is overlapping with it.

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, grid: "boundary", text }));

or

    axisRepresentation.push(new CIQ.ChartEngine.XAxisLabel({ hz, grid: "line", text }));

Here is a sample of what the some of the resulting axisRepresentation array may look like. Notice there are a few more fields which are generated when creating the XAxisLabel. Also, every record is present in the axisRepresentation regardless of whether its corresponding label is actually printed. See CIQ.ChartEngine.XAxisLabel and CIQ.ChartEngine.XAxis.setPropertiesForInterval for details on the various properties below.

[
 {
  "hz": 10.147355731389755,
  "grid": "boundary",
  "raw": 3,
  "level": 2,
  "text": "Apr '19",
  "boundaryOnlyText": "Apr"
 },
 {
  "hz": 45.66310079125388,
  "grid": "boundary",
  "raw": 4,
  "level": 3,
  "text": "May '19",
  "boundaryOnlyText": "May"
 },
 {
  "hz": 82.87007180634967,
  "grid": "boundary",
  "raw": 5,
  "level": 3,
  "text": "Jun '19",
  "boundaryOnlyText": "Jun"
 },
 {
  "hz": 116.69459091098227,
  "grid": "boundary",
  "raw": 6,
  "level": 2,
  "text": "Jul '19",
  "boundaryOnlyText": "Jul"
 },
 {
  "hz": 153.90156192607813,
  "grid": "boundary",
  "raw": 7,
  "level": 3,
  "text": "Aug '19",
  "boundaryOnlyText": "Aug"
 },
 {
  "hz": 191.108532941174,
  "grid": "boundary",
  "raw": 8,
  "level": 3,
  "text": "Sep '19",
  "boundaryOnlyText": "Sep"
 },
 {
  "hz": 224.9330520458066,
  "grid": "boundary",
  "raw": 9,
  "level": 2,
  "text": "Oct '19",
  "boundaryOnlyText": "Oct"
 },
 {
  "hz": 263.83124901613405,
  "grid": "boundary",
  "raw": 10,
  "level": 3,
  "text": "Nov '19",
  "boundaryOnlyText": "Nov"
 },
 {
  "hz": 297.65576812076665,
  "grid": "boundary",
  "raw": 11,
  "level": 3,
  "text": "Dec '19",
  "boundaryOnlyText": "Dec"
 },
 {
  "hz": 333.1715131806309,
  "grid": "boundary",
  "raw": 0,
  "level": 0,
  "text": "Jan '20",
  "boundaryOnlyText": "2020",
  "anchor": true
 },
 {
  "hz": 368.6872582404951,
  "grid": "boundary",
  "raw": 1,
  "level": 3,
  "text": "Feb '20",
  "boundaryOnlyText": "Feb"
 },
 {
  "hz": 400.8205513898961,
  "grid": "boundary",
  "raw": 2,
  "level": 3,
  "text": "Mar '20",
  "boundaryOnlyText": "Mar"
 },
 {
  "hz": 438.02752240499194,
  "grid": "boundary",
  "raw": 3,
  "level": 2,
  "text": "Apr '20",
  "boundaryOnlyText": "Apr"
 },
 {
  "hz": 473.54326746485617,
  "grid": "boundary",
  "raw": 4,
  "level": 3,
  "text": "May '20",
  "boundaryOnlyText": "May"
 },
 {
  "hz": 0.8456129776158129,
  "grid": "line",
  "raw": 22,
  "level": 505,
  "text": "22"
 },
 {
  "hz": 2.5368389328474388,
  "grid": "line",
  "raw": 25,
  "level": 504,
  "text": "25"
 },
 {
  "hz": 4.228064888079064,
  "grid": "line",
  "raw": 26,
  "level": 505,
  "text": "26"
 },
 {
  "hz": 5.919290843310691,
  "grid": "line",
  "raw": 27,
  "level": 505,
  "text": "27"
 },
 {
  "hz": 7.610516798542317,
  "grid": "line",
  "raw": 28,
  "level": 505,
  "text": "28"
 },
 {
  "hz": 9.301742753773944,
  "grid": "line",
  "raw": 29,
  "level": 505,
  "text": "29"
 },
 {
  "hz": 10.99296870900557,
  "grid": "line",
  "raw": 1,
  "level": 504,
  "text": "1"
 },
 {
  "hz": 12.684194664237197,
  "grid": "line",
  "raw": 2,
  "level": 505,
  "text": "2"
 },
 {
  "hz": 14.375420619468823,
  "grid": "line",
  "raw": 3,
  "level": 505,
  "text": "3"
 },
 ...
]

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. The following code 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.createSpacedDateXAxis(chart);
	var newAxisRepresentation=[];
	for (var i = 0; i < axisRepresentation.length; i++) {

			// exclude if you want to see major boundaries 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;

			newAxisRepresentation.push(axisRepresentation[i]);
	}
	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:

chart.xaxis.push(obj);

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. You can use an API injection similar to this:

CIQ.ChartEngine.prototype.prepend("drawXAxis", function(chart, axisRepresentation){
	// add you logic here to adjust the default axisRepresentation array as needed
	// iterate through each element and adjust labels (text) or horizontal location (hz)
});

Option 5

Replace the createXAxis function by implementing your own. This method is most useful when you need to create an x-axis which is specific to a certain renderer and is not date-based. To do this, create a function that returns an axisRepresentation and assign the function to the renderer's createXAxis property:

stxx.myRenderer.createXAxis = ((chart) => {
	const axisRepresentation = [];
  let hz = chart.left;
  chart.xaxis = [];
	chart.dataSegment.forEach((data, index) => {
		hz += stxx.layout.candleWidth;
		axisRepresentation.push(
			new CIQ.ChartEngine.XAxisLabel(
				{ hz, raw: index, grid: "line", level: index % 10 ? 1 : 0 }
			)
		);
		chart.xaxis.push( { index, data } );
	});
	return axisRepresentation;
});

Rendering

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 x-axis label width settings to allow less whitespace between the labels using CIQ.ChartEngine.XAxis#minimumLabelWidth and CIQ.ChartEngine.XAxis#fitTight.

Example:

stxx.chart.xAxis.minimumLabelWidth = 20;  // reserves at least 20 pixels for each label (default is 50)
stxx.chart.fitTight = true;  // removes right padding to allow greater compression of axis

Customizing the the floating label over the x-axis

The floating label over the x-axis displays text based on the values stored in the chart.xaxis array's entries.

  • If a DT key is found with a value, CIQ.displayableDate will be used to format that date and use it as text on the label. This formatting may be overridden by using 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.
  • If no DT key is found, the value of the text on the floating label becomes the value of the index key instead. You can set the index and remove the DT entries either within CIQ.ChartEngine.XAxis.setPropertiesForInterval (by mutating record), within a createXAxis injection, or by creating the xaxis array yourself within your own createXAxis function.
  • If neither key is found, no text is displayed on the label.

Suppressing labels

You can also cause labels to be suppressed (not displayed).

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

stxx.setSpan(
	{
		multiplier: 5,
		base: "day"
	},
	function() {
		// remember to restore original values if/when this is no longer needed
		CIQ.ChartEngine.XAxis.setPropertiesForInterval.minute = (label, record, market, stx) => {
			label.suppress=true;
		};
		CIQ.ChartEngine.XAxis.setPropertiesForInterval.day = (label, record, market, stx) => {
			label.text=record.DT.getDate() + "/" + (record.DT.getMonth()+1);
		};
		stxx.draw();	
	}
);

Labels every day custom

Next steps