Skip to main content
Version: 6.1.2

Axis

Here you can find guides for most often required configurations of Axis, a critical part of almost all chart types.

Accessing axis

This depends on the type of chart.

const axisX = ChartXY.axisX
const axisY = ChartXY.axisY

const axisZ = Chart3D.axisZ

const radialAxis = PolarChart.radialAxis
const amplitudeAxis = PolarChart.amplitudeAxis

const valueAxis = BarChart.valueAxis
const categoryAxis = BarChart.categoryAxis

Axis title

axis
.setTitle('Voltage')
.setTitleFont(font => font.setSize(10).setFamily('Segoe UI'))
.setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))

For ChartXY, setTitlePosition and setTitleRotation are also available. See API documentation](/6.1.2/api) for more details.

For more details about style API, please see Styles, colors and fonts](/6.1.2/more-guides/styles-colors-and-fonts).

Axis interval / view

By default, any axis automatically adjusts its "interval" [start - end] to display all series connected to it. The start-end interval can be explicitly set using the setInterval or setDefaultInterval methods.

// Example, show interval between 0 and 100
axis.setInterval({ start: 0, end: 100 })

By using setDefaultInterval this configured interval is also restored if the user triggers the "zoom to fit" interaction (hold left mouse button down and drag towards upper-left).

axis.setDefaultInterval({ start: 0, end: 100 })

Numeric axis

Display numeric values using automatically placed axis ticks (10, 20, 30, etc.). This is the default axis behavior.

axis.setTickStrategy(AxisTickStrategies.Numeric)

Chart with numeric axis Chart with numeric axis

Extreme ticks

By default, ticks are placed on logical key values (e.g. 0, 10, 20, 30, ...) depending on active axis interval. Sometimes, with physically small charts you may get problems from not having enough ticks visible. One solution for this is to enable "extreme ticks", this simply means always displaying the axis start and end values, even if they are not "logical key values".

axis.setTickStrategy(AxisTickStrategies.Numeric, (ticks) => ticks
// Enable extreme ticks
.setExtremeTickStyle(ticks.getMajorTickStyle())
)

Even if extreme ticks are not enabled, in some cases they can be displayed as an automatic fallback if otherwise a small Numeric axis would only display 1 tick label. This behavior can be prevented with:

axis.setTickStrategy(AxisTickStrategies.Numeric, (strategy) => strategy
.setFallBackToExtremeTicksAutomatically(false)
)

Date-Time axis

Display seconds, minutes, hours, days, months and years using automatically placed axis ticks.

axis.setTickStrategy(AxisTickStrategies.DateTime)

Chart with date-time axis Chart with date-time axis

Date-time axis intervals are defined as UTC timestamps, meaning timestamp = milliseconds since 1st January 1970.

axis.setInterval({
start: Date.UTC(2022, 0, 1), // 1st Jan 2022
end: Date.UTC(2022, 0, 31) // 31st Jan 2022
})

Zoom ability

By default, you can only show time axis intervals of around 1 day range. To improve on this, you can enable so called "high precision axis", which enables zooming to 1 millisecond range:

const chart = lc.ChartXY({
defaultAxisX: {
type: 'linear-highPrecision'
}
})

Even smaller time ranges can be achieved, but not as conveniently as these examples. If your use case requires this, please contact us](/6.1.2/contact).

Show year / month / day

In some use cases you might want to have an additional label displaying for example what year or month is in question. One way of doing this is by enabling great ticks:

chart.axisX.setTickStrategy(AxisTickStrategies.DateTime, (strategy) =>
strategy.setGreatTickStyle(strategy.getMajorTickStyle().setTickLength(28).setTickStyle(emptyLine)),
)

These are automatically displayed on relevant zoom levels with just one (or two) extra labels so you don't have to display year/month/day on every tick label.

Custom formatting

Commonly used alternate formatting scheme of formatting every tick with "HH:MM:SS:MMM":

const t1s = 1000
const t1m = 60 * 1000
const t1h = 60 * 60 * 1000
const t1d = 24 * 60 * 60 * 1000
const formatterX = (x) => {
const hour = Math.floor((x % t1d) / t1h).toString().padStart(2, '0')
const minute = Math.floor((x % t1h) / t1m).toString().padStart(2, '0')
const second = Math.floor((x % t1m) / t1s).toString().padStart(2, '0')
const millis = Math.floor((x % t1s)).toString().padStart(3, '0')
return `${hour}:${minute}:${second}.${millis}`
}
chart.axisX.setTickStrategy(AxisTickStrategies.DateTime, strategy => strategy
.setFormatting(formatterX, formatterX, formatterX)
.setCursorFormatter(formatterX)
)

