Cursor
LightningChart JS has a built in Cursor functionality. This is activated when user places mouse over a chart, displaying information about the pointed chart component.


The cursor functionality can be customized in many ways, most of which are covered in below documentation.
Enabling/disabling cursor
// Disable cursor
Chart.setCursorMode(undefined)
// Enable cursor, showing only one series at a time
Chart.setCursorMode('show-nearest')
// Enable cursor, showing a "cut" of all series along the same coordinate (if applicable)
Chart.setCursorMode('show-all')
// Enable cursor when its directly above a chart component, series, etc.
Chart.setCursorMode('show-pointed')
Cursor formatting
Before overriding default cursor formatting, it is recommended to check if using Axis units, configuring Axis cursor formatting, configuring LUT formatting, configuring series name, or overriding series formatting would be enough.
There are many benefits to NOT overriding default cursor formatting, so it should always be the preferred option!
Text displayed by cursors can be configured with Chart.setCursorFormatting method.
In simple terms, this is done by supplying a callback function which receives information about the pointed data, and returns a list of text to display:
chart.setCursorFormatting((_, hit, hits) => {
return [
['Cursor pointing at'],
[hit.series], // returning a series will display the series color and its name automatically.
['X', '', hit.axisX.formatValue(hit.x)], // utilizing axis formatValue is useful for considering active zoom level and type of axis
['Y', '', hit.y.toFixed(2)], // empty string '' results in gap between cells
[{ text: 'Example', font: { weight: 'bold' }, fillStyle: fillRed }] // any cell can also be styled individually
]
})
const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0) })
Here's a common code snippet to conveniently highlight specific rows as "headers":
chart.setCursorFormatting((_, hit) => [
[{ component: hit.series, rowFillStyle: chart.getTheme().cursorResultTableHeaderBackgroundFillStyle }]
])
// or alternatively...
chart.setCursorFormatting((_, hit) => [
[{ text: 'Header text', rowFillStyle: chart.getTheme().cursorResultTableHeaderBackgroundFillStyle }]
])
Axis specific cursor formatting
If you just need to configure how values along 1 specific Axis are formatted in cursors and cursor tick markers, the best way is to configure the cursor formatting of that Axes' tick strategy:
chart.axisX.setTickStrategy(AxisTickStrategies.Numeric, (strategy) => strategy
.setCursorFormatter((x) => `${x.toFixed(2)} Hz`)
)
This takes effect whenever your application, or the library utilizes that Axes' formatValue method.
For example, when a value along that Axis is displayed in a cursor, or a cursor tick marker.
Alternatively, Axis titles also affect how values are displayed in cursors. Often simply specifying an axis title will do the trick:
chart.axisX.setTitle('Time')
chart.axisY.setTitle('Voltage')
Series specific data properties
With some chart types, there are series specific data properties.
For example, XY chart can contain Line Series, or Heatmap Series whose data properties are very different.
By default, you'll have access to only the standard data properties such as hit.x, hit.y (in XY charts).
In order to access any series specific properties, you must use a Type Guard:
chart.setCursorFormatting((_, hit, hits) => {
if (!isHitHeatmap(hit)) return undefined
// `hit` is for a heatmap series
return [
['Column', '', hit.column.toFixed(0)],
['Row', '', hit.row.toFixed(0)],
['Intensity', '', hit.intensity.toFixed(3)],
]
})
These type guards all start with isHit... pattern, so you can find them from the API documentation.
Here's a list of the most frequently needed type guards:
isHitSampleXY(forPointLineAreaSeries)isHitHeatmap(for 2D heatmaps)
If you need to show completely custom data values (not x, not y, not value, etc.) in result table, see series specific cursor formatting.
Series specific cursor formatting
As described just about everywhere on this page, cursor formatting is defined on a per-chart basis. However, there are a couple of ways to alter how cursor formatting acts for a specific, single series.
// By default, series name is displayed next to corresponding values in cursors
series.setName('Projected revenue')
For more advanced formatting customization, there is setCursorFormattingOverride method:
series.setCursorFormattingOverride((hit, before) => {
if (!isHitSampleXY(hit)) return before
// Example 1: extra data property that should be displayed in cursor is loaded into data set, but not mapped to either X/Y, only in cursor
const customPropertyValue = hit.sample.myProperty
// Example 2: extra data property is not loaded to data set, but instead it is referred from outside the chart library
const customPropertyValue = data[hit.iSample].myProperty
// Add an extra row to the cursor, displaying the property value
return [...before, ['Custom:', '', customPropertyValue.toFixed(3)]]
})
This is generally recommended when you want to expand the default cursor formatting with extra rows, such as showing completely custom data properties (not x, y, value, etc.).
LUT formatting
If your application uses look-up-tables (for example, heatmap, dynamically color scatter plots, etc.), then look-up values are displayed in cursors. The formatting can be specified with a property of the LUT object:
heatmapSeries.setFillStyle(new PalettedFill({
lut: new LUT({
interpolate: true,
steps: [
{ value: 0, color: ColorRGBA(255, 0, 0) },
{ value: 10, color: ColorRGBA(0, 0, 255) },
],
formatter: (value) => value.toFixed(0) // show 0 decimals
}),
}))
The LUT formatter affects both cursors and legends if LUTs are displayed there.
If LUT percentageValues is set to true, then this does not affect step display in legends.
Multi-series cursors
To display multiple series simultaneously in a cursor, ensure that cursor mode is set to "show-all" or "show-all-interpolated" and use hits parameter:
chart
.setCursorMode('show-all')
.setCursorFormatting((_, __, hits) => {
return [
['X', '', hits[0].axisX.formatValue(hits[0].x)],
...hits.map((hit) => [hit.series, '', hit.axisY.formatValue(hit.y)])
]
})
Kindly note that multi-series cursors generally expect your series to have progressive data patterns!
Furthermore, cursor modes like "show-all" and "show-all-interpolated" are only able to pick results that belong to the same chart.
If your series are scattered across different charts, consider migrating to Stacked Axis.
Icons
Icons can also be shown in result table content:
const image = new Image()
image.src = 'my-image.png'
const icon = chart.engine.addCustomIcon(image)
chart.setCursorFormatting((_, hit, hits) => {
return { icon, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) }
})
Controlling cursor per series / component
All series and equivalent components that are pickable by cursor can be individually removed from cursor behavior with setCursorEnabled method:
series.setCursorEnabled(false) // cursor won't pick on this series
Styling result table
Text color and font
chart.setCursor((cursor) => cursor
.setResultTable((resultTable) => resultTable
.setTextFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))
.setTextFont((font) => font.setWeight('bold'))
)
)
For more details about style API, please see Styles, colors and fonts.
Background fill and border
chart.setCursor((cursor) => cursor
.setResultTable((resultTable) => resultTable
.setBackground((background) => background
.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))
.setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) }))
)
)
)
For more details about style API, please see Styles, colors and fonts.
Styling point marker
By default, cursors display a small crosshair at pointed location.
Hide point marker
chart.setCursor((cursor) => cursor
.setPointMarkerVisible(false)
)
Shape, size and color
chart.setCursorDynamicBehavior(false).setCursor((cursor) => cursor
.setPointMarker((pointMarker) => pointMarker
.setShape(PointShape.Circle)
.setSize({ x: 5, y: 5 })
.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))
.setStrokeStyle(new SolidLine({ thickness: 2, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) }))
)
)
For more details about style API, please see Styles, colors and fonts.
Cursor interpolation
By default, cursors snap to the closest real data point. Alternatively, automatic interpolation can be enabled to display interpolated data value at cursor location:
// When using "show-all" mode and your data points do not align with each other you should enable interpolation:
chart.setCursorMode('show-all-interpolated')
chart.setCursorMode('show-nearest-interpolated')
chart.setCursorMode('show-pointed-interpolated')
XY Cursors
Hide tick markers
chart.setCursor((cursor) => cursor
.setTickMarkerXVisible(false)
.setTickMarkerYVisible(false)
)
Style tick marker text
chart.setCursor((cursor) => cursor
.setTickMarkerY((tickMarker) => tickMarker
.setTextFont((font) => font.setSize(10))
.setTextFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))
.setTextRotation(0)
)
)
For more details about style API, please see Styles, colors and fonts.
Style tick marker background
chart.setCursor((cursor) => cursor.setTickMarkerY((tickMarker) =>
hasUIElementBackground(tickMarker) &&
tickMarker.setBackground((background) => background
.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))
.setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) }))
)
))
For more details about style API, please see Styles, colors and fonts.
Hide grid lines
chart.setCursor((cursor) => cursor
.setGridStrokeXStyle(emptyLine)
.setGridStrokeYStyle(emptyLine)
)
Style grid lines
chart.setCursor((cursor) => cursor
.setGridStrokeXStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) }))
)
For more details about style API, please see Styles, colors and fonts.
3D Cursors
Otherwise, 3D cursors work like with other charts, but they are only activated when user mouse is directly above a series which has mouse interactions enabled. e.g. there is no Solve Nearest functionality in 3D.
Map Chart Cursors
formatLongitudeLatitude function is available for displaying longitude and latitude coordinates.
mapChart.setCursorFormatting((_, hit) => {
return [
[formatLongitudeLatitude(hit.longitude, hit.latitude)]
[hit.region.name]
]
})
Custom cursor actions and events
You can monitor what is displayed by the cursor at any given time, as well as trigger custom actions using cursor target changed event:
chart.addEventListener('cursortargetchange', (event) => {
// this callback is triggered whenever the cursor targets change.
console.log(event)
})
Custom cursors
The default cursors in LightningChart JS are fairly simple in terms of visual options (for example, rounded borders are not supported). When advanced CSS styling is needed, the recommended approach is to implement a Custom Cursor.
Essentially, the idea is that instead of using the built-in way of displaying cursors, you can display any kind of cursor you want, either using LightningChart JS user interface elements (such as Custom Ticks, UI Textboxes, etc.) or even with custom HTML/JS/CSS or components of your UI framework (such as React, Angular, Vue, Next, etc...)
Regardless, plugging a custom cursor to any chart is excessively simple, as you only need to use one method setCustomCursor, which will disable the built-in functionality and trigger a callback function whenever the custom cursor should display something or hide itself:
chart.setCustomCursor((event) => {
// this callback is triggered whenever the cursor targets change.
// if cursor is hidden `event` will be undefined
// Otherwise it will supply same information as `setCursorFormatting.`
})
For some examples of custom cursors, you can check out our examples gallery under cursor tag.
Optimizing cursors
In most use cases, cursors are extremely performant without any special attention. However, there are two cases where a little bit of performance optimization can be useful:
1: Large freeform data sets
e.g. point cloud with > 1 000 000 samples. Finding the closest data point from this data set is a very slow operation.
It is recommended to use
"show-pointed"cursor mode for significantly better performance!
2: Large data sets that are heavy to render
e.g. large point clouds or 3D series with many data points.
For best performance, you can separate the cursor from chart re-rendering, by using a custom cursor and displaying the cursor either using direct HTML/JS/CSS or an UI framework. This way even if the cursor is changing, the heavy chart does not need to be re-rendered.
This online example showcases both of these optimization approaches: Large Scatter Chart
Manual cursors
Charts also support ability to add manual cursors. These function exactly same as normal cursors, but instead of being automatically triggered by end user actions they can be freely controlled by the users own application logic.
Manual cursors are created with chart.addCursor() method:
const cursor = chart.addCursor()
Manual cursors are styled just like normal cursors (e.g. result table styling, point marker styling, tick marker styling, ...).
Their position and content are set separately using cursor.setPosition(...) and cursor.setResultTable(table => table.setContent(...)) methods:
// Simple example of placing a manual cursor when clicking on a series
series.addEventListener('click', (event, hit) => {
cursor
.setPosition(hit.cursorPosition)
.setResultTable((table) => table.setContent(chart.getCursorFormatting()(chart, hit, [hit])))
})
Manual cursors can also be placed on arbitrary locations.
const cursor = chart
.addCursor()
.setPosition({
pointMarker: { x: 200, y: 0 },
pointMarkerScale: chart.coordsAxis,
resultTable: { x: 200, y: 0 },
resultTableScale: chart.coordsAxis,
})
.setResultTable((table) => table.setContent('Hello'))