Skip to main content
Version: 8.3.2

v7.x -> v8.x

When upgrading, please reference each of the below LCJS entries in your application (for example, using Search functionality in your code base).

For each code block found this way, refer to the corresponding migration guide.

Legend rework

One of the main changes in v8 is a complete rework of the Legend functionality. The main improvement goals for the rework were:

  1. Improving charts look by moving legends away from obstructing the actual data visualizations and making them adapt to available space.
  2. Making the configuration API easier to learn and use, as well as more powerful in general.
  3. Enabling more use cases with built-in functionality.
  4. Adding plugin support for custom legend functionality without user having to reinvent the wheel.

This document is intended to serve users migrating from old legend API to new one by providing instructions to as many possible migration situations as we could think of. To learn of new legends functionality without regard to previous API, please see newly written Legend documentation.

Preventing legend from being shown after migration

Previous to v8.0, legends were UI components created by the user and added to the chart with addLegendBox. In v8.0, the legend has been reworked to be a chart property, and is now created and managed by itself. As a consequence, legends are now always displayed by default.

const chart = lc.ChartXY({
legend: {
visible: false,
},
})

Migrating previous code of creating a legend

A common usage of old API was to create a legend, and display all components of the charts in it:

// Old API
const legend = chart.addLegendBox().add(chart)

The correct migration step for this, is to simply remove it, as a legend is now automatically created and by default shows all components of the chart.

Migrating applications that show only specific components in legend

Previously, components were manually added to legends:

// Before
// series1, series2, series3, series4
const legend = chart.addLegendBox()
legend.add(series1)
legend.add(series2)

After v8.0, controlling what components should be displayed in legends is configured when creating those components.

// After
const series1 = chart.addLineSeries({ legend: { show: true } })
const series2 = chart.addLineSeries({ legend: { show: true } })
const series3 = chart.addLineSeries({ legend: { show: false } })
const series4 = chart.addLineSeries({ legend: { show: false } })

Legend position/orientation config

Previously legend position (and orientation) was controlled by passing a legend box builder when creating the legend.

// Before
const legend = chart.addLegendBox(LegendBoxBuilders.HorizontalLegendBox)
// If default position wasn't good, then setPosition, setOrigin and setMargin
// methods could be used to position legends where desired.

After v8, the majority of cases of legend positioning should be realized by simply selecting from a preset range of position options:

// After
const chart = lc.ChartXY({
legend: {
position: LegendPosition.RightCenter,
}
})

For more niche use cases, please refer to newly written Legend documentation.

Legend layout changes

Previously, legend was an overlay component that was always drawn above the charts, series, axes, etc. This was often undesirable, as it obstructed part of the data visualization.

After v8, legend defaults to a completely separate area of the chart component. Depending on legend contents, the chart allocates space for the legend, in turn reducing the size of the series area. This means that by default, the legend never obstructs the series area.

Old behavior can be restored by setting renderOnTop to true:

const chart = lc.ChartXY({
legend: {
renderOnTop: true
}
})

Legend title config

// Before
legend
.setTitle('Legend')
.setTitleFont(font => font.setSize(10))
// After
legend.setOptions({
title: 'Legend',
titleFont: { size: 10 }
})

Legend background config

// Before
legend.setBackground((background) => background
.setFillStyle(fill)
.setStrokeStyle(stroke)
)
// After
chart.legend.setOptions({
backgroundVisible: true,
})
// or
chart.legend.setOptions({
backgroundFillStyle: fill,
backgroundStrokeStyle: stroke,
})

Migrating applications with several legends in 1 chart

User managed legends can be freely created with chart.addLegend method. This allows having several legends in 1 chart, and manually controlling where they are and what they show.

LUT legend configurations

// Before
legend.setEntries((entry, component) => {
if (isLUTCheckBox(entry)) {
entry
.setLUTDisplayProportionalSteps(true)
.setLUTLength(100)
.setLUTThickness(10)
}
})
// After
const chart = lc.ChartXY({
legend: {
entries: {
lutDisplayProportionalSteps: true
lutLength: 100,
lutThickness: 10,
},
},
})

Legend.setEntries

Previously setEntries method was used for any configuration of the legend entries. In new API these are replaced with interface LegendEntryOptions which can be specified in many different, flexible ways.

