Line
The best and most appreciated feature of LightningChart JS is the simple LineSeries.
This guide goes through most of the things you can do with it and how.


// Creation of a line series
const lineSeries = chart.addPointLineAreaSeries({ dataPattern: 'ProgressiveX' })
.setAreaFillStyle(emptyFill)
Line series are actually "Point line area series" with disabled area fill. This is mainly for performance reasons - this 1 class can be styled to many different use cases. More information why this is like so can be found here.
Adding data
There are many different ways you can add data to a Line series.
The most basic way is to specify 2 number arrays, 1 for X coordinates, and another for Y coordinates.
These arrays can be either primitive number arrays, or TypedArrays.
lineSeries.appendSamples({
xValues: [0, 1, 2],
yValues: [10, 12, 5]
})
Another common way of adding data is to read from a list of JavaScript objects:
lineSeries.appendJSON(
[
{ i: 0, voltage: 4.2 },
{ i: 1, voltage: 4.6 },
{ i: 2, voltage: 4.5 },
],
{ x: 'i', y: 'voltage' }
)
A common user problem is that data is supplied as
stringinstead ofnumber- please confirm this if you are experiencing any strange issues.
console.log(typeof y) // ---> 'number'
You can also omit either X or Y values for automatic incrementation:
lineSeries.appendSamples({
yValues: [10, 12, 5],
// Optional, defaults to 1
step: 2,
}) // ---> x: 0, 2, 4
Using timestamps
PointLineAreaSeries and DataSetXY APIs can directly consume timestamp data as any of following types:
- Date time string
- Date object
- UTC timestamp
number(e.g.Date.getTime())
Features other than PointLineAreaSeries/DataSetXY currently only support direct input as UTC timestamp numbers!
// Example 1, date time string
lineSeries.appendSample({
x: '2022-12-31T22:00:00.000Z',
y: Math.random()
})
// Example 2, Date object
lineSeries.appendSample({
x: new Date(2023, 0, 1),
y: Math.random()
})
// Example 3, UTC timestamp number
setInterval(() => {
lineSeries.appendSample({
x: Date.now(),
y: Math.random(),
})
}, 100)
When using timestamps, you generally want to also setup a Date-Time Axis.
Displaying nanosecond timestamps requires some extra configurations. See Nanosecond timestamps for more details.
Data cleaning / maximum memory use
In streaming use cases, memory usage can be limited by configuring the maximum number of samples that are retained. After this number is reached, the oldest samples will be dropped to keep only the max number of samples in memory.
// Example, keep max 1 million samples.
lineSeries.setMaxSampleCount(1_000_000)
Alternatively, if you are uncertain what value to use and don't want to allocate too much memory up-front, you can start small and automatically increase the buffer size as samples flow in:
lineSeries.setMaxSampleCount({ mode: 'auto', max: 10_000_000 })
This would first allocate only small amount of memory, progressively increase memory allocation as samples come in until eventually limiting sample count to 10 million.
Changing stroke color
const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0, 255) })
lineSeries.setStrokeStyle((stroke) => stroke.setFillStyle(fillRed))
Color can also be defined in a handful of other ways.
const fillBlue = new SolidFill({ color: ColorHEX('#0000ff') })
const fillGreen = new SolidFill({ color: ColorCSS('green') })
For more details about style API, please see Styles, colors and fonts.
Changing stroke thickness
lineSeries.setStrokeStyle((stroke) => stroke.setThickness(1))
By setting thickness to exactly -1 you can enable "primitive line rendering".
This is very light on GPU utilization (works well on weak machines), but results in lines being 1 pixel thick and not very smooth.
// Enable primitive line rendering - 1 px thick and considerably lighter on GPU.
lineSeries.setStrokeStyle((stroke) => stroke.setThickness(-1))
Dashed line series
lineSeries.setStrokeStyle((stroke) => new DashedLine({
fillStyle: stroke.getFillStyle(),
thickness: stroke.getThickness(),
pattern: StipplePatterns.Dashed,
}))
Connecting to non-default axis
If your ChartXY has several axes, then you can specify which axis the series should be connected to like this:
const lineSeries = chart.addPointLineAreaSeries({
dataPattern: 'ProgressiveX',
xAxis: myXAxis,
yAxis: myYAxis,
})
Data gaps
If your data has gaps, which should not be connected in the line visualization, you can create a gap with a NaN Y value:
lineSeries.appendSamples({
yValues: [1, 2, NaN, 2, 1]
}) // ---> line from { x: 0, y: 1 } to { x: 1, y: 2 }, then GAP, starts again from { x: 3, y: 2 }.
Please note that the value has to be specifically NaN - undefined, null or any other value will not have the same effect.
Solve nearest from location
// Example 1, when user clicks on chart, log closest data point to console
chart.seriesBackground.addEventListener('click', event => {
const nearest = lineSeries.solveNearest(event)
console.log(nearest)
})
// Example 2, solve nearest data point from an axis coordinate
const locationAxis = { x: 10, y: 10 }
const locationClient = chart.translateCoordinate(locationAxis, chart.coordsAxis, chart.coordsClient)
const nearest = lineSeries.solveNearest(locationClient)
console.log(nearest)
Interactions with data points
All series support tracking user interactions:
lineSeries.addEventListener('click', (event, hit) => {
// hit.x
// hit.y
console.log(hit)
})
For all available events to track, please see API
Individual colors
const lineSeries = chart.addPointLineAreaSeries({
dataPattern: 'ProgressiveX',
colors: true
})
.setAreaFillStyle(emptyFill)
.setStrokeStyle((stroke) => stroke.setFillStyle(new IndividualPointFill()))
.appendSamples({
yValues: [10, 12, 9],
colors: [0xff0000ff, 0xff00ff00, 0xff00ff00], // red, green, green
})