Different formatting schemes can be applied by simply acting differently in the formatter logic based on current Axis interval (Axis.getInterval()) OR separately defining formatter functions for different zoom levels (setFormattingDay, setFormattingHour, etc.).

Modifying default formatting without full override

Built-in date-time formatting is based on Intl.DateTimeFormat, so it is possible to add global overrides - for example, adding timeZone: "GMT" to all zoom levels:

chart.axisX.setTickStrategy(AxisTickStrategies.DateTime, strategy => {
const keys = Object.keys(strategy.toJS())
return strategy.withMutations((mutable) => {
mutable.set('utc', true) // align ticks by GMT+0 rather than client timezone
mutable.set('cursorFormatter', (x) => new Intl.DateTimeFormat(undefined, { timeZone: 'GMT', hour: '2-digit'}).format(x))
keys.forEach((key) => {
if (!key.includes('formatOptions')) return
const prevValue = strategy[key]
if (typeof prevValue === 'function') return
mutable.set(key, { ...prevValue, timeZone: 'GMT' })
})
})
})

Time axis

Display passing of time since a reference point using automatically placed axis ticks.

axis.setTickStrategy(AxisTickStrategies.Time)

Chart with time axis Chart with time axis

Time axis intervals are defined as milliseconds.

// Example, show range of 10 seconds
axis.setInterval({ start: 0, end: 10_000 })

Time axis is well suited to display "run-time", as in how long the application has been running:

setInterval(() => {
lineSeries.appendSample({ x: performance.now(), y: Math.random() })
}, 1000 / 60)

Alternatively, you can input UTC timestamps and specify which timestamp is displayed as 00:00:00:

axis.setTickStrategy(AxisTickStrategies.Time, (ticks) => ticks.setTimeOrigin(-Date.now()))
setInterval(() => {
lineSeries.appendSample({ x: Date.now(), y: Math.random() })
}, 1000 / 60)

Custom formatting

const formatMilliseconds = (ms) => {
const seconds = Math.floor(ms / 1000);
const milliseconds = Math.round(ms % 1000);
return `${seconds.toString().padStart(2, '0')}:${milliseconds.toString().padStart(3, '0')}`;
}
chart.axisX.setTickStrategy(AxisTickStrategies.Time, strategy => strategy
.setFormattingFunction((x, range) => formatMilliseconds(x))
)

Custom formatting can be adjusted to active zoom level by referring to supplied range parameter. If needed, cursor formatting can be separately configured with setCursorFormatter method.

Disable ticks

axis.setTickStrategy(AxisTickStrategies.Empty)

Custom, manual or category axis

If built-in tick placement modes don't suit your use case, you can disable them and place ticks manually.

axis.setTickStrategy(AxisTickStrategies.Empty, (strategy) => strategy
.setCursorFormatting((value) => {
// Specify logic of getting displayed `string` from axis coordinate `number`
return ['A', 'B', 'C', 'D', 'E'][Math.round(value)] ?? ''
})
)
const tick = axis.addCustomTick().setValue(2)

Custom ticks are available currently in XY, 3D and Parallel coordinate charts.

Scrolling axis

// Example, configure a scrolling axis that keeps interval with length: `1000`
axis
.setScrollStrategy(AxisScrollStrategies.progressive)
.setDefaultInterval((state) => ({
end: state.dataMax ?? 0,
start: (state.dataMax ?? 0) - 1_000,
stopAxisAfter: false,
}))

This results in the axis automatically revealing new data as it is spawned, but keeping the size of axis interval unchanged - resulting in older data going out of view.

Axis units

When you know what Units your axis represents you can utilize Axis.setUnits(unit: string), which might make your application development a little bit easier.

chart.axisY
.setTitle('Frequency')
.setUnits('Hz')

Setting the Units only affects following things:

  • The unit is displayed after axis title (if specified) - e.g. "Frequency (Hz)"
  • The unit is displayed by default cursor formatters when pointing at series connected to that axis - e.g. "Y: 18.2 Hz"
tip

The main value in using Axis.setUnits is to avoid having to specify custom cursor formatting just because you need to display the units in there.

These behaviors can also be individually control like so:

chart.axisY.setUnits('Hz', {
displayInCursor: true,
displayOnAxis: true
})

Multiple axes and positioning

In ChartXY, you can have more than two axes, and axes can also be positioned in different locations. Additional axes can be added with addAxisX and addAxisY methods:

const axisX2 = chart.addAxisX().setTitle('Extra Axis X')

By default, series are attached to the default axes. When an application has several axes, it is recommended to explicitly specify which axis each series should be connected to:

const lineSeries = chart.addLineSeries({
xAxis: axisX2,
yAxis: chart.axisY
})

Axis side

Axes can be positioned at the opposite end of the chart (right for X axis, top for Y axis):

const axisY2 = chart.addAxisY({ opposite: true })

Opposite Y axis Opposite Y axis

For default axes, this has to be specified when the ChartXY is created:

const chart = lc.ChartXY({
defaultAxisY: { opposite: true }
})

Stacked axes

Multiple axes can also be stacked on top of each other. Most commonly this is used to share 1 X axis between many different Y axes and respective Series:

chart.axisY.dispose()
for (let i = 0; i < 3; i += 1) {
const axisY = chart.addAxisY({ iStack: i }).setMargins(i > 0 ? 15 : 0, i < 2 ? 15 : 0)
const series = chart
.addPointLineAreaSeries({ axisY, dataPattern: 'ProgressiveX' })
.setAreaFillStyle(emptyFill)
.appendSamples({ yValues: new Array(100).fill(0).map((_) => Math.random()) })
}

Stacked Y axis Stacked Y axis

The key parts here are:

  1. iStack parameter given to addAxisY method. This determines the vertical position of the Y axis.
  2. Axis.setMargins is used to add empty space along vertical plane to avoid Y axis tick labels colliding, and adding a clear separation between channels.
  3. For simplicity of implementation, the default Y axis is disposed before creating the stacked Y axes.

The length of axes can be changed in two different ways:

// Set relative length of axis to `0.5`
// By default, every axis has relative length of `1`, so this operation would effectively make the axis half the length of others
axis.setLength({ relative: 0.5 })
// Set length of axis to 200 pixels exactly.
axis.setLength({ pixels: 200 })

Parallel axes

Axes can also be placed parallel to each other:

const axisY1 = chart.axisY
const axisY2 = chart.addAxisY({ iParallel: 1 })
.setTickStrategy(AxisTickStrategies.Numeric, (ticks) => ticks.setTickStyle((major) => major.setGridStrokeStyle(emptyLine)))
const series1 = chart.addPointLineAreaSeries({ axisY: axisY1, dataPattern: 'ProgressiveX' })
.setAreaFillStyle(emptyFill)
.appendSamples({ yValues: new Array(100).fill(0).map((_) => Math.random()) })
const series2 = chart.addPointLineAreaSeries({ axisY: axisY2, dataPattern: 'ProgressiveX' })
.setAreaFillStyle(emptyFill)
.appendSamples({ yValues: new Array(100).fill(0).map((_) => Math.random()) })

Parallel Y axis Parallel Y axis

Generally, when there are parallel axis, you want to disable tick gridlines because it is practically impossible to recognize which axis they belong to. In above example, one of the Y axis has gridlines visible and the other not.

Parallel axes can also be combined with opposite axes, so that one Y axis is on left, and another on right.

User interactions

info

Currently most axis types have no user interactions, except for those belonging to ChartXY](/6.1.2/features/xy)

Generally, the best way to configure user interactions of axes is to configure them via the owning chart. Please see Chart XY user interactions](/6.1.2/features/xy/#user-interactions) section for more information.

However, further user interaction overrides can be configured on any individual axis. This essentially allows:

  1. Controlling whether the particular axis is affected by chart level interactions which by default affect all axes.
  2. Controlling what user interactions are enabled above the particular axis - overriding the chart level configuration.
axis.setUserInteractions({
chartInteractions: false // this axis will be unaffected by any and all "chart level" interactions
})
axis.setUserInteractions({
chartInteractions: {
pan: false, // alternatively, individual "chart level" interactions can be disabled like so
zoom: false
}
})

Styling axis ticks

Axis ticks are configured via the AxisTickStrategy interface:

axis.setTickStrategy(AxisTickStrategies.Numeric, tickStrategy => tickStrategy
.setTickStyle(ticks => ticks
.setLabelFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))
.setLabelFont(font => font.setSize(10).setFamily('Segoe UI'))
.setGridStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) }))
.setTickStyle(emptyLine)
)
)