// Before
legend.setEntries((entry) => entry
.setButtonShape(PointShape.Circle)
.setButtonSize({ x: 10, y: 10 })
.setButtonOnFillStyle(fill)
.setText('Text')
.setTextFillStyle(fill)
.setTextFont((font) => font.setSize(10))
)
// After
const chart = lc.ChartXY({
legend: {
entries: {
// These properties apply to all entries of the legend
buttonShape: PointShape.Circle,
buttonSize: 10,
buttonFillStyle: fill,
text: 'Text',
textFillStyle: fill,
textFont: { size: 10 }
}
}
})
const series = chart.addLineSeries({
legend: {
// These properties only apply to entries that represent this particular series
text: 'Temperature'
}
})

Contrary to before, this operation also affects legend entries that are only created afterwards. Previously applications needed to retrigger this kind of functions repeatedly during runtime.

These options can also be modified during runtime by using legend.setOptions or legend.setEntryOptions.

toggleVisibilityOnClick

// Before
legend.add(chart, { toggleVisibilityOnClick: false })
// After
const chart = lc.ChartXY({
legend: {
entries: {
events: {
click: LegendEntryClickBehaviors.doNothing,
},
},
},
})

Legend.setDraggingMode

This method has been removed without a direct replacement. If your application requires draggable legends then there are a number of different approaches (custom interactions, custom legends). Feel free to contact us in this case.

Legend.setAutoDispose

This method has been removed. The new legends have automatic overflow control built-in so it is not needed anymore.

Legacy XY series

Legacy XY series LineSeries, PointSeries, PointLineSeries, AreaSeries, StepSeries and SplineSeries which were deprecated in v6.1.0 have now been removed. Their functionality is replaced by PointLineAreaSeries.

Related backwards incompatible APIs: ChartXY.addLineSeries, addPointSeries, addPointLineSeries, addAreaSeries, addStepSeries, addSplineSeries

These methods still exist after v8.0, however, there is a big functional change under the hood. Instead of creating previous LineSeries for example, a PointLineAreaSeries is created. These series types have different APIs, which means a migration is needed.

The difference between methods addLineSeries, addPointSeries, etc. is just in default styling.

// Before
const lineSeries = chart
.addLineSeries({ dataPattern: { pattern: 'ProgressiveX' } })
.setDataCleaning({ minDataPointCount: 1 })
.add(data)
.addArrayY(dataY)
.addArraysXY(dataX, dataY)
// After
const lineSeries = chart.addLineSeries({
schema: {
xValues: { pattern: 'progressive' }
}
})
.setMaxSampleCount(100_000)
.appendJSON(data)
.appendSamples({ yValues: dataY })
.appendSamples({ xValues: dataX, yValues: dataY })

For more migration references from legacy XY series to newer series API, consider following options:

warning

Some functionalities of previous AreaSeries are not available under PointLineAreaSeries. Namely, bipolar area mode and baseline configuration. If your use case requires these functionalities, please contact us.

PointLineAreaSeries.add, addArrayY, addArrayX, addArraysXY

These methods are removed, and should be replaced with either appendJSON or appendSamples:

// Before
pointLineAreaSeries
.add(data)
.addArrayY(dataY)
.addArraysXY(dataX, dataY)
// After
pointLineAreaSeries
.appendJSON(data)
.appendSamples({ yValues: dataY })
.appendSamples({ xValues: dataX, yValues: dataY })
warning

Contrary to previous add method, using appendJSON will by default store all supplied data properties. If you only want to load specific properties into the data set, then you should whitelist them:

pointLineAreaSeries.appendJSON(data, { whitelist: ['index', 'value'] })

DataSetXY and PointLineAreaSeries rework

v8 introduces some relatively large functional differences for DataSetXY and PointLineAreaSeries. This required changing some of the APIs.

The key functional change:

Previously, DataSetXY was a data storage class for 1 series. It contained a set of X and Y values, and possibly extra values such as individual colors, lookup values, point rotations, etc.

After v8, DataSetXY can store any number of data properties (for example, several channels that may be displayed as Y).

tip

DataSetXY is always used under the hood by PointLineAreaSeries, even if user application doesn't directly utilize DataSetXY.

Below you can find help for migrating from previous API syntax to v8. For actual improvements and new capabilities from this change, please refer to release news or updated series documentation

dataPattern

// Before
const series = chart.addPointLineAreaSeries({ dataPattern: 'ProgressiveX' })
.appendSamples({ xValues, yValues })
// After
const series = chart.addPointLineAreaSeries({
schema: {
xValues: { pattern: 'progressive' },
yValues: { pattern: null }
}
})
.appendSamples({ xValues, yValues })