Colors can also be supplied as Color objects created using one of the many Color factories (ColorRGBA, ColorHEX, etc.).
However, for performance reasons you should avoid creating a large number of individual Color objects.
const colorRed = new SolidFill({ color: ColorRGBA(255, 0, 0) })
const colorGreen = new SolidFill({ color: ColorRGBA(0, 255, 0) })
lineSeries
.appendSamples({
yValues: [10, 12, 9],
// This is fine (reuse same color objects)
colors: [colorRed, colorGreen, colorGreen],
})
Color by lookup table
Alternative approach to individual colors, you can give each data point a number value, and specify rules for getting a color from any number:
const lineSeries = chart.addPointLineAreaSeries({
dataPattern: 'ProgressiveX',
lookupValues: true
})
.setAreaFillStyle(emptyFill)
.setStrokeStyle((stroke) => stroke.setFillStyle(new PalettedFill({
lookUpProperty: 'value',
lut: new LUT({
interpolate: true,
steps: [
{ value: 0, color: ColorCSS('red') },
{ value: 100, color: ColorCSS('green') },
]
})
})))
.appendSamples({
yValues: [10, 12, 9],
lookupValues: [0, 100, 50]
})
const legend = chart.addLegendBox().add(chart)


LUT has also a percentageValues option, which you can use if you want to use % values of the present min-max lookup value range, rather than specific value steps:
new LUT({
percentageValues: true,
interpolate: true,
steps: [
// 0 = 0 %, 1 = 100%
{ value: 0, color: ColorCSS('red') },
{ value: 1, color: ColorCSS('green') },
]
})
Color by X or Y
Instead of using lookup values in data set, you can also use X or Y coordinates directly, by changing the lookUpProperty value in PalettedFill.
new PalettedFill({
lookUpProperty: 'x', // 'x' or 'y'
lut: new LUT({
interpolate: true,
steps: [
{ value: 0, color: ColorCSS('red') },
{ value: 100, color: ColorCSS('green') },
]
})
})
Color by gradient
More alternatives to dynamic coloring - you can also define a gradient (linear or radial), to color the line stroke.
const lineSeries = chart.addPointLineAreaSeries({ dataPattern: 'ProgressiveX' })
.setAreaFillStyle(emptyFill)
.setStrokeStyle((stroke) => stroke.setFillStyle(
new LinearGradientFill({
// down -> up
angle: 0,
stops: [
{ offset: 0, color: ColorCSS('red') },
{ offset: 1, color: ColorCSS('green') },
],
})
))


Edit samples data
alterSamples
This method allows you to specify a starting location in the existing data set with a sample index. This is a simple counter that points to the n:th sample that has been pushed to the data set, in the order of appearance.
lineSeries.appendSamples({ yValues: [0, 1, 2, 3, 4, 5] })
lineSeries.alterSamples(1, { yValues: [10, 11] })
// ---> Data set = [0, 10, 11, 3, 4, 5]
alterSamples lets you modify any sample values, X, Y, lookup value, color, etc.
It also supports automatically appending data if you attempt to modify samples that would come after the existing ones.
alterSamplesByID
This method allows you to arbitrarily edit any number of existing samples by identifying them with an user supplied ID property:
lineSeries.appendSamples({
yValues: [100, 101, 102, 103, 104],
ids: [0, 1, 2, 3, 4]
})
lineSeries.alterSamplesByID(
[2, 4], // Edit samples ID:2 and ID:4
{
yValues: [200, 201]
}
)
// ---> Data set yValues = [100, 101, 200, 103, 201]
IMPORTANT, in order to use ID values, you have to enable them when creating the Series or DataSetXY:
const series = chart.addPointLineAreaSeries({ ids: true })
// or
const dataSet = new DataSetXY({ ids: true })
// Example, load same value for all altered samples
lineSeries.alterSamplesByID([2, 4], { size: 5 })
fill
This method allows you to load a single value for all existing samples in the data set:
// Set point size of all samples to 5 pixels
lineSeries.fill({ size: 5 })
Read back data
Currently existing data in a PointLineAreaSeries can be read back using readback method:
lineSeries.readback()
// xValues: TypedArray
// yValues: TypedArray
// iSampleFirst: number
// lookupValues?: Float32Array | undefined
// colors?: Uint32Array | undefined
// ids?: Uint32Array | undefined
// sizes?: Float32Array | undefined
// rotations?: Float32Array | undefined
With progressive data sets you can also conveniently read back only data in given range:
series.readBack({ onlyInRange: chart.xAxis.getInterval() })
series.readBack({ onlyInRange: { start: 0, end: 100 } })