Alternatively instead of setTickStyle, you can style major and minor ticks separately using setMajorTickStyle / setMinorTickStyle.

For more details about style API, please see Styles, colors and fonts](/6.1.2/more-guides/styles-colors-and-fonts).

Same syntax works for DateTime and Time axis tick strategies.

Styling axis line

// Hide axis line
axis.setStrokeStyle(emptyLine)
// Red axis line
axis.setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) }))

For more details about style API, please see Styles, colors and fonts](/6.1.2/more-guides/styles-colors-and-fonts).

Hiding grid-lines

axis.setTickStrategy(AxisTickStrategies.Numeric, tickStrategy => tickStrategy
.setTickStyle(ticks => ticks
.setGridStrokeStyle(emptyLine)
)
)

Same syntax works for DateTime and Time axis tick strategies.

Logarithmic axis

Logarithmic axes can be enabled when creating an Axis or Chart. Here's how it looks for ChartXY:

const chart = lc.ChartXY({
defaultAxisY: {
type: 'logarithmic',
base: 10,
}
})

...or for non-default Axis:

const axisLog = chart.addAxisY({ type: 'logarithmic', base: 10 })

Please note that logarithmic axis range is not defined at 0, so you should confirm that your data doesn't have exact 0 values.

Inverted or reverted axis

By default, axis fits visible data range so that min value is shown at axis "start" (left for X axis, bottom for Y axis). This can be reversed so that min value is shown at axis "end" (right for X axis, top for Y axis) like this:

// Reverse axis interval
axis.setInterval({ start: 1, end: 0, stopAxisAfter: false })

Restricting axis interval

// Example 1, prevent zooming outside active data set
axis.setIntervalRestrictions((state) => ({
startMin: state.dataMin,
endMax: state.dataMax,
}))
// Example 2, set max zoom in level (intervalMin)
axis.setIntervalRestrictions({ intervalMin: 10 })
// Example 3, set max zoom out level (intervalMax)
axis.setIntervalRestrictions({ intervalMax: 1000 })

Restrict zoom level to certain decimal count

While there is no convenience feature for restricting axis by decimal count, it is relatively easy to achieve with setIntervalRestrictions by manually identifying the thresholds where built-in tick formatting changes number of decimals.

// Some examples
axis.setIntervalRestrictions({ intervalMin: 100 }) // max decimals = 0
axis.setIntervalRestrictions({ intervalMin: 10 }) // max decimals = 1
axis.setIntervalRestrictions({ intervalMin: 1 }) // max decimals = 2

Following changes to axis interval

axis.addEventListener('intervalchange', (event) => {
console.log(event)
})

Synchronizing several axes

Any number of axes can be conveniently synchronized with synchronizeAxisIntervals function. This ensures that their interval is always the same.

synchronizeAxisIntervals(axis1, axis2, axis3)

It's worth pointing out that this operation creates a lasting connection between the axes. If any axis should be removed at any time, then the synchronization should be cleaned up:

const syncHandle = synchronizeAxisIntervals(axis1, axis2, axis3)
syncHandle.remove()

Removing series from affecting axis

Individual series can be disabled from affecting any axis scrolling or fitting using setAutoScrollingEnabled method:

series.setAutoScrollingEnabled(false)

Zoom range limitations

LightningChart JS is based on WebGL powered graphics. This imposes some limitations when dealing with extremely large numbers, or numbers with extremely precise decimal points.

The main way this limitation shows itself is Axis zoom range limitations, meaning axis preventing its interval [start: number, end: number] from going to ranges that could result in rendering errors.

If your application results in Axis refusing to go to required zoom range, you should change the axis type to "high precision":

const chart = lightningChart().ChartXY({
defaultAxisX: {
type: 'linear-highPrecision'
}
})

The high precision axis supports almost all series types, but not every single one, and comes with a slight performance penalty. To find what series types are unsupported, navigate to API documentation](/6.1.2/api) and find the AxisOptions entry.