Conceptual difference here is that data patterns are specified per data property rather than per data set.

appendJSON

// Before
const series = chart.addPointLineAreaSeries({
dataPattern: 'ProgressiveX'
})
series.appendJSON(data, { x: 'index', y: 'value' })
// After
const series = chart.addPointLineAreaSeries({
schema: {
index: { pattern: 'progressive' },
value: { pattern: null }
}
})
series
.appendJSON(data)
.setDataMapping({ x: 'index', y: 'value' })

Conceptual difference here is that data is stored exactly as it is received (rather than as X and Y specifically). A separate configuration (data mapping) is used on series level to control what data property should be X and Y.

warning

Contrary to before, using appendJSON will by default store all supplied data properties. If you want to load only specific properties into the data set, then you should whitelist them:

series.appendJSON(data, { whitelist: ['index', 'value'] })

readBack

PointLineAreaSeries.readBack is unchanged. However, DataSetXY.readBack return type is changed:

// Before
DataSetXY.readBack():
{
xValues: TypedArray
yValues: TypedArray
iSampleFirst: number
lookupValues?: TypedArray
colors?: Uint32Array
ids?: Uint32Array
sizes?: TypedArray
rotations?: TypedArray
}
// After
DataSetXY.readBack():{
data: Record<string, TypedArray>;
iSampleFirst: number;
}

Conceptual difference is that using read back on DataSetXY will return all stored data, rather than only X and Y values.

dataStorage

Data storage configuration is moved from data set option to data property specific option:

// Before
const series = chart.addPointLineAreaSeries({
dataStorage: Float32Array
})
// After
const series = chart.addPointLineAreaSeries({
schema: {
xValues: { storage: Float32Array },
yValues: { storage: Float32Array },
}
})
warning

In above example "xValues" does not strictly refer to X coordinates, but rather a data property with key "xValues". It could just as well be "timestamps", as long as it matches the incoming data.

alterSamplesByID

Replaced by alterSamplesByMatch.

// Before
series.alterSamplesByID([0], { y: 10 })
// After
series.alterSamplesByMatch('id', [0], { y: 10 })

Conceptual difference is that there is no predetermined "ID" data property anymore. Instead, any data property can be used to select samples to alter.

alterSamples

Renamed to alterSamplesStartingFrom.

Schema and data mapping

Both schema and data mapping concepts are new concepts and technically optional (as in you can ignore them, supply data to series and most of the times it will work). However, depending on the use case you may need to understand what they are and how to configure them.

  • Schema defines what data can be stored in a series or data set
  • Data mapping specifies how data properties of the schema should be used

Most importantly, leaving them unconfigured results in console warnings. For more information, please refer to Line series documentation

AxisScrollStrategies.progressive, regressive, fittingStart, fittingEnd

Usage of these previously deprecated APIs should be migrated as below:

❌ Before:

axis.setScrollStrategy(AxisScrollStrategies.progressive)
axis.setScrollStrategy(AxisScrollStrategies.regressive)
axis.setScrollStrategy(AxisScrollStrategies.fittingStart)
axis.setScrollStrategy(AxisScrollStrategies.fittingEnd)

❎ After:

axis.setScrollStrategy(AxisScrollStrategies.scrolling)
axis.setScrollStrategy(AxisScrollStrategies.scrolling({ progressive: false }))
axis.setScrollStrategy(AxisScrollStrategies.fitting({ end: false }))
axis.setScrollStrategy(AxisScrollStrategies.fitting({ start: false }))

Point series borders

In v8, point series are drawn with borders by default. This can be reverted back by setting point stroke style:

pointSeries.setPointStrokeStyle(emptyLine)

This applies to XY and Polar point series.

Cursor point marker style changes

In v8, cursor point marker style was changed to a circle with transparent fill and opaque border. The colors of the point marker automatically adjust according to pointed data.

Previous point marker style (simple cross), can be restored like so:

chart.setCursorDynamicBehavior(undefined).setCursor((cursor) =>
cursor.setPointMarker((pointMarker) =>
pointMarker
.setShape(PointShape.Cross)
.setSize({ x: 7, y: 7 })
.setFillStyle(new SolidFill({ color: ColorRGBA(100, 100, 100) }))
.setStrokeStyle(emptyLine),
),
)