Skip to main content
Version: 5.2.1

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.

Chart with line seriesChart with line series

(New) Beta version of new, improved XY series

// Creation of a line series
const lineSeries = chart.addPointLineAreaSeries({ dataPattern: 'ProgressiveX' })
.setAreaFillStyle(emptyFill)

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 string instead of number - 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

Timestamps must be supplied as numbers! Generally this is done in UTC format, meaning timestamp = milliseconds since 1st January 1970.

// Example 1
lineSeries.appendSamples({
xValues: [Date.UTC(2023, 0, 1), Date.UTC(2023, 0, 2), Date.UTC(2023, 0, 3)],
yValues: [10, 12, 5],
})
// Example 2
setInterval(() => {
lineSeries.appendSample({
x: Date.now(),
y: Math.random(),
})
}, 100)

When using timestamps, you generally want to also setup a Date-Time Axis.

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.onSeriesBackgroundMouseClick((_, event) => {
const nearest = lineSeries.solveNearestFromScreen(event)
console.log(nearest?.location)
})
// 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 = chart.solveNearest(locationClient)
console.log(nearest?.location)

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
})

Individual color line seriesIndividual color line series

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)

Lookup color line seriesLookup color line series

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') },
],
})
))

Gradient color line seriesGradient color line series

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 })

Legacy version

This refers to the older LineSeries feature, which is still around and used in many online examples, for example. For new application development, using the newer PointLineAreaSeries is recommended, except if your use case requires:

  • RegressiveX data pattern (scrolling left)
  • Vertical data patterns (scrolling up / down)

These are not yet supported by the newer version. Support will be added in near future.

Partial documentation for legacy line series can be found